@1agh/maude 0.15.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/LICENSE +21 -0
- package/README.md +166 -0
- package/cli/bin/maude.exe +15 -0
- package/cli/bin/maude.mjs +45 -0
- package/cli/bin/mdcc.exe +10 -0
- package/cli/bin/mdcc.mjs +7 -0
- package/cli/cli-wrapper.cjs +67 -0
- package/cli/commands/config.mjs +94 -0
- package/cli/commands/design.mjs +386 -0
- package/cli/commands/help.mjs +57 -0
- package/cli/commands/init.mjs +178 -0
- package/cli/commands/version.mjs +7 -0
- package/cli/install.cjs +113 -0
- package/cli/lib/argv.mjs +37 -0
- package/cli/lib/argv.test.mjs +46 -0
- package/cli/lib/copy-tree.mjs +78 -0
- package/package.json +94 -0
- package/plugins/design/dev-server/annotations-context-toolbar.tsx +397 -0
- package/plugins/design/dev-server/annotations-layer.tsx +1717 -0
- package/plugins/design/dev-server/api.ts +674 -0
- package/plugins/design/dev-server/bin/_screenshot-playwright.mjs +50 -0
- package/plugins/design/dev-server/bin/bootstrap-check.sh +83 -0
- package/plugins/design/dev-server/bin/canvas-edit.sh +48 -0
- package/plugins/design/dev-server/bin/handoff.sh +27 -0
- package/plugins/design/dev-server/bin/screenshot.sh +232 -0
- package/plugins/design/dev-server/bin/server-up.sh +135 -0
- package/plugins/design/dev-server/bin/slug.sh +22 -0
- package/plugins/design/dev-server/bin/smoke.sh +272 -0
- package/plugins/design/dev-server/build.ts +267 -0
- package/plugins/design/dev-server/canvas-build.ts +219 -0
- package/plugins/design/dev-server/canvas-edit.ts +388 -0
- package/plugins/design/dev-server/canvas-header.ts +165 -0
- package/plugins/design/dev-server/canvas-icons.tsx +131 -0
- package/plugins/design/dev-server/canvas-lib-inline.ts +260 -0
- package/plugins/design/dev-server/canvas-lib-resolver.ts +85 -0
- package/plugins/design/dev-server/canvas-lib.tsx +1995 -0
- package/plugins/design/dev-server/canvas-meta.schema.json +181 -0
- package/plugins/design/dev-server/canvas-pipeline.ts +270 -0
- package/plugins/design/dev-server/canvas-shell.tsx +813 -0
- package/plugins/design/dev-server/client/app.jsx +2027 -0
- package/plugins/design/dev-server/client/hmr.mjs +85 -0
- package/plugins/design/dev-server/client/iframe-lazy.mjs +121 -0
- package/plugins/design/dev-server/client/index.html +15 -0
- package/plugins/design/dev-server/client/styles/0-reset.css +18 -0
- package/plugins/design/dev-server/client/styles/1-tokens.css +297 -0
- package/plugins/design/dev-server/client/styles/2-layout.css +35 -0
- package/plugins/design/dev-server/client/styles/3-shell.css +906 -0
- package/plugins/design/dev-server/client/styles/4-components.css +1268 -0
- package/plugins/design/dev-server/client/styles/5-utilities.css +4 -0
- package/plugins/design/dev-server/client/styles/_index.css +24 -0
- package/plugins/design/dev-server/client/styles.css +1419 -0
- package/plugins/design/dev-server/config.schema.json +147 -0
- package/plugins/design/dev-server/context-menu.tsx +343 -0
- package/plugins/design/dev-server/context.ts +173 -0
- package/plugins/design/dev-server/dist/client.bundle.js +20323 -0
- package/plugins/design/dev-server/dist/styles.css +2875 -0
- package/plugins/design/dev-server/examples/README.md +9 -0
- package/plugins/design/dev-server/examples/perf-100-artboards.tsx +113 -0
- package/plugins/design/dev-server/fs-watch.ts +63 -0
- package/plugins/design/dev-server/handoff.ts +721 -0
- package/plugins/design/dev-server/history.ts +125 -0
- package/plugins/design/dev-server/hmr-broadcast.ts +114 -0
- package/plugins/design/dev-server/http.ts +413 -0
- package/plugins/design/dev-server/input-router.tsx +485 -0
- package/plugins/design/dev-server/inspect.ts +365 -0
- package/plugins/design/dev-server/locator.ts +159 -0
- package/plugins/design/dev-server/mem.ts +97 -0
- package/plugins/design/dev-server/runtime-bundle.ts +235 -0
- package/plugins/design/dev-server/server.mjs +1246 -0
- package/plugins/design/dev-server/server.ts +131 -0
- package/plugins/design/dev-server/test/_helpers.ts +81 -0
- package/plugins/design/dev-server/test/active-state.test.ts +145 -0
- package/plugins/design/dev-server/test/annotations-api.test.ts +146 -0
- package/plugins/design/dev-server/test/annotations-layer.test.ts +419 -0
- package/plugins/design/dev-server/test/binary-smoke.test.ts +47 -0
- package/plugins/design/dev-server/test/bundle-smoke.test.ts +29 -0
- package/plugins/design/dev-server/test/canvas-build.test.ts +78 -0
- package/plugins/design/dev-server/test/canvas-edit.test.ts +139 -0
- package/plugins/design/dev-server/test/canvas-header.test.ts +127 -0
- package/plugins/design/dev-server/test/canvas-lib-inline.test.ts +146 -0
- package/plugins/design/dev-server/test/canvas-lib-resolver.test.ts +112 -0
- package/plugins/design/dev-server/test/canvas-meta-api.test.ts +236 -0
- package/plugins/design/dev-server/test/canvas-pipeline.test.ts +180 -0
- package/plugins/design/dev-server/test/canvas-route.test.ts +176 -0
- package/plugins/design/dev-server/test/fs-watch.test.ts +41 -0
- package/plugins/design/dev-server/test/handoff-static-frames.test.ts +215 -0
- package/plugins/design/dev-server/test/handoff.test.ts +281 -0
- package/plugins/design/dev-server/test/history-rollback.test.ts +62 -0
- package/plugins/design/dev-server/test/hmr-broadcast.test.ts +108 -0
- package/plugins/design/dev-server/test/input-router.test.ts +316 -0
- package/plugins/design/dev-server/test/locator.test.ts +214 -0
- package/plugins/design/dev-server/test/perf-harness.ts +193 -0
- package/plugins/design/dev-server/test/phase-3.6-smoke.test.ts +77 -0
- package/plugins/design/dev-server/test/runtime-bundle.test.ts +69 -0
- package/plugins/design/dev-server/test/server-lifecycle.test.ts +28 -0
- package/plugins/design/dev-server/test/tool-palette.test.tsx +55 -0
- package/plugins/design/dev-server/test/use-annotation-selection.test.tsx +77 -0
- package/plugins/design/dev-server/test/use-artboard-drag.test.ts +325 -0
- package/plugins/design/dev-server/test/use-selection-set.test.tsx +166 -0
- package/plugins/design/dev-server/test/use-snap-guides.test.ts +190 -0
- package/plugins/design/dev-server/test/use-tool-mode.test.tsx +93 -0
- package/plugins/design/dev-server/test/ws-handshake.test.ts +33 -0
- package/plugins/design/dev-server/tool-palette.tsx +278 -0
- package/plugins/design/dev-server/tsconfig.json +26 -0
- package/plugins/design/dev-server/use-annotation-selection.tsx +92 -0
- package/plugins/design/dev-server/use-annotations-visibility.tsx +43 -0
- package/plugins/design/dev-server/use-artboard-drag.tsx +445 -0
- package/plugins/design/dev-server/use-selection-set.tsx +224 -0
- package/plugins/design/dev-server/use-snap-guides.tsx +215 -0
- package/plugins/design/dev-server/use-tool-mode.tsx +114 -0
- package/plugins/design/dev-server/ws.ts +90 -0
- package/plugins/design/templates/_shell.html +177 -0
- package/plugins/design/templates/canvas.tsx.template +54 -0
- package/plugins/design/templates/design-system-inspiration/_MAPPING.md +277 -0
- package/plugins/design/templates/design-system-inspiration/_README.md +71 -0
- package/plugins/design/templates/design-system-inspiration/audience-consumer/components-banner.html +68 -0
- package/plugins/design/templates/design-system-inspiration/audience-consumer/components-empty-state-generous.html +39 -0
- package/plugins/design/templates/design-system-inspiration/audience-consumer/components-feature-grid.html +62 -0
- package/plugins/design/templates/design-system-inspiration/audience-consumer/components-marketing-card.html +63 -0
- package/plugins/design/templates/design-system-inspiration/audience-consumer/components-testimonial.html +80 -0
- package/plugins/design/templates/design-system-inspiration/audience-developer/components-code-block.html +71 -0
- package/plugins/design/templates/design-system-inspiration/audience-developer/components-diff-view.html +65 -0
- package/plugins/design/templates/design-system-inspiration/audience-developer/components-log-stream.html +62 -0
- package/plugins/design/templates/design-system-inspiration/audience-developer/components-monospace-table.html +57 -0
- package/plugins/design/templates/design-system-inspiration/audience-developer/components-terminal-pane.html +67 -0
- package/plugins/design/templates/design-system-inspiration/audience-developer/type-mono.html +57 -0
- package/plugins/design/templates/design-system-inspiration/audience-pro/colors-presence.html +74 -0
- package/plugins/design/templates/design-system-inspiration/audience-pro/components-command-palette.html +90 -0
- package/plugins/design/templates/design-system-inspiration/audience-pro/components-keyboard.html +51 -0
- package/plugins/design/templates/design-system-inspiration/audience-pro/components-list.html +89 -0
- package/plugins/design/templates/design-system-inspiration/audience-pro/components-shortcuts-overlay.html +85 -0
- package/plugins/design/templates/design-system-inspiration/audience-pro/components-toast-menu.html +80 -0
- package/plugins/design/templates/design-system-inspiration/core/INDEX.md.tpl +25 -0
- package/plugins/design/templates/design-system-inspiration/core/README.orchestration.md.tpl +71 -0
- package/plugins/design/templates/design-system-inspiration/core/README.philosophy.md.tpl +77 -0
- package/plugins/design/templates/design-system-inspiration/core/SKILL.md.tpl +50 -0
- package/plugins/design/templates/design-system-inspiration/core/colors_and_type.css.tpl +111 -0
- package/plugins/design/templates/design-system-inspiration/core/config.json.tpl +28 -0
- package/plugins/design/templates/design-system-inspiration/core/preview/_layout.css +113 -0
- package/plugins/design/templates/design-system-inspiration/core/preview/colors-accent.html +48 -0
- package/plugins/design/templates/design-system-inspiration/core/preview/colors-surfaces.html +49 -0
- package/plugins/design/templates/design-system-inspiration/core/preview/colors-text.html +52 -0
- package/plugins/design/templates/design-system-inspiration/core/preview/components-buttons.html +83 -0
- package/plugins/design/templates/design-system-inspiration/core/preview/components-cards.html +79 -0
- package/plugins/design/templates/design-system-inspiration/core/preview/components-inputs.html +82 -0
- package/plugins/design/templates/design-system-inspiration/core/preview/motion.html +66 -0
- package/plugins/design/templates/design-system-inspiration/core/preview/spacing-scale.html +53 -0
- package/plugins/design/templates/design-system-inspiration/core/preview/type-scale.html +62 -0
- package/plugins/design/templates/design-system-inspiration/foundations/borders.html +39 -0
- package/plugins/design/templates/design-system-inspiration/foundations/elevation.html +46 -0
- package/plugins/design/templates/design-system-inspiration/foundations/focus.html +48 -0
- package/plugins/design/templates/design-system-inspiration/foundations/grid.html +51 -0
- package/plugins/design/templates/design-system-inspiration/foundations/iconography.html +73 -0
- package/plugins/design/templates/design-system-inspiration/foundations/opacity.html +45 -0
- package/plugins/design/templates/design-system-inspiration/foundations/radii.html +40 -0
- package/plugins/design/templates/design-system-inspiration/foundations/selection.html +45 -0
- package/plugins/design/templates/design-system-inspiration/meta/accessibility.html +62 -0
- package/plugins/design/templates/design-system-inspiration/meta/i18n.html +73 -0
- package/plugins/design/templates/design-system-inspiration/meta/presence-multiplayer.html +71 -0
- package/plugins/design/templates/design-system-inspiration/meta/tokens-index.html +80 -0
- package/plugins/design/templates/design-system-inspiration/patterns/patterns-auth.html +78 -0
- package/plugins/design/templates/design-system-inspiration/patterns/patterns-data-density.html +61 -0
- package/plugins/design/templates/design-system-inspiration/patterns/patterns-error-pages.html +70 -0
- package/plugins/design/templates/design-system-inspiration/patterns/patterns-form-layouts.html +70 -0
- package/plugins/design/templates/design-system-inspiration/patterns/patterns-onboarding.html +71 -0
- package/plugins/design/templates/design-system-inspiration/patterns/patterns-pricing.html +83 -0
- package/plugins/design/templates/design-system-inspiration/platform-desktop/components-resize-panels.html +63 -0
- package/plugins/design/templates/design-system-inspiration/platform-desktop/ui_kits-desktop-index.html +55 -0
- package/plugins/design/templates/design-system-inspiration/platform-desktop/ui_kits-desktop-showcase.html +302 -0
- package/plugins/design/templates/design-system-inspiration/platform-mobile/components-bottom-sheet.html +63 -0
- package/plugins/design/templates/design-system-inspiration/platform-mobile/components-pull-to-refresh.html +74 -0
- package/plugins/design/templates/design-system-inspiration/platform-mobile/components-segmented-control.html +51 -0
- package/plugins/design/templates/design-system-inspiration/platform-mobile/components-tab-bar.html +57 -0
- package/plugins/design/templates/design-system-inspiration/platform-mobile/ui_kits-mobile-index.html +58 -0
- package/plugins/design/templates/design-system-inspiration/platform-mobile/ui_kits-mobile-showcase.html +237 -0
- package/plugins/design/templates/design-system-inspiration/status/colors-status.html +49 -0
- package/plugins/design/templates/design-system-inspiration/status/components-status.html +63 -0
- package/plugins/design/templates/design-system-inspiration/status/skeletons.html +74 -0
- package/plugins/design/templates/design-system-inspiration/theme-both/colors-themes-side-by-side.html +59 -0
- package/plugins/design/templates/design-system-inspiration/universal/components-callout.html +74 -0
- package/plugins/design/templates/design-system-inspiration/universal/components-dialogs.html +81 -0
- package/plugins/design/templates/design-system-inspiration/universal/components-tables.html +101 -0
- package/plugins/design/templates/design-system-inspiration/universal/components-toggles.html +74 -0
- package/plugins/design/templates/design-system-inspiration/universal/components-tooltips.html +74 -0
- package/plugins/design/templates/design-system-inspiration/universal/empty-state.html.tpl +34 -0
- package/plugins/design/templates/design-system-inspiration/universal/logo.html +42 -0
- package/plugins/design/templates/ds-specimen.tsx.template +59 -0
- package/plugins/flow/.claude-plugin/config.schema.json +398 -0
- package/plugins/flow/README.md +45 -0
- package/plugins/flow/templates/ai-skeleton/INDEX.md +50 -0
- package/plugins/flow/templates/ai-skeleton/README.md +46 -0
- package/plugins/flow/templates/ai-skeleton/browser/har/.gitkeep +0 -0
- package/plugins/flow/templates/ai-skeleton/browser/snapshots/.gitkeep +0 -0
- package/plugins/flow/templates/ai-skeleton/business/README.md +12 -0
- package/plugins/flow/templates/ai-skeleton/context/README.md +12 -0
- package/plugins/flow/templates/ai-skeleton/decisions/README.md +20 -0
- package/plugins/flow/templates/ai-skeleton/design-import/.gitkeep +0 -0
- package/plugins/flow/templates/ai-skeleton/dev-logs/.gitkeep +0 -0
- package/plugins/flow/templates/ai-skeleton/device/.gitkeep +0 -0
- package/plugins/flow/templates/ai-skeleton/docs/README.md +5 -0
- package/plugins/flow/templates/ai-skeleton/logs/.gitkeep +0 -0
- package/plugins/flow/templates/ai-skeleton/logs/README.md +13 -0
- package/plugins/flow/templates/ai-skeleton/plans/README.md +16 -0
- package/plugins/flow/templates/ai-skeleton/plans/archive/.gitkeep +0 -0
- package/plugins/flow/templates/ai-skeleton/release-guide.md +35 -0
- package/plugins/flow/templates/ai-skeleton/reviews/README.md +15 -0
- package/plugins/flow/templates/ai-skeleton/scenarios/README.md +26 -0
- package/plugins/flow/templates/ai-skeleton/scenarios/_lib/.gitkeep +0 -0
- package/plugins/flow/templates/ai-skeleton/state/STATE.md +25 -0
- package/plugins/flow/templates/ai-skeleton/templates/HANDOFF.md +32 -0
- package/plugins/flow/templates/ai-skeleton/workflows.config.json +74 -0
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
// canvas-header.ts — Phase 3.6 Task 12a. JSDoc header generator for canvas
|
|
2
|
+
// TSX files. Projects `<Slug>.meta.json` → the leading block comment, so a
|
|
3
|
+
// future Claude (or human) cold-reading the canvas sees DS / opt-out / brief /
|
|
4
|
+
// artboards / handoff command without opening the sidecar.
|
|
5
|
+
//
|
|
6
|
+
// Header is *generated*. `.meta.json` stays the source of truth; this module
|
|
7
|
+
// is a projection. /design:edit step 1 (or scripts/migrate-canvases.ts) calls
|
|
8
|
+
// `applyHeader(canvasPath)` to keep the projection in sync.
|
|
9
|
+
//
|
|
10
|
+
// Idempotency:
|
|
11
|
+
// - First non-empty token is `/**` ending in `*/` → overwrite that block.
|
|
12
|
+
// - Otherwise → prepend a new block.
|
|
13
|
+
//
|
|
14
|
+
// JSX / imports are never touched. The module-level `import` order survives.
|
|
15
|
+
|
|
16
|
+
import path from 'node:path';
|
|
17
|
+
|
|
18
|
+
export interface MetaSidecar {
|
|
19
|
+
title?: string;
|
|
20
|
+
subtitle?: string;
|
|
21
|
+
brief?: string;
|
|
22
|
+
platform?: string;
|
|
23
|
+
designSystem?: string;
|
|
24
|
+
opt_out_scope?: string;
|
|
25
|
+
css_mode?: 'inline' | 'tailwind' | 'modules';
|
|
26
|
+
sections?: Array<{ id: string; artboards?: Array<{ id: string }> }>;
|
|
27
|
+
ai_context?: {
|
|
28
|
+
pinned_decisions?: string[];
|
|
29
|
+
known_quirks?: string[];
|
|
30
|
+
why_this_exists?: string;
|
|
31
|
+
};
|
|
32
|
+
[k: string]: unknown;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export interface HeaderOpts {
|
|
36
|
+
/** Bare canvas name (file stem, no extension). Used for @canvas + @handoff. */
|
|
37
|
+
name: string;
|
|
38
|
+
meta: MetaSidecar;
|
|
39
|
+
/** Override the DS slug. Defaults to meta.designSystem ?? 'project'. */
|
|
40
|
+
dsName?: string;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Build the JSDoc header string (no trailing newline padding — caller picks).
|
|
45
|
+
* Pure function — no fs.
|
|
46
|
+
*/
|
|
47
|
+
export function buildHeader(opts: HeaderOpts): string {
|
|
48
|
+
const m = opts.meta;
|
|
49
|
+
const artboardIds =
|
|
50
|
+
(m.sections ?? [])
|
|
51
|
+
.flatMap((s) => s.artboards ?? [])
|
|
52
|
+
.map((a) => a.id)
|
|
53
|
+
.join(' | ') || '—';
|
|
54
|
+
const opt = m.opt_out_scope ?? 'palette';
|
|
55
|
+
const platform = m.platform ?? 'desktop';
|
|
56
|
+
const ds = m.designSystem ?? opts.dsName ?? 'project';
|
|
57
|
+
const cssMode = m.css_mode ?? 'inline';
|
|
58
|
+
const brief = (m.brief ?? '').replace(/\s+/g, ' ').trim() || '—';
|
|
59
|
+
const subtitle = (m.subtitle ?? '').replace(/\s+/g, ' ').trim();
|
|
60
|
+
const slug = kebabSlug(opts.name);
|
|
61
|
+
|
|
62
|
+
const lines = [
|
|
63
|
+
'/**',
|
|
64
|
+
` * @canvas ${opts.name}${subtitle ? ` — ${subtitle}` : ''}`,
|
|
65
|
+
` * @ds ${ds}`,
|
|
66
|
+
` * @platform ${platform}`,
|
|
67
|
+
` * @opt_out ${opt}`,
|
|
68
|
+
` * @artboards ${artboardIds}`,
|
|
69
|
+
` * @brief ${brief}`,
|
|
70
|
+
` * @stack React 19 · TSX · Bun.build · css_mode=${cssMode}`,
|
|
71
|
+
` * @history .design/_history/${slug}/`,
|
|
72
|
+
` * @handoff bunx shadcn add file://./${opts.name}.registry.json`,
|
|
73
|
+
];
|
|
74
|
+
|
|
75
|
+
const ai = m.ai_context;
|
|
76
|
+
if (ai && (ai.why_this_exists || (ai.pinned_decisions?.length ?? 0) > 0)) {
|
|
77
|
+
lines.push(' *');
|
|
78
|
+
if (ai.why_this_exists) {
|
|
79
|
+
lines.push(` * @notes ${ai.why_this_exists.replace(/\s+/g, ' ').trim()}`);
|
|
80
|
+
}
|
|
81
|
+
for (const dec of ai.pinned_decisions ?? []) {
|
|
82
|
+
lines.push(` * @decision ${dec.replace(/\s+/g, ' ').trim()}`);
|
|
83
|
+
}
|
|
84
|
+
for (const q of ai.known_quirks ?? []) {
|
|
85
|
+
lines.push(` * @quirk ${q.replace(/\s+/g, ' ').trim()}`);
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
lines.push(' */');
|
|
89
|
+
return lines.join('\n');
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* Pure helper: replace or insert the header in a TSX source string. Returns
|
|
94
|
+
* the new source. If the source already starts with a block comment, that
|
|
95
|
+
* comment is replaced; otherwise a fresh header is prepended.
|
|
96
|
+
*/
|
|
97
|
+
export function applyHeaderToSource(source: string, header: string): string {
|
|
98
|
+
const trimmed = source.trimStart();
|
|
99
|
+
const leading = source.length - trimmed.length;
|
|
100
|
+
if (trimmed.startsWith('/**')) {
|
|
101
|
+
const end = trimmed.indexOf('*/');
|
|
102
|
+
if (end > 0) {
|
|
103
|
+
const before = source.slice(0, leading);
|
|
104
|
+
const after = trimmed.slice(end + 2);
|
|
105
|
+
return `${before}${header}${after}`;
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
return `${header}\n\n${source}`;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/**
|
|
112
|
+
* Read a canvas file + sibling .meta.json, regenerate the JSDoc header, write
|
|
113
|
+
* the canvas back atomically. No-op when content is identical.
|
|
114
|
+
*/
|
|
115
|
+
export async function applyHeader(canvasAbsPath: string): Promise<{ changed: boolean }> {
|
|
116
|
+
const ext = path.extname(canvasAbsPath);
|
|
117
|
+
const stem = path.basename(canvasAbsPath, ext);
|
|
118
|
+
const metaPath = path.join(path.dirname(canvasAbsPath), `${stem}.meta.json`);
|
|
119
|
+
const metaFile = Bun.file(metaPath);
|
|
120
|
+
const meta: MetaSidecar = (await metaFile.exists())
|
|
121
|
+
? ((await metaFile.json()) as MetaSidecar)
|
|
122
|
+
: {};
|
|
123
|
+
const source = await Bun.file(canvasAbsPath).text();
|
|
124
|
+
const header = buildHeader({ name: stem, meta });
|
|
125
|
+
const next = applyHeaderToSource(source, header);
|
|
126
|
+
if (next === source) return { changed: false };
|
|
127
|
+
const tmp = `${canvasAbsPath}.tmp.${Math.random().toString(36).slice(2, 10)}`;
|
|
128
|
+
await Bun.write(tmp, next);
|
|
129
|
+
const { rename } = await import('node:fs/promises');
|
|
130
|
+
await rename(tmp, canvasAbsPath);
|
|
131
|
+
return { changed: true };
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
function kebabSlug(s: string): string {
|
|
135
|
+
return s
|
|
136
|
+
.replace(/([a-z0-9])([A-Z])/g, '$1-$2')
|
|
137
|
+
.toLowerCase()
|
|
138
|
+
.replace(/[\s_]+/g, '-')
|
|
139
|
+
.replace(/[^a-z0-9-]+/g, '-')
|
|
140
|
+
.replace(/-+/g, '-')
|
|
141
|
+
.replace(/^-|-$/g, '');
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
// ---------------------------------------------------------------------------
|
|
145
|
+
// CLI entry — invoked by /design:edit's pre-flight when canvas-meta has
|
|
146
|
+
// changed since the header was last projected.
|
|
147
|
+
|
|
148
|
+
if (import.meta.main) {
|
|
149
|
+
const argv = process.argv.slice(2);
|
|
150
|
+
if (argv[0] === '--invoke' && argv.length === 2) {
|
|
151
|
+
const canvas = argv[1] as string;
|
|
152
|
+
try {
|
|
153
|
+
const { changed } = await applyHeader(canvas);
|
|
154
|
+
console.log(JSON.stringify({ canvas, changed }));
|
|
155
|
+
process.exit(0);
|
|
156
|
+
} catch (err) {
|
|
157
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
158
|
+
console.error(`canvas-header: ${msg}`);
|
|
159
|
+
process.exit(2);
|
|
160
|
+
}
|
|
161
|
+
} else {
|
|
162
|
+
console.error('Usage: bun run canvas-header.ts --invoke <canvas-abs-path>');
|
|
163
|
+
process.exit(2);
|
|
164
|
+
}
|
|
165
|
+
}
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @file canvas-icons.tsx — Phase 5.1 inline-SVG icon set
|
|
3
|
+
* @scope plugins/design/dev-server/canvas-icons.tsx
|
|
4
|
+
* @purpose Tiny dependency-free Lucide-style icon set for the canvas
|
|
5
|
+
* chrome (tool palette, context toolbar). Each icon is a single
|
|
6
|
+
* `<svg>` with `currentColor` stroke so it inherits the button
|
|
7
|
+
* foreground. Sized 16 px by default; the parent button is
|
|
8
|
+
* 32 × 32, leaving 8 px of optical padding on every side.
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import type { SVGProps } from 'react';
|
|
12
|
+
|
|
13
|
+
type IconProps = SVGProps<SVGSVGElement> & { size?: number };
|
|
14
|
+
|
|
15
|
+
function Svg({ size = 16, children, ...rest }: IconProps) {
|
|
16
|
+
return (
|
|
17
|
+
<svg
|
|
18
|
+
width={size}
|
|
19
|
+
height={size}
|
|
20
|
+
viewBox="0 0 24 24"
|
|
21
|
+
fill="none"
|
|
22
|
+
stroke="currentColor"
|
|
23
|
+
strokeWidth={1.75}
|
|
24
|
+
strokeLinecap="round"
|
|
25
|
+
strokeLinejoin="round"
|
|
26
|
+
aria-hidden="true"
|
|
27
|
+
focusable="false"
|
|
28
|
+
{...rest}
|
|
29
|
+
>
|
|
30
|
+
{children}
|
|
31
|
+
</svg>
|
|
32
|
+
);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export function IconMove(props: IconProps) {
|
|
36
|
+
return (
|
|
37
|
+
<Svg {...props}>
|
|
38
|
+
<path d="M4 4l16 6-7 2-2 7z" />
|
|
39
|
+
</Svg>
|
|
40
|
+
);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export function IconHand(props: IconProps) {
|
|
44
|
+
return (
|
|
45
|
+
<Svg {...props}>
|
|
46
|
+
<path d="M8 13V5a1.5 1.5 0 013 0v6" />
|
|
47
|
+
<path d="M11 11V4a1.5 1.5 0 013 0v7" />
|
|
48
|
+
<path d="M14 11V5.5a1.5 1.5 0 013 0V13" />
|
|
49
|
+
<path d="M17 9a1.5 1.5 0 013 0v6a6 6 0 01-6 6h-2a6 6 0 01-5-2.5L4 14a1.5 1.5 0 012-2l2 2" />
|
|
50
|
+
</Svg>
|
|
51
|
+
);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
export function IconComment(props: IconProps) {
|
|
55
|
+
return (
|
|
56
|
+
<Svg {...props}>
|
|
57
|
+
<path d="M21 12a8 8 0 11-3.3-6.5L21 4l-1 4.3A8 8 0 0121 12z" />
|
|
58
|
+
</Svg>
|
|
59
|
+
);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
export function IconPen(props: IconProps) {
|
|
63
|
+
return (
|
|
64
|
+
<Svg {...props}>
|
|
65
|
+
<path d="M14.5 4.5l5 5L8 21H3v-5z" />
|
|
66
|
+
<path d="M13 6l5 5" />
|
|
67
|
+
</Svg>
|
|
68
|
+
);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
export function IconRect(props: IconProps) {
|
|
72
|
+
return (
|
|
73
|
+
<Svg {...props}>
|
|
74
|
+
<rect x="4" y="5" width="16" height="14" rx="1.5" />
|
|
75
|
+
</Svg>
|
|
76
|
+
);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
export function IconEllipse(props: IconProps) {
|
|
80
|
+
return (
|
|
81
|
+
<Svg {...props}>
|
|
82
|
+
<ellipse cx="12" cy="12" rx="8" ry="6" />
|
|
83
|
+
</Svg>
|
|
84
|
+
);
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
export function IconArrow(props: IconProps) {
|
|
88
|
+
return (
|
|
89
|
+
<Svg {...props}>
|
|
90
|
+
<path d="M4 12h14" />
|
|
91
|
+
<path d="M13 6l6 6-6 6" />
|
|
92
|
+
</Svg>
|
|
93
|
+
);
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
export function IconEraser(props: IconProps) {
|
|
97
|
+
return (
|
|
98
|
+
<Svg {...props}>
|
|
99
|
+
<path d="M16 3l5 5-11 11H4v-6z" />
|
|
100
|
+
<path d="M8 14l5 5" />
|
|
101
|
+
</Svg>
|
|
102
|
+
);
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
export function IconPresentation(props: IconProps) {
|
|
106
|
+
return (
|
|
107
|
+
<Svg {...props}>
|
|
108
|
+
<rect x="3" y="4" width="18" height="12" rx="1.5" />
|
|
109
|
+
<path d="M9 21l3-5 3 5" />
|
|
110
|
+
</Svg>
|
|
111
|
+
);
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
export function IconChevronDown(props: IconProps) {
|
|
115
|
+
return (
|
|
116
|
+
<Svg {...props}>
|
|
117
|
+
<path d="M6 9l6 6 6-6" />
|
|
118
|
+
</Svg>
|
|
119
|
+
);
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
export const TOOL_ICONS: Record<string, (p: IconProps) => JSX.Element> = {
|
|
123
|
+
move: IconMove,
|
|
124
|
+
hand: IconHand,
|
|
125
|
+
comment: IconComment,
|
|
126
|
+
pen: IconPen,
|
|
127
|
+
rect: IconRect,
|
|
128
|
+
ellipse: IconEllipse,
|
|
129
|
+
arrow: IconArrow,
|
|
130
|
+
eraser: IconEraser,
|
|
131
|
+
};
|
|
@@ -0,0 +1,260 @@
|
|
|
1
|
+
// canvas-lib inlining for /design:handoff (Phase 3.6.1 Task 9; per DDR-025 the
|
|
2
|
+
// lib source now ships with the dev-server install).
|
|
3
|
+
//
|
|
4
|
+
// `/design:handoff` emits a self-contained shadcn registry-item.json. Canvases
|
|
5
|
+
// import their envelope + helpers from `@maude/canvas-lib`, which the dev-server
|
|
6
|
+
// resolves to its bundled `plugins/design/dev-server/canvas-lib.tsx`. The
|
|
7
|
+
// handoff drop must inline every used export so the consumer never sees the
|
|
8
|
+
// `@maude/canvas-lib` specifier — it's a dev-time virtual module, not a real
|
|
9
|
+
// npm dep.
|
|
10
|
+
//
|
|
11
|
+
// Strategy:
|
|
12
|
+
// 1. Parse the dev-server-bundled canvas-lib.tsx via oxc-parser. Walk top-level declarations
|
|
13
|
+
// to build a Map<name, { source, deps }>:
|
|
14
|
+
// - source: byte range of the declaration (including JSDoc immediately
|
|
15
|
+
// above, when present).
|
|
16
|
+
// - deps: other top-level identifiers referenced inside the body.
|
|
17
|
+
// 2. Scan the canvas TSX for `import { ... } from "@maude/canvas-lib"`.
|
|
18
|
+
// Collect the named imports.
|
|
19
|
+
// 3. Transitively resolve deps: each import drags in helpers it calls,
|
|
20
|
+
// which drag in helpers THEY call, etc.
|
|
21
|
+
// 4. Strip the import line.
|
|
22
|
+
// 5. Append the resolved function/const bodies AFTER the canvas's default
|
|
23
|
+
// export. (Placing them after default avoids "use before declare" issues
|
|
24
|
+
// in dev-mode tooling — JSX inside the component reads the outer scope.)
|
|
25
|
+
//
|
|
26
|
+
// Pure module — caller persists. Tested.
|
|
27
|
+
|
|
28
|
+
import { parseSync } from 'oxc-parser';
|
|
29
|
+
|
|
30
|
+
// biome-ignore lint/suspicious/noExplicitAny: oxc AST nodes are heterogeneous.
|
|
31
|
+
type AnyNode = any;
|
|
32
|
+
|
|
33
|
+
export interface LibExport {
|
|
34
|
+
name: string;
|
|
35
|
+
/** Full declaration source, including leading JSDoc block. */
|
|
36
|
+
source: string;
|
|
37
|
+
/** Other top-level identifiers this declaration references. */
|
|
38
|
+
deps: string[];
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export type LibMap = Map<string, LibExport>;
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Parse a canvas-lib TSX source into a Map keyed by named-export identifier.
|
|
45
|
+
* For each entry, `source` is the verbatim declaration text (with JSDoc
|
|
46
|
+
* comment if it precedes the declaration), and `deps` is the set of other
|
|
47
|
+
* top-level names the declaration body references.
|
|
48
|
+
*/
|
|
49
|
+
export function buildLibMap(libPath: string, libSource: string): LibMap {
|
|
50
|
+
const parsed = parseSync(libPath, libSource, { sourceType: 'module' });
|
|
51
|
+
if (parsed.errors && parsed.errors.length > 0) {
|
|
52
|
+
const first = parsed.errors[0];
|
|
53
|
+
throw new Error(
|
|
54
|
+
`oxc-parser failed on ${libPath} (${parsed.errors.length} errors). First: ${first?.message ?? 'unknown'}`
|
|
55
|
+
);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
// First pass — collect every top-level binding name (exports + internals).
|
|
59
|
+
const topLevelNames = new Set<string>();
|
|
60
|
+
for (const node of parsed.program.body as AnyNode[]) {
|
|
61
|
+
for (const n of namesFromTopLevel(node)) topLevelNames.add(n);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
// Second pass — record exported declarations with their source range +
|
|
65
|
+
// identifier references inside the body.
|
|
66
|
+
const map: LibMap = new Map();
|
|
67
|
+
// Also keep internal (non-exported) declarations indexed for transitive
|
|
68
|
+
// resolution — when an exported helper calls an internal helper, that
|
|
69
|
+
// internal must travel along.
|
|
70
|
+
const internals = new Map<string, LibExport>();
|
|
71
|
+
|
|
72
|
+
for (const node of parsed.program.body as AnyNode[]) {
|
|
73
|
+
if (node.type === 'ExportNamedDeclaration' && node.declaration) {
|
|
74
|
+
for (const name of namesFromDecl(node.declaration)) {
|
|
75
|
+
const range = nodeRangeWithComment(libSource, node);
|
|
76
|
+
const source = libSource.slice(range.start, range.end);
|
|
77
|
+
// Strip the leading `export` token from the captured source — inlined
|
|
78
|
+
// bodies must NOT re-export.
|
|
79
|
+
const stripped = source.replace(/^export\s+/m, '');
|
|
80
|
+
const deps = collectIdentifierRefs(node.declaration, topLevelNames, name);
|
|
81
|
+
map.set(name, { name, source: stripped, deps: [...deps] });
|
|
82
|
+
}
|
|
83
|
+
} else if (
|
|
84
|
+
node.type === 'FunctionDeclaration' ||
|
|
85
|
+
node.type === 'VariableDeclaration' ||
|
|
86
|
+
node.type === 'ClassDeclaration'
|
|
87
|
+
) {
|
|
88
|
+
for (const name of namesFromDecl(node)) {
|
|
89
|
+
const range = nodeRangeWithComment(libSource, node);
|
|
90
|
+
const source = libSource.slice(range.start, range.end);
|
|
91
|
+
const deps = collectIdentifierRefs(node, topLevelNames, name);
|
|
92
|
+
internals.set(name, { name, source, deps: [...deps] });
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
// Merge internals into the map under their own names — they're resolvable
|
|
98
|
+
// by the inliner when an export depends on them. Internals are never
|
|
99
|
+
// surfaced as user-requested imports but are reachable transitively.
|
|
100
|
+
for (const [name, info] of internals) {
|
|
101
|
+
if (!map.has(name)) map.set(name, info);
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
return map;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
function namesFromTopLevel(node: AnyNode): string[] {
|
|
108
|
+
if (!node || typeof node !== 'object') return [];
|
|
109
|
+
if (node.type === 'ExportNamedDeclaration' && node.declaration) {
|
|
110
|
+
return namesFromDecl(node.declaration);
|
|
111
|
+
}
|
|
112
|
+
if (
|
|
113
|
+
node.type === 'FunctionDeclaration' ||
|
|
114
|
+
node.type === 'ClassDeclaration' ||
|
|
115
|
+
node.type === 'VariableDeclaration'
|
|
116
|
+
) {
|
|
117
|
+
return namesFromDecl(node);
|
|
118
|
+
}
|
|
119
|
+
return [];
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
function namesFromDecl(decl: AnyNode): string[] {
|
|
123
|
+
if (!decl) return [];
|
|
124
|
+
if (decl.type === 'FunctionDeclaration' || decl.type === 'ClassDeclaration') {
|
|
125
|
+
return decl.id?.name ? [decl.id.name as string] : [];
|
|
126
|
+
}
|
|
127
|
+
if (decl.type === 'VariableDeclaration') {
|
|
128
|
+
const out: string[] = [];
|
|
129
|
+
for (const d of decl.declarations ?? []) {
|
|
130
|
+
if (d.id?.type === 'Identifier' && d.id.name) out.push(d.id.name as string);
|
|
131
|
+
}
|
|
132
|
+
return out;
|
|
133
|
+
}
|
|
134
|
+
return [];
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
/**
|
|
138
|
+
* Walk an AST subtree collecting Identifier names that match the top-level
|
|
139
|
+
* registry, excluding `selfName` (no self-reference).
|
|
140
|
+
*/
|
|
141
|
+
function collectIdentifierRefs(
|
|
142
|
+
decl: AnyNode,
|
|
143
|
+
registry: Set<string>,
|
|
144
|
+
selfName: string
|
|
145
|
+
): Set<string> {
|
|
146
|
+
const found = new Set<string>();
|
|
147
|
+
function visit(node: AnyNode): void {
|
|
148
|
+
if (!node || typeof node !== 'object') return;
|
|
149
|
+
if (Array.isArray(node)) {
|
|
150
|
+
for (const c of node) visit(c);
|
|
151
|
+
return;
|
|
152
|
+
}
|
|
153
|
+
if (typeof node.type !== 'string') return;
|
|
154
|
+
if (node.type === 'Identifier' && typeof node.name === 'string') {
|
|
155
|
+
if (node.name !== selfName && registry.has(node.name)) found.add(node.name);
|
|
156
|
+
}
|
|
157
|
+
// JSXIdentifier looks the same shape — treat like Identifier.
|
|
158
|
+
if (node.type === 'JSXIdentifier' && typeof node.name === 'string') {
|
|
159
|
+
if (node.name !== selfName && registry.has(node.name)) found.add(node.name);
|
|
160
|
+
}
|
|
161
|
+
for (const k of Object.keys(node)) {
|
|
162
|
+
if (k === 'loc' || k === 'range' || k === 'start' || k === 'end' || k === 'type') continue;
|
|
163
|
+
visit(node[k]);
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
visit(decl);
|
|
167
|
+
return found;
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
/**
|
|
171
|
+
* Find the source-range start of a node, extended backwards over any
|
|
172
|
+
* immediately-preceding block-comment (JSDoc) so the inlined declaration
|
|
173
|
+
* keeps its docs.
|
|
174
|
+
*/
|
|
175
|
+
function nodeRangeWithComment(source: string, node: AnyNode): { start: number; end: number } {
|
|
176
|
+
const end = node.end as number;
|
|
177
|
+
let start = node.start as number;
|
|
178
|
+
// Walk back over whitespace; if we hit `*/` we extend through the matching
|
|
179
|
+
// `/*`.
|
|
180
|
+
let i = start - 1;
|
|
181
|
+
while (i >= 0 && /\s/.test(source[i] ?? '')) i--;
|
|
182
|
+
if (i >= 1 && source[i - 1] === '*' && source[i] === '/') {
|
|
183
|
+
// We're at the `*/` — find matching `/*`.
|
|
184
|
+
const open = source.lastIndexOf('/*', i);
|
|
185
|
+
if (open >= 0) start = open;
|
|
186
|
+
}
|
|
187
|
+
return { start, end };
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
export interface InlineResult {
|
|
191
|
+
/** Canvas source with the import stripped + helpers appended. */
|
|
192
|
+
content: string;
|
|
193
|
+
/** True when an `@maude/canvas-lib` import was found + removed. */
|
|
194
|
+
droppedImport: boolean;
|
|
195
|
+
/** Sorted list of helper names inlined (including transitive). */
|
|
196
|
+
inlined: string[];
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
/**
|
|
200
|
+
* Replace `import { ... } from "@maude/canvas-lib"` with the resolved bodies
|
|
201
|
+
* of every named import (+ their transitive dependencies). Returns the
|
|
202
|
+
* rewritten source.
|
|
203
|
+
*/
|
|
204
|
+
export function inlineUsedExports(canvasSource: string, libMap: LibMap): InlineResult {
|
|
205
|
+
// 1. Locate the import line. We tolerate single OR double quotes, type-only
|
|
206
|
+
// imports (rare), trailing commas, multi-line shapes.
|
|
207
|
+
const importRe = /\bimport\s+(?:type\s+)?\{([^}]+)\}\s*from\s*["']@maude\/canvas-lib["']\s*;?/m;
|
|
208
|
+
const m = importRe.exec(canvasSource);
|
|
209
|
+
if (!m) {
|
|
210
|
+
return { content: canvasSource, droppedImport: false, inlined: [] };
|
|
211
|
+
}
|
|
212
|
+
const importList = (m[1] ?? '')
|
|
213
|
+
.split(',')
|
|
214
|
+
.map((s) => s.trim())
|
|
215
|
+
.filter(Boolean)
|
|
216
|
+
// Drop `type` prefix on individual names + handle `X as Y` aliases (rare).
|
|
217
|
+
.map((s) => s.replace(/^type\s+/, '').split(/\s+as\s+/)[0])
|
|
218
|
+
.filter((s): s is string => Boolean(s));
|
|
219
|
+
|
|
220
|
+
// 2. Resolve transitive deps. BFS over the libMap.
|
|
221
|
+
const wanted = new Set<string>();
|
|
222
|
+
const queue = [...importList];
|
|
223
|
+
while (queue.length > 0) {
|
|
224
|
+
const name = queue.shift() as string;
|
|
225
|
+
if (wanted.has(name)) continue;
|
|
226
|
+
const info = libMap.get(name);
|
|
227
|
+
if (!info) {
|
|
228
|
+
throw new Error(
|
|
229
|
+
`[canvas-lib-inline] Canvas imports '${name}' from @maude/canvas-lib but the lib has no such export.`
|
|
230
|
+
);
|
|
231
|
+
}
|
|
232
|
+
wanted.add(name);
|
|
233
|
+
for (const dep of info.deps) {
|
|
234
|
+
if (!wanted.has(dep)) queue.push(dep);
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
// 3. Strip the import line.
|
|
239
|
+
let out = canvasSource.slice(0, m.index) + canvasSource.slice(m.index + m[0].length);
|
|
240
|
+
// Cleanup: collapse runs of blank lines we just opened up.
|
|
241
|
+
out = out.replace(/\n{3,}/g, '\n\n');
|
|
242
|
+
|
|
243
|
+
// 4. Append the resolved declarations at the file's tail. The plan calls
|
|
244
|
+
// for "before export default", but a trailing block works in modern JS
|
|
245
|
+
// (function declarations hoist, top-level consts referenced inside the
|
|
246
|
+
// default export are evaluated at module init). Place after a clear
|
|
247
|
+
// separator + section comment so cold readers grok what's happening.
|
|
248
|
+
const ordered = [...wanted].sort();
|
|
249
|
+
const banner =
|
|
250
|
+
'\n\n// ─────────────────────────────────────────────────────────────────────────────\n' +
|
|
251
|
+
'// Canvas-lib helpers (inlined by /design:handoff). Self-contained drop —\n' +
|
|
252
|
+
'// no dev-time specifier is referenced from this file.\n\n';
|
|
253
|
+
const bodies = ordered.map((n) => (libMap.get(n)?.source ?? '').trimEnd()).join('\n\n');
|
|
254
|
+
|
|
255
|
+
return {
|
|
256
|
+
content: `${out.trimEnd()}${banner}${bodies}\n`,
|
|
257
|
+
droppedImport: true,
|
|
258
|
+
inlined: ordered,
|
|
259
|
+
};
|
|
260
|
+
}
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
// Virtual-module resolver for `@maude/canvas-lib` (Phase 3.6.1; relocated 4.0.5).
|
|
2
|
+
//
|
|
3
|
+
// Canvases import the shared canvas library via the specifier `@maude/canvas-lib`.
|
|
4
|
+
// At build time we redirect that to the dev-server-bundled source at
|
|
5
|
+
// `plugins/design/dev-server/canvas-lib.tsx` so:
|
|
6
|
+
//
|
|
7
|
+
// - the lib ships with the dev-server install (single source of truth — see
|
|
8
|
+
// DDR-025; reverses DDR-022's "project-owned source under <designRoot>/_lib/"),
|
|
9
|
+
// - Bun.build bundles the actually-used exports into the canvas module via
|
|
10
|
+
// normal tree-shaking,
|
|
11
|
+
// - `/design:handoff` can strip the import + inline the same exports for the
|
|
12
|
+
// emitted registry-item (see canvas-lib-inline.ts).
|
|
13
|
+
//
|
|
14
|
+
// Two surfaces:
|
|
15
|
+
//
|
|
16
|
+
// - `canvasLibResolver()` — Bun.build plugin. Registered alongside
|
|
17
|
+
// `exact-externals` in canvas-build.ts. Must run FIRST so the bare specifier
|
|
18
|
+
// gets claimed before any other resolver.
|
|
19
|
+
// - `readCanvasLibSource()` — small async helper used by handoff.ts
|
|
20
|
+
// to read the lib source once for inlining.
|
|
21
|
+
//
|
|
22
|
+
// Failure mode: if the dev-server's bundled canvas-lib is missing, the install
|
|
23
|
+
// is corrupt; canvas-build.ts surfaces a re-install hint.
|
|
24
|
+
|
|
25
|
+
import { existsSync } from 'node:fs';
|
|
26
|
+
import path from 'node:path';
|
|
27
|
+
|
|
28
|
+
import type { BunPlugin } from 'bun';
|
|
29
|
+
|
|
30
|
+
export const CANVAS_LIB_SPECIFIER = '@maude/canvas-lib';
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Returns the dev-server-internal canvas-lib path. The `_designRoot` parameter
|
|
34
|
+
* is retained for one minor (back-compat with callers we don't control) but
|
|
35
|
+
* ignored — canvas-lib now ships with the dev-server install (DDR-025).
|
|
36
|
+
*/
|
|
37
|
+
export function canvasLibPath(_designRoot?: string): string {
|
|
38
|
+
return path.join(import.meta.dir, 'canvas-lib.tsx');
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export interface CanvasLibResolverOptions {
|
|
42
|
+
/** Throw at resolve-time when the lib file is missing. Defaults to true. */
|
|
43
|
+
failLoud?: boolean;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Bun.build plugin factory. Maps `@maude/canvas-lib` → the dev-server-bundled
|
|
48
|
+
* `canvas-lib.tsx`. No-op for any other specifier.
|
|
49
|
+
*/
|
|
50
|
+
export function canvasLibResolver(
|
|
51
|
+
_designRoot?: string,
|
|
52
|
+
opts: CanvasLibResolverOptions = {}
|
|
53
|
+
): BunPlugin {
|
|
54
|
+
const target = canvasLibPath();
|
|
55
|
+
const failLoud = opts.failLoud !== false;
|
|
56
|
+
return {
|
|
57
|
+
name: 'maude-canvas-lib',
|
|
58
|
+
setup(builder) {
|
|
59
|
+
builder.onResolve({ filter: /^@maude\/canvas-lib$/ }, () => {
|
|
60
|
+
if (failLoud && !existsSync(target)) {
|
|
61
|
+
throw new Error(
|
|
62
|
+
`[@maude/canvas-lib] canvas library missing at ${target} — dev-server install is corrupt; re-install @1agh/maude.`
|
|
63
|
+
);
|
|
64
|
+
}
|
|
65
|
+
return { path: target };
|
|
66
|
+
});
|
|
67
|
+
},
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* Read the on-disk canvas-lib source. Returns the raw TSX. Throws if the file
|
|
73
|
+
* is missing. Used by handoff.ts to build the export-name → source map for
|
|
74
|
+
* inlining used helpers into the emitted registry-item.
|
|
75
|
+
*/
|
|
76
|
+
export async function readCanvasLibSource(_designRoot?: string): Promise<string> {
|
|
77
|
+
const p = canvasLibPath();
|
|
78
|
+
const f = Bun.file(p);
|
|
79
|
+
if (!(await f.exists())) {
|
|
80
|
+
throw new Error(
|
|
81
|
+
`[@maude/canvas-lib] canvas library missing at ${p} — dev-server install is corrupt; re-install @1agh/maude.`
|
|
82
|
+
);
|
|
83
|
+
}
|
|
84
|
+
return f.text();
|
|
85
|
+
}
|