diff --git a/.antigravityrules b/.antigravityrules index f41693c..211256f 100644 --- a/.antigravityrules +++ b/.antigravityrules @@ -13,7 +13,7 @@ All AI agents (including Cline, Antigravity, Cursor, and Copilot) must adhere to - If your changes introduce new code or modify existing code in a way that drops the coverage below 80%, you must write appropriate unit tests in `.spec.ts` files to bring it back up. 2. **Code Formatting (Prettier)**: - - All code files must be correctly formatted according to the project's Prettier config before finishing. + - All code files, the CI scripts and the workflow must be correctly formatted according to the project's Prettier config before finishing. - Run the formatter with: ```bash bun run format @@ -30,8 +30,8 @@ All AI agents (including Cline, Antigravity, Cursor, and Copilot) must adhere to - You must execute the local validation commands before completing your task. The Gitea workflow is `.gitea/workflows/ci.yml`; owner activation is tracked in issue #268, and local checks do not satisfy the migration gate. All of the following commands must pass: - **Linting check**: `bun run lint` (or use `bun run lint:fix` to resolve auto-fixable issues) - **Formatting check**: `bun run format:check` - - **TypeScript Type check**: `bunx tsc --noEmit -p tsconfig.app.json` and `bunx tsc --noEmit -p tsconfig.spec.json` + - **TypeScript Type check**: `bun run typecheck` (`bun check` for application and test code; it does not replace the production build, which alone checks Angular templates) - **Unit Tests and Coverage**: generate the language registry, then run the coverage command in rule 1, matching the Gitea functional-test job. - - **CI policy and publication**: `node scripts/ci-policy.test.mjs` and `node scripts/ci-image.test.mjs` + - **CI policy and publication**: `bun scripts/ci-policy.test.mjs` and `bun scripts/ci-image.test.mjs` - **Production Build**: `bun run build -- --configuration production` - The separate performance benchmark is a required CI job with a relative gate: each scenario's budget is a multiple of the frozen engine's two-year calibration measured in the same run. Run it outside coverage with `timeout 600s bun test src/app/benchmark --timeout=600000` when verifying performance; see `docs/benchmark/README.md`. diff --git a/.gitea/workflows/ci.yml b/.gitea/workflows/ci.yml index 266165e..6adf463 100644 --- a/.gitea/workflows/ci.yml +++ b/.gitea/workflows/ci.yml @@ -9,6 +9,11 @@ on: permissions: contents: read +# Gitea's checkout and upload-artifact actions are JavaScript actions that the runner +# executes with the job container's own `node`, so those containers keep Node. Project +# commands never use it: the jobs that run them install the pinned Bun with +# scripts/install-bun.sh and run `run:` steps under a shell whose PATH has Bun but no Node. + concurrency: group: ci-${{ github.event_name }}-${{ github.ref }} cancel-in-progress: ${{ !startsWith(github.ref, 'refs/tags/') }} @@ -18,6 +23,9 @@ jobs: name: policy runs-on: ubuntu-latest container: node:24.21.0 + defaults: + run: + shell: env PATH=/opt/bun/bin:/usr/bin:/bin bash -e {0} outputs: slug: ${{ steps.policy.outputs.slug }} tag: ${{ steps.policy.outputs.tag }} @@ -26,37 +34,45 @@ jobs: with: fetch-depth: 0 persist-credentials: false + - name: Install Bun + run: sh scripts/install-bun.sh - name: Policy regression tests - run: node scripts/ci-policy.test.mjs && node scripts/ci-image.test.mjs + run: bun scripts/ci-policy.test.mjs && bun scripts/ci-image.test.mjs - name: Validate event and commit id: policy - run: node scripts/ci-policy.mjs + run: bun scripts/ci-policy.mjs validate: name: validate runs-on: ubuntu-latest container: node:24.21.0 + defaults: + run: + shell: env PATH=/opt/bun/bin:/usr/bin:/bin bash -e {0} steps: - uses: https://gitea.com/actions/checkout@v4 with: persist-credentials: false - - run: npm install --global bun@1.4.2 + - name: Install Bun + run: sh scripts/install-bun.sh - run: bun install --frozen-lockfile - run: bun run lint - run: bun run format:check - - run: bunx prettier --check 'scripts/ci-*.mjs' '.gitea/workflows/*.yml' - - run: bunx tsc --noEmit -p tsconfig.app.json - - run: bunx tsc --noEmit -p tsconfig.spec.json + - run: bun run typecheck test: name: test runs-on: ubuntu-latest container: node:24.21.0 + defaults: + run: + shell: env PATH=/opt/bun/bin:/usr/bin:/bin bash -e {0} steps: - uses: https://gitea.com/actions/checkout@v4 with: persist-credentials: false - - run: npm install --global bun@1.4.3 + - name: Install Bun + run: sh scripts/install-bun.sh - run: bun install --frozen-lockfile - run: bun scripts/generate-langs.js - name: Functional coverage @@ -79,11 +95,15 @@ jobs: container: image: node:24.21.0 options: --memory=3g + defaults: + run: + shell: env PATH=/opt/bun/bin:/usr/bin:/bin bash -e {0} steps: - uses: https://gitea.com/actions/checkout@v4 with: persist-credentials: false - - run: npm install --global bun@1.4.3 + - name: Install Bun + run: sh scripts/install-bun.sh - run: bun install --frozen-lockfile - run: bun scripts/generate-langs.js - name: Benchmark protocol with relative gate @@ -93,11 +113,15 @@ jobs: name: build runs-on: ubuntu-latest container: node:24.21.0 + defaults: + run: + shell: env PATH=/opt/bun/bin:/usr/bin:/bin bash -e {0} steps: - uses: https://gitea.com/actions/checkout@v4 with: persist-credentials: false - - run: npm install --global bun@1.4.2 + - name: Install Bun + run: sh scripts/install-bun.sh - run: bun install --frozen-lockfile - run: bun run build -- --configuration production - uses: https://gitea.com/actions/upload-artifact@v3 @@ -176,17 +200,21 @@ jobs: run: shell: sh steps: - - name: Install checkout and policy runtimes - run: apk add --no-cache nodejs git + # Alpine's nodejs package is for the checkout action only (it shares /usr/bin + # with git); Bun needs libgcc and libstdc++ and the installer needs curl. + - name: Install checkout runtime and tools + run: apk add --no-cache nodejs git curl libgcc libstdc++ - uses: https://gitea.com/actions/checkout@v4 with: fetch-depth: 0 persist-credentials: false + - name: Install Bun + run: sh scripts/install-bun.sh - name: Build branch images or promote a release env: REGISTRY_USERNAME: ${{ secrets.REGISTRY_USERNAME }} REGISTRY_PASSWORD: ${{ secrets.REGISTRY_PASSWORD }} - run: node scripts/ci-image.mjs + run: /opt/bun/bin/bun scripts/ci-image.mjs required: name: required diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..55a4009 --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,3 @@ +{ + "npm.packageManager": "bun" +} diff --git a/Dockerfile b/Dockerfile index 44d1795..b0381db 100644 --- a/Dockerfile +++ b/Dockerfile @@ -8,8 +8,8 @@ RUN bun install --frozen-lockfile COPY . . -# --bun makes Angular's Node-shebang CLI run on Bun instead of a Node on PATH -RUN bun --bun run build +# bunfig.toml makes the Node-shebang Angular CLI run on Bun; the image has no Node +RUN bun run build # Stage 2: Production image FROM oven/bun:1.4.3-alpine@sha256:629e17411f1f129dbec3af78d5af9c9f2a937435c80349437206c6b0b7422373 AS production diff --git a/README.md b/README.md index 699b20a..7fb115b 100644 --- a/README.md +++ b/README.md @@ -163,7 +163,7 @@ Incomplete outcomes explain the retained plan and unfinished phases; conflicts i - `date-holidays` for the French public calendar - Bun for dependency management and project scripts - `bun test` with jsdom and Angular TestBed (JIT) -- ESLint and Prettier +- ESLint and Prettier, run by Bun, and `bun check` for standalone type checking - Client-only Angular build served by a small `Bun.serve` static server (`src/server.ts`) - Gitea repository hosting and a multi-stage Docker image @@ -198,9 +198,10 @@ CONTEXT.md # Canonical domain language and rules ## Local development -Prerequisite: [Bun](https://bun.sh/). Dependency versions are locked in `bun.lock`. +Prerequisite: [Bun](https://bun.sh/) 1.4.3, the exact version pinned by `packageManager` in `package.json` and used by CI and the Docker image. Node.js is not needed. Dependency versions are locked in `bun.lock`. ```bash +curl -fsSL https://bun.sh/install | bash -s "bun-v1.4.3" bun install --frozen-lockfile bun start ``` @@ -212,17 +213,21 @@ Open . The development server reloads when source files c | Command | Purpose | | ------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------- | | `bun start` | Generate the i18n registry and start the development server. | +| `bun run watch` | Rebuild the development bundle when source files change. | | `bun run test` | Run the unit, integration and benchmark suites once. | | `bun test --isolate --path-ignore-patterns='**/benchmark/**'` | Run the functional suite only. | | `bun test src/app/benchmark --timeout=600000` | Run the planning benchmark (no coverage). | | `bun test --config=bunfig.coverage.toml --isolate --path-ignore-patterns='**/benchmark/**' && bun scripts/coverage-reports.mjs` | Functional coverage: text, `lcov.info`, HTML and Cobertura in `coverage/`. Line coverage only; frozen engine excluded. | -| `bun run lint` | Run ESLint. | -| `bun run format:check` | Check Prettier formatting. | -| `bunx tsc --noEmit -p tsconfig.app.json` | Type-check application code. | -| `bunx tsc --noEmit -p tsconfig.spec.json` | Type-check tests. | -| `bun --bun run build` | Create the optimized browser bundle and the static server bundle in `dist/vac-optimizer/`. | +| `bun run lint` | Run ESLint (`bun run lint:fix` applies its fixes). | +| `bun run format:check` | Check Prettier formatting of the application, the CI scripts and the workflow (`bun run format` writes it). | +| `bun run typecheck` | Type-check application and test code with `bun check`. It does not check Angular templates; only the build does. | +| `bun run build` | Create the optimized browser bundle and the static server bundle in `dist/vac-optimizer/`. | | `bun run serve` | Serve a completed production build on `PORT` (default 4000). | +Project scripts run Node-shebang tools (`ng`, `eslint`, `prettier`) on Bun even when Node is installed: `bunfig.toml` sets `[run] bun = true`, which is the `--bun` flag for every `bun run`. Start them through the scripts above. A bare `bunx` or `bun x` uses Node when it is on `PATH`; for a one-off tool, use `bun --bun x `. + +Angular compiles with TypeScript 6. `bun check` is Bun's TypeScript 7 checker for standalone type errors in application and test code. Neither replaces the other: only `bun run build` (Angular's compiler) checks templates, so a passing `typecheck` does not replace the production build. ESLint and Prettier stay until a stable Bun release ships its native linter and formatter ([bun-adoption](docs/bun-adoption.md#status)). + ## Performance benchmark Build or start the application, then open `/benchmark`. The isolated harness covers a representative year, typed obligations, chained earned entitlements, a deliberate conflict, a loaded year, and two- and three-year horizons with dated annual balances. It reports preparation time, planning latency, planning-work time, total wall time through explanation delivery, UI timer delay, available main-thread heap measurements, outcome, last completed stage, placement count, and determinism. @@ -244,7 +249,9 @@ The server answers `GET` and `HEAD` only (other methods get 405). It serves file [`.gitea/workflows/ci.yml`](.gitea/workflows/ci.yml) defines Gitea Actions validation, functional coverage, production builds, security scans, and Docker image publication. [Issue #268](https://gitea.nessar.fr/nessar/vac-optimizer/issues/268) tracks activation. Actions is enabled with owner approval; publisher credentials and live workflow validation are still pending. The [activation guide](docs/gitea-actions.md) records runner, credential, permission, and merge-gate requirements. -The workflow runs on branch/tag pushes, PR opening/reopening/synchronization/edits, and manual dispatch. Manual runs validate without publishing images. Required jobs cover policy regression tests, GitFlow and Conventional Commits, ESLint, Prettier, application/test type checks, Bun coverage, the production build, Semgrep SAST, and Trivy dependency/secret scans. Coverage, build output, and SARIF reports are retained for seven days. +The workflow runs on branch/tag pushes, PR opening/reopening/synchronization/edits, and manual dispatch. Manual runs validate without publishing images. Required jobs cover policy regression tests, GitFlow and Conventional Commits, ESLint, Prettier, `bun check` type checks, Bun coverage, the production build, Semgrep SAST, and Trivy dependency/secret scans. Coverage, build output, and SARIF reports are retained for seven days. + +Jobs that run project commands install Bun 1.4.3 with `scripts/install-bun.sh` (a checksum-verified download, no npm) and run their steps with a `PATH` that holds Bun but no Node. The job containers keep Node only for Gitea's checkout and artifact actions, which are JavaScript actions the runner executes itself; see the [activation guide](docs/gitea-actions.md#runners-and-permissions). Functional tests run with `bun test --isolate`; coverage is line-level only (Bun reports no branch or function data). The separate required benchmark job runs without coverage in a container limited to 3 GiB (JavaScriptCore has no heap-size flag, so this replaces the former 2 GiB Node heap cap) and keeps its 600-second hang guard, one warm-up and two measured repetitions of all seven scenarios. [#78](https://gitea.nessar.fr/nessar/vac-optimizer/issues/78) restores this gate after a complete local pass in 298.6 seconds; see [the measurements and hardware limits](docs/benchmark/issue-78.md). Benchmark success is required for image publication and the aggregate `required` job. The narrow local timing margin still needs confirmation on the CI runner. @@ -283,7 +290,7 @@ The Gitea repository policy requires `develop` as the default branch and **Merge Only `release/*` and `hotfix/*` PRs may target `main`; working PRs target `develop`. CI validates these rules and checks working-branch rebases against the fetched integration head. If `develop` advances after validation, rebase and rerun the PR checks before merging. -Run `node scripts/ci-policy.test.mjs` and `node scripts/ci-image.test.mjs` locally to verify GitFlow event handling and image publication without writing to a registry. The adapter reads Gitea webhook data through the runner's `GITHUB_*` compatibility variables. Local checks do not satisfy the migration gate. +Run `bun scripts/ci-policy.test.mjs` and `bun scripts/ci-image.test.mjs` locally to verify GitFlow event handling and image publication without writing to a registry. The adapter reads Gitea webhook data through the runner's `GITHUB_*` compatibility variables. Local checks do not satisfy the migration gate. ## Domain and design references diff --git a/bunfig.toml b/bunfig.toml index 72c18b8..9c0b830 100644 --- a/bunfig.toml +++ b/bunfig.toml @@ -1,3 +1,7 @@ +[run] +# Node-shebang tools (ng, eslint, prettier) run on Bun even when Node is on PATH. +bun = true + [test] preload = ["./src/test-setup.ts"] root = "src" diff --git a/docs/bun-adoption.md b/docs/bun-adoption.md index ad8662f..e1ff60e 100644 --- a/docs/bun-adoption.md +++ b/docs/bun-adoption.md @@ -4,6 +4,49 @@ Status: the agreed decisions are amended by the verification results in #289, recorded below. ADR 0009 remains proposed until the migration passes its final gate. +## Status + +Implementation status after #290 and the local parts of #293. "Verified locally" +means a command ran with Node unavailable (or a tripwire `node` that logs every +start); it is not a result from the live Gitea workflow. + +**Done, verified locally:** + +- **One pinned release.** Bun 1.4.3 is pinned by `packageManager`, the + checksum-verified `scripts/install-bun.sh` that every CI job running project + commands uses (no npm), the + `@types/bun` version and both Docker stages. The installer refuses a + `package.json` that names another version. Frozen installs leave `bun.lock` + unchanged. +- **Node-shebang tools on Bun.** `bunfig.toml` sets `[run] bun = true`, so + `start`, `build`, `watch`, `lint`, `lint:fix`, `format` and `format:check` run + `ng`, `eslint` and `prettier` on Bun even when Node is on `PATH`. CI also runs + project steps with a `PATH` that has Bun and no Node. +- **Standalone type checking.** `bun run typecheck` runs `bun check` on the + application and test projects. The production build remains the only check of + Angular templates. +- **Tests, coverage, benchmark, server, container** (#291, #292), and the policy + and image-publisher scripts with their regression tests, under Bun. The fake + `docker` fixture uses a `bun` shebang. + +**Still open:** + +- **Native lint and format (#290, user story 5).** Replace ESLint and Prettier in + one change when a stable Bun ships `bun lint` and `bun fmt`; today they exist + only in canary builds. Until then user story 5 and the matching #290 criteria + stay open. +- **Live Gitea validation (#293).** The custom job `shell` template, the Bun + installer, JavaScript actions beside Node-free project steps, the benchmark's + timing margin and the required checks on the final PR revision have not run on + the live runner. The `images` job still has Alpine's `nodejs` on `PATH` for the + checkout action, which the project command does not use. These and the Gitea + activation steps remain unverified until a live pull-request run. +- **Intermittent spec.** `real planning worker cancellation > preserves balances +and provenance after a valid checkpoint` failed in one of three full coverage + runs on the development laptop (it cancels from a stage's progress callback and + asserts that the stage did not complete); the cause is not yet investigated. +- **ADR 0009** stays `proposed` until the final gate. + ## Problem Statement The maintainer wants VacOptimizer to use Bun as its actual JavaScript runtime and @@ -118,7 +161,8 @@ executable plus `/usr/bin:/bin`, with `command -v node` failing. - **Runtime.** Package binaries with a `#!/usr/bin/env node` shebang (`ng`, `eslint`, `prettier`, `tsc`) run on Bun only with `--bun`. Without it they use Node when Node is on `PATH`, so every such command must pass `--bun` - explicitly. Spawned build children are native executables (esbuild, + explicitly. #290 sets it once for every `bun run` script with `[run] bun = true` + in `bunfig.toml`. Spawned build children are native executables (esbuild, dart-sass); Angular's workers are threads of the Bun process. - **Development server.** `bun --bun x ng serve` serves the application and direct navigation to `/benchmark` with Node unavailable. @@ -228,7 +272,7 @@ executable plus `/usr/bin:/bin`, with `command -v node` failing. ## Further Notes This spec records the decisions made in the interview. The architecture record -remains proposed, and no migration implementation or compatibility proof exists. +remains proposed; the Status section lists what is implemented. The glossary receives no new terms because the choices concern general tooling, rather than new leave-planning concepts. diff --git a/docs/gitea-actions.md b/docs/gitea-actions.md index 30c35b9..d031482 100644 --- a/docs/gitea-actions.md +++ b/docs/gitea-actions.md @@ -18,14 +18,14 @@ configuration; the Compose files remain manual deployment options. | Job | Behavior | | ----------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| `policy` | Tests the policy and publisher, then validates the checked-out commit, GitFlow, PR title, commits, and stable release version. | -| `validate` | Runs lint, formatting, and application/test type checks. | +| `policy` | Tests the policy and publisher with Bun, then validates the checked-out commit, GitFlow, PR title, commits, and stable release version. | +| `validate` | Runs ESLint, Prettier (application, `scripts/ci-*.mjs` and workflow files) and `bun check` type checks of application and test code, all on Bun with the frozen lockfile. | | `test` | Runs functional coverage with `bun test --isolate` (line coverage only), converts it to HTML and Cobertura, and uploads coverage for seven days even on failure. | | `benchmark` | Required seven-scenario protocol (one warm-up, two measured repetitions) outside coverage in a container limited to 3 GiB. It first times the frozen engine on `two years` (best of three) and fails any scenario whose mean planning time exceeds its multiplier times that calibration; the 600-second timeout is only a hang guard. | -| `build` | Builds browser/server production bundles and retains `dist` for seven days. | -| `security` | Runs Semgrep JavaScript/TypeScript SAST and Trivy dependency/secret scans; retains SARIF reports for seven days. Scanner execution must succeed; findings remain reports rather than a new severity gate. | -| `images` | Publishes branch/SHA images after required validation, including the benchmark, or promotes an existing SHA image for a stable tag. Runs only for pushes and same-repository PRs. | -| `required` | Fails on failed, cancelled, or skipped required validation, including the benchmark. Requires image publication when expected; accepts its intentional skip for fork PRs and manual dispatch. | +| `build` | Builds the client-only Angular production bundle (the only check of Angular templates; `bun check` does not replace it) and the Bun static-server bundle, and retains `dist` for seven days. | +| `security` | Runs Semgrep JavaScript/TypeScript SAST and Trivy dependency/secret scans; retains SARIF reports for seven days. Scanner execution must succeed; findings remain reports rather than a new severity gate. The scanners are not project JavaScript, so this job installs no Bun. | +| `images` | Publishes branch/SHA images after required validation, including the benchmark, or promotes an existing SHA image for a stable tag, running the publisher with Bun. Runs only for pushes and same-repository PRs. | +| `required` | Fails on failed, cancelled, or skipped required validation, including the benchmark. Requires image publication when expected; accepts its intentional skip for fork PRs and manual dispatch. It is shell only and needs no Bun. | The benchmark runs on both push and pull_request events: the push run gates image publication (`images.needs` includes `benchmark`) and the `required` aggregate. @@ -41,7 +41,7 @@ branch slug; stable promotions share one group and are not cancelled in progress ## Runners and permissions -Validation uses Node 24.21.0 and Bun 1.4.2 with the frozen lockfile; the test and benchmark jobs use Bun 1.4.3. The image +Every job that runs project commands uses Bun 1.4.3 with the frozen lockfile (see [Bun toolchain and Node](#bun-toolchain-and-node)). Job containers stay on Node 24.21.0. The image job uses Docker 29.8.2 and its matching Docker-in-Docker service. Scanner versions are Semgrep 1.180.0 and Trivy 0.75.0; Trivy downloads are checksum-checked. Trivy downloads its vulnerability database from Aqua's public ECR mirror because @@ -72,6 +72,42 @@ Keep secret values in Gitea's Actions secret store. The configured tea account's administrator token is not a CI publishing credential. No SSH or deployment secret is required by this workflow. +## Bun toolchain and Node + +Project commands run on Bun 1.4.3 only. `package.json` (`packageManager`), +`@types/bun`, both Docker stages and `scripts/install-bun.sh` carry the same +version. To upgrade, change all of them together and replace the installer's +four SHA-256 values with those of the new release's `SHASUMS256.txt`; the +installer refuses a `package.json` that names a different version, so a bump of +only one place fails CI instead of drifting. + +- **Install.** `policy`, `validate`, `test`, `benchmark`, `build` and `images` + run `sh scripts/install-bun.sh`. It downloads the release zip for the + runner's architecture (x64 or aarch64) and libc (glibc, or musl on Alpine), + verifies its pinned SHA-256, and installs `/opt/bun/bin/bun`. It uses no npm. +- **Node-free project steps.** The five Debian jobs set + `defaults.run.shell: env PATH=/opt/bun/bin:/usr/bin:/bin bash -e {0}`. Every + `run:` step then sees Bun but not `/usr/local/bin`, where the `node` image keeps + `node`, `npm` and `yarn`. `bun run` additionally runs Node-shebang tools on Bun + through `bunfig.toml`. The security scanners are not project JavaScript, so + `security` has no Bun and no custom shell. +- **Why the containers keep Node.** `actions/checkout@v4` (`node20`) and + `actions/upload-artifact@v3` (`node16`) are JavaScript actions. The runner + starts them with the container's `node`; the custom shell does not affect + `uses:` steps. This is a platform requirement, not permission to run project + commands on Node. +- **`images` exception.** Alpine's `nodejs` package installs `node` into + `/usr/bin`, beside `git`, and checkout's post step needs it, so `PATH` cannot + hide it there. The project command is `/opt/bun/bin/bun scripts/ci-image.mjs`; + it starts only `process.execPath`, `docker` and `git`. Hiding Node there needs + an infrastructure decision (for example a runner image that ships Node outside + `PATH`), which this conversion does not make. +- **Needs live validation.** The custom shell template is a GitHub Actions + feature that Gitea's runner implements. A local run of the real engine + (`gitea-runner exec` v5.1.0, with the job containers above) executed it, but + only a live Gitea run confirms the instance's runner version, the checkout + action and the privileged Docker-in-Docker `images` job. + ## Images and releases Images use the [Gitea container registry](https://docs.gitea.com/usage/packages/container/) diff --git a/docs/planner-cancellation.md b/docs/planner-cancellation.md index f65474a..ad85c0c 100644 --- a/docs/planner-cancellation.md +++ b/docs/planner-cancellation.md @@ -32,19 +32,18 @@ If Cancel is accepted just before a queued natural result reaches the facade, th ## Verification -Run the focused checks with the project's installed Bun and Node: +Run the focused checks with the pinned Bun (Node is not needed): ```sh bun test --isolate src/app/services/leave-planner-cancellation.spec.ts bun test --isolate src/app/services/leave-planner.worker.spec.ts bun test --isolate src/app/components/calendar-year/calendar-year-planning.spec.ts -bunx tsc --noEmit -p tsconfig.app.json -bunx tsc --noEmit -p tsconfig.spec.json +bun run typecheck ``` The public cancellation tests cover immediate cancellation, a stable partial outcome despite late completion or artifact error, distinct worker failure and replacement (including non-browser explanation metadata), and cancellation after settlement. Core regressions verify retained validity, provenance, earned chronology and the best available partial plan through the existing checkpoint seam. The CalendarYear tests cover keyboard activation, translated statuses with and without an incumbent, focus, no implicit recalculation, explicit recalculation, and obsolete explanation delivery. -The real-worker suite bundles the production worker source with Bun and runs it on a separate Node worker thread. Its small host adapter maps browser worker messages to the thread's message port; the planner, CPU work, cancellation handling, and outcome and explanation delivery protocol are real. It checks interruption after preparation has started, before the first obligation solution, during active obligation CPU work after a solution exists, and after a completed checkpoint. It also compares an uncancelled outcome with the synchronous adapter's deterministic result. These checks complement the controlled worker boundary tests. +The real-worker suite bundles the production worker source with Bun and runs it on a separate `node:worker_threads` thread of the Bun process. Its small host adapter maps browser worker messages to the thread's message port; the planner, CPU work, cancellation handling, and outcome and explanation delivery protocol are real. It checks interruption after preparation has started, before the first obligation solution, during active obligation CPU work after a solution exists, and after a completed checkpoint. It also compares an uncancelled outcome with the synchronous adapter's deterministic result. These checks complement the controlled worker boundary tests. For a browser check, serve the production browser build and use native Chromium Web Workers with a saved annual horizon. Check cancellation during calendar preparation, a broad obligation search, and a quality pass. Confirm one planning request per Calculate, no new request on Cancel, the appropriate translated status, joint rule satisfaction and balance accounting for an incumbent, and stable explanation delivery after the outcome. Also check the Cancel control with the keyboard, at 375px width, and in light and dark themes. diff --git a/package.json b/package.json index 20dc7e7..a24178b 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,7 @@ { "name": "vac-optimizer", "version": "1.5.0", + "packageManager": "bun@1.4.3", "scripts": { "ng": "ng", "start": "bun scripts/generate-langs.js && ng serve", @@ -9,10 +10,11 @@ "watch": "bun scripts/generate-langs.js && ng build --watch --configuration development", "test": "bun scripts/generate-langs.js && bun test --isolate", "serve": "bun dist/vac-optimizer/server/server.js", + "typecheck": "bun scripts/generate-langs.js && bun check -p tsconfig.app.json && bun check -p tsconfig.spec.json", "lint": "bun scripts/generate-langs.js && eslint \"src/**/*.ts\"", "lint:fix": "bun scripts/generate-langs.js && eslint \"src/**/*.ts\" --fix", - "format": "bun scripts/generate-langs.js && prettier --write \"src/**/*.{ts,html,scss,css,json}\"", - "format:check": "bun scripts/generate-langs.js && prettier --check \"src/**/*.{ts,html,scss,css,json}\"" + "format": "bun scripts/generate-langs.js && prettier --write \"src/**/*.{ts,html,scss,css,json}\" \"scripts/ci-*.mjs\" \".gitea/workflows/*.yml\"", + "format:check": "bun scripts/generate-langs.js && prettier --check \"src/**/*.{ts,html,scss,css,json}\" \"scripts/ci-*.mjs\" \".gitea/workflows/*.yml\"" }, "prettier": { "printWidth": 100, diff --git a/scripts/ci-image.test.mjs b/scripts/ci-image.test.mjs index ae0a444..d014242 100644 --- a/scripts/ci-image.test.mjs +++ b/scripts/ci-image.test.mjs @@ -70,7 +70,7 @@ try { const docker = join(bin, 'docker'); writeFileSync( docker, - `#!/usr/bin/env node + `#!/usr/bin/env bun import { appendFileSync, readFileSync } from 'node:fs'; const args = process.argv.slice(2); const input = args[0] === 'login' ? readFileSync(0, 'utf8') : ''; diff --git a/scripts/install-bun.sh b/scripts/install-bun.sh new file mode 100755 index 0000000..39d129f --- /dev/null +++ b/scripts/install-bun.sh @@ -0,0 +1,41 @@ +#!/bin/sh +# Installs the pinned Bun release to /opt/bun/bin without npm, after verifying its SHA-256. +# Needs curl, unzip and sha256sum. Bun upgrades change the version, package.json and every checksum together. +set -eu + +version=1.4.3 +dest=${BUN_INSTALL_DIR:-/opt/bun} + +grep -q "\"packageManager\": \"bun@${version}\"" package.json || { + echo "package.json packageManager must be bun@${version}." >&2 + exit 1 +} + +case "$(uname -m)" in +x86_64) arch=x64 ;; +aarch64 | arm64) arch=aarch64 ;; +*) + echo "Unsupported Bun architecture: $(uname -m)" >&2 + exit 1 + ;; +esac +libc= +if [ -e /etc/alpine-release ]; then libc=-musl; fi +asset="bun-linux-${arch}${libc}" +case "$asset" in +bun-linux-x64) sha256=3e730528d89775f03f87f05c06787c4df4099d0a37f3f252e7b829cb23816e18 ;; +bun-linux-x64-musl) sha256=6e9ea98554ea0d05efda5840ae292618aaa3adefd66b0853520c5c86a37eac9c ;; +bun-linux-aarch64) sha256=efa9813da5ed72423bf847f916e8d2c47c0d776add972354026a75e10da9aa21 ;; +bun-linux-aarch64-musl) sha256=8de8a0003cc2707bb555887e3d450e24e1edf3f605f491393e89ed61a8853420 ;; +esac + +work=$(mktemp -d) +trap 'rm -rf "$work"' EXIT +curl --fail --location --silent --show-error \ + "https://github.com/oven-sh/bun/releases/download/bun-v${version}/${asset}.zip" \ + --output "$work/bun.zip" +echo "${sha256} ${work}/bun.zip" | sha256sum -c - +unzip -q "$work/bun.zip" -d "$work" +mkdir -p "${dest}/bin" +install -m 0755 "${work}/${asset}/bun" "${dest}/bin/bun" +"${dest}/bin/bun" --version