@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.
Files changed (257) hide show
  1. package/LICENSE.md +98 -0
  2. package/README.md +41 -0
  3. package/bin/create-toolcraft-app.mjs +8 -0
  4. package/bin/toolcraft.mjs +8 -0
  5. package/package.json +24 -0
  6. package/scripts/prepare-pack.mjs +29 -0
  7. package/src/cli.mjs +392 -0
  8. package/src/cli.test.mjs +284 -0
  9. package/src/copy-recursive.mjs +86 -0
  10. package/src/generate.mjs +212 -0
  11. package/src/generate.test.mjs +322 -0
  12. package/src/import-map.mjs +14 -0
  13. package/src/package-json.mjs +80 -0
  14. package/src/package-json.test.mjs +67 -0
  15. package/src/rewrite-imports.mjs +85 -0
  16. package/src/rewrite-imports.test.mjs +58 -0
  17. package/templates/runtime/contracts/component-contracts.test.ts +1165 -0
  18. package/templates/runtime/contracts/component-contracts.ts +1340 -0
  19. package/templates/runtime/contracts/decision-contracts.test.ts +206 -0
  20. package/templates/runtime/contracts/decision-contracts.ts +283 -0
  21. package/templates/runtime/contracts/index.test.ts +14 -0
  22. package/templates/runtime/contracts/index.ts +3 -0
  23. package/templates/runtime/contracts/types.ts +56 -0
  24. package/templates/runtime/export/export.test.ts +203 -0
  25. package/templates/runtime/export/export.ts +132 -0
  26. package/templates/runtime/export/index.ts +1 -0
  27. package/templates/runtime/index.ts +14 -0
  28. package/templates/runtime/react/canvas-shell.test.tsx +424 -0
  29. package/templates/runtime/react/canvas-shell.tsx +408 -0
  30. package/templates/runtime/react/control-renderers.ts +31 -0
  31. package/templates/runtime/react/controls-panel.test.tsx +3736 -0
  32. package/templates/runtime/react/controls-panel.tsx +2327 -0
  33. package/templates/runtime/react/curve-geometry.test.ts +70 -0
  34. package/templates/runtime/react/index.ts +15 -0
  35. package/templates/runtime/react/layer-tree.ts +96 -0
  36. package/templates/runtime/react/layers-panel.test.tsx +487 -0
  37. package/templates/runtime/react/layers-panel.tsx +1348 -0
  38. package/templates/runtime/react/media-file.ts +82 -0
  39. package/templates/runtime/react/panel-host-config.ts +80 -0
  40. package/templates/runtime/react/panel-host-geometry.test.ts +66 -0
  41. package/templates/runtime/react/panel-host-geometry.ts +109 -0
  42. package/templates/runtime/react/panel-host-types.ts +74 -0
  43. package/templates/runtime/react/panel-host.test.tsx +102 -0
  44. package/templates/runtime/react/panel-host.tsx +353 -0
  45. package/templates/runtime/react/runtime-public-api.test.tsx +132 -0
  46. package/templates/runtime/react/settings-transfer.test.ts +150 -0
  47. package/templates/runtime/react/settings-transfer.ts +279 -0
  48. package/templates/runtime/react/storage-key-migration.ts +48 -0
  49. package/templates/runtime/react/theme-runtime.tsx +177 -0
  50. package/templates/runtime/react/timeline-panel.test.tsx +668 -0
  51. package/templates/runtime/react/timeline-panel.tsx +2953 -0
  52. package/templates/runtime/react/toolbar-panel.test.tsx +212 -0
  53. package/templates/runtime/react/toolbar-panel.tsx +205 -0
  54. package/templates/runtime/react/toolcraft-app.integration.test.tsx +350 -0
  55. package/templates/runtime/react/toolcraft-app.test.tsx +339 -0
  56. package/templates/runtime/react/toolcraft-app.tsx +81 -0
  57. package/templates/runtime/react/toolcraft-root.test.tsx +347 -0
  58. package/templates/runtime/react/toolcraft-root.tsx +203 -0
  59. package/templates/runtime/react/use-toolcraft.ts +41 -0
  60. package/templates/runtime/schema/define-toolcraft.test.ts +1524 -0
  61. package/templates/runtime/schema/define-toolcraft.ts +1442 -0
  62. package/templates/runtime/schema/keyframe-capability.test.ts +90 -0
  63. package/templates/runtime/schema/keyframe-capability.ts +51 -0
  64. package/templates/runtime/schema/runtime-targets.ts +40 -0
  65. package/templates/runtime/schema/types.ts +370 -0
  66. package/templates/runtime/state/canvas-zoom.ts +8 -0
  67. package/templates/runtime/state/create-template-state.test.ts +242 -0
  68. package/templates/runtime/state/create-template-state.ts +95 -0
  69. package/templates/runtime/state/keyframe-evaluation.test.ts +141 -0
  70. package/templates/runtime/state/keyframe-evaluation.ts +203 -0
  71. package/templates/runtime/state/persistence.test.ts +217 -0
  72. package/templates/runtime/state/persistence.ts +511 -0
  73. package/templates/runtime/state/reducer.test.ts +937 -0
  74. package/templates/runtime/state/reducer.ts +1212 -0
  75. package/templates/runtime/state/timeline-readiness.ts +43 -0
  76. package/templates/runtime/state/types.ts +242 -0
  77. package/templates/runtime/styles.css +125 -0
  78. package/templates/runtime/testing/performance.test.ts +1058 -0
  79. package/templates/runtime/testing/performance.ts +1078 -0
  80. package/templates/starter/AGENTS.md +186 -0
  81. package/templates/starter/LICENSE.md +98 -0
  82. package/templates/starter/NOTICE.md +8 -0
  83. package/templates/starter/docs/toolcraft/README.md +41 -0
  84. package/templates/starter/docs/toolcraft/acceptance-testing.md +205 -0
  85. package/templates/starter/docs/toolcraft/agent-worklog.md +81 -0
  86. package/templates/starter/docs/toolcraft/assembly-workflow.md +206 -0
  87. package/templates/starter/docs/toolcraft/component-rules.md +299 -0
  88. package/templates/starter/docs/toolcraft/custom-controls.md +71 -0
  89. package/templates/starter/docs/toolcraft/decision-contract.md +71 -0
  90. package/templates/starter/docs/toolcraft/performance.md +112 -0
  91. package/templates/starter/docs/toolcraft/renderer-technique.md +48 -0
  92. package/templates/starter/docs/toolcraft/schema-reference.md +265 -0
  93. package/templates/starter/docs/toolcraft/workflow.md +87 -0
  94. package/templates/starter/e2e/app-browser-acceptance.spec.ts +785 -0
  95. package/templates/starter/e2e/app-controls.spec.ts +41 -0
  96. package/templates/starter/e2e/app-performance.spec.ts +326 -0
  97. package/templates/starter/e2e/canvas-handle-helpers.ts +244 -0
  98. package/templates/starter/e2e/performance-helpers.ts +612 -0
  99. package/templates/starter/e2e/product-observable-helpers.ts +170 -0
  100. package/templates/starter/index.html +12 -0
  101. package/templates/starter/package.json +52 -0
  102. package/templates/starter/playwright.config.ts +43 -0
  103. package/templates/starter/scripts/check-ai-skills.mjs +95 -0
  104. package/templates/starter/scripts/check-toolcraft-docs.mjs +159 -0
  105. package/templates/starter/scripts/check-toolcraft-integrity.mjs +232 -0
  106. package/templates/starter/scripts/run-vite-on-free-port.mjs +48 -0
  107. package/templates/starter/scripts/toolcraft-port.mjs +54 -0
  108. package/templates/starter/scripts/toolcraft-port.test.mjs +73 -0
  109. package/templates/starter/src/app/starter-acceptance.test.ts +5959 -0
  110. package/templates/starter/src/app/starter-acceptance.ts +2646 -0
  111. package/templates/starter/src/app/starter-performance.test.ts +1390 -0
  112. package/templates/starter/src/app/starter-performance.ts +12 -0
  113. package/templates/starter/src/app/starter-schema.test.ts +70 -0
  114. package/templates/starter/src/app/starter-schema.ts +15 -0
  115. package/templates/starter/src/main.tsx +18 -0
  116. package/templates/starter/src/router.tsx +16 -0
  117. package/templates/starter/src/routes/index.tsx +7 -0
  118. package/templates/starter/src/routes/root.tsx +19 -0
  119. package/templates/starter/src/styles.css +120 -0
  120. package/templates/starter/tsconfig.json +11 -0
  121. package/templates/starter/vite.config.ts +13 -0
  122. package/templates/ui/components/composites/accordion.tsx +73 -0
  123. package/templates/ui/components/composites/alert-dialog.tsx +190 -0
  124. package/templates/ui/components/composites/alert.tsx +74 -0
  125. package/templates/ui/components/composites/aspect-ratio.tsx +22 -0
  126. package/templates/ui/components/composites/avatar.tsx +98 -0
  127. package/templates/ui/components/composites/badge.tsx +69 -0
  128. package/templates/ui/components/composites/breadcrumb.tsx +106 -0
  129. package/templates/ui/components/composites/card.tsx +91 -0
  130. package/templates/ui/components/composites/combobox.tsx +486 -0
  131. package/templates/ui/components/composites/command.tsx +296 -0
  132. package/templates/ui/components/composites/context-menu.tsx +247 -0
  133. package/templates/ui/components/composites/dialog.tsx +282 -0
  134. package/templates/ui/components/composites/dropdown-menu.tsx +299 -0
  135. package/templates/ui/components/composites/empty.tsx +110 -0
  136. package/templates/ui/components/composites/hover-card.tsx +44 -0
  137. package/templates/ui/components/composites/index.ts +30 -0
  138. package/templates/ui/components/composites/menubar.tsx +214 -0
  139. package/templates/ui/components/composites/navigation-menu.tsx +167 -0
  140. package/templates/ui/components/composites/pagination.tsx +131 -0
  141. package/templates/ui/components/composites/progress.tsx +72 -0
  142. package/templates/ui/components/composites/radio-group.tsx +84 -0
  143. package/templates/ui/components/composites/resizable.tsx +42 -0
  144. package/templates/ui/components/composites/sheet.tsx +153 -0
  145. package/templates/ui/components/composites/sidebar-structural.tsx +310 -0
  146. package/templates/ui/components/composites/sidebar.tsx +431 -0
  147. package/templates/ui/components/composites/sonner.tsx +35 -0
  148. package/templates/ui/components/composites/spinner.tsx +43 -0
  149. package/templates/ui/components/composites/table.tsx +108 -0
  150. package/templates/ui/components/composites/tabs.tsx +83 -0
  151. package/templates/ui/components/control-layout/index.tsx +437 -0
  152. package/templates/ui/components/controls/actions/actions-control.tsx +139 -0
  153. package/templates/ui/components/controls/actions/index.ts +9 -0
  154. package/templates/ui/components/controls/anchor-grid/anchor-grid-control.tsx +107 -0
  155. package/templates/ui/components/controls/anchor-grid/index.ts +4 -0
  156. package/templates/ui/components/controls/boolean/boolean-controls.tsx +79 -0
  157. package/templates/ui/components/controls/boolean/index.ts +4 -0
  158. package/templates/ui/components/controls/channel-mixer/channel-mixer-control.tsx +95 -0
  159. package/templates/ui/components/controls/channel-mixer/index.ts +4 -0
  160. package/templates/ui/components/controls/channel-tabs/channel-tabs.tsx +42 -0
  161. package/templates/ui/components/controls/channel-tabs/index.ts +6 -0
  162. package/templates/ui/components/controls/code-textarea/code-textarea-control.tsx +90 -0
  163. package/templates/ui/components/controls/code-textarea/index.ts +4 -0
  164. package/templates/ui/components/controls/color/color-control.tsx +571 -0
  165. package/templates/ui/components/controls/color/color-picker-popover.tsx +104 -0
  166. package/templates/ui/components/controls/color/index.ts +41 -0
  167. package/templates/ui/components/controls/color/palette-control-data.ts +436 -0
  168. package/templates/ui/components/controls/color/palette-control.tsx +535 -0
  169. package/templates/ui/components/controls/color/style-guide-color-picker-channel-utils.ts +162 -0
  170. package/templates/ui/components/controls/color/style-guide-color-picker-interactions.ts +190 -0
  171. package/templates/ui/components/controls/color/style-guide-color-picker-logic.ts +485 -0
  172. package/templates/ui/components/controls/color/style-guide-color-picker-parts.tsx +710 -0
  173. package/templates/ui/components/controls/color/style-guide-color-picker.tsx +503 -0
  174. package/templates/ui/components/controls/control-types.ts +43 -0
  175. package/templates/ui/components/controls/curves/curve-geometry.ts +355 -0
  176. package/templates/ui/components/controls/curves/curve-graph.tsx +390 -0
  177. package/templates/ui/components/controls/curves/curves-control.tsx +445 -0
  178. package/templates/ui/components/controls/curves/index.ts +6 -0
  179. package/templates/ui/components/controls/file-drop/file-drop-control.tsx +191 -0
  180. package/templates/ui/components/controls/file-drop/index.ts +5 -0
  181. package/templates/ui/components/controls/font-picker/font-catalog.json +15360 -0
  182. package/templates/ui/components/controls/font-picker/font-catalog.ts +116 -0
  183. package/templates/ui/components/controls/font-picker/font-picker-control.tsx +1202 -0
  184. package/templates/ui/components/controls/font-picker/font-preview-loader.ts +336 -0
  185. package/templates/ui/components/controls/font-picker/index.ts +24 -0
  186. package/templates/ui/components/controls/font-picker/use-hover-intent.ts +46 -0
  187. package/templates/ui/components/controls/gradient/gradient-control-utils.ts +190 -0
  188. package/templates/ui/components/controls/gradient/gradient-control.tsx +612 -0
  189. package/templates/ui/components/controls/gradient/gradient-stop-list.tsx +400 -0
  190. package/templates/ui/components/controls/gradient/gradient-toolbar.tsx +152 -0
  191. package/templates/ui/components/controls/gradient/index.ts +4 -0
  192. package/templates/ui/components/controls/image-picker/image-picker-control.tsx +139 -0
  193. package/templates/ui/components/controls/image-picker/index.ts +7 -0
  194. package/templates/ui/components/controls/index.ts +192 -0
  195. package/templates/ui/components/controls/range-input/index.ts +4 -0
  196. package/templates/ui/components/controls/range-input/range-input-control.tsx +173 -0
  197. package/templates/ui/components/controls/range-slider/index.ts +4 -0
  198. package/templates/ui/components/controls/range-slider/range-slider-control.tsx +122 -0
  199. package/templates/ui/components/controls/range-slider/range-slider-value.ts +61 -0
  200. package/templates/ui/components/controls/segmented/index.ts +8 -0
  201. package/templates/ui/components/controls/segmented/segmented-control.tsx +94 -0
  202. package/templates/ui/components/controls/select/index.ts +4 -0
  203. package/templates/ui/components/controls/select/select-control.tsx +223 -0
  204. package/templates/ui/components/controls/slider/index.ts +4 -0
  205. package/templates/ui/components/controls/slider/slider-control.tsx +150 -0
  206. package/templates/ui/components/controls/slider/slider-value.ts +56 -0
  207. package/templates/ui/components/controls/text-input/index.ts +4 -0
  208. package/templates/ui/components/controls/text-input/text-input-control.tsx +158 -0
  209. package/templates/ui/components/controls/use-measured-element-width.ts +42 -0
  210. package/templates/ui/components/controls/vector/index.ts +8 -0
  211. package/templates/ui/components/controls/vector/vector-control.tsx +401 -0
  212. package/templates/ui/components/panel/index.ts +19 -0
  213. package/templates/ui/components/panel/panel-actions.tsx +165 -0
  214. package/templates/ui/components/panel/panel-header.tsx +61 -0
  215. package/templates/ui/components/panel/panel-icon-button.tsx +96 -0
  216. package/templates/ui/components/panel/panel-section.tsx +168 -0
  217. package/templates/ui/components/panel/panel-surface.tsx +206 -0
  218. package/templates/ui/components/panel/panel.tsx +210 -0
  219. package/templates/ui/components/primitives/animated-loader.tsx +61 -0
  220. package/templates/ui/components/primitives/button-group.tsx +134 -0
  221. package/templates/ui/components/primitives/button.tsx +429 -0
  222. package/templates/ui/components/primitives/checkbox.tsx +62 -0
  223. package/templates/ui/components/primitives/editable-slider-value-label.tsx +337 -0
  224. package/templates/ui/components/primitives/field.tsx +225 -0
  225. package/templates/ui/components/primitives/index.ts +82 -0
  226. package/templates/ui/components/primitives/input-group.tsx +298 -0
  227. package/templates/ui/components/primitives/input.tsx +61 -0
  228. package/templates/ui/components/primitives/internal/button-loading.tsx +178 -0
  229. package/templates/ui/components/primitives/label.tsx +16 -0
  230. package/templates/ui/components/primitives/popover.tsx +126 -0
  231. package/templates/ui/components/primitives/portal-layer-context.tsx +33 -0
  232. package/templates/ui/components/primitives/primitive-arrow-icon.tsx +38 -0
  233. package/templates/ui/components/primitives/scroll-fade-logic.ts +441 -0
  234. package/templates/ui/components/primitives/scroll-fade-render.tsx +75 -0
  235. package/templates/ui/components/primitives/scroll-fade-types.ts +41 -0
  236. package/templates/ui/components/primitives/scroll-fade.tsx +72 -0
  237. package/templates/ui/components/primitives/select.tsx +408 -0
  238. package/templates/ui/components/primitives/selection-state.ts +31 -0
  239. package/templates/ui/components/primitives/separator.tsx +21 -0
  240. package/templates/ui/components/primitives/slider/index.ts +4 -0
  241. package/templates/ui/components/primitives/slider/slider-interaction.tsx +96 -0
  242. package/templates/ui/components/primitives/slider/slider-parts.tsx +303 -0
  243. package/templates/ui/components/primitives/slider/slider-reset.ts +152 -0
  244. package/templates/ui/components/primitives/slider/slider-value.ts +114 -0
  245. package/templates/ui/components/primitives/slider/slider.tsx +511 -0
  246. package/templates/ui/components/primitives/switch.tsx +35 -0
  247. package/templates/ui/components/primitives/textarea.tsx +49 -0
  248. package/templates/ui/components/primitives/toggle-group.tsx +114 -0
  249. package/templates/ui/components/primitives/toggle.tsx +46 -0
  250. package/templates/ui/components/primitives/tooltip.tsx +100 -0
  251. package/templates/ui/hooks/use-mobile.ts +21 -0
  252. package/templates/ui/index.ts +31 -0
  253. package/templates/ui/lib/control-outline.ts +3 -0
  254. package/templates/ui/lib/input-control-style.ts +131 -0
  255. package/templates/ui/lib/style-guide-color-utils.ts +111 -0
  256. package/templates/ui/lib/utils.ts +6 -0
  257. 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
+ ```
@@ -0,0 +1,8 @@
1
+ #!/usr/bin/env node
2
+
3
+ import { runToolcraftCli } from "../src/cli.mjs";
4
+
5
+ await runToolcraftCli(["create", ...process.argv.slice(2)], {
6
+ cwd: process.cwd(),
7
+ env: process.env,
8
+ });
@@ -0,0 +1,8 @@
1
+ #!/usr/bin/env node
2
+
3
+ import { runToolcraftCli } from "../src/cli.mjs";
4
+
5
+ await runToolcraftCli(process.argv.slice(2), {
6
+ cwd: process.cwd(),
7
+ env: process.env,
8
+ });
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
+ }