@1agh/maude 0.28.0 → 0.28.1
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/cli/commands/design.mjs +69 -3
- package/cli/commands/design.test.mjs +97 -1
- package/cli/lib/cache.mjs +1 -1
- package/cli/lib/config-lint.mjs +1 -1
- package/cli/lib/copy-tree.mjs +1 -1
- package/cli/lib/design-link.mjs +1 -1
- package/cli/lib/flow-design-integration.test.mjs +1 -1
- package/cli/lib/gitignore-block.test.mjs +2 -2
- package/cli/lib/stack-detect.mjs +1 -1
- package/package.json +9 -9
- package/plugins/design/dev-server/annotations-context-toolbar.tsx +1 -1
- package/plugins/design/dev-server/annotations-layer.tsx +3 -3
- package/plugins/design/dev-server/api.ts +1 -1
- package/plugins/design/dev-server/bin/scenario-report.mjs +1 -1
- package/plugins/design/dev-server/bin/server-up.sh +102 -9
- package/plugins/design/dev-server/bin/visual-sanity.sh +19 -3
- package/plugins/design/dev-server/canvas-comment-mount.tsx +1 -1
- package/plugins/design/dev-server/canvas-cursors.ts +1 -1
- package/plugins/design/dev-server/canvas-lib.tsx +10 -21
- package/plugins/design/dev-server/canvas-shell.tsx +1 -1
- package/plugins/design/dev-server/collab/awareness-bridge.ts +1 -1
- package/plugins/design/dev-server/collab/index.ts +4 -4
- package/plugins/design/dev-server/collab/persistence.ts +1 -1
- package/plugins/design/dev-server/context-menu.tsx +1 -1
- package/plugins/design/dev-server/dist/client.bundle.js +4 -4
- package/plugins/design/dev-server/dist/comment-mount.js +5 -5
- package/plugins/design/dev-server/dist/runtime/lib0_decoding.js +1 -620
- package/plugins/design/dev-server/dist/runtime/lib0_encoding.js +1 -604
- package/plugins/design/dev-server/dist/runtime/motion.js +4 -4944
- package/plugins/design/dev-server/dist/runtime/motion_react.js +10 -10054
- package/plugins/design/dev-server/dist/runtime/pixi-js.js +2775 -52482
- package/plugins/design/dev-server/dist/runtime/react-dom.js +1 -231
- package/plugins/design/dev-server/dist/runtime/react-dom_client.js +9 -10357
- package/plugins/design/dev-server/dist/runtime/react.js +1 -535
- package/plugins/design/dev-server/dist/runtime/react_jsx-dev-runtime.js +1 -60
- package/plugins/design/dev-server/dist/runtime/react_jsx-runtime.js +1 -85
- package/plugins/design/dev-server/dist/runtime/y-protocols_awareness.js +1 -376
- package/plugins/design/dev-server/dist/runtime/y-protocols_sync.js +1 -100
- package/plugins/design/dev-server/dist/runtime/yjs.js +5 -6687
- package/plugins/design/dev-server/draw/brush.ts +3 -3
- package/plugins/design/dev-server/draw/index.ts +5 -5
- package/plugins/design/dev-server/draw/layout.ts +1 -1
- package/plugins/design/dev-server/draw/test/geometry.test.ts +1 -1
- package/plugins/design/dev-server/draw/test/layout.test.ts +1 -1
- package/plugins/design/dev-server/draw/test/primitives.test.ts +1 -1
- package/plugins/design/dev-server/draw/test/serialize.test.ts +1 -1
- package/plugins/design/dev-server/equal-spacing-handles.tsx +1 -1
- package/plugins/design/dev-server/examples/perf-100-artboards.tsx +1 -2
- package/plugins/design/dev-server/export-dialog.tsx +1 -1
- package/plugins/design/dev-server/exporters/html.ts +1 -1
- package/plugins/design/dev-server/exporters/index.ts +2 -2
- package/plugins/design/dev-server/exporters/pdf.ts +1 -1
- package/plugins/design/dev-server/exporters/png.ts +1 -1
- package/plugins/design/dev-server/exporters/pptx.ts +1 -1
- package/plugins/design/dev-server/exporters/svg.ts +1 -1
- package/plugins/design/dev-server/http.ts +1 -1
- package/plugins/design/dev-server/marquee-overlay.tsx +1 -1
- package/plugins/design/dev-server/server.ts +1 -1
- package/plugins/design/dev-server/sync/agent.ts +1 -1
- package/plugins/design/dev-server/sync/index.ts +5 -5
- package/plugins/design/dev-server/sync/materialize.ts +2 -2
- package/plugins/design/dev-server/sync/migrate-seed.ts +1 -1
- package/plugins/design/dev-server/sync/projection.ts +3 -3
- package/plugins/design/dev-server/test/ai-activity.test.ts +1 -1
- package/plugins/design/dev-server/test/annotations-api.test.ts +1 -2
- package/plugins/design/dev-server/test/annotations-draw-modifiers.test.ts +2 -2
- package/plugins/design/dev-server/test/annotations-layer.test.ts +6 -6
- package/plugins/design/dev-server/test/annotations-roundtrip.test.ts +1 -1
- package/plugins/design/dev-server/test/boot-self-heal.test.ts +1 -1
- package/plugins/design/dev-server/test/canvas-cursors.test.ts +1 -1
- package/plugins/design/dev-server/test/canvas-edit.test.ts +1 -1
- package/plugins/design/dev-server/test/canvas-header.test.ts +1 -1
- package/plugins/design/dev-server/test/canvas-hmr-runtime.test.tsx +1 -1
- package/plugins/design/dev-server/test/canvas-lib-inline.test.ts +1 -1
- package/plugins/design/dev-server/test/canvas-lib-resolver.test.ts +1 -2
- package/plugins/design/dev-server/test/canvas-meta-api.test.ts +1 -2
- package/plugins/design/dev-server/test/canvas-origin-gate.test.ts +1 -2
- package/plugins/design/dev-server/test/collab-annotations-bridge.test.ts +1 -1
- package/plugins/design/dev-server/test/collab-bridge.test.ts +1 -1
- package/plugins/design/dev-server/test/collab-protocol.test.ts +2 -2
- package/plugins/design/dev-server/test/collab-room.test.ts +1 -1
- package/plugins/design/dev-server/test/collab-stress.test.ts +1 -1
- package/plugins/design/dev-server/test/comments-api.test.ts +1 -2
- package/plugins/design/dev-server/test/compile-entry.test.ts +1 -2
- package/plugins/design/dev-server/test/exporters/canva.test.ts +1 -2
- package/plugins/design/dev-server/test/exporters/history.test.ts +1 -2
- package/plugins/design/dev-server/test/exporters/pw-launch.test.ts +1 -1
- package/plugins/design/dev-server/test/exporters/scope.test.ts +1 -2
- package/plugins/design/dev-server/test/exporters/zip.test.ts +1 -2
- package/plugins/design/dev-server/test/git-lifecycle.test.ts +1 -2
- package/plugins/design/dev-server/test/handoff-static-frames.test.ts +1 -1
- package/plugins/design/dev-server/test/handoff.test.ts +1 -1
- package/plugins/design/dev-server/test/hmr-broadcast.test.ts +1 -1
- package/plugins/design/dev-server/test/input-router.test.ts +2 -2
- package/plugins/design/dev-server/test/locator.test.ts +1 -1
- package/plugins/design/dev-server/test/runtime-bundle.test.ts +1 -1
- package/plugins/design/dev-server/test/shared-doc-convergence.test.ts +1 -1
- package/plugins/design/dev-server/test/shared-doc-migrate.test.ts +2 -3
- package/plugins/design/dev-server/test/shared-doc-projection.test.ts +1 -1
- package/plugins/design/dev-server/test/snap-distance-pill.test.ts +1 -1
- package/plugins/design/dev-server/test/sync-agent.test.ts +2 -3
- package/plugins/design/dev-server/test/sync-atomic-write.test.ts +2 -3
- package/plugins/design/dev-server/test/sync-codec.test.ts +1 -1
- package/plugins/design/dev-server/test/sync-connection-state.test.ts +1 -1
- package/plugins/design/dev-server/test/sync-echo-guard.test.ts +1 -1
- package/plugins/design/dev-server/test/sync-fs-mirror.test.ts +1 -2
- package/plugins/design/dev-server/test/sync-hardening.test.ts +4 -5
- package/plugins/design/dev-server/test/sync-hubs-config.test.ts +1 -2
- package/plugins/design/dev-server/test/sync-meta-codec.test.ts +1 -1
- package/plugins/design/dev-server/test/sync-runtime.test.ts +3 -8
- package/plugins/design/dev-server/test/sync-status.test.ts +1 -1
- package/plugins/design/dev-server/test/sync-untrusted.test.ts +1 -2
- package/plugins/design/dev-server/test/undo-stack.test.ts +2 -2
- package/plugins/design/dev-server/test/use-agent-presence.test.tsx +4 -1
- package/plugins/design/dev-server/test/use-artboard-drag.test.ts +3 -3
- package/plugins/design/dev-server/test/use-canvas-activity.test.tsx +2 -2
- package/plugins/design/dev-server/test/use-selection-set.test.tsx +1 -1
- package/plugins/design/dev-server/test/use-snap-guides.test.ts +1 -1
- package/plugins/design/dev-server/test/use-undo-stack.test.tsx +2 -2
- package/plugins/design/dev-server/use-agent-presence.tsx +1 -1
- package/plugins/design/dev-server/use-annotation-resize.tsx +1 -5
- package/plugins/design/dev-server/use-annotation-selection.tsx +2 -6
- package/plugins/design/dev-server/use-annotations-visibility.tsx +1 -1
- package/plugins/design/dev-server/use-artboard-drag.tsx +1 -1
- package/plugins/design/dev-server/use-canvas-activity.tsx +1 -1
- package/plugins/design/dev-server/use-collab.tsx +3 -4
- package/plugins/design/dev-server/use-selection-set.tsx +1 -1
- package/plugins/design/dev-server/use-tool-mode.tsx +1 -1
- package/plugins/design/dev-server/use-undo-stack.tsx +4 -5
- package/plugins/design/templates/_shell.html +3 -3
- package/plugins/design/templates/design-system-inspiration/SUB-AGENT-PROMPTS.md +18 -3
package/cli/commands/design.mjs
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { execSync, spawn, spawnSync } from 'node:child_process';
|
|
2
|
-
import { existsSync } from 'node:fs';
|
|
3
|
-
import { mkdir,
|
|
2
|
+
import { chmodSync, existsSync, readFileSync } from 'node:fs';
|
|
3
|
+
import { mkdir, readdir, readFile, stat, writeFile } from 'node:fs/promises';
|
|
4
4
|
import { createRequire } from 'node:module';
|
|
5
5
|
import { basename, dirname, join, resolve } from 'node:path';
|
|
6
6
|
import { parseArgs } from '../lib/argv.mjs';
|
|
@@ -42,6 +42,11 @@ const BIN_VERBS = new Set([
|
|
|
42
42
|
'svg-optimize',
|
|
43
43
|
]);
|
|
44
44
|
|
|
45
|
+
// Bin verbs that boot the dev-server (directly, or by shelling into server-up.sh).
|
|
46
|
+
// For these we resolve the compiled platform binary and hand its path to the
|
|
47
|
+
// helper via MAUDE_DEV_SERVER_BIN — see runBinDispatch + DDR-084.
|
|
48
|
+
const BOOT_VERBS = new Set(['server-up', 'visual-sanity', 'smoke']);
|
|
49
|
+
|
|
45
50
|
export async function run({ args, pkgRoot }) {
|
|
46
51
|
const { positional } = parseArgs(args);
|
|
47
52
|
const sub = positional[0];
|
|
@@ -90,9 +95,21 @@ function runBinDispatch(verb, { args, pkgRoot }) {
|
|
|
90
95
|
process.exit(1);
|
|
91
96
|
}
|
|
92
97
|
const rest = args.slice(args.indexOf(verb) + 1); // everything after the verb token
|
|
98
|
+
const childEnv = { ...process.env, CLAUDE_PLUGIN_ROOT: pluginRoot };
|
|
99
|
+
// For verbs that boot the dev-server, resolve the compiled platform binary the
|
|
100
|
+
// SAME way `maude design serve` does and hand the path down. The binary embeds
|
|
101
|
+
// yjs + every runtime dep; `bun server.ts` from source does NOT (the deps live
|
|
102
|
+
// in a nested package.json the npm tarball excludes), so on a production install
|
|
103
|
+
// server.ts crashes at boot on `yjs`. server-up.sh prefers MAUDE_DEV_SERVER_BIN
|
|
104
|
+
// when set; absent ⇒ dev tree ⇒ it falls back to `bun server.ts`. Helpers that
|
|
105
|
+
// shell into server-up.sh (visual-sanity, smoke) inherit it. DDR-084.
|
|
106
|
+
if (BOOT_VERBS.has(verb) && !process.env.MAUDE_DEV_SERVER_BIN) {
|
|
107
|
+
const bin = resolveServerBinary({ pkgRoot });
|
|
108
|
+
if (bin) childEnv.MAUDE_DEV_SERVER_BIN = bin;
|
|
109
|
+
}
|
|
93
110
|
const child = spawnSync('bash', [script, ...rest], {
|
|
94
111
|
stdio: 'inherit',
|
|
95
|
-
env:
|
|
112
|
+
env: childEnv,
|
|
96
113
|
});
|
|
97
114
|
if (child.error) {
|
|
98
115
|
process.stderr.write(`maude design ${verb}: ${child.error.message}\n`);
|
|
@@ -295,6 +312,55 @@ function detectPlatformSlug() {
|
|
|
295
312
|
return null;
|
|
296
313
|
}
|
|
297
314
|
|
|
315
|
+
// Read-only resolution of the compiled platform binary the dev-server should
|
|
316
|
+
// boot as in a production install (it embeds yjs + every runtime dep). Mirrors
|
|
317
|
+
// runServe's order — side-channel cache (postinstall) → lazy resolve of the
|
|
318
|
+
// @1agh/maude-<slug> sibling/nested package — but performs NO caching and NEVER
|
|
319
|
+
// hard-fails: returns the path, or null when none is found. A null result means
|
|
320
|
+
// "fall back to `bun server.ts` from source" (correct in the local dev tree).
|
|
321
|
+
// Honors MAUDE_FORCE_SOURCE=1 so maintainers hacking on the dev-server source
|
|
322
|
+
// still boot their working copy. Used by runBinDispatch to hand the path to
|
|
323
|
+
// server-up.sh; runServe keeps its own inline copy (it also caches + hard-fails).
|
|
324
|
+
// Structural allowlist for a path we're about to spawn (DDR-084 hardening). The
|
|
325
|
+
// compiled binary is ALWAYS named `maude` / `maude.exe` inside a `maude-<slug>/`
|
|
326
|
+
// dir (the @1agh/maude-<slug> package, or the dev-tree `packages/maude-<slug>/`).
|
|
327
|
+
// The side-channel file's content is the one attacker-influenceable input in a
|
|
328
|
+
// shared-prefix / poisoned-clone layout — a path outside this shape is ignored
|
|
329
|
+
// (caller falls back to source), so the file can only DENY the binary, never
|
|
330
|
+
// redirect the spawn to an arbitrary executable. Paths `lazyResolveBinary`
|
|
331
|
+
// constructs always conform, so this never rejects a legit resolve.
|
|
332
|
+
export function isPlausiblePlatformBinary(p) {
|
|
333
|
+
if (!p) return false;
|
|
334
|
+
const b = basename(p);
|
|
335
|
+
if (b !== 'maude' && b !== 'maude.exe') return false;
|
|
336
|
+
return /^maude-[a-z0-9-]+$/.test(basename(dirname(p)));
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
export function resolveServerBinary({ pkgRoot }) {
|
|
340
|
+
if (process.env.MAUDE_FORCE_SOURCE === '1') return null;
|
|
341
|
+
// In the local source checkout, return null so server-up.sh boots
|
|
342
|
+
// `bun server.ts` from source — the maintainer is editing the dev-server and
|
|
343
|
+
// needs their working copy live, not a stale compiled binary that happens to
|
|
344
|
+
// be installed via pnpm. A production install has no `packages/` dir, so this
|
|
345
|
+
// never short-circuits there. (Preserves server-up's historic dev-tree
|
|
346
|
+
// behavior — it always ran source before DDR-084.)
|
|
347
|
+
if (isLocalDevTree(pkgRoot, { existsSync })) return null;
|
|
348
|
+
const sideChannel = resolve(pkgRoot, 'cli', '.platform-binary-path');
|
|
349
|
+
try {
|
|
350
|
+
if (existsSync(sideChannel)) {
|
|
351
|
+
const candidate = readFileSync(sideChannel, 'utf8').trim();
|
|
352
|
+
// Existence AND the structural allowlist — a poisoned side-channel pointing
|
|
353
|
+
// at an arbitrary executable is ignored (falls through to lazy resolve).
|
|
354
|
+
if (candidate && existsSync(candidate) && isPlausiblePlatformBinary(candidate)) {
|
|
355
|
+
return candidate;
|
|
356
|
+
}
|
|
357
|
+
}
|
|
358
|
+
} catch {
|
|
359
|
+
/* fall through to lazy resolve */
|
|
360
|
+
}
|
|
361
|
+
return lazyResolveBinary({ pkgRoot, fs: { existsSync, chmodSync } }).binPath;
|
|
362
|
+
}
|
|
363
|
+
|
|
298
364
|
function lazyResolveBinary({ pkgRoot, fs }) {
|
|
299
365
|
const slug = detectPlatformSlug();
|
|
300
366
|
if (!slug) return { binPath: null, slug: null };
|
|
@@ -8,11 +8,12 @@
|
|
|
8
8
|
|
|
9
9
|
import assert from 'node:assert/strict';
|
|
10
10
|
import { spawnSync } from 'node:child_process';
|
|
11
|
-
import { mkdtempSync, rmSync } from 'node:fs';
|
|
11
|
+
import { chmodSync, mkdirSync, mkdtempSync, rmSync, writeFileSync } from 'node:fs';
|
|
12
12
|
import { tmpdir } from 'node:os';
|
|
13
13
|
import { dirname, join, resolve } from 'node:path';
|
|
14
14
|
import { test } from 'node:test';
|
|
15
15
|
import { fileURLToPath } from 'node:url';
|
|
16
|
+
import { isPlausiblePlatformBinary, resolveServerBinary } from './design.mjs';
|
|
16
17
|
|
|
17
18
|
const BIN = resolve(dirname(fileURLToPath(import.meta.url)), '..', 'bin', 'maude.mjs');
|
|
18
19
|
|
|
@@ -54,3 +55,98 @@ test('design help lists the dev-tooling verbs', () => {
|
|
|
54
55
|
assert.match(res.stdout, new RegExp(verb), `usage should mention ${verb}`);
|
|
55
56
|
}
|
|
56
57
|
});
|
|
58
|
+
|
|
59
|
+
// resolveServerBinary — the dev-server boot-runtime resolver (DDR-084). server-up
|
|
60
|
+
// must boot the compiled platform binary on a production install (it embeds yjs +
|
|
61
|
+
// every dep; `bun server.ts` from source can't resolve them — the historic
|
|
62
|
+
// yjs-at-boot crash), but keep using SOURCE in the local dev tree so a maintainer's
|
|
63
|
+
// edits are live.
|
|
64
|
+
function fakePkgRoot({ devTree = false, sideChannelBin = null } = {}) {
|
|
65
|
+
const root = mkdtempSync(join(tmpdir(), 'maude-pkgroot-'));
|
|
66
|
+
if (devTree) {
|
|
67
|
+
mkdirSync(join(root, 'packages', 'maude-darwin-arm64'), { recursive: true });
|
|
68
|
+
writeFileSync(join(root, 'packages', 'maude-darwin-arm64', 'package.json'), '{}');
|
|
69
|
+
}
|
|
70
|
+
if (sideChannelBin) {
|
|
71
|
+
mkdirSync(join(root, 'cli'), { recursive: true });
|
|
72
|
+
writeFileSync(join(root, 'cli', '.platform-binary-path'), sideChannelBin);
|
|
73
|
+
}
|
|
74
|
+
return root;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
test('resolveServerBinary → null in the local dev tree (packages/ present) — boots source', () => {
|
|
78
|
+
const root = fakePkgRoot({ devTree: true });
|
|
79
|
+
try {
|
|
80
|
+
delete process.env.MAUDE_FORCE_SOURCE;
|
|
81
|
+
assert.equal(resolveServerBinary({ pkgRoot: root }), null);
|
|
82
|
+
} finally {
|
|
83
|
+
rmSync(root, { recursive: true, force: true });
|
|
84
|
+
}
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
// Build an executable in a layout the allowlist accepts (`maude-<slug>/maude`)
|
|
88
|
+
// or rejects (anything else), under a throwaway dir.
|
|
89
|
+
function fakeBinary({ conforming = true } = {}) {
|
|
90
|
+
const home = mkdtempSync(join(tmpdir(), 'maude-bin-'));
|
|
91
|
+
const dir = join(home, conforming ? 'maude-darwin-arm64' : 'evil');
|
|
92
|
+
mkdirSync(dir, { recursive: true });
|
|
93
|
+
const bin = join(dir, 'maude');
|
|
94
|
+
writeFileSync(bin, '#!/bin/sh\n');
|
|
95
|
+
chmodSync(bin, 0o755);
|
|
96
|
+
return { home, bin };
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
test('resolveServerBinary → null when MAUDE_FORCE_SOURCE=1 (maintainer override)', () => {
|
|
100
|
+
const { home, bin } = fakeBinary();
|
|
101
|
+
const root = fakePkgRoot({ sideChannelBin: bin }); // NOT a dev tree
|
|
102
|
+
const prev = process.env.MAUDE_FORCE_SOURCE;
|
|
103
|
+
try {
|
|
104
|
+
process.env.MAUDE_FORCE_SOURCE = '1';
|
|
105
|
+
assert.equal(resolveServerBinary({ pkgRoot: root }), null);
|
|
106
|
+
} finally {
|
|
107
|
+
if (prev === undefined) delete process.env.MAUDE_FORCE_SOURCE;
|
|
108
|
+
else process.env.MAUDE_FORCE_SOURCE = prev;
|
|
109
|
+
rmSync(root, { recursive: true, force: true });
|
|
110
|
+
rmSync(home, { recursive: true, force: true });
|
|
111
|
+
}
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
test('resolveServerBinary → side-channel path on a production install (no packages/)', () => {
|
|
115
|
+
const { home, bin } = fakeBinary(); // conforming maude-<slug>/maude layout
|
|
116
|
+
const root = fakePkgRoot({ sideChannelBin: bin }); // production shape
|
|
117
|
+
const prev = process.env.MAUDE_FORCE_SOURCE;
|
|
118
|
+
try {
|
|
119
|
+
delete process.env.MAUDE_FORCE_SOURCE;
|
|
120
|
+
assert.equal(resolveServerBinary({ pkgRoot: root }), bin);
|
|
121
|
+
} finally {
|
|
122
|
+
if (prev !== undefined) process.env.MAUDE_FORCE_SOURCE = prev;
|
|
123
|
+
rmSync(root, { recursive: true, force: true });
|
|
124
|
+
rmSync(home, { recursive: true, force: true });
|
|
125
|
+
}
|
|
126
|
+
});
|
|
127
|
+
|
|
128
|
+
test('resolveServerBinary → REJECTS a side-channel pointing outside the maude-<slug>/ layout (DDR-084 allowlist)', () => {
|
|
129
|
+
const { home, bin } = fakeBinary({ conforming: false }); // <tmp>/evil/maude
|
|
130
|
+
const root = fakePkgRoot({ sideChannelBin: bin }); // production shape, no real platform pkg
|
|
131
|
+
const prev = process.env.MAUDE_FORCE_SOURCE;
|
|
132
|
+
try {
|
|
133
|
+
delete process.env.MAUDE_FORCE_SOURCE;
|
|
134
|
+
// Poisoned side-channel is ignored → falls through to lazyResolve, which finds
|
|
135
|
+
// nothing in the fake root → null (server-up then boots source, not the planted bin).
|
|
136
|
+
assert.equal(resolveServerBinary({ pkgRoot: root }), null);
|
|
137
|
+
} finally {
|
|
138
|
+
if (prev !== undefined) process.env.MAUDE_FORCE_SOURCE = prev;
|
|
139
|
+
rmSync(root, { recursive: true, force: true });
|
|
140
|
+
rmSync(home, { recursive: true, force: true });
|
|
141
|
+
}
|
|
142
|
+
});
|
|
143
|
+
|
|
144
|
+
test('isPlausiblePlatformBinary — accepts maude-<slug>/maude, rejects everything else', () => {
|
|
145
|
+
assert.equal(isPlausiblePlatformBinary('/x/node_modules/@1agh/maude-darwin-arm64/maude'), true);
|
|
146
|
+
assert.equal(isPlausiblePlatformBinary('/x/packages/maude-linux-x64-musl/maude'), true);
|
|
147
|
+
assert.equal(isPlausiblePlatformBinary('/x/maude-win32-x64/maude.exe'), true);
|
|
148
|
+
assert.equal(isPlausiblePlatformBinary('/tmp/evil/maude'), false); // parent not maude-<slug>
|
|
149
|
+
assert.equal(isPlausiblePlatformBinary('/x/maude-darwin-arm64/evil'), false); // basename not maude
|
|
150
|
+
assert.equal(isPlausiblePlatformBinary(''), false);
|
|
151
|
+
assert.equal(isPlausiblePlatformBinary(null), false);
|
|
152
|
+
});
|
package/cli/lib/cache.mjs
CHANGED
package/cli/lib/config-lint.mjs
CHANGED
|
@@ -16,8 +16,8 @@
|
|
|
16
16
|
|
|
17
17
|
import { readFile } from 'node:fs/promises';
|
|
18
18
|
import { resolve } from 'node:path';
|
|
19
|
-
import addFormats from 'ajv-formats';
|
|
20
19
|
import Ajv2020 from 'ajv/dist/2020.js';
|
|
20
|
+
import addFormats from 'ajv-formats';
|
|
21
21
|
|
|
22
22
|
function levenshtein(a, b) {
|
|
23
23
|
if (a === b) return 0;
|
package/cli/lib/copy-tree.mjs
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { copyFile, mkdir,
|
|
1
|
+
import { copyFile, mkdir, readdir, readFile, stat, writeFile } from 'node:fs/promises';
|
|
2
2
|
import { basename, join, relative } from 'node:path';
|
|
3
3
|
|
|
4
4
|
// Recursively copy `src` → `dest`. Returns { created, skipped, replaced }.
|
package/cli/lib/design-link.mjs
CHANGED
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
//
|
|
11
11
|
// Token NEVER lands in .design/config.json — that's git-committed.
|
|
12
12
|
|
|
13
|
-
import { existsSync,
|
|
13
|
+
import { existsSync, readdirSync, readFileSync, statSync, writeFileSync } from 'node:fs';
|
|
14
14
|
import { dirname, resolve } from 'node:path';
|
|
15
15
|
import { createInterface } from 'node:readline';
|
|
16
16
|
|
|
@@ -19,8 +19,8 @@ import assert from 'node:assert/strict';
|
|
|
19
19
|
import { readFileSync } from 'node:fs';
|
|
20
20
|
import { resolve } from 'node:path';
|
|
21
21
|
import { test } from 'node:test';
|
|
22
|
-
import addFormats from 'ajv-formats';
|
|
23
22
|
import Ajv2020 from 'ajv/dist/2020.js';
|
|
23
|
+
import addFormats from 'ajv-formats';
|
|
24
24
|
import { lintConfig } from './config-lint.mjs';
|
|
25
25
|
|
|
26
26
|
const read = (p) => readFileSync(resolve(p), 'utf8');
|
package/cli/lib/stack-detect.mjs
CHANGED
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
//
|
|
11
11
|
// Consumers: cli/commands/doctor.mjs and the drift-aware /flow:init re-run.
|
|
12
12
|
|
|
13
|
-
import {
|
|
13
|
+
import { readdir, readFile, stat } from 'node:fs/promises';
|
|
14
14
|
import { join } from 'node:path';
|
|
15
15
|
|
|
16
16
|
async function fileExists(path) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@1agh/maude",
|
|
3
|
-
"version": "0.28.
|
|
3
|
+
"version": "0.28.1",
|
|
4
4
|
"description": "Marketplace of Claude Code plugins by Michal Dovrtěl: `design` (canvas-first design iteration) + `flow` (generic agentic workflow loop with .ai second brain). Ships the `maude` CLI (with `mdcc` legacy alias) to scaffold workspace, run the design dev server, and manage configs.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"engines": {
|
|
@@ -41,13 +41,13 @@
|
|
|
41
41
|
"prepublishOnly": "bash scripts/check-version-parity.sh && bash plugins/design/dev-server/bin/check-runtime-bundles.sh"
|
|
42
42
|
},
|
|
43
43
|
"optionalDependencies": {
|
|
44
|
-
"@1agh/maude-darwin-arm64": "0.28.
|
|
45
|
-
"@1agh/maude-darwin-x64": "0.28.
|
|
46
|
-
"@1agh/maude-linux-arm64": "0.28.
|
|
47
|
-
"@1agh/maude-linux-arm64-musl": "0.28.
|
|
48
|
-
"@1agh/maude-linux-x64": "0.28.
|
|
49
|
-
"@1agh/maude-linux-x64-musl": "0.28.
|
|
50
|
-
"@1agh/maude-win32-x64": "0.28.
|
|
44
|
+
"@1agh/maude-darwin-arm64": "0.28.1",
|
|
45
|
+
"@1agh/maude-darwin-x64": "0.28.1",
|
|
46
|
+
"@1agh/maude-linux-arm64": "0.28.1",
|
|
47
|
+
"@1agh/maude-linux-arm64-musl": "0.28.1",
|
|
48
|
+
"@1agh/maude-linux-x64": "0.28.1",
|
|
49
|
+
"@1agh/maude-linux-x64-musl": "0.28.1",
|
|
50
|
+
"@1agh/maude-win32-x64": "0.28.1"
|
|
51
51
|
},
|
|
52
52
|
"files": [
|
|
53
53
|
"cli",
|
|
@@ -85,7 +85,7 @@
|
|
|
85
85
|
"scaffold"
|
|
86
86
|
],
|
|
87
87
|
"devDependencies": {
|
|
88
|
-
"@biomejs/biome": "^
|
|
88
|
+
"@biomejs/biome": "^2.4.16",
|
|
89
89
|
"@changesets/cli": "^2.27.10",
|
|
90
90
|
"@playwright/test": "^1.60.0"
|
|
91
91
|
},
|
|
@@ -36,12 +36,12 @@ import {
|
|
|
36
36
|
IconAlignCenter,
|
|
37
37
|
IconAlignLeft,
|
|
38
38
|
IconAlignRight,
|
|
39
|
-
IconArrowNone,
|
|
40
39
|
IconArrowheadCircle,
|
|
41
40
|
IconArrowheadDiamond,
|
|
42
41
|
IconArrowheadLine,
|
|
43
42
|
IconArrowheadTriangle,
|
|
44
43
|
IconArrowheadTriangleOutline,
|
|
44
|
+
IconArrowNone,
|
|
45
45
|
IconBold,
|
|
46
46
|
IconChevronDown,
|
|
47
47
|
IconCornerPill,
|
|
@@ -23,8 +23,8 @@
|
|
|
23
23
|
|
|
24
24
|
import {
|
|
25
25
|
type CSSProperties,
|
|
26
|
-
type PointerEvent as ReactPointerEvent,
|
|
27
26
|
createContext,
|
|
27
|
+
type PointerEvent as ReactPointerEvent,
|
|
28
28
|
useCallback,
|
|
29
29
|
useContext,
|
|
30
30
|
useEffect,
|
|
@@ -39,13 +39,13 @@ import {
|
|
|
39
39
|
ARROW_HEADS,
|
|
40
40
|
type ArrowHead,
|
|
41
41
|
type ArrowLineType,
|
|
42
|
-
type SvgPrimitive,
|
|
43
42
|
arrowPrimitives,
|
|
43
|
+
type SvgPrimitive,
|
|
44
44
|
} from './canvas-arrowheads.ts';
|
|
45
45
|
import { IconLineThick, IconLineThin } from './canvas-icons.tsx';
|
|
46
46
|
import { useViewportControllerContext, useWorldRefContext } from './canvas-lib.tsx';
|
|
47
47
|
import { buildAnnotationStrokesRecord } from './commands/annotation-strokes-command.ts';
|
|
48
|
-
import { type Tool
|
|
48
|
+
import { crossedDragThreshold, type Tool } from './input-router.tsx';
|
|
49
49
|
import { AnnotationResizeOverlay } from './use-annotation-resize.tsx';
|
|
50
50
|
import { useAnnotationSelectionOptional } from './use-annotation-selection.tsx';
|
|
51
51
|
import { useAnnotationsVisibility } from './use-annotations-visibility.tsx';
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
|
|
4
4
|
import crypto from 'node:crypto';
|
|
5
5
|
import type { Dirent } from 'node:fs';
|
|
6
|
-
import {
|
|
6
|
+
import { readdir, readFile, stat as statp } from 'node:fs/promises';
|
|
7
7
|
import path from 'node:path';
|
|
8
8
|
|
|
9
9
|
import type { Context } from './context.ts';
|
|
@@ -21,7 +21,7 @@
|
|
|
21
21
|
//
|
|
22
22
|
// Output: <run-dir>/report.md (or --out), zero external dependencies (node:fs only).
|
|
23
23
|
|
|
24
|
-
import { existsSync,
|
|
24
|
+
import { existsSync, readdirSync, readFileSync, statSync, writeFileSync } from 'node:fs';
|
|
25
25
|
import { basename, join } from 'node:path';
|
|
26
26
|
|
|
27
27
|
const PLATFORM_ORDER = ['web-desktop', 'web-mobile', 'ios-phone', 'ios-tablet', 'android-phone'];
|
|
@@ -9,13 +9,17 @@
|
|
|
9
9
|
# $DESIGN_ROOT/_server.json (PID + port the running server wrote)
|
|
10
10
|
# $DESIGN_ROOT/_server.log (stdout/stderr of the spawned server)
|
|
11
11
|
#
|
|
12
|
-
# Runtime selection (DDR-020):
|
|
13
|
-
# -
|
|
14
|
-
# -
|
|
15
|
-
#
|
|
12
|
+
# Runtime selection (DDR-020 / DDR-009 / DDR-084), in priority order:
|
|
13
|
+
# - --allow-legacy: opt-in node + server.mjs (debug only; no TSX pipeline, no HMR).
|
|
14
|
+
# - compiled platform binary: the PRODUCTION runtime — embeds yjs + every dep, so
|
|
15
|
+
# it boots on a global @1agh/maude install where `bun server.ts` from source
|
|
16
|
+
# cannot. Path comes from MAUDE_DEV_SERVER_BIN (set by `maude design <verb>`) or
|
|
17
|
+
# the postinstall side-channel <pkgRoot>/cli/.platform-binary-path.
|
|
18
|
+
# - bun + server.ts: dev tree / fallback. MAUDE_FORCE_SOURCE=1 forces this.
|
|
16
19
|
#
|
|
17
20
|
# Output: prints the port on stdout. Diagnostic lines go to stderr.
|
|
18
|
-
# Exit: 0 = server ready / 1 = start timeout or missing runtime / 2 = bad args
|
|
21
|
+
# Exit: 0 = server ready / 1 = start timeout or missing runtime / 2 = bad args /
|
|
22
|
+
# 3 = dev-server runtime deps missing (yjs/y-protocols/lib0 — see preflight).
|
|
19
23
|
|
|
20
24
|
REPO=""
|
|
21
25
|
TIMEOUT=10
|
|
@@ -41,9 +45,15 @@ if [ -z "$REPO" ]; then
|
|
|
41
45
|
REPO="${CLAUDE_PROJECT_DIR:-$(git rev-parse --show-toplevel 2>/dev/null || pwd)}"
|
|
42
46
|
fi
|
|
43
47
|
|
|
44
|
-
# Resolve plugin root + pick a server runtime.
|
|
45
|
-
#
|
|
46
|
-
#
|
|
48
|
+
# Resolve plugin root + pick a server runtime.
|
|
49
|
+
# --allow-legacy → node + server.mjs (debug-only — no TSX pipeline, no HMR).
|
|
50
|
+
# compiled platform binary → the PRODUCTION runtime (DDR-009/DDR-084). It embeds
|
|
51
|
+
# yjs + every dev-server dep, so it boots on a global `@1agh/maude` install
|
|
52
|
+
# where `bun server.ts` from source CANNOT (the deps live in a nested
|
|
53
|
+
# package.json the npm tarball excludes — the historic yjs-at-boot crash).
|
|
54
|
+
# `maude design <verb>` resolves it and passes MAUDE_DEV_SERVER_BIN; a direct
|
|
55
|
+
# `bash server-up.sh` falls back to the postinstall side-channel.
|
|
56
|
+
# bun + server.ts → dev tree / fallback (DDR-020 made bun authoritative for source).
|
|
47
57
|
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
|
|
48
58
|
PLUGIN_ROOT="${CLAUDE_PLUGIN_ROOT:-$(cd "$SCRIPT_DIR/../.." && pwd)}"
|
|
49
59
|
SERVER_TS="$PLUGIN_ROOT/dev-server/server.ts"
|
|
@@ -51,6 +61,37 @@ SERVER_MJS="$PLUGIN_ROOT/dev-server/server.mjs"
|
|
|
51
61
|
if [ ! -f "$SERVER_TS" ]; then SERVER_TS="$SCRIPT_DIR/../server.ts"; fi
|
|
52
62
|
if [ ! -f "$SERVER_MJS" ]; then SERVER_MJS="$SCRIPT_DIR/../server.mjs"; fi
|
|
53
63
|
|
|
64
|
+
# Resolve the compiled binary: env (set by `maude design <verb>`) wins; else the
|
|
65
|
+
# postinstall side-channel at <pkgRoot>/cli/.platform-binary-path (covers a direct
|
|
66
|
+
# `bash server-up.sh` on a production install). MAUDE_FORCE_SOURCE=1 forces source.
|
|
67
|
+
SERVER_BIN="${MAUDE_DEV_SERVER_BIN:-}"
|
|
68
|
+
if [ -z "$SERVER_BIN" ] && [ "${MAUDE_FORCE_SOURCE:-0}" != "1" ]; then
|
|
69
|
+
# PLUGIN_ROOT=<pkgRoot>/plugins/design, so the side-channel is ../../cli.
|
|
70
|
+
# `head -n1` (parity with the Node readers' `.trim()`) tolerates a trailing newline.
|
|
71
|
+
SC="$PLUGIN_ROOT/../../cli/.platform-binary-path"
|
|
72
|
+
if [ -f "$SC" ]; then
|
|
73
|
+
CAND="$(head -n1 "$SC" 2>/dev/null)"
|
|
74
|
+
[ -n "$CAND" ] && [ -x "$CAND" ] && SERVER_BIN="$CAND"
|
|
75
|
+
fi
|
|
76
|
+
fi
|
|
77
|
+
# Structural allowlist (DDR-084 hardening): only spawn a path that LOOKS like the
|
|
78
|
+
# compiled platform binary — basename `maude`/`maude.exe` inside a `maude-<slug>/`
|
|
79
|
+
# dir. A poisoned side-channel file or an injected MAUDE_DEV_SERVER_BIN pointing
|
|
80
|
+
# elsewhere is ignored (fall back to source), so the env/file can only DENY the
|
|
81
|
+
# binary, never redirect the spawn to an arbitrary executable. Mirrors
|
|
82
|
+
# isPlausiblePlatformBinary() in cli/commands/design.mjs.
|
|
83
|
+
if [ -n "$SERVER_BIN" ]; then
|
|
84
|
+
_sbase="$(basename "$SERVER_BIN")"
|
|
85
|
+
_sparent="$(basename "$(dirname "$SERVER_BIN")")"
|
|
86
|
+
case "$_sbase:$_sparent" in
|
|
87
|
+
maude:maude-* | maude.exe:maude-*) : ;;
|
|
88
|
+
*)
|
|
89
|
+
echo "server-up.sh: ignoring dev-server binary outside the expected maude-<slug>/ layout ($SERVER_BIN) — falling back to source" >&2
|
|
90
|
+
SERVER_BIN=""
|
|
91
|
+
;;
|
|
92
|
+
esac
|
|
93
|
+
fi
|
|
94
|
+
|
|
54
95
|
RUNTIME=""
|
|
55
96
|
RUNTIME_CMD=""
|
|
56
97
|
if [ $ALLOW_LEGACY -eq 1 ]; then
|
|
@@ -61,6 +102,10 @@ if [ $ALLOW_LEGACY -eq 1 ]; then
|
|
|
61
102
|
RUNTIME="node"
|
|
62
103
|
RUNTIME_CMD="node $SERVER_MJS"
|
|
63
104
|
echo "server-up.sh: WARNING — running legacy server.mjs (no TSX canvas pipeline, no HMR). DDR-020 sunsets this in Phase B." >&2
|
|
105
|
+
elif [ -n "$SERVER_BIN" ] && [ -x "$SERVER_BIN" ] && [ "${MAUDE_FORCE_SOURCE:-0}" != "1" ]; then
|
|
106
|
+
RUNTIME="binary"
|
|
107
|
+
RUNTIME_CMD="$SERVER_BIN"
|
|
108
|
+
echo "server-up.sh: using compiled dev-server binary ($SERVER_BIN)" >&2
|
|
64
109
|
elif command -v bun >/dev/null 2>&1 && [ -f "$SERVER_TS" ]; then
|
|
65
110
|
RUNTIME="bun"
|
|
66
111
|
RUNTIME_CMD="bun $SERVER_TS"
|
|
@@ -106,10 +151,58 @@ if [ -f "$STATE" ]; then
|
|
|
106
151
|
fi
|
|
107
152
|
fi
|
|
108
153
|
|
|
154
|
+
# Step 1.5 — dependency preflight (source `bun server.ts` path ONLY; runs on cold
|
|
155
|
+
# start). The compiled binary embeds its deps, so this is skipped when RUNTIME=binary
|
|
156
|
+
# — it only guards the source fallback. The dev-server's runtime deps live in a
|
|
157
|
+
# NESTED package.json (plugins/design/dev-server/) that a global `@1agh/maude` npm
|
|
158
|
+
# install or a fresh `git worktree` does NOT populate; a missing `yjs` (imported at
|
|
159
|
+
# boot by sync/index.ts) crashes `bun server.ts` AFTER spawn → without this guard we
|
|
160
|
+
# poll the full ${TIMEOUT}s and report a generic "start timeout", burying the cause
|
|
161
|
+
# in _server.log and silently degrading the mandatory visual-sanity gate. Fail loud
|
|
162
|
+
# NOW with a context-correct hint. No auto-install: boot-self-heal.ts (DDR-044)
|
|
163
|
+
# dropped that, and the durable production fix is booting the compiled binary
|
|
164
|
+
# (DDR-009/DDR-084), not a boot-time install. setup-ds Round-2 / DDR-083 + DDR-084.
|
|
165
|
+
if [ "$RUNTIME" = "bun" ]; then
|
|
166
|
+
DEV_SERVER_DIR="$(cd "$(dirname "$SERVER_TS")" && pwd)"
|
|
167
|
+
_resolve_dep() {
|
|
168
|
+
# Mirror node/bun bare-specifier resolution: walk node_modules up to /.
|
|
169
|
+
# Handles both the dev-server-local install and a workspace-hoisted one.
|
|
170
|
+
local dep="$1" d="$DEV_SERVER_DIR"
|
|
171
|
+
while [ -n "$d" ] && [ "$d" != "/" ]; do
|
|
172
|
+
[ -e "$d/node_modules/$dep/package.json" ] && return 0
|
|
173
|
+
d="$(dirname "$d")"
|
|
174
|
+
done
|
|
175
|
+
return 1
|
|
176
|
+
}
|
|
177
|
+
MISSING_DEPS=""
|
|
178
|
+
for dep in yjs y-protocols lib0; do
|
|
179
|
+
_resolve_dep "$dep" || MISSING_DEPS="$MISSING_DEPS $dep"
|
|
180
|
+
done
|
|
181
|
+
if [ -n "$MISSING_DEPS" ]; then
|
|
182
|
+
{
|
|
183
|
+
echo "✗ dev-server runtime deps not installed:$MISSING_DEPS"
|
|
184
|
+
if [ -f "$DEV_SERVER_DIR/package.json" ]; then
|
|
185
|
+
# A manifest is present (local dev tree / checkout) — install resolves it.
|
|
186
|
+
echo " → run: (cd \"$DEV_SERVER_DIR\" && bun install) # or: pnpm install at the repo root"
|
|
187
|
+
else
|
|
188
|
+
# No manifest beside server.ts AND no compiled binary resolved → the
|
|
189
|
+
# '@1agh/maude' install is incomplete. The platform binary (which embeds
|
|
190
|
+
# these deps) is the production runtime — reinstall to get it.
|
|
191
|
+
echo " (no dev-server package.json here and no compiled binary resolved — the"
|
|
192
|
+
echo " '@1agh/maude' install is incomplete; the platform binary embeds these deps)"
|
|
193
|
+
echo " → reinstall: npm i -g @1agh/maude (or: npm rebuild -g @1agh/maude)"
|
|
194
|
+
fi
|
|
195
|
+
} >&2
|
|
196
|
+
exit 3
|
|
197
|
+
fi
|
|
198
|
+
fi
|
|
199
|
+
|
|
109
200
|
# Step 2 — spawn.
|
|
110
201
|
mkdir -p "$DESIGN_ROOT"
|
|
111
202
|
echo "→ starting dev server: $RUNTIME_CMD --root $REPO" >&2
|
|
112
|
-
if [ "$RUNTIME" = "
|
|
203
|
+
if [ "$RUNTIME" = "binary" ]; then
|
|
204
|
+
nohup "$SERVER_BIN" --root "$REPO" > "$DESIGN_ROOT/_server.log" 2>&1 &
|
|
205
|
+
elif [ "$RUNTIME" = "bun" ]; then
|
|
113
206
|
nohup bun "$SERVER_TS" --root "$REPO" > "$DESIGN_ROOT/_server.log" 2>&1 &
|
|
114
207
|
elif [ "$RUNTIME" = "node" ]; then
|
|
115
208
|
nohup node "$SERVER_MJS" --root "$REPO" > "$DESIGN_ROOT/_server.log" 2>&1 &
|
|
@@ -41,6 +41,9 @@
|
|
|
41
41
|
# 2 bad args
|
|
42
42
|
# 3 one or more specimen screenshots failed
|
|
43
43
|
# 4 no specimens existed on disk (DS not scaffolded? typo?)
|
|
44
|
+
# 5 dev-server runtime deps missing (yjs/y-protocols/lib0) — recovery is
|
|
45
|
+
# `bun install`, NOT skip-or-retry; distinct from a generic boot failure
|
|
46
|
+
# so the caller routes to the right hint (server-up.sh exit 3). DDR-083.
|
|
44
47
|
|
|
45
48
|
DS=""
|
|
46
49
|
SPECIMENS=""
|
|
@@ -142,18 +145,31 @@ echo "→ booting dev-server (timeout ${BOOT_TIMEOUT}s)" >&2
|
|
|
142
145
|
PORT=$(bash "$SCRIPT_DIR/server-up.sh" --root "$ROOT_ABS" --timeout "$BOOT_TIMEOUT")
|
|
143
146
|
SERVER_RC=$?
|
|
144
147
|
if [ $SERVER_RC -ne 0 ] || [ -z "$PORT" ]; then
|
|
145
|
-
|
|
148
|
+
# server-up.sh exit 3 = the dev-server's runtime deps aren't installed. That's
|
|
149
|
+
# a distinct failure from a generic boot crash/timeout: the fix is `bun install`,
|
|
150
|
+
# not "skip or retry". Surface a dedicated status + exit code (5) so the caller
|
|
151
|
+
# routes to the right remediation instead of the boot-failed AskUserQuestion.
|
|
152
|
+
# The actionable hint already went to stderr from server-up.sh. DDR-083.
|
|
153
|
+
if [ $SERVER_RC -eq 3 ]; then
|
|
154
|
+
echo "✗ dev-server runtime deps missing — run \`bun install\` in the dev-server dir (see hint above)" >&2
|
|
155
|
+
VS_STATUS="server-deps-missing"
|
|
156
|
+
VS_EXIT=5
|
|
157
|
+
else
|
|
158
|
+
echo "✗ dev-server boot failed (server-up.sh exit $SERVER_RC); see $DESIGN_ROOT/_server.log" >&2
|
|
159
|
+
VS_STATUS="server-boot-failed"
|
|
160
|
+
VS_EXIT=1
|
|
161
|
+
fi
|
|
146
162
|
cat > "$OUT_DIR/_manifest.json" <<EOF
|
|
147
163
|
{
|
|
148
164
|
"ds": "$DS",
|
|
149
165
|
"ts": "$TS",
|
|
150
|
-
"status": "
|
|
166
|
+
"status": "$VS_STATUS",
|
|
151
167
|
"server_up_rc": $SERVER_RC,
|
|
152
168
|
"specimens_requested": $(printf '%s\n' "${SPEC_ARR[@]}" | sed 's/^[[:space:]]*//;s/[[:space:]]*$//' | sed '/^$/d' | sed 's/.*/"&"/' | paste -sd, -),
|
|
153
169
|
"specimens_captured": []
|
|
154
170
|
}
|
|
155
171
|
EOF
|
|
156
|
-
exit
|
|
172
|
+
exit $VS_EXIT
|
|
157
173
|
fi
|
|
158
174
|
echo "✓ dev-server on port $PORT" >&2
|
|
159
175
|
|
|
@@ -168,6 +168,6 @@ export const TOOL_CURSORS: Record<Tool, string> = Object.freeze({
|
|
|
168
168
|
*/
|
|
169
169
|
export function resolveToolCursor(token: unknown): string | null {
|
|
170
170
|
if (typeof token !== 'string' || !/^[a-z-]+$/.test(token)) return null;
|
|
171
|
-
if (!Object.
|
|
171
|
+
if (!Object.hasOwn(TOOL_CURSORS, token)) return null;
|
|
172
172
|
return TOOL_CURSORS[token as Tool];
|
|
173
173
|
}
|