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
Under bun zero-install, webjs's pin rewrite forwards the package.json RANGE (e.g. ^0.8.0) as an inline specifier, so bun resolves the HIGHEST in-range version on each run. A fresh bun run dev therefore AUTO-UPDATES to a newly-published in-range version (drift), with no lockfile pinning it. We want the npm / bun lockfile behaviour: a declared range resolves once, gets LOCKED to a specific version, and stays there until deliberately updated.
Design / approach
The building blocks ALREADY EXIST, so this is largely ergonomics + discoverability, not new mechanism:
packages/server/src/bun-pin-rewrite.jsresolveDepVersions ALREADY prefers a bun.lock EXACT version over the package.json range ("bun.lock exact wins (precise and reproducible). Otherwise pass the declared semver through"). So a committed bun.lock with exact pins makes webjs rewrite each dep to the EXACT version, locked, no drift.
(a) a webjs lock command wrapping bun install --lockfile-only (and refreshing the committed bun.lock), so one command gives reproducible pins with no node_modules;
(b) document committing a bun.lock as THE reproducibility opt-in across the runtime docs;
(c) decide whether the scaffold ships a bun.lock by default (tradeoff: reproducibility vs the zero-install "no install ever, picks up patches" simplicity, probably keep opt-in);
(d) a webjs lock --update to deliberately bump within range (the unlock + re-resolve flow).
The DEFAULT stays range-forwarding (idiomatic auto-update); the lock is the explicit reproducibility tier.
Implementation notes (for the implementing agent)
Where: packages/server/src/bun-pin-rewrite.js (resolveDepVersions already prefers the bun.lock exact, the core is done); a new cli command in packages/cli/bin/webjs.js + packages/cli/lib/ (e.g. webjs lock wrapping bun install --lockfile-only); docs in agent-docs/runtime.md (the zero-install version-resolution section), the docs site docs/app/docs/runtime/page.ts, and the scaffold template packages/cli/templates/AGENTS.md.
Landmines: do NOT rely on bun's runtime honoring bun.lock (it does not, research: can bun zero-install get lockfile-like reproducible versions? #705); webjs's own rewrite reading bun.lock is the mechanism. bun install --lockfile-only DOES hit the registry to resolve (a resolve step, not fully offline), so it is an explicit opt-in, not automatic. Keep the no-lock default (range forwarding) unchanged so the zero-install "no install ever" path still works. A committed bun.lock must stay in sync when package.json changes (document re-running webjs lock).
A documented, ergonomic way (e.g. webjs lock) to generate + commit a bun.lock with no full install, so declared ranges lock to exact resolved versions.
With a committed bun.lock, a fresh zero-install run pins every dep to the locked exact version (no drift to a newer in-range release).
A counterfactual (remove the lock) shows it drifts back to the highest in-range version.
The no-lock default (range forwarding, auto-update) is unchanged.
Docs (runtime.md + docs site + scaffold template) explain the lock opt-in and that it does not depend on bun honoring bun.lock at runtime.
Update: the lock should be AUTOMATIC and seamless, not a manual step
Clarified requirement: the user does NOT want a manual bun install --lockfile-only / webjs lock step. The version locking should happen automatically and transparently as part of auto-install. A first zero-install run resolves each declared range to a concrete version and PERSISTS it (a webjs-managed lock), and every subsequent run reuses that exact pin, so versions never drift to a newer in-range release without an explicit update. No command, no ceremony.
Design implication: webjs already controls version resolution (the onLoad rewrite, resolveDepVersions). So webjs can, on first resolution, WRITE the resolved exact version to a webjs-owned lock (e.g. .webjs/auto-lock.json, or refresh bun.lock) and prefer it on every later run (the rewrite already prefers an exact lock over a range). The "auto-update within range" behaviour becomes opt-in (an explicit webjs update, or deleting the lock), the inverse of today. Open questions to settle: where the lock lives and whether it is committed by default; how a package.json range edit invalidates or refreshes the relevant entries; first-run write performance; and the Node URL-resolution track (#669) equivalent. Keep it seamless: the user types the same commands and gets reproducibility for free, with auto-update as the deliberate action.
Problem
Under bun zero-install, webjs's pin rewrite forwards the
package.jsonRANGE (e.g.^0.8.0) as an inline specifier, so bun resolves the HIGHEST in-range version on each run. A freshbun run devtherefore AUTO-UPDATES to a newly-published in-range version (drift), with no lockfile pinning it. We want the npm / bun lockfile behaviour: a declared range resolves once, gets LOCKED to a specific version, and stays there until deliberately updated.Design / approach
The building blocks ALREADY EXIST, so this is largely ergonomics + discoverability, not new mechanism:
packages/server/src/bun-pin-rewrite.jsresolveDepVersionsALREADY prefers abun.lockEXACT version over thepackage.jsonrange ("bun.lock exact wins (precise and reproducible). Otherwise pass the declared semver through"). So a committedbun.lockwith exact pins makes webjs rewrite each dep to the EXACT version, locked, no drift.bun.lockitself in the rewrite; it does NOT depend on bun's runtime honoringbun.lock(which research: can bun zero-install get lockfile-like reproducible versions? #705, closed, proved it does NOT for bare imports).bun.lockcan be generated fast WITHOUT a full install viabun install --lockfile-only(resolves + writes the lock, nonode_modules, ~220ms, verified in research: can bun zero-install get lockfile-like reproducible versions? #705).So provide an ergonomic, documented lock opt-in:
webjs lockcommand wrappingbun install --lockfile-only(and refreshing the committedbun.lock), so one command gives reproducible pins with nonode_modules;bun.lockas THE reproducibility opt-in across the runtime docs;bun.lockby default (tradeoff: reproducibility vs the zero-install "no install ever, picks up patches" simplicity, probably keep opt-in);webjs lock --updateto deliberately bump within range (the unlock + re-resolve flow).The DEFAULT stays range-forwarding (idiomatic auto-update); the lock is the explicit reproducibility tier.
Implementation notes (for the implementing agent)
packages/server/src/bun-pin-rewrite.js(resolveDepVersionsalready prefers the bun.lock exact, the core is done); a new cli command inpackages/cli/bin/webjs.js+packages/cli/lib/(e.g.webjs lockwrappingbun install --lockfile-only); docs inagent-docs/runtime.md(the zero-install version-resolution section), the docs sitedocs/app/docs/runtime/page.ts, and the scaffold templatepackages/cli/templates/AGENTS.md.bun.lock(it does not, research: can bun zero-install get lockfile-like reproducible versions? #705); webjs's own rewrite readingbun.lockis the mechanism.bun install --lockfile-onlyDOES hit the registry to resolve (a resolve step, not fully offline), so it is an explicit opt-in, not automatic. Keep the no-lock default (range forwarding) unchanged so the zero-install "no install ever" path still works. A committedbun.lockmust stay in sync whenpackage.jsonchanges (document re-runningwebjs lock).Acceptance criteria
webjs lock) to generate + commit abun.lockwith no full install, so declared ranges lock to exact resolved versions.bun.lock, a fresh zero-install run pins every dep to the locked exact version (no drift to a newer in-range release).bun.lockat runtime.Relates to #705, #698, #703, #700, #669.
Update: the lock should be AUTOMATIC and seamless, not a manual step
Clarified requirement: the user does NOT want a manual
bun install --lockfile-only/webjs lockstep. The version locking should happen automatically and transparently as part of auto-install. A first zero-install run resolves each declared range to a concrete version and PERSISTS it (a webjs-managed lock), and every subsequent run reuses that exact pin, so versions never drift to a newer in-range release without an explicit update. No command, no ceremony.Design implication: webjs already controls version resolution (the onLoad rewrite,
resolveDepVersions). So webjs can, on first resolution, WRITE the resolved exact version to a webjs-owned lock (e.g..webjs/auto-lock.json, or refreshbun.lock) and prefer it on every later run (the rewrite already prefers an exact lock over a range). The "auto-update within range" behaviour becomes opt-in (an explicitwebjs update, or deleting the lock), the inverse of today. Open questions to settle: where the lock lives and whether it is committed by default; how apackage.jsonrange edit invalidates or refreshes the relevant entries; first-run write performance; and the Node URL-resolution track (#669) equivalent. Keep it seamless: the user types the same commands and gets reproducibility for free, with auto-update as the deliberate action.