@webjsdev/cli 0.10.24 → 0.10.25

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -33,7 +33,7 @@ npx @webjsdev/cli create my-app
33
33
  cd my-app && npm run dev
34
34
  ```
35
35
 
36
- Both `webjs create` and `create-webjs-app` auto-install dependencies in the new directory using your detected package manager (npm / pnpm / yarn / bun). Pass `--no-install` to opt out.
36
+ `webjs create` installs dependencies in the new directory by default on **Node** (it needs `node_modules` to run). On **Bun** it **skips** the install (zero-install: `bun run dev` resolves deps on the fly). Pass `--install` to force the install, or `--no-install` to skip it, on either runtime.
37
37
 
38
38
  ## Commands
39
39
 
package/bin/webjs.js CHANGED
@@ -50,12 +50,12 @@ const USAGE = `webjs commands:
50
50
  webjs doctor Verify project health (Node, tsconfig, env, vendor pins, importmap coherence, @webjsdev versions, git hook, page/layout elision)
51
51
  webjs types Generate .webjs/routes.d.ts (typed Route union + per-route params)
52
52
  webjs typecheck [tsc args...] Type-check the app with the project's tsc --noEmit (non-zero on errors)
53
- webjs create <name> [--template full-stack|api|saas] [--db sqlite|postgres] [--runtime node|bun] [--no-install] Scaffold a new webjs app
53
+ webjs create <name> [--template full-stack|api|saas] [--db sqlite|postgres] [--runtime node|bun] [--install|--no-install] Scaffold a new webjs app
54
54
  (only 3 templates exist. default: full-stack, Drizzle, --db sqlite, --runtime node)
55
55
  --runtime bun emits a Bun-flavored app (bun.lock, bun Dockerfile/CI, bun docs);
56
56
  also auto-detected when run via "bun create webjs".
57
- Auto-runs the detected package manager's install in the new dir
58
- unless --no-install is passed.
57
+ Install default is per runtime: Node installs; Bun skips (zero-install,
58
+ "bun run dev" resolves deps on the fly). --install / --no-install override.
59
59
  webjs db generate Generate a SQL migration from the schema (drizzle-kit generate)
60
60
  webjs db migrate Apply pending migrations (drizzle-kit migrate)
61
61
  webjs db push Push the schema straight to the dev DB (drizzle-kit push)
@@ -521,7 +521,11 @@ files.
521
521
  Full docs: https://docs.webjs.com`);
522
522
  process.exit(1);
523
523
  }
524
+ // Install policy (#682). Default per runtime: Node installs (needs
525
+ // node_modules to run); Bun skips (zero-install, `bun run dev` resolves
526
+ // on the fly). `--install` / `--no-install` override either way.
524
527
  const noInstall = rest.includes('--no-install');
528
+ const explicitInstall = rest.includes('--install');
525
529
  // --db picks the database dialect: sqlite (default) or postgres.
526
530
  const db = flag(rest, '--db', 'sqlite');
527
531
  // --runtime picks the target runtime: node (default) or bun. Orthogonal
@@ -532,8 +536,9 @@ Full docs: https://docs.webjs.com`);
532
536
  console.error(`Error: unknown --runtime '${runtime}'. Only node / bun are supported.`);
533
537
  process.exit(1);
534
538
  }
535
- const { scaffoldApp } = await import('../lib/create.js');
536
- await scaffoldApp(name, process.cwd(), { template, db, runtime, install: !noInstall });
539
+ const { scaffoldApp, resolveCreateInstall } = await import('../lib/create.js');
540
+ const install = resolveCreateInstall({ runtime, explicitInstall, noInstall });
541
+ await scaffoldApp(name, process.cwd(), { template, db, runtime, install });
537
542
  break;
538
543
  }
