@webstir-io/webstir 0.1.1 → 0.1.3
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 +103 -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 +215 -144
- package/src/doctor.ts +164 -0
- package/src/enable-assets.ts +18 -1
- package/src/enable.ts +133 -41
- package/src/execute.ts +30 -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 +25 -14
- package/src/workspace-lock.ts +207 -0
- package/src/workspace-watcher.ts +10 -6
- package/src/workspace.ts +4 -2
- package/src/watch-daemon-client.ts +0 -171
|
@@ -0,0 +1,178 @@
|
|
|
1
|
+
import os from 'node:os';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import { spawnSync } from 'node:child_process';
|
|
4
|
+
import { cp, mkdtemp, readFile, writeFile } from 'node:fs/promises';
|
|
5
|
+
|
|
6
|
+
import { monorepoRoot } from './paths.ts';
|
|
7
|
+
import { resolveRuntimeCommand } from './runtime.ts';
|
|
8
|
+
|
|
9
|
+
const REPO_LOCAL_PACKAGES = new Map<string, string>([
|
|
10
|
+
[
|
|
11
|
+
'@webstir-io/module-contract',
|
|
12
|
+
path.join(monorepoRoot ?? '', 'packages', 'contracts', 'module-contract'),
|
|
13
|
+
],
|
|
14
|
+
[
|
|
15
|
+
'@webstir-io/testing-contract',
|
|
16
|
+
path.join(monorepoRoot ?? '', 'packages', 'contracts', 'testing-contract'),
|
|
17
|
+
],
|
|
18
|
+
[
|
|
19
|
+
'@webstir-io/webstir-frontend',
|
|
20
|
+
path.join(monorepoRoot ?? '', 'packages', 'tooling', 'webstir-frontend'),
|
|
21
|
+
],
|
|
22
|
+
[
|
|
23
|
+
'@webstir-io/webstir-backend',
|
|
24
|
+
path.join(monorepoRoot ?? '', 'packages', 'tooling', 'webstir-backend'),
|
|
25
|
+
],
|
|
26
|
+
[
|
|
27
|
+
'@webstir-io/webstir-testing',
|
|
28
|
+
path.join(monorepoRoot ?? '', 'packages', 'tooling', 'webstir-testing'),
|
|
29
|
+
],
|
|
30
|
+
]);
|
|
31
|
+
|
|
32
|
+
const REPO_LOCAL_TRANSITIVE_DEPENDENCIES = new Map<string, readonly string[]>([
|
|
33
|
+
['@webstir-io/webstir-frontend', ['@webstir-io/module-contract']],
|
|
34
|
+
['@webstir-io/webstir-backend', ['@webstir-io/module-contract']],
|
|
35
|
+
['@webstir-io/webstir-testing', ['@webstir-io/testing-contract']],
|
|
36
|
+
]);
|
|
37
|
+
|
|
38
|
+
export async function prepareExternalWorkspaceCopy(
|
|
39
|
+
workspaceRoot: string,
|
|
40
|
+
tempPrefix: string,
|
|
41
|
+
options: {
|
|
42
|
+
readonly forceLocalPackages?: boolean;
|
|
43
|
+
readonly installStdio?: 'inherit' | 'pipe';
|
|
44
|
+
} = {},
|
|
45
|
+
): Promise<{ readonly workspaceRoot: string; readonly cleanupRoot: string } | null> {
|
|
46
|
+
if (!monorepoRoot || !isExternalWorkspace(workspaceRoot)) {
|
|
47
|
+
return null;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
const tempRoot = await mkdtemp(path.join(os.tmpdir(), tempPrefix));
|
|
51
|
+
const tempWorkspaceRoot = path.join(tempRoot, path.basename(workspaceRoot));
|
|
52
|
+
await cp(workspaceRoot, tempWorkspaceRoot, { recursive: true });
|
|
53
|
+
await materializeRepoLocalWorkspaceDependencies(tempWorkspaceRoot, options);
|
|
54
|
+
|
|
55
|
+
return {
|
|
56
|
+
workspaceRoot: tempWorkspaceRoot,
|
|
57
|
+
cleanupRoot: tempRoot,
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
export async function materializeRepoLocalWorkspaceDependencies(
|
|
62
|
+
workspaceRoot: string,
|
|
63
|
+
options: {
|
|
64
|
+
readonly forceLocalPackages?: boolean;
|
|
65
|
+
readonly installStdio?: 'inherit' | 'pipe';
|
|
66
|
+
} = {},
|
|
67
|
+
): Promise<void> {
|
|
68
|
+
if (!monorepoRoot || !isExternalWorkspace(workspaceRoot)) {
|
|
69
|
+
return;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
const packageJsonPath = path.join(workspaceRoot, 'package.json');
|
|
73
|
+
const packageJson = JSON.parse(await readFile(packageJsonPath, 'utf8')) as {
|
|
74
|
+
dependencies?: Record<string, string>;
|
|
75
|
+
devDependencies?: Record<string, string>;
|
|
76
|
+
overrides?: Record<string, string>;
|
|
77
|
+
};
|
|
78
|
+
const normalized = normalizeRepoLocalDependencySpecs(packageJson, options);
|
|
79
|
+
if (!normalized.changed) {
|
|
80
|
+
return;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
await writeFile(packageJsonPath, `${JSON.stringify(normalized.packageJson, null, 2)}\n`, 'utf8');
|
|
84
|
+
const install = spawnSync(resolveRuntimeCommand(), ['install'], {
|
|
85
|
+
cwd: workspaceRoot,
|
|
86
|
+
env: process.env,
|
|
87
|
+
stdio: options.installStdio ?? 'inherit',
|
|
88
|
+
});
|
|
89
|
+
if (install.error) {
|
|
90
|
+
throw install.error;
|
|
91
|
+
}
|
|
92
|
+
if (install.status !== 0) {
|
|
93
|
+
throw new Error(`Failed to install repo-local workspace dependencies for ${workspaceRoot}.`);
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
export function isExternalWorkspace(workspaceRoot: string): boolean {
|
|
98
|
+
if (!monorepoRoot) {
|
|
99
|
+
return true;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
const relativeToRepo = path.relative(monorepoRoot, workspaceRoot);
|
|
103
|
+
return relativeToRepo.startsWith('..') || path.isAbsolute(relativeToRepo);
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
export function normalizeRepoLocalDependencySpecs(
|
|
107
|
+
source: {
|
|
108
|
+
dependencies?: Record<string, string>;
|
|
109
|
+
devDependencies?: Record<string, string>;
|
|
110
|
+
overrides?: Record<string, string>;
|
|
111
|
+
},
|
|
112
|
+
options: {
|
|
113
|
+
readonly forceLocalPackages?: boolean;
|
|
114
|
+
} = {},
|
|
115
|
+
): {
|
|
116
|
+
readonly packageJson: {
|
|
117
|
+
dependencies?: Record<string, string>;
|
|
118
|
+
devDependencies?: Record<string, string>;
|
|
119
|
+
overrides?: Record<string, string>;
|
|
120
|
+
};
|
|
121
|
+
readonly changed: boolean;
|
|
122
|
+
} {
|
|
123
|
+
let changed = false;
|
|
124
|
+
const packageJson = structuredClone(source);
|
|
125
|
+
|
|
126
|
+
function setLocalOverride(packageName: string, packageRoot: string): void {
|
|
127
|
+
packageJson.overrides ??= {};
|
|
128
|
+
const dependencySpec = `file:${packageRoot}`;
|
|
129
|
+
if (packageJson.overrides[packageName] === dependencySpec) {
|
|
130
|
+
return;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
packageJson.overrides[packageName] = dependencySpec;
|
|
134
|
+
changed = true;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
for (const field of ['dependencies', 'devDependencies'] as const) {
|
|
138
|
+
const entries = packageJson[field];
|
|
139
|
+
if (!entries) {
|
|
140
|
+
continue;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
const localizedPackages = new Set<string>();
|
|
144
|
+
for (const [packageName, packageRoot] of REPO_LOCAL_PACKAGES) {
|
|
145
|
+
if (!entries[packageName]) {
|
|
146
|
+
continue;
|
|
147
|
+
}
|
|
148
|
+
if (!options.forceLocalPackages && entries[packageName] !== 'workspace:*') {
|
|
149
|
+
continue;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
entries[packageName] = `file:${packageRoot}`;
|
|
153
|
+
setLocalOverride(packageName, packageRoot);
|
|
154
|
+
localizedPackages.add(packageName);
|
|
155
|
+
changed = true;
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
for (const packageName of localizedPackages) {
|
|
159
|
+
for (const dependencyName of REPO_LOCAL_TRANSITIVE_DEPENDENCIES.get(packageName) ?? []) {
|
|
160
|
+
const dependencyRoot = REPO_LOCAL_PACKAGES.get(dependencyName);
|
|
161
|
+
if (!dependencyRoot) {
|
|
162
|
+
continue;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
const dependencySpec = `file:${dependencyRoot}`;
|
|
166
|
+
setLocalOverride(dependencyName, dependencyRoot);
|
|
167
|
+
if (entries[dependencyName] === dependencySpec) {
|
|
168
|
+
continue;
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
entries[dependencyName] = dependencySpec;
|
|
172
|
+
changed = true;
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
return { packageJson, changed };
|
|
178
|
+
}
|
package/src/format.ts
CHANGED
|
@@ -1,11 +1,15 @@
|
|
|
1
|
+
import type { AgentResult } from './agent.ts';
|
|
2
|
+
import type { FrontendInspectResult } from './frontend-inspect.ts';
|
|
1
3
|
import type { EnableResult } from './enable.ts';
|
|
4
|
+
import type { InspectResult } from './inspect.ts';
|
|
5
|
+
import type { DoctorResult } from './doctor.ts';
|
|
2
6
|
import type { InitResult } from './init.ts';
|
|
7
|
+
import type { WebstirOperationDescriptor } from './operations.ts';
|
|
3
8
|
import type { RefreshResult } from './refresh.ts';
|
|
4
9
|
import type { RepairResult } from './repair.ts';
|
|
5
10
|
import type { BackendInspectResult } from './backend-inspect.ts';
|
|
6
11
|
import type { SmokeResult } from './smoke.ts';
|
|
7
12
|
import type { TestCommandResult } from './test.ts';
|
|
8
|
-
import { formatFailedTests } from './test.ts';
|
|
9
13
|
import type { CommandExecutionResult } from './types.ts';
|
|
10
14
|
|
|
11
15
|
export function formatBuildSummary(result: CommandExecutionResult): string {
|
|
@@ -37,11 +41,21 @@ export function formatEnableSummary(result: EnableResult): string {
|
|
|
37
41
|
}
|
|
38
42
|
|
|
39
43
|
export function formatInitSummary(result: InitResult): string {
|
|
40
|
-
return formatWorkspaceMutationSummary(
|
|
44
|
+
return formatWorkspaceMutationSummary(
|
|
45
|
+
'[webstir] init complete',
|
|
46
|
+
result.mode,
|
|
47
|
+
result.workspaceRoot,
|
|
48
|
+
result.changes,
|
|
49
|
+
);
|
|
41
50
|
}
|
|
42
51
|
|
|
43
52
|
export function formatRefreshSummary(result: RefreshResult): string {
|
|
44
|
-
return formatWorkspaceMutationSummary(
|
|
53
|
+
return formatWorkspaceMutationSummary(
|
|
54
|
+
'[webstir] refresh complete',
|
|
55
|
+
result.mode,
|
|
56
|
+
result.workspaceRoot,
|
|
57
|
+
result.changes,
|
|
58
|
+
);
|
|
45
59
|
}
|
|
46
60
|
|
|
47
61
|
export function formatRepairSummary(result: RepairResult): string {
|
|
@@ -65,6 +79,224 @@ export function formatRepairSummary(result: RepairResult): string {
|
|
|
65
79
|
return lines.join('\n');
|
|
66
80
|
}
|
|
67
81
|
|
|
82
|
+
export function formatRepairJson(result: RepairResult): string {
|
|
83
|
+
return JSON.stringify(
|
|
84
|
+
{
|
|
85
|
+
command: 'repair',
|
|
86
|
+
workspaceRoot: result.workspaceRoot,
|
|
87
|
+
mode: result.mode,
|
|
88
|
+
dryRun: result.dryRun,
|
|
89
|
+
changes: result.changes,
|
|
90
|
+
},
|
|
91
|
+
null,
|
|
92
|
+
2,
|
|
93
|
+
);
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
export function formatOperationsSummary(operations: readonly WebstirOperationDescriptor[]): string {
|
|
97
|
+
const lines = ['[webstir] operations', `count: ${operations.length}`];
|
|
98
|
+
|
|
99
|
+
for (const operation of operations) {
|
|
100
|
+
const details = [
|
|
101
|
+
operation.requiresWorkspace ? 'workspace' : 'no-workspace',
|
|
102
|
+
operation.mutatesWorkspace ? 'mutates' : 'read-only',
|
|
103
|
+
operation.supportsJson ? 'json' : 'text',
|
|
104
|
+
operation.stableForMcp ? 'mcp-ready' : 'manual-only',
|
|
105
|
+
operation.workspaceModes ? `modes: ${operation.workspaceModes.join(', ')}` : undefined,
|
|
106
|
+
].filter(Boolean);
|
|
107
|
+
lines.push(
|
|
108
|
+
` - ${operation.id}: ${operation.summary}${details.length > 0 ? ` (${details.join(', ')})` : ''}`,
|
|
109
|
+
);
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
return lines.join('\n');
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
export function formatOperationsJson(operations: readonly WebstirOperationDescriptor[]): string {
|
|
116
|
+
return JSON.stringify(
|
|
117
|
+
{
|
|
118
|
+
command: 'operations',
|
|
119
|
+
operations,
|
|
120
|
+
},
|
|
121
|
+
null,
|
|
122
|
+
2,
|
|
123
|
+
);
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
export function formatDoctorSummary(result: DoctorResult): string {
|
|
127
|
+
const lines = [
|
|
128
|
+
'[webstir] doctor complete',
|
|
129
|
+
`workspace: ${result.workspace.name}`,
|
|
130
|
+
`mode: ${result.workspace.mode}`,
|
|
131
|
+
`root: ${result.workspace.root}`,
|
|
132
|
+
`healthy: ${result.healthy ? 'true' : 'false'}`,
|
|
133
|
+
];
|
|
134
|
+
|
|
135
|
+
lines.push(`checks: ${result.checks.length}`);
|
|
136
|
+
for (const check of result.checks) {
|
|
137
|
+
lines.push(` - ${check.id}: ${check.status} (${check.summary})`);
|
|
138
|
+
if (check.detail) {
|
|
139
|
+
lines.push(` detail: ${check.detail}`);
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
if (result.issues.length === 0) {
|
|
144
|
+
lines.push('issues: none');
|
|
145
|
+
} else {
|
|
146
|
+
lines.push(`issues: ${result.issues.length}`);
|
|
147
|
+
for (const issue of result.issues) {
|
|
148
|
+
lines.push(` - ${issue.code}: ${issue.message}`);
|
|
149
|
+
if (issue.changes && issue.changes.length > 0) {
|
|
150
|
+
for (const change of issue.changes) {
|
|
151
|
+
lines.push(` change: ${change}`);
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
if (result.repair.changes.length > 0) {
|
|
158
|
+
lines.push(`repair: webstir ${result.repair.command} ${result.repair.args.join(' ')}`);
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
return lines.join('\n');
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
export function formatDoctorJson(result: DoctorResult): string {
|
|
165
|
+
return JSON.stringify(
|
|
166
|
+
{
|
|
167
|
+
command: 'doctor',
|
|
168
|
+
workspace: {
|
|
169
|
+
name: result.workspace.name,
|
|
170
|
+
mode: result.workspace.mode,
|
|
171
|
+
root: result.workspace.root,
|
|
172
|
+
},
|
|
173
|
+
healthy: result.healthy,
|
|
174
|
+
checks: result.checks,
|
|
175
|
+
issues: result.issues,
|
|
176
|
+
repair: result.repair,
|
|
177
|
+
...(result.backend ? { backend: result.backend } : {}),
|
|
178
|
+
},
|
|
179
|
+
null,
|
|
180
|
+
2,
|
|
181
|
+
);
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
export function formatFrontendInspectSummary(result: FrontendInspectResult): string {
|
|
185
|
+
const enabledFeatures = listEnabledFlags(result.frontend.packageJson.enable.known);
|
|
186
|
+
const lines = [
|
|
187
|
+
'[webstir] frontend-inspect complete',
|
|
188
|
+
`workspace: ${result.workspace.name}`,
|
|
189
|
+
`mode: ${result.workspace.mode}`,
|
|
190
|
+
`root: ${result.workspace.root}`,
|
|
191
|
+
`app-shell: ${result.frontend.appShell.exists ? 'present' : 'missing'}`,
|
|
192
|
+
`frontend-features: ${enabledFeatures.length > 0 ? enabledFeatures.join(', ') : 'none'}`,
|
|
193
|
+
`pages: ${result.frontend.pages.length}`,
|
|
194
|
+
];
|
|
195
|
+
|
|
196
|
+
for (const page of result.frontend.pages) {
|
|
197
|
+
const details = [
|
|
198
|
+
page.htmlExists ? 'html' : undefined,
|
|
199
|
+
page.stylesheetExists ? 'css' : undefined,
|
|
200
|
+
page.scriptExists ? 'script' : undefined,
|
|
201
|
+
].filter(Boolean);
|
|
202
|
+
lines.push(` - ${page.name}${details.length > 0 ? ` (${details.join(', ')})` : ''}`);
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
lines.push(`content-root: ${result.frontend.content.root}`);
|
|
206
|
+
lines.push(`content-present: ${result.frontend.content.exists ? 'true' : 'false'}`);
|
|
207
|
+
lines.push(
|
|
208
|
+
`content-sidebar-override: ${result.frontend.content.sidebarOverrideExists ? 'true' : 'false'}`,
|
|
209
|
+
);
|
|
210
|
+
|
|
211
|
+
return lines.join('\n');
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
export function formatFrontendInspectJson(result: FrontendInspectResult): string {
|
|
215
|
+
return JSON.stringify(
|
|
216
|
+
{
|
|
217
|
+
command: 'frontend-inspect',
|
|
218
|
+
workspace: {
|
|
219
|
+
name: result.workspace.name,
|
|
220
|
+
mode: result.workspace.mode,
|
|
221
|
+
root: result.workspace.root,
|
|
222
|
+
},
|
|
223
|
+
frontend: result.frontend,
|
|
224
|
+
},
|
|
225
|
+
null,
|
|
226
|
+
2,
|
|
227
|
+
);
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
export function formatInspectSummary(result: InspectResult): string {
|
|
231
|
+
const lines = [
|
|
232
|
+
'[webstir] inspect complete',
|
|
233
|
+
`workspace: ${result.workspace.name}`,
|
|
234
|
+
`mode: ${result.workspace.mode}`,
|
|
235
|
+
`root: ${result.workspace.root}`,
|
|
236
|
+
`success: ${result.success ? 'true' : 'false'}`,
|
|
237
|
+
`steps: ${result.steps.length}`,
|
|
238
|
+
];
|
|
239
|
+
|
|
240
|
+
for (const step of result.steps) {
|
|
241
|
+
lines.push(` - ${step.id}: ${step.status} (${step.summary})`);
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
return lines.join('\n');
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
export function formatInspectJson(result: InspectResult): string {
|
|
248
|
+
return JSON.stringify(
|
|
249
|
+
{
|
|
250
|
+
command: 'inspect',
|
|
251
|
+
workspace: {
|
|
252
|
+
name: result.workspace.name,
|
|
253
|
+
mode: result.workspace.mode,
|
|
254
|
+
root: result.workspace.root,
|
|
255
|
+
},
|
|
256
|
+
success: result.success,
|
|
257
|
+
steps: result.steps,
|
|
258
|
+
doctor: result.doctor,
|
|
259
|
+
...(result.frontend ? { frontend: result.frontend } : {}),
|
|
260
|
+
...(result.backend ? { backend: result.backend } : {}),
|
|
261
|
+
},
|
|
262
|
+
null,
|
|
263
|
+
2,
|
|
264
|
+
);
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
export function formatAgentSummary(result: AgentResult): string {
|
|
268
|
+
const lines = [
|
|
269
|
+
'[webstir] agent complete',
|
|
270
|
+
`goal: ${result.goal}`,
|
|
271
|
+
`root: ${result.workspaceRoot}`,
|
|
272
|
+
`success: ${result.success ? 'true' : 'false'}`,
|
|
273
|
+
`steps: ${result.steps.length}`,
|
|
274
|
+
];
|
|
275
|
+
|
|
276
|
+
for (const step of result.steps) {
|
|
277
|
+
lines.push(` - ${step.id}: ${step.status} (${step.summary})`);
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
return lines.join('\n');
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
function listEnabledFlags(flags: object): string[] {
|
|
284
|
+
return Object.entries(flags)
|
|
285
|
+
.filter(([, enabled]) => enabled === true)
|
|
286
|
+
.map(([name]) => name);
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
export function formatAgentJson(result: AgentResult): string {
|
|
290
|
+
return JSON.stringify(
|
|
291
|
+
{
|
|
292
|
+
command: 'agent',
|
|
293
|
+
...result,
|
|
294
|
+
},
|
|
295
|
+
null,
|
|
296
|
+
2,
|
|
297
|
+
);
|
|
298
|
+
}
|
|
299
|
+
|
|
68
300
|
export function formatBackendInspectSummary(result: BackendInspectResult): string {
|
|
69
301
|
const lines = [
|
|
70
302
|
'[webstir] backend-inspect complete',
|
|
@@ -82,6 +314,12 @@ export function formatBackendInspectSummary(result: BackendInspectResult): strin
|
|
|
82
314
|
lines.push(` - ${route.method} ${route.path}${route.name ? ` (${route.name})` : ''}`);
|
|
83
315
|
}
|
|
84
316
|
|
|
317
|
+
const views = result.manifest.views ?? [];
|
|
318
|
+
lines.push(`views: ${views.length}`);
|
|
319
|
+
for (const view of views) {
|
|
320
|
+
lines.push(` - ${view.path}${view.name ? ` (${view.name})` : ''}`);
|
|
321
|
+
}
|
|
322
|
+
|
|
85
323
|
const jobs = result.manifest.jobs ?? [];
|
|
86
324
|
lines.push(`jobs: ${jobs.length}`);
|
|
87
325
|
for (const job of jobs) {
|
|
@@ -95,9 +333,38 @@ export function formatBackendInspectSummary(result: BackendInspectResult): strin
|
|
|
95
333
|
lines.push(` - ${job.name}${details.length > 0 ? ` (${details.join(', ')})` : ''}`);
|
|
96
334
|
}
|
|
97
335
|
|
|
336
|
+
const migrations = result.data.migrations;
|
|
337
|
+
lines.push('data-migrations:');
|
|
338
|
+
lines.push(` runner: ${migrations.runnerPresent ? 'present' : 'missing'}`);
|
|
339
|
+
lines.push(
|
|
340
|
+
` directory: ${migrations.migrationsDirectoryPresent ? 'present' : 'missing'} (${migrations.migrationsDirectory})`,
|
|
341
|
+
);
|
|
342
|
+
lines.push(` files: ${migrations.migrationFilesCount}`);
|
|
343
|
+
lines.push(` example: ${migrations.exampleMigrationPresent ? 'present' : 'missing'}`);
|
|
344
|
+
lines.push(` table-env: ${migrations.tableEnvKey}`);
|
|
345
|
+
lines.push(` configured-table: ${migrations.configuredTable}`);
|
|
346
|
+
|
|
98
347
|
return lines.join('\n');
|
|
99
348
|
}
|
|
100
349
|
|
|
350
|
+
export function formatBackendInspectJson(result: BackendInspectResult): string {
|
|
351
|
+
return JSON.stringify(
|
|
352
|
+
{
|
|
353
|
+
command: 'backend-inspect',
|
|
354
|
+
workspace: {
|
|
355
|
+
name: result.workspace.name,
|
|
356
|
+
mode: result.workspace.mode,
|
|
357
|
+
root: result.workspace.root,
|
|
358
|
+
},
|
|
359
|
+
buildRoot: result.buildRoot,
|
|
360
|
+
manifest: result.manifest,
|
|
361
|
+
data: result.data,
|
|
362
|
+
},
|
|
363
|
+
null,
|
|
364
|
+
2,
|
|
365
|
+
);
|
|
366
|
+
}
|
|
367
|
+
|
|
101
368
|
export function formatTestSummary(result: TestCommandResult): string {
|
|
102
369
|
const lines = [
|
|
103
370
|
'[webstir] test complete',
|
|
@@ -154,13 +421,9 @@ export function formatAddSummary(
|
|
|
154
421
|
target: string,
|
|
155
422
|
workspaceRoot: string,
|
|
156
423
|
changes: readonly string[],
|
|
157
|
-
note?: string
|
|
424
|
+
note?: string,
|
|
158
425
|
): string {
|
|
159
|
-
const lines = [
|
|
160
|
-
header,
|
|
161
|
-
`target: ${target}`,
|
|
162
|
-
`root: ${workspaceRoot}`,
|
|
163
|
-
];
|
|
426
|
+
const lines = [header, `target: ${target}`, `root: ${workspaceRoot}`];
|
|
164
427
|
|
|
165
428
|
if (changes.length === 0) {
|
|
166
429
|
lines.push('changes: none');
|
|
@@ -195,12 +458,12 @@ function formatExecutionSummary(result: CommandExecutionResult): string {
|
|
|
195
458
|
lines.push(
|
|
196
459
|
`${target.kind}: ${target.result.artifacts.length} artifacts, ` +
|
|
197
460
|
`${target.result.manifest.entryPoints.length} entries, ` +
|
|
198
|
-
`${target.result.manifest.staticAssets.length} static assets -> ${target.outputRoot}
|
|
461
|
+
`${target.result.manifest.staticAssets.length} static assets -> ${target.outputRoot}`,
|
|
199
462
|
);
|
|
200
463
|
|
|
201
464
|
if (diagnostics.errors > 0 || diagnostics.warnings > 0) {
|
|
202
465
|
lines.push(
|
|
203
|
-
`${target.kind}: ${diagnostics.errors} error(s), ${diagnostics.warnings} warning(s), ${diagnostics.info} info
|
|
466
|
+
`${target.kind}: ${diagnostics.errors} error(s), ${diagnostics.warnings} warning(s), ${diagnostics.info} info`,
|
|
204
467
|
);
|
|
205
468
|
}
|
|
206
469
|
}
|
|
@@ -208,17 +471,33 @@ function formatExecutionSummary(result: CommandExecutionResult): string {
|
|
|
208
471
|
return lines.join('\n');
|
|
209
472
|
}
|
|
210
473
|
|
|
474
|
+
function formatFailedTests(
|
|
475
|
+
results: readonly {
|
|
476
|
+
readonly passed: boolean;
|
|
477
|
+
readonly file: string;
|
|
478
|
+
readonly name: string;
|
|
479
|
+
readonly message?: string | null;
|
|
480
|
+
}[],
|
|
481
|
+
): string[] {
|
|
482
|
+
return results
|
|
483
|
+
.filter((result) => !result.passed)
|
|
484
|
+
.map(
|
|
485
|
+
(result) =>
|
|
486
|
+
`${result.file}: ${result.name}${result.message ? ` — ${firstLine(result.message)}` : ''}`,
|
|
487
|
+
);
|
|
488
|
+
}
|
|
489
|
+
|
|
490
|
+
function firstLine(message: string): string {
|
|
491
|
+
return message.split(/\r?\n/, 1)[0] ?? message;
|
|
492
|
+
}
|
|
493
|
+
|
|
211
494
|
function formatWorkspaceMutationSummary(
|
|
212
495
|
header: string,
|
|
213
496
|
mode: string,
|
|
214
497
|
workspaceRoot: string,
|
|
215
|
-
changes: readonly string[]
|
|
498
|
+
changes: readonly string[],
|
|
216
499
|
): string {
|
|
217
|
-
const lines = [
|
|
218
|
-
header,
|
|
219
|
-
`mode: ${mode}`,
|
|
220
|
-
`root: ${workspaceRoot}`,
|
|
221
|
-
];
|
|
500
|
+
const lines = [header, `mode: ${mode}`, `root: ${workspaceRoot}`];
|
|
222
501
|
|
|
223
502
|
if (changes.length === 0) {
|
|
224
503
|
lines.push('changes: none');
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import type { FrontendWorkspaceInspectResult } from '@webstir-io/webstir-frontend';
|
|
2
|
+
|
|
3
|
+
import { inspectFrontendWorkspace } from '@webstir-io/webstir-frontend';
|
|
4
|
+
|
|
5
|
+
import type { WorkspaceDescriptor } from './types.ts';
|
|
6
|
+
|
|
7
|
+
import { readWorkspaceDescriptor } from './workspace.ts';
|
|
8
|
+
|
|
9
|
+
export interface RunFrontendInspectOptions {
|
|
10
|
+
readonly workspaceRoot: string;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export interface FrontendInspectResult {
|
|
14
|
+
readonly workspace: WorkspaceDescriptor;
|
|
15
|
+
readonly frontend: FrontendWorkspaceInspectResult;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export async function runFrontendInspect(
|
|
19
|
+
options: RunFrontendInspectOptions,
|
|
20
|
+
): Promise<FrontendInspectResult> {
|
|
21
|
+
const workspace = await readWorkspaceDescriptor(options.workspaceRoot);
|
|
22
|
+
if (workspace.mode === 'api') {
|
|
23
|
+
throw new Error(
|
|
24
|
+
`frontend-inspect only supports spa, ssg, and full workspaces. Received mode "${workspace.mode}".`,
|
|
25
|
+
);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
return {
|
|
29
|
+
workspace,
|
|
30
|
+
frontend: await inspectFrontendWorkspace(workspace.root),
|
|
31
|
+
};
|
|
32
|
+
}
|