@webjsdev/cli 0.10.25 → 0.10.27
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/bin/webjs.js +10 -9
- package/lib/create.js +45 -28
- package/lib/import-webjsdev.js +99 -0
- package/package.json +1 -1
- package/templates/AGENTS.md +11 -6
package/bin/webjs.js
CHANGED
|
@@ -6,6 +6,7 @@ import { resolveBin } from '../lib/resolve-bin.js';
|
|
|
6
6
|
import { checkNodeInline, nodeInlineMessage } from '../lib/node-preflight.js';
|
|
7
7
|
import { loadAppEnv, resolvePort } from '../lib/port.js';
|
|
8
8
|
import { planDevSupervisor } from '../lib/dev-supervisor.js';
|
|
9
|
+
import { importWebjsdev } from '../lib/import-webjsdev.js';
|
|
9
10
|
|
|
10
11
|
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
11
12
|
const [cmd, ...rest] = process.argv.slice(2);
|
|
@@ -121,7 +122,7 @@ async function main() {
|
|
|
121
122
|
// instead of crashing cryptically later. `help` is exempt so a user on an
|
|
122
123
|
// old Node can still read usage.
|
|
123
124
|
if (cmd !== 'help' && cmd !== undefined) {
|
|
124
|
-
const { assertNodeVersion } = await
|
|
125
|
+
const { assertNodeVersion } = await importWebjsdev('@webjsdev/server');
|
|
125
126
|
assertNodeVersion({ onFail: 'exit' });
|
|
126
127
|
}
|
|
127
128
|
switch (cmd) {
|
|
@@ -129,7 +130,7 @@ async function main() {
|
|
|
129
130
|
// If we're already inside the reload child (node --watch or bun --hot),
|
|
130
131
|
// start the server directly.
|
|
131
132
|
if (process.env.__WEBJS_DEV_CHILD === '1') {
|
|
132
|
-
const { startServer } = await
|
|
133
|
+
const { startServer } = await importWebjsdev('@webjsdev/server');
|
|
133
134
|
// Load `.env` BEFORE resolving the port so a `PORT` set there is in
|
|
134
135
|
// process.env at resolution time (#447). The server loads `.env`
|
|
135
136
|
// too, but that runs too late to affect the port the CLI computes.
|
|
@@ -165,7 +166,7 @@ async function main() {
|
|
|
165
166
|
});
|
|
166
167
|
|
|
167
168
|
if (plan.mode === 'inline') {
|
|
168
|
-
const { startServer } = await
|
|
169
|
+
const { startServer } = await importWebjsdev('@webjsdev/server');
|
|
169
170
|
loadAppEnv(process.cwd());
|
|
170
171
|
const port = resolvePort(flag(rest, '--port'));
|
|
171
172
|
await startServer({ appDir: process.cwd(), port, dev: true });
|
|
@@ -182,7 +183,7 @@ async function main() {
|
|
|
182
183
|
break;
|
|
183
184
|
}
|
|
184
185
|
case 'start': {
|
|
185
|
-
const { startServer } = await
|
|
186
|
+
const { startServer } = await importWebjsdev('@webjsdev/server');
|
|
186
187
|
// Load `.env` BEFORE resolving the port so a `PORT` set there wins over
|
|
187
188
|
// the 8080 default (#447), same as for `dev`.
|
|
188
189
|
loadAppEnv(process.cwd());
|
|
@@ -364,7 +365,7 @@ async function main() {
|
|
|
364
365
|
break;
|
|
365
366
|
}
|
|
366
367
|
case 'check': {
|
|
367
|
-
const { checkConventions, RULES } = await
|
|
368
|
+
const { checkConventions, RULES } = await importWebjsdev('@webjsdev/server/check');
|
|
368
369
|
|
|
369
370
|
if (rest.includes('--rules')) {
|
|
370
371
|
console.log('webjs check, correctness rules:');
|
|
@@ -390,7 +391,7 @@ async function main() {
|
|
|
390
391
|
if (rest.includes('--json')) {
|
|
391
392
|
// The projector lives in @webjsdev/mcp (the MCP `check` tool's home),
|
|
392
393
|
// so `check --json` and the MCP tool stay byte-identical (#415).
|
|
393
|
-
const { projectCheck } = await
|
|
394
|
+
const { projectCheck } = await importWebjsdev('@webjsdev/mcp/check-report');
|
|
394
395
|
console.log(JSON.stringify(projectCheck(violations)));
|
|
395
396
|
if (violations.length > 0) process.exit(1);
|
|
396
397
|
break;
|
|
@@ -447,7 +448,7 @@ async function main() {
|
|
|
447
448
|
// narrowing the @webjsdev/core `Route` href union + per-route `params`.
|
|
448
449
|
// Opt-in codegen: the static types in @webjsdev/core work without it
|
|
449
450
|
// (un-generated apps see `Route = string`).
|
|
450
|
-
const { generateRouteTypes } = await
|
|
451
|
+
const { generateRouteTypes } = await importWebjsdev('@webjsdev/server');
|
|
451
452
|
const { mkdir, writeFile } = await import('node:fs/promises');
|
|
452
453
|
const appDir = process.cwd();
|
|
453
454
|
const text = await generateRouteTypes(appDir);
|
|
@@ -545,7 +546,7 @@ Full docs: https://docs.webjs.com`);
|
|
|
545
546
|
const sub = rest[0];
|
|
546
547
|
const args = rest.slice(1);
|
|
547
548
|
const appDir = process.cwd();
|
|
548
|
-
const { pinAll, unpinPackage, listPinned, auditPinned, findOutdated, updatePinned, readPinFile, ensureVendorCommittable, SUPPORTED_PROVIDERS } = await
|
|
549
|
+
const { pinAll, unpinPackage, listPinned, auditPinned, findOutdated, updatePinned, readPinFile, ensureVendorCommittable, SUPPORTED_PROVIDERS } = await importWebjsdev('@webjsdev/server');
|
|
549
550
|
|
|
550
551
|
// Parse `--from <provider>` once at the top so subcommands share it.
|
|
551
552
|
// Mirrors importmap-rails's `bin/importmap pin foo --from jsdelivr`.
|
|
@@ -808,7 +809,7 @@ Full docs: https://docs.webjs.com`);
|
|
|
808
809
|
// runnable directly as `npx @webjsdev/mcp`); `webjs mcp` delegates to it
|
|
809
810
|
// for back-compat. The version advertised in the initialize handshake is
|
|
810
811
|
// @webjsdev/mcp's own, resolved by its bin, so this passes none.
|
|
811
|
-
const { runMcpServer } = await
|
|
812
|
+
const { runMcpServer } = await importWebjsdev('@webjsdev/mcp');
|
|
812
813
|
const { createRequire } = await import('node:module');
|
|
813
814
|
const require = createRequire(import.meta.url);
|
|
814
815
|
let version = '0.0.0';
|
package/lib/create.js
CHANGED
|
@@ -39,9 +39,10 @@ function detectPackageManager() {
|
|
|
39
39
|
* Decide whether `webjs create` runs the post-scaffold install, per target
|
|
40
40
|
* runtime (#682). Node installs by default (it needs `node_modules` to run);
|
|
41
41
|
* Bun SKIPS by default (zero-install: `bun run dev` resolves deps on the fly,
|
|
42
|
-
* #675). Under Bun zero-install
|
|
43
|
-
*
|
|
44
|
-
*
|
|
42
|
+
* #675). Under Bun zero-install webjs pins a declared dep to its package.json
|
|
43
|
+
* version via the #685/#698 onLoad rewrite (a caret range now resolves the
|
|
44
|
+
* highest match, not absolute latest), so `bun install` is the path to a frozen
|
|
45
|
+
* lockfile, not a correctness fix. Explicit flags win: `--install` forces it
|
|
45
46
|
* on, `--no-install` forces it off. The CLI entry points call this and pass an
|
|
46
47
|
* explicit boolean to `scaffoldApp`, so the library default (no install unless
|
|
47
48
|
* `install: true`) is unchanged for programmatic callers.
|
|
@@ -57,9 +58,9 @@ export function resolveCreateInstall({ runtime, explicitInstall, noInstall } = {
|
|
|
57
58
|
}
|
|
58
59
|
|
|
59
60
|
/**
|
|
60
|
-
* Read the EXACT version of `@webjsdev/<pkg>` the scaffolding CLI ships with
|
|
61
|
-
* the generated `package.json`
|
|
62
|
-
* the
|
|
61
|
+
* Read the EXACT version of `@webjsdev/<pkg>` the scaffolding CLI ships with.
|
|
62
|
+
* `webjsdevRange` carets over this for the generated `package.json` (#700), so a
|
|
63
|
+
* fresh app tracks the line the CLI shipped. Walks the `require.resolve` node_modules search paths
|
|
63
64
|
* and fs-reads `<pkg>/package.json` directly: `@webjsdev/server` (and `ui`) hide
|
|
64
65
|
* `./package.json` behind `exports`, so a bare `require('<pkg>/package.json')`
|
|
65
66
|
* fails (same constraint #687 hit). Falls back to `'latest'` when the package is
|
|
@@ -83,18 +84,35 @@ function webjsdevVersion(pkg) {
|
|
|
83
84
|
}
|
|
84
85
|
|
|
85
86
|
/**
|
|
86
|
-
*
|
|
87
|
-
*
|
|
88
|
-
*
|
|
89
|
-
*
|
|
90
|
-
*
|
|
91
|
-
*
|
|
92
|
-
*
|
|
87
|
+
* The `@webjsdev/<pkg>` specifier for the generated `package.json` (#700): a
|
|
88
|
+
* caret range over the version the scaffolding CLI ships with, so a fresh app
|
|
89
|
+
* picks up patch updates the way an npm user expects (a `^0.x` caret stays
|
|
90
|
+
* within the minor). Bun zero-install resolves a normal caret range correctly
|
|
91
|
+
* since #698, so this no longer diverges from npm. Falls back to `'latest'` when
|
|
92
|
+
* the version is not resolvable (keeps `^latest` from ever being emitted).
|
|
93
|
+
* @param {string} pkg e.g. 'cli', 'core', 'server'
|
|
94
|
+
* @returns {string}
|
|
95
|
+
*/
|
|
96
|
+
function webjsdevRange(pkg) {
|
|
97
|
+
const v = webjsdevVersion(pkg);
|
|
98
|
+
return v === 'latest' ? 'latest' : '^' + v;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
|
+
* Third-party dep specifiers the scaffold ships (#700). These are template deps
|
|
103
|
+
* the CLI does NOT itself depend on, so they cannot be read from the CLI's
|
|
104
|
+
* closure (unlike `@webjsdev/*`). Since #698, Bun zero-install resolves a normal
|
|
105
|
+
* caret range correctly (highest match, not absolute latest), so `pg` is an
|
|
106
|
+
* idiomatic `^` range. Drizzle is PINNED to an exact RC: its 1.0 line is a
|
|
107
|
+
* PRERELEASE (`1.0.0-rc.3`), and Bun zero-install ENOENTs on a caret-prerelease
|
|
108
|
+
* inline specifier (`drizzle-orm@^1.0.0-rc.3`, verified on Bun 1.3.14) while the
|
|
109
|
+
* exact prerelease resolves, so drizzle must stay exact until the 1.0 stable
|
|
110
|
+
* ships. Refresh on a deliberate bump.
|
|
93
111
|
*/
|
|
94
112
|
const SCAFFOLD_DEP_VERSIONS = {
|
|
95
113
|
'drizzle-orm': '1.0.0-rc.3',
|
|
96
114
|
'drizzle-kit': '1.0.0-rc.3',
|
|
97
|
-
pg: '8.22.0',
|
|
115
|
+
pg: '^8.22.0',
|
|
98
116
|
};
|
|
99
117
|
|
|
100
118
|
/**
|
|
@@ -418,21 +436,19 @@ export async function scaffoldApp(name, cwd, opts = {}) {
|
|
|
418
436
|
// for relations v2. SQLite needs NO driver dependency: the connection
|
|
419
437
|
// uses the built-in node:sqlite (Node) / bun:sqlite (Bun) via Drizzle's
|
|
420
438
|
// node-sqlite / bun-sqlite adapters. Postgres still needs the pg driver.
|
|
421
|
-
//
|
|
422
|
-
//
|
|
423
|
-
//
|
|
424
|
-
//
|
|
425
|
-
// versions the scaffolding CLI itself ships with.
|
|
439
|
+
// Since #698 a normal caret range resolves correctly under bun
|
|
440
|
+
// zero-install, so @webjsdev/* and pg are idiomatic `^` ranges (#700).
|
|
441
|
+
// Drizzle stays EXACT: its 1.0 line is a prerelease RC, and bun ENOENTs
|
|
442
|
+
// on a caret-prerelease inline specifier, so a range would break it.
|
|
426
443
|
'drizzle-orm': SCAFFOLD_DEP_VERSIONS['drizzle-orm'],
|
|
427
444
|
...(dialect === 'postgres' ? { pg: SCAFFOLD_DEP_VERSIONS.pg } : {}),
|
|
428
|
-
'@webjsdev/cli':
|
|
429
|
-
'@webjsdev/core':
|
|
430
|
-
'@webjsdev/server':
|
|
445
|
+
'@webjsdev/cli': webjsdevRange('cli'),
|
|
446
|
+
'@webjsdev/core': webjsdevRange('core'),
|
|
447
|
+
'@webjsdev/server': webjsdevRange('server'),
|
|
431
448
|
},
|
|
432
449
|
devDependencies: {
|
|
433
|
-
// Exact pin
|
|
434
|
-
//
|
|
435
|
-
// exact version across runtimes (a range would diverge, #690).
|
|
450
|
+
// Exact pin: drizzle-kit shares drizzle-orm's prerelease 1.0 RC, which bun
|
|
451
|
+
// cannot resolve as a caret-prerelease range, so it stays exact (#700).
|
|
436
452
|
'drizzle-kit': SCAFFOLD_DEP_VERSIONS['drizzle-kit'],
|
|
437
453
|
...(dialect === 'postgres' ? { '@types/pg': '^8.11.0' } : {}),
|
|
438
454
|
// The TypeScript compiler, for `npm run typecheck` (webjs typecheck runs
|
|
@@ -1476,10 +1492,11 @@ For AI agents, read this before editing scaffolded files:
|
|
|
1476
1492
|
}
|
|
1477
1493
|
} else if (isBun) {
|
|
1478
1494
|
// Bun zero-install (#675): no install needed; `bun run dev` resolves deps on
|
|
1479
|
-
// the fly.
|
|
1480
|
-
//
|
|
1495
|
+
// the fly. Since #698 they resolve to their package.json versions (a caret
|
|
1496
|
+
// range to its highest match), so point at `bun install` for a frozen
|
|
1497
|
+
// lockfile and editor type intelligence, not a correctness fix.
|
|
1481
1498
|
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
|
|
1499
|
+
console.log(`These resolve to the versions in package.json. Run 'bun install' in ${name}/ to freeze a lockfile and get editor type intelligence.\n`);
|
|
1483
1500
|
}
|
|
1484
1501
|
|
|
1485
1502
|
// Next-steps banner prints LAST so the actionable command is the
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
// Pin `@webjsdev/*` imports under Bun zero-install (#709).
|
|
2
|
+
//
|
|
3
|
+
// Under Bun zero-install a bare `import('@webjsdev/server')` from the cli (run
|
|
4
|
+
// out of the global cache) ENOENTs: Bun's runtime auto-install ignores the cli's
|
|
5
|
+
// declared range and flakily fetches latest, which fails. An INLINE-versioned
|
|
6
|
+
// specifier (`@webjsdev/server@^0.8.0`) resolves reliably. So we read the version
|
|
7
|
+
// the APP declares (the scaffold adds `@webjsdev/*` to its deps) and retry inline
|
|
8
|
+
// only when the bare import fails, so Node and installed apps are unaffected.
|
|
9
|
+
|
|
10
|
+
import { readFileSync } from 'node:fs';
|
|
11
|
+
import { join, dirname } from 'node:path';
|
|
12
|
+
import { fileURLToPath } from 'node:url';
|
|
13
|
+
|
|
14
|
+
// The cli's OWN package.json (one level up from this lib), used as the fallback
|
|
15
|
+
// pin source for `@webjsdev/*` packages the app does not declare (mcp, ui).
|
|
16
|
+
const CLI_PKG = join(dirname(fileURLToPath(import.meta.url)), '..', 'package.json');
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Whether a declared version is a Bun inline-safe specifier: an exact version or
|
|
20
|
+
* a single caret / tilde / comparator range, but NOT a range over a prerelease
|
|
21
|
+
* (`^1.0.0-rc.3`, which Bun ENOENTs on, #703), a wildcard, a multi-token range,
|
|
22
|
+
* or a protocol range (`workspace:` / `file:`).
|
|
23
|
+
* @param {unknown} v
|
|
24
|
+
* @returns {boolean}
|
|
25
|
+
*/
|
|
26
|
+
export function inlineSafeVersion(v) {
|
|
27
|
+
if (typeof v !== 'string') return false;
|
|
28
|
+
const m = /^(>=|<=|>|<|=|\^|~)?(\d+(?:\.\d+){0,2})([-+][0-9A-Za-z.-]+)?$/.exec(v);
|
|
29
|
+
return !!m && !(m[1] && m[3]);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* The version a `package.json` at `pkgPath` declares for `name`, when it is
|
|
34
|
+
* inline-safe; else null.
|
|
35
|
+
* @param {string} name
|
|
36
|
+
* @param {string} pkgPath absolute path to a package.json
|
|
37
|
+
* @returns {string | null}
|
|
38
|
+
*/
|
|
39
|
+
function declaredIn(name, pkgPath) {
|
|
40
|
+
try {
|
|
41
|
+
const pkg = JSON.parse(readFileSync(pkgPath, 'utf8'));
|
|
42
|
+
const v = { ...pkg.dependencies, ...pkg.devDependencies }[name];
|
|
43
|
+
if (inlineSafeVersion(v)) return v;
|
|
44
|
+
} catch { /* no readable package.json */ }
|
|
45
|
+
return null;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* The inline-safe version to pin `name` to: the app's `package.json` (in `cwd`)
|
|
50
|
+
* first (it declares `@webjsdev/server` etc.), else the cli's OWN package.json
|
|
51
|
+
* (for `@webjsdev/*` the app does not declare, like `mcp` / `ui`). Null if
|
|
52
|
+
* neither has an inline-safe declaration.
|
|
53
|
+
* @param {string} name
|
|
54
|
+
* @param {string} [cwd]
|
|
55
|
+
* @returns {string | null}
|
|
56
|
+
*/
|
|
57
|
+
export function appDeclaredVersion(name, cwd = process.cwd()) {
|
|
58
|
+
return declaredIn(name, join(cwd, 'package.json')) || declaredIn(name, CLI_PKG);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* Whether an import error is a resolution / not-found failure (so a version
|
|
63
|
+
* retry is warranted), vs a genuine load-time throw from the module's own code
|
|
64
|
+
* (which we must NOT retry, to avoid masking it and re-running side effects).
|
|
65
|
+
* @param {unknown} err
|
|
66
|
+
* @returns {boolean}
|
|
67
|
+
*/
|
|
68
|
+
function isResolutionError(err) {
|
|
69
|
+
if (err && /** @type {any} */ (err).code === 'ERR_MODULE_NOT_FOUND') return true;
|
|
70
|
+
const msg = err && String(/** @type {any} */ (err).message || err);
|
|
71
|
+
// Narrow on purpose: Node sets the code above; Bun's auto-install miss is
|
|
72
|
+
// `ENOENT while resolving package '...'`. A broad "cannot find" would match
|
|
73
|
+
// ordinary runtime errors ("Cannot find user") and wrongly trigger a retry.
|
|
74
|
+
return !!msg && /ENOENT|resolving package|module not found/i.test(msg);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* Import an `@webjsdev/*` module, pinning the version under Bun zero-install. On
|
|
79
|
+
* Node or an installed app the bare specifier resolves from `node_modules` (no
|
|
80
|
+
* retry). On a RESOLUTION failure (Bun zero-install), retry with the app's (else
|
|
81
|
+
* the cli's own) declared version inline. A real load-time throw is rethrown,
|
|
82
|
+
* not retried. A subpath (`/check`) is preserved across the rewrite.
|
|
83
|
+
* @param {string} spec e.g. `@webjsdev/server` or `@webjsdev/server/check`
|
|
84
|
+
* @param {(s: string) => Promise<any>} [importer] injectable for tests
|
|
85
|
+
* @returns {Promise<any>}
|
|
86
|
+
*/
|
|
87
|
+
export async function importWebjsdev(spec, importer = (s) => import(s)) {
|
|
88
|
+
try {
|
|
89
|
+
return await importer(spec);
|
|
90
|
+
} catch (err) {
|
|
91
|
+
if (!isResolutionError(err)) throw err;
|
|
92
|
+
const m = /^(@webjsdev\/[^/]+)(\/.*)?$/.exec(spec);
|
|
93
|
+
const pkg = m && m[1];
|
|
94
|
+
const sub = (m && m[2]) || '';
|
|
95
|
+
const v = pkg && appDeclaredVersion(pkg);
|
|
96
|
+
if (v) return await importer(pkg + '@' + v + sub);
|
|
97
|
+
throw err;
|
|
98
|
+
}
|
|
99
|
+
}
|
package/package.json
CHANGED
package/templates/AGENTS.md
CHANGED
|
@@ -415,12 +415,17 @@ bun run dev # or: bun run start (no install step required)
|
|
|
415
415
|
```
|
|
416
416
|
|
|
417
417
|
`bun create` does not run an install on Bun, so a fresh app serves immediately.
|
|
418
|
-
Under zero-install,
|
|
419
|
-
`bun.lock`
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
418
|
+
Under zero-install, Bun's runtime auto-install resolves a BARE import to LATEST
|
|
419
|
+
(it ignores `package.json` and `bun.lock`), so webjs rewrites each declared dep's
|
|
420
|
+
specifier to an inline-versioned one via an `onLoad` transform. The version is the
|
|
421
|
+
`bun.lock` exact when present, else the `package.json` value when it is an
|
|
422
|
+
inline-safe semver (an exact pin, or a caret / tilde / comparator range, which
|
|
423
|
+
Bun resolves to the highest match). A protocol range (`workspace:`, `file:`), a
|
|
424
|
+
wildcard (`*`), and a dist-tag (`latest`) stay at latest. Run `bun install` when
|
|
425
|
+
you want versions frozen identically across machines (it materializes
|
|
426
|
+
`node_modules` from the lockfile) or editor type intelligence. (To run a
|
|
427
|
+
Node-flavored app on Bun instead, force `bun --bun run dev`, which still expects
|
|
428
|
+
an install.)
|
|
424
429
|
|
|
425
430
|
On Node the `.ts` type-stripping is the built-in `module.stripTypeScriptTypes`;
|
|
426
431
|
on Bun (which has no built-in) it comes from `amaro` automatically, so the same
|