539
544
  case 'vendor': {
package/lib/create.js CHANGED
@@ -14,7 +14,7 @@
14
14
  import { mkdir, writeFile, readFile, cp } from 'node:fs/promises';
15
15
  import { join, resolve, dirname } from 'node:path';
16
16
  import { fileURLToPath } from 'node:url';
17
- import { existsSync } from 'node:fs';
17
+ import { existsSync, readFileSync } from 'node:fs';
18
18
  import { createRequire } from 'node:module';
19
19
  import { spawnSync } from 'node:child_process';
20
20
  import { bunifyProse, bunifyDockerfile, bunifyCompose, bunifyCi } from './runtime-rewrite.js';
@@ -35,6 +35,68 @@ function detectPackageManager() {
35
35
  return 'npm';
36
36
  }
37
37
 
38
+ /**
39
+ * Decide whether `webjs create` runs the post-scaffold install, per target
40
+ * runtime (#682). Node installs by default (it needs `node_modules` to run);
41
+ * Bun SKIPS by default (zero-install: `bun run dev` resolves deps on the fly,
42
+ * #675). Under Bun zero-install deps resolve to their LATEST version (ranges
43
+ * and any lockfile are ignored at runtime, #690), so `bun install` is the path
44
+ * to pinned, reproducible versions. Explicit flags win: `--install` forces it
45
+ * on, `--no-install` forces it off. The CLI entry points call this and pass an
46
+ * explicit boolean to `scaffoldApp`, so the library default (no install unless
47
+ * `install: true`) is unchanged for programmatic callers.
48
+ *
49
+ * @param {{ runtime?: string, explicitInstall?: boolean, noInstall?: boolean }} [o]
50
+ * @returns {boolean}
51
+ */
52
+ export function resolveCreateInstall({ runtime, explicitInstall, noInstall } = {}) {
53
+ if (explicitInstall) return true;
54
+ if (noInstall) return false;
55
+ const isBun = runtime === 'bun' || (!runtime && detectPackageManager() === 'bun');
56
+ return !isBun;
57
+ }
58
+
59
+ /**
60
+ * Read the EXACT version of `@webjsdev/<pkg>` the scaffolding CLI ships with, so
61
+ * the generated `package.json` pins it precisely and BOTH npm and bun resolve
62
+ * the same version (#692). Walks the `require.resolve` node_modules search paths
63
+ * and fs-reads `<pkg>/package.json` directly: `@webjsdev/server` (and `ui`) hide
64
+ * `./package.json` behind `exports`, so a bare `require('<pkg>/package.json')`
65
+ * fails (same constraint #687 hit). Falls back to `'latest'` when the package is
66
+ * not resolvable (defensive; the CLI's own dependency closure is normally
67
+ * present), which keeps the scaffold working rather than emitting a bad pin.
68
+ * @param {string} pkg e.g. 'cli', 'core', 'server'
69
+ * @returns {string} an exact version, or 'latest'
70
+ */
71
+ function webjsdevVersion(pkg) {
72
+ const req = createRequire(import.meta.url);
73
+ for (const base of (req.resolve.paths(`@webjsdev/${pkg}`) || [])) {
74
+ const pj = join(base, '@webjsdev', pkg, 'package.json');
75
+ if (existsSync(pj)) {
76
+ try {
77
+ const v = JSON.parse(readFileSync(pj, 'utf8')).version;
78
+ if (v) return v;
79
+ } catch { /* unreadable; keep looking, then fall back */ }
80
+ }
81
+ }
82
+ return 'latest';
83
+ }
84
+
85
+ /**
86
+ * Exact third-party dep versions the scaffold pins (#692). These are template
87
+ * deps the CLI does NOT itself depend on, so they cannot be read from the CLI's
88
+ * closure (unlike `@webjsdev/*`). Pinned EXACT so npm and bun resolve identically
89
+ * (a `^` range diverges: npm takes latest-in-range, bun zero-install takes
90
+ * absolute latest, #690). Drizzle is the 1.0 relations-v2 RC the scaffold's db
91
+ * code targets (its npm `latest` tag is a 0.x line, so a range would resolve the
92
+ * wrong major under bun). Refresh on a deliberate bump, same as the old ranges.
93
+ */
94
+ const SCAFFOLD_DEP_VERSIONS = {
95
+ 'drizzle-orm': '1.0.0-rc.3',
96
+ 'drizzle-kit': '1.0.0-rc.3',
97
+ pg: '8.22.0',
98
+ };
99
+
38
100
  /**
39
101
  * Run `<pm> install` inside the scaffolded app. Returns true on success.
40
102
  * Inherits stdio so the user sees the install progress live. Caller decides
@@ -356,14 +418,22 @@ export async function scaffoldApp(name, cwd, opts = {}) {
356
418
  // for relations v2. SQLite needs NO driver dependency: the connection
357
419
  // uses the built-in node:sqlite (Node) / bun:sqlite (Bun) via Drizzle's
358
420
  // node-sqlite / bun-sqlite adapters. Postgres still needs the pg driver.
359
- 'drizzle-orm': '^1.0.0-rc.3',
360
- ...(dialect === 'postgres' ? { pg: '^8.13.0' } : {}),
361
- '@webjsdev/cli': 'latest',
362
- '@webjsdev/core': 'latest',
363
- '@webjsdev/server': 'latest',
421
+ // Exact pins (#692): npm and bun must resolve identical versions. A `^`
422
+ // range diverges (npm = latest-in-range; bun zero-install = absolute
423
+ // latest, #690), and drizzle's npm `latest` tag is a 0.x line, so a range
424
+ // would pull the wrong major under bun. @webjsdev/* are pinned to the
425
+ // versions the scaffolding CLI itself ships with.
426
+ 'drizzle-orm': SCAFFOLD_DEP_VERSIONS['drizzle-orm'],
427
+ ...(dialect === 'postgres' ? { pg: SCAFFOLD_DEP_VERSIONS.pg } : {}),
428
+ '@webjsdev/cli': webjsdevVersion('cli'),
429
+ '@webjsdev/core': webjsdevVersion('core'),
430
+ '@webjsdev/server': webjsdevVersion('server'),
364
431
  },
365
432
  devDependencies: {
366
- 'drizzle-kit': '^1.0.0-rc.3',
433
+ // Exact pin (#692): drizzle-kit is resolved under bun zero-install via
434
+ // `bun run db:generate` / `db:migrate`, so it must match drizzle-orm's
435
+ // exact version across runtimes (a range would diverge, #690).
436
+ 'drizzle-kit': SCAFFOLD_DEP_VERSIONS['drizzle-kit'],
367
437
  ...(dialect === 'postgres' ? { '@types/pg': '^8.11.0' } : {}),
368
438
  // The TypeScript compiler, for `npm run typecheck` (webjs typecheck runs
369
439
  // tsc --noEmit). Not needed at runtime (Node strips types in place), only
@@ -1404,6 +1474,12 @@ For AI agents, read this before editing scaffolded files:
1404
1474
  if (!installed) {
1405
1475
  console.log(`\n[warn] ${pm} install failed. Run '${pm} install' manually in ${name}/ to finish setup.\n`);
1406
1476
  }
1477
+ } else if (isBun) {
1478
+ // Bun zero-install (#675): no install needed; `bun run dev` resolves deps on
1479
+ // the fly. They resolve to LATEST (ranges + lockfile ignored at runtime,
1480
+ // #690), so point at `bun install` for pinned, reproducible versions.
1481
+ console.log(`Skipped install. Bun resolves dependencies on the fly, so 'bun run dev' and 'bun run start' work as-is (no node_modules).`);
1482
+ console.log(`These resolve to each dependency's LATEST version. Run 'bun install' in ${name}/ when you want pinned, reproducible versions (and editor type intelligence).\n`);
1407
1483
  }
1408
1484
 
1409
1485
  // Next-steps banner prints LAST so the actionable command is the
@@ -1415,7 +1491,10 @@ For AI agents, read this before editing scaffolded files:
1415
1491
  // generate + migrate before the first run (the example User model wants
1416
1492
  // its table to exist). Drizzle splits Prisma's `migrate dev` into
1417
1493
  // `db:generate` (schema to SQL) then `db:migrate` (apply).
1418
- const installSegment = installed ? '' : `${pm} install && `;
1494
+ // Omit the install step from the next-steps line for a deliberate Bun
1495
+ // zero-install (#682): `bun run dev` resolves deps on the fly, so it works
1496
+ // without an install. Otherwise (Node, or an install that did not run) keep it.
1497
+ const installSegment = (installed || (isBun && !shouldInstall)) ? '' : `${pm} install && `;
1419
1498
  const dbSegment = isSaas ? `${pm} run db:generate && ${pm} run db:migrate && ` : '';
1420
1499
  const runCommand = `cd ${name} && ${installSegment}${dbSegment}${pm} run dev`;
1421
1500
  // Use `npx webjsdev ui ...` here, not `npx webjs ui ...`. The bare
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@webjsdev/cli",
3
- "version": "0.10.24",
3
+ "version": "0.10.25",
4
4
  "type": "module",
5
5
  "description": "webjs CLI - dev, start, create, db",
6
6
  "bin": {
@@ -414,8 +414,12 @@ demand and **no `bun install` is needed**:
414
414
  bun run dev # or: bun run start (no install step required)
415
415
  ```
416
416
 
417
- `bun install` is optional here, run it for editor type intelligence or a pinned
418
- offline install. (To run a Node-flavored app on Bun instead, force `bun --bun run
417
+ `bun create` does not run an install on Bun, so a fresh app serves immediately.
418
+ Under zero-install, deps resolve to their LATEST version: ranges and any
419
+ `bun.lock` are ignored by the runtime auto-install (only an exact `package.json`
420
+ pin is honored, via an `onLoad` rewrite). Run `bun install` when you want pinned,
421
+ reproducible versions (it materializes `node_modules` from the lockfile) or
422
+ editor type intelligence. (To run a Node-flavored app on Bun instead, force `bun --bun run
419
423
  dev`, which still expects an install.)
420
424
 
421
425
  On Node the `.ts` type-stripping is the built-in `module.stripTypeScriptTypes`;