From 558c70aa24ee40853cc279d80bdfeda7069df62a Mon Sep 17 00:00:00 2001
From: Nessar
Date: Sat, 17 Oct 2026 12:49:17 +0200
Subject: [PATCH] docs(adr): record Bun 1.4.3 verification results and
decisions
Stable Bun 1.4.3 runs installs, the development server, standalone type
checks, the native test runner and a Bun.serve static server. Native
lint/fmt are canary-only, and Angular's server build needs Node loader
hooks Bun does not apply, so the spec now keeps ESLint/Prettier until a
stable release, builds Angular client-only behind a Bun static server,
converts line coverage from lcov, and replaces the heap cap with a
container memory limit.
Refs #289
Co-Authored-By: Claude Opus 5.5
---
.../0009-bun-runtime-and-native-tooling.md | 8 ++
docs/bun-adoption.md | 74 ++++++++++++++++++-
2 files changed, 80 insertions(+), 2 deletions(-)
diff --git a/docs/adr/0009-bun-runtime-and-native-tooling.md b/docs/adr/0009-bun-runtime-and-native-tooling.md
index 2abfdf1..5aa648f 100644
--- a/docs/adr/0009-bun-runtime-and-native-tooling.md
+++ b/docs/adr/0009-bun-runtime-and-native-tooling.md
@@ -11,6 +11,14 @@ This deliberately accepts Bun-specific tooling and migration work while retainin
Angular's standard compiler and build pipeline, including TypeScript 6 for
Angular compilation alongside `bun check` for standalone TypeScript checking.
+Verification on stable Bun 1.4.3 narrowed this: ESLint and Prettier stay,
+run by Bun, until a stable release ships the native linter and formatter;
+Angular builds a client-only bundle, because its server build relies on Node
+loader hooks Bun does not apply, and a small `Bun.serve` server delivers it,
+giving up `@angular/ssr`; coverage is line-level, converted from lcov to the
+existing HTML and Cobertura reports; and the benchmark's heap cap becomes a
+container memory limit.
+
Existing behavior, tests, lint rules, coverage reporting, and benchmark budgets
must be preserved; incompatibilities must be fixed or reported as blockers before
declaring completion, and a Node.js fallback does not meet the runtime requirement.
diff --git a/docs/bun-adoption.md b/docs/bun-adoption.md
index d0c0c45..ad8662f 100644
--- a/docs/bun-adoption.md
+++ b/docs/bun-adoption.md
@@ -1,7 +1,8 @@
# Bun Runtime and Native Tooling — Development Spec
-Status: local draft synthesized from the agreed individual decisions. Integration
-feasibility remains unverified; ADR 0009 remains proposed.
+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.
## Problem Statement
@@ -104,6 +105,75 @@ The following acceptance criteria define completion:
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. 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