@webjsdev/cli 0.10.40 → 0.10.42
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 +349 -66
- package/lib/create.js +282 -479
- package/lib/doctor.js +44 -39
- package/package.json +5 -1
- package/templates/.agents/rules/workflow.md +61 -271
- package/templates/.agents/skills/webjs/SKILL.md +226 -0
- package/templates/.agents/skills/webjs/references/auth-and-sessions.md +220 -0
- package/templates/.agents/skills/webjs/references/built-ins.md +200 -0
- package/templates/.agents/skills/webjs/references/client-router-and-streaming.md +204 -0
- package/templates/.agents/skills/webjs/references/components.md +167 -0
- package/templates/.agents/skills/webjs/references/data-and-actions.md +187 -0
- package/templates/.agents/skills/webjs/references/muscle-memory-gotchas.md +170 -0
- package/templates/.agents/skills/webjs/references/optimistic-ui.md +128 -0
- package/templates/.agents/skills/webjs/references/routing-and-pages.md +158 -0
- package/templates/.agents/skills/webjs/references/runtime.md +80 -0
- package/templates/.agents/skills/webjs/references/service-worker.md +78 -0
- package/templates/.agents/skills/webjs/references/styling.md +123 -0
- package/templates/.agents/skills/webjs/references/testing.md +125 -0
- package/templates/.agents/skills/webjs/references/typescript.md +148 -0
- package/templates/.claude/hooks/check-server-imports.mjs +1 -1
- package/templates/.claude/hooks/require-tests-with-src.sh +1 -1
- package/templates/.claude/settings.json +0 -14
- package/templates/.cursorrules +21 -189
- package/templates/.github/copilot-instructions.md +7 -185
- package/templates/.github/pull_request_template.md +1 -1
- package/templates/AGENTS.md +59 -1494
- package/templates/CLAUDE.md +0 -1
- package/templates/CONVENTIONS.md +32 -1383
- package/templates/GEMINI.md +11 -0
- package/templates/gallery/app/apple-icon.ts +0 -1
- package/templates/gallery/app/examples/todo/page.ts +0 -1
- package/templates/gallery/app/features/async-render/page.ts +0 -1
- package/templates/gallery/app/features/boundaries/page.ts +0 -1
- package/templates/gallery/app/features/broadcast/page.ts +0 -1
- package/templates/gallery/app/features/caching/page.ts +0 -1
- package/templates/gallery/app/features/client-router/page.ts +0 -1
- package/templates/gallery/app/features/client-router/second/page.ts +0 -1
- package/templates/gallery/app/features/components/page.ts +0 -1
- package/templates/gallery/app/features/directives/page.ts +0 -1
- package/templates/gallery/app/features/env/page.ts +0 -1
- package/templates/gallery/app/features/file-storage/page.ts +0 -1
- package/templates/gallery/app/features/forms/page.ts +0 -1
- package/templates/gallery/app/features/metadata/page.ts +0 -1
- package/templates/gallery/app/features/optimistic-ui/page.ts +0 -1
- package/templates/gallery/app/features/rate-limit/page.ts +0 -1
- package/templates/gallery/app/features/route-handler/page.ts +0 -1
- package/templates/gallery/app/features/routing/page.ts +0 -1
- package/templates/gallery/app/features/server-actions/page.ts +0 -1
- package/templates/gallery/app/features/service-worker/page.ts +0 -1
- package/templates/gallery/app/features/sessions/page.ts +0 -1
- package/templates/gallery/app/features/websockets/page.ts +0 -1
- package/templates/gallery/app/global-error.ts +0 -1
- package/templates/gallery/app/global-not-found.ts +0 -1
- package/templates/gallery/app/icon.ts +0 -1
- package/templates/gallery/app/manifest.ts +0 -1
- package/templates/gallery/app/opengraph-image.ts +0 -1
- package/templates/gallery/app/robots.ts +0 -1
- package/templates/gallery/app/sitemap.ts +0 -1
- package/templates/gallery/app/twitter-image.ts +0 -1
- package/templates/public/favicon.svg +5 -0
- package/templates/public/sw.js +1 -1
- package/templates/scripts/clear-gallery.mjs +95 -0
- package/lib/clear-placeholders.js +0 -98
- package/lib/design-bar.js +0 -67
- package/templates/.claude/hooks/design-review-before-stop.sh +0 -36
- package/templates/.claude/hooks/route-skills.sh +0 -35
- package/templates/.claude/skills/webjs-design-review/SKILL.md +0 -84
- package/templates/LAYOUT-REFERENCE.md +0 -96
- package/templates/lib/utils/ui.ts +0 -83
package/bin/webjs.js
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import { resolve, join, dirname } from 'node:path';
|
|
3
3
|
import { spawn } from 'node:child_process';
|
|
4
|
+
import { readFileSync } from 'node:fs';
|
|
4
5
|
import { fileURLToPath } from 'node:url';
|
|
5
6
|
import { resolveBin } from '../lib/resolve-bin.js';
|
|
6
7
|
import { dbGenerateTtyHint } from '../lib/db-hints.js';
|
|
7
|
-
import { DESIGN_REMINDER, hasUiLayout } from '../lib/design-bar.js';
|
|
8
8
|
import { checkNodeInline, nodeInlineMessage } from '../lib/node-preflight.js';
|
|
9
9
|
import { loadAppEnv, resolvePort } from '../lib/port.js';
|
|
10
10
|
import { planDevSupervisor } from '../lib/dev-supervisor.js';
|
|
@@ -12,6 +12,11 @@ import { planDevSupervisor } from '../lib/dev-supervisor.js';
|
|
|
12
12
|
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
13
13
|
const [cmd, ...rest] = process.argv.slice(2);
|
|
14
14
|
|
|
15
|
+
// A `--help`/`-h` or `--version`/`-v` request (top-level or after a subcommand)
|
|
16
|
+
// must not be gated by the Node-version preflight, so it works on an old Node.
|
|
17
|
+
const wantsHelp = cmd === '--help' || cmd === '-h' || rest.includes('--help') || rest.includes('-h');
|
|
18
|
+
const wantsVersion = cmd === '--version' || cmd === '-v' || cmd === 'version';
|
|
19
|
+
|
|
15
20
|
// Node-version preflight (issue #238), INLINE and dependency-free.
|
|
16
21
|
// This MUST run before any `import @webjsdev/server`: importing the server
|
|
17
22
|
// package links `src/dev.js`, which references Node 24+ builtins, so on an old
|
|
@@ -20,8 +25,9 @@ const [cmd, ...rest] = process.argv.slice(2);
|
|
|
20
25
|
// `../lib/node-preflight.js`, which imports nothing), depending only on
|
|
21
26
|
// `process.versions.node`. The richer `assertNodeVersion` import inside main()
|
|
22
27
|
// stays as belt-and-suspenders for the link-ok (>= 22.13) cases.
|
|
23
|
-
// `help` / no-arg
|
|
24
|
-
|
|
28
|
+
// `help` / no-arg / any `--help`|`-h` / `version`|`--version`|`-v` request is
|
|
29
|
+
// exempt so a user on an old Node can still read usage and the version.
|
|
30
|
+
if (cmd !== 'help' && cmd !== undefined && !wantsHelp && !wantsVersion) {
|
|
25
31
|
let engines = '>=24.0.0';
|
|
26
32
|
try {
|
|
27
33
|
const { readFileSync } = await import('node:fs');
|
|
@@ -47,9 +53,11 @@ const USAGE = `webjs commands:
|
|
|
47
53
|
(--no-hot: run in-process, no hot-reload supervisor)
|
|
48
54
|
webjs start [--port 8080] Start production server (serves source directly, no build step)
|
|
49
55
|
webjs test [--server|--browser] Run server + browser tests
|
|
50
|
-
webjs check [--json]
|
|
56
|
+
webjs check [--json] Run correctness checks (--json emits structured violations)
|
|
57
|
+
webjs routes [--json|--table] [--no-headers] Print the route table (path / owner file / methods). Default tree; --json matches the MCP list_routes shape; --no-headers drops the --table header
|
|
51
58
|
webjs mcp Start the read-only MCP server (routes / actions / components / check)
|
|
52
|
-
webjs doctor
|
|
59
|
+
webjs doctor [--json] [--strict] Verify project health (Node, tsconfig, env, vendor pins, importmap coherence, @webjsdev versions, git hook, page/layout elision).
|
|
60
|
+
--json emits the structured results (with stable codes). --strict also fails the exit on warnings
|
|
53
61
|
webjs types Generate .webjs/routes.d.ts (typed Route union + per-route params)
|
|
54
62
|
webjs typecheck [tsc args...] Type-check the app with the project's tsc --noEmit (non-zero on errors)
|
|
55
63
|
webjs create <name> [--template full-stack|api|saas] [--db sqlite|postgres] [--runtime node|bun] [--no-install] Scaffold a new webjs app
|
|
@@ -71,7 +79,189 @@ const USAGE = `webjs commands:
|
|
|
71
79
|
--download: also downloads bundles for offline production
|
|
72
80
|
webjs vendor unpin <pkg> Remove a specific package from the pin file
|
|
73
81
|
webjs vendor list Show pinned packages with versions and URLs
|
|
74
|
-
webjs
|
|
82
|
+
webjs version Print the installed @webjsdev/cli version (also: webjs --version / -v)
|
|
83
|
+
webjs help [command] Show this help, or per-command usage + examples (e.g. webjs help routes).
|
|
84
|
+
The flag forms work too: webjs --help / -h for this banner, webjs <command> --help / -h for one command`;
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* Per-command help: usage line, one-line summary, an Options table, and an
|
|
88
|
+
* Examples block (#975). `webjs help <cmd>` (and `webjs <cmd> --help`) renders
|
|
89
|
+
* this so an agent sees the exact flags + worked examples instead of guessing
|
|
90
|
+
* from the one-line USAGE row. Keyed by the top-level command; `db` / `ui` /
|
|
91
|
+
* `vendor` document their subcommand shape. Every entry that takes flags lists
|
|
92
|
+
* them in `options` so the flag surface is machine-readable, matching the Remix
|
|
93
|
+
* CLI's per-command Options section.
|
|
94
|
+
* @type {Record<string, { usage: string, summary: string, options?: Array<{ flag: string, description: string }>, examples: string[] }>}
|
|
95
|
+
*/
|
|
96
|
+
const HELP = {
|
|
97
|
+
dev: {
|
|
98
|
+
usage: 'webjs dev [--port <n>] [--no-hot]',
|
|
99
|
+
summary: 'Start the dev server with live reload (source is the runtime, no build step).',
|
|
100
|
+
options: [
|
|
101
|
+
{ flag: '--port <n>', description: 'Port to listen on (else PORT, else 8080).' },
|
|
102
|
+
{ flag: '--no-hot', description: 'Run in-process, without the hot-reload supervisor.' },
|
|
103
|
+
],
|
|
104
|
+
examples: ['webjs dev', 'webjs dev --port 3000', 'webjs dev --no-hot'],
|
|
105
|
+
},
|
|
106
|
+
start: {
|
|
107
|
+
usage: 'webjs start [--port <n>]',
|
|
108
|
+
summary: 'Start the production server (serves source directly, plain HTTP/1.1).',
|
|
109
|
+
options: [
|
|
110
|
+
{ flag: '--port <n>', description: 'Port to listen on (else PORT, else 8080).' },
|
|
111
|
+
],
|
|
112
|
+
examples: ['webjs start', 'webjs start --port 8080', 'PORT=8080 webjs start'],
|
|
113
|
+
},
|
|
114
|
+
test: {
|
|
115
|
+
usage: 'webjs test [--server] [--browser] [--watch]',
|
|
116
|
+
summary: 'Run the app test suites (server-side node:test and/or browser via web-test-runner).',
|
|
117
|
+
options: [
|
|
118
|
+
{ flag: '--server', description: 'Run only the server-side tests.' },
|
|
119
|
+
{ flag: '--browser', description: 'Run only the browser tests.' },
|
|
120
|
+
{ flag: '--watch', description: 'Re-run on change.' },
|
|
121
|
+
],
|
|
122
|
+
examples: ['webjs test', 'webjs test --server', 'webjs test --browser --watch'],
|
|
123
|
+
},
|
|
124
|
+
check: {
|
|
125
|
+
usage: 'webjs check [--rules] [--json]',
|
|
126
|
+
summary: 'Run the correctness checks (report-only, no autofix). Exits non-zero on any violation.',
|
|
127
|
+
options: [
|
|
128
|
+
{ flag: '--json', description: 'Emit the structured violations + summary as JSON (agent-friendly).' },
|
|
129
|
+
{ flag: '--rules', description: 'List the correctness rules instead of running them.' },
|
|
130
|
+
],
|
|
131
|
+
examples: ['webjs check', 'webjs check --json', 'webjs check --rules'],
|
|
132
|
+
},
|
|
133
|
+
routes: {
|
|
134
|
+
usage: 'webjs routes [--json | --table] [--no-headers]',
|
|
135
|
+
summary: 'Print the route table: each page/route path, its owner file, and (for route handlers) its HTTP methods.',
|
|
136
|
+
options: [
|
|
137
|
+
{ flag: '--json', description: 'Emit { pages, apis } as JSON (byte-identical to the MCP list_routes tool).' },
|
|
138
|
+
{ flag: '--table', description: 'Flat aligned KIND / PATH / METHODS / FILE columns.' },
|
|
139
|
+
{ flag: '--no-headers', description: 'Omit the header row (only with --table); easier to pipe.' },
|
|
140
|
+
],
|
|
141
|
+
examples: ['webjs routes', 'webjs routes --table', 'webjs routes --table --no-headers', 'webjs routes --json'],
|
|
142
|
+
},
|
|
143
|
+
doctor: {
|
|
144
|
+
usage: 'webjs doctor [--json] [--strict]',
|
|
145
|
+
summary: 'Verify project health. Each result carries a stable code so an agent branches on the failure kind.',
|
|
146
|
+
options: [
|
|
147
|
+
{ flag: '--json', description: 'Emit the DoctorResult[] (with stable codes) + a summary as JSON.' },
|
|
148
|
+
{ flag: '--strict', description: 'Also fail the exit on warnings, not just hard failures.' },
|
|
149
|
+
],
|
|
150
|
+
examples: ['webjs doctor', 'webjs doctor --json', 'webjs doctor --strict', 'webjs doctor --json --strict'],
|
|
151
|
+
},
|
|
152
|
+
types: {
|
|
153
|
+
usage: 'webjs types',
|
|
154
|
+
summary: 'Generate .webjs/routes.d.ts (the typed Route href union + per-route params).',
|
|
155
|
+
examples: ['webjs types'],
|
|
156
|
+
},
|
|
157
|
+
typecheck: {
|
|
158
|
+
usage: 'webjs typecheck [tsc args...]',
|
|
159
|
+
summary: "Type-check the app with the project's own tsc --noEmit. Extra args pass through to tsc.",
|
|
160
|
+
examples: ['webjs typecheck', 'webjs typecheck --watch'],
|
|
161
|
+
},
|
|
162
|
+
create: {
|
|
163
|
+
usage: 'webjs create <name> [--template full-stack|api|saas] [--db sqlite|postgres] [--runtime node|bun] [--no-install]',
|
|
164
|
+
summary: 'Scaffold a new app. Defaults: full-stack template, Drizzle + SQLite, Node runtime.',
|
|
165
|
+
options: [
|
|
166
|
+
{ flag: '--template <t>', description: 'full-stack (default), api, or saas.' },
|
|
167
|
+
{ flag: '--db <d>', description: 'sqlite (default) or postgres.' },
|
|
168
|
+
{ flag: '--runtime <r>', description: 'node (default) or bun.' },
|
|
169
|
+
{ flag: '--no-install', description: 'Skip the package-manager install step.' },
|
|
170
|
+
],
|
|
171
|
+
examples: [
|
|
172
|
+
'webjs create my-app',
|
|
173
|
+
'webjs create my-api --template api',
|
|
174
|
+
'webjs create my-saas --template saas --db postgres',
|
|
175
|
+
'webjs create my-app --runtime bun',
|
|
176
|
+
],
|
|
177
|
+
},
|
|
178
|
+
db: {
|
|
179
|
+
usage: 'webjs db <generate|migrate|push|studio|seed>',
|
|
180
|
+
summary: 'Database tasks (wraps drizzle-kit); seed runs db/seed.server.ts.',
|
|
181
|
+
examples: ['webjs db generate', 'webjs db migrate', 'webjs db studio', 'webjs db seed'],
|
|
182
|
+
},
|
|
183
|
+
ui: {
|
|
184
|
+
usage: 'webjs ui <init|add|list|view|diff|info> [names...]',
|
|
185
|
+
summary: 'AI-first component library CLI. Requires @webjsdev/ui installed in the project.',
|
|
186
|
+
examples: ['webjs ui init', 'webjs ui add button card', 'webjs ui list'],
|
|
187
|
+
},
|
|
188
|
+
vendor: {
|
|
189
|
+
usage: 'webjs vendor <pin|unpin|list|audit|outdated|update> [--from <provider>] [--download]',
|
|
190
|
+
summary: 'Pin client-side npm packages into .webjs/vendor/importmap.json.',
|
|
191
|
+
options: [
|
|
192
|
+
{ flag: '--from <provider>', description: 'jspm (default), jsdelivr, unpkg, or skypack.' },
|
|
193
|
+
{ flag: '--download', description: 'Also download the bundles for offline production (with pin).' },
|
|
194
|
+
],
|
|
195
|
+
examples: ['webjs vendor pin', 'webjs vendor pin --download', 'webjs vendor list', 'webjs vendor outdated'],
|
|
196
|
+
},
|
|
197
|
+
mcp: {
|
|
198
|
+
usage: 'webjs mcp',
|
|
199
|
+
summary: 'Start the read-only MCP server (routes / actions / components / check + a docs/source knowledge layer).',
|
|
200
|
+
examples: ['webjs mcp'],
|
|
201
|
+
},
|
|
202
|
+
version: {
|
|
203
|
+
usage: 'webjs version',
|
|
204
|
+
summary: 'Print the installed @webjsdev/cli version. Also available as webjs --version / -v.',
|
|
205
|
+
examples: ['webjs version', 'webjs --version'],
|
|
206
|
+
},
|
|
207
|
+
};
|
|
208
|
+
|
|
209
|
+
/**
|
|
210
|
+
* Render `webjs help <cmd>` to stdout: usage, summary, an Options table (when
|
|
211
|
+
* the command takes flags), and Examples, in the Remix-CLI section shape. A
|
|
212
|
+
* universal `-h, --help` option row is appended so every command advertises it.
|
|
213
|
+
* Returns true if the command was found, false otherwise (so the caller can
|
|
214
|
+
* exit non-zero on an unknown help topic, matching the Remix CLI).
|
|
215
|
+
* @param {string} name
|
|
216
|
+
* @returns {boolean}
|
|
217
|
+
*/
|
|
218
|
+
function printCommandHelp(name) {
|
|
219
|
+
const h = HELP[name];
|
|
220
|
+
if (!h) {
|
|
221
|
+
console.error(`Unknown help topic "${name}".\n`);
|
|
222
|
+
console.error(USAGE);
|
|
223
|
+
return false;
|
|
224
|
+
}
|
|
225
|
+
console.log(`Usage: ${h.usage}\n`);
|
|
226
|
+
console.log(` ${h.summary}\n`);
|
|
227
|
+
// A command that forwards to an external tool does NOT show webjs's own help
|
|
228
|
+
// for `--help`; word its `-h, --help` row to say so, rather than the generic
|
|
229
|
+
// "Show this help." which would be false for those three commands.
|
|
230
|
+
const tool = HELP_FLAG_PASSTHROUGH_TOOL[name];
|
|
231
|
+
const helpRow = tool
|
|
232
|
+
? { flag: '-h, --help', description: `Forwarded to ${tool} (this command wraps it).` }
|
|
233
|
+
: { flag: '-h, --help', description: 'Show this help.' };
|
|
234
|
+
const options = [...(h.options || []), helpRow];
|
|
235
|
+
const width = Math.max(...options.map((o) => o.flag.length));
|
|
236
|
+
console.log('Options:');
|
|
237
|
+
for (const o of options) console.log(` ${o.flag.padEnd(width)} ${o.description}`);
|
|
238
|
+
console.log('\nExamples:');
|
|
239
|
+
for (const ex of h.examples) console.log(` ${ex}`);
|
|
240
|
+
return true;
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
/**
|
|
244
|
+
* Commands that forward their remaining args to an external CLI, so a trailing
|
|
245
|
+
* `--help` / `-h` should reach THAT tool's own help, not webjs's. Maps the
|
|
246
|
+
* command to the tool it wraps (used both to skip the help-flag intercept and
|
|
247
|
+
* to word the `-h, --help` row in that command's help accurately).
|
|
248
|
+
* @type {Record<string, string>}
|
|
249
|
+
*/
|
|
250
|
+
const HELP_FLAG_PASSTHROUGH_TOOL = { typecheck: 'tsc', db: 'drizzle-kit', ui: '@webjsdev/ui' };
|
|
251
|
+
const HELP_FLAG_PASSTHROUGH = new Set(Object.keys(HELP_FLAG_PASSTHROUGH_TOOL));
|
|
252
|
+
|
|
253
|
+
/**
|
|
254
|
+
* The installed `@webjsdev/cli` version, read from this package's own
|
|
255
|
+
* package.json. Falls back to `0.0.0` if unreadable (never throws).
|
|
256
|
+
* @returns {string}
|
|
257
|
+
*/
|
|
258
|
+
function readCliVersion() {
|
|
259
|
+
try {
|
|
260
|
+
return JSON.parse(readFileSync(join(__dirname, '..', 'package.json'), 'utf8')).version || '0.0.0';
|
|
261
|
+
} catch {
|
|
262
|
+
return '0.0.0';
|
|
263
|
+
}
|
|
264
|
+
}
|
|
75
265
|
|
|
76
266
|
/** @param {string[]} args */
|
|
77
267
|
function flag(args, name, def) {
|
|
@@ -117,12 +307,34 @@ async function startDevParallelTasks(commands, cwd) {
|
|
|
117
307
|
}
|
|
118
308
|
|
|
119
309
|
async function main() {
|
|
120
|
-
//
|
|
310
|
+
// `--version` / `-v` (top level): print the installed CLI version and exit.
|
|
311
|
+
if (cmd === '--version' || cmd === '-v') {
|
|
312
|
+
console.log(readCliVersion());
|
|
313
|
+
return;
|
|
314
|
+
}
|
|
315
|
+
// `--help` / `-h` (#975): a top-level flag (webjs --help / -h) prints the
|
|
316
|
+
// banner; the same flag AFTER a subcommand (webjs routes --help) prints that
|
|
317
|
+
// command's help and short-circuits it. Handled before the Node preflight so
|
|
318
|
+
// it works even on an old Node. A command that forwards its args to an
|
|
319
|
+
// external CLI (typecheck/db/ui, in HELP_FLAG_PASSTHROUGH) is skipped so the
|
|
320
|
+
// tool's own --help still reaches it; an UNRECOGNISED command is not
|
|
321
|
+
// intercepted either, so it falls through to the Unknown-command error
|
|
322
|
+
// (exit 1) rather than a silent 0.
|
|
323
|
+
if (cmd === '--help' || cmd === '-h') {
|
|
324
|
+
console.log(USAGE);
|
|
325
|
+
return;
|
|
326
|
+
}
|
|
327
|
+
if (cmd && HELP[cmd] && !HELP_FLAG_PASSTHROUGH.has(cmd) && (rest.includes('--help') || rest.includes('-h'))) {
|
|
328
|
+
printCommandHelp(cmd);
|
|
329
|
+
return;
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
// Node preflight: WebJs needs Node 24+ (built-in TS strip + recursive fs.watch).
|
|
121
333
|
// Run before any subcommand so an older Node fails fast with a clear,
|
|
122
334
|
// actionable message naming the found + required version, exiting non-zero
|
|
123
|
-
// instead of crashing cryptically later. `help`
|
|
124
|
-
// old Node can still read usage.
|
|
125
|
-
if (cmd !== 'help' && cmd !== undefined) {
|
|
335
|
+
// instead of crashing cryptically later. `help` / a help or version request
|
|
336
|
+
// is exempt so a user on an old Node can still read usage and the version.
|
|
337
|
+
if (cmd !== 'help' && cmd !== undefined && !wantsHelp && !wantsVersion) {
|
|
126
338
|
const { assertNodeVersion } = await import('@webjsdev/server');
|
|
127
339
|
assertNodeVersion({ onFail: 'exit' });
|
|
128
340
|
}
|
|
@@ -412,37 +624,12 @@ async function main() {
|
|
|
412
624
|
case 'check': {
|
|
413
625
|
const { checkConventions, RULES } = await import('@webjsdev/server/check');
|
|
414
626
|
|
|
415
|
-
// --clear-placeholders: acknowledge the whole scaffold gallery in one
|
|
416
|
-
// command (strip the marker comment lines, keep the demo code), instead of
|
|
417
|
-
// one hand-edit per file. The gate then reflects only real violations.
|
|
418
|
-
if (rest.includes('--clear-placeholders')) {
|
|
419
|
-
const { clearPlaceholders } = await import('../lib/clear-placeholders.js');
|
|
420
|
-
const report = clearPlaceholders(process.cwd());
|
|
421
|
-
const total = report.reduce((n, r) => n + r.markers, 0);
|
|
422
|
-
if (report.length === 0) {
|
|
423
|
-
console.log('webjs check: no scaffold-placeholder markers found (nothing to clear).');
|
|
424
|
-
} else {
|
|
425
|
-
console.log(`webjs check: cleared ${total} scaffold-placeholder marker(s) across ${report.length} file(s):`);
|
|
426
|
-
for (const r of report) console.log(` ${r.file}`);
|
|
427
|
-
console.log('\nThe demo code is kept. Delete any gallery route/module you do not want, then re-run `webjs check`.');
|
|
428
|
-
// Re-surface the design bar the cleared markers carried. The
|
|
429
|
-
// layout/home marker was the just-in-time "adapt this chrome" reminder;
|
|
430
|
-
// stripping it silently is how an app ends up shipping the scaffold
|
|
431
|
-
// shell. Print the bar so clearing the markers cannot quietly drop it,
|
|
432
|
-
// but only for a UI app (the api template has no layout / no chrome).
|
|
433
|
-
if (hasUiLayout(process.cwd())) console.log(DESIGN_REMINDER);
|
|
434
|
-
}
|
|
435
|
-
break;
|
|
436
|
-
}
|
|
437
|
-
|
|
438
627
|
if (rest.includes('--rules')) {
|
|
439
628
|
console.log('webjs check, correctness rules:');
|
|
440
629
|
console.log(' Every rule catches code that is wrong to ship: a crash, a');
|
|
441
|
-
console.log(' security leak, a build/type-strip failure
|
|
442
|
-
console.log('
|
|
443
|
-
console.log('
|
|
444
|
-
console.log(' conventions (layout, style, process) are guidance in');
|
|
445
|
-
console.log(' CONVENTIONS.md, not rules here.\n');
|
|
630
|
+
console.log(' security leak, or a build/type-strip failure. They always');
|
|
631
|
+
console.log(' run. Project conventions (layout, style, process) are');
|
|
632
|
+
console.log(' guidance in CONVENTIONS.md, not rules here.\n');
|
|
446
633
|
for (const r of RULES) {
|
|
447
634
|
console.log(` ${r.name.padEnd(30)} ${r.description}`);
|
|
448
635
|
}
|
|
@@ -469,28 +656,12 @@ async function main() {
|
|
|
469
656
|
console.log('webjs check: all checks pass ✓');
|
|
470
657
|
} else {
|
|
471
658
|
console.log(`webjs check: ${violations.length} violation(s) found\n`);
|
|
472
|
-
// A fresh scaffold trips no-scaffold-placeholder on every unadapted demo
|
|
473
|
-
// file at once. Printing an identical block per file drowns the real
|
|
474
|
-
// feature violations, so collapse the sentinel to ONE grouped summary
|
|
475
|
-
// (with a one-command clear) and print every other rule per-violation.
|
|
476
|
-
// The returned violations and --json are unchanged; only this human
|
|
477
|
-
// printout groups, so agents/tools still see one entry per file.
|
|
478
|
-
const PLACEHOLDER = 'no-scaffold-placeholder';
|
|
479
|
-
const placeholders = violations.filter((v) => v.rule === PLACEHOLDER);
|
|
480
659
|
for (const v of violations) {
|
|
481
|
-
if (v.rule === PLACEHOLDER) continue;
|
|
482
660
|
console.log(` ✗ [${v.rule}] ${v.file}`);
|
|
483
661
|
console.log(` ${v.message}`);
|
|
484
662
|
if (v.fix) console.log(` Fix: ${v.fix}`);
|
|
485
663
|
console.log();
|
|
486
664
|
}
|
|
487
|
-
if (placeholders.length > 0) {
|
|
488
|
-
console.log(` ✗ [${PLACEHOLDER}] ${placeholders.length} file(s) still carry scaffold example content:`);
|
|
489
|
-
for (const v of placeholders) console.log(` ${v.file}`);
|
|
490
|
-
console.log(' Fix: adapt or delete each file, then remove its marker comment line.');
|
|
491
|
-
console.log(' Keeping the gallery? Run `webjs check --clear-placeholders` to clear all markers at once.');
|
|
492
|
-
console.log();
|
|
493
|
-
}
|
|
494
665
|
process.exit(1);
|
|
495
666
|
}
|
|
496
667
|
break;
|
|
@@ -503,14 +674,6 @@ async function main() {
|
|
|
503
674
|
// app's concern, not a broken toolchain).
|
|
504
675
|
const { runDoctorChecks } = await import('../lib/doctor.js');
|
|
505
676
|
const results = await runDoctorChecks(process.cwd());
|
|
506
|
-
const marker = { pass: '[pass]', warn: '[warn]', fail: '[fail]' };
|
|
507
|
-
console.log('webjs doctor: project-health checklist\n');
|
|
508
|
-
for (const r of results) {
|
|
509
|
-
console.log(` ${marker[r.status]} ${r.name}`);
|
|
510
|
-
console.log(` ${r.message}`);
|
|
511
|
-
if (r.fix && r.status !== 'pass') console.log(` Fix: ${r.fix}`);
|
|
512
|
-
console.log();
|
|
513
|
-
}
|
|
514
677
|
const counts = results.reduce((acc, r) => {
|
|
515
678
|
acc[r.status] = (acc[r.status] || 0) + 1;
|
|
516
679
|
return acc;
|
|
@@ -518,15 +681,121 @@ async function main() {
|
|
|
518
681
|
const pass = counts.pass || 0;
|
|
519
682
|
const warn = counts.warn || 0;
|
|
520
683
|
const fail = counts.fail || 0;
|
|
684
|
+
// `--strict` also fails the exit on warnings, so an agent can gate on a
|
|
685
|
+
// fully-clean toolchain (drift / staleness / pin freshness) in a fix loop,
|
|
686
|
+
// not just on a hard toolchain break. Default keeps warnings non-fatal.
|
|
687
|
+
const strict = rest.includes('--strict');
|
|
688
|
+
const failing = fail > 0 || (strict && warn > 0);
|
|
689
|
+
|
|
690
|
+
// --json emits the raw DoctorResult[] (each carries a stable `code`) plus
|
|
691
|
+
// a summary, so an agent consumes structured data instead of scraping the
|
|
692
|
+
// text. Shape mirrors `check --json`: a top-level array-bearing object
|
|
693
|
+
// with a `summary` count. The non-zero exit is preserved (an agent gates
|
|
694
|
+
// on the exit code AND parses the report).
|
|
695
|
+
if (rest.includes('--json')) {
|
|
696
|
+
console.log(JSON.stringify({
|
|
697
|
+
results,
|
|
698
|
+
summary: { pass, warn, fail, strict, ok: !failing },
|
|
699
|
+
}));
|
|
700
|
+
if (failing) process.exit(1);
|
|
701
|
+
break;
|
|
702
|
+
}
|
|
703
|
+
|
|
704
|
+
const marker = { pass: '[pass]', warn: '[warn]', fail: '[fail]' };
|
|
705
|
+
console.log('webjs doctor: project-health checklist\n');
|
|
706
|
+
for (const r of results) {
|
|
707
|
+
console.log(` ${marker[r.status]} ${r.name} (${r.code})`);
|
|
708
|
+
console.log(` ${r.message}`);
|
|
709
|
+
if (r.fix && r.status !== 'pass') console.log(` Fix: ${r.fix}`);
|
|
710
|
+
console.log();
|
|
711
|
+
}
|
|
521
712
|
console.log(` ${pass} passed, ${warn} warning(s), ${fail} failed.`);
|
|
522
|
-
if (
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
713
|
+
if (failing) {
|
|
714
|
+
const reason = fail > 0
|
|
715
|
+
? `${fail} hard check(s) failed. Fix the toolchain issue(s) above.`
|
|
716
|
+
: `${warn} warning(s) found and --strict was set.`;
|
|
717
|
+
console.error(`\nwebjs doctor: ${reason}`);
|
|
526
718
|
process.exit(1);
|
|
527
719
|
}
|
|
528
720
|
break;
|
|
529
721
|
}
|
|
722
|
+
case 'routes': {
|
|
723
|
+
// Print the app route table to stdout (#975): every page (path, owner
|
|
724
|
+
// file, dynamic params) and every route.{js,ts} API handler (path, owner
|
|
725
|
+
// file, HTTP methods). Reuses the ONE route walker (`buildRouteTable`, the
|
|
726
|
+
// same walk that backs `webjs types` and the dev server) and the shared
|
|
727
|
+
// `projectRoutes` projector, so `--json` is byte-identical to the MCP
|
|
728
|
+
// `list_routes` tool. Read-only: no module load, no autofix.
|
|
729
|
+
const { buildRouteTable } = await import('@webjsdev/server');
|
|
730
|
+
const { projectRoutes } = await import('@webjsdev/mcp/routes-report');
|
|
731
|
+
const { extractRouteMethods } = await import('@webjsdev/mcp');
|
|
732
|
+
const { readFile } = await import('node:fs/promises');
|
|
733
|
+
const appDir = process.cwd();
|
|
734
|
+
const table = await buildRouteTable(appDir);
|
|
735
|
+
const report = await projectRoutes(table, { appDir, readFile, extractRouteMethods });
|
|
736
|
+
|
|
737
|
+
// --json: the machine contract, identical to the MCP `list_routes` shape.
|
|
738
|
+
if (rest.includes('--json')) {
|
|
739
|
+
console.log(JSON.stringify(report));
|
|
740
|
+
break;
|
|
741
|
+
}
|
|
742
|
+
|
|
743
|
+
const { pages, apis } = report;
|
|
744
|
+
// A page is reached via GET (its server render); a route.{js,ts} exposes
|
|
745
|
+
// exactly its exported verbs. Present both as one path -> methods -> file
|
|
746
|
+
// view. Dynamic pages append their param names.
|
|
747
|
+
const pageMethods = 'GET';
|
|
748
|
+
|
|
749
|
+
// --table: flat, aligned columns (KIND / PATH / METHODS / FILE), the
|
|
750
|
+
// easiest shape for an agent to scan or a human to grep. `--no-headers`
|
|
751
|
+
// drops the header row so the output pipes cleanly into awk/cut.
|
|
752
|
+
if (rest.includes('--table')) {
|
|
753
|
+
/** @type {Array<[string,string,string,string]>} */
|
|
754
|
+
const rows = [];
|
|
755
|
+
if (!rest.includes('--no-headers')) rows.push(['KIND', 'PATH', 'METHODS', 'FILE']);
|
|
756
|
+
for (const p of pages) {
|
|
757
|
+
rows.push(['page', p.path + (p.params ? ` [${p.params.join(', ')}]` : ''), pageMethods, p.file]);
|
|
758
|
+
}
|
|
759
|
+
for (const a of apis) {
|
|
760
|
+
rows.push(['api', a.path, a.methods.join(', ') || '(none)', a.file]);
|
|
761
|
+
}
|
|
762
|
+
// With --no-headers and no routes there are no rows; nothing to print.
|
|
763
|
+
if (rows.length === 0) break;
|
|
764
|
+
const widths = [0, 1, 2].map((c) => Math.max(...rows.map((r) => r[c].length)));
|
|
765
|
+
for (const r of rows) {
|
|
766
|
+
console.log(
|
|
767
|
+
`${r[0].padEnd(widths[0])} ${r[1].padEnd(widths[1])} ${r[2].padEnd(widths[2])} ${r[3]}`,
|
|
768
|
+
);
|
|
769
|
+
}
|
|
770
|
+
break;
|
|
771
|
+
}
|
|
772
|
+
|
|
773
|
+
// Default: a grouped tree.
|
|
774
|
+
console.log(`webjs routes: ${pages.length} page(s), ${apis.length} API route(s)\n`);
|
|
775
|
+
if (pages.length) {
|
|
776
|
+
console.log('Pages');
|
|
777
|
+
const pathW = Math.max(...pages.map((p) => p.path.length));
|
|
778
|
+
for (const p of pages) {
|
|
779
|
+
const params = p.params ? ` ${p.params.map((n) => `[${n}]`).join(' ')}` : '';
|
|
780
|
+
console.log(` ${p.path.padEnd(pathW)} ${p.file}${params}`);
|
|
781
|
+
}
|
|
782
|
+
console.log();
|
|
783
|
+
}
|
|
784
|
+
if (apis.length) {
|
|
785
|
+
console.log('API routes');
|
|
786
|
+
const pathW = Math.max(...apis.map((a) => a.path.length));
|
|
787
|
+
const methW = Math.max(...apis.map((a) => (a.methods.join(', ') || '(none)').length));
|
|
788
|
+
for (const a of apis) {
|
|
789
|
+
const methods = a.methods.join(', ') || '(none)';
|
|
790
|
+
console.log(` ${a.path.padEnd(pathW)} ${methods.padEnd(methW)} ${a.file}`);
|
|
791
|
+
}
|
|
792
|
+
console.log();
|
|
793
|
+
}
|
|
794
|
+
if (!pages.length && !apis.length) {
|
|
795
|
+
console.log(' No routes found. Add an app/page.ts or an app/**/route.ts.');
|
|
796
|
+
}
|
|
797
|
+
break;
|
|
798
|
+
}
|
|
530
799
|
case 'types': {
|
|
531
800
|
// Generate `.webjs/routes.d.ts` from the app's `app/` routes (#258),
|
|
532
801
|
// narrowing the @webjsdev/core `Route` href union + per-route `params`.
|
|
@@ -941,7 +1210,21 @@ Full docs: https://docs.webjs.dev`);
|
|
|
941
1210
|
});
|
|
942
1211
|
break;
|
|
943
1212
|
}
|
|
1213
|
+
case 'version':
|
|
1214
|
+
// Print the installed CLI version (#975). Also reachable as the top-level
|
|
1215
|
+
// `webjs --version` / `-v` flag, handled at the top of main().
|
|
1216
|
+
console.log(readCliVersion());
|
|
1217
|
+
break;
|
|
944
1218
|
case 'help':
|
|
1219
|
+
// `webjs help <cmd>` prints that command's usage + Options + Examples
|
|
1220
|
+
// (#975); a bare `webjs help` prints the full banner. An unknown topic
|
|
1221
|
+
// exits non-zero (printCommandHelp returns false), matching the Remix CLI.
|
|
1222
|
+
if (rest[0]) {
|
|
1223
|
+
if (!printCommandHelp(rest[0])) process.exit(1);
|
|
1224
|
+
} else {
|
|
1225
|
+
console.log(USAGE);
|
|
1226
|
+
}
|
|
1227
|
+
break;
|
|
945
1228
|
case undefined:
|
|
946
1229
|
console.log(USAGE);
|
|
947
1230
|
break;
|