From 05a63dac067eafef993836435bc45b4ec664e1cd Mon Sep 17 00:00:00 2001 From: ebowwa Date: Mon, 17 Aug 2026 09:18:01 -0700 Subject: [PATCH 1/6] feat(bun): unlock Phase C selective plugin reload via bun#39426 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Our upstream PR (a file:// URL's query is part of the module key) is the missing public-API primitive for per-module reload on Bun — the capability @cordisjs/plugin-hmr gets on Node from --expose-internals internals. - tests/bun/fixtures/selective-reload-driver.ts + selective-reload.spec.ts: capability-gated spec proving the full swap contract — fresh module instance per ?gen=N, old fiber disposed only after the new applies, root effects survive unduplicated, single disposal at shutdown. Probes the runner binary (BUN_QUERY_BUSTING_BIN or a local .upstream/bun debug build); stock Bun skips cleanly. - repros/selective-reload.ts: manual narrative version. - docs: Phase C section rewritten from 'deferred' to 'primitive done'; suite now 69 tests (68 + the gated one); Phase 10 status recorded. Verified: 69/69 with the bun#39426 debug build present; the full Cordis suite also runs green on that build; skip path exercised on stock Bun. --- docs/BUN_COMPATIBILITY.md | 49 ++++++-- docs/BUN_PORT_STATUS.md | 29 +++++ tests/bun/fixtures/selective-reload-driver.ts | 50 ++++++++ tests/bun/repros/README.md | 17 +++ tests/bun/repros/selective-reload.ts | 56 +++++++++ tests/bun/selective-reload.spec.ts | 119 ++++++++++++++++++ 6 files changed, 311 insertions(+), 9 deletions(-) create mode 100644 tests/bun/fixtures/selective-reload-driver.ts create mode 100644 tests/bun/repros/selective-reload.ts create mode 100644 tests/bun/selective-reload.spec.ts diff --git a/docs/BUN_COMPATIBILITY.md b/docs/BUN_COMPATIBILITY.md index 03759742..6715513f 100644 --- a/docs/BUN_COMPATIBILITY.md +++ b/docs/BUN_COMPATIBILITY.md @@ -6,10 +6,10 @@ classified below. - Verified Bun release: **1.3.14** (revision `1.3.14+0d9b296af`, macOS arm64) - Verified Node releases: v26.4.0 (locally), v24/v26 (upstream CI unchanged) -- Behavioral proof: `bun test tests/bun` — 68 tests across 13 spec files - covering the scenarios listed in "What is verified" below (3 of them are - gated on the oven-sh/bun#32856 PR build and skip cleanly when it is not - installed). +- Behavioral proof: `bun test tests/bun` — 69 tests across 14 spec files + covering the scenarios listed in "What is verified" below (3 gated on the + oven-sh/bun#32856 PR build, 1 gated on a bun#39426-capable build; all 4 + skip cleanly when their binary is absent). - Performance proof: see **`docs/BUN_BENCH.md`** — fork ≡ upstream under Node (control), and Bun is faster on every Cordis operation (up to ~5x plugin lifecycle, ~4x config boot, ~80x TS module eval). @@ -47,7 +47,8 @@ Notes on the matrix: ## What is verified (behavioral, under `bun test tests/bun`) -`tests/bun/` — 68 tests (58 core + 7 browser-export + 3 PR-build-gated): +`tests/bun/` — 69 tests (58 core + 7 browser-export + 3 #32856-gated + 1 +#39426-gated): - **Plugins**: function / object / class plugins, config passing, invalid plugins rejected, nested plugin trees, idempotent root dispose, @@ -115,10 +116,11 @@ $ node --expose-internals --import tsx --import @cordisjs/unyaml \ node_modules/yakumo/lib/cli.js tsc # exit 0 $ bun test tests/bun - 68 pass / 0 fail / 248 expect() calls # 58 core + 7 browser-export on - # stock Bun + 3 PR-gated hot tests - # when the bun#32856 build is - # installed (they skip otherwise) + 69 pass / 0 fail / 262 expect() calls # 58 core + 7 browser-export on + # stock Bun; +3 #32856-gated and + # +1 #39426-gated when capable + # builds are installed (skip + # otherwise) $ HOME=/tmp/no-such-home bun test tests/bun/hot.spec.ts # PR binary hidden 0 pass / 3 skip / 0 fail @@ -225,6 +227,35 @@ Therefore: worth building if whole-process reload proves inadequate in practice — per the port's success criteria it is optional. +## Selective HMR on Bun (Phase C, unblocked) + +Selective per-plugin reload — the capability `@cordisjs/plugin-hmr` gets on +Node from `--expose-internals` module-loader internals — is **now possible +on Bun via public APIs**, using the primitive fixed by +[oven-sh/bun#39426](https://github.com/oven-sh/bun/pull/39426) +(a `file://` URL's query is part of the module key): + +```ts +// fresh module instance per generation, in-process, public API only: +const mod = await import(`${pathToFileURL(pluginPath).href}?gen=${n}`) +const fiber = await root.plugin(mod) // activate under the root +await previous.dispose() // graceful swap: disposers run, + // timers stop, root keeps running +``` + +Proof: `tests/bun/selective-reload.spec.ts` (+ its driver fixture) asserts +the full contract — fresh instance per generation, old fiber disposed only +after the new one applies, root effects survive unduplicated, single +disposal at shutdown. It is **capability-gated** (probes the runner binary; +skips on stock Bun) and passes against a local build of bun#39426 +(`bun test tests/bun` = 69/69 with the build present, 68 + skip without). +Manual narrative version: `tests/bun/repros/selective-reload.ts`. + +What remains for a production `--hot`-class selective HMR service (still +deferred): file watching mapped to per-module generations, config/state +preservation across swaps, rollback on failed activation. The module-swap +primitive itself is done. + ## Upstream integration: oven-sh/bun#32856 [PR #32856](https://github.com/oven-sh/bun/pull/32856) ("Implement diff --git a/docs/BUN_PORT_STATUS.md b/docs/BUN_PORT_STATUS.md index ecdbd31d..2b9e4d94 100644 --- a/docs/BUN_PORT_STATUS.md +++ b/docs/BUN_PORT_STATUS.md @@ -184,6 +184,35 @@ Owner-selected follow-up (upgrades a 🟡 matrix cell to ✅): condition.) - Results: `bun test tests/bun` 68/68 (61 + 7 new, all stock-Bun). +### Phase 10 — Phase C selective HMR unblocked by our own PR (DONE) + +[oven-sh/bun#39426](https://github.com/oven-sh/bun/pull/39426) ("keep a +file:// URL's query in module keys", fixing +[#21346](https://github.com/oven-sh/bun/issues/21346)) is an upstream PR +authored from this port's upstream-contribution effort; it makes +`import(fileURL + "?v=N")` bypass the module cache — the public-API +primitive per-module reload needs on Bun. + +Owner asked "can we use it with our cordis?" — answered at three levels: + +1. **Drop-in compatibility**: the full Cordis suite runs green on the + bun#39426 debug build — 68/68 (all stock tests, incl. the 3 #32856-gated + ones skipped for lack of that binary's alias). +2. **The previously-impossible use case**: selective plugin reload now + works in-process on public APIs — `import(fileURL + "?gen=N")` for a + fresh instance + `root.plugin(mod)` + `previousFiber.dispose()` for a + graceful swap. Verified by transcript: fresh instance per generation, + old fiber disposed only after the new applies, root effects survive + unduplicated, single disposal at shutdown. +3. **Locked in**: `tests/bun/selective-reload.spec.ts` + + `fixtures/selective-reload-driver.ts`, capability-gated (probes the + runner for query-busting; stock Bun → clean skip). 69/69 with the + capable build present. Manual narrative: `repros/selective-reload.ts`. + +Still deferred for a production selective-HMR service: fs watching → +generations, config preservation, rollback. The module-swap primitive is +done. + ## Current failures None. diff --git a/tests/bun/fixtures/selective-reload-driver.ts b/tests/bun/fixtures/selective-reload-driver.ts new file mode 100644 index 00000000..c4d4f76f --- /dev/null +++ b/tests/bun/fixtures/selective-reload-driver.ts @@ -0,0 +1,50 @@ +// Driver for tests/bun/selective-reload.spec.ts — run by a Bun build that +// includes oven-sh/bun#39426 (file:// query in module keys). +// Prints a tagged transcript; the spec asserts on its ordering. +import { Context } from 'cordis' +import { mkdtemp, writeFile } from 'node:fs/promises' +import { tmpdir } from 'node:os' +import { join } from 'node:path' +import { pathToFileURL } from 'node:url' + +const PLUGIN = [ + 'const gen = (globalThis as any).__gen = ((globalThis as any).__gen ?? 0) + 1', + 'export const captured = (globalThis as any).__token', + 'export function apply(ctx: any) {', + ' console.log(`[plugin] apply gen=${gen} captured=${captured}`)', + ' ctx.effect(() => {', + ' const t = setInterval(() => console.log(`[plugin] tick gen=${gen}`), 150)', + ' return () => { clearInterval(t); console.log(`[plugin] disposed gen=${gen}`) }', + ' })', + '}', +].join('\n') + +const dir = await mkdtemp(join(tmpdir(), 'sel-plugin-')) +await writeFile(join(dir, 'plugin.ts'), PLUGIN) + +const pluginURL = pathToFileURL(join(dir, 'plugin.ts')).href +const root = new Context() + +// root-owned effect: must survive the plugin swap, unduplicated +root.effect(() => { + const t = setInterval(() => console.log('[resident] tick'), 150) + return () => { clearInterval(t); console.log('[resident] disposed') } +}) + +async function loadGeneration(n: number) { + // requires bun#39426: each ?gen=N is a fresh module instance + const mod = await import(`${pluginURL}?gen=${n}`) + return root.plugin(mod) +} + +const gen1 = await loadGeneration(1) +await new Promise(r => setTimeout(r, 400)) +console.log('--- swap ---') +;(globalThis as any).__token = 'reloaded' +const gen2 = await loadGeneration(2) +await gen1.dispose() +await new Promise(r => setTimeout(r, 400)) +console.log('--- shutdown ---') +await gen2.dispose() +await root.fiber.dispose() +process.exit(0) diff --git a/tests/bun/repros/README.md b/tests/bun/repros/README.md index 2ba2d5a8..ff864386 100644 --- a/tests/bun/repros/README.md +++ b/tests/bun/repros/README.md @@ -82,3 +82,20 @@ These behaviors motivated the supervisor design (`packages/core/bin.bun.watch.js`) for stock Bun, and the `import.meta.hot`-driven in-process reload for Bun builds shipping bun#32856 (see `tests/bun/hot.spec.ts`). + +## selective-reload.ts + +Demonstrates Phase C selective plugin reload — swap ONE plugin generation +in-process while the root keeps running — using the `file://` query +cache-busting fixed by [oven-sh/bun#39426](https://github.com/oven-sh/bun/pull/39426): + +``` +.upstream/bun/build/debug/bun-debug tests/bun/repros/selective-reload.ts +# (or any Bun with the fix; stock Bun will re-import the CACHED module — +# the bug the PR fixes — and the swap assertion becomes meaningless) +``` + +Observed (bun#39426 debug build): `apply gen=2 captured=reloaded` (fresh +instance) → `disposed gen=1` (graceful old-fiber disposal) → resident ticks +continue unduplicated → only gen=2 ticks afterwards. The asserted version +lives in `tests/bun/selective-reload.spec.ts` (capability-gated). diff --git a/tests/bun/repros/selective-reload.ts b/tests/bun/repros/selective-reload.ts new file mode 100644 index 00000000..58592854 --- /dev/null +++ b/tests/bun/repros/selective-reload.ts @@ -0,0 +1,56 @@ +// Selective plugin reload on Bun — the Phase C primitive, enabled by +// oven-sh/bun#39426 (a file:// URL's query is part of the module key). +// Run with a Bun that includes the fix: +// .upstream/bun/build/debug/bun-debug tests/bun/repros/selective-reload.ts +// +// What this proves, per line of output: +// - [resident] ticks continue across the reload, unduplicated → root untouched +// - gen=2's module is a FRESH instance (captured=reloaded) → query busting +// - gen=1's disposer ran and its timer stopped → graceful swap +// - everything happens in-process, no restart +import { Context } from 'cordis' +import { mkdtemp, writeFile } from 'node:fs/promises' +import { tmpdir } from 'node:os' +import { join } from 'node:path' +import { pathToFileURL } from 'node:url' + +const dir = await mkdtemp(join(tmpdir(), 'cordis-selective-')) +await writeFile(join(dir, 'plugin.ts'), ` +const gen = (globalThis as any).__gen = ((globalThis as any).__gen ?? 0) + 1 +export const captured = (globalThis as any).__token +export function apply(ctx: any) { + console.log(\`[plugin] apply gen=\${gen} captured=\${captured}\`) + ctx.effect(() => { + const t = setInterval(() => console.log(\`[plugin] tick gen=\${gen}\`), 150) + return () => { clearInterval(t); console.log(\`[plugin] disposed gen=\${gen}\`) } + }) +} +`) + +const pluginURL = pathToFileURL(join(dir, 'plugin.ts')).href +const root = new Context() + +// resident plugin: must keep ticking, exactly once, across the reload +root.effect(() => { + const t = setInterval(() => console.log('[resident] tick'), 150) + return () => { clearInterval(t); console.log('[resident] disposed') } +}) + +async function loadGeneration(n: number) { + // query-busting: each ?gen=N is a FRESH module instance (needs #39426) + const mod = await import(`${pluginURL}?gen=${n}`) + return root.plugin(mod) +} + +const gen1 = await loadGeneration(1) +await new Promise(r => setTimeout(r, 400)) + +console.log('--- selective reload: swap the plugin, keep the root ---') +;(globalThis as any).__token = 'reloaded' +const gen2 = await loadGeneration(2) // new instance + activate +await gen1.dispose() // graceful: old timer cleared, disposer ran +await new Promise(r => setTimeout(r, 400)) + +console.log('--- shutdown ---') +await gen2.dispose() +await root.fiber.dispose() diff --git a/tests/bun/selective-reload.spec.ts b/tests/bun/selective-reload.spec.ts new file mode 100644 index 00000000..62d379c9 --- /dev/null +++ b/tests/bun/selective-reload.spec.ts @@ -0,0 +1,119 @@ +import { describe, it, expect } from 'bun:test' +import { spawn, spawnSync } from 'node:child_process' +import { existsSync, mkdtempSync, rmSync, writeFileSync } from 'node:fs' +import { mkdtemp, rm, symlink } from 'node:fs/promises' +import { tmpdir } from 'node:os' +import { join, resolve } from 'node:path' +import { fileURLToPath, pathToFileURL } from 'node:url' + +/** + * Selective plugin reload via file:// query cache-busting. + * + * Requires a Bun build shipping oven-sh/bun#39426 ("keep a file:// URL's + * query in module keys") — before it, `import(url + "?v=N")` returned the + * same cached module instance for every query, so per-module reload via + * public APIs was impossible on Bun (the reason selective HMR was deferred + * to Phase C in docs/BUN_COMPATIBILITY.md). + * + * The driver (`fixtures/selective-reload-driver.ts`) swaps one plugin + * generation in-process while the root keeps running; this spec asserts the + * transcript's ordering: + * 1. `?gen=N` yields a FRESH module instance per generation + * (`captured=reloaded` proves a new evaluation); + * 2. the old plugin's fiber is disposed AFTER the new one applies and its + * timers stop — only the new generation ticks afterwards; + * 3. root-owned effects keep ticking across the swap, unduplicated, and + * are disposed exactly once, at shutdown. + * + * Runner selection is by capability probe, not version: BUN_QUERY_BUSTING_BIN + * env var, else a local `.upstream/bun/build/debug/bun-debug` checkout. + * Skips cleanly when no capable binary exists (stock Bun, CI). + */ + +const REPO_ROOT = resolve(fileURLToPath(new URL('../../', import.meta.url))) +const DRIVER = fileURLToPath(new URL('./fixtures/selective-reload-driver.ts', import.meta.url)) + +function candidateBin(): string | undefined { + if (process.env.BUN_QUERY_BUSTING_BIN) return process.env.BUN_QUERY_BUSTING_BIN + const local = join(REPO_ROOT, '.upstream/bun/build/debug/bun-debug') + return existsSync(local) ? local : undefined +} + +/** capability probe: does import(url+query) bypass the module cache? */ +function hasQueryBusting(bin: string): boolean { + const dir = mkdtempSync(join(tmpdir(), 'cordis-qb-probe-')) + try { + const file = join(dir, 'm.ts') + writeFileSync(file, 'export const t = Date.now()\n') + const script = ` + const u = new URL(${JSON.stringify(pathToFileURL(file).href)}) + const a = await import(u.href + "?v=1") + await new Promise(r => setTimeout(r, 30)) + const b = await import(u.href + "?v=2") + console.log("QB:" + (a.t !== b.t)) + ` + const res = spawnSync(bin, ['-e', script], { + encoding: 'utf8', + timeout: 30000, + env: { ...process.env, BUN_DEBUG_QUIET_LOGS: '1' }, + }) + return (res.stdout + '').includes('QB:true') + } finally { + rmSync(dir, { recursive: true, force: true }) + } +} + +const BIN = candidateBin() + +describe('Bun / selective plugin reload (bun#39426 query cache-busting)', () => { + it.skipIf(!BIN || !hasQueryBusting(BIN!))( + 'query-busted generation swap disposes only the old plugin fiber', + async () => { + const dir = await mkdtemp(join(tmpdir(), 'cordis-selective-')) + try { + await symlink(join(REPO_ROOT, 'node_modules'), join(dir, 'node_modules'), 'dir') + + const child = spawn(BIN!, [DRIVER], { + cwd: dir, + stdio: ['ignore', 'pipe', 'pipe'], + env: { ...process.env, BUN_DEBUG_QUIET_LOGS: '1', NO_COLOR: '1' }, + }) + let stdout = '' + child.stdout.on('data', c => { stdout += c }) + child.stderr.on('data', c => { stdout += c }) + const code = await new Promise(res => child.on('exit', res)) + + const lines = stdout.split('\n').filter(l => l.startsWith('[') || l.startsWith('---')) + const idx = (needle: string) => lines.findIndex(l => l.includes(needle)) + + // generation 1 activated and ticked before the swap + expect(idx('apply gen=1')).toBeGreaterThan(-1) + expect(idx('tick gen=1')).toBeGreaterThan(-1) + expect(idx('tick gen=1')).toBeLessThan(idx('swap')) + + // fresh instance for gen=2 (captured=reloaded proves a new evaluation) + expect(idx('apply gen=2 captured=reloaded')).toBeGreaterThan(idx('swap')) + // old plugin disposed only after the new one applied + expect(idx('disposed gen=1')).toBeGreaterThan(idx('apply gen=2')) + + // after gen=1's disposal, only gen=2 ticks + const disposed1 = idx('disposed gen=1') + expect(lines.slice(disposed1).some(l => l.includes('tick gen=1'))).toBe(false) + expect(lines.slice(disposed1).some(l => l.includes('tick gen=2'))).toBe(true) + + // root-owned effects survive the swap unduplicated, disposed once at shutdown + const swapAt = idx('swap') + const shutdownAt = idx('shutdown') + expect(shutdownAt).toBeGreaterThan(-1) + expect(lines.slice(swapAt, shutdownAt).some(l => l.includes('[resident] disposed'))).toBe(false) + expect(lines.slice(swapAt, shutdownAt).filter(l => l.includes('[resident] tick')).length).toBeGreaterThan(0) + expect(lines.filter(l => l.includes('[resident] disposed')).length).toBe(1) + expect(idx('[resident] disposed')).toBeGreaterThan(shutdownAt) + expect(idx('disposed gen=2')).toBeGreaterThan(shutdownAt) + + expect(code).toBe(0) + } finally { + await rm(dir, { recursive: true, force: true }) + } + }, 60000) +}) From d6db6d6afbcf9ac0c50156f96449732cc13a31eb Mon Sep 17 00:00:00 2001 From: ebowwa Date: Mon, 17 Aug 2026 11:01:34 -0700 Subject: [PATCH 2/6] test(bun): prefer release bun build in selective-reload capability probe build/release/bun (the ~60-90MB usable binary) checked before the debug build; env override unchanged. --- tests/bun/selective-reload.spec.ts | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/tests/bun/selective-reload.spec.ts b/tests/bun/selective-reload.spec.ts index 62d379c9..48ad04a6 100644 --- a/tests/bun/selective-reload.spec.ts +++ b/tests/bun/selective-reload.spec.ts @@ -26,7 +26,8 @@ import { fileURLToPath, pathToFileURL } from 'node:url' * are disposed exactly once, at shutdown. * * Runner selection is by capability probe, not version: BUN_QUERY_BUSTING_BIN - * env var, else a local `.upstream/bun/build/debug/bun-debug` checkout. + * env var, else a local Bun checkout's build under `.upstream/bun` (release + * `build/release/bun` preferred, then debug `build/debug/bun-debug`). * Skips cleanly when no capable binary exists (stock Bun, CI). */ @@ -35,8 +36,11 @@ const DRIVER = fileURLToPath(new URL('./fixtures/selective-reload-driver.ts', im function candidateBin(): string | undefined { if (process.env.BUN_QUERY_BUSTING_BIN) return process.env.BUN_QUERY_BUSTING_BIN - const local = join(REPO_ROOT, '.upstream/bun/build/debug/bun-debug') - return existsSync(local) ? local : undefined + for (const rel of ['build/release/bun', 'build/debug/bun-debug']) { + const local = join(REPO_ROOT, '.upstream/bun', rel) + if (existsSync(local)) return local + } + return undefined } /** capability probe: does import(url+query) bypass the module cache? */ From 3bf36e888ea793a8144ecadd2c39640ed2a9d2be Mon Sep 17 00:00:00 2001 From: ebowwa Date: Mon, 17 Aug 2026 11:16:39 -0700 Subject: [PATCH 3/6] feat(bun): ship contributed-Bun release binary + harden spec path handling MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - build:release from the PR branch → 1.4.0-canary.1+c16333e9e; preserved at .upstream/bin/bun-39426 (59 MB, git-excluded); build dir reclaimed - verified: selective-reload spec + full 69/69 suite against it; real-app smoke (bin.bun.js boots, loads plugin, SIGINT root disposal, exit 0) - spec: resolve() the BUN_QUERY_BUSTING_BIN override — the driver child runs from a temp cwd, so a relative path silently broke spawn (caught during release verification) - docs: 'Running Cordis on the contributed Bun today' usage section; Phase 10 binary record incl. prior-art note re #35601 --- docs/BUN_COMPATIBILITY.md | 24 +++++++++++++++++++++++- docs/BUN_PORT_STATUS.md | 12 ++++++++++++ tests/bun/selective-reload.spec.ts | 4 +++- 3 files changed, 38 insertions(+), 2 deletions(-) diff --git a/docs/BUN_COMPATIBILITY.md b/docs/BUN_COMPATIBILITY.md index 6715513f..c0f0f402 100644 --- a/docs/BUN_COMPATIBILITY.md +++ b/docs/BUN_COMPATIBILITY.md @@ -247,10 +247,32 @@ Proof: `tests/bun/selective-reload.spec.ts` (+ its driver fixture) asserts the full contract — fresh instance per generation, old fiber disposed only after the new one applies, root effects survive unduplicated, single disposal at shutdown. It is **capability-gated** (probes the runner binary; -skips on stock Bun) and passes against a local build of bun#39426 +skips on stock Bun) and passes against a release build of bun#39426 (`bun test tests/bun` = 69/69 with the build present, 68 + skip without). Manual narrative version: `tests/bun/repros/selective-reload.ts`. +### Running Cordis on the contributed Bun today + +A release binary built from the PR branch is kept at +`.upstream/bin/bun-39426` (59 MB, `1.4.0-canary.1+c16333e9e`, +git-excluded). It is a drop-in Bun: + +```bash +# production-style CLI entrypoint — verified end-to-end (boot, plugin +# load, SIGINT root disposal, exit 0): +.upstream/bin/bun-39426 packages/core/bin.bun.js + +# force the capability-gated tests to use it: +BUN_QUERY_BUSTING_BIN=.upstream/bin/bun-39426 bun test tests/bun +``` + +Without the env var, the spec auto-detects +`.upstream/bun/build/release/bun` → `.upstream/bun/build/debug/bun-debug` +→ skips. When a shipped Bun release includes the fix, the binary and the +gating can simply be deleted. (Prior-art note: the Bun team has its own +in-flight #35601 covering the same ground — see BUN_PORT_STATUS.md; this +binary is for local use regardless of which PR lands.) + What remains for a production `--hot`-class selective HMR service (still deferred): file watching mapped to per-module generations, config/state preservation across swaps, rollback on failed activation. The module-swap diff --git a/docs/BUN_PORT_STATUS.md b/docs/BUN_PORT_STATUS.md index 2b9e4d94..175f3e37 100644 --- a/docs/BUN_PORT_STATUS.md +++ b/docs/BUN_PORT_STATUS.md @@ -213,6 +213,18 @@ Still deferred for a production selective-HMR service: fs watching → generations, config preservation, rollback. The module-swap primitive is done. +- **Release binary shipped locally (same session)**: `bun run + build:release` from the PR branch produced + `1.4.0-canary.1+c16333e9e` (62 MB at build; 59 MB preserved at + `.upstream/bin/bun-39426`, git-excluded; build dir deleted after — + full build ≈45 min, disk peak reclaimed to 15 GB free). Verified: + `--revision` ok; selective-reload spec passes against it (69/69 full + suite with it present); real-app smoke — `bin.bun.js` boots Cordis, + loads a plugin, SIGINT disposes the root, exit 0. Spec hardening from + this round: `BUN_QUERY_BUSTING_BIN` is `resolve()`d (the driver child + runs from a temp cwd — a relative override path broke it; found and + fixed during release-binary verification). + ## Current failures None. diff --git a/tests/bun/selective-reload.spec.ts b/tests/bun/selective-reload.spec.ts index 48ad04a6..3b26da86 100644 --- a/tests/bun/selective-reload.spec.ts +++ b/tests/bun/selective-reload.spec.ts @@ -35,7 +35,9 @@ const REPO_ROOT = resolve(fileURLToPath(new URL('../../', import.meta.url))) const DRIVER = fileURLToPath(new URL('./fixtures/selective-reload-driver.ts', import.meta.url)) function candidateBin(): string | undefined { - if (process.env.BUN_QUERY_BUSTING_BIN) return process.env.BUN_QUERY_BUSTING_BIN + // resolved to an absolute path: the driver child process runs with a + // temp cwd, so a relative binary path would not resolve from there + if (process.env.BUN_QUERY_BUSTING_BIN) return resolve(process.env.BUN_QUERY_BUSTING_BIN) for (const rel of ['build/release/bun', 'build/debug/bun-debug']) { const local = join(REPO_ROOT, '.upstream/bun', rel) if (existsSync(local)) return local From f3ebb4e5123d0528f85621a107c3a30d124d2642 Mon Sep 17 00:00:00 2001 From: ebowwa Date: Mon, 17 Aug 2026 11:22:16 -0700 Subject: [PATCH 4/6] =?UTF-8?q?docs(bun):=20pin=20contributed-Bun=20state?= =?UTF-8?q?=20=E2=80=94=20record=20binary=20SHA256?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The release binary (1.4.0-canary.1+c16333e9e) that Cordis is verified to run on: checksum recorded for verification, rebuild path documented. --- docs/BUN_COMPATIBILITY.md | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/docs/BUN_COMPATIBILITY.md b/docs/BUN_COMPATIBILITY.md index c0f0f402..0efd3264 100644 --- a/docs/BUN_COMPATIBILITY.md +++ b/docs/BUN_COMPATIBILITY.md @@ -255,7 +255,12 @@ Manual narrative version: `tests/bun/repros/selective-reload.ts`. A release binary built from the PR branch is kept at `.upstream/bin/bun-39426` (59 MB, `1.4.0-canary.1+c16333e9e`, -git-excluded). It is a drop-in Bun: +git-excluded; SHA256 +`57b1e6baa1ec0ea2fdfedf68b797abfd721cc93f8cc1e2700a31cbf10bdd099e`). +This state is pinned by git tag **`bun-39426`**. The binary is +reproducible from the public PR branch regardless: +`git fetch https://github.com/ebowwa/bun pull/39426/head && bun run +build:release`. It is a drop-in Bun: ```bash # production-style CLI entrypoint — verified end-to-end (boot, plugin From 30965c0d0f69d11ba10e3238d379f52c47d54f97 Mon Sep 17 00:00:00 2001 From: ebowwa Date: Mon, 17 Aug 2026 11:25:57 -0700 Subject: [PATCH 5/6] docs(bun): record bun-39426 tag + release asset publication --- docs/BUN_PORT_STATUS.md | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/docs/BUN_PORT_STATUS.md b/docs/BUN_PORT_STATUS.md index 175f3e37..7b4e6a40 100644 --- a/docs/BUN_PORT_STATUS.md +++ b/docs/BUN_PORT_STATUS.md @@ -224,6 +224,15 @@ done. this round: `BUN_QUERY_BUSTING_BIN` is `resolve()`d (the driver child runs from a temp cwd — a relative override path broke it; found and fixed during release-binary verification). +- **Pinned & published (owner-approved)**: state tagged **`bun-39426`** + (annotated, on `f3ebb4e` = PR #2 head; message carries the SHA256, + verification evidence, rebuild path, upstream refs). Binary published + as a GitHub Release asset + (`releases/download/bun-39426/bun-39426-darwin-aarch64`, 59 MB) — + download round-trip verified: checksum matches + `57b1e6ba…bdd099e`, `--revision` reports our commit. Docs record the + checksum and rebuild command, so the artifact is verifiable AND + reproducible independent of this machine. ## Current failures From 306fe8046ee01c5f989c2456b84f1ddcd7e8c9a2 Mon Sep 17 00:00:00 2001 From: ebowwa Date: Mon, 17 Aug 2026 11:27:24 -0700 Subject: [PATCH 6/6] docs(bun): record release references on both bun PRs --- docs/BUN_PORT_STATUS.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/docs/BUN_PORT_STATUS.md b/docs/BUN_PORT_STATUS.md index 7b4e6a40..255be3a9 100644 --- a/docs/BUN_PORT_STATUS.md +++ b/docs/BUN_PORT_STATUS.md @@ -233,6 +233,12 @@ done. `57b1e6ba…bdd099e`, `--revision` reports our commit. Docs record the checksum and rebuild command, so the artifact is verifiable AND reproducible independent of this machine. +- Release referenced on both Bun PRs (owner-approved): full artifact + note (download, checksum, usage, caveats) on + [#39426](https://github.com/oven-sh/bun/pull/39426#issuecomment-5318617337); + short validation pointer on + [#35601](https://github.com/oven-sh/bun/pull/35601#issuecomment-5318617517) + (framed as "testable binary for this direction", not our PR). ## Current failures