@pixel-point/toolcraft 0.0.2
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.md +98 -0
- package/README.md +41 -0
- package/bin/create-toolcraft-app.mjs +8 -0
- package/bin/toolcraft.mjs +8 -0
- package/package.json +24 -0
- package/scripts/prepare-pack.mjs +29 -0
- package/src/cli.mjs +392 -0
- package/src/cli.test.mjs +284 -0
- package/src/copy-recursive.mjs +86 -0
- package/src/generate.mjs +212 -0
- package/src/generate.test.mjs +322 -0
- package/src/import-map.mjs +14 -0
- package/src/package-json.mjs +80 -0
- package/src/package-json.test.mjs +67 -0
- package/src/rewrite-imports.mjs +85 -0
- package/src/rewrite-imports.test.mjs +58 -0
- package/templates/runtime/contracts/component-contracts.test.ts +1165 -0
- package/templates/runtime/contracts/component-contracts.ts +1340 -0
- package/templates/runtime/contracts/decision-contracts.test.ts +206 -0
- package/templates/runtime/contracts/decision-contracts.ts +283 -0
- package/templates/runtime/contracts/index.test.ts +14 -0
- package/templates/runtime/contracts/index.ts +3 -0
- package/templates/runtime/contracts/types.ts +56 -0
- package/templates/runtime/export/export.test.ts +203 -0
- package/templates/runtime/export/export.ts +132 -0
- package/templates/runtime/export/index.ts +1 -0
- package/templates/runtime/index.ts +14 -0
- package/templates/runtime/react/canvas-shell.test.tsx +424 -0
- package/templates/runtime/react/canvas-shell.tsx +408 -0
- package/templates/runtime/react/control-renderers.ts +31 -0
- package/templates/runtime/react/controls-panel.test.tsx +3736 -0
- package/templates/runtime/react/controls-panel.tsx +2327 -0
- package/templates/runtime/react/curve-geometry.test.ts +70 -0
- package/templates/runtime/react/index.ts +15 -0
- package/templates/runtime/react/layer-tree.ts +96 -0
- package/templates/runtime/react/layers-panel.test.tsx +487 -0
- package/templates/runtime/react/layers-panel.tsx +1348 -0
- package/templates/runtime/react/media-file.ts +82 -0
- package/templates/runtime/react/panel-host-config.ts +80 -0
- package/templates/runtime/react/panel-host-geometry.test.ts +66 -0
- package/templates/runtime/react/panel-host-geometry.ts +109 -0
- package/templates/runtime/react/panel-host-types.ts +74 -0
- package/templates/runtime/react/panel-host.test.tsx +102 -0
- package/templates/runtime/react/panel-host.tsx +353 -0
- package/templates/runtime/react/runtime-public-api.test.tsx +132 -0
- package/templates/runtime/react/settings-transfer.test.ts +150 -0
- package/templates/runtime/react/settings-transfer.ts +279 -0
- package/templates/runtime/react/storage-key-migration.ts +48 -0
- package/templates/runtime/react/theme-runtime.tsx +177 -0
- package/templates/runtime/react/timeline-panel.test.tsx +668 -0
- package/templates/runtime/react/timeline-panel.tsx +2953 -0
- package/templates/runtime/react/toolbar-panel.test.tsx +212 -0
- package/templates/runtime/react/toolbar-panel.tsx +205 -0
- package/templates/runtime/react/toolcraft-app.integration.test.tsx +350 -0
- package/templates/runtime/react/toolcraft-app.test.tsx +339 -0
- package/templates/runtime/react/toolcraft-app.tsx +81 -0
- package/templates/runtime/react/toolcraft-root.test.tsx +347 -0
- package/templates/runtime/react/toolcraft-root.tsx +203 -0
- package/templates/runtime/react/use-toolcraft.ts +41 -0
- package/templates/runtime/schema/define-toolcraft.test.ts +1524 -0
- package/templates/runtime/schema/define-toolcraft.ts +1442 -0
- package/templates/runtime/schema/keyframe-capability.test.ts +90 -0
- package/templates/runtime/schema/keyframe-capability.ts +51 -0
- package/templates/runtime/schema/runtime-targets.ts +40 -0
- package/templates/runtime/schema/types.ts +370 -0
- package/templates/runtime/state/canvas-zoom.ts +8 -0
- package/templates/runtime/state/create-template-state.test.ts +242 -0
- package/templates/runtime/state/create-template-state.ts +95 -0
- package/templates/runtime/state/keyframe-evaluation.test.ts +141 -0
- package/templates/runtime/state/keyframe-evaluation.ts +203 -0
- package/templates/runtime/state/persistence.test.ts +217 -0
- package/templates/runtime/state/persistence.ts +511 -0
- package/templates/runtime/state/reducer.test.ts +937 -0
- package/templates/runtime/state/reducer.ts +1212 -0
- package/templates/runtime/state/timeline-readiness.ts +43 -0
- package/templates/runtime/state/types.ts +242 -0
- package/templates/runtime/styles.css +125 -0
- package/templates/runtime/testing/performance.test.ts +1058 -0
- package/templates/runtime/testing/performance.ts +1078 -0
- package/templates/starter/AGENTS.md +186 -0
- package/templates/starter/LICENSE.md +98 -0
- package/templates/starter/NOTICE.md +8 -0
- package/templates/starter/docs/toolcraft/README.md +41 -0
- package/templates/starter/docs/toolcraft/acceptance-testing.md +205 -0
- package/templates/starter/docs/toolcraft/agent-worklog.md +81 -0
- package/templates/starter/docs/toolcraft/assembly-workflow.md +206 -0
- package/templates/starter/docs/toolcraft/component-rules.md +299 -0
- package/templates/starter/docs/toolcraft/custom-controls.md +71 -0
- package/templates/starter/docs/toolcraft/decision-contract.md +71 -0
- package/templates/starter/docs/toolcraft/performance.md +112 -0
- package/templates/starter/docs/toolcraft/renderer-technique.md +48 -0
- package/templates/starter/docs/toolcraft/schema-reference.md +265 -0
- package/templates/starter/docs/toolcraft/workflow.md +87 -0
- package/templates/starter/e2e/app-browser-acceptance.spec.ts +785 -0
- package/templates/starter/e2e/app-controls.spec.ts +41 -0
- package/templates/starter/e2e/app-performance.spec.ts +326 -0
- package/templates/starter/e2e/canvas-handle-helpers.ts +244 -0
- package/templates/starter/e2e/performance-helpers.ts +612 -0
- package/templates/starter/e2e/product-observable-helpers.ts +170 -0
- package/templates/starter/index.html +12 -0
- package/templates/starter/package.json +52 -0
- package/templates/starter/playwright.config.ts +43 -0
- package/templates/starter/scripts/check-ai-skills.mjs +95 -0
- package/templates/starter/scripts/check-toolcraft-docs.mjs +159 -0
- package/templates/starter/scripts/check-toolcraft-integrity.mjs +232 -0
- package/templates/starter/scripts/run-vite-on-free-port.mjs +48 -0
- package/templates/starter/scripts/toolcraft-port.mjs +54 -0
- package/templates/starter/scripts/toolcraft-port.test.mjs +73 -0
- package/templates/starter/src/app/starter-acceptance.test.ts +5959 -0
- package/templates/starter/src/app/starter-acceptance.ts +2646 -0
- package/templates/starter/src/app/starter-performance.test.ts +1390 -0
- package/templates/starter/src/app/starter-performance.ts +12 -0
- package/templates/starter/src/app/starter-schema.test.ts +70 -0
- package/templates/starter/src/app/starter-schema.ts +15 -0
- package/templates/starter/src/main.tsx +18 -0
- package/templates/starter/src/router.tsx +16 -0
- package/templates/starter/src/routes/index.tsx +7 -0
- package/templates/starter/src/routes/root.tsx +19 -0
- package/templates/starter/src/styles.css +120 -0
- package/templates/starter/tsconfig.json +11 -0
- package/templates/starter/vite.config.ts +13 -0
- package/templates/ui/components/composites/accordion.tsx +73 -0
- package/templates/ui/components/composites/alert-dialog.tsx +190 -0
- package/templates/ui/components/composites/alert.tsx +74 -0
- package/templates/ui/components/composites/aspect-ratio.tsx +22 -0
- package/templates/ui/components/composites/avatar.tsx +98 -0
- package/templates/ui/components/composites/badge.tsx +69 -0
- package/templates/ui/components/composites/breadcrumb.tsx +106 -0
- package/templates/ui/components/composites/card.tsx +91 -0
- package/templates/ui/components/composites/combobox.tsx +486 -0
- package/templates/ui/components/composites/command.tsx +296 -0
- package/templates/ui/components/composites/context-menu.tsx +247 -0
- package/templates/ui/components/composites/dialog.tsx +282 -0
- package/templates/ui/components/composites/dropdown-menu.tsx +299 -0
- package/templates/ui/components/composites/empty.tsx +110 -0
- package/templates/ui/components/composites/hover-card.tsx +44 -0
- package/templates/ui/components/composites/index.ts +30 -0
- package/templates/ui/components/composites/menubar.tsx +214 -0
- package/templates/ui/components/composites/navigation-menu.tsx +167 -0
- package/templates/ui/components/composites/pagination.tsx +131 -0
- package/templates/ui/components/composites/progress.tsx +72 -0
- package/templates/ui/components/composites/radio-group.tsx +84 -0
- package/templates/ui/components/composites/resizable.tsx +42 -0
- package/templates/ui/components/composites/sheet.tsx +153 -0
- package/templates/ui/components/composites/sidebar-structural.tsx +310 -0
- package/templates/ui/components/composites/sidebar.tsx +431 -0
- package/templates/ui/components/composites/sonner.tsx +35 -0
- package/templates/ui/components/composites/spinner.tsx +43 -0
- package/templates/ui/components/composites/table.tsx +108 -0
- package/templates/ui/components/composites/tabs.tsx +83 -0
- package/templates/ui/components/control-layout/index.tsx +437 -0
- package/templates/ui/components/controls/actions/actions-control.tsx +139 -0
- package/templates/ui/components/controls/actions/index.ts +9 -0
- package/templates/ui/components/controls/anchor-grid/anchor-grid-control.tsx +107 -0
- package/templates/ui/components/controls/anchor-grid/index.ts +4 -0
- package/templates/ui/components/controls/boolean/boolean-controls.tsx +79 -0
- package/templates/ui/components/controls/boolean/index.ts +4 -0
- package/templates/ui/components/controls/channel-mixer/channel-mixer-control.tsx +95 -0
- package/templates/ui/components/controls/channel-mixer/index.ts +4 -0
- package/templates/ui/components/controls/channel-tabs/channel-tabs.tsx +42 -0
- package/templates/ui/components/controls/channel-tabs/index.ts +6 -0
- package/templates/ui/components/controls/code-textarea/code-textarea-control.tsx +90 -0
- package/templates/ui/components/controls/code-textarea/index.ts +4 -0
- package/templates/ui/components/controls/color/color-control.tsx +571 -0
- package/templates/ui/components/controls/color/color-picker-popover.tsx +104 -0
- package/templates/ui/components/controls/color/index.ts +41 -0
- package/templates/ui/components/controls/color/palette-control-data.ts +436 -0
- package/templates/ui/components/controls/color/palette-control.tsx +535 -0
- package/templates/ui/components/controls/color/style-guide-color-picker-channel-utils.ts +162 -0
- package/templates/ui/components/controls/color/style-guide-color-picker-interactions.ts +190 -0
- package/templates/ui/components/controls/color/style-guide-color-picker-logic.ts +485 -0
- package/templates/ui/components/controls/color/style-guide-color-picker-parts.tsx +710 -0
- package/templates/ui/components/controls/color/style-guide-color-picker.tsx +503 -0
- package/templates/ui/components/controls/control-types.ts +43 -0
- package/templates/ui/components/controls/curves/curve-geometry.ts +355 -0
- package/templates/ui/components/controls/curves/curve-graph.tsx +390 -0
- package/templates/ui/components/controls/curves/curves-control.tsx +445 -0
- package/templates/ui/components/controls/curves/index.ts +6 -0
- package/templates/ui/components/controls/file-drop/file-drop-control.tsx +191 -0
- package/templates/ui/components/controls/file-drop/index.ts +5 -0
- package/templates/ui/components/controls/font-picker/font-catalog.json +15360 -0
- package/templates/ui/components/controls/font-picker/font-catalog.ts +116 -0
- package/templates/ui/components/controls/font-picker/font-picker-control.tsx +1202 -0
- package/templates/ui/components/controls/font-picker/font-preview-loader.ts +336 -0
- package/templates/ui/components/controls/font-picker/index.ts +24 -0
- package/templates/ui/components/controls/font-picker/use-hover-intent.ts +46 -0
- package/templates/ui/components/controls/gradient/gradient-control-utils.ts +190 -0
- package/templates/ui/components/controls/gradient/gradient-control.tsx +612 -0
- package/templates/ui/components/controls/gradient/gradient-stop-list.tsx +400 -0
- package/templates/ui/components/controls/gradient/gradient-toolbar.tsx +152 -0
- package/templates/ui/components/controls/gradient/index.ts +4 -0
- package/templates/ui/components/controls/image-picker/image-picker-control.tsx +139 -0
- package/templates/ui/components/controls/image-picker/index.ts +7 -0
- package/templates/ui/components/controls/index.ts +192 -0
- package/templates/ui/components/controls/range-input/index.ts +4 -0
- package/templates/ui/components/controls/range-input/range-input-control.tsx +173 -0
- package/templates/ui/components/controls/range-slider/index.ts +4 -0
- package/templates/ui/components/controls/range-slider/range-slider-control.tsx +122 -0
- package/templates/ui/components/controls/range-slider/range-slider-value.ts +61 -0
- package/templates/ui/components/controls/segmented/index.ts +8 -0
- package/templates/ui/components/controls/segmented/segmented-control.tsx +94 -0
- package/templates/ui/components/controls/select/index.ts +4 -0
- package/templates/ui/components/controls/select/select-control.tsx +223 -0
- package/templates/ui/components/controls/slider/index.ts +4 -0
- package/templates/ui/components/controls/slider/slider-control.tsx +150 -0
- package/templates/ui/components/controls/slider/slider-value.ts +56 -0
- package/templates/ui/components/controls/text-input/index.ts +4 -0
- package/templates/ui/components/controls/text-input/text-input-control.tsx +158 -0
- package/templates/ui/components/controls/use-measured-element-width.ts +42 -0
- package/templates/ui/components/controls/vector/index.ts +8 -0
- package/templates/ui/components/controls/vector/vector-control.tsx +401 -0
- package/templates/ui/components/panel/index.ts +19 -0
- package/templates/ui/components/panel/panel-actions.tsx +165 -0
- package/templates/ui/components/panel/panel-header.tsx +61 -0
- package/templates/ui/components/panel/panel-icon-button.tsx +96 -0
- package/templates/ui/components/panel/panel-section.tsx +168 -0
- package/templates/ui/components/panel/panel-surface.tsx +206 -0
- package/templates/ui/components/panel/panel.tsx +210 -0
- package/templates/ui/components/primitives/animated-loader.tsx +61 -0
- package/templates/ui/components/primitives/button-group.tsx +134 -0
- package/templates/ui/components/primitives/button.tsx +429 -0
- package/templates/ui/components/primitives/checkbox.tsx +62 -0
- package/templates/ui/components/primitives/editable-slider-value-label.tsx +337 -0
- package/templates/ui/components/primitives/field.tsx +225 -0
- package/templates/ui/components/primitives/index.ts +82 -0
- package/templates/ui/components/primitives/input-group.tsx +298 -0
- package/templates/ui/components/primitives/input.tsx +61 -0
- package/templates/ui/components/primitives/internal/button-loading.tsx +178 -0
- package/templates/ui/components/primitives/label.tsx +16 -0
- package/templates/ui/components/primitives/popover.tsx +126 -0
- package/templates/ui/components/primitives/portal-layer-context.tsx +33 -0
- package/templates/ui/components/primitives/primitive-arrow-icon.tsx +38 -0
- package/templates/ui/components/primitives/scroll-fade-logic.ts +441 -0
- package/templates/ui/components/primitives/scroll-fade-render.tsx +75 -0
- package/templates/ui/components/primitives/scroll-fade-types.ts +41 -0
- package/templates/ui/components/primitives/scroll-fade.tsx +72 -0
- package/templates/ui/components/primitives/select.tsx +408 -0
- package/templates/ui/components/primitives/selection-state.ts +31 -0
- package/templates/ui/components/primitives/separator.tsx +21 -0
- package/templates/ui/components/primitives/slider/index.ts +4 -0
- package/templates/ui/components/primitives/slider/slider-interaction.tsx +96 -0
- package/templates/ui/components/primitives/slider/slider-parts.tsx +303 -0
- package/templates/ui/components/primitives/slider/slider-reset.ts +152 -0
- package/templates/ui/components/primitives/slider/slider-value.ts +114 -0
- package/templates/ui/components/primitives/slider/slider.tsx +511 -0
- package/templates/ui/components/primitives/switch.tsx +35 -0
- package/templates/ui/components/primitives/textarea.tsx +49 -0
- package/templates/ui/components/primitives/toggle-group.tsx +114 -0
- package/templates/ui/components/primitives/toggle.tsx +46 -0
- package/templates/ui/components/primitives/tooltip.tsx +100 -0
- package/templates/ui/hooks/use-mobile.ts +21 -0
- package/templates/ui/index.ts +31 -0
- package/templates/ui/lib/control-outline.ts +3 -0
- package/templates/ui/lib/input-control-style.ts +131 -0
- package/templates/ui/lib/style-guide-color-utils.ts +111 -0
- package/templates/ui/lib/utils.ts +6 -0
- package/templates/ui/styles.css +291 -0
|
@@ -0,0 +1,232 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
import crypto from "node:crypto";
|
|
4
|
+
import fs from "node:fs/promises";
|
|
5
|
+
import path from "node:path";
|
|
6
|
+
import { fileURLToPath } from "node:url";
|
|
7
|
+
|
|
8
|
+
const appRoot = path.resolve(fileURLToPath(new URL("..", import.meta.url)));
|
|
9
|
+
const toolcraftRoot = path.join(appRoot, "src/toolcraft");
|
|
10
|
+
const manifestPath = path.join(toolcraftRoot, ".toolcraft-manifest.json");
|
|
11
|
+
const appSourceRoot = path.join(appRoot, "src/app");
|
|
12
|
+
const routesSourceRoot = path.join(appRoot, "src/routes");
|
|
13
|
+
const runtimeSurfaceComponentNames = [
|
|
14
|
+
"ToolcraftRoot",
|
|
15
|
+
"CanvasShell",
|
|
16
|
+
"ControlsPanel",
|
|
17
|
+
"LayersPanel",
|
|
18
|
+
"TimelinePanel",
|
|
19
|
+
"ToolbarPanel",
|
|
20
|
+
];
|
|
21
|
+
const runtimeSurfaceNamePattern = runtimeSurfaceComponentNames.join("|");
|
|
22
|
+
const lowLevelRuntimeSurfaceImportPattern = new RegExp(
|
|
23
|
+
`import\\s*\\{[^}]*\\b(?:${runtimeSurfaceNamePattern})\\b[^}]*\\}\\s*from\\s*["'][^"']*runtime/react["']`,
|
|
24
|
+
);
|
|
25
|
+
const manualRuntimeSurfaceRenderPattern = new RegExp(
|
|
26
|
+
`<\\s*(?:(?:${runtimeSurfaceNamePattern})\\b|[A-Za-z_$][\\w$]*\\.(?:${runtimeSurfaceNamePattern})\\b)|React\\.createElement\\s*\\(\\s*(?:(?:${runtimeSurfaceNamePattern})\\b|[A-Za-z_$][\\w$]*\\.(?:${runtimeSurfaceNamePattern})\\b)`,
|
|
27
|
+
);
|
|
28
|
+
const builtInControlComponentNames = [
|
|
29
|
+
"ActionsControl",
|
|
30
|
+
"AnchorGridControl",
|
|
31
|
+
"ChannelMixerControl",
|
|
32
|
+
"CheckboxControl",
|
|
33
|
+
"CodeTextareaControl",
|
|
34
|
+
"ColorControl",
|
|
35
|
+
"ColorOpacityControl",
|
|
36
|
+
"CurvesControl",
|
|
37
|
+
"FileDropControl",
|
|
38
|
+
"FontPickerControl",
|
|
39
|
+
"GradientControl",
|
|
40
|
+
"ImagePickerControl",
|
|
41
|
+
"PaletteControl",
|
|
42
|
+
"PanelActionsControl",
|
|
43
|
+
"RangeInputControl",
|
|
44
|
+
"RangeSliderControl",
|
|
45
|
+
"SegmentedControl",
|
|
46
|
+
"SelectControl",
|
|
47
|
+
"SliderControl",
|
|
48
|
+
"SwitchControl",
|
|
49
|
+
"TextInputControl",
|
|
50
|
+
"VectorControl",
|
|
51
|
+
];
|
|
52
|
+
const builtInControlNamePattern = builtInControlComponentNames.join("|");
|
|
53
|
+
const repoUiImportPattern = "@repo" + "/ui";
|
|
54
|
+
const builtInControlImportSourcePattern = ["toolcraft/ui", repoUiImportPattern].join("|");
|
|
55
|
+
const builtInControlImportPattern = new RegExp(
|
|
56
|
+
`import\\s*\\{[^}]*\\b(?:${builtInControlNamePattern})\\b[^}]*\\}\\s*from\\s*["'][^"']*(?:${builtInControlImportSourcePattern})[^"']*["']`,
|
|
57
|
+
);
|
|
58
|
+
const directBuiltInControlRenderPattern = new RegExp(
|
|
59
|
+
`<\\s*(?:(?:${builtInControlNamePattern})\\b|[A-Za-z_$][\\w$]*\\.(?:${builtInControlNamePattern})\\b)|React\\.createElement\\s*\\(\\s*(?:(?:${builtInControlNamePattern})\\b|[A-Za-z_$][\\w$]*\\.(?:${builtInControlNamePattern})\\b)`,
|
|
60
|
+
);
|
|
61
|
+
|
|
62
|
+
async function pathExists(filePath) {
|
|
63
|
+
try {
|
|
64
|
+
await fs.access(filePath);
|
|
65
|
+
return true;
|
|
66
|
+
} catch {
|
|
67
|
+
return false;
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
async function hashFile(filePath) {
|
|
72
|
+
const buffer = await fs.readFile(filePath);
|
|
73
|
+
return crypto.createHash("sha256").update(buffer).digest("hex");
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
async function collectFiles(rootDir) {
|
|
77
|
+
const files = [];
|
|
78
|
+
|
|
79
|
+
async function visit(currentDir) {
|
|
80
|
+
const entries = await fs.readdir(currentDir, { withFileTypes: true });
|
|
81
|
+
|
|
82
|
+
for (const entry of entries) {
|
|
83
|
+
const filePath = path.join(currentDir, entry.name);
|
|
84
|
+
|
|
85
|
+
if (entry.isDirectory()) {
|
|
86
|
+
await visit(filePath);
|
|
87
|
+
continue;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
if (entry.isFile() && entry.name !== ".toolcraft-manifest.json") {
|
|
91
|
+
files.push(path.relative(rootDir, filePath).split(path.sep).join("/"));
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
await visit(rootDir);
|
|
97
|
+
return files.sort();
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
function stripJsComments(source) {
|
|
101
|
+
return source
|
|
102
|
+
.replace(/\/\*[\s\S]*?\*\//g, "")
|
|
103
|
+
.replace(/(^|[^:])\/\/.*$/gm, "$1");
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
async function collectSourceText(rootDir, { excludeTests = false } = {}) {
|
|
107
|
+
if (!(await pathExists(rootDir))) {
|
|
108
|
+
return "";
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
const chunks = [];
|
|
112
|
+
|
|
113
|
+
async function visit(currentDir) {
|
|
114
|
+
const entries = await fs.readdir(currentDir, { withFileTypes: true });
|
|
115
|
+
|
|
116
|
+
for (const entry of entries) {
|
|
117
|
+
const filePath = path.join(currentDir, entry.name);
|
|
118
|
+
|
|
119
|
+
if (entry.isDirectory()) {
|
|
120
|
+
await visit(filePath);
|
|
121
|
+
continue;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
if (!entry.isFile() || !/\.[cm]?[jt]sx?$/.test(entry.name)) {
|
|
125
|
+
continue;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
if (excludeTests && /\.(test|spec)\.[cm]?[jt]sx?$/.test(entry.name)) {
|
|
129
|
+
continue;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
chunks.push(await fs.readFile(filePath, "utf8"));
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
await visit(rootDir);
|
|
137
|
+
return stripJsComments(chunks.join("\n"));
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
if (!(await pathExists(manifestPath))) {
|
|
141
|
+
console.log("Toolcraft integrity manifest not found; skipping copied source check.");
|
|
142
|
+
process.exit(0);
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
const manifest = JSON.parse(await fs.readFile(manifestPath, "utf8"));
|
|
146
|
+
const expectedFiles = new Map(
|
|
147
|
+
Object.entries(manifest.files ?? {}).map(([relativePath, hash]) => [
|
|
148
|
+
relativePath,
|
|
149
|
+
String(hash),
|
|
150
|
+
]),
|
|
151
|
+
);
|
|
152
|
+
const actualFiles = await collectFiles(toolcraftRoot);
|
|
153
|
+
const actualFileSet = new Set(actualFiles);
|
|
154
|
+
const failures = [];
|
|
155
|
+
const routesSource = await collectSourceText(routesSourceRoot, { excludeTests: true });
|
|
156
|
+
const implementationSource = [
|
|
157
|
+
routesSource,
|
|
158
|
+
await collectSourceText(appSourceRoot, { excludeTests: true }),
|
|
159
|
+
].join("\n");
|
|
160
|
+
|
|
161
|
+
if (!/\bToolcraftApp\b/.test(implementationSource)) {
|
|
162
|
+
failures.push(
|
|
163
|
+
"app shell bypass: src/routes or src/app must render ToolcraftApp instead of replacing the Toolcraft runtime shell",
|
|
164
|
+
);
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
if (lowLevelRuntimeSurfaceImportPattern.test(implementationSource)) {
|
|
168
|
+
failures.push(
|
|
169
|
+
"app shell bypass: app-specific source must not import low-level runtime surfaces; render ToolcraftApp and use schema, canvasContent, controlRenderers, onPanelAction, and runtime commands",
|
|
170
|
+
);
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
if (manualRuntimeSurfaceRenderPattern.test(implementationSource)) {
|
|
174
|
+
failures.push(
|
|
175
|
+
"app shell bypass: app-specific source must not render low-level runtime surfaces such as CanvasShell, ControlsPanel, TimelinePanel, LayersPanel, or ToolbarPanel directly",
|
|
176
|
+
);
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
if (builtInControlImportPattern.test(implementationSource)) {
|
|
180
|
+
failures.push(
|
|
181
|
+
"control bypass: app-specific source must not import built-in Toolcraft control components directly; declare schema controls or register a true custom controlRenderer",
|
|
182
|
+
);
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
if (directBuiltInControlRenderPattern.test(implementationSource)) {
|
|
186
|
+
failures.push(
|
|
187
|
+
"control bypass: app-specific source must not render built-in Toolcraft control components directly; declare schema controls or register a true custom controlRenderer",
|
|
188
|
+
);
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
if (/<\s*iframe\b|React\.createElement\s*\(\s*["']iframe["']/i.test(routesSource)) {
|
|
192
|
+
failures.push(
|
|
193
|
+
"app shell bypass: route files must not render a full-page iframe; preserve reference behavior inside ToolcraftApp canvasContent",
|
|
194
|
+
);
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
for (const relativePath of expectedFiles.keys()) {
|
|
198
|
+
if (!actualFileSet.has(relativePath)) {
|
|
199
|
+
failures.push(`deleted ${relativePath}`);
|
|
200
|
+
continue;
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
const actualHash = await hashFile(path.join(toolcraftRoot, relativePath));
|
|
204
|
+
|
|
205
|
+
if (actualHash !== expectedFiles.get(relativePath)) {
|
|
206
|
+
failures.push(`modified ${relativePath}`);
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
for (const relativePath of actualFiles) {
|
|
211
|
+
if (!expectedFiles.has(relativePath)) {
|
|
212
|
+
failures.push(`added ${relativePath}`);
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
if (failures.length > 0) {
|
|
217
|
+
console.error("Toolcraft generated app integrity check failed.");
|
|
218
|
+
console.error(
|
|
219
|
+
"Do not edit src/toolcraft or replace the Toolcraft runtime shell in generated apps.",
|
|
220
|
+
);
|
|
221
|
+
console.error(
|
|
222
|
+
"Fix the app schema/source runtime in the monorepo, preserve ToolcraftApp, then regenerate.",
|
|
223
|
+
);
|
|
224
|
+
|
|
225
|
+
for (const failure of failures) {
|
|
226
|
+
console.error(`- ${failure}`);
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
process.exit(1);
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
console.log(`Toolcraft integrity check passed (${actualFiles.length} files).`);
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
import { spawn } from "node:child_process";
|
|
4
|
+
|
|
5
|
+
import { findAvailablePort, readPreferredPort } from "./toolcraft-port.mjs";
|
|
6
|
+
|
|
7
|
+
const viteCommand = process.argv[2] ?? "dev";
|
|
8
|
+
const passthroughArgs = process.argv.slice(3).filter((arg) => arg !== "--");
|
|
9
|
+
const preferredPort = readPreferredPort([
|
|
10
|
+
"TOOLCRAFT_DEV_PORT",
|
|
11
|
+
"TOOLCRAFT_PORT",
|
|
12
|
+
"PORT",
|
|
13
|
+
]);
|
|
14
|
+
const port = await findAvailablePort(preferredPort);
|
|
15
|
+
|
|
16
|
+
if (port !== preferredPort) {
|
|
17
|
+
console.log(`[toolcraft] Port ${preferredPort} is busy; using ${port} instead.`);
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
const pnpmCommand = process.platform === "win32" ? "pnpm.cmd" : "pnpm";
|
|
21
|
+
const child = spawn(pnpmCommand, ["exec", "vite", viteCommand, "--port", String(port), ...passthroughArgs], {
|
|
22
|
+
env: {
|
|
23
|
+
...process.env,
|
|
24
|
+
TOOLCRAFT_ACTIVE_PORT: String(port),
|
|
25
|
+
PORT: String(port),
|
|
26
|
+
},
|
|
27
|
+
stdio: "inherit",
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
const signalExitCodes = {
|
|
31
|
+
SIGINT: 130,
|
|
32
|
+
SIGTERM: 143,
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
for (const signal of ["SIGINT", "SIGTERM"]) {
|
|
36
|
+
process.on(signal, () => {
|
|
37
|
+
child.kill(signal);
|
|
38
|
+
});
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
child.on("exit", (code, signal) => {
|
|
42
|
+
if (signal) {
|
|
43
|
+
process.exit(signalExitCodes[signal] ?? 1);
|
|
44
|
+
return;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
process.exit(code ?? 0);
|
|
48
|
+
});
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import net from "node:net";
|
|
2
|
+
|
|
3
|
+
export const DEFAULT_TOOLCRAFT_PORT = 3002;
|
|
4
|
+
const LOOPBACK_HOSTS = ["127.0.0.1", "::1"];
|
|
5
|
+
|
|
6
|
+
export function readPreferredPort(names, fallback = DEFAULT_TOOLCRAFT_PORT, env = process.env) {
|
|
7
|
+
for (const name of names) {
|
|
8
|
+
const value = env[name];
|
|
9
|
+
|
|
10
|
+
if (value == null || value === "") {
|
|
11
|
+
continue;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
const port = Number(value);
|
|
15
|
+
|
|
16
|
+
if (Number.isInteger(port) && port > 0 && port <= 65_535) {
|
|
17
|
+
return port;
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
return fallback;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export function isPortAvailable(port, host) {
|
|
25
|
+
return new Promise((resolve) => {
|
|
26
|
+
const server = net.createServer();
|
|
27
|
+
|
|
28
|
+
server.unref();
|
|
29
|
+
server.once("error", () => {
|
|
30
|
+
resolve(false);
|
|
31
|
+
});
|
|
32
|
+
server.listen({ host, port }, () => {
|
|
33
|
+
server.close(() => {
|
|
34
|
+
resolve(true);
|
|
35
|
+
});
|
|
36
|
+
});
|
|
37
|
+
});
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export async function isPortAvailableOnLoopback(port) {
|
|
41
|
+
const results = await Promise.all(LOOPBACK_HOSTS.map((host) => isPortAvailable(port, host)));
|
|
42
|
+
|
|
43
|
+
return results.every(Boolean);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export async function findAvailablePort(startPort = DEFAULT_TOOLCRAFT_PORT) {
|
|
47
|
+
for (let port = startPort; port <= 65_535; port += 1) {
|
|
48
|
+
if (await isPortAvailableOnLoopback(port)) {
|
|
49
|
+
return port;
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
throw new Error(`No free port found at or above ${startPort}.`);
|
|
54
|
+
}
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import assert from "node:assert/strict";
|
|
2
|
+
import net from "node:net";
|
|
3
|
+
import test from "node:test";
|
|
4
|
+
|
|
5
|
+
import { findAvailablePort } from "./toolcraft-port.mjs";
|
|
6
|
+
|
|
7
|
+
function listen(host) {
|
|
8
|
+
return new Promise((resolve, reject) => {
|
|
9
|
+
const server = net.createServer();
|
|
10
|
+
|
|
11
|
+
server.once("error", reject);
|
|
12
|
+
server.listen({ host, port: 0 }, () => {
|
|
13
|
+
resolve(server);
|
|
14
|
+
});
|
|
15
|
+
});
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
function close(server) {
|
|
19
|
+
return new Promise((resolve, reject) => {
|
|
20
|
+
server.close((error) => {
|
|
21
|
+
if (error) {
|
|
22
|
+
reject(error);
|
|
23
|
+
return;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
resolve();
|
|
27
|
+
});
|
|
28
|
+
});
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
test("findAvailablePort skips ports occupied on IPv6 localhost", async (t) => {
|
|
32
|
+
let server;
|
|
33
|
+
|
|
34
|
+
try {
|
|
35
|
+
server = await listen("::1");
|
|
36
|
+
} catch (error) {
|
|
37
|
+
if (error?.code === "EAFNOSUPPORT" || error?.code === "EADDRNOTAVAIL") {
|
|
38
|
+
t.skip("IPv6 localhost is not available in this environment.");
|
|
39
|
+
return;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
throw error;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
t.after(() => close(server));
|
|
46
|
+
|
|
47
|
+
const address = server.address();
|
|
48
|
+
assert.equal(typeof address, "object");
|
|
49
|
+
|
|
50
|
+
const selectedPort = await findAvailablePort(address.port);
|
|
51
|
+
|
|
52
|
+
assert.notEqual(
|
|
53
|
+
selectedPort,
|
|
54
|
+
address.port,
|
|
55
|
+
"A port occupied on ::1 must be treated as unavailable for localhost URLs.",
|
|
56
|
+
);
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
test("findAvailablePort skips ports occupied on IPv4 localhost", async (t) => {
|
|
60
|
+
const server = await listen("127.0.0.1");
|
|
61
|
+
t.after(() => close(server));
|
|
62
|
+
|
|
63
|
+
const address = server.address();
|
|
64
|
+
assert.equal(typeof address, "object");
|
|
65
|
+
|
|
66
|
+
const selectedPort = await findAvailablePort(address.port);
|
|
67
|
+
|
|
68
|
+
assert.notEqual(
|
|
69
|
+
selectedPort,
|
|
70
|
+
address.port,
|
|
71
|
+
"A port occupied on 127.0.0.1 must be treated as unavailable for localhost URLs.",
|
|
72
|
+
);
|
|
73
|
+
});
|