@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
package/LICENSE.md
ADDED
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
# Toolcraft Designer License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Pixel Point
|
|
4
|
+
|
|
5
|
+
This license governs Toolcraft, including the CLI, starter template, runtime,
|
|
6
|
+
UI components, documentation, and any generated application code that includes
|
|
7
|
+
Toolcraft source code.
|
|
8
|
+
|
|
9
|
+
## 1. Permitted Designer Use
|
|
10
|
+
|
|
11
|
+
You may use Toolcraft for personal, educational, internal evaluation, and
|
|
12
|
+
designer client work.
|
|
13
|
+
|
|
14
|
+
Designer client work means using Toolcraft as part of paid design, development,
|
|
15
|
+
consulting, or creative services to create a custom application for a specific
|
|
16
|
+
client.
|
|
17
|
+
|
|
18
|
+
Designers, freelancers, agencies, and studios may deliver generated
|
|
19
|
+
applications to their clients, and those clients may use the delivered
|
|
20
|
+
application for their own business, website, marketing, internal workflows, or
|
|
21
|
+
product presentation.
|
|
22
|
+
|
|
23
|
+
## 2. Generated Applications
|
|
24
|
+
|
|
25
|
+
A generated application may be modified, deployed, and used by the designer or
|
|
26
|
+
the designer's client as a custom client deliverable.
|
|
27
|
+
|
|
28
|
+
You may not sell, sublicense, redistribute, publish, or offer generated
|
|
29
|
+
applications as standalone products, stock templates, starter kits, theme packs,
|
|
30
|
+
marketplace items, or reusable application templates.
|
|
31
|
+
|
|
32
|
+
## 3. AI-Assisted Development
|
|
33
|
+
|
|
34
|
+
You may use AI coding assistants, AI agents, design assistants, large language
|
|
35
|
+
models, editors, IDE assistants, and similar tools to create, inspect, modify,
|
|
36
|
+
refactor, test, document, deploy, or maintain generated applications for any use
|
|
37
|
+
permitted by this license.
|
|
38
|
+
|
|
39
|
+
This includes tools such as Codex, Claude, ChatGPT, Cursor, and similar
|
|
40
|
+
assistants. Using those tools to work on Toolcraft projects or generated
|
|
41
|
+
applications does not, by itself, make your use a prohibited AI software, AI app
|
|
42
|
+
builder, AI design tool, code generation platform, or competing generator tool.
|
|
43
|
+
|
|
44
|
+
This section does not allow you to package, embed, host, or provide Toolcraft or
|
|
45
|
+
generated applications as part of a paid AI software product, AI app builder, AI
|
|
46
|
+
design tool, code generation platform, or competing generator service.
|
|
47
|
+
|
|
48
|
+
## 4. Prohibited Uses
|
|
49
|
+
|
|
50
|
+
You may not use Toolcraft, generated applications, or any Toolcraft runtime,
|
|
51
|
+
starter, UI, or template code as part of:
|
|
52
|
+
|
|
53
|
+
- paid AI software;
|
|
54
|
+
- AI app builders;
|
|
55
|
+
- AI design tools;
|
|
56
|
+
- website builders;
|
|
57
|
+
- app builders;
|
|
58
|
+
- design-to-code SaaS products;
|
|
59
|
+
- code generation platforms;
|
|
60
|
+
- template marketplaces;
|
|
61
|
+
- competing generator tools;
|
|
62
|
+
- any product or service whose primary purpose is to let third parties generate,
|
|
63
|
+
resell, or reuse applications based on Toolcraft.
|
|
64
|
+
|
|
65
|
+
These uses require a separate commercial license from Pixel Point.
|
|
66
|
+
|
|
67
|
+
## 5. No Hosted Generator or Resale Rights
|
|
68
|
+
|
|
69
|
+
You may not provide Toolcraft or substantially similar functionality to third
|
|
70
|
+
parties as a hosted service, API, plugin, marketplace product, or downloadable
|
|
71
|
+
generator without a separate commercial license.
|
|
72
|
+
|
|
73
|
+
## 6. No Trademark Rights
|
|
74
|
+
|
|
75
|
+
This license does not grant rights to use the Pixel Point or Toolcraft names,
|
|
76
|
+
logos, branding, or trademarks except to identify Toolcraft as the tool used to
|
|
77
|
+
create a project.
|
|
78
|
+
|
|
79
|
+
## 7. Ownership
|
|
80
|
+
|
|
81
|
+
You own the original design, content, configuration, and product-specific code
|
|
82
|
+
that you create.
|
|
83
|
+
|
|
84
|
+
Pixel Point retains ownership of Toolcraft, including the CLI, starter template,
|
|
85
|
+
runtime, UI components, documentation, and copied Toolcraft source code included
|
|
86
|
+
in generated applications.
|
|
87
|
+
|
|
88
|
+
## 8. Commercial Licensing
|
|
89
|
+
|
|
90
|
+
If your intended use is not allowed by this license, you must obtain a separate
|
|
91
|
+
commercial license before using Toolcraft for that purpose.
|
|
92
|
+
|
|
93
|
+
Contact: licensing@pixel-point.com
|
|
94
|
+
|
|
95
|
+
## 9. No Warranty
|
|
96
|
+
|
|
97
|
+
Toolcraft is provided "as is", without warranty of any kind. Pixel Point is not
|
|
98
|
+
liable for damages arising from use of Toolcraft or generated applications.
|
package/README.md
ADDED
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
# Toolcraft CLI
|
|
2
|
+
|
|
3
|
+
Package entrypoint for creating standalone Toolcraft apps.
|
|
4
|
+
|
|
5
|
+
```bash
|
|
6
|
+
npx @pixel-point/toolcraft create
|
|
7
|
+
```
|
|
8
|
+
|
|
9
|
+
The create command uses the current directory when no target directory is passed, prompts for missing project values in an interactive terminal, generates the app, runs `pnpm install`, then prints the command to start the dev server.
|
|
10
|
+
|
|
11
|
+
## License
|
|
12
|
+
|
|
13
|
+
Toolcraft is distributed under the Toolcraft Designer License in `LICENSE.md`.
|
|
14
|
+
Designer client work is permitted under that license. Using AI coding assistants
|
|
15
|
+
or agents such as Codex, Claude, ChatGPT, Cursor, or similar tools to work on
|
|
16
|
+
generated apps is permitted. Platform, generator, AI software product,
|
|
17
|
+
app-builder, website-builder, template-marketplace, and resale uses require a
|
|
18
|
+
separate commercial license from Pixel Point.
|
|
19
|
+
|
|
20
|
+
Scripted usage:
|
|
21
|
+
|
|
22
|
+
```bash
|
|
23
|
+
npx @pixel-point/toolcraft create my-toolcraft-app --name my-toolcraft-app --yes --force
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
Local source test without publishing:
|
|
27
|
+
|
|
28
|
+
```bash
|
|
29
|
+
mkdir -p /tmp/toolcraft-local-cli-test
|
|
30
|
+
cd /tmp/toolcraft-local-cli-test
|
|
31
|
+
node /Users/alex/Projects/primeui-v2/cli/bin/toolcraft.mjs --name local-cli-test --yes --force --no-install
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
Local tarball test, matching the published package layout:
|
|
35
|
+
|
|
36
|
+
```bash
|
|
37
|
+
cd cli
|
|
38
|
+
npm pack --pack-destination /tmp
|
|
39
|
+
cd ..
|
|
40
|
+
TOOLCRAFT_SKIP_INSTALL=1 npm exec --package /tmp/pixel-point-toolcraft-0.0.0.tgz -- toolcraft create /tmp/toolcraft-pack-exec-test --name pack-exec-test --yes --force
|
|
41
|
+
```
|
package/package.json
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@pixel-point/toolcraft",
|
|
3
|
+
"version": "0.0.2",
|
|
4
|
+
"license": "SEE LICENSE IN LICENSE.md",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"bin": {
|
|
7
|
+
"toolcraft": "./bin/toolcraft.mjs",
|
|
8
|
+
"create-toolcraft-app": "./bin/create-toolcraft-app.mjs"
|
|
9
|
+
},
|
|
10
|
+
"files": [
|
|
11
|
+
"LICENSE.md",
|
|
12
|
+
"README.md",
|
|
13
|
+
"bin",
|
|
14
|
+
"scripts",
|
|
15
|
+
"src",
|
|
16
|
+
"templates",
|
|
17
|
+
"package.json"
|
|
18
|
+
],
|
|
19
|
+
"scripts": {
|
|
20
|
+
"prepack": "node scripts/prepare-pack.mjs",
|
|
21
|
+
"test": "node --test src/*.test.mjs",
|
|
22
|
+
"typecheck": "node --check bin/toolcraft.mjs && node --check bin/create-toolcraft-app.mjs && node --check scripts/*.mjs && node --check src/*.mjs"
|
|
23
|
+
}
|
|
24
|
+
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import path from "node:path";
|
|
2
|
+
import { fileURLToPath } from "node:url";
|
|
3
|
+
|
|
4
|
+
import { copyDirectory, removeDirectory } from "../src/copy-recursive.mjs";
|
|
5
|
+
|
|
6
|
+
const packageRoot = path.resolve(fileURLToPath(new URL("..", import.meta.url)));
|
|
7
|
+
const repoRoot = path.resolve(packageRoot, "..");
|
|
8
|
+
const templatesRoot = path.join(packageRoot, "templates");
|
|
9
|
+
|
|
10
|
+
const sources = [
|
|
11
|
+
{
|
|
12
|
+
from: path.join(repoRoot, "starter"),
|
|
13
|
+
to: path.join(templatesRoot, "starter"),
|
|
14
|
+
},
|
|
15
|
+
{
|
|
16
|
+
from: path.join(repoRoot, "packages/ui/src"),
|
|
17
|
+
to: path.join(templatesRoot, "ui"),
|
|
18
|
+
},
|
|
19
|
+
{
|
|
20
|
+
from: path.join(repoRoot, "packages/toolcraft-runtime/src"),
|
|
21
|
+
to: path.join(templatesRoot, "runtime"),
|
|
22
|
+
},
|
|
23
|
+
];
|
|
24
|
+
|
|
25
|
+
await removeDirectory(templatesRoot);
|
|
26
|
+
|
|
27
|
+
for (const source of sources) {
|
|
28
|
+
await copyDirectory(source.from, source.to);
|
|
29
|
+
}
|
package/src/cli.mjs
ADDED
|
@@ -0,0 +1,392 @@
|
|
|
1
|
+
import { spawn } from "node:child_process";
|
|
2
|
+
import fs from "node:fs/promises";
|
|
3
|
+
import path from "node:path";
|
|
4
|
+
import readline from "node:readline/promises";
|
|
5
|
+
|
|
6
|
+
import { pathExists } from "./copy-recursive.mjs";
|
|
7
|
+
import { generateToolcraft } from "./generate.mjs";
|
|
8
|
+
import { sanitizePackageName } from "./package-json.mjs";
|
|
9
|
+
|
|
10
|
+
const DEFAULT_PROJECT_NAME = "my-toolcraft-app";
|
|
11
|
+
|
|
12
|
+
function writeLine(stream, message = "") {
|
|
13
|
+
stream.write(`${message}\n`);
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
function writeBlock(stream, message) {
|
|
17
|
+
stream.write(message.endsWith("\n") ? message : `${message}\n`);
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
function getStdout(context) {
|
|
21
|
+
return context.stdout ?? process.stdout;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
function getStderr(context) {
|
|
25
|
+
return context.stderr ?? process.stderr;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
function getStdin(context) {
|
|
29
|
+
return context.stdin ?? process.stdin;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function setExitCode(context, exitCode) {
|
|
33
|
+
if (typeof context.setExitCode === "function") {
|
|
34
|
+
context.setExitCode(exitCode);
|
|
35
|
+
return;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
process.exitCode = exitCode;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
function canPrompt(context) {
|
|
42
|
+
const stdin = getStdin(context);
|
|
43
|
+
const stdout = getStdout(context);
|
|
44
|
+
return Boolean(context.forcePrompts || (stdin.isTTY && stdout.isTTY));
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
function createMissingValueError(valueName) {
|
|
48
|
+
return new Error(`${valueName} is required. Pass it with a flag, or run in an interactive terminal.`);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function defaultNameForTarget(cwd, targetDir) {
|
|
52
|
+
if (!targetDir) {
|
|
53
|
+
return sanitizePackageName(path.basename(cwd)) || DEFAULT_PROJECT_NAME;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
const basename = path.basename(path.resolve(cwd, targetDir));
|
|
57
|
+
return sanitizePackageName(basename === "." ? path.basename(cwd) : basename) || DEFAULT_PROJECT_NAME;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
async function promptText(context, label, defaultValue) {
|
|
61
|
+
if (context.prompts?.text) {
|
|
62
|
+
return context.prompts.text({ label, defaultValue });
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
if (!canPrompt(context)) {
|
|
66
|
+
throw createMissingValueError(label);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
const rl = readline.createInterface({
|
|
70
|
+
input: getStdin(context),
|
|
71
|
+
output: getStdout(context),
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
try {
|
|
75
|
+
const answer = await rl.question(`? ${label} (${defaultValue}): `);
|
|
76
|
+
return answer.trim() || defaultValue;
|
|
77
|
+
} finally {
|
|
78
|
+
rl.close();
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
async function promptConfirm(context, label, defaultValue = false) {
|
|
83
|
+
if (context.prompts?.confirm) {
|
|
84
|
+
return context.prompts.confirm({ label, defaultValue });
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
if (!canPrompt(context)) {
|
|
88
|
+
return defaultValue;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
const suffix = defaultValue ? "Y/n" : "y/N";
|
|
92
|
+
const rl = readline.createInterface({
|
|
93
|
+
input: getStdin(context),
|
|
94
|
+
output: getStdout(context),
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
try {
|
|
98
|
+
const answer = (await rl.question(`? ${label} (${suffix}): `)).trim().toLowerCase();
|
|
99
|
+
if (!answer) {
|
|
100
|
+
return defaultValue;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
return ["y", "yes"].includes(answer);
|
|
104
|
+
} finally {
|
|
105
|
+
rl.close();
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
async function directoryHasMeaningfulEntries(directoryPath) {
|
|
110
|
+
if (!(await pathExists(directoryPath))) {
|
|
111
|
+
return false;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
const stats = await fs.stat(directoryPath);
|
|
115
|
+
if (!stats.isDirectory()) {
|
|
116
|
+
return true;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
const entries = await fs.readdir(directoryPath);
|
|
120
|
+
return entries.some((entry) => entry !== ".DS_Store");
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
function readOptionValue(argv, index, optionName) {
|
|
124
|
+
const value = argv[index + 1];
|
|
125
|
+
if (!value || value.startsWith("-")) {
|
|
126
|
+
throw new Error(`${optionName} requires a value.`);
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
return value;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
export function parseCreateArgs(argv) {
|
|
133
|
+
const options = {
|
|
134
|
+
force: false,
|
|
135
|
+
help: false,
|
|
136
|
+
install: true,
|
|
137
|
+
name: undefined,
|
|
138
|
+
targetDir: undefined,
|
|
139
|
+
yes: false,
|
|
140
|
+
};
|
|
141
|
+
|
|
142
|
+
for (let index = 0; index < argv.length; index += 1) {
|
|
143
|
+
const arg = argv[index];
|
|
144
|
+
|
|
145
|
+
if (arg === "--help" || arg === "-h") {
|
|
146
|
+
options.help = true;
|
|
147
|
+
continue;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
if (arg === "--force" || arg === "-f") {
|
|
151
|
+
options.force = true;
|
|
152
|
+
continue;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
if (arg === "--yes" || arg === "-y") {
|
|
156
|
+
options.yes = true;
|
|
157
|
+
continue;
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
if (arg === "--no-install") {
|
|
161
|
+
options.install = false;
|
|
162
|
+
continue;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
if (arg === "--name") {
|
|
166
|
+
options.name = readOptionValue(argv, index, "--name");
|
|
167
|
+
index += 1;
|
|
168
|
+
continue;
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
if (arg === "--dir") {
|
|
172
|
+
if (options.targetDir) {
|
|
173
|
+
throw new Error("Pass either a positional target directory or --dir, not both.");
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
options.targetDir = readOptionValue(argv, index, "--dir");
|
|
177
|
+
index += 1;
|
|
178
|
+
continue;
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
if (arg.startsWith("-")) {
|
|
182
|
+
throw new Error(`Unknown option: ${arg}`);
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
if (options.targetDir) {
|
|
186
|
+
throw new Error(`Unexpected extra argument: ${arg}`);
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
options.targetDir = arg;
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
return options;
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
export async function resolveCreateOptions(parsedOptions, context = {}) {
|
|
196
|
+
const cwd = path.resolve(context.cwd ?? process.cwd());
|
|
197
|
+
let name = parsedOptions.name?.trim();
|
|
198
|
+
let targetDir = parsedOptions.targetDir?.trim();
|
|
199
|
+
let promptedForName = false;
|
|
200
|
+
|
|
201
|
+
if (!name) {
|
|
202
|
+
const defaultName = defaultNameForTarget(cwd, targetDir);
|
|
203
|
+
const shouldPrompt = !parsedOptions.yes && (context.prompts?.text || canPrompt(context));
|
|
204
|
+
if (shouldPrompt) {
|
|
205
|
+
name = await promptText(context, "Project name", defaultName);
|
|
206
|
+
promptedForName = true;
|
|
207
|
+
} else {
|
|
208
|
+
name = defaultName;
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
name = sanitizePackageName(name);
|
|
213
|
+
|
|
214
|
+
if (!targetDir) {
|
|
215
|
+
const shouldCreateNamedFolder =
|
|
216
|
+
promptedForName && (await directoryHasMeaningfulEntries(cwd));
|
|
217
|
+
targetDir = shouldCreateNamedFolder ? `./${name}` : ".";
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
targetDir = targetDir || ".";
|
|
221
|
+
const resolvedTargetDir = path.resolve(cwd, targetDir);
|
|
222
|
+
let force = parsedOptions.force;
|
|
223
|
+
|
|
224
|
+
if (!force && (await directoryHasMeaningfulEntries(resolvedTargetDir))) {
|
|
225
|
+
const shouldContinue =
|
|
226
|
+
!parsedOptions.yes &&
|
|
227
|
+
(await promptConfirm(context, "Directory is not empty. Continue?", false));
|
|
228
|
+
|
|
229
|
+
if (!shouldContinue) {
|
|
230
|
+
throw new Error(
|
|
231
|
+
`Target directory is not empty: ${resolvedTargetDir}. Use --force to write into it.`,
|
|
232
|
+
);
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
force = true;
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
return {
|
|
239
|
+
force,
|
|
240
|
+
install: parsedOptions.install && context.env?.TOOLCRAFT_SKIP_INSTALL !== "1",
|
|
241
|
+
name,
|
|
242
|
+
targetDir,
|
|
243
|
+
};
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
function runCommand(command, args, options = {}, context = {}) {
|
|
247
|
+
if (context.runCommand) {
|
|
248
|
+
return context.runCommand(command, args, options);
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
return new Promise((resolve, reject) => {
|
|
252
|
+
const child = spawn(command, args, {
|
|
253
|
+
cwd: options.cwd,
|
|
254
|
+
env: options.env,
|
|
255
|
+
shell: process.platform === "win32",
|
|
256
|
+
stdio: options.stdio ?? "inherit",
|
|
257
|
+
});
|
|
258
|
+
|
|
259
|
+
child.on("error", reject);
|
|
260
|
+
child.on("exit", (exitCode) => {
|
|
261
|
+
if (exitCode === 0) {
|
|
262
|
+
resolve();
|
|
263
|
+
return;
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
reject(new Error(`${command} ${args.join(" ")} failed with exit code ${exitCode}.`));
|
|
267
|
+
});
|
|
268
|
+
});
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
export async function runCreateCommand(parsedOptions, context = {}) {
|
|
272
|
+
const cwd = path.resolve(context.cwd ?? process.cwd());
|
|
273
|
+
const stdout = getStdout(context);
|
|
274
|
+
const createOptions = await resolveCreateOptions(parsedOptions, context);
|
|
275
|
+
|
|
276
|
+
const result = await generateToolcraft({
|
|
277
|
+
cwd,
|
|
278
|
+
force: createOptions.force,
|
|
279
|
+
name: createOptions.name,
|
|
280
|
+
targetDir: createOptions.targetDir,
|
|
281
|
+
});
|
|
282
|
+
|
|
283
|
+
if (createOptions.install) {
|
|
284
|
+
writeLine(stdout, "");
|
|
285
|
+
writeLine(stdout, "Installing dependencies with pnpm...");
|
|
286
|
+
await runCommand(
|
|
287
|
+
"pnpm",
|
|
288
|
+
["install"],
|
|
289
|
+
{
|
|
290
|
+
cwd: result.targetDir,
|
|
291
|
+
env: {
|
|
292
|
+
...process.env,
|
|
293
|
+
...(context.env ?? {}),
|
|
294
|
+
},
|
|
295
|
+
stdio: "inherit",
|
|
296
|
+
},
|
|
297
|
+
context,
|
|
298
|
+
);
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
const sameDirectory = result.targetDir === cwd;
|
|
302
|
+
|
|
303
|
+
writeLine(stdout, "");
|
|
304
|
+
writeLine(stdout, `Created ${result.packageName} at ${result.targetDir}`);
|
|
305
|
+
writeLine(stdout, "");
|
|
306
|
+
writeLine(stdout, "Next steps:");
|
|
307
|
+
if (!sameDirectory) {
|
|
308
|
+
const displayTargetDir = path.isAbsolute(createOptions.targetDir)
|
|
309
|
+
? result.targetDir
|
|
310
|
+
: result.relativeTargetDir;
|
|
311
|
+
writeLine(stdout, ` cd ${displayTargetDir}`);
|
|
312
|
+
}
|
|
313
|
+
writeLine(stdout, " pnpm dev");
|
|
314
|
+
|
|
315
|
+
return result;
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
export function getMainHelp() {
|
|
319
|
+
return `Usage:
|
|
320
|
+
npx @pixel-point/toolcraft [create] [target-dir] [options]
|
|
321
|
+
toolcraft [create] [target-dir] [options]
|
|
322
|
+
|
|
323
|
+
Commands:
|
|
324
|
+
create Create a standalone Toolcraft template app. This is the default command.
|
|
325
|
+
|
|
326
|
+
Options:
|
|
327
|
+
--help, -h Show this help message.`;
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
export function getCreateHelp() {
|
|
331
|
+
return `Usage:
|
|
332
|
+
npx @pixel-point/toolcraft create [target-dir] [options]
|
|
333
|
+
|
|
334
|
+
Creates a standalone Toolcraft template app. When target-dir is omitted, the current directory is used.
|
|
335
|
+
Missing project values are prompted in an interactive terminal.
|
|
336
|
+
|
|
337
|
+
Options:
|
|
338
|
+
--name <name> Package name for the generated app.
|
|
339
|
+
--dir <dir> Target directory for the generated app.
|
|
340
|
+
--force, -f Allow writing into a non-empty target folder.
|
|
341
|
+
--yes, -y Use defaults for missing values and skip prompts.
|
|
342
|
+
--no-install Skip automatic pnpm install. Intended for local CLI tests and automation.
|
|
343
|
+
--help, -h Show this help message.`;
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
export async function runToolcraftCli(argv, context = {}) {
|
|
347
|
+
const stdout = getStdout(context);
|
|
348
|
+
const stderr = getStderr(context);
|
|
349
|
+
|
|
350
|
+
try {
|
|
351
|
+
const [command, ...rest] = argv;
|
|
352
|
+
|
|
353
|
+
if (command === "--help" || command === "-h") {
|
|
354
|
+
writeBlock(stdout, getMainHelp());
|
|
355
|
+
return { ok: true };
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
if (!command || command.startsWith("-")) {
|
|
359
|
+
const createOptions = parseCreateArgs(argv);
|
|
360
|
+
if (createOptions.help) {
|
|
361
|
+
writeBlock(stdout, getMainHelp());
|
|
362
|
+
return { ok: true };
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
const result = await runCreateCommand(createOptions, context);
|
|
366
|
+
return { ok: true, result };
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
if (command !== "create") {
|
|
370
|
+
throw new Error(`Unknown command: ${command}`);
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
const createOptions = parseCreateArgs(rest);
|
|
374
|
+
if (createOptions.help) {
|
|
375
|
+
writeBlock(stdout, getCreateHelp());
|
|
376
|
+
return { ok: true };
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
const result = await runCreateCommand(createOptions, context);
|
|
380
|
+
return { ok: true, result };
|
|
381
|
+
} catch (error) {
|
|
382
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
383
|
+
writeLine(stderr, message);
|
|
384
|
+
|
|
385
|
+
if (context.throwOnError) {
|
|
386
|
+
throw error;
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
setExitCode(context, 1);
|
|
390
|
+
return { ok: false, error };
|
|
391
|
+
}
|
|
392
|
+
}
|