@1agh/maude 0.17.1 → 0.18.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/cli/cli-wrapper.cjs +0 -0
- package/cli/commands/design.mjs +248 -43
- package/package.json +8 -8
- package/plugins/design/dev-server/bin/screenshot.sh +12 -0
- package/plugins/design/dev-server/boot-self-heal.ts +90 -0
- package/plugins/design/dev-server/build.ts +136 -8
- package/plugins/design/dev-server/canvas-pipeline.ts +5 -0
- package/plugins/design/dev-server/config.schema.json +12 -0
- package/plugins/design/dev-server/dist/client.bundle.js +3 -3
- package/plugins/design/dev-server/runtime-bundle.ts +29 -1
- package/plugins/design/dev-server/server.ts +6 -0
- package/plugins/design/dev-server/test/boot-self-heal.test.ts +112 -0
- package/plugins/design/dev-server/test/compile-entry.test.ts +134 -0
- package/plugins/design/dev-server/test/runtime-bundle-error-mapping.test.ts +43 -0
- package/plugins/design/templates/canvas.tsx.template +7 -7
- package/plugins/design/templates/design-system-inspiration/audience-pro/colors-presence.html +1 -1
- package/plugins/design/templates/design-system-inspiration/core/README.philosophy.md.tpl +11 -7
- package/plugins/design/templates/design-system-inspiration/core/SKILL.md.tpl +4 -3
- package/plugins/design/templates/design-system-inspiration/core/colors_and_type.css.tpl +61 -57
- package/plugins/design/templates/design-system-inspiration/core/config.json.tpl +2 -0
- package/plugins/design/templates/design-system-inspiration/core/preview/colors-accent.html +4 -4
- package/plugins/design/templates/design-system-inspiration/meta/presence-multiplayer.html +1 -1
- package/plugins/design/templates/design-system-inspiration/platform-desktop/ui_kits-desktop-showcase.html +3 -0
- package/plugins/design/templates/design-system-inspiration/platform-mobile/ui_kits-mobile-showcase.html +1 -1
- package/plugins/design/templates/design-system-inspiration/theme-both/colors-themes-side-by-side.html +6 -6
- package/plugins/design/templates/design-system-inspiration/universal/components-dialogs.html +3 -2
- package/plugins/design/templates/design-system-inspiration/universal/logo.html +2 -1
package/cli/cli-wrapper.cjs
CHANGED
|
File without changes
|
package/cli/commands/design.mjs
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
|
-
import { spawn } from 'node:child_process';
|
|
1
|
+
import { execSync, spawn } from 'node:child_process';
|
|
2
2
|
import { mkdir, readFile, readdir, stat, writeFile } from 'node:fs/promises';
|
|
3
|
-
import {
|
|
3
|
+
import { createRequire } from 'node:module';
|
|
4
|
+
import { basename, dirname, join, resolve } from 'node:path';
|
|
4
5
|
import { parseArgs } from '../lib/argv.mjs';
|
|
5
6
|
|
|
6
7
|
const SUBCOMMANDS = new Set(['serve', 'init', 'export', 'help']);
|
|
@@ -60,23 +61,42 @@ function usage() {
|
|
|
60
61
|
|
|
61
62
|
async function runServe({ args, pkgRoot }) {
|
|
62
63
|
const forwarded = args.slice(args.indexOf('serve') + 1);
|
|
64
|
+
const fs = await import('node:fs');
|
|
63
65
|
|
|
64
66
|
// Resolution order:
|
|
65
|
-
// 1. Side-channel from postinstall (cli/.platform-binary-path)
|
|
66
|
-
//
|
|
67
|
-
//
|
|
68
|
-
// 3.
|
|
69
|
-
//
|
|
67
|
+
// 1. Side-channel cache from postinstall (cli/.platform-binary-path).
|
|
68
|
+
// 2. Lazy resolve of @1agh/maude-<slug>/maude (postinstall was skipped
|
|
69
|
+
// — Bun global, --ignore-scripts, Docker layer without scripts, etc.).
|
|
70
|
+
// 3. (only in local dev tree) Bun + server.ts from source.
|
|
71
|
+
// 4. Hard-fail with actionable hint.
|
|
72
|
+
// Maintainers hacking on the dev-server source can force #3 with
|
|
73
|
+
// MAUDE_FORCE_SOURCE=1.
|
|
74
|
+
const forceSource = process.env.MAUDE_FORCE_SOURCE === '1';
|
|
70
75
|
const sideChannel = resolve(pkgRoot, 'cli', '.platform-binary-path');
|
|
71
76
|
let binPath = null;
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
+
|
|
78
|
+
if (!forceSource) {
|
|
79
|
+
try {
|
|
80
|
+
if (fs.existsSync(sideChannel)) {
|
|
81
|
+
const candidate = fs.readFileSync(sideChannel, 'utf8').trim();
|
|
82
|
+
if (candidate && fs.existsSync(candidate)) binPath = candidate;
|
|
83
|
+
}
|
|
84
|
+
} catch {
|
|
85
|
+
/* fall through to lazy resolve */
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
if (!binPath) {
|
|
89
|
+
const resolved = lazyResolveBinary({ pkgRoot, fs });
|
|
90
|
+
if (resolved.binPath) {
|
|
91
|
+
binPath = resolved.binPath;
|
|
92
|
+
// Cache for next invocation (best-effort — read-only fs is fine).
|
|
93
|
+
try {
|
|
94
|
+
fs.writeFileSync(sideChannel, binPath, 'utf8');
|
|
95
|
+
} catch {
|
|
96
|
+
/* read-only fs / no permission — non-fatal */
|
|
97
|
+
}
|
|
98
|
+
}
|
|
77
99
|
}
|
|
78
|
-
} catch {
|
|
79
|
-
/* fall through */
|
|
80
100
|
}
|
|
81
101
|
|
|
82
102
|
if (binPath) {
|
|
@@ -89,6 +109,30 @@ async function runServe({ args, pkgRoot }) {
|
|
|
89
109
|
return;
|
|
90
110
|
}
|
|
91
111
|
|
|
112
|
+
// No binary found. Two possibilities:
|
|
113
|
+
// - Production install missing platform package — hard-fail with guidance.
|
|
114
|
+
// - Local dev tree (claude-design checkout) — fall through to source.
|
|
115
|
+
const inDevTree = isLocalDevTree(pkgRoot, fs);
|
|
116
|
+
|
|
117
|
+
if (!inDevTree && !forceSource) {
|
|
118
|
+
const slug = detectPlatformSlug();
|
|
119
|
+
const siblingHint = slug ? resolve(pkgRoot, '..', `maude-${slug}`) : '(unknown platform)';
|
|
120
|
+
process.stderr.write(
|
|
121
|
+
`maude design serve: platform binary not found.\n\n Expected: @1agh/maude-${slug || '<platform>'}/maude\n Looked in: ${siblingHint}\n\nLikely causes:\n • Installer skipped postinstall (Bun global, npm --ignore-scripts,\n pnpm strict-scripts, Docker layer rebuilds).\n • Optional dependency for your platform did not install.\n • Global 'maude' is a leftover 'npm link' to a source checkout.\n\nFix (clean reinstall):\n cd ~ # NOT inside a maude source repo\n npm uninstall -g @1agh/maude # remove any stale link\n npm i -g @1agh/maude # real tarball install + postinstall\n\nOr re-run postinstall on the existing install:\n npm rebuild -g @1agh/maude\n\nPlatform: ${process.platform}-${process.arch}\n`
|
|
122
|
+
);
|
|
123
|
+
process.exit(1);
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
// Local dev tree (or forced source). Verify deps before invoking source —
|
|
127
|
+
// catches the npm/pnpm/oxc-parser native-binding bug (npm#4828) up front.
|
|
128
|
+
const missing = checkDevDeps({ pkgRoot });
|
|
129
|
+
if (missing.length) {
|
|
130
|
+
process.stderr.write(
|
|
131
|
+
`maude design serve: missing dev-server dependencies in local checkout.\n\n Missing: ${missing.join(', ')}\n Repo: ${pkgRoot}\n\nThis repo uses pnpm (see packageManager in package.json). Install deps:\n cd ${pkgRoot} && pnpm install\n\nIf you already ran 'npm install' here, the npm optional-deps bug\n(npm#4828) may have left native bindings broken. Reset first:\n rm -rf node_modules package-lock.json && pnpm install\n`
|
|
132
|
+
);
|
|
133
|
+
process.exit(1);
|
|
134
|
+
}
|
|
135
|
+
|
|
92
136
|
const tsEntry = resolve(pkgRoot, 'plugins', 'design', 'dev-server', 'server.ts');
|
|
93
137
|
const mjsEntry = resolve(pkgRoot, 'plugins', 'design', 'dev-server', 'server.mjs');
|
|
94
138
|
|
|
@@ -108,6 +152,104 @@ async function runServe({ args, pkgRoot }) {
|
|
|
108
152
|
});
|
|
109
153
|
}
|
|
110
154
|
|
|
155
|
+
// Mirrors cli/install.cjs:detectSlug — kept in sync intentionally. Both forms
|
|
156
|
+
// run during the lifecycle of a single install (postinstall + first serve).
|
|
157
|
+
function detectPlatformSlug() {
|
|
158
|
+
const p = process.platform;
|
|
159
|
+
const a = process.arch;
|
|
160
|
+
if (p === 'darwin') {
|
|
161
|
+
if (a === 'arm64') return 'darwin-arm64';
|
|
162
|
+
try {
|
|
163
|
+
const t = execSync('sysctl -n sysctl.proc_translated', {
|
|
164
|
+
stdio: ['ignore', 'pipe', 'ignore'],
|
|
165
|
+
})
|
|
166
|
+
.toString()
|
|
167
|
+
.trim();
|
|
168
|
+
if (t === '1') return 'darwin-arm64';
|
|
169
|
+
} catch {
|
|
170
|
+
/* not under Rosetta */
|
|
171
|
+
}
|
|
172
|
+
return 'darwin-x64';
|
|
173
|
+
}
|
|
174
|
+
if (p === 'linux') {
|
|
175
|
+
let isMusl = false;
|
|
176
|
+
try {
|
|
177
|
+
const report = process.report?.getReport?.();
|
|
178
|
+
isMusl = !report?.header?.glibcVersionRuntime;
|
|
179
|
+
} catch {
|
|
180
|
+
/* default to glibc */
|
|
181
|
+
}
|
|
182
|
+
if (a === 'arm64') return isMusl ? 'linux-arm64-musl' : 'linux-arm64';
|
|
183
|
+
return isMusl ? 'linux-x64-musl' : 'linux-x64';
|
|
184
|
+
}
|
|
185
|
+
if (p === 'win32') return 'win32-x64';
|
|
186
|
+
return null;
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
function lazyResolveBinary({ pkgRoot, fs }) {
|
|
190
|
+
const slug = detectPlatformSlug();
|
|
191
|
+
if (!slug) return { binPath: null, slug: null };
|
|
192
|
+
const filename = process.platform === 'win32' ? 'maude.exe' : 'maude';
|
|
193
|
+
|
|
194
|
+
// Flat layout — production npm/bun global install. pkgRoot is
|
|
195
|
+
// <node_modules>/@1agh/maude, sibling is <node_modules>/@1agh/maude-<slug>.
|
|
196
|
+
const sibling = resolve(pkgRoot, '..', `maude-${slug}`, filename);
|
|
197
|
+
if (fs.existsSync(sibling)) {
|
|
198
|
+
try {
|
|
199
|
+
fs.chmodSync(sibling, 0o755);
|
|
200
|
+
} catch {
|
|
201
|
+
/* read-only — ignore */
|
|
202
|
+
}
|
|
203
|
+
return { binPath: sibling, slug };
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
// Nested layout — pnpm with isolation, or dev tree's own node_modules.
|
|
207
|
+
try {
|
|
208
|
+
const require = createRequire(import.meta.url);
|
|
209
|
+
const manifest = require.resolve(`@1agh/maude-${slug}/package.json`, {
|
|
210
|
+
paths: [pkgRoot, resolve(pkgRoot, 'cli')],
|
|
211
|
+
});
|
|
212
|
+
const fromManifest = resolve(dirname(manifest), filename);
|
|
213
|
+
if (fs.existsSync(fromManifest)) {
|
|
214
|
+
try {
|
|
215
|
+
fs.chmodSync(fromManifest, 0o755);
|
|
216
|
+
} catch {
|
|
217
|
+
/* ignore */
|
|
218
|
+
}
|
|
219
|
+
return { binPath: fromManifest, slug };
|
|
220
|
+
}
|
|
221
|
+
} catch {
|
|
222
|
+
/* platform package not installed */
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
return { binPath: null, slug };
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
// Mirrors cli/install.cjs:isLocalDev — the `packages/` directory is only present
|
|
229
|
+
// in the source checkout, never in a published npm tarball (excluded from
|
|
230
|
+
// package.json:files).
|
|
231
|
+
function isLocalDevTree(pkgRoot, fs) {
|
|
232
|
+
return fs.existsSync(resolve(pkgRoot, 'packages', 'maude-darwin-arm64', 'package.json'));
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
function checkDevDeps({ pkgRoot }) {
|
|
236
|
+
const require = createRequire(import.meta.url);
|
|
237
|
+
// The dev-server source path imports these as bare specifiers. If they
|
|
238
|
+
// resolve, bun/node will load them; if they don't, the spawn will fail
|
|
239
|
+
// with a less actionable error.
|
|
240
|
+
const required = ['magic-string', 'oxc-parser'];
|
|
241
|
+
const missing = [];
|
|
242
|
+
const paths = [resolve(pkgRoot, 'plugins', 'design', 'dev-server'), resolve(pkgRoot)];
|
|
243
|
+
for (const dep of required) {
|
|
244
|
+
try {
|
|
245
|
+
require.resolve(dep, { paths });
|
|
246
|
+
} catch {
|
|
247
|
+
missing.push(dep);
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
return missing;
|
|
251
|
+
}
|
|
252
|
+
|
|
111
253
|
async function runExport({ args }) {
|
|
112
254
|
// `maude design export <format> [--scope ...] [--port N] [--out <path>] [--option key=value]`
|
|
113
255
|
const subArgs = args.slice(args.indexOf('export') + 1);
|
|
@@ -366,6 +508,13 @@ function escapeReg(s) {
|
|
|
366
508
|
}
|
|
367
509
|
|
|
368
510
|
function defaultPayload({ projectName, dsName }) {
|
|
511
|
+
// `--no-discovery` mode emits a deliberately NEUTRAL skeleton:
|
|
512
|
+
// achromatic grayscale, system fonts only, equal spacing/radii steps,
|
|
513
|
+
// single accent set to neutral graphite. The intent is for the project
|
|
514
|
+
// to immediately overwrite these with discovery-driven values via
|
|
515
|
+
// `/design:setup-ds`. These are NOT a "recommended starter palette" —
|
|
516
|
+
// they're an explicit placeholder that should look unfinished, so the
|
|
517
|
+
// designer is nudged toward actual decisions (DDR-026, 2026-05-25).
|
|
369
518
|
return {
|
|
370
519
|
project_name: projectName,
|
|
371
520
|
project_label: titleCase(projectName),
|
|
@@ -373,7 +522,7 @@ function defaultPayload({ projectName, dsName }) {
|
|
|
373
522
|
ds_skill_name: `${dsName}-design`,
|
|
374
523
|
ds_description: `Default design system for ${projectName}.`,
|
|
375
524
|
root_class: 'app',
|
|
376
|
-
theme_default: '
|
|
525
|
+
theme_default: 'light',
|
|
377
526
|
theme_extra: '',
|
|
378
527
|
handoff_targets: '[]',
|
|
379
528
|
active_families: JSON.stringify(['accent']),
|
|
@@ -386,38 +535,94 @@ function defaultPayload({ projectName, dsName }) {
|
|
|
386
535
|
platform_hard_rules: '',
|
|
387
536
|
content_tone: 'direct-terse',
|
|
388
537
|
mood_references_block: '_(not specified — extend during the next /design:setup-ds run)_',
|
|
389
|
-
type_scale_summary: '8-step ladder,
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
538
|
+
type_scale_summary: '8-step ladder, populated by discovery.',
|
|
539
|
+
type_scale_summary_compact: '8-step (modular ratio)',
|
|
540
|
+
voice_tone_block: 'Voice not declared yet — capture during /design:setup-ds.',
|
|
541
|
+
voice_tone_summary: 'not-declared',
|
|
542
|
+
iconography_summary: 'Icon family not declared yet — capture during /design:setup-ds.',
|
|
393
543
|
hard_rules_from_system_readme: '_(see system/<ds>/README.md Hard rules section)_',
|
|
394
544
|
hard_rules_block:
|
|
395
|
-
'- WCAG 2.1 AA contrast\n- No off-token
|
|
545
|
+
'- WCAG 2.1 AA contrast\n- No off-token values in canvases\n- Real product strings only — no placeholder copy',
|
|
396
546
|
iso_timestamp: new Date().toISOString(),
|
|
397
|
-
//
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
547
|
+
// ─── Neutral-skeleton tokens (light theme grayscale, no chroma) ───
|
|
548
|
+
bg_0: 'oklch(100% 0 0)',
|
|
549
|
+
bg_1: 'oklch(97% 0 0)',
|
|
550
|
+
bg_2: 'oklch(94% 0 0)',
|
|
551
|
+
bg_3: 'oklch(90% 0 0)',
|
|
552
|
+
bg_4: 'oklch(85% 0 0)',
|
|
553
|
+
border_subtle: 'oklch(92% 0 0)',
|
|
554
|
+
border_default: 'oklch(85% 0 0)',
|
|
555
|
+
border_strong: 'oklch(70% 0 0)',
|
|
556
|
+
border_strategy: 'fixed-token',
|
|
557
|
+
fg_0: 'oklch(15% 0 0)',
|
|
558
|
+
fg_1: 'oklch(35% 0 0)',
|
|
559
|
+
fg_2: 'oklch(55% 0 0)',
|
|
560
|
+
fg_3: 'oklch(75% 0 0)',
|
|
561
|
+
accent_strategy: 'single',
|
|
562
|
+
accent_strategy_summary: 'single accent — discovery has not been run',
|
|
563
|
+
accent_block:
|
|
564
|
+
' --accent: oklch(35% 0 0);\n --accent-hover: oklch(30% 0 0);\n --accent-active: oklch(25% 0 0);\n --accent-fg: oklch(98% 0 0);',
|
|
565
|
+
accent_rules_block:
|
|
566
|
+
'Single accent family (`--accent`, `--accent-hover`, `--accent-active`, `--accent-fg`). Project has not run discovery yet — the accent is set to neutral graphite as a placeholder. Run `/design:setup-ds` to pick a real palette.',
|
|
567
|
+
accent_rules_summary: 'Single family until discovery runs.',
|
|
568
|
+
color_space: 'oklch',
|
|
569
|
+
color_space_summary: 'OKLCH (default)',
|
|
570
|
+
color_space_block:
|
|
571
|
+
'**Color space:** OKLCH (default). Override via `config.colorSpace` if your project requires HSL, hex, or LAB.',
|
|
572
|
+
status_success: 'oklch(55% 0 0)',
|
|
573
|
+
status_warn: 'oklch(55% 0 0)',
|
|
574
|
+
status_error: 'oklch(55% 0 0)',
|
|
575
|
+
status_info: 'oklch(55% 0 0)',
|
|
576
|
+
presence_online: 'var(--status-success)',
|
|
577
|
+
presence_away: 'var(--status-warn)',
|
|
578
|
+
presence_offline: 'var(--fg-3)',
|
|
579
|
+
shadow_sm: 'none',
|
|
580
|
+
shadow_md: 'none',
|
|
581
|
+
shadow_lg: 'none',
|
|
582
|
+
radius_xs: '0',
|
|
583
|
+
radius_sm: '0',
|
|
584
|
+
radius_md: '0',
|
|
585
|
+
radius_lg: '0',
|
|
586
|
+
radius_xl: '0',
|
|
587
|
+
radius_pill: '999px',
|
|
588
|
+
space_0: '0',
|
|
589
|
+
space_1: '4px',
|
|
590
|
+
space_2: '8px',
|
|
591
|
+
space_3: '12px',
|
|
592
|
+
space_4: '16px',
|
|
593
|
+
space_5: '24px',
|
|
594
|
+
space_6: '32px',
|
|
595
|
+
space_7: '48px',
|
|
596
|
+
space_8: '64px',
|
|
597
|
+
font_display: 'system-ui, sans-serif',
|
|
598
|
+
font_body: 'system-ui, sans-serif',
|
|
599
|
+
font_mono: 'ui-monospace, monospace',
|
|
600
|
+
type_xs: '12px',
|
|
601
|
+
lh_xs: '16px',
|
|
602
|
+
type_sm: '13px',
|
|
603
|
+
lh_sm: '18px',
|
|
604
|
+
type_base: '14px',
|
|
605
|
+
lh_base: '20px',
|
|
606
|
+
type_md: '16px',
|
|
607
|
+
lh_md: '22px',
|
|
608
|
+
type_lg: '18px',
|
|
609
|
+
lh_lg: '26px',
|
|
610
|
+
type_xl: '22px',
|
|
611
|
+
lh_xl: '30px',
|
|
612
|
+
type_2xl: '28px',
|
|
613
|
+
lh_2xl: '36px',
|
|
614
|
+
type_3xl: '36px',
|
|
615
|
+
lh_3xl: '44px',
|
|
616
|
+
dur_flip: '120ms',
|
|
617
|
+
dur_panel: '200ms',
|
|
618
|
+
dur_route: '240ms',
|
|
420
619
|
dur_soft: '320ms',
|
|
620
|
+
ease_out_curve: 'ease-out',
|
|
621
|
+
ease_in_out_curve: 'ease-in-out',
|
|
622
|
+
layout_max_w: '1200px',
|
|
623
|
+
layout_gutter: 'var(--space-4)',
|
|
624
|
+
touch_target_rule:
|
|
625
|
+
'44×44 minimum on touch surfaces (iOS HIG); 48×48 if the primary platform is Android (Material). N/A on desktop-only systems.',
|
|
421
626
|
// Empty-state template fields
|
|
422
627
|
empty_subject: 'items',
|
|
423
628
|
empty_supporting: "When you create your first one, it'll show up here.",
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@1agh/maude",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.18.0",
|
|
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"
|
|
42
42
|
},
|
|
43
43
|
"optionalDependencies": {
|
|
44
|
-
"@1agh/maude-darwin-arm64": "0.
|
|
45
|
-
"@1agh/maude-darwin-x64": "0.
|
|
46
|
-
"@1agh/maude-linux-arm64": "0.
|
|
47
|
-
"@1agh/maude-linux-arm64-musl": "0.
|
|
48
|
-
"@1agh/maude-linux-x64": "0.
|
|
49
|
-
"@1agh/maude-linux-x64-musl": "0.
|
|
50
|
-
"@1agh/maude-win32-x64": "0.
|
|
44
|
+
"@1agh/maude-darwin-arm64": "0.18.0",
|
|
45
|
+
"@1agh/maude-darwin-x64": "0.18.0",
|
|
46
|
+
"@1agh/maude-linux-arm64": "0.18.0",
|
|
47
|
+
"@1agh/maude-linux-arm64-musl": "0.18.0",
|
|
48
|
+
"@1agh/maude-linux-x64": "0.18.0",
|
|
49
|
+
"@1agh/maude-linux-x64-musl": "0.18.0",
|
|
50
|
+
"@1agh/maude-win32-x64": "0.18.0"
|
|
51
51
|
},
|
|
52
52
|
"files": [
|
|
53
53
|
"cli",
|
|
@@ -64,6 +64,18 @@ else
|
|
|
64
64
|
[ -z "$OUT" ] && { echo "screenshot.sh: --out required for single-shot modes" >&2; exit 2; }
|
|
65
65
|
fi
|
|
66
66
|
|
|
67
|
+
# TSX specimens cannot be opened via file:// — the browser would see raw JSX.
|
|
68
|
+
# They must go through the dev-server route (http://localhost:PORT/<rel>),
|
|
69
|
+
# which transpiles via _canvas-shell.html?canvas=<rel>. Phase 19 / DDR-044.
|
|
70
|
+
case "$URL" in
|
|
71
|
+
file://*.tsx)
|
|
72
|
+
echo "screenshot.sh: TSX specimens cannot be screenshot via file:// (the browser sees raw JSX)." >&2
|
|
73
|
+
echo " Use --port instead — the dev-server compiles TSX through _canvas-shell.html?canvas=<rel>." >&2
|
|
74
|
+
echo " Example: screenshot.sh --port 4399 --full --out shot.png (set the active canvas in the browser first)" >&2
|
|
75
|
+
exit 2
|
|
76
|
+
;;
|
|
77
|
+
esac
|
|
78
|
+
|
|
67
79
|
# ---------- url resolution ----------
|
|
68
80
|
if [ -z "$URL" ]; then
|
|
69
81
|
REPO="${ROOT:-${CLAUDE_PROJECT_DIR:-$(git rev-parse --show-toplevel 2>/dev/null || pwd)}}"
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
// Boot-time self-heal — covers the gap between marketplace-cache installs
|
|
2
|
+
// (git clone, honors .gitignore, no `npm install`) and what server.ts needs
|
|
3
|
+
// at runtime. Two artifacts can be missing on a fresh clone:
|
|
4
|
+
//
|
|
5
|
+
// - dist/client.bundle.js → 404 on /_client/*
|
|
6
|
+
// - node_modules/react → 500 on /_canvas-runtime/*
|
|
7
|
+
//
|
|
8
|
+
// Per DDR-044 we commit the bundle + styles, so dist/ should be present.
|
|
9
|
+
// For node_modules/ we self-heal: detect missing react, run `bun install
|
|
10
|
+
// --production`. Opt out with MAUDE_NO_AUTOBUILD=1 for read-only-filesystem
|
|
11
|
+
// deployments (e.g. immutable infra).
|
|
12
|
+
//
|
|
13
|
+
// Phase 19. DDR-044.
|
|
14
|
+
|
|
15
|
+
import { existsSync } from 'node:fs';
|
|
16
|
+
import { dirname, join } from 'node:path';
|
|
17
|
+
import { fileURLToPath } from 'node:url';
|
|
18
|
+
|
|
19
|
+
export interface SelfHealOptions {
|
|
20
|
+
/** Plugin install directory (the dev-server root). Defaults to this file's dir. */
|
|
21
|
+
here?: string;
|
|
22
|
+
/** Defaults to process.env.MAUDE_NO_AUTOBUILD === '1'. */
|
|
23
|
+
optOut?: boolean;
|
|
24
|
+
/** Defaults to Bun.spawn; tests override. */
|
|
25
|
+
spawn?: (cmd: readonly string[], cwd: string) => Promise<{ code: number }>;
|
|
26
|
+
/** Defaults to console.error; tests override to capture. */
|
|
27
|
+
log?: (msg: string) => void;
|
|
28
|
+
/** Defaults to process.exit; tests override to assert. */
|
|
29
|
+
exit?: (code: number) => never;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export interface SelfHealResult {
|
|
33
|
+
ran: ('install' | 'build')[];
|
|
34
|
+
skipped: 'all-present' | null;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export async function bootSelfHeal(opts: SelfHealOptions = {}): Promise<SelfHealResult> {
|
|
38
|
+
const here = opts.here ?? dirname(fileURLToPath(import.meta.url));
|
|
39
|
+
const optOut = opts.optOut ?? process.env.MAUDE_NO_AUTOBUILD === '1';
|
|
40
|
+
const log = opts.log ?? ((m) => console.error(m));
|
|
41
|
+
const exit =
|
|
42
|
+
opts.exit ??
|
|
43
|
+
((code: number) => {
|
|
44
|
+
process.exit(code);
|
|
45
|
+
});
|
|
46
|
+
const spawn = opts.spawn ?? defaultSpawn;
|
|
47
|
+
|
|
48
|
+
const distMissing = !existsSync(join(here, 'dist', 'client.bundle.js'));
|
|
49
|
+
const depsMissing = !existsSync(join(here, 'node_modules', 'react', 'package.json'));
|
|
50
|
+
|
|
51
|
+
if (!distMissing && !depsMissing) return { ran: [], skipped: 'all-present' };
|
|
52
|
+
|
|
53
|
+
if (optOut) {
|
|
54
|
+
const missing = [
|
|
55
|
+
distMissing ? 'dist/client.bundle.js (run `bun run build.ts`)' : null,
|
|
56
|
+
depsMissing ? 'node_modules/react (run `bun install --production`)' : null,
|
|
57
|
+
]
|
|
58
|
+
.filter(Boolean)
|
|
59
|
+
.join('\n - ');
|
|
60
|
+
log(`\n ⚠ first-boot artifacts missing and MAUDE_NO_AUTOBUILD=1 is set:\n - ${missing}\n`);
|
|
61
|
+
exit(1);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
const ran: ('install' | 'build')[] = [];
|
|
65
|
+
if (depsMissing) {
|
|
66
|
+
log(' ⚠ first-boot: installing runtime deps (one-time, ~15s)…');
|
|
67
|
+
const { code } = await spawn(['bun', 'install', '--production'], here);
|
|
68
|
+
if (code !== 0) {
|
|
69
|
+
log(` ⚠ \`bun install --production\` exited ${code}. Set MAUDE_NO_AUTOBUILD=1 and run manually.`);
|
|
70
|
+
exit(1);
|
|
71
|
+
}
|
|
72
|
+
ran.push('install');
|
|
73
|
+
}
|
|
74
|
+
if (distMissing) {
|
|
75
|
+
log(' ⚠ first-boot: building client assets (one-time, ~2s)…');
|
|
76
|
+
const { code } = await spawn(['bun', 'run', 'build.ts'], here);
|
|
77
|
+
if (code !== 0) {
|
|
78
|
+
log(` ⚠ \`bun run build.ts\` exited ${code}. Set MAUDE_NO_AUTOBUILD=1 and run manually.`);
|
|
79
|
+
exit(1);
|
|
80
|
+
}
|
|
81
|
+
ran.push('build');
|
|
82
|
+
}
|
|
83
|
+
return { ran, skipped: null };
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
async function defaultSpawn(cmd: readonly string[], cwd: string): Promise<{ code: number }> {
|
|
87
|
+
const proc = Bun.spawn([...cmd], { cwd, stdout: 'inherit', stderr: 'inherit' });
|
|
88
|
+
const code = await proc.exited;
|
|
89
|
+
return { code };
|
|
90
|
+
}
|