minutework 0.1.0
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/EXTERNAL_ALPHA.md +74 -0
- package/README.md +57 -0
- package/assets/claude-local/CLAUDE.md.template +45 -0
- package/assets/claude-local/bundle.json +22 -0
- package/assets/claude-local/skills/README.md +6 -0
- package/assets/claude-local/skills/app-pack-authoring.md +8 -0
- package/assets/claude-local/skills/event-bus.md +8 -0
- package/assets/claude-local/skills/ontology-mapping.md +8 -0
- package/assets/claude-local/skills/openclaw-skill-importer.md +7 -0
- package/assets/claude-local/skills/schema-engine.md +8 -0
- package/assets/claude-local/skills/secrets-runtime-bridge.md +9 -0
- package/assets/claude-local/skills/sidecar-generation.md +9 -0
- package/assets/templates/fastapi-sidecar/.env.example +8 -0
- package/assets/templates/fastapi-sidecar/README.md +77 -0
- package/assets/templates/fastapi-sidecar/poetry.lock +757 -0
- package/assets/templates/fastapi-sidecar/pyproject.toml +42 -0
- package/assets/templates/fastapi-sidecar/src/fastapi_sidecar/__init__.py +3 -0
- package/assets/templates/fastapi-sidecar/src/fastapi_sidecar/auth.py +70 -0
- package/assets/templates/fastapi-sidecar/src/fastapi_sidecar/bridge/__init__.py +3 -0
- package/assets/templates/fastapi-sidecar/src/fastapi_sidecar/bridge/client.py +71 -0
- package/assets/templates/fastapi-sidecar/src/fastapi_sidecar/logging_utils.py +25 -0
- package/assets/templates/fastapi-sidecar/src/fastapi_sidecar/main.py +85 -0
- package/assets/templates/fastapi-sidecar/src/fastapi_sidecar/receipts.py +24 -0
- package/assets/templates/fastapi-sidecar/src/fastapi_sidecar/settings.py +41 -0
- package/assets/templates/fastapi-sidecar/src/fastapi_sidecar/template_validation.py +26 -0
- package/assets/templates/fastapi-sidecar/src/fastapi_sidecar/worker.py +33 -0
- package/assets/templates/fastapi-sidecar/template.json +43 -0
- package/assets/templates/fastapi-sidecar/template.schema.json +160 -0
- package/assets/templates/fastapi-sidecar/tests/conftest.py +36 -0
- package/assets/templates/fastapi-sidecar/tests/test_app.py +39 -0
- package/assets/templates/fastapi-sidecar/tests/test_auth.py +32 -0
- package/assets/templates/fastapi-sidecar/tests/test_bridge_client.py +31 -0
- package/assets/templates/fastapi-sidecar/tests/test_materialization.py +55 -0
- package/assets/templates/fastapi-sidecar/tests/test_template_contract.py +49 -0
- package/assets/templates/fastapi-sidecar/tests/test_worker.py +7 -0
- package/assets/templates/fastapi-sidecar/tools/template/validate_template.py +20 -0
- package/assets/templates/next-tenant-app/.env.example +8 -0
- package/assets/templates/next-tenant-app/.storybook/main.ts +19 -0
- package/assets/templates/next-tenant-app/.storybook/preview.tsx +38 -0
- package/assets/templates/next-tenant-app/README.md +115 -0
- package/assets/templates/next-tenant-app/components.json +21 -0
- package/assets/templates/next-tenant-app/eslint.config.mjs +41 -0
- package/assets/templates/next-tenant-app/next-env.d.ts +6 -0
- package/assets/templates/next-tenant-app/next.config.ts +8 -0
- package/assets/templates/next-tenant-app/package-lock.json +9682 -0
- package/assets/templates/next-tenant-app/package.json +59 -0
- package/assets/templates/next-tenant-app/pnpm-lock.yaml +6062 -0
- package/assets/templates/next-tenant-app/postcss.config.mjs +8 -0
- package/assets/templates/next-tenant-app/src/app/api/auth/context/route.test.ts +90 -0
- package/assets/templates/next-tenant-app/src/app/api/auth/context/route.ts +78 -0
- package/assets/templates/next-tenant-app/src/app/api/auth/login/route.ts +31 -0
- package/assets/templates/next-tenant-app/src/app/api/auth/logout/route.ts +16 -0
- package/assets/templates/next-tenant-app/src/app/api/auth/password-change/route.test.ts +79 -0
- package/assets/templates/next-tenant-app/src/app/api/auth/password-change/route.ts +40 -0
- package/assets/templates/next-tenant-app/src/app/api/auth/password-status/route.test.ts +42 -0
- package/assets/templates/next-tenant-app/src/app/api/auth/password-status/route.ts +29 -0
- package/assets/templates/next-tenant-app/src/app/api/auth/session/route.ts +26 -0
- package/assets/templates/next-tenant-app/src/app/api/gateway/commands/[runId]/route.test.ts +40 -0
- package/assets/templates/next-tenant-app/src/app/api/gateway/commands/[runId]/route.ts +47 -0
- package/assets/templates/next-tenant-app/src/app/api/gateway/commands/route.test.ts +43 -0
- package/assets/templates/next-tenant-app/src/app/api/gateway/commands/route.ts +45 -0
- package/assets/templates/next-tenant-app/src/app/app/examples/runtime-commands/page.test.ts +83 -0
- package/assets/templates/next-tenant-app/src/app/app/examples/runtime-commands/page.tsx +30 -0
- package/assets/templates/next-tenant-app/src/app/app/layout.tsx +20 -0
- package/assets/templates/next-tenant-app/src/app/app/page.test.ts +62 -0
- package/assets/templates/next-tenant-app/src/app/app/page.tsx +24 -0
- package/assets/templates/next-tenant-app/src/app/blog/[slug]/page.test.ts +70 -0
- package/assets/templates/next-tenant-app/src/app/blog/[slug]/page.tsx +57 -0
- package/assets/templates/next-tenant-app/src/app/blog/page.test.ts +42 -0
- package/assets/templates/next-tenant-app/src/app/blog/page.tsx +37 -0
- package/assets/templates/next-tenant-app/src/app/docs/[...slug]/page.test.ts +70 -0
- package/assets/templates/next-tenant-app/src/app/docs/[...slug]/page.tsx +55 -0
- package/assets/templates/next-tenant-app/src/app/docs/page.test.ts +42 -0
- package/assets/templates/next-tenant-app/src/app/docs/page.tsx +37 -0
- package/assets/templates/next-tenant-app/src/app/globals.css +70 -0
- package/assets/templates/next-tenant-app/src/app/layout.tsx +69 -0
- package/assets/templates/next-tenant-app/src/app/login/page.test.ts +55 -0
- package/assets/templates/next-tenant-app/src/app/login/page.tsx +33 -0
- package/assets/templates/next-tenant-app/src/app/page.test.ts +56 -0
- package/assets/templates/next-tenant-app/src/app/page.tsx +35 -0
- package/assets/templates/next-tenant-app/src/app/pricing/page.test.ts +55 -0
- package/assets/templates/next-tenant-app/src/app/pricing/page.tsx +35 -0
- package/assets/templates/next-tenant-app/src/app/providers.tsx +25 -0
- package/assets/templates/next-tenant-app/src/app/robots.test.ts +20 -0
- package/assets/templates/next-tenant-app/src/app/robots.ts +18 -0
- package/assets/templates/next-tenant-app/src/app/sitemap.test.ts +49 -0
- package/assets/templates/next-tenant-app/src/app/sitemap.ts +54 -0
- package/assets/templates/next-tenant-app/src/components/ui/button.tsx +59 -0
- package/assets/templates/next-tenant-app/src/components/ui/input.tsx +21 -0
- package/assets/templates/next-tenant-app/src/design-system/docs/governance.mdx +26 -0
- package/assets/templates/next-tenant-app/src/design-system/patterns/panel-frame.stories.tsx +48 -0
- package/assets/templates/next-tenant-app/src/design-system/patterns/panel-frame.tsx +26 -0
- package/assets/templates/next-tenant-app/src/design-system/patterns/status-badge.stories.tsx +26 -0
- package/assets/templates/next-tenant-app/src/design-system/patterns/status-badge.tsx +35 -0
- package/assets/templates/next-tenant-app/src/design-system/patterns/theme-mode-toggle.stories.tsx +21 -0
- package/assets/templates/next-tenant-app/src/design-system/patterns/theme-mode-toggle.tsx +75 -0
- package/assets/templates/next-tenant-app/src/design-system/primitives/button.stories.tsx +37 -0
- package/assets/templates/next-tenant-app/src/design-system/primitives/button.ts +1 -0
- package/assets/templates/next-tenant-app/src/design-system/primitives/input.stories.tsx +26 -0
- package/assets/templates/next-tenant-app/src/design-system/primitives/input.ts +1 -0
- package/assets/templates/next-tenant-app/src/design-system/recipes/chrome.ts +28 -0
- package/assets/templates/next-tenant-app/src/design-system/tokens/foundation.css +31 -0
- package/assets/templates/next-tenant-app/src/design-system/tokens/index.css +3 -0
- package/assets/templates/next-tenant-app/src/design-system/tokens/manifest.json +85 -0
- package/assets/templates/next-tenant-app/src/design-system/tokens/manifest.ts +87 -0
- package/assets/templates/next-tenant-app/src/design-system/tokens/semantic.css +105 -0
- package/assets/templates/next-tenant-app/src/design-system/tokens/theme.css +59 -0
- package/assets/templates/next-tenant-app/src/design-system/tokens/tokens.stories.tsx +71 -0
- package/assets/templates/next-tenant-app/src/features/auth/components/login-screen.tsx +198 -0
- package/assets/templates/next-tenant-app/src/features/dashboard/components/tenant-dashboard.tsx +153 -0
- package/assets/templates/next-tenant-app/src/features/examples/runtime-command-demo/components/runtime-command-demo.tsx +342 -0
- package/assets/templates/next-tenant-app/src/features/public-shell/components/content-article.tsx +66 -0
- package/assets/templates/next-tenant-app/src/features/public-shell/components/content-collection.tsx +108 -0
- package/assets/templates/next-tenant-app/src/features/public-shell/components/marketing-page-canvas.tsx +111 -0
- package/assets/templates/next-tenant-app/src/features/public-shell/components/public-site-shell.tsx +111 -0
- package/assets/templates/next-tenant-app/src/features/shell/components/private-app-shell.tsx +624 -0
- package/assets/templates/next-tenant-app/src/lib/app-routes.test.ts +20 -0
- package/assets/templates/next-tenant-app/src/lib/app-routes.ts +59 -0
- package/assets/templates/next-tenant-app/src/lib/content/__fixtures__/public-site-snapshot.ts +189 -0
- package/assets/templates/next-tenant-app/src/lib/content/adapter.server.test.ts +318 -0
- package/assets/templates/next-tenant-app/src/lib/content/adapter.server.ts +232 -0
- package/assets/templates/next-tenant-app/src/lib/content/contracts.ts +339 -0
- package/assets/templates/next-tenant-app/src/lib/content/custom-adapter.ts +5 -0
- package/assets/templates/next-tenant-app/src/lib/content/empty-state.ts +96 -0
- package/assets/templates/next-tenant-app/src/lib/platform/auth.server.test.ts +75 -0
- package/assets/templates/next-tenant-app/src/lib/platform/auth.server.ts +25 -0
- package/assets/templates/next-tenant-app/src/lib/platform/client.server.test.ts +170 -0
- package/assets/templates/next-tenant-app/src/lib/platform/client.server.ts +661 -0
- package/assets/templates/next-tenant-app/src/lib/platform/contracts.ts +131 -0
- package/assets/templates/next-tenant-app/src/lib/platform/endpoints.server.ts +34 -0
- package/assets/templates/next-tenant-app/src/lib/platform/env.server.test.ts +102 -0
- package/assets/templates/next-tenant-app/src/lib/platform/env.server.ts +87 -0
- package/assets/templates/next-tenant-app/src/lib/platform/route-response.ts +33 -0
- package/assets/templates/next-tenant-app/src/lib/platform/session.server.ts +108 -0
- package/assets/templates/next-tenant-app/src/lib/public-site.test.ts +20 -0
- package/assets/templates/next-tenant-app/src/lib/public-site.ts +49 -0
- package/assets/templates/next-tenant-app/src/lib/theme-config.ts +10 -0
- package/assets/templates/next-tenant-app/src/lib/theme.tsx +159 -0
- package/assets/templates/next-tenant-app/src/lib/utils.ts +6 -0
- package/assets/templates/next-tenant-app/template.json +27 -0
- package/assets/templates/next-tenant-app/template.schema.json +160 -0
- package/assets/templates/next-tenant-app/test/server-only-stub.ts +1 -0
- package/assets/templates/next-tenant-app/tools/design-system/build-token-manifest.mjs +3 -0
- package/assets/templates/next-tenant-app/tools/design-system/check-imports.mjs +9 -0
- package/assets/templates/next-tenant-app/tools/design-system/check-stories.mjs +9 -0
- package/assets/templates/next-tenant-app/tools/design-system/check-values.mjs +9 -0
- package/assets/templates/next-tenant-app/tools/design-system/checks.mjs +238 -0
- package/assets/templates/next-tenant-app/tools/design-system/eslint-plugin-design-system.mjs +184 -0
- package/assets/templates/next-tenant-app/tools/design-system/playwright.config.mjs +34 -0
- package/assets/templates/next-tenant-app/tools/design-system/run-checks.mjs +22 -0
- package/assets/templates/next-tenant-app/tools/design-system/shared.mjs +166 -0
- package/assets/templates/next-tenant-app/tools/design-system/visual.spec.ts +41 -0
- package/assets/templates/next-tenant-app/tools/template/validate-route-contract.mjs +39 -0
- package/assets/templates/next-tenant-app/tools/template/validate-template.mjs +45 -0
- package/assets/templates/next-tenant-app/tsconfig.json +42 -0
- package/assets/templates/next-tenant-app/vitest.config.ts +25 -0
- package/bin/minutework.js +40 -0
- package/dist/auth.d.ts +59 -0
- package/dist/auth.js +338 -0
- package/dist/auth.js.map +1 -0
- package/dist/browser.d.ts +1 -0
- package/dist/browser.js +26 -0
- package/dist/browser.js.map +1 -0
- package/dist/cli.d.ts +2 -0
- package/dist/cli.js +5 -0
- package/dist/cli.js.map +1 -0
- package/dist/compile.d.ts +20 -0
- package/dist/compile.js +121 -0
- package/dist/compile.js.map +1 -0
- package/dist/config.d.ts +25 -0
- package/dist/config.js +102 -0
- package/dist/config.js.map +1 -0
- package/dist/deploy-state.d.ts +35 -0
- package/dist/deploy-state.js +30 -0
- package/dist/deploy-state.js.map +1 -0
- package/dist/deploy.d.ts +22 -0
- package/dist/deploy.js +308 -0
- package/dist/deploy.js.map +1 -0
- package/dist/developer-client.d.ts +88 -0
- package/dist/developer-client.js +78 -0
- package/dist/developer-client.js.map +1 -0
- package/dist/index.d.ts +27 -0
- package/dist/index.js +290 -0
- package/dist/index.js.map +1 -0
- package/dist/init.d.ts +22 -0
- package/dist/init.js +421 -0
- package/dist/init.js.map +1 -0
- package/dist/launcher.d.ts +1 -0
- package/dist/launcher.js +50 -0
- package/dist/launcher.js.map +1 -0
- package/dist/paths.d.ts +12 -0
- package/dist/paths.js +33 -0
- package/dist/paths.js.map +1 -0
- package/dist/sandbox.d.ts +30 -0
- package/dist/sandbox.js +852 -0
- package/dist/sandbox.js.map +1 -0
- package/dist/state.d.ts +46 -0
- package/dist/state.js +82 -0
- package/dist/state.js.map +1 -0
- package/dist/tokens.d.ts +14 -0
- package/dist/tokens.js +293 -0
- package/dist/tokens.js.map +1 -0
- package/package.json +43 -0
package/dist/sandbox.js
ADDED
|
@@ -0,0 +1,852 @@
|
|
|
1
|
+
import * as childProcess from "node:child_process";
|
|
2
|
+
import { promises as fs } from "node:fs";
|
|
3
|
+
import path from "node:path";
|
|
4
|
+
import { analyzeCompilerWorkspace, } from "@minutework/schema-compiler";
|
|
5
|
+
import { collectLocalRuntimeStatus, createSha256Digest, loadLocalRuntimeStatusContext, } from "@minutework/workspace-mcp";
|
|
6
|
+
import { formatJson as formatCompilerJson, writeCodegenArtifacts, writeCompileGraph } from "./compile.js";
|
|
7
|
+
export async function runSandboxCommand(options) {
|
|
8
|
+
const [subcommand, ...rest] = options.args;
|
|
9
|
+
if (!subcommand || subcommand === "--help" || subcommand === "-h" || subcommand === "help") {
|
|
10
|
+
options.io.stdout(renderSandboxHelp());
|
|
11
|
+
return 0;
|
|
12
|
+
}
|
|
13
|
+
if (subcommand === "status") {
|
|
14
|
+
return runSandboxStatusCommand({
|
|
15
|
+
args: rest,
|
|
16
|
+
cwd: options.cwd,
|
|
17
|
+
env: options.env,
|
|
18
|
+
io: options.io,
|
|
19
|
+
platform: options.platform,
|
|
20
|
+
});
|
|
21
|
+
}
|
|
22
|
+
options.io.stderr(`Unknown "sandbox" subcommand: ${subcommand}`);
|
|
23
|
+
options.io.stderr("");
|
|
24
|
+
options.io.stderr(renderSandboxHelp());
|
|
25
|
+
return 1;
|
|
26
|
+
}
|
|
27
|
+
export async function runDevCommand(options) {
|
|
28
|
+
return runLocalRuntimeCommand("dev", options);
|
|
29
|
+
}
|
|
30
|
+
export async function runTestCommand(options) {
|
|
31
|
+
return runLocalRuntimeCommand("test", options);
|
|
32
|
+
}
|
|
33
|
+
async function runSandboxStatusCommand(options) {
|
|
34
|
+
const parsed = parseSandboxStatusArgs(options.args);
|
|
35
|
+
if (parsed.help) {
|
|
36
|
+
options.io.stdout(renderSandboxStatusHelp());
|
|
37
|
+
return 0;
|
|
38
|
+
}
|
|
39
|
+
const context = await loadLocalRuntimeStatusContext({
|
|
40
|
+
cwd: options.cwd,
|
|
41
|
+
env: options.env,
|
|
42
|
+
platform: options.platform,
|
|
43
|
+
workspacePath: parsed.workspacePath,
|
|
44
|
+
});
|
|
45
|
+
const status = collectLocalRuntimeStatus(context);
|
|
46
|
+
options.io.stdout(formatLocalRuntimeJson(status));
|
|
47
|
+
return status.status === "ready" ? 0 : 1;
|
|
48
|
+
}
|
|
49
|
+
async function runLocalRuntimeCommand(commandName, options) {
|
|
50
|
+
const parsed = parseLocalRuntimeCommandArgs(options.args, commandName);
|
|
51
|
+
if (parsed.help) {
|
|
52
|
+
options.io.stdout(commandName === "dev" ? renderDevHelp() : renderTestHelp());
|
|
53
|
+
return 0;
|
|
54
|
+
}
|
|
55
|
+
const initialContext = await loadLocalRuntimeStatusContext({
|
|
56
|
+
cwd: options.cwd,
|
|
57
|
+
env: options.env,
|
|
58
|
+
platform: options.platform,
|
|
59
|
+
workspacePath: parsed.workspacePath,
|
|
60
|
+
});
|
|
61
|
+
const initialStatus = collectLocalRuntimeStatus(initialContext);
|
|
62
|
+
const initialPlan = getLocalRuntimeCommandPlan(initialStatus, commandName);
|
|
63
|
+
const currentActivity = getLocalRuntimeActivitySummary(initialStatus, commandName);
|
|
64
|
+
const runtimeStatePath = getLocalRuntimeStatePath(initialStatus, commandName);
|
|
65
|
+
if (currentActivity.status === "running") {
|
|
66
|
+
options.io.stderr(currentActivity.detail);
|
|
67
|
+
options.io.stderr(`Recorded activity: ${currentActivity.filePath}`);
|
|
68
|
+
return 1;
|
|
69
|
+
}
|
|
70
|
+
const tracker = new LocalRuntimeActivityTracker(commandName, runtimeStatePath, options.dependencies?.controllerPid ?? process.pid);
|
|
71
|
+
const preflightState = {};
|
|
72
|
+
let currentStep = null;
|
|
73
|
+
try {
|
|
74
|
+
await tracker.initialize(initialPlan.steps.filter(isManagedPreflightStep));
|
|
75
|
+
for (const step of initialPlan.steps) {
|
|
76
|
+
if (!isManagedPreflightStep(step)) {
|
|
77
|
+
continue;
|
|
78
|
+
}
|
|
79
|
+
currentStep = step;
|
|
80
|
+
await tracker.markStepRunning(step);
|
|
81
|
+
const exitCode = await runManagedPreflightStep(step, initialContext.workspaceRoot, preflightState, options.io);
|
|
82
|
+
if (preflightState.analysis?.compileGraph && step.args[0] !== "validate") {
|
|
83
|
+
await tracker.setCompileGraphDigest(preflightState.analysis.compileGraph);
|
|
84
|
+
}
|
|
85
|
+
if (exitCode !== 0) {
|
|
86
|
+
await tracker.markStepFailed(step, {
|
|
87
|
+
errorCode: null,
|
|
88
|
+
exitCode,
|
|
89
|
+
interrupted: false,
|
|
90
|
+
signal: null,
|
|
91
|
+
});
|
|
92
|
+
await tracker.markPendingStepsSkipped();
|
|
93
|
+
await tracker.finalize("failed", {
|
|
94
|
+
errorCode: null,
|
|
95
|
+
exitCode,
|
|
96
|
+
failedStepId: step.id,
|
|
97
|
+
signal: null,
|
|
98
|
+
});
|
|
99
|
+
return exitCode;
|
|
100
|
+
}
|
|
101
|
+
await tracker.markStepCompleted(step, { code: 0, signal: null });
|
|
102
|
+
currentStep = null;
|
|
103
|
+
}
|
|
104
|
+
const refreshedContext = await loadLocalRuntimeStatusContext({
|
|
105
|
+
cwd: options.cwd,
|
|
106
|
+
env: options.env,
|
|
107
|
+
platform: options.platform,
|
|
108
|
+
workspacePath: parsed.workspacePath,
|
|
109
|
+
});
|
|
110
|
+
const refreshedStatus = collectLocalRuntimeStatus(refreshedContext);
|
|
111
|
+
const commandPlan = getLocalRuntimeCommandPlan(refreshedStatus, commandName);
|
|
112
|
+
await tracker.syncPlan(commandPlan.steps);
|
|
113
|
+
if (commandPlan.status !== "ready") {
|
|
114
|
+
await tracker.markPendingStepsSkipped();
|
|
115
|
+
await tracker.finalize("failed", {
|
|
116
|
+
errorCode: commandPlan.prerequisiteCodes[0] ?? null,
|
|
117
|
+
exitCode: 1,
|
|
118
|
+
failedStepId: null,
|
|
119
|
+
signal: null,
|
|
120
|
+
});
|
|
121
|
+
options.io.stderr(commandPlan.detail);
|
|
122
|
+
if (commandPlan.prerequisiteCodes.length > 0) {
|
|
123
|
+
options.io.stderr(`Blocked prerequisites: ${commandPlan.prerequisiteCodes.join(", ")}`);
|
|
124
|
+
}
|
|
125
|
+
return 1;
|
|
126
|
+
}
|
|
127
|
+
const starterSteps = commandPlan.steps.filter((step) => !isManagedPreflightStep(step));
|
|
128
|
+
if (starterSteps.length === 0) {
|
|
129
|
+
await tracker.finalize(getSuccessStatus(commandName), {
|
|
130
|
+
errorCode: null,
|
|
131
|
+
exitCode: 0,
|
|
132
|
+
failedStepId: null,
|
|
133
|
+
signal: null,
|
|
134
|
+
});
|
|
135
|
+
options.io.stdout(commandName === "test"
|
|
136
|
+
? "No starter-local test steps are configured. Schema validation, compile, and codegen are current."
|
|
137
|
+
: "No local preview surfaces are configured for this workspace.");
|
|
138
|
+
return 0;
|
|
139
|
+
}
|
|
140
|
+
if (commandName === "dev") {
|
|
141
|
+
return runConcurrentLocalRuntimeSteps(starterSteps, options, tracker);
|
|
142
|
+
}
|
|
143
|
+
return runSequentialLocalRuntimeSteps(starterSteps, options, tracker);
|
|
144
|
+
}
|
|
145
|
+
catch (error) {
|
|
146
|
+
if (!tracker.isFinalized()) {
|
|
147
|
+
if (currentStep) {
|
|
148
|
+
await tracker.markStepFailed(currentStep, {
|
|
149
|
+
errorCode: errorCodeFromUnknown(error),
|
|
150
|
+
exitCode: 1,
|
|
151
|
+
interrupted: false,
|
|
152
|
+
signal: null,
|
|
153
|
+
});
|
|
154
|
+
}
|
|
155
|
+
await tracker.markPendingStepsSkipped();
|
|
156
|
+
await tracker.finalize("failed", {
|
|
157
|
+
errorCode: errorCodeFromUnknown(error),
|
|
158
|
+
exitCode: 1,
|
|
159
|
+
failedStepId: currentStep?.id ?? null,
|
|
160
|
+
signal: null,
|
|
161
|
+
});
|
|
162
|
+
}
|
|
163
|
+
throw error;
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
function parseSandboxStatusArgs(args) {
|
|
167
|
+
let workspacePath;
|
|
168
|
+
const setWorkspacePath = (value) => {
|
|
169
|
+
if (workspacePath) {
|
|
170
|
+
throw new Error('The "sandbox status" command accepts at most one workspace path.');
|
|
171
|
+
}
|
|
172
|
+
workspacePath = value;
|
|
173
|
+
};
|
|
174
|
+
for (let index = 0; index < args.length; index += 1) {
|
|
175
|
+
const arg = args[index];
|
|
176
|
+
if (arg === "--help" || arg === "-h") {
|
|
177
|
+
return { help: true };
|
|
178
|
+
}
|
|
179
|
+
if (arg === "--workspace") {
|
|
180
|
+
const next = args[index + 1];
|
|
181
|
+
if (!next || next.startsWith("--")) {
|
|
182
|
+
throw new Error('The "sandbox status" command requires a path after --workspace.');
|
|
183
|
+
}
|
|
184
|
+
setWorkspacePath(next);
|
|
185
|
+
index += 1;
|
|
186
|
+
continue;
|
|
187
|
+
}
|
|
188
|
+
if (arg.startsWith("--workspace=")) {
|
|
189
|
+
const value = arg.slice("--workspace=".length);
|
|
190
|
+
if (!value) {
|
|
191
|
+
throw new Error('The "sandbox status" command requires a path after --workspace.');
|
|
192
|
+
}
|
|
193
|
+
setWorkspacePath(value);
|
|
194
|
+
continue;
|
|
195
|
+
}
|
|
196
|
+
if (arg.startsWith("--")) {
|
|
197
|
+
throw new Error(`Unknown sandbox status option: ${arg}`);
|
|
198
|
+
}
|
|
199
|
+
setWorkspacePath(arg);
|
|
200
|
+
}
|
|
201
|
+
return {
|
|
202
|
+
help: false,
|
|
203
|
+
workspacePath,
|
|
204
|
+
};
|
|
205
|
+
}
|
|
206
|
+
function parseLocalRuntimeCommandArgs(args, commandName) {
|
|
207
|
+
let workspacePath;
|
|
208
|
+
const setWorkspacePath = (value) => {
|
|
209
|
+
if (workspacePath) {
|
|
210
|
+
throw new Error(`${commandName} accepts at most one workspace path.`);
|
|
211
|
+
}
|
|
212
|
+
workspacePath = value;
|
|
213
|
+
};
|
|
214
|
+
for (let index = 0; index < args.length; index += 1) {
|
|
215
|
+
const arg = args[index];
|
|
216
|
+
if (arg === "--help" || arg === "-h") {
|
|
217
|
+
return { help: true };
|
|
218
|
+
}
|
|
219
|
+
if (arg === "--workspace") {
|
|
220
|
+
const next = args[index + 1];
|
|
221
|
+
if (!next || next.startsWith("--")) {
|
|
222
|
+
throw new Error(`The "${commandName}" command requires a path after --workspace.`);
|
|
223
|
+
}
|
|
224
|
+
setWorkspacePath(next);
|
|
225
|
+
index += 1;
|
|
226
|
+
continue;
|
|
227
|
+
}
|
|
228
|
+
if (arg.startsWith("--workspace=")) {
|
|
229
|
+
const value = arg.slice("--workspace=".length);
|
|
230
|
+
if (!value) {
|
|
231
|
+
throw new Error(`The "${commandName}" command requires a path after --workspace.`);
|
|
232
|
+
}
|
|
233
|
+
setWorkspacePath(value);
|
|
234
|
+
continue;
|
|
235
|
+
}
|
|
236
|
+
if (arg.startsWith("--")) {
|
|
237
|
+
throw new Error(`Unknown ${commandName} option: ${arg}`);
|
|
238
|
+
}
|
|
239
|
+
setWorkspacePath(arg);
|
|
240
|
+
}
|
|
241
|
+
return {
|
|
242
|
+
help: false,
|
|
243
|
+
workspacePath,
|
|
244
|
+
};
|
|
245
|
+
}
|
|
246
|
+
function getLocalRuntimeCommandPlan(status, commandName) {
|
|
247
|
+
const command = status.commands.find((item) => item.name === commandName);
|
|
248
|
+
if (!command) {
|
|
249
|
+
throw new Error(`Local sandbox status did not define a "${commandName}" command plan.`);
|
|
250
|
+
}
|
|
251
|
+
return command;
|
|
252
|
+
}
|
|
253
|
+
function getLocalRuntimeActivitySummary(status, commandName) {
|
|
254
|
+
return commandName === "dev" ? status.activity.dev : status.activity.test;
|
|
255
|
+
}
|
|
256
|
+
function getLocalRuntimeStatePath(status, commandName) {
|
|
257
|
+
return commandName === "dev" ? status.topology.devSessionPath : status.topology.testRunPath;
|
|
258
|
+
}
|
|
259
|
+
function isManagedPreflightStep(step) {
|
|
260
|
+
return (step.program === "minutework" &&
|
|
261
|
+
step.args.length > 0 &&
|
|
262
|
+
(step.args[0] === "validate" || step.args[0] === "compile" || step.args[0] === "codegen"));
|
|
263
|
+
}
|
|
264
|
+
async function runManagedPreflightStep(step, workspaceRoot, state, io) {
|
|
265
|
+
io.stdout(renderStepBanner(step));
|
|
266
|
+
if (!state.analysis) {
|
|
267
|
+
state.analysis = await analyzeCompilerWorkspace(workspaceRoot);
|
|
268
|
+
}
|
|
269
|
+
const analysis = state.analysis;
|
|
270
|
+
if (analysis.validateReport.status !== "ok") {
|
|
271
|
+
io.stderr(formatCompilerJson(analysis.validateReport));
|
|
272
|
+
return 1;
|
|
273
|
+
}
|
|
274
|
+
if (step.args[0] === "validate") {
|
|
275
|
+
return 0;
|
|
276
|
+
}
|
|
277
|
+
if (!analysis.compileGraph) {
|
|
278
|
+
io.stderr(formatCompilerJson(analysis.validateReport));
|
|
279
|
+
return 1;
|
|
280
|
+
}
|
|
281
|
+
await writeCompileGraph(workspaceRoot, analysis.compileGraph);
|
|
282
|
+
if (step.args[0] === "compile") {
|
|
283
|
+
return 0;
|
|
284
|
+
}
|
|
285
|
+
if (!analysis.codegenArtifacts) {
|
|
286
|
+
io.stderr(formatCompilerJson(analysis.validateReport));
|
|
287
|
+
return 1;
|
|
288
|
+
}
|
|
289
|
+
await writeCodegenArtifacts(workspaceRoot, analysis.compileGraph.artifacts, analysis.codegenArtifacts);
|
|
290
|
+
return 0;
|
|
291
|
+
}
|
|
292
|
+
async function runSequentialLocalRuntimeSteps(steps, options, tracker) {
|
|
293
|
+
let activeChild = null;
|
|
294
|
+
const cleanupSignalForwarding = installSignalForwarding({
|
|
295
|
+
getChildren: () => (activeChild ? [activeChild] : []),
|
|
296
|
+
signalTarget: options.dependencies?.signalTarget,
|
|
297
|
+
});
|
|
298
|
+
try {
|
|
299
|
+
for (const step of steps) {
|
|
300
|
+
options.io.stdout(renderStepBanner(step));
|
|
301
|
+
let runningStep;
|
|
302
|
+
try {
|
|
303
|
+
runningStep = spawnLocalRuntimeStep(step, options.env, options.dependencies);
|
|
304
|
+
}
|
|
305
|
+
catch (error) {
|
|
306
|
+
const spawnError = error instanceof LocalRuntimeSpawnError ? error : createLocalRuntimeSpawnError(step, error);
|
|
307
|
+
await tracker.markStepFailed(step, {
|
|
308
|
+
errorCode: spawnError.code,
|
|
309
|
+
exitCode: 1,
|
|
310
|
+
interrupted: false,
|
|
311
|
+
signal: null,
|
|
312
|
+
});
|
|
313
|
+
await tracker.markPendingStepsSkipped();
|
|
314
|
+
await tracker.finalize("failed", {
|
|
315
|
+
errorCode: spawnError.code,
|
|
316
|
+
exitCode: 1,
|
|
317
|
+
failedStepId: step.id,
|
|
318
|
+
signal: null,
|
|
319
|
+
});
|
|
320
|
+
options.io.stderr(spawnError.message);
|
|
321
|
+
return 1;
|
|
322
|
+
}
|
|
323
|
+
activeChild = runningStep.child;
|
|
324
|
+
await tracker.markStepRunning(step, runningStep.child.pid ?? null);
|
|
325
|
+
const outcome = await waitForRunningStepOutcome(runningStep);
|
|
326
|
+
activeChild = null;
|
|
327
|
+
await applyRunningStepOutcomeToTracker(tracker, outcome);
|
|
328
|
+
if (outcome.type === "error") {
|
|
329
|
+
const errorCode = errorCodeFromUnknown(outcome.error);
|
|
330
|
+
options.io.stderr(outcome.error instanceof Error ? outcome.error.message : String(outcome.error));
|
|
331
|
+
await tracker.markPendingStepsSkipped();
|
|
332
|
+
await tracker.finalize("failed", {
|
|
333
|
+
errorCode,
|
|
334
|
+
exitCode: 1,
|
|
335
|
+
failedStepId: step.id,
|
|
336
|
+
signal: null,
|
|
337
|
+
});
|
|
338
|
+
return 1;
|
|
339
|
+
}
|
|
340
|
+
if (outcome.result.signal) {
|
|
341
|
+
await tracker.markPendingStepsSkipped();
|
|
342
|
+
await tracker.finalize("interrupted", {
|
|
343
|
+
errorCode: null,
|
|
344
|
+
exitCode: null,
|
|
345
|
+
failedStepId: step.id,
|
|
346
|
+
signal: outcome.result.signal,
|
|
347
|
+
});
|
|
348
|
+
return 1;
|
|
349
|
+
}
|
|
350
|
+
const exitCode = exitCodeFromChildResult(outcome.result);
|
|
351
|
+
if (exitCode !== 0) {
|
|
352
|
+
await tracker.markPendingStepsSkipped();
|
|
353
|
+
await tracker.finalize("failed", {
|
|
354
|
+
errorCode: null,
|
|
355
|
+
exitCode,
|
|
356
|
+
failedStepId: step.id,
|
|
357
|
+
signal: null,
|
|
358
|
+
});
|
|
359
|
+
return exitCode;
|
|
360
|
+
}
|
|
361
|
+
}
|
|
362
|
+
await tracker.finalize("passed", {
|
|
363
|
+
errorCode: null,
|
|
364
|
+
exitCode: 0,
|
|
365
|
+
failedStepId: null,
|
|
366
|
+
signal: null,
|
|
367
|
+
});
|
|
368
|
+
return 0;
|
|
369
|
+
}
|
|
370
|
+
finally {
|
|
371
|
+
cleanupSignalForwarding();
|
|
372
|
+
}
|
|
373
|
+
}
|
|
374
|
+
async function runConcurrentLocalRuntimeSteps(steps, options, tracker) {
|
|
375
|
+
const runningSteps = [];
|
|
376
|
+
const cleanupSignalForwarding = installSignalForwarding({
|
|
377
|
+
getChildren: () => runningSteps.map((runningStep) => runningStep.child),
|
|
378
|
+
signalTarget: options.dependencies?.signalTarget,
|
|
379
|
+
});
|
|
380
|
+
try {
|
|
381
|
+
for (const step of steps) {
|
|
382
|
+
options.io.stdout(renderStepBanner(step));
|
|
383
|
+
let runningStep;
|
|
384
|
+
try {
|
|
385
|
+
runningStep = spawnLocalRuntimeStep(step, options.env, options.dependencies);
|
|
386
|
+
}
|
|
387
|
+
catch (error) {
|
|
388
|
+
const spawnError = error instanceof LocalRuntimeSpawnError ? error : createLocalRuntimeSpawnError(step, error);
|
|
389
|
+
await tracker.markStepFailed(step, {
|
|
390
|
+
errorCode: spawnError.code,
|
|
391
|
+
exitCode: 1,
|
|
392
|
+
interrupted: false,
|
|
393
|
+
signal: null,
|
|
394
|
+
});
|
|
395
|
+
const siblingOutcomes = await terminateSiblingProcesses(runningSteps);
|
|
396
|
+
await applyRunningStepOutcomesToTracker(tracker, siblingOutcomes);
|
|
397
|
+
await tracker.markPendingStepsSkipped();
|
|
398
|
+
await tracker.finalize("failed", {
|
|
399
|
+
errorCode: spawnError.code,
|
|
400
|
+
exitCode: 1,
|
|
401
|
+
failedStepId: step.id,
|
|
402
|
+
signal: null,
|
|
403
|
+
});
|
|
404
|
+
options.io.stderr(spawnError.message);
|
|
405
|
+
return 1;
|
|
406
|
+
}
|
|
407
|
+
runningSteps.push(runningStep);
|
|
408
|
+
await tracker.markStepRunning(step, runningStep.child.pid ?? null);
|
|
409
|
+
}
|
|
410
|
+
const firstOutcome = await Promise.race(runningSteps.map((runningStep) => waitForRunningStepOutcome(runningStep)));
|
|
411
|
+
const siblingOutcomes = await terminateSiblingProcesses(runningSteps.filter((runningStep) => runningStep !== firstOutcome.runningStep));
|
|
412
|
+
await applyRunningStepOutcomeToTracker(tracker, firstOutcome);
|
|
413
|
+
await applyRunningStepOutcomesToTracker(tracker, siblingOutcomes);
|
|
414
|
+
if (firstOutcome.type === "error") {
|
|
415
|
+
const errorCode = errorCodeFromUnknown(firstOutcome.error);
|
|
416
|
+
options.io.stderr(firstOutcome.error instanceof Error ? firstOutcome.error.message : String(firstOutcome.error));
|
|
417
|
+
await tracker.finalize("failed", {
|
|
418
|
+
errorCode,
|
|
419
|
+
exitCode: 1,
|
|
420
|
+
failedStepId: firstOutcome.runningStep.step.id,
|
|
421
|
+
signal: null,
|
|
422
|
+
});
|
|
423
|
+
return 1;
|
|
424
|
+
}
|
|
425
|
+
if (firstOutcome.result.signal) {
|
|
426
|
+
await tracker.finalize("interrupted", {
|
|
427
|
+
errorCode: null,
|
|
428
|
+
exitCode: null,
|
|
429
|
+
failedStepId: firstOutcome.runningStep.step.id,
|
|
430
|
+
signal: firstOutcome.result.signal,
|
|
431
|
+
});
|
|
432
|
+
return 1;
|
|
433
|
+
}
|
|
434
|
+
const exitCode = exitCodeFromChildResult(firstOutcome.result);
|
|
435
|
+
if (exitCode !== 0) {
|
|
436
|
+
await tracker.finalize("failed", {
|
|
437
|
+
errorCode: null,
|
|
438
|
+
exitCode,
|
|
439
|
+
failedStepId: firstOutcome.runningStep.step.id,
|
|
440
|
+
signal: null,
|
|
441
|
+
});
|
|
442
|
+
return exitCode;
|
|
443
|
+
}
|
|
444
|
+
await tracker.finalize("exited", {
|
|
445
|
+
errorCode: null,
|
|
446
|
+
exitCode: 0,
|
|
447
|
+
failedStepId: null,
|
|
448
|
+
signal: null,
|
|
449
|
+
});
|
|
450
|
+
return 0;
|
|
451
|
+
}
|
|
452
|
+
finally {
|
|
453
|
+
cleanupSignalForwarding();
|
|
454
|
+
}
|
|
455
|
+
}
|
|
456
|
+
function spawnLocalRuntimeStep(step, env, dependencies) {
|
|
457
|
+
const spawnProcess = dependencies?.spawnProcess ?? childProcess.spawn;
|
|
458
|
+
try {
|
|
459
|
+
const child = spawnProcess(step.program, step.args, {
|
|
460
|
+
cwd: step.cwd,
|
|
461
|
+
env,
|
|
462
|
+
stdio: "inherit",
|
|
463
|
+
});
|
|
464
|
+
const completion = new Promise((resolve, reject) => {
|
|
465
|
+
child.once("error", (error) => {
|
|
466
|
+
reject(createLocalRuntimeSpawnError(step, error));
|
|
467
|
+
});
|
|
468
|
+
child.once("exit", (code, signal) => {
|
|
469
|
+
resolve({ code, signal });
|
|
470
|
+
});
|
|
471
|
+
});
|
|
472
|
+
return {
|
|
473
|
+
child,
|
|
474
|
+
completion,
|
|
475
|
+
step,
|
|
476
|
+
};
|
|
477
|
+
}
|
|
478
|
+
catch (error) {
|
|
479
|
+
throw createLocalRuntimeSpawnError(step, error);
|
|
480
|
+
}
|
|
481
|
+
}
|
|
482
|
+
function waitForRunningStepOutcome(runningStep) {
|
|
483
|
+
return runningStep.completion
|
|
484
|
+
.then((result) => ({
|
|
485
|
+
result,
|
|
486
|
+
runningStep,
|
|
487
|
+
type: "exit",
|
|
488
|
+
}))
|
|
489
|
+
.catch((error) => ({
|
|
490
|
+
error,
|
|
491
|
+
runningStep,
|
|
492
|
+
type: "error",
|
|
493
|
+
}));
|
|
494
|
+
}
|
|
495
|
+
async function terminateSiblingProcesses(runningSteps) {
|
|
496
|
+
for (const runningStep of runningSteps) {
|
|
497
|
+
if (runningStep.child.exitCode === null && runningStep.child.signalCode === null) {
|
|
498
|
+
runningStep.child.kill("SIGTERM");
|
|
499
|
+
}
|
|
500
|
+
}
|
|
501
|
+
return Promise.all(runningSteps.map((runningStep) => waitForRunningStepOutcome(runningStep)));
|
|
502
|
+
}
|
|
503
|
+
function installSignalForwarding(options) {
|
|
504
|
+
const signals = ["SIGINT", "SIGTERM"];
|
|
505
|
+
const handlers = new Map();
|
|
506
|
+
const signalTarget = options.signalTarget ?? process;
|
|
507
|
+
for (const signal of signals) {
|
|
508
|
+
const handler = () => {
|
|
509
|
+
for (const child of options.getChildren()) {
|
|
510
|
+
if (child.exitCode === null && child.signalCode === null) {
|
|
511
|
+
child.kill(signal);
|
|
512
|
+
}
|
|
513
|
+
}
|
|
514
|
+
};
|
|
515
|
+
handlers.set(signal, handler);
|
|
516
|
+
signalTarget.on(signal, handler);
|
|
517
|
+
}
|
|
518
|
+
return () => {
|
|
519
|
+
for (const [signal, handler] of handlers) {
|
|
520
|
+
signalTarget.off(signal, handler);
|
|
521
|
+
}
|
|
522
|
+
};
|
|
523
|
+
}
|
|
524
|
+
async function applyRunningStepOutcomesToTracker(tracker, outcomes) {
|
|
525
|
+
for (const outcome of outcomes) {
|
|
526
|
+
await applyRunningStepOutcomeToTracker(tracker, outcome);
|
|
527
|
+
}
|
|
528
|
+
}
|
|
529
|
+
async function applyRunningStepOutcomeToTracker(tracker, outcome) {
|
|
530
|
+
if (outcome.type === "error") {
|
|
531
|
+
await tracker.markStepFailed(outcome.runningStep.step, {
|
|
532
|
+
errorCode: errorCodeFromUnknown(outcome.error),
|
|
533
|
+
exitCode: 1,
|
|
534
|
+
interrupted: false,
|
|
535
|
+
signal: null,
|
|
536
|
+
});
|
|
537
|
+
return;
|
|
538
|
+
}
|
|
539
|
+
if (outcome.result.signal) {
|
|
540
|
+
await tracker.markStepFailed(outcome.runningStep.step, {
|
|
541
|
+
errorCode: null,
|
|
542
|
+
exitCode: null,
|
|
543
|
+
interrupted: true,
|
|
544
|
+
signal: outcome.result.signal,
|
|
545
|
+
});
|
|
546
|
+
return;
|
|
547
|
+
}
|
|
548
|
+
const exitCode = exitCodeFromChildResult(outcome.result);
|
|
549
|
+
if (exitCode !== 0) {
|
|
550
|
+
await tracker.markStepFailed(outcome.runningStep.step, {
|
|
551
|
+
errorCode: null,
|
|
552
|
+
exitCode,
|
|
553
|
+
interrupted: false,
|
|
554
|
+
signal: null,
|
|
555
|
+
});
|
|
556
|
+
return;
|
|
557
|
+
}
|
|
558
|
+
await tracker.markStepCompleted(outcome.runningStep.step, outcome.result);
|
|
559
|
+
}
|
|
560
|
+
function createLocalRuntimeSpawnError(step, error) {
|
|
561
|
+
const errno = error;
|
|
562
|
+
if (errno.code === "ENOENT") {
|
|
563
|
+
return new LocalRuntimeSpawnError(step, `Unable to start local sandbox step "${step.id}": program "${step.program}" was not found in ${step.cwd}.`, errno.code);
|
|
564
|
+
}
|
|
565
|
+
return new LocalRuntimeSpawnError(step, `Unable to start local sandbox step "${step.id}" with "${step.program}" in ${step.cwd}: ${error instanceof Error ? error.message : String(error)}`, errno.code ?? null);
|
|
566
|
+
}
|
|
567
|
+
function createCompileGraphDigest(compileGraph) {
|
|
568
|
+
return createSha256Digest(formatCompilerJson(compileGraph));
|
|
569
|
+
}
|
|
570
|
+
function createPendingActivityStep(step) {
|
|
571
|
+
return {
|
|
572
|
+
args: [...step.args],
|
|
573
|
+
cwd: step.cwd,
|
|
574
|
+
endedAt: null,
|
|
575
|
+
exitCode: null,
|
|
576
|
+
id: step.id,
|
|
577
|
+
pid: null,
|
|
578
|
+
program: step.program,
|
|
579
|
+
signal: null,
|
|
580
|
+
startedAt: null,
|
|
581
|
+
status: "pending",
|
|
582
|
+
};
|
|
583
|
+
}
|
|
584
|
+
function createInitialActivityRecord(commandName, controllerPid, steps) {
|
|
585
|
+
const timestamp = createTimestamp();
|
|
586
|
+
const base = {
|
|
587
|
+
compileGraphDigest: null,
|
|
588
|
+
controllerPid,
|
|
589
|
+
endedAt: null,
|
|
590
|
+
outcome: createEmptyOutcome(),
|
|
591
|
+
startedAt: timestamp,
|
|
592
|
+
steps: steps.map(createPendingActivityStep),
|
|
593
|
+
updatedAt: timestamp,
|
|
594
|
+
version: "MinuteWorkLocalRuntimeActivityV1",
|
|
595
|
+
};
|
|
596
|
+
if (commandName === "dev") {
|
|
597
|
+
return {
|
|
598
|
+
...base,
|
|
599
|
+
command: "dev",
|
|
600
|
+
status: "running",
|
|
601
|
+
};
|
|
602
|
+
}
|
|
603
|
+
return {
|
|
604
|
+
...base,
|
|
605
|
+
command: "test",
|
|
606
|
+
status: "running",
|
|
607
|
+
};
|
|
608
|
+
}
|
|
609
|
+
function mergeActivitySteps(existingSteps, plannedSteps) {
|
|
610
|
+
const existingById = new Map(existingSteps.map((step) => [step.id, step]));
|
|
611
|
+
return plannedSteps.map((plannedStep) => {
|
|
612
|
+
const existingStep = existingById.get(plannedStep.id);
|
|
613
|
+
if (!existingStep) {
|
|
614
|
+
return createPendingActivityStep(plannedStep);
|
|
615
|
+
}
|
|
616
|
+
return {
|
|
617
|
+
...existingStep,
|
|
618
|
+
args: [...plannedStep.args],
|
|
619
|
+
cwd: plannedStep.cwd,
|
|
620
|
+
program: plannedStep.program,
|
|
621
|
+
};
|
|
622
|
+
});
|
|
623
|
+
}
|
|
624
|
+
function createEmptyOutcome() {
|
|
625
|
+
return {
|
|
626
|
+
errorCode: null,
|
|
627
|
+
exitCode: null,
|
|
628
|
+
failedStepId: null,
|
|
629
|
+
signal: null,
|
|
630
|
+
};
|
|
631
|
+
}
|
|
632
|
+
function createTimestamp() {
|
|
633
|
+
return new Date().toISOString();
|
|
634
|
+
}
|
|
635
|
+
async function writeActivityRecord(filePath, record) {
|
|
636
|
+
await fs.mkdir(path.dirname(filePath), { recursive: true });
|
|
637
|
+
const tempPath = `${filePath}.tmp`;
|
|
638
|
+
await fs.writeFile(tempPath, JSON.stringify(record, null, 2) + "\n", "utf8");
|
|
639
|
+
await fs.rename(tempPath, filePath);
|
|
640
|
+
}
|
|
641
|
+
function errorCodeFromUnknown(error) {
|
|
642
|
+
if (error instanceof LocalRuntimeSpawnError) {
|
|
643
|
+
return error.code;
|
|
644
|
+
}
|
|
645
|
+
const errno = error;
|
|
646
|
+
return typeof errno.code === "string" ? errno.code : null;
|
|
647
|
+
}
|
|
648
|
+
function exitCodeFromChildResult(result) {
|
|
649
|
+
return result.code ?? (result.signal ? 1 : 0);
|
|
650
|
+
}
|
|
651
|
+
function renderStepBanner(step) {
|
|
652
|
+
return `==> ${step.detail}`;
|
|
653
|
+
}
|
|
654
|
+
function formatLocalRuntimeJson(payload) {
|
|
655
|
+
return `${JSON.stringify(payload, null, 2)}\n`;
|
|
656
|
+
}
|
|
657
|
+
function getSuccessStatus(commandName) {
|
|
658
|
+
return commandName === "dev" ? "exited" : "passed";
|
|
659
|
+
}
|
|
660
|
+
function renderSandboxHelp() {
|
|
661
|
+
return `Usage:
|
|
662
|
+
minutework sandbox status [workspace] [--workspace <path>]
|
|
663
|
+
|
|
664
|
+
Inspect local sandbox topology and readiness for the current MinuteWork workspace.`;
|
|
665
|
+
}
|
|
666
|
+
function renderSandboxStatusHelp() {
|
|
667
|
+
return `Usage:
|
|
668
|
+
minutework sandbox status [workspace] [--workspace <path>]
|
|
669
|
+
|
|
670
|
+
Print deterministic local sandbox status, compile-graph readiness, and planned dev/test steps derived from the current workspace.`;
|
|
671
|
+
}
|
|
672
|
+
function renderDevHelp() {
|
|
673
|
+
return `Usage:
|
|
674
|
+
minutework dev [workspace] [--workspace <path>]
|
|
675
|
+
|
|
676
|
+
Refresh local schema artifacts, then start the enabled local preview surfaces defined by sandbox status.`;
|
|
677
|
+
}
|
|
678
|
+
function renderTestHelp() {
|
|
679
|
+
return `Usage:
|
|
680
|
+
minutework test [workspace] [--workspace <path>]
|
|
681
|
+
|
|
682
|
+
Refresh local schema artifacts, then run the starter-local test steps defined by sandbox status.`;
|
|
683
|
+
}
|
|
684
|
+
class LocalRuntimeActivityTracker {
|
|
685
|
+
commandName;
|
|
686
|
+
filePath;
|
|
687
|
+
finalized = false;
|
|
688
|
+
record;
|
|
689
|
+
constructor(commandName, filePath, controllerPid) {
|
|
690
|
+
this.commandName = commandName;
|
|
691
|
+
this.filePath = filePath;
|
|
692
|
+
this.record = createInitialActivityRecord(commandName, controllerPid, []);
|
|
693
|
+
}
|
|
694
|
+
async initialize(steps) {
|
|
695
|
+
this.record = createInitialActivityRecord(this.commandName, this.record.controllerPid, steps);
|
|
696
|
+
await this.persist();
|
|
697
|
+
}
|
|
698
|
+
isFinalized() {
|
|
699
|
+
return this.finalized;
|
|
700
|
+
}
|
|
701
|
+
async syncPlan(steps) {
|
|
702
|
+
this.record = {
|
|
703
|
+
...this.record,
|
|
704
|
+
steps: mergeActivitySteps(this.record.steps, steps),
|
|
705
|
+
updatedAt: createTimestamp(),
|
|
706
|
+
};
|
|
707
|
+
await this.persist();
|
|
708
|
+
}
|
|
709
|
+
async setCompileGraphDigest(compileGraph) {
|
|
710
|
+
this.record = {
|
|
711
|
+
...this.record,
|
|
712
|
+
compileGraphDigest: createCompileGraphDigest(compileGraph),
|
|
713
|
+
updatedAt: createTimestamp(),
|
|
714
|
+
};
|
|
715
|
+
await this.persist();
|
|
716
|
+
}
|
|
717
|
+
async markStepRunning(step, pid = null) {
|
|
718
|
+
this.updateStep(step, (currentStep) => {
|
|
719
|
+
const startedAt = currentStep.startedAt ?? createTimestamp();
|
|
720
|
+
return {
|
|
721
|
+
...currentStep,
|
|
722
|
+
args: [...step.args],
|
|
723
|
+
cwd: step.cwd,
|
|
724
|
+
endedAt: null,
|
|
725
|
+
exitCode: null,
|
|
726
|
+
pid: pid ?? currentStep.pid ?? null,
|
|
727
|
+
program: step.program,
|
|
728
|
+
signal: null,
|
|
729
|
+
startedAt,
|
|
730
|
+
status: "running",
|
|
731
|
+
};
|
|
732
|
+
});
|
|
733
|
+
await this.persist();
|
|
734
|
+
}
|
|
735
|
+
async markStepCompleted(step, result) {
|
|
736
|
+
this.updateStep(step, (currentStep) => ({
|
|
737
|
+
...currentStep,
|
|
738
|
+
args: [...step.args],
|
|
739
|
+
cwd: step.cwd,
|
|
740
|
+
endedAt: createTimestamp(),
|
|
741
|
+
exitCode: result.code ?? 0,
|
|
742
|
+
program: step.program,
|
|
743
|
+
signal: result.signal,
|
|
744
|
+
startedAt: currentStep.startedAt ?? createTimestamp(),
|
|
745
|
+
status: "completed",
|
|
746
|
+
}));
|
|
747
|
+
await this.persist();
|
|
748
|
+
}
|
|
749
|
+
async markStepFailed(step, options) {
|
|
750
|
+
this.updateStep(step, (currentStep) => ({
|
|
751
|
+
...currentStep,
|
|
752
|
+
args: [...step.args],
|
|
753
|
+
cwd: step.cwd,
|
|
754
|
+
endedAt: createTimestamp(),
|
|
755
|
+
exitCode: options.exitCode,
|
|
756
|
+
program: step.program,
|
|
757
|
+
signal: options.signal,
|
|
758
|
+
status: options.interrupted ? "interrupted" : "failed",
|
|
759
|
+
}));
|
|
760
|
+
if (!this.record.outcome.failedStepId) {
|
|
761
|
+
this.record = {
|
|
762
|
+
...this.record,
|
|
763
|
+
outcome: {
|
|
764
|
+
errorCode: options.errorCode,
|
|
765
|
+
exitCode: options.exitCode,
|
|
766
|
+
failedStepId: step.id,
|
|
767
|
+
signal: options.signal,
|
|
768
|
+
},
|
|
769
|
+
updatedAt: createTimestamp(),
|
|
770
|
+
};
|
|
771
|
+
}
|
|
772
|
+
await this.persist();
|
|
773
|
+
}
|
|
774
|
+
async markPendingStepsSkipped() {
|
|
775
|
+
const timestamp = createTimestamp();
|
|
776
|
+
this.record = {
|
|
777
|
+
...this.record,
|
|
778
|
+
steps: this.record.steps.map((step) => step.status === "pending"
|
|
779
|
+
? {
|
|
780
|
+
...step,
|
|
781
|
+
endedAt: timestamp,
|
|
782
|
+
status: "skipped",
|
|
783
|
+
}
|
|
784
|
+
: step),
|
|
785
|
+
updatedAt: timestamp,
|
|
786
|
+
};
|
|
787
|
+
await this.persist();
|
|
788
|
+
}
|
|
789
|
+
async finalize(status, outcome) {
|
|
790
|
+
const timestamp = createTimestamp();
|
|
791
|
+
if (this.commandName === "dev") {
|
|
792
|
+
if (status === "passed") {
|
|
793
|
+
throw new Error('The dev runtime activity record cannot be finalized with "passed".');
|
|
794
|
+
}
|
|
795
|
+
this.record = {
|
|
796
|
+
...this.record,
|
|
797
|
+
endedAt: timestamp,
|
|
798
|
+
outcome,
|
|
799
|
+
status,
|
|
800
|
+
updatedAt: timestamp,
|
|
801
|
+
};
|
|
802
|
+
}
|
|
803
|
+
else {
|
|
804
|
+
if (status === "exited") {
|
|
805
|
+
throw new Error('The test runtime activity record cannot be finalized with "exited".');
|
|
806
|
+
}
|
|
807
|
+
this.record = {
|
|
808
|
+
...this.record,
|
|
809
|
+
endedAt: timestamp,
|
|
810
|
+
outcome,
|
|
811
|
+
status,
|
|
812
|
+
updatedAt: timestamp,
|
|
813
|
+
};
|
|
814
|
+
}
|
|
815
|
+
this.finalized = true;
|
|
816
|
+
await this.persist();
|
|
817
|
+
}
|
|
818
|
+
ensureStep(step) {
|
|
819
|
+
const existingIndex = this.record.steps.findIndex((currentStep) => currentStep.id === step.id);
|
|
820
|
+
if (existingIndex !== -1) {
|
|
821
|
+
return existingIndex;
|
|
822
|
+
}
|
|
823
|
+
this.record = {
|
|
824
|
+
...this.record,
|
|
825
|
+
steps: [...this.record.steps, createPendingActivityStep(step)],
|
|
826
|
+
};
|
|
827
|
+
return this.record.steps.length - 1;
|
|
828
|
+
}
|
|
829
|
+
updateStep(step, updater) {
|
|
830
|
+
const index = this.ensureStep(step);
|
|
831
|
+
const nextSteps = [...this.record.steps];
|
|
832
|
+
nextSteps[index] = updater(nextSteps[index]);
|
|
833
|
+
this.record = {
|
|
834
|
+
...this.record,
|
|
835
|
+
steps: nextSteps,
|
|
836
|
+
updatedAt: createTimestamp(),
|
|
837
|
+
};
|
|
838
|
+
}
|
|
839
|
+
async persist() {
|
|
840
|
+
await writeActivityRecord(this.filePath, this.record);
|
|
841
|
+
}
|
|
842
|
+
}
|
|
843
|
+
class LocalRuntimeSpawnError extends Error {
|
|
844
|
+
step;
|
|
845
|
+
code;
|
|
846
|
+
constructor(step, message, code) {
|
|
847
|
+
super(message);
|
|
848
|
+
this.step = step;
|
|
849
|
+
this.code = code;
|
|
850
|
+
}
|
|
851
|
+
}
|
|
852
|
+
//# sourceMappingURL=sandbox.js.map
|