You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
The bun "pin shim" (the inline-import transpile that rewrites a bare import 'zod' into import 'zod@^3.0.0' from package.json / bun.lock so bun zero-install fetches the declared version instead of latest, plus its siblings: the cli @webjsdev retry #709, the spawn preload #704, the importmap version fallback #699) is a WORKAROUND coupled to bun internals (Bun.plugin onLoad, inline-specifier resolution which is bun's own "buggy and untested" auto-install path, bun --preload, Bun.Transpiler.scanImports). It cannot be fault-proof; a future bun release can change or obsolete it. Today it has concrete resilience gaps that would make a bun change break boot rather than degrade gracefully.
This is the CODE half of the resilience plan; the documentation half (canonical shim doc, grep markers, deletion checklist) is #714.
Design / approach (resilience properties to build in)
Fail-open everywhere. CONCRETE GAP: packages/server/src/bun-pin-preload.js calls rewriteDepSpecifiers(src, imports, versions) in its onLoad WITHOUT a try/catch, so a throw breaks the spawned tool load instead of falling back to bun's native resolution. Contrast packages/server/src/action-seed-bun.js, which DOES wrap pinTransform in try/catch (fail-open to raw source). Wrap the preload rewrite the same way, and audit every shim site for fail-open.
Capability probe + self-disable. Detect at boot whether THIS bun already honors package.json / bun.lock natively (a tiny probe resolving a known dep and checking the resolved version). If yes, skip the rewrite entirely, so the shim self-disables the day bun fixes the gap (the runtime version of "easy to trash"). Fail-safe: if the probe is inconclusive, keep the shim ON.
Escape hatch. A WEBJS_BUN_PIN=0 env var (and the webjs config block) to disable the whole pin mechanism, so a user on a future bun that breaks the shim can opt out without waiting for a webjs release. Mirror the existing WEBJS_ELIDE=0 / WEBJS_SEED=0 pattern.
CI against bun latest + canary. The Bun matrix CI (scripts/run-bun-tests.js) currently pins a bun version, so a future bun behavior change is invisible until a user hits it. Add a job running the bun-pin assertions against bun LATEST and CANARY so a behavior change FAILS loudly and early (the real safety net for a workaround built on evolving internals).
Design record context (for the cold implementing agent)
The pin shim is the ONLY mechanism for the zero-install DEFAULT (no install ever), because a committed bun.lock cannot exist without a resolve step (bun install or bun install --lockfile-only, both explicit; bun run writes no lockfile, verified on bun 1.3.14). So the bun.lock path (#705) is a separate OPT-IN reproducibility tier, NOT a more-fault-proof replacement for the default. Resilience for the default means hardening the rewrite (this issue), not swapping it out. Empirically (verified this session) inline specifiers resolve where bare imports do not; bun's source comment says auto-install "always installs the latest version regardless of package.json". The capability probe is what lets webjs notice if and when that changes.
Implementation notes (for the implementing agent)
Where: packages/server/src/bun-pin-preload.js (wrap the rewrite, add the probe + WEBJS_BUN_PIN gate), packages/server/src/action-seed-bun.js (the installBunSeedPlugin pinTransform branch: probe + gate), packages/cli/lib/import-webjsdev.js (dogfood: bun zero-install bun run dev fails, cli can't resolve @webjsdev/server #709: respect the escape hatch, optional probe), packages/server/src/vendor.js (declaredVendorVersions: gate), packages/cli/lib/create.js (Revert scaffold @webjsdev/* + drizzle exact pins to semver ranges (after #698) #700: the banner should mention WEBJS_BUN_PIN), the Bun CI job (scripts/run-bun-tests.js + the .github workflow) for the canary matrix.
Landmines: the probe itself must be cheap and fail-safe (a slow or throwing probe must not delay or break boot; default to keeping the shim ON when unsure). Do not disable the shim on a single ambiguous signal. WEBJS_BUN_PIN=0 must degrade to bun's native behavior (latest), documented as such. Keep the Bun.serve listener seam and the TS stripper OUT of scope (those are permanent bun support, not the pin shim) per docs: make the bun auto-install pin shim self-documenting + deletable #714.
Tests + docs: unit tests for the probe (both branches), the fail-open wrap (counterfactual: a throwing rewriteDepSpecifiers must serve raw source, not crash), and the WEBJS_BUN_PIN gate; a bun-parity test; update agent-docs/runtime.md + the docs: make the bun auto-install pin shim self-documenting + deletable #714 shim doc + the configuration docs for WEBJS_BUN_PIN.
Acceptance criteria
bun-pin-preload.js rewrite is fail-open (a throw serves raw source; a counterfactual proves it).
A capability probe exists and self-disables the shim when bun honors package.json / bun.lock natively (fail-safe to ON when unsure).
WEBJS_BUN_PIN=0 disables the whole pin mechanism (env + config), documented.
CI runs the bun-pin assertions against bun latest + canary.
Problem
The bun "pin shim" (the inline-import transpile that rewrites a bare
import 'zod'intoimport 'zod@^3.0.0'frompackage.json/bun.lockso bun zero-install fetches the declared version instead of latest, plus its siblings: the cli@webjsdevretry #709, the spawn preload #704, the importmap version fallback #699) is a WORKAROUND coupled to bun internals (Bun.pluginonLoad, inline-specifier resolution which is bun's own "buggy and untested" auto-install path,bun --preload,Bun.Transpiler.scanImports). It cannot be fault-proof; a future bun release can change or obsolete it. Today it has concrete resilience gaps that would make a bun change break boot rather than degrade gracefully.This is the CODE half of the resilience plan; the documentation half (canonical shim doc, grep markers, deletion checklist) is #714.
Design / approach (resilience properties to build in)
packages/server/src/bun-pin-preload.jscallsrewriteDepSpecifiers(src, imports, versions)in its onLoad WITHOUT a try/catch, so a throw breaks the spawned tool load instead of falling back to bun's native resolution. Contrastpackages/server/src/action-seed-bun.js, which DOES wrappinTransformin try/catch (fail-open to raw source). Wrap the preload rewrite the same way, and audit every shim site for fail-open.package.json/bun.locknatively (a tiny probe resolving a known dep and checking the resolved version). If yes, skip the rewrite entirely, so the shim self-disables the day bun fixes the gap (the runtime version of "easy to trash"). Fail-safe: if the probe is inconclusive, keep the shim ON.WEBJS_BUN_PIN=0env var (and thewebjsconfig block) to disable the whole pin mechanism, so a user on a future bun that breaks the shim can opt out without waiting for a webjs release. Mirror the existingWEBJS_ELIDE=0/WEBJS_SEED=0pattern.scripts/run-bun-tests.js) currently pins a bun version, so a future bun behavior change is invisible until a user hits it. Add a job running the bun-pin assertions against bun LATEST and CANARY so a behavior change FAILS loudly and early (the real safety net for a workaround built on evolving internals).Design record context (for the cold implementing agent)
The pin shim is the ONLY mechanism for the zero-install DEFAULT (no install ever), because a committed
bun.lockcannot exist without a resolve step (bun installorbun install --lockfile-only, both explicit;bun runwrites no lockfile, verified on bun 1.3.14). So thebun.lockpath (#705) is a separate OPT-IN reproducibility tier, NOT a more-fault-proof replacement for the default. Resilience for the default means hardening the rewrite (this issue), not swapping it out. Empirically (verified this session) inline specifiers resolve where bare imports do not; bun's source comment says auto-install "always installs the latest version regardless of package.json". The capability probe is what lets webjs notice if and when that changes.Implementation notes (for the implementing agent)
packages/server/src/bun-pin-preload.js(wrap the rewrite, add the probe +WEBJS_BUN_PINgate),packages/server/src/action-seed-bun.js(theinstallBunSeedPluginpinTransform branch: probe + gate),packages/cli/lib/import-webjsdev.js(dogfood: bun zero-install bun run dev fails, cli can't resolve @webjsdev/server #709: respect the escape hatch, optional probe),packages/server/src/vendor.js(declaredVendorVersions: gate),packages/cli/lib/create.js(Revert scaffold @webjsdev/* + drizzle exact pins to semver ranges (after #698) #700: the banner should mentionWEBJS_BUN_PIN), the Bun CI job (scripts/run-bun-tests.js+ the.githubworkflow) for the canary matrix.WEBJS_BUN_PIN=0must degrade to bun's native behavior (latest), documented as such. Keep theBun.servelistener seam and the TS stripper OUT of scope (those are permanent bun support, not the pin shim) per docs: make the bun auto-install pin shim self-documenting + deletable #714.rewriteDepSpecifiersmust serve raw source, not crash), and theWEBJS_BUN_PINgate; a bun-parity test; updateagent-docs/runtime.md+ the docs: make the bun auto-install pin shim self-documenting + deletable #714 shim doc + the configuration docs forWEBJS_BUN_PIN.Acceptance criteria
bun-pin-preload.jsrewrite is fail-open (a throw serves raw source; a counterfactual proves it).package.json/bun.locknatively (fail-safe to ON when unsure).WEBJS_BUN_PIN=0disables the whole pin mechanism (env + config), documented.Relates to #685, #698, #699, #700, #703, #704, #709, #714, #705.