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
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>
305 lines
18 KiB
Markdown
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.
|