Files
vac-optimizer/docs/bun-adoption.md
T
nessarandClaude Opus 5.5 282d9c6787
CI / policy (push) Successful in 7s
CI / validate (push) Successful in 41s
CI / build (push) Successful in 36s
CI / policy (pull_request) Successful in 10s
CI / validate (pull_request) Successful in 32s
CI / security (push) Successful in 1m3s
CI / test (push) Successful in 3m3s
CI / build (pull_request) Successful in 23s
CI / security (pull_request) Successful in 57s
CI / test (pull_request) Successful in 3m25s
CI / benchmark (push) Successful in 5m19s
CI / benchmark (pull_request) Successful in 4m48s
CI / images (push) Successful in 1m20s
CI / required (push) Successful in 3s
CI / images (pull_request) Successful in 54s
CI / required (pull_request) Successful in 1s
fix: report line-only coverage honestly and tighten Bun test helpers
The generated HTML and Cobertura reports no longer show unmeasured
branch and function coverage as 100%. Poll and waitFor deadlines use
performance.now so frozen dates cannot make them loop forever, the
malformed-configuration test covers undefined again next to the empty
array, compose health checks honor PORT, and the migration spec lists
only what is still open.

Refs #291, #292, #293

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-17 16:10:29 +02:00

305 lines
18 KiB
Markdown

# 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.