@webstir-io/webstir 0.1.0 → 0.1.2
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 +13 -0
- package/assets/deployment/docker/.dockerignore +7 -0
- package/assets/deployment/docker/Dockerfile +17 -0
- package/assets/deployment/docker/README.md +44 -0
- package/assets/deployment/docker/example.env +3 -0
- package/assets/features/client_nav/client_nav.ts +369 -264
- package/assets/features/client_nav/document_navigation.ts +344 -0
- package/assets/features/client_nav/form_enhancement.ts +275 -0
- package/assets/templates/api/src/backend/index.ts +71 -10
- package/assets/templates/api/src/backend/tsconfig.json +6 -1
- package/assets/templates/full/src/backend/index.ts +71 -10
- package/assets/templates/full/src/backend/module.ts +515 -0
- package/assets/templates/full/src/backend/tests/progressive-enhancement.test.ts +180 -0
- package/assets/templates/full/src/backend/tsconfig.json +6 -1
- package/assets/templates/full/src/frontend/app/scripts/features/client-nav.ts +574 -0
- package/assets/templates/full/src/frontend/app/scripts/features/document-navigation.ts +344 -0
- package/assets/templates/full/src/frontend/app/scripts/features/form-enhancement.ts +275 -0
- package/assets/templates/full/src/frontend/pages/home/index.css +8 -0
- package/assets/templates/full/src/frontend/pages/home/index.html +6 -1
- package/assets/templates/full/src/frontend/pages/home/tests/home.test.ts +12 -2
- package/assets/templates/spa/src/frontend/pages/home/tests/home.test.ts +10 -2
- package/package.json +31 -13
- package/scripts/check-feature-projections.mjs +87 -0
- package/scripts/check-full-demo-sync.mjs +89 -0
- package/scripts/check-package-install.mjs +537 -0
- package/scripts/check-standalone-install.mjs +221 -0
- package/scripts/pack-standalone.mjs +52 -28
- package/scripts/publish.sh +9 -0
- package/scripts/run-tests.mjs +99 -0
- package/scripts/sync-assets.mjs +175 -17
- package/src/add-backend-compat.ts +628 -0
- package/src/add-backend.ts +155 -27
- package/src/add.ts +111 -4
- package/src/agent.ts +393 -0
- package/src/api-watch.ts +7 -4
- package/src/backend-inspect.ts +70 -2
- package/src/backend-runtime.ts +22 -14
- package/src/build.ts +1 -3
- package/src/bun-generated-frontend-watch.ts +209 -0
- package/src/bun-globals.d.ts +23 -0
- package/src/bun-spa-document.ts +310 -0
- package/src/bun-spa-routes.ts +159 -0
- package/src/bun-spa-watch.ts +29 -0
- package/src/bun-ssg-watch.ts +304 -0
- package/src/cli.ts +381 -50
- package/src/compile-tests.ts +37 -29
- package/src/dev-server.ts +214 -143
- package/src/doctor.ts +164 -0
- package/src/enable-assets.ts +18 -1
- package/src/enable.ts +133 -41
- package/src/execute.ts +28 -4
- package/src/external-workspace.ts +178 -0
- package/src/format.ts +296 -17
- package/src/frontend-inspect.ts +32 -0
- package/src/frontend-watch.ts +27 -102
- package/src/full-watch.ts +13 -18
- package/src/index.ts +7 -0
- package/src/init-assets.ts +41 -11
- package/src/init.ts +85 -71
- package/src/inspect.ts +112 -0
- package/src/mcp/run-cli-json.ts +46 -0
- package/src/mcp/server.ts +307 -0
- package/src/operations.ts +176 -0
- package/src/providers.ts +20 -18
- package/src/refresh.ts +29 -3
- package/src/repair.ts +110 -43
- package/src/runtime-filter.ts +41 -0
- package/src/runtime.ts +1 -1
- package/src/smoke.ts +48 -16
- package/src/test.ts +54 -16
- package/src/testing-runtime.ts +273 -0
- package/src/types.ts +1 -4
- package/src/watch-events.ts +46 -17
- package/src/watch.ts +5 -1
- package/src/workspace-watcher.ts +10 -6
- package/src/workspace.ts +4 -2
- package/src/watch-daemon-client.ts +0 -171
package/src/cli.ts
CHANGED
|
@@ -4,30 +4,30 @@ import path from 'node:path';
|
|
|
4
4
|
import { realpathSync } from 'node:fs';
|
|
5
5
|
import { fileURLToPath } from 'node:url';
|
|
6
6
|
|
|
7
|
-
import { runAddPageCommand, runAddTestCommand } from './add.ts';
|
|
8
|
-
import { runAddJobCommand, runAddRouteCommand } from './add-backend.ts';
|
|
9
|
-
import { runBackendInspect } from './backend-inspect.ts';
|
|
10
|
-
import { runEnable } from './enable.ts';
|
|
11
7
|
import {
|
|
12
8
|
formatAddSummary,
|
|
9
|
+
formatAgentJson,
|
|
10
|
+
formatAgentSummary,
|
|
11
|
+
formatBackendInspectJson,
|
|
13
12
|
formatBackendInspectSummary,
|
|
14
13
|
formatBuildSummary,
|
|
14
|
+
formatDoctorJson,
|
|
15
|
+
formatDoctorSummary,
|
|
15
16
|
formatEnableSummary,
|
|
17
|
+
formatFrontendInspectJson,
|
|
18
|
+
formatFrontendInspectSummary,
|
|
16
19
|
formatInitSummary,
|
|
20
|
+
formatInspectJson,
|
|
21
|
+
formatInspectSummary,
|
|
22
|
+
formatOperationsJson,
|
|
23
|
+
formatOperationsSummary,
|
|
17
24
|
formatPublishSummary,
|
|
25
|
+
formatRepairJson,
|
|
18
26
|
formatRepairSummary,
|
|
19
27
|
formatRefreshSummary,
|
|
20
28
|
formatSmokeSummary,
|
|
21
29
|
formatTestSummary,
|
|
22
30
|
} from './format.ts';
|
|
23
|
-
import { runInit } from './init.ts';
|
|
24
|
-
import { runRepair } from './repair.ts';
|
|
25
|
-
import { runRefresh } from './refresh.ts';
|
|
26
|
-
import { runBuild } from './build.ts';
|
|
27
|
-
import { runPublish } from './publish.ts';
|
|
28
|
-
import { runSmoke } from './smoke.ts';
|
|
29
|
-
import { runTest } from './test.ts';
|
|
30
|
-
import { runWatch } from './watch.ts';
|
|
31
31
|
|
|
32
32
|
interface CliStream {
|
|
33
33
|
write(message: string): void;
|
|
@@ -43,13 +43,19 @@ const HELP_TEXT = `Usage:
|
|
|
43
43
|
webstir init <directory>
|
|
44
44
|
webstir add-page <name> --workspace <path>
|
|
45
45
|
webstir add-test <name-or-path> --workspace <path>
|
|
46
|
-
webstir add-route <name> --workspace <path> [--method <METHOD>] [--path <path>] [--
|
|
46
|
+
webstir add-route <name> --workspace <path> [--method <METHOD>] [--path <path>] [--interaction <navigation|mutation>] [--session <optional|required>] [--session-write] [--form-urlencoded] [--csrf] [--fragment-target <target>] [--fragment-selector <selector>] [--fragment-mode <replace|append|prepend>]
|
|
47
47
|
webstir add-job <name> --workspace <path> [--schedule <expression>]
|
|
48
|
+
webstir operations
|
|
49
|
+
webstir mcp
|
|
50
|
+
webstir agent <inspect|validate|repair|scaffold-page|scaffold-route|scaffold-job> [...]
|
|
51
|
+
webstir inspect --workspace <path>
|
|
52
|
+
webstir frontend-inspect --workspace <path>
|
|
48
53
|
webstir backend-inspect --workspace <path>
|
|
54
|
+
webstir doctor --workspace <path>
|
|
49
55
|
webstir test --workspace <path> [--runtime <frontend|backend|all>]
|
|
50
56
|
webstir smoke [--workspace <path>]
|
|
51
57
|
webstir build --workspace <path>
|
|
52
|
-
webstir publish --workspace <path>
|
|
58
|
+
webstir publish --workspace <path> [--frontend-mode <bundle|ssg>]
|
|
53
59
|
webstir enable <feature> [feature-args...] --workspace <path>
|
|
54
60
|
webstir repair --workspace <path> [--dry-run]
|
|
55
61
|
webstir refresh <mode> --workspace <path>
|
|
@@ -61,7 +67,13 @@ Commands:
|
|
|
61
67
|
add-test Scaffold a test file in an existing workspace.
|
|
62
68
|
add-route Scaffold a backend route in an existing workspace.
|
|
63
69
|
add-job Scaffold a backend job in an existing workspace.
|
|
70
|
+
operations List the stable Webstir framework operations.
|
|
71
|
+
mcp Run the Webstir MCP server over stdio.
|
|
72
|
+
agent Orchestrate stable Webstir operations for a narrow goal.
|
|
73
|
+
inspect Diagnose and surface stable frontend and backend contract data.
|
|
74
|
+
frontend-inspect Inspect stable frontend workspace facts for an existing workspace.
|
|
64
75
|
backend-inspect Inspect the backend manifest for an existing workspace.
|
|
76
|
+
doctor Diagnose scaffold drift and backend health for an existing workspace.
|
|
65
77
|
test Build and run workspace tests with the Bun orchestrator.
|
|
66
78
|
smoke Run an end-to-end Bun orchestrator verification flow.
|
|
67
79
|
build Build a Webstir workspace with the Bun orchestrator.
|
|
@@ -75,7 +87,9 @@ Options:
|
|
|
75
87
|
-w, --workspace <path> Workspace root to operate on.
|
|
76
88
|
--host <host> Dev host or bind address (default: 127.0.0.1).
|
|
77
89
|
--port <port> Dev port (SPA default: 8088, API default: 4321).
|
|
90
|
+
--frontend-mode <mode> Frontend publish mode for publish (defaults to bundle; bundle or ssg).
|
|
78
91
|
--dry-run Report repair changes without writing files.
|
|
92
|
+
--json Emit machine-readable JSON for supported commands.
|
|
79
93
|
-v, --verbose Enable verbose frontend watch diagnostics.
|
|
80
94
|
--hmr-verbose Enable detailed hot-update diagnostics.
|
|
81
95
|
-h, --help Show this help text.
|
|
@@ -89,27 +103,33 @@ export async function runCli(argv: readonly string[], io: CliIo = defaultIo): Pr
|
|
|
89
103
|
|
|
90
104
|
const [command, ...rest] = argv;
|
|
91
105
|
if (
|
|
92
|
-
command !== 'init'
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
+
command !== 'init' &&
|
|
107
|
+
command !== 'add-page' &&
|
|
108
|
+
command !== 'add-test' &&
|
|
109
|
+
command !== 'add-route' &&
|
|
110
|
+
command !== 'add-job' &&
|
|
111
|
+
command !== 'operations' &&
|
|
112
|
+
command !== 'mcp' &&
|
|
113
|
+
command !== 'agent' &&
|
|
114
|
+
command !== 'inspect' &&
|
|
115
|
+
command !== 'frontend-inspect' &&
|
|
116
|
+
command !== 'backend-inspect' &&
|
|
117
|
+
command !== 'doctor' &&
|
|
118
|
+
command !== 'test' &&
|
|
119
|
+
command !== 'smoke' &&
|
|
120
|
+
command !== 'build' &&
|
|
121
|
+
command !== 'publish' &&
|
|
122
|
+
command !== 'enable' &&
|
|
123
|
+
command !== 'repair' &&
|
|
124
|
+
command !== 'refresh' &&
|
|
125
|
+
command !== 'watch'
|
|
106
126
|
) {
|
|
107
127
|
io.stderr.write(`Unknown command "${command}".\n\n${HELP_TEXT}`);
|
|
108
128
|
return 1;
|
|
109
129
|
}
|
|
110
130
|
|
|
111
131
|
const options = parseCommandOptions(rest, {
|
|
112
|
-
allowUnknownOptions: command === 'add-route' || command === 'add-job',
|
|
132
|
+
allowUnknownOptions: command === 'add-route' || command === 'add-job' || command === 'agent',
|
|
113
133
|
});
|
|
114
134
|
if (options.help) {
|
|
115
135
|
io.stdout.write(HELP_TEXT);
|
|
@@ -126,8 +146,35 @@ export async function runCli(argv: readonly string[], io: CliIo = defaultIo): Pr
|
|
|
126
146
|
return 1;
|
|
127
147
|
}
|
|
128
148
|
|
|
149
|
+
if (
|
|
150
|
+
options.json &&
|
|
151
|
+
command !== 'operations' &&
|
|
152
|
+
command !== 'agent' &&
|
|
153
|
+
command !== 'inspect' &&
|
|
154
|
+
command !== 'frontend-inspect' &&
|
|
155
|
+
command !== 'backend-inspect' &&
|
|
156
|
+
command !== 'doctor' &&
|
|
157
|
+
command !== 'repair'
|
|
158
|
+
) {
|
|
159
|
+
io.stderr.write(
|
|
160
|
+
`Only operations, agent, inspect, frontend-inspect, backend-inspect, doctor, and repair accept --json.\n\n${HELP_TEXT}`,
|
|
161
|
+
);
|
|
162
|
+
return 1;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
if (options.frontendMode && command !== 'publish') {
|
|
166
|
+
io.stderr.write(`Only publish accepts --frontend-mode.\n\n${HELP_TEXT}`);
|
|
167
|
+
return 1;
|
|
168
|
+
}
|
|
169
|
+
|
|
129
170
|
const workspaceRoot = options.workspaceRoot;
|
|
130
|
-
if (
|
|
171
|
+
if (
|
|
172
|
+
command !== 'init' &&
|
|
173
|
+
command !== 'smoke' &&
|
|
174
|
+
command !== 'operations' &&
|
|
175
|
+
command !== 'mcp' &&
|
|
176
|
+
!workspaceRoot
|
|
177
|
+
) {
|
|
131
178
|
io.stderr.write(`Missing required --workspace <path>.\n\n${HELP_TEXT}`);
|
|
132
179
|
return 1;
|
|
133
180
|
}
|
|
@@ -139,6 +186,7 @@ export async function runCli(argv: readonly string[], io: CliIo = defaultIo): Pr
|
|
|
139
186
|
return 1;
|
|
140
187
|
}
|
|
141
188
|
|
|
189
|
+
const { runInit } = await import('./init.ts');
|
|
142
190
|
const result = await runInit({
|
|
143
191
|
args: options.positionals,
|
|
144
192
|
workspaceRoot,
|
|
@@ -148,18 +196,63 @@ export async function runCli(argv: readonly string[], io: CliIo = defaultIo): Pr
|
|
|
148
196
|
}
|
|
149
197
|
|
|
150
198
|
const resolvedWorkspaceRoot = workspaceRoot ? path.resolve(workspaceRoot) : undefined;
|
|
199
|
+
const requireWorkspaceRoot = (): string => {
|
|
200
|
+
if (!resolvedWorkspaceRoot) {
|
|
201
|
+
throw new Error('Missing required --workspace <path>.');
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
return resolvedWorkspaceRoot;
|
|
205
|
+
};
|
|
206
|
+
if (command === 'operations') {
|
|
207
|
+
if (options.host || options.port !== undefined || options.verbose || options.hmrVerbose) {
|
|
208
|
+
io.stderr.write(`Operations does not accept watch options.\n\n${HELP_TEXT}`);
|
|
209
|
+
return 1;
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
if (options.positionals.length > 0) {
|
|
213
|
+
io.stderr.write(`Operations does not accept positional arguments.\n\n${HELP_TEXT}`);
|
|
214
|
+
return 1;
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
const { listOperations } = await import('./operations.ts');
|
|
218
|
+
const operations = listOperations();
|
|
219
|
+
io.stdout.write(
|
|
220
|
+
`${options.json ? formatOperationsJson(operations) : formatOperationsSummary(operations)}\n`,
|
|
221
|
+
);
|
|
222
|
+
return 0;
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
if (command === 'mcp') {
|
|
226
|
+
if (
|
|
227
|
+
options.host ||
|
|
228
|
+
options.port !== undefined ||
|
|
229
|
+
options.verbose ||
|
|
230
|
+
options.hmrVerbose ||
|
|
231
|
+
options.positionals.length > 0 ||
|
|
232
|
+
options.workspaceRoot ||
|
|
233
|
+
options.json
|
|
234
|
+
) {
|
|
235
|
+
io.stderr.write(`MCP does not accept CLI options.\n\n${HELP_TEXT}`);
|
|
236
|
+
return 1;
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
const { runMcpServer } = await import('./mcp/server.ts');
|
|
240
|
+
await runMcpServer();
|
|
241
|
+
return 0;
|
|
242
|
+
}
|
|
151
243
|
if (command === 'add-page') {
|
|
152
244
|
if (options.host || options.port !== undefined || options.verbose || options.hmrVerbose) {
|
|
153
245
|
io.stderr.write(`Add-page does not accept watch options.\n\n${HELP_TEXT}`);
|
|
154
246
|
return 1;
|
|
155
247
|
}
|
|
156
248
|
|
|
249
|
+
const { runAddPageCommand } = await import('./add.ts');
|
|
157
250
|
const result = await runAddPageCommand({
|
|
158
|
-
workspaceRoot:
|
|
251
|
+
workspaceRoot: requireWorkspaceRoot(),
|
|
159
252
|
args: options.positionals,
|
|
160
253
|
});
|
|
161
254
|
io.stdout.write(
|
|
162
|
-
`${formatAddSummary('[webstir] add-page complete', result.target, result.workspaceRoot, result.changes, result.note)}\n
|
|
255
|
+
`${formatAddSummary('[webstir] add-page complete', result.target, result.workspaceRoot, result.changes, result.note)}\n`,
|
|
163
256
|
);
|
|
164
257
|
return 0;
|
|
165
258
|
}
|
|
@@ -170,12 +263,13 @@ export async function runCli(argv: readonly string[], io: CliIo = defaultIo): Pr
|
|
|
170
263
|
return 1;
|
|
171
264
|
}
|
|
172
265
|
|
|
266
|
+
const { runAddTestCommand } = await import('./add.ts');
|
|
173
267
|
const result = await runAddTestCommand({
|
|
174
|
-
workspaceRoot:
|
|
268
|
+
workspaceRoot: requireWorkspaceRoot(),
|
|
175
269
|
args: options.positionals,
|
|
176
270
|
});
|
|
177
271
|
io.stdout.write(
|
|
178
|
-
`${formatAddSummary('[webstir] add-test complete', result.target, result.workspaceRoot, result.changes, result.note)}\n
|
|
272
|
+
`${formatAddSummary('[webstir] add-test complete', result.target, result.workspaceRoot, result.changes, result.note)}\n`,
|
|
179
273
|
);
|
|
180
274
|
return 0;
|
|
181
275
|
}
|
|
@@ -186,12 +280,13 @@ export async function runCli(argv: readonly string[], io: CliIo = defaultIo): Pr
|
|
|
186
280
|
return 1;
|
|
187
281
|
}
|
|
188
282
|
|
|
283
|
+
const { runAddRouteCommand } = await import('./add-backend.ts');
|
|
189
284
|
const result = await runAddRouteCommand({
|
|
190
|
-
workspaceRoot:
|
|
285
|
+
workspaceRoot: requireWorkspaceRoot(),
|
|
191
286
|
rawArgs: options.rawArgs,
|
|
192
287
|
});
|
|
193
288
|
io.stdout.write(
|
|
194
|
-
`${formatAddSummary('[webstir] add-route complete', result.target, result.workspaceRoot, result.changes, result.note)}\n
|
|
289
|
+
`${formatAddSummary('[webstir] add-route complete', result.target, result.workspaceRoot, result.changes, result.note)}\n`,
|
|
195
290
|
);
|
|
196
291
|
return 0;
|
|
197
292
|
}
|
|
@@ -202,12 +297,13 @@ export async function runCli(argv: readonly string[], io: CliIo = defaultIo): Pr
|
|
|
202
297
|
return 1;
|
|
203
298
|
}
|
|
204
299
|
|
|
300
|
+
const { runAddJobCommand } = await import('./add-backend.ts');
|
|
205
301
|
const result = await runAddJobCommand({
|
|
206
|
-
workspaceRoot:
|
|
302
|
+
workspaceRoot: requireWorkspaceRoot(),
|
|
207
303
|
rawArgs: options.rawArgs,
|
|
208
304
|
});
|
|
209
305
|
io.stdout.write(
|
|
210
|
-
`${formatAddSummary('[webstir] add-job complete', result.target, result.workspaceRoot, result.changes, result.note)}\n
|
|
306
|
+
`${formatAddSummary('[webstir] add-job complete', result.target, result.workspaceRoot, result.changes, result.note)}\n`,
|
|
211
307
|
);
|
|
212
308
|
return 0;
|
|
213
309
|
}
|
|
@@ -223,21 +319,130 @@ export async function runCli(argv: readonly string[], io: CliIo = defaultIo): Pr
|
|
|
223
319
|
return 1;
|
|
224
320
|
}
|
|
225
321
|
|
|
226
|
-
const
|
|
227
|
-
|
|
322
|
+
const { runBackendInspect } = await import('./backend-inspect.ts');
|
|
323
|
+
const result = options.json
|
|
324
|
+
? await withSuppressedStdout(() =>
|
|
325
|
+
runBackendInspect({
|
|
326
|
+
workspaceRoot: requireWorkspaceRoot(),
|
|
327
|
+
}),
|
|
328
|
+
)
|
|
329
|
+
: await runBackendInspect({
|
|
330
|
+
workspaceRoot: requireWorkspaceRoot(),
|
|
331
|
+
});
|
|
332
|
+
io.stdout.write(
|
|
333
|
+
`${options.json ? formatBackendInspectJson(result) : formatBackendInspectSummary(result)}\n`,
|
|
334
|
+
);
|
|
335
|
+
return 0;
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
if (command === 'frontend-inspect') {
|
|
339
|
+
if (options.host || options.port !== undefined || options.verbose || options.hmrVerbose) {
|
|
340
|
+
io.stderr.write(`Frontend-inspect does not accept watch options.\n\n${HELP_TEXT}`);
|
|
341
|
+
return 1;
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
if (options.positionals.length > 0) {
|
|
345
|
+
io.stderr.write(`Frontend-inspect does not accept positional arguments.\n\n${HELP_TEXT}`);
|
|
346
|
+
return 1;
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
const { runFrontendInspect } = await import('./frontend-inspect.ts');
|
|
350
|
+
const result = await runFrontendInspect({
|
|
351
|
+
workspaceRoot: requireWorkspaceRoot(),
|
|
228
352
|
});
|
|
229
|
-
io.stdout.write(
|
|
353
|
+
io.stdout.write(
|
|
354
|
+
`${options.json ? formatFrontendInspectJson(result) : formatFrontendInspectSummary(result)}\n`,
|
|
355
|
+
);
|
|
230
356
|
return 0;
|
|
231
357
|
}
|
|
232
358
|
|
|
359
|
+
if (command === 'doctor') {
|
|
360
|
+
if (options.host || options.port !== undefined || options.verbose || options.hmrVerbose) {
|
|
361
|
+
io.stderr.write(`Doctor does not accept watch options.\n\n${HELP_TEXT}`);
|
|
362
|
+
return 1;
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
if (options.positionals.length > 0) {
|
|
366
|
+
io.stderr.write(`Doctor does not accept positional arguments.\n\n${HELP_TEXT}`);
|
|
367
|
+
return 1;
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
const { runDoctor } = await import('./doctor.ts');
|
|
371
|
+
const result = await withSuppressedStdout(() =>
|
|
372
|
+
runDoctor({
|
|
373
|
+
workspaceRoot: requireWorkspaceRoot(),
|
|
374
|
+
}),
|
|
375
|
+
);
|
|
376
|
+
io.stdout.write(`${options.json ? formatDoctorJson(result) : formatDoctorSummary(result)}\n`);
|
|
377
|
+
return result.healthy ? 0 : 1;
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
if (command === 'inspect') {
|
|
381
|
+
if (options.host || options.port !== undefined || options.verbose || options.hmrVerbose) {
|
|
382
|
+
io.stderr.write(`Inspect does not accept watch options.\n\n${HELP_TEXT}`);
|
|
383
|
+
return 1;
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
if (options.positionals.length > 0) {
|
|
387
|
+
io.stderr.write(`Inspect does not accept positional arguments.\n\n${HELP_TEXT}`);
|
|
388
|
+
return 1;
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
const { runInspect } = await import('./inspect.ts');
|
|
392
|
+
const result = await withSuppressedStdout(() =>
|
|
393
|
+
runInspect({
|
|
394
|
+
workspaceRoot: requireWorkspaceRoot(),
|
|
395
|
+
}),
|
|
396
|
+
);
|
|
397
|
+
io.stdout.write(
|
|
398
|
+
`${options.json ? formatInspectJson(result) : formatInspectSummary(result)}\n`,
|
|
399
|
+
);
|
|
400
|
+
return result.success ? 0 : 1;
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
if (command === 'agent') {
|
|
404
|
+
if (options.host || options.port !== undefined || options.verbose || options.hmrVerbose) {
|
|
405
|
+
io.stderr.write(`Agent does not accept watch options.\n\n${HELP_TEXT}`);
|
|
406
|
+
return 1;
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
const goal = options.positionals[0];
|
|
410
|
+
if (
|
|
411
|
+
goal !== 'inspect' &&
|
|
412
|
+
goal !== 'validate' &&
|
|
413
|
+
goal !== 'repair' &&
|
|
414
|
+
goal !== 'scaffold-page' &&
|
|
415
|
+
goal !== 'scaffold-route' &&
|
|
416
|
+
goal !== 'scaffold-job'
|
|
417
|
+
) {
|
|
418
|
+
io.stderr.write(
|
|
419
|
+
`Agent requires one of: inspect, validate, repair, scaffold-page, scaffold-route, scaffold-job.\n\n${HELP_TEXT}`,
|
|
420
|
+
);
|
|
421
|
+
return 1;
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
const { runAgent } = await import('./agent.ts');
|
|
425
|
+
const result = await withSuppressedStdout(() =>
|
|
426
|
+
runAgent({
|
|
427
|
+
workspaceRoot: requireWorkspaceRoot(),
|
|
428
|
+
goal,
|
|
429
|
+
rawArgs: options.rawArgs,
|
|
430
|
+
positionals: options.positionals,
|
|
431
|
+
}),
|
|
432
|
+
);
|
|
433
|
+
io.stdout.write(`${options.json ? formatAgentJson(result) : formatAgentSummary(result)}\n`);
|
|
434
|
+
return result.success ? 0 : 1;
|
|
435
|
+
}
|
|
436
|
+
|
|
233
437
|
if (command === 'test') {
|
|
234
438
|
if (options.host || options.port !== undefined || options.verbose || options.hmrVerbose) {
|
|
235
439
|
io.stderr.write(`Test does not accept watch options.\n\n${HELP_TEXT}`);
|
|
236
440
|
return 1;
|
|
237
441
|
}
|
|
238
442
|
|
|
443
|
+
const { runTest } = await import('./test.ts');
|
|
239
444
|
const result = await runTest({
|
|
240
|
-
workspaceRoot:
|
|
445
|
+
workspaceRoot: requireWorkspaceRoot(),
|
|
241
446
|
rawArgs: options.rawArgs,
|
|
242
447
|
});
|
|
243
448
|
io.stdout.write(`${formatTestSummary(result)}\n`);
|
|
@@ -255,6 +460,7 @@ export async function runCli(argv: readonly string[], io: CliIo = defaultIo): Pr
|
|
|
255
460
|
return 1;
|
|
256
461
|
}
|
|
257
462
|
|
|
463
|
+
const { runSmoke } = await import('./smoke.ts');
|
|
258
464
|
const result = await runSmoke({
|
|
259
465
|
workspaceRoot: resolvedWorkspaceRoot,
|
|
260
466
|
});
|
|
@@ -268,8 +474,9 @@ export async function runCli(argv: readonly string[], io: CliIo = defaultIo): Pr
|
|
|
268
474
|
return 1;
|
|
269
475
|
}
|
|
270
476
|
|
|
477
|
+
const { runBuild } = await import('./build.ts');
|
|
271
478
|
const result = await runBuild({
|
|
272
|
-
workspaceRoot:
|
|
479
|
+
workspaceRoot: requireWorkspaceRoot(),
|
|
273
480
|
});
|
|
274
481
|
io.stdout.write(`${formatBuildSummary(result)}\n`);
|
|
275
482
|
return 0;
|
|
@@ -281,16 +488,19 @@ export async function runCli(argv: readonly string[], io: CliIo = defaultIo): Pr
|
|
|
281
488
|
return 1;
|
|
282
489
|
}
|
|
283
490
|
|
|
491
|
+
const { runPublish } = await import('./publish.ts');
|
|
284
492
|
const result = await runPublish({
|
|
285
|
-
workspaceRoot:
|
|
493
|
+
workspaceRoot: requireWorkspaceRoot(),
|
|
494
|
+
env: options.frontendMode ? { WEBSTIR_FRONTEND_MODE: options.frontendMode } : undefined,
|
|
286
495
|
});
|
|
287
496
|
io.stdout.write(`${formatPublishSummary(result)}\n`);
|
|
288
497
|
return 0;
|
|
289
498
|
}
|
|
290
499
|
|
|
291
500
|
if (command === 'enable') {
|
|
501
|
+
const { runEnable } = await import('./enable.ts');
|
|
292
502
|
const result = await runEnable({
|
|
293
|
-
workspaceRoot:
|
|
503
|
+
workspaceRoot: requireWorkspaceRoot(),
|
|
294
504
|
args: options.positionals,
|
|
295
505
|
});
|
|
296
506
|
io.stdout.write(`${formatEnableSummary(result)}\n`);
|
|
@@ -308,11 +518,12 @@ export async function runCli(argv: readonly string[], io: CliIo = defaultIo): Pr
|
|
|
308
518
|
return 1;
|
|
309
519
|
}
|
|
310
520
|
|
|
521
|
+
const { runRepair } = await import('./repair.ts');
|
|
311
522
|
const result = await runRepair({
|
|
312
|
-
workspaceRoot:
|
|
523
|
+
workspaceRoot: requireWorkspaceRoot(),
|
|
313
524
|
rawArgs: options.rawArgs,
|
|
314
525
|
});
|
|
315
|
-
io.stdout.write(`${formatRepairSummary(result)}\n`);
|
|
526
|
+
io.stdout.write(`${options.json ? formatRepairJson(result) : formatRepairSummary(result)}\n`);
|
|
316
527
|
return 0;
|
|
317
528
|
}
|
|
318
529
|
|
|
@@ -322,8 +533,9 @@ export async function runCli(argv: readonly string[], io: CliIo = defaultIo): Pr
|
|
|
322
533
|
return 1;
|
|
323
534
|
}
|
|
324
535
|
|
|
536
|
+
const { runRefresh } = await import('./refresh.ts');
|
|
325
537
|
const result = await runRefresh({
|
|
326
|
-
workspaceRoot:
|
|
538
|
+
workspaceRoot: requireWorkspaceRoot(),
|
|
327
539
|
args: options.positionals,
|
|
328
540
|
});
|
|
329
541
|
io.stdout.write(`${formatRefreshSummary(result)}\n`);
|
|
@@ -335,8 +547,9 @@ export async function runCli(argv: readonly string[], io: CliIo = defaultIo): Pr
|
|
|
335
547
|
return 1;
|
|
336
548
|
}
|
|
337
549
|
|
|
550
|
+
const { runWatch } = await import('./watch.ts');
|
|
338
551
|
await runWatch({
|
|
339
|
-
workspaceRoot:
|
|
552
|
+
workspaceRoot: requireWorkspaceRoot(),
|
|
340
553
|
host: options.host,
|
|
341
554
|
port: options.port,
|
|
342
555
|
verbose: options.verbose,
|
|
@@ -355,7 +568,9 @@ interface ParsedCommandOptions {
|
|
|
355
568
|
readonly workspaceRoot?: string;
|
|
356
569
|
readonly host?: string;
|
|
357
570
|
readonly port?: number;
|
|
571
|
+
readonly frontendMode?: 'bundle' | 'ssg';
|
|
358
572
|
readonly dryRun: boolean;
|
|
573
|
+
readonly json: boolean;
|
|
359
574
|
readonly verbose: boolean;
|
|
360
575
|
readonly hmrVerbose: boolean;
|
|
361
576
|
readonly positionals: readonly string[];
|
|
@@ -366,12 +581,14 @@ interface ParsedCommandOptions {
|
|
|
366
581
|
|
|
367
582
|
function parseCommandOptions(
|
|
368
583
|
args: readonly string[],
|
|
369
|
-
options: { readonly allowUnknownOptions?: boolean } = {}
|
|
584
|
+
options: { readonly allowUnknownOptions?: boolean } = {},
|
|
370
585
|
): ParsedCommandOptions {
|
|
371
586
|
let workspaceRoot: string | undefined;
|
|
372
587
|
let host: string | undefined;
|
|
373
588
|
let port: number | undefined;
|
|
589
|
+
let frontendMode: 'bundle' | 'ssg' | undefined;
|
|
374
590
|
let dryRun = false;
|
|
591
|
+
let json = false;
|
|
375
592
|
let verbose = false;
|
|
376
593
|
let hmrVerbose = false;
|
|
377
594
|
const positionals: string[] = [];
|
|
@@ -390,7 +607,9 @@ function parseCommandOptions(
|
|
|
390
607
|
workspaceRoot,
|
|
391
608
|
host,
|
|
392
609
|
port,
|
|
610
|
+
frontendMode,
|
|
393
611
|
dryRun,
|
|
612
|
+
json,
|
|
394
613
|
verbose,
|
|
395
614
|
hmrVerbose,
|
|
396
615
|
positionals,
|
|
@@ -405,6 +624,89 @@ function parseCommandOptions(
|
|
|
405
624
|
continue;
|
|
406
625
|
}
|
|
407
626
|
|
|
627
|
+
if (arg === '--frontend-mode') {
|
|
628
|
+
const next = args[index + 1];
|
|
629
|
+
if (!next || next.startsWith('-')) {
|
|
630
|
+
return {
|
|
631
|
+
workspaceRoot,
|
|
632
|
+
host,
|
|
633
|
+
port,
|
|
634
|
+
frontendMode,
|
|
635
|
+
dryRun,
|
|
636
|
+
json,
|
|
637
|
+
verbose,
|
|
638
|
+
hmrVerbose,
|
|
639
|
+
positionals,
|
|
640
|
+
rawArgs: args,
|
|
641
|
+
help: false,
|
|
642
|
+
error: 'Missing value for --frontend-mode.',
|
|
643
|
+
};
|
|
644
|
+
}
|
|
645
|
+
|
|
646
|
+
const normalizedMode = next.toLowerCase();
|
|
647
|
+
if (normalizedMode !== 'bundle' && normalizedMode !== 'ssg') {
|
|
648
|
+
return {
|
|
649
|
+
workspaceRoot,
|
|
650
|
+
host,
|
|
651
|
+
port,
|
|
652
|
+
frontendMode,
|
|
653
|
+
dryRun,
|
|
654
|
+
json,
|
|
655
|
+
verbose,
|
|
656
|
+
hmrVerbose,
|
|
657
|
+
positionals,
|
|
658
|
+
rawArgs: args,
|
|
659
|
+
help: false,
|
|
660
|
+
error: `Invalid --frontend-mode value "${next}". Expected bundle or ssg.`,
|
|
661
|
+
};
|
|
662
|
+
}
|
|
663
|
+
|
|
664
|
+
frontendMode = normalizedMode;
|
|
665
|
+
index += 1;
|
|
666
|
+
continue;
|
|
667
|
+
}
|
|
668
|
+
|
|
669
|
+
if (arg.startsWith('--frontend-mode=')) {
|
|
670
|
+
const rawMode = arg.slice('--frontend-mode='.length);
|
|
671
|
+
if (rawMode.length === 0) {
|
|
672
|
+
return {
|
|
673
|
+
workspaceRoot,
|
|
674
|
+
host,
|
|
675
|
+
port,
|
|
676
|
+
frontendMode,
|
|
677
|
+
dryRun,
|
|
678
|
+
json,
|
|
679
|
+
verbose,
|
|
680
|
+
hmrVerbose,
|
|
681
|
+
positionals,
|
|
682
|
+
rawArgs: args,
|
|
683
|
+
help: false,
|
|
684
|
+
error: 'Missing value for --frontend-mode.',
|
|
685
|
+
};
|
|
686
|
+
}
|
|
687
|
+
|
|
688
|
+
const normalizedMode = rawMode.toLowerCase();
|
|
689
|
+
if (normalizedMode !== 'bundle' && normalizedMode !== 'ssg') {
|
|
690
|
+
return {
|
|
691
|
+
workspaceRoot,
|
|
692
|
+
host,
|
|
693
|
+
port,
|
|
694
|
+
frontendMode,
|
|
695
|
+
dryRun,
|
|
696
|
+
json,
|
|
697
|
+
verbose,
|
|
698
|
+
hmrVerbose,
|
|
699
|
+
positionals,
|
|
700
|
+
rawArgs: args,
|
|
701
|
+
help: false,
|
|
702
|
+
error: `Invalid --frontend-mode value "${rawMode}". Expected bundle or ssg.`,
|
|
703
|
+
};
|
|
704
|
+
}
|
|
705
|
+
|
|
706
|
+
frontendMode = normalizedMode;
|
|
707
|
+
continue;
|
|
708
|
+
}
|
|
709
|
+
|
|
408
710
|
if (arg === '--host') {
|
|
409
711
|
const next = args[index + 1];
|
|
410
712
|
if (!next || next.startsWith('-')) {
|
|
@@ -413,6 +715,7 @@ function parseCommandOptions(
|
|
|
413
715
|
host,
|
|
414
716
|
port,
|
|
415
717
|
dryRun,
|
|
718
|
+
json,
|
|
416
719
|
verbose,
|
|
417
720
|
hmrVerbose,
|
|
418
721
|
positionals,
|
|
@@ -436,6 +739,7 @@ function parseCommandOptions(
|
|
|
436
739
|
host,
|
|
437
740
|
port,
|
|
438
741
|
dryRun,
|
|
742
|
+
json,
|
|
439
743
|
verbose,
|
|
440
744
|
hmrVerbose,
|
|
441
745
|
positionals,
|
|
@@ -458,6 +762,7 @@ function parseCommandOptions(
|
|
|
458
762
|
host,
|
|
459
763
|
port,
|
|
460
764
|
dryRun,
|
|
765
|
+
json,
|
|
461
766
|
verbose,
|
|
462
767
|
hmrVerbose,
|
|
463
768
|
positionals,
|
|
@@ -480,6 +785,11 @@ function parseCommandOptions(
|
|
|
480
785
|
continue;
|
|
481
786
|
}
|
|
482
787
|
|
|
788
|
+
if (arg === '--json') {
|
|
789
|
+
json = true;
|
|
790
|
+
continue;
|
|
791
|
+
}
|
|
792
|
+
|
|
483
793
|
if (arg === '--hmr-verbose') {
|
|
484
794
|
hmrVerbose = true;
|
|
485
795
|
continue;
|
|
@@ -491,6 +801,7 @@ function parseCommandOptions(
|
|
|
491
801
|
host,
|
|
492
802
|
port,
|
|
493
803
|
dryRun,
|
|
804
|
+
json,
|
|
494
805
|
verbose,
|
|
495
806
|
hmrVerbose,
|
|
496
807
|
positionals,
|
|
@@ -513,6 +824,7 @@ function parseCommandOptions(
|
|
|
513
824
|
host,
|
|
514
825
|
port,
|
|
515
826
|
dryRun,
|
|
827
|
+
json,
|
|
516
828
|
verbose,
|
|
517
829
|
hmrVerbose,
|
|
518
830
|
positionals,
|
|
@@ -526,7 +838,9 @@ function parseCommandOptions(
|
|
|
526
838
|
workspaceRoot,
|
|
527
839
|
host,
|
|
528
840
|
port,
|
|
841
|
+
frontendMode,
|
|
529
842
|
dryRun,
|
|
843
|
+
json,
|
|
530
844
|
verbose,
|
|
531
845
|
hmrVerbose,
|
|
532
846
|
positionals,
|
|
@@ -567,3 +881,20 @@ function resolveRealpath(filePath: string): string {
|
|
|
567
881
|
return path.resolve(filePath);
|
|
568
882
|
}
|
|
569
883
|
}
|
|
884
|
+
|
|
885
|
+
async function withSuppressedStdout<T>(callback: () => Promise<T>): Promise<T> {
|
|
886
|
+
const originalWrite = process.stdout.write.bind(process.stdout);
|
|
887
|
+
const originalLog = console.log;
|
|
888
|
+
const originalInfo = console.info;
|
|
889
|
+
process.stdout.write = (() => true) as typeof process.stdout.write;
|
|
890
|
+
console.log = (() => undefined) as typeof console.log;
|
|
891
|
+
console.info = (() => undefined) as typeof console.info;
|
|
892
|
+
|
|
893
|
+
try {
|
|
894
|
+
return await callback();
|
|
895
|
+
} finally {
|
|
896
|
+
process.stdout.write = originalWrite;
|
|
897
|
+
console.log = originalLog;
|
|
898
|
+
console.info = originalInfo;
|
|
899
|
+
}
|
|
900
|
+
}
|