Skip to content

Add a Bun-first scaffold mode (--runtime bun) across all 3 templates #541

@vivek7405

Description

@vivek7405

Problem

webjs runs on Node 24+ OR Bun, and all 4 in-repo apps now deploy on Bun. But a scaffolded app is still npm/node-flavored everywhere: the package.json scripts call npm, the lockfile is package-lock.json, the Dockerfile uses a node base, and the README + agent-config markdown all show npm run commands. A Bun-first user who runs bun create webjs my-app gets an app whose generated files do not speak Bun, so they have to convert it by hand. We should let the scaffold emit a fully Bun-flavored app.

Design / approach

Add a RUNTIME dimension orthogonal to --template (keep the exactly-3 templates invariant intact, runtime is a separate axis), defaulting to node:

  • webjs create <name> --runtime bun (and the same flag forwarded by create-webjs).
  • Auto-adopt bun when the scaffold is invoked through bun: scaffoldApp() already has detectPackageManager() which reads npm_config_user_agent and returns bun. So bun create webjs my-app can imply bun mode, with the explicit flag overriding.

ENTRY POINT IS ALREADY BUN-COMPATIBLE (verified): bun create webjs maps to bunx create-webjs per bun's own bun create --help ("NPM: Runs bunx create-"), which resolves the existing packages/wrappers/create-webjs bin. No new package is needed. bunx create-webjs@latest my-app is the explicit pin-latest form. One flag-forwarding difference to document: npm needs the -- separator (npm create webjs@latest my-app -- --template api) while bun forwards flags directly (bun create webjs my-app --template api).

Surfaces bun mode must rewrite in the generated app:

  • package.json scripts: bun run dev / start, the db:* scripts, bun test, predev / prestart hooks.
  • Lockfile: generate and commit bun.lock (text JSONC, Bun 1.2+, git-diffable) instead of package-lock.json. Adjust .gitignore / any CI to expect it.
  • Dockerfile: an oven/bun base instead of node:NN-alpine + copied-in bun binary, IF every build step (prisma generate, the ui registry copy) runs on the pure-bun base. Verify before switching the base.
  • compose.yaml: the service command runs bun.
  • README + agent-config markdown (AGENTS.md, CONVENTIONS.md, .cursorrules, .agents/rules/workflow.md, .github/copilot-instructions.md): show bun commands, and show both bun create webjs and bunx create-webjs@latest invocation styles.
  • .claude/hooks/*: anywhere they shell out to npm.

TWO BUN-SPECIFIC GOTCHAS the implementation must handle (both verified):

  1. The --bun shebang override. bun run dev where dev is webjs dev execs the webjs bin under NODE, because the bin is #!/usr/bin/env node. To actually run on bun you need bun --bun run dev (the --bun flag overrides shebangs for the script and its subprocesses), which is exactly what the docs already prescribe. So the bun-mode scripts/docs MUST use the --bun form, or the "bun" app silently runs on node.

  2. trustedDependencies. Unlike npm, bun does not run a dependency's postinstall by default (security); a package needs listing in "trustedDependencies" in package.json for its postinstall to run on bun install. webjs generates the Prisma client via the app's own predev / prestart (prisma generate), which always runs, so that path is safe, but the bun-mode template should still set a sane trustedDependencies so no dep silently skips its postinstall.

NON-GOALS: do not flip the in-repo example apps' local scripts (separate concern); do not add a 4th template (runtime is orthogonal to the 3 templates).

Acceptance criteria

  • webjs create <name> --runtime bun and bun create webjs <name> (auto-detected) both produce a Bun-flavored app across all 3 templates (full-stack / api / saas).
  • Generated package.json scripts use bun --bun run (closing the shebang gotcha), the app commits bun.lock (not package-lock.json), and trustedDependencies is set so Prisma and any postinstall dep work.
  • Generated Dockerfile + compose.yaml run on bun (the Dockerfile base decision is made and justified: pure oven/bun vs node-base + bun binary).
  • Generated README + agent-config markdown show bun commands and both bun create webjs / bunx create-webjs@latest invocation styles.
  • --runtime node (the default) is unchanged from today, so existing npm users see no difference.
  • Tests cover the new behaviour end to end: a --runtime bun scaffold (and a bun create webjs invocation) passes webjs check and BOOTS on bun (test/scaffolds/ + a cross-runtime check). A node-mode scaffold still boots on node.
  • Docs updated: the CLI reference, the getting-started / deployment docs, the scaffold templates, and packages/cli/AGENTS.md reflect the --runtime flag and the bun invocation forms.

Implementation notes (where to edit)

  • Scaffold logic lives in packages/cli/lib/create.js. scaffoldApp() writes package.json PROGRAMMATICALLY (around L275, the scripts block around L280) and tsconfig (around L335), and already has detectPackageManager() (around L23, returns bun from the UA). Branch the scripts, the lockfile choice, and trustedDependencies here on the resolved runtime.
  • CLI flag plumbing: packages/cli/bin/webjs.js parses --template via flag(rest, '--template', ...) (around L400) against TEMPLATES (L40). Add --runtime node|bun here and thread it into scaffoldApp() opts. Mirror the same flag in packages/wrappers/create-webjs/bin/create-webjs.js (its own flagValue('--template') parsing) and forward to scaffoldApp.
  • saas-only files: packages/cli/lib/saas-template.js.
  • Verbatim template files copied as-is: packages/cli/templates/Dockerfile, compose.yaml, plus the README and agent-config markdown under templates/. Bun mode needs either variants or a post-copy rewrite driven from create.js.
  • Tests: test/scaffolds/ (scaffold-template-validation, scaffold-integration, scaffold-ui-integration).
  • Invariants to respect: keep EXACTLY 3 templates (runtime is orthogonal, do not add a 4th); packages/ stays plain .js + JSDoc (no .ts).

Metadata

Metadata

Assignees

Labels

enhancementNew feature or request

Type

No type
No fields configured for issues without a type.

Projects

Status
Done

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions