decantr 0.9.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (382) hide show
  1. package/AGENTS.md +868 -0
  2. package/CHANGELOG.md +255 -0
  3. package/CLAUDE.md +178 -0
  4. package/LICENSE +21 -0
  5. package/README.md +229 -0
  6. package/cli/art.js +127 -0
  7. package/cli/commands/a11y.js +61 -0
  8. package/cli/commands/audit.js +225 -0
  9. package/cli/commands/build.js +38 -0
  10. package/cli/commands/dev.js +18 -0
  11. package/cli/commands/doctor.js +197 -0
  12. package/cli/commands/figma-sync.js +48 -0
  13. package/cli/commands/figma-tokens.js +55 -0
  14. package/cli/commands/generate.js +26 -0
  15. package/cli/commands/init.js +116 -0
  16. package/cli/commands/lint.js +209 -0
  17. package/cli/commands/mcp.js +530 -0
  18. package/cli/commands/migrate.js +175 -0
  19. package/cli/commands/test.js +38 -0
  20. package/cli/commands/validate.js +354 -0
  21. package/cli/index.js +113 -0
  22. package/package.json +95 -0
  23. package/reference/atoms.md +517 -0
  24. package/reference/behaviors.md +384 -0
  25. package/reference/build-tooling.md +275 -0
  26. package/reference/color-guidelines.md +965 -0
  27. package/reference/component-lifecycle.md +137 -0
  28. package/reference/compound-spacing.md +95 -0
  29. package/reference/decantation-process.md +499 -0
  30. package/reference/dev-server-routes.md +93 -0
  31. package/reference/form-system.md +253 -0
  32. package/reference/i18n.md +336 -0
  33. package/reference/icons.md +576 -0
  34. package/reference/llm-primer.md +953 -0
  35. package/reference/plugins.md +252 -0
  36. package/reference/registry-consumption.md +76 -0
  37. package/reference/router.md +217 -0
  38. package/reference/shells.md +116 -0
  39. package/reference/spatial-guidelines.md +541 -0
  40. package/reference/ssr.md +234 -0
  41. package/reference/state-data.md +215 -0
  42. package/reference/state-patterns.md +166 -0
  43. package/reference/state.md +194 -0
  44. package/reference/style-system.md +110 -0
  45. package/reference/tokens.md +460 -0
  46. package/src/app.js +19 -0
  47. package/src/chart/_animate.js +266 -0
  48. package/src/chart/_base.js +109 -0
  49. package/src/chart/_data.js +209 -0
  50. package/src/chart/_format.js +106 -0
  51. package/src/chart/_interact.js +364 -0
  52. package/src/chart/_palette.js +105 -0
  53. package/src/chart/_renderer.js +52 -0
  54. package/src/chart/_scene.js +262 -0
  55. package/src/chart/_shared.js +371 -0
  56. package/src/chart/index.js +637 -0
  57. package/src/chart/layouts/_layout-base.js +328 -0
  58. package/src/chart/layouts/cartesian.js +148 -0
  59. package/src/chart/layouts/hierarchy.js +562 -0
  60. package/src/chart/layouts/polar.js +101 -0
  61. package/src/chart/renderers/canvas.js +179 -0
  62. package/src/chart/renderers/svg.js +256 -0
  63. package/src/chart/renderers/webgpu.js +715 -0
  64. package/src/chart/types/_type-base.js +26 -0
  65. package/src/chart/types/area.js +134 -0
  66. package/src/chart/types/bar.js +173 -0
  67. package/src/chart/types/box-plot.js +125 -0
  68. package/src/chart/types/bubble.js +63 -0
  69. package/src/chart/types/candlestick.js +115 -0
  70. package/src/chart/types/chord.js +85 -0
  71. package/src/chart/types/combination.js +108 -0
  72. package/src/chart/types/funnel.js +68 -0
  73. package/src/chart/types/gauge.js +163 -0
  74. package/src/chart/types/heatmap.js +98 -0
  75. package/src/chart/types/histogram.js +71 -0
  76. package/src/chart/types/line.js +111 -0
  77. package/src/chart/types/org-chart.js +93 -0
  78. package/src/chart/types/pie.js +81 -0
  79. package/src/chart/types/radar.js +96 -0
  80. package/src/chart/types/radial.js +68 -0
  81. package/src/chart/types/range-area.js +55 -0
  82. package/src/chart/types/range-bar.js +61 -0
  83. package/src/chart/types/sankey.js +73 -0
  84. package/src/chart/types/scatter.js +66 -0
  85. package/src/chart/types/sparkline.js +81 -0
  86. package/src/chart/types/sunburst.js +69 -0
  87. package/src/chart/types/swimlane.js +88 -0
  88. package/src/chart/types/treemap.js +62 -0
  89. package/src/chart/types/waterfall.js +100 -0
  90. package/src/components/_base.js +1658 -0
  91. package/src/components/_behaviors.js +1140 -0
  92. package/src/components/_primitives.js +534 -0
  93. package/src/components/_qr-encoder.js +539 -0
  94. package/src/components/accordion.js +207 -0
  95. package/src/components/affix.js +62 -0
  96. package/src/components/alert-dialog.js +75 -0
  97. package/src/components/alert.js +47 -0
  98. package/src/components/aspect-ratio.js +24 -0
  99. package/src/components/avatar-group.js +55 -0
  100. package/src/components/avatar.js +38 -0
  101. package/src/components/back-top.js +75 -0
  102. package/src/components/badge.js +74 -0
  103. package/src/components/banner.js +68 -0
  104. package/src/components/breadcrumb.js +162 -0
  105. package/src/components/button.js +115 -0
  106. package/src/components/calendar.js +131 -0
  107. package/src/components/card.js +192 -0
  108. package/src/components/carousel.js +98 -0
  109. package/src/components/cascader.js +261 -0
  110. package/src/components/checkbox.js +80 -0
  111. package/src/components/chip.js +81 -0
  112. package/src/components/code-block.js +82 -0
  113. package/src/components/collapsible.js +50 -0
  114. package/src/components/color-palette.js +438 -0
  115. package/src/components/color-picker.js +314 -0
  116. package/src/components/combobox.js +181 -0
  117. package/src/components/command.js +174 -0
  118. package/src/components/comment.js +206 -0
  119. package/src/components/context-menu.js +76 -0
  120. package/src/components/data-table.js +724 -0
  121. package/src/components/date-picker.js +217 -0
  122. package/src/components/date-range-picker.js +244 -0
  123. package/src/components/datetime-picker.js +271 -0
  124. package/src/components/descriptions.js +68 -0
  125. package/src/components/drawer.js +179 -0
  126. package/src/components/dropdown.js +88 -0
  127. package/src/components/empty.js +41 -0
  128. package/src/components/float-button.js +90 -0
  129. package/src/components/form.js +106 -0
  130. package/src/components/hover-card.js +49 -0
  131. package/src/components/icon.js +87 -0
  132. package/src/components/image.js +97 -0
  133. package/src/components/index.js +117 -0
  134. package/src/components/input-group.js +75 -0
  135. package/src/components/input-number.js +155 -0
  136. package/src/components/input-otp.js +178 -0
  137. package/src/components/input.js +91 -0
  138. package/src/components/kbd.js +36 -0
  139. package/src/components/label.js +25 -0
  140. package/src/components/list.js +118 -0
  141. package/src/components/masked-input.js +236 -0
  142. package/src/components/mentions.js +165 -0
  143. package/src/components/menu.js +259 -0
  144. package/src/components/message.js +80 -0
  145. package/src/components/modal.js +147 -0
  146. package/src/components/navigation-menu.js +166 -0
  147. package/src/components/notification.js +84 -0
  148. package/src/components/pagination.js +104 -0
  149. package/src/components/placeholder.js +132 -0
  150. package/src/components/popconfirm.js +70 -0
  151. package/src/components/popover.js +58 -0
  152. package/src/components/progress.js +61 -0
  153. package/src/components/qrcode.js +251 -0
  154. package/src/components/radiogroup.js +120 -0
  155. package/src/components/range-slider.js +176 -0
  156. package/src/components/rate.js +186 -0
  157. package/src/components/resizable.js +83 -0
  158. package/src/components/result.js +57 -0
  159. package/src/components/scroll-area.js +43 -0
  160. package/src/components/segmented.js +97 -0
  161. package/src/components/select.js +165 -0
  162. package/src/components/separator.js +31 -0
  163. package/src/components/shell.js +407 -0
  164. package/src/components/skeleton.js +39 -0
  165. package/src/components/slider.js +141 -0
  166. package/src/components/sortable-list.js +176 -0
  167. package/src/components/space.js +42 -0
  168. package/src/components/spinner.js +112 -0
  169. package/src/components/splitter.js +147 -0
  170. package/src/components/statistic.js +136 -0
  171. package/src/components/steps.js +99 -0
  172. package/src/components/switch.js +95 -0
  173. package/src/components/table.js +44 -0
  174. package/src/components/tabs.js +216 -0
  175. package/src/components/tag.js +115 -0
  176. package/src/components/textarea.js +82 -0
  177. package/src/components/time-picker.js +153 -0
  178. package/src/components/time-range-picker.js +170 -0
  179. package/src/components/timeline.js +226 -0
  180. package/src/components/toast.js +71 -0
  181. package/src/components/toggle.js +213 -0
  182. package/src/components/tooltip.js +57 -0
  183. package/src/components/tour.js +159 -0
  184. package/src/components/transfer.js +163 -0
  185. package/src/components/tree-select.js +274 -0
  186. package/src/components/tree.js +141 -0
  187. package/src/components/typography.js +136 -0
  188. package/src/components/upload.js +118 -0
  189. package/src/components/visually-hidden.js +20 -0
  190. package/src/components/watermark.js +124 -0
  191. package/src/core/index.js +539 -0
  192. package/src/core/lifecycle.js +69 -0
  193. package/src/css/atoms.js +651 -0
  194. package/src/css/components.js +940 -0
  195. package/src/css/derive.js +1296 -0
  196. package/src/css/index.js +265 -0
  197. package/src/css/runtime.js +268 -0
  198. package/src/css/styles/addons/bioluminescent.js +93 -0
  199. package/src/css/styles/addons/clay.js +70 -0
  200. package/src/css/styles/addons/clean.js +57 -0
  201. package/src/css/styles/addons/command-center.js +143 -0
  202. package/src/css/styles/addons/dopamine.js +83 -0
  203. package/src/css/styles/addons/editorial.js +80 -0
  204. package/src/css/styles/addons/glassmorphism.js +99 -0
  205. package/src/css/styles/addons/liquid-glass.js +105 -0
  206. package/src/css/styles/addons/prismatic.js +100 -0
  207. package/src/css/styles/addons/retro.js +63 -0
  208. package/src/css/styles/auradecantism.js +96 -0
  209. package/src/css/theme-registry.js +444 -0
  210. package/src/data/entity.js +281 -0
  211. package/src/data/index.js +13 -0
  212. package/src/data/persist.js +225 -0
  213. package/src/data/query.js +839 -0
  214. package/src/data/realtime.js +299 -0
  215. package/src/data/url.js +177 -0
  216. package/src/data/worker.js +134 -0
  217. package/src/explorer/archetypes.js +243 -0
  218. package/src/explorer/atoms.js +228 -0
  219. package/src/explorer/charts.js +497 -0
  220. package/src/explorer/components.js +129 -0
  221. package/src/explorer/foundations.js +949 -0
  222. package/src/explorer/icons.js +178 -0
  223. package/src/explorer/patterns.js +247 -0
  224. package/src/explorer/recipes.js +194 -0
  225. package/src/explorer/shared/pattern-examples.js +1337 -0
  226. package/src/explorer/shared/showcase-renderer.js +958 -0
  227. package/src/explorer/shared/spec-table.js +41 -0
  228. package/src/explorer/shared/usage-links.js +87 -0
  229. package/src/explorer/shell-config.js +10 -0
  230. package/src/explorer/shells.js +551 -0
  231. package/src/explorer/styles.js +161 -0
  232. package/src/explorer/tokens.js +262 -0
  233. package/src/explorer/tools.js +525 -0
  234. package/src/form/index.js +804 -0
  235. package/src/i18n/index.js +251 -0
  236. package/src/icons/essential.js +479 -0
  237. package/src/icons/index.js +53 -0
  238. package/src/plugins/index.js +282 -0
  239. package/src/registry/archetypes/content-site.json +71 -0
  240. package/src/registry/archetypes/docs-explorer.json +23 -0
  241. package/src/registry/archetypes/ecommerce.json +104 -0
  242. package/src/registry/archetypes/financial-dashboard.json +77 -0
  243. package/src/registry/archetypes/index.json +41 -0
  244. package/src/registry/archetypes/portfolio.json +82 -0
  245. package/src/registry/archetypes/recipe-community.json +159 -0
  246. package/src/registry/archetypes/saas-dashboard.json +86 -0
  247. package/src/registry/architect/cross-cutting.json +45 -0
  248. package/src/registry/architect/domains/ecommerce.json +294 -0
  249. package/src/registry/architect/domains/financial-services.json +302 -0
  250. package/src/registry/architect/index.json +26 -0
  251. package/src/registry/architect/traits.json +379 -0
  252. package/src/registry/atoms.json +16 -0
  253. package/src/registry/chart-showcase.json +160 -0
  254. package/src/registry/chart.json +136 -0
  255. package/src/registry/components.json +8616 -0
  256. package/src/registry/core.json +216 -0
  257. package/src/registry/css.json +319 -0
  258. package/src/registry/data.json +135 -0
  259. package/src/registry/foundations.json +11 -0
  260. package/src/registry/icons.json +463 -0
  261. package/src/registry/index.json +101 -0
  262. package/src/registry/patterns/activity-feed.json +37 -0
  263. package/src/registry/patterns/article-content.json +27 -0
  264. package/src/registry/patterns/auth-form.json +37 -0
  265. package/src/registry/patterns/author-card.json +20 -0
  266. package/src/registry/patterns/card-grid.json +127 -0
  267. package/src/registry/patterns/category-nav.json +26 -0
  268. package/src/registry/patterns/chart-grid.json +36 -0
  269. package/src/registry/patterns/chat-interface.json +37 -0
  270. package/src/registry/patterns/checklist-card.json +55 -0
  271. package/src/registry/patterns/comparison-panel.json +27 -0
  272. package/src/registry/patterns/component-showcase.json +24 -0
  273. package/src/registry/patterns/contact-form.json +31 -0
  274. package/src/registry/patterns/cta-section.json +20 -0
  275. package/src/registry/patterns/data-table.json +37 -0
  276. package/src/registry/patterns/detail-header.json +83 -0
  277. package/src/registry/patterns/detail-panel.json +27 -0
  278. package/src/registry/patterns/explorer-shell.json +22 -0
  279. package/src/registry/patterns/filter-bar.json +33 -0
  280. package/src/registry/patterns/filter-sidebar.json +27 -0
  281. package/src/registry/patterns/form-sections.json +110 -0
  282. package/src/registry/patterns/goal-tracker.json +27 -0
  283. package/src/registry/patterns/hero.json +107 -0
  284. package/src/registry/patterns/index.json +47 -0
  285. package/src/registry/patterns/kpi-grid.json +36 -0
  286. package/src/registry/patterns/media-gallery.json +20 -0
  287. package/src/registry/patterns/order-history.json +20 -0
  288. package/src/registry/patterns/pagination.json +19 -0
  289. package/src/registry/patterns/photo-to-recipe.json +36 -0
  290. package/src/registry/patterns/pipeline-tracker.json +28 -0
  291. package/src/registry/patterns/post-list.json +27 -0
  292. package/src/registry/patterns/pricing-table.json +32 -0
  293. package/src/registry/patterns/scorecard.json +28 -0
  294. package/src/registry/patterns/search-bar.json +20 -0
  295. package/src/registry/patterns/specimen-grid.json +19 -0
  296. package/src/registry/patterns/stat-card.json +55 -0
  297. package/src/registry/patterns/stats-bar.json +55 -0
  298. package/src/registry/patterns/steps-card.json +55 -0
  299. package/src/registry/patterns/table-of-contents.json +19 -0
  300. package/src/registry/patterns/testimonials.json +21 -0
  301. package/src/registry/patterns/timeline.json +27 -0
  302. package/src/registry/patterns/token-inspector.json +21 -0
  303. package/src/registry/patterns/wizard.json +27 -0
  304. package/src/registry/recipe-auradecantism.json +69 -0
  305. package/src/registry/recipe-clean.json +65 -0
  306. package/src/registry/recipe-command-center.json +78 -0
  307. package/src/registry/router.json +73 -0
  308. package/src/registry/schema/README.md +197 -0
  309. package/src/registry/skeletons.json +259 -0
  310. package/src/registry/state.json +137 -0
  311. package/src/registry/tokens.json +40 -0
  312. package/src/router/hash.js +17 -0
  313. package/src/router/history.js +18 -0
  314. package/src/router/index.js +598 -0
  315. package/src/ssr/index.js +922 -0
  316. package/src/state/arrays.js +181 -0
  317. package/src/state/devtools.js +647 -0
  318. package/src/state/index.js +498 -0
  319. package/src/state/middleware.js +288 -0
  320. package/src/state/scheduler.js +206 -0
  321. package/src/state/store.js +300 -0
  322. package/src/tags/index.js +19 -0
  323. package/src/tannins/auth.js +396 -0
  324. package/src/test/dom.js +352 -0
  325. package/src/test/index.js +62 -0
  326. package/src/test/state.js +306 -0
  327. package/tools/a11y-audit.js +487 -0
  328. package/tools/analyzer.js +315 -0
  329. package/tools/audit.js +706 -0
  330. package/tools/builder.js +1422 -0
  331. package/tools/css-extract.js +188 -0
  332. package/tools/dev-server.js +316 -0
  333. package/tools/dts-gen.js +1260 -0
  334. package/tools/figma-components.js +329 -0
  335. package/tools/figma-patterns.js +516 -0
  336. package/tools/figma-plugin/code.js +453 -0
  337. package/tools/figma-plugin/manifest.json +14 -0
  338. package/tools/figma-plugin/ui.html +268 -0
  339. package/tools/figma-render.js +293 -0
  340. package/tools/figma-tokens.js +712 -0
  341. package/tools/figma-upload.js +318 -0
  342. package/tools/generate.js +738 -0
  343. package/tools/icons.js +133 -0
  344. package/tools/init-templates.js +265 -0
  345. package/tools/install-hooks.sh +5 -0
  346. package/tools/migrations/0.5.0.js +53 -0
  347. package/tools/migrations/0.6.0.js +95 -0
  348. package/tools/minify.js +170 -0
  349. package/tools/pre-commit +4 -0
  350. package/tools/registry.js +662 -0
  351. package/tools/reset-playground.js +61 -0
  352. package/tools/starter-templates/content-site/app.js +49 -0
  353. package/tools/starter-templates/content-site/essence.js +19 -0
  354. package/tools/starter-templates/content-site/pages.js +31 -0
  355. package/tools/starter-templates/ecommerce/app.js +50 -0
  356. package/tools/starter-templates/ecommerce/essence.js +19 -0
  357. package/tools/starter-templates/ecommerce/pages.js +31 -0
  358. package/tools/starter-templates/landing-page/app.js +38 -0
  359. package/tools/starter-templates/landing-page/essence.js +18 -0
  360. package/tools/starter-templates/landing-page/pages.js +21 -0
  361. package/tools/starter-templates/portfolio/app.js +45 -0
  362. package/tools/starter-templates/portfolio/essence.js +19 -0
  363. package/tools/starter-templates/portfolio/pages.js +33 -0
  364. package/tools/starter-templates/saas-dashboard/app.js +70 -0
  365. package/tools/starter-templates/saas-dashboard/essence.js +19 -0
  366. package/tools/starter-templates/saas-dashboard/pages.js +31 -0
  367. package/tools/verify-pack.js +203 -0
  368. package/types/chart.d.ts +77 -0
  369. package/types/components.d.ts +587 -0
  370. package/types/core.d.ts +89 -0
  371. package/types/css.d.ts +149 -0
  372. package/types/data.d.ts +238 -0
  373. package/types/form.d.ts +164 -0
  374. package/types/i18n.d.ts +51 -0
  375. package/types/icons.d.ts +27 -0
  376. package/types/index.d.ts +13 -0
  377. package/types/router.d.ts +116 -0
  378. package/types/ssr.d.ts +102 -0
  379. package/types/state.d.ts +83 -0
  380. package/types/tags.d.ts +62 -0
  381. package/types/tannins.d.ts +63 -0
  382. package/types/test.d.ts +48 -0
@@ -0,0 +1,197 @@
1
+ import { readFile, stat, access } from 'node:fs/promises';
2
+ import { join } from 'node:path';
3
+
4
+ const cwd = process.cwd();
5
+
6
+ // ── Color helpers ─────────────────────────────────────────────────
7
+ const green = (s) => `\x1b[32m✓\x1b[0m ${s}`;
8
+ const red = (s) => `\x1b[31m✗\x1b[0m ${s}`;
9
+ const yellow = (s) => `\x1b[33m⚠\x1b[0m ${s}`;
10
+ const bold = (s) => `\x1b[1m${s}\x1b[0m`;
11
+ const dim = (s) => `\x1b[2m${s}\x1b[0m`;
12
+
13
+ let passes = 0;
14
+ let warns = 0;
15
+ let fails = 0;
16
+
17
+ function pass(msg) { passes++; console.log(green(msg)); }
18
+ function warn(msg) { warns++; console.log(yellow(msg)); }
19
+ function fail(msg) { fails++; console.log(red(msg)); }
20
+
21
+ async function fileExists(path) {
22
+ try {
23
+ await access(path);
24
+ return true;
25
+ } catch {
26
+ return false;
27
+ }
28
+ }
29
+
30
+ async function readJSON(path) {
31
+ try {
32
+ const raw = await readFile(path, 'utf-8');
33
+ return JSON.parse(raw);
34
+ } catch {
35
+ return null;
36
+ }
37
+ }
38
+
39
+ // ── Checks ────────────────────────────────────────────────────────
40
+
41
+ async function checkNodeVersion() {
42
+ const [major] = process.versions.node.split('.').map(Number);
43
+ if (major >= 20) {
44
+ pass(`Node.js ${process.versions.node} (>= 20.0.0 required)`);
45
+ } else {
46
+ fail(`Node.js ${process.versions.node} — requires >= 20.0.0`);
47
+ }
48
+ }
49
+
50
+ async function checkPackageJson() {
51
+ const pkgPath = join(cwd, 'package.json');
52
+ const pkg = await readJSON(pkgPath);
53
+ if (!pkg) {
54
+ fail('package.json not found');
55
+ return;
56
+ }
57
+ pass('package.json exists');
58
+
59
+ if (pkg.type === 'module') {
60
+ pass('package.json type is "module"');
61
+ } else {
62
+ warn('package.json type should be "module" for ESM support');
63
+ }
64
+
65
+ // Check for decantr dependency (accept both scoped and unscoped names)
66
+ const deps = { ...pkg.dependencies, ...pkg.devDependencies };
67
+ const decantrVersion = deps['decantr'] || deps['decantr'];
68
+ if (decantrVersion) {
69
+ pass(`decantr ${decantrVersion} in dependencies`);
70
+ } else {
71
+ fail('decantr not found in dependencies (expected decantr or decantr)');
72
+ }
73
+ }
74
+
75
+ async function checkConfig() {
76
+ const configPath = join(cwd, 'decantr.config.json');
77
+ const config = await readJSON(configPath);
78
+ if (!config) {
79
+ warn('decantr.config.json not found — using defaults');
80
+ return;
81
+ }
82
+ pass('decantr.config.json exists and is valid JSON');
83
+
84
+ if (config.build?.sizeBudget) {
85
+ pass('Size budgets configured');
86
+ } else {
87
+ warn('No size budgets configured in decantr.config.json');
88
+ }
89
+ }
90
+
91
+ async function checkEssence() {
92
+ const essPath = join(cwd, 'decantr.essence.json');
93
+ const ess = await readJSON(essPath);
94
+ if (!ess) {
95
+ warn('decantr.essence.json not found — run `decantr generate` or create manually');
96
+ return;
97
+ }
98
+ pass('decantr.essence.json exists and is valid JSON');
99
+
100
+ if (ess.terroir) pass(`Terroir: ${ess.terroir}`);
101
+ else if (ess.sections) pass(`Sectioned essence with ${ess.sections.length} section(s)`);
102
+ else warn('Essence missing terroir or sections');
103
+
104
+ if (ess.vintage?.style) pass(`Style: ${ess.vintage.style}`);
105
+ else if (ess.sections?.[0]?.vintage?.style) pass(`Style: ${ess.sections[0].vintage.style} (first section)`);
106
+ else warn('Essence missing vintage.style');
107
+
108
+ const structure = ess.structure || ess.sections?.flatMap(s => s.structure || []) || [];
109
+ if (structure.length > 0) {
110
+ pass(`Structure: ${structure.length} page(s) defined`);
111
+ } else {
112
+ warn('No pages defined in structure');
113
+ }
114
+ }
115
+
116
+ async function checkEntryPoint() {
117
+ const candidates = ['src/app.js', 'src/index.js', 'src/main.js'];
118
+ for (const c of candidates) {
119
+ if (await fileExists(join(cwd, c))) {
120
+ pass(`Entry point found: ${c}`);
121
+ return;
122
+ }
123
+ }
124
+ warn('No entry point found (expected src/app.js, src/index.js, or src/main.js)');
125
+ }
126
+
127
+ async function checkDirectories() {
128
+ const dirs = ['src', 'public'];
129
+ for (const d of dirs) {
130
+ try {
131
+ const s = await stat(join(cwd, d));
132
+ if (s.isDirectory()) {
133
+ pass(`${d}/ directory exists`);
134
+ } else {
135
+ fail(`${d} exists but is not a directory`);
136
+ }
137
+ } catch {
138
+ if (d === 'src') fail('src/ directory not found');
139
+ else warn(`${d}/ directory not found — optional`);
140
+ }
141
+ }
142
+ }
143
+
144
+ async function checkPublicHTML() {
145
+ const htmlPath = join(cwd, 'public', 'index.html');
146
+ if (await fileExists(htmlPath)) {
147
+ pass('public/index.html exists');
148
+ const html = await readFile(htmlPath, 'utf-8');
149
+ if (html.includes('type="module"')) {
150
+ pass('index.html loads app as ES module');
151
+ } else {
152
+ warn('index.html missing type="module" on script tag');
153
+ }
154
+ } else {
155
+ warn('public/index.html not found');
156
+ }
157
+ }
158
+
159
+ async function checkFrameworkVersion() {
160
+ const scopedPath = join(cwd, 'node_modules', '@decantr', 'decantr', 'package.json');
161
+ const unscopedPath = join(cwd, 'node_modules', 'decantr', 'package.json');
162
+ const pkg = await readJSON(scopedPath) || await readJSON(unscopedPath);
163
+ if (!pkg) {
164
+ warn('Cannot read installed decantr version — run npm install');
165
+ return;
166
+ }
167
+ const [major, minor] = pkg.version.split('.').map(Number);
168
+ pass(`Framework version: ${pkg.version}`);
169
+ if (major === 0 && minor < 4) {
170
+ warn(`Framework version ${pkg.version} is outdated — consider updating to latest`);
171
+ }
172
+ }
173
+
174
+ // ── Main ──────────────────────────────────────────────────────────
175
+ export async function run() {
176
+ console.log(`\n${bold('decantr doctor')} ${dim('— checking project health')}\n`);
177
+
178
+ await checkNodeVersion();
179
+ await checkPackageJson();
180
+ await checkConfig();
181
+ await checkEssence();
182
+ await checkEntryPoint();
183
+ await checkDirectories();
184
+ await checkPublicHTML();
185
+ await checkFrameworkVersion();
186
+
187
+ console.log(`\n${bold('Results:')} ${green(`${passes} passed`)}${warns ? `, ${yellow(`${warns} warnings`)}` : ''}${fails ? `, ${red(`${fails} failed`)}` : ''}\n`);
188
+
189
+ if (fails > 0) {
190
+ console.log(`${red('Some checks failed. Fix the issues above and run again.')}\n`);
191
+ process.exit(1);
192
+ } else if (warns > 0) {
193
+ console.log(`${yellow('All critical checks passed, but there are warnings.')}\n`);
194
+ } else {
195
+ console.log(`${green('All checks passed! Your project is healthy.')}\n`);
196
+ }
197
+ }
@@ -0,0 +1,48 @@
1
+ import { parseArgs } from 'node:util';
2
+
3
+ const BOLD = '\x1b[1m';
4
+ const DIM = '\x1b[2m';
5
+ const GREEN = '\x1b[32m';
6
+ const CYAN = '\x1b[36m';
7
+ const RESET = '\x1b[0m';
8
+
9
+ export async function run() {
10
+ const { values } = parseArgs({
11
+ options: {
12
+ token: { type: 'string' },
13
+ file: { type: 'string' },
14
+ style: { type: 'string', default: 'all' },
15
+ input: { type: 'string' },
16
+ 'dry-run': { type: 'boolean', default: false },
17
+ },
18
+ strict: false,
19
+ });
20
+
21
+ console.log(`\n ${BOLD}decantr figma:sync${RESET}\n`);
22
+
23
+ const { syncFigmaTokens } = await import('../../tools/figma-upload.js');
24
+
25
+ try {
26
+ const result = await syncFigmaTokens({
27
+ token: values.token,
28
+ file: values.file,
29
+ style: values.style,
30
+ input: values.input,
31
+ 'dry-run': values['dry-run'],
32
+ cwd: process.cwd(),
33
+ });
34
+
35
+ if (values['dry-run']) {
36
+ console.log(` ${DIM}Collections:${RESET} ${result.collections}`);
37
+ console.log(` ${DIM}Variables:${RESET} ${result.variables}`);
38
+ console.log(` ${DIM}Mode values:${RESET} ${result.modeValues}`);
39
+ console.log(`\n ${DIM}(dry run — no changes pushed)${RESET}\n`);
40
+ } else {
41
+ console.log(` ${GREEN}✓${RESET} Synced ${BOLD}${result.variables}${RESET} variables across ${BOLD}${result.collections}${RESET} collection(s)`);
42
+ console.log(` ${GREEN}✓${RESET} ${result.modeValues} mode values set\n`);
43
+ }
44
+ } catch (e) {
45
+ console.error(`\n ✗ ${e.message}\n`);
46
+ process.exitCode = 1;
47
+ }
48
+ }
@@ -0,0 +1,55 @@
1
+ import { parseArgs } from 'node:util';
2
+
3
+ const BOLD = '\x1b[1m';
4
+ const DIM = '\x1b[2m';
5
+ const GREEN = '\x1b[32m';
6
+ const CYAN = '\x1b[36m';
7
+ const RESET = '\x1b[0m';
8
+
9
+ export async function run() {
10
+ const { values } = parseArgs({
11
+ options: {
12
+ style: { type: 'string', default: 'all' },
13
+ format: { type: 'string', default: 'dtcg' },
14
+ output: { type: 'string' },
15
+ 'dry-run': { type: 'boolean', default: false },
16
+ },
17
+ strict: false,
18
+ });
19
+
20
+ console.log(`\n ${BOLD}decantr figma:tokens${RESET}\n`);
21
+
22
+ const { generateFigmaTokens } = await import('../../tools/figma-tokens.js');
23
+
24
+ try {
25
+ const result = await generateFigmaTokens({
26
+ style: values.style,
27
+ format: values.format,
28
+ output: values.output,
29
+ 'dry-run': values['dry-run'],
30
+ cwd: process.cwd(),
31
+ });
32
+
33
+ if (values['dry-run']) {
34
+ console.log(` ${DIM}Styles:${RESET} ${result.styles.join(', ')}`);
35
+ console.log(` ${DIM}Tokens per style:${RESET} ~${Math.round(result.tokenCount / result.styles.length)}`);
36
+ console.log(` ${DIM}Shape tokens:${RESET} ${result.shapeTokenCount}`);
37
+ console.log(` ${DIM}Files:${RESET}`);
38
+ for (const f of result.files) {
39
+ console.log(` ${DIM}→${RESET} ${f}`);
40
+ }
41
+ console.log();
42
+ } else {
43
+ console.log(` ${GREEN}✓${RESET} Generated ${BOLD}${result.tokenCount}${RESET} tokens across ${BOLD}${result.styles.length}${RESET} style(s)`);
44
+ console.log(` ${GREEN}✓${RESET} ${result.shapeTokenCount} shape tokens (3 modes)`);
45
+ console.log(` ${DIM}Output:${RESET} ${result.files.length} files\n`);
46
+ for (const f of result.files) {
47
+ console.log(` ${CYAN}→${RESET} ${f}`);
48
+ }
49
+ console.log();
50
+ }
51
+ } catch (e) {
52
+ console.error(`\n ✗ ${e.message}\n`);
53
+ process.exitCode = 1;
54
+ }
55
+ }
@@ -0,0 +1,26 @@
1
+ import { parseArgs } from 'node:util';
2
+
3
+ export async function run() {
4
+ const { values } = parseArgs({
5
+ options: {
6
+ force: { type: 'boolean', default: false },
7
+ 'dry-run': { type: 'boolean', default: false },
8
+ page: { type: 'string' },
9
+ },
10
+ strict: false,
11
+ });
12
+
13
+ const { generate } = await import('../../tools/generate.js');
14
+
15
+ try {
16
+ await generate({
17
+ cwd: process.cwd(),
18
+ force: values.force,
19
+ dryRun: values['dry-run'],
20
+ pageFilter: values.page || null,
21
+ });
22
+ } catch (e) {
23
+ console.error(`\n ✗ ${e.message}\n`);
24
+ process.exitCode = 1;
25
+ }
26
+ }
@@ -0,0 +1,116 @@
1
+ import { mkdir, writeFile, copyFile } from 'node:fs/promises';
2
+ import { join, basename, dirname, resolve } from 'node:path';
3
+ import { fileURLToPath } from 'node:url';
4
+ import { welcome, success, info, heading } from '../art.js';
5
+ import {
6
+ packageJson, configJson, essenceJson, indexHtml, manifestJson,
7
+ claudeMd, appJs, agentsMd
8
+ } from '../../tools/init-templates.js';
9
+
10
+ const __dirname = dirname(fileURLToPath(import.meta.url));
11
+ const frameworkRoot = resolve(__dirname, '..', '..');
12
+
13
+ const TEMPLATES = ['saas-dashboard', 'ecommerce', 'portfolio', 'content-site', 'landing-page'];
14
+
15
+ /**
16
+ * Parse --template flag from process.argv.
17
+ * Supports: --template=name and --template name
18
+ */
19
+ function parseTemplate() {
20
+ const args = process.argv.slice(3);
21
+ for (let i = 0; i < args.length; i++) {
22
+ if (args[i].startsWith('--template=')) return args[i].split('=')[1];
23
+ if (args[i] === '--template' && args[i + 1]) return args[i + 1];
24
+ }
25
+ return null;
26
+ }
27
+
28
+ /**
29
+ * Create a new decantr project skeleton.
30
+ * User then prompts their AI to build the app.
31
+ */
32
+ export async function run() {
33
+ const cwd = process.cwd();
34
+ const name = process.argv[3] && !process.argv[3].startsWith('--') ? process.argv[3] : basename(cwd);
35
+ const templateName = parseTemplate();
36
+
37
+ // Validate template name if provided
38
+ if (templateName && !TEMPLATES.includes(templateName)) {
39
+ console.log(`\x1b[31mError: Unknown template "${templateName}"\x1b[0m`);
40
+ console.log(info(`Available templates: ${TEMPLATES.join(', ')}`));
41
+ process.exitCode = 1;
42
+ return;
43
+ }
44
+
45
+ console.log(welcome('0.4.0'));
46
+ if (templateName) {
47
+ console.log(heading(`Creating project from "${templateName}" template...`));
48
+ } else {
49
+ console.log(heading('Creating project skeleton...'));
50
+ }
51
+
52
+ // Create directories
53
+ const dirs = ['public', 'public/images', '.decantr', 'src', 'src/pages', 'src/components'];
54
+ for (const dir of dirs) {
55
+ await mkdir(join(cwd, dir), { recursive: true });
56
+ }
57
+
58
+ // Load template overrides if specified
59
+ let templateEssence = essenceJson;
60
+ let templateApp = appJs;
61
+ let templatePages = null;
62
+ if (templateName) {
63
+ const tmpl = await import(`../../tools/starter-templates/${templateName}/essence.js`);
64
+ const tmplApp = await import(`../../tools/starter-templates/${templateName}/app.js`);
65
+ const tmplPages = await import(`../../tools/starter-templates/${templateName}/pages.js`);
66
+ templateEssence = tmpl.essenceJson;
67
+ templateApp = tmplApp.appJs;
68
+ templatePages = tmplPages.pageFiles;
69
+ }
70
+
71
+ const files = [
72
+ ['package.json', packageJson(name)],
73
+ ['decantr.config.json', configJson(name)],
74
+ ['decantr.essence.json', templateEssence()],
75
+ ['public/index.html', indexHtml(name)],
76
+ ['.decantr/manifest.json', manifestJson(name)],
77
+ ['CLAUDE.md', claudeMd(name)],
78
+ ['AGENTS.md', await agentsMd()],
79
+ ['src/app.js', templateApp()]
80
+ ];
81
+
82
+ // Add template page stubs
83
+ if (templatePages) {
84
+ for (const [path, content] of templatePages()) {
85
+ files.push([path, content]);
86
+ }
87
+ }
88
+
89
+ for (const [path, content] of files) {
90
+ await writeFile(join(cwd, path), content + '\n');
91
+ console.log(' ' + success(path));
92
+ }
93
+
94
+ // Copy logo asset
95
+ const logoSrc = join(frameworkRoot, 'workbench', 'public', 'images', 'logo-portrait.svg');
96
+ const logoDest = join(cwd, 'public', 'images', 'logo-portrait.svg');
97
+ try {
98
+ await copyFile(logoSrc, logoDest);
99
+ console.log(' ' + success('public/images/logo-portrait.svg'));
100
+ } catch {
101
+ // Logo not found — non-fatal, welcome page still works without it
102
+ }
103
+
104
+ console.log('');
105
+ console.log(heading('Project created!'));
106
+ console.log(info('Next steps:'));
107
+ console.log(' npm install');
108
+ console.log(' npx decantr dev');
109
+ console.log('');
110
+ if (templateName) {
111
+ console.log(info(`Template "${templateName}" applied. Run decantr generate to scaffold full pages from the essence.`));
112
+ } else {
113
+ console.log(info('Then prompt your AI to build your app.'));
114
+ }
115
+ console.log('');
116
+ }
@@ -0,0 +1,209 @@
1
+ /**
2
+ * decantr lint — Code quality gates
3
+ *
4
+ * Checks:
5
+ * 1. Atom validation — flags unknown `_` atoms in source files
6
+ * 2. Essence drift — detects divergence between essence and generated code
7
+ * 3. Inline style detection — flags inline styles that should be atoms
8
+ * 4. Pattern coverage — checks that all blend patterns are implemented
9
+ */
10
+
11
+ import { readFile, readdir, stat } from 'node:fs/promises';
12
+ import { join, dirname, relative } from 'node:path';
13
+ import { fileURLToPath } from 'node:url';
14
+ import { createHash } from 'node:crypto';
15
+
16
+ const __dirname = dirname(fileURLToPath(import.meta.url));
17
+ const registryRoot = join(__dirname, '..', '..', 'src', 'registry');
18
+
19
+ export async function run() {
20
+ const cwd = process.cwd();
21
+ const errors = [];
22
+ const warnings = [];
23
+
24
+ console.log('\n decantr lint\n');
25
+
26
+ // ─── 1. Essence drift detection ─────────────────────────────
27
+ try {
28
+ const essenceRaw = await readFile(join(cwd, 'decantr.essence.json'), 'utf-8');
29
+ const essence = JSON.parse(essenceRaw);
30
+ const essenceHash = createHash('md5').update(essenceRaw).digest('hex');
31
+
32
+ try {
33
+ const lockRaw = await readFile(join(cwd, 'decantr.lock.json'), 'utf-8');
34
+ const lock = JSON.parse(lockRaw);
35
+
36
+ if (lock.essenceHash !== essenceHash) {
37
+ warnings.push('Essence has changed since last generate — run `decantr generate` to sync');
38
+ }
39
+
40
+ // Check that all essence pages have corresponding files
41
+ const structures = Array.isArray(essence.sections)
42
+ ? essence.sections.flatMap(s => s.structure || [])
43
+ : (essence.structure || []);
44
+
45
+ for (const page of structures) {
46
+ try {
47
+ await stat(join(cwd, 'src', 'pages', `${page.id}.js`));
48
+ } catch {
49
+ warnings.push(`Page "${page.id}" in essence but no src/pages/${page.id}.js found`);
50
+ }
51
+ }
52
+ } catch {
53
+ warnings.push('No decantr.lock.json — run `decantr generate` to create');
54
+ }
55
+
56
+ console.log(' ✓ Essence drift check complete');
57
+ } catch {
58
+ console.log(' ○ No essence file — skipping drift check');
59
+ }
60
+
61
+ // ─── 2. Atom validation in source files ──────────────────────
62
+ const srcDir = join(cwd, 'src');
63
+ try {
64
+ const jsFiles = await collectJSFiles(srcDir);
65
+ const atomRegex = /_[a-zA-Z][a-zA-Z0-9\[\]]*(?:\/\d+)?/g;
66
+
67
+ // Load known atoms from registry
68
+ let knownAtomPrefixes;
69
+ try {
70
+ const atomsSource = await readFile(join(__dirname, '..', '..', 'src', 'css', 'atoms.js'), 'utf-8');
71
+ knownAtomPrefixes = extractAtomPrefixes(atomsSource);
72
+ } catch {
73
+ knownAtomPrefixes = null;
74
+ }
75
+
76
+ let atomErrors = 0;
77
+ for (const file of jsFiles) {
78
+ const source = await readFile(file, 'utf-8');
79
+ // Only check inside css() calls
80
+ const cssCallRegex = /css\(['"`]([^'"`]+)['"`]\)/g;
81
+ let match;
82
+ while ((match = cssCallRegex.exec(source)) !== null) {
83
+ const atomStr = match[1];
84
+ const atoms = atomStr.split(/\s+/).filter(a => a.startsWith('_'));
85
+ for (const atom of atoms) {
86
+ // Skip arbitrary value atoms like _w[100px] and responsive prefixes
87
+ if (atom.includes('[')) continue;
88
+ if (atom.includes(':')) continue;
89
+ // Basic validation: known prefixes
90
+ if (knownAtomPrefixes && !matchesKnownAtom(atom, knownAtomPrefixes)) {
91
+ const rel = relative(cwd, file);
92
+ warnings.push(`Unknown atom "${atom}" in ${rel}`);
93
+ atomErrors++;
94
+ if (atomErrors >= 20) break; // Don't flood
95
+ }
96
+ }
97
+ if (atomErrors >= 20) break;
98
+ }
99
+ if (atomErrors >= 20) {
100
+ warnings.push('... truncated (20+ atom warnings)');
101
+ break;
102
+ }
103
+ }
104
+
105
+ console.log(` ✓ Atom validation: ${jsFiles.length} files scanned`);
106
+ } catch {
107
+ console.log(' ○ No src/ directory — skipping atom validation');
108
+ }
109
+
110
+ // ─── 3. Inline style detection ───────────────────────────────
111
+ try {
112
+ const jsFiles = await collectJSFiles(srcDir);
113
+ let inlineCount = 0;
114
+ for (const file of jsFiles) {
115
+ const source = await readFile(file, 'utf-8');
116
+ // Look for style: { ... } with static values (not functions)
117
+ const staticStyleRegex = /style:\s*['"`][^'"`]*(?:background|color|font|margin|padding|border|width|height|display|flex|grid)[^'"`]*['"`]/gi;
118
+ let match;
119
+ while ((match = staticStyleRegex.exec(source)) !== null) {
120
+ const rel = relative(cwd, file);
121
+ warnings.push(`Possible inline style in ${rel} — consider using atoms`);
122
+ inlineCount++;
123
+ if (inlineCount >= 10) break;
124
+ }
125
+ if (inlineCount >= 10) {
126
+ warnings.push('... truncated (10+ inline style warnings)');
127
+ break;
128
+ }
129
+ }
130
+ console.log(` ✓ Inline style check complete`);
131
+ } catch {
132
+ // Already handled above
133
+ }
134
+
135
+ // ─── Report ─────────────────────────────────────────────────
136
+ if (errors.length === 0 && warnings.length === 0) {
137
+ console.log('\n ✓ All checks passed\n');
138
+ } else {
139
+ if (errors.length > 0) {
140
+ console.error(`\n ${errors.length} error(s):`);
141
+ for (const e of errors) console.error(` ✗ ${e}`);
142
+ }
143
+ if (warnings.length > 0) {
144
+ console.log(`\n ${warnings.length} warning(s):`);
145
+ for (const w of warnings) console.log(` ⚠ ${w}`);
146
+ }
147
+ console.log('');
148
+ if (errors.length > 0) process.exitCode = 1;
149
+ }
150
+ }
151
+
152
+ async function collectJSFiles(dir) {
153
+ const files = [];
154
+ try {
155
+ const entries = await readdir(dir, { withFileTypes: true });
156
+ for (const entry of entries) {
157
+ const full = join(dir, entry.name);
158
+ if (entry.isDirectory() && entry.name !== 'node_modules') {
159
+ files.push(...await collectJSFiles(full));
160
+ } else if (entry.name.endsWith('.js')) {
161
+ files.push(full);
162
+ }
163
+ }
164
+ } catch { /* skip inaccessible dirs */ }
165
+ return files;
166
+ }
167
+
168
+ function extractAtomPrefixes(atomsSource) {
169
+ // Extract known atom name patterns from atoms.js
170
+ const prefixes = new Set();
171
+ // Match ALIASES keys
172
+ const aliasRegex = /'(_[a-zA-Z]+)'/g;
173
+ let m;
174
+ while ((m = aliasRegex.exec(atomsSource)) !== null) {
175
+ prefixes.add(m[1]);
176
+ }
177
+ // Add common known atom bases
178
+ const common = [
179
+ '_flex', '_col', '_row', '_grid', '_gap', '_p', '_px', '_py', '_pt', '_pr', '_pb', '_pl',
180
+ '_m', '_mx', '_my', '_mt', '_mr', '_mb', '_ml', '_w', '_h', '_mw', '_mh', '_r', '_b',
181
+ '_aic', '_jcc', '_jcsb', '_jcse', '_jce', '_tc', '_tl', '_tr', '_ais', '_aie',
182
+ '_relative', '_absolute', '_fixed', '_sticky', '_static',
183
+ '_overflow', '_wfull', '_hfull', '_flex1', '_grow', '_shrink0',
184
+ '_heading1', '_heading2', '_heading3', '_heading4', '_heading5', '_heading6',
185
+ '_body', '_caption', '_textsm', '_textxs', '_bold', '_italic',
186
+ '_fgfg', '_fgmuted', '_fgmutedfg', '_fgprimary', '_bgbg', '_bgmuted',
187
+ '_nounder', '_trans', '_block', '_inline', '_inlineFlex',
188
+ '_borderB', '_borderT', '_borderR', '_borderL', '_bordert', '_borderb',
189
+ '_object', '_inset0', '_top0', '_bottom0', '_left0', '_right0',
190
+ '_lineClamp2', '_clamp2', '_wrap', '_flexWrap', '_rfull',
191
+ '_gc', '_span', '_gcaf', '_mxa', '_mxAuto',
192
+ '_center', '_lhrelaxed', '_fontBold',
193
+ ];
194
+ for (const c of common) prefixes.add(c);
195
+ return prefixes;
196
+ }
197
+
198
+ function matchesKnownAtom(atom, prefixes) {
199
+ if (prefixes.has(atom)) return true;
200
+ // Check prefix matches (e.g. _gap4 matches _gap prefix)
201
+ for (const prefix of prefixes) {
202
+ if (atom.startsWith(prefix) && atom.length > prefix.length) {
203
+ const suffix = atom.slice(prefix.length);
204
+ // Numeric suffix is always valid
205
+ if (/^\d+(-\d+)?$/.test(suffix)) return true;
206
+ }
207
+ }
208
+ return false;
209
+ }