# Bun Runtime and Native Tooling — Development Spec 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. - **ADR 0009** stays `proposed` until the final gate. ## Problem Statement The maintainer wants VacOptimizer to use Bun as its actual JavaScript runtime and adopt its native tooling wherever agreed, even without measurable performance gains. Today Bun installs dependencies, generates the language registry, and runs the deployed server, but development commands can launch Node-based CLIs, CI uses Node containers, and the Docker build explicitly installs Node. The toolchain is also inconsistent: the local Bun installation is 1.3.14, CI pins 1.4.2, and Docker uses a floating Bun image. Merely prefixing commands with Bun does not establish which runtime executes their JavaScript. ## Solution Use Bun for project-owned JavaScript in development, CI, and production. Adopt Bun's native test runner, linter, formatter, standalone type checker, and HTTP server. Retain Angular and its standard compiler and build pipeline, running the JavaScript tooling under Bun. Preserve the application's existing behavior and verification standards. The planner remains client-side: planning horizons, planning rules, leave placements, planning outcomes, plan statistics, and plan explanations retain their current meaning. Browser code and Web Workers continue to run in the user's browser. ## User Stories The following acceptance criteria define completion: 1. **Actual Bun execution.** Development serving and watch mode, production builds, language generation, repository CI scripts, tests, and the production server execute their project-owned JavaScript under Bun, including spawned tools and test fixtures. A Node.js fallback does not satisfy this criterion. 2. **Reproducible setup.** Development instructions, CI, and both Docker stages specify the same exact, verified Bun version. Frozen dependency installation succeeds from a clean checkout without changing the lockfile, and the production image installs only production dependencies. 3. **Native tests with preserved behavior.** All existing functional and Angular component tests execute under `bun test`, preserving assertions, parameterized cases, clock control, global mocks, cleanup, isolation, and real worker integration. Tests cannot be dropped or weakened to make the migration pass. 4. **Preserved coverage.** Functional tests retain coverage reporting and CI artifacts, including useful output when tests fail. Preserve the current production-code coverage scope and exclusion of the frozen engine baseline; report unsupported coverage capabilities or report formats as compatibility gaps rather than silently removing them. 5. **Native linting and formatting.** Bun's native linter and formatter replace ESLint and Prettier, preserving the current rule severities, type-aware checks, unused-name exceptions, formatting options, and Angular-template formatting. CI continues to reject violations. Checks cover the same application, repository-script, and workflow content as before. 6. **Type checking alongside Angular.** `bun check` checks application and test TypeScript. Angular's compiler retains its supported TypeScript 6 dependency and continues to check templates during builds. Type errors still fail the appropriate checks; successful standalone checking does not replace a successful Angular build. 7. **Native HTTP serving.** `Bun.serve` replaces Express, preserving Angular's request-handler integration, configured rendering modes, static assets and cache behavior, configurable port, error handling, and container health check. Development and production routes continue to work, including direct visits to the benchmark page. The current catch-all route uses client rendering; this migration does not enable server rendering for that route. 8. **Existing CI guarantees.** Policy and image-publication regression checks, security scans, production builds, artifact retention, and required checks continue to work. The separate required seven-scenario benchmark preserves its calibration protocol, warm-up, measured repetitions, determinism checks, timeouts, and performance budgets. A runtime change cannot justify weakening these gates. 9. **Honest completion.** Fix incompatible required tooling before declaring the migration complete. If a fix proves infeasible, report the failing behavior, evidence, and remaining blocker. Local success must be distinguished from successful live Gitea checks. ## Implementation Decisions - **Agreed: runtime and native tools.** Bun adoption is a requirement independent of speed gains. Use its native test runner, linter, formatter, standalone type checker, and HTTP server. Compatible Node API imports may remain when executed by Bun; rewriting every import is not required to satisfy the runtime goal. - **Agreed: Angular boundary.** Keep Angular's standard compiler, bundler, development server, and framework build support. Preserve the existing rendering configuration and browser-based planner, consistent with ADR 0002. - **Agreed: TypeScript coexistence.** Keep TypeScript 6 for Angular compilation and use Bun's TypeScript 7-based checker for standalone checking. An Angular upgrade is not required for this arrangement. - **Agreed: tests and server integration.** Adapt the Angular test compilation and setup, mocks, DOM environment, worker harnesses, and subprocess fixtures to the chosen runtime and runner. Use Angular's web request API with Bun's HTTP server, preserving its development/build handler contract and static-file protections. - **Migration reach.** Update package commands, test configuration and typings, generated executable fixtures, server entry and exports, container build and runtime stages, project-owned CI invocations, and development documentation. Remove superseded dependencies and configuration after checking their callers. - **Proposed implementation order.** Verify the Bun release, an Angular production build, representative Angular component tests, and native lint/format/type checks first. Extend that verified setup to the full suite, server, containers, and CI before removing the replaced tools. - **Proposed version.** Bun 1.4.3 is the initial candidate based on the researched native-tool documentation. Select the exact version only after verifying the required capabilities together. ## Verification Results and Decisions (#289) Probes ran on 2026-10-17 against the stable Bun `1.4.3` release (`1.4.3+c6da4a4d3`) from a clean checkout. "Node unavailable" means a `PATH` holding only a `bun` executable plus `/usr/bin:/bin`, with `command -v node` failing. **Verified:** - **Install.** `bun install --frozen-lockfile` succeeds and leaves `bun.lock` unchanged. - **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. #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. - **Type checking.** `bun check -p tsconfig.app.json` and `-p tsconfig.spec.json` match `tsc` 6 on the clean tree and catch injected application and test type errors. Only the Angular build catches template errors, as expected. - **Tests.** `bun test` runs the whole functional suite with a preload that compiles components through Angular's JIT transform with inlined templates, sets up jsdom, TestBed and zoneless change detection, plus a small shim for the Vitest APIs Bun lacks (`stubGlobal`, `unstubAllGlobals`, `waitFor`, `advanceTimersByTimeAsync`, `expect.poll`, `toHaveBeenCalledExactlyOnceWith`, `setSystemTime` ordering and Date-only fake timers). Real-worker specs work with Node unavailable. `--isolate` or `--parallel` is required for per-file isolation. No spec uses snapshots. - **Server.** `Bun.serve` passes route, asset, cache-header, missing-file and path-traversal checks. - **CI scripts.** The policy regression script passes under Bun. The image regression script needs its fake `docker` shebang changed from `node` to `bun`. **Decisions taken after the probes:** - **Linter and formatter.** Native `bun lint` and `bun fmt` exist only in canary builds (`1.4.4-canary.1`), not in stable `1.4.3`. Pin stable `1.4.3` everywhere and keep ESLint and Prettier, executed by Bun, until a stable release ships them. Then replace them in one change. Until then, user story 5 stays open. - **Client-only Angular build and a Bun static server.** Angular's production build fails under Bun whenever a server or SSR entry is configured: route extraction loads the server bundle through Node `module.register` loader hooks, which Bun accepts but never applies. Every route is client-rendered today, so the Angular build drops its server and SSR entries and a small `Bun.serve` server delivers static assets with the same cache behavior and falls back to `index.html`. This replaces the agreed Angular web request handler integration and gives up `@angular/ssr`. Adding server rendering later requires revisiting this decision. - **Coverage.** `bun test` reports text and lcov with line data only. HTML and Cobertura reports are generated from the lcov with the istanbul report libraries. Branch and function coverage are no longer reported; this reduction is accepted. - **Benchmark memory guard.** JavaScriptCore has no `--max-old-space-size` equivalent. The CI benchmark job enforces the limit with a container memory limit instead of a heap-only cap. **Platform constraints, not project commands:** - **Gitea actions.** `actions/checkout@v4` (`node20`) and `actions/upload-artifact@v3` (`node16`) are JavaScript actions, so the job container must still provide `node` for the runner. Project commands must not use it. - **Allowed hosts.** Found while probing: the current Express server answers 400 unless `NG_ALLOWED_HOSTS` is set, and the compose files only set `PORT`. The static Bun server removes this dependency. ## Testing Decisions - Test observable behavior and failure outcomes, rather than configuration text or internal adapter shapes. Reuse the existing behavior-level tests and change harnesses only where the runtime or runner requires it. - **Runtime and installation:** exercise clean frozen installs, development and production commands, and project-owned subprocesses with Node unavailable to those commands. Verify actual execution by Bun, rather than relying on a command's name. Keep any CI-platform Node requirement separate from this check. - **Angular and planning:** use the existing TestBed-based component and service suites as prior art for compilation and DOM behavior. Retain the planning integration, property-based, migration, date-sensitive, and cancellation tests to check planning outcomes, saved inputs, and plan explanations. - **Workers and subprocesses:** preserve the existing real-worker tests for submitted-configuration isolation, outcome and explanation delivery, and worker cleanup. Run policy and publisher regression scripts with Bun, keeping their temporary Git repositories and fake Docker executable so verification remains independent of real registry writes. - **Lint, format, and types:** compare the current checks with the native tools. Use small representative invalid inputs to establish that unused variables, deprecated APIs, formatting violations, application/test type errors, and Angular template errors still fail the intended command. Verify formatting options and representative Angular templates without an unrelated reformat. - **HTTP and containers:** add focused HTTP behavior checks around the native server, since the explored test suite provides no equivalent server coverage. Exercise configured routes, direct navigation, static JavaScript/CSS assets, cache and content-type headers, missing assets, error responses, and traversal protection. Smoke-test the production container and its health check. - **Coverage and benchmark:** verify functional coverage artifacts, including failure-path uploads, and run the existing benchmark outside coverage. Keep the frozen-engine calibration and current budget assertions. Record Bun results as new measurements rather than claiming browser or phone speedups from them. - **CI:** use the existing policy/publisher regression scenarios as prior art for push, pull-request, fork, manual, and release-event behavior. Obtain successful required checks on the final PR revision; live publication and activation validation remain subject to the repository's existing Gitea process. ## Out of Scope - Replacing Angular's build pipeline with Bun's bundler, rewriting the framework, or bypassing Angular's TypeScript support checks. - Moving leave planning to the server, changing planning rules or objectives, altering saved data, or adding accounts, synchronization, or backend services. - Changing configured rendering modes or treating Bun as the browser runtime. - Dropping tests, weakening lint rules or benchmark budgets, or silently using Node to complete otherwise incompatible project commands. - Replacing security scanners, changing registry publication policy, provisioning CI credentials, modifying branch protections or activation settings, creating releases, publishing this spec, or deploying the application. - Rewriting compatible Node API imports solely because their names mention Node. ## Further Notes This spec records the decisions made in the interview. The architecture record 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. Remaining verification (everything else is recorded as verified under Status): - Native lint and format wait for a stable Bun release that ships `bun lint` and `bun fmt` (#290). - Live Gitea validation (#293): the custom job `shell` template, the Bun download in each job, the `images` job with publisher credentials, and the benchmark's timing margin on the runners. - The `images` job keeps Alpine's `nodejs` on `PATH` because the checkout action needs it (a runner requirement); project commands do not use it and no Node fallback for them is agreed. - `src/test-setup.ts` imports Analog's internal `jitTransform` by path; replace it when Angular or Analog exports a JIT transform. Research checked on 2026-10-17: - [Bun runtime selection](https://bun.sh/docs/runtime#--bun) explains why a Bun command can still launch a Node executable. - [Bun 1.4.3](https://bun.sh/blog/bun-v1.4.3), [native linting](https://bun.sh/docs/lint), [formatting](https://bun.sh/docs/format), and [type checking](https://bun.sh/docs/runtime/check) describe the candidate tools. - [Angular build guidance](https://angular.dev/ecosystem/custom-build-pipeline) recommends retaining its standard CLI pipeline. - [Angular's web request engine](https://angular.dev/api/ssr/AngularAppEngine) and [Bun's HTTP server](https://bun.sh/docs/runtime/http/server) describe the original server integration plan, replaced by the `Bun.serve` static server above. - [Angular version compatibility](https://angular.dev/reference/versions) and [compiler roadmap](https://angular.dev/roadmap) establish the current Angular compiler boundary. [Microsoft's coexistence guidance](https://devblogs.microsoft.com/typescript/announcing-typescript-7-0/#typescript-and-embedded-languages) describes standalone TypeScript 7 checks alongside Angular-specific tooling.