Skip to content

Make webjs db/test/typecheck work under bun zero-install (auto-install, no bun install) #704

Description

@vivek7405

Goal

Make webjs db / test / typecheck work under true Bun zero-install (auto-install), WITHOUT requiring bun install. Supersedes #695 (and reverses its earlier "just run bun install" framing). The dev/start server already runs zero-install and gets its deps pinned via the #685 onLoad rewrite; the spawned tooling should reach the same outcome, not bail to an install.

Why it breaks today

The #685 onLoad pin runs only in the webjs SERVER process. webjs db spawns drizzle-kit as a SEPARATE Bun process (and test/typecheck spawn their own), so:

  1. The spawned tool's own bare imports (drizzle-kit's import 'drizzle-orm', the user's db/schema.server.ts import 'drizzle-orm') hit raw auto-install and get the wrong version.
  2. resolveBin(cwd, 'drizzle-kit') cannot find the tool bin under an empty cache (no node_modules), so even locating the tool fails (fix: bun zero-install breaks every spawned-subprocess command (db/test/seed/typecheck); pin at the spawn layer #695 option B's gap).

Approach (zero-install, no bun install)

Two pieces, both leaning on drizzle now being EXACT-pinned (#700), which is exactly what Bun auto-install can resolve inline:

  1. Run the tool at its exact pin via auto-install, not resolveBin. Spawn bun x drizzle-kit@<exact-pin-from-package.json> (bunx auto-installs + runs the pinned bin), instead of resolving a bin from a node_modules that does not exist. This closes the fix: bun zero-install breaks every spawned-subprocess command (db/test/seed/typecheck); pin at the spawn layer #695 resolveBin gap.
  2. Propagate the bun-pin onLoad into the spawned tool process so the tool's AND the schema's bare imports (import 'drizzle-orm') resolve to the pinned version. A spawned bun run picks up the cwd's bunfig.toml preload (verified locally), so the scaffold can ship (or webjs db can inject) a bunfig.toml preload that installs the same buildBunPinTransform onLoad plugin. The spawned tool then gets every bare dep inline-versioned, the same as the server.

First task: verify the mechanism end to end (do NOT assume)

This is where #695 option B died, so prove it before building:

  • Does a bunfig.toml preload onLoad plugin in the spawned process rewrite the bare imports of (a) the tool's own code and (b) the user-authored db/schema.server.ts it loads, not just the entry file? Measure the LOADED version (import the dep's package.json version), never import.meta.resolve(bareName) (that resolves the unrewritten bare specifier and reads latest).
  • Does bun x drizzle-kit@1.0.0-rc.3 run the pinned bin under an empty cache, and does its transitive drizzle-orm import resolve to the pin once the preload is active?
  • Confirm on a fresh bun create ... && bun run db:generate (no install) end to end.

If a piece proves infeasible, fall back is a friendly "this command needs bun install" guard (NOT the preferred outcome; the goal is zero-install).

Implementation notes

  • Where: the case 'db' block (and test / typecheck spawn paths) in packages/cli/bin/webjs.js; the bun-pin onLoad lives in packages/server/src/action-seed-bun.js + bun-pin-rewrite.js (reuse buildBunPinTransform); the scaffold bunfig.toml generation in packages/cli/lib/create.js.
  • Landmine: the spawned tool must get BOTH the pinned bin AND the in-process onLoad; either alone is insufficient (the fix: bun zero-install breaks every spawned-subprocess command (db/test/seed/typecheck); pin at the spawn layer #695 finding).
  • Tests: a cli/scaffold test that a fresh bun zero-install app runs db:generate + db:migrate successfully with NO install (counterfactual: it fails today). Bun-only path, so a test/bun/* assertion.
  • Docs: update agent-docs/runtime.md, docs/app/docs/runtime/page.ts, packages/cli/AGENTS.md (the current "tooling needs an install" note flips to "tooling works zero-install").

Acceptance criteria

  • A fresh bun create ... && bun run db:generate && bun run db:migrate works with NO bun install.
  • The spawned tool resolves drizzle-orm at the pinned version (not the wrong 0.x major).
  • webjs test / typecheck likewise work zero-install, or are documented as a separate constraint with a reason.
  • Counterfactual test + docs synced.

Supersedes #695. Relates to #698, #700, #703.


Update (2026-06-25): the bun x approach does NOT work, use bun --preload <runner> (mechanism verified)

Verified this session (recorded on draft PR #708). The body's Approach #1 (spawn bun x drizzle-kit@<pin> and propagate the onLoad into it) is NOT viable as written:

  • bun x cannot carry the preload. bun --preload P x <pkg> gives Script not found "x"; bun x --preload P treats --preload as a package to install (404 @./P). So the pin onLoad cannot ride bun x.
  • The working mechanism: spawn bun --preload <server bun-pin-preload.js abspath> <cli runner.mjs> <sub> <args>. bun run honors --preload, and the preload's onLoad reaches the runner's dynamic import('drizzle-kit') (rewriting it to the app-declared pin) AND the user schema's import 'drizzle-orm'. Confirmed with a cowsay repro (a preload rewrote a bare import('cowsay') to cowsay@1.6.0 and it loaded).
  • The keystone preload (packages/server/src/bun-pin-preload.js, on feat: run webjs db/test/typecheck under bun zero-install (no install) #708) is dependency-free (bun-pin-rewrite.js has zero imports), so loading it by abspath triggers no auto-install (no chicken-and-egg).

Remaining wrinkles to nail in wiring (each needs a check): resolve the server preload abspath from the cli under zero-install (likely a @webjsdev/server/bun-pin-preload subpath export that import.meta.resolve finds in the cache); invoke drizzle-kit programmatically from the runner (set process.argv, import('drizzle-kit/bin.cjs')); generalize to test (node --test) and typecheck (tsc). Builds on #709 (now merged). The cold-cache fetch reliability stays the #705-class caveat (the inline-pinned fetch is far more reliable than bare-latest, not 100%).

Metadata

Metadata

Assignees

Labels

enhancementNew feature or request

Type

No type
No fields configured for issues without a type.

Projects

Status
In progress

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions