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
package/README.md ADDED
@@ -0,0 +1,229 @@
1
+ # decantr
2
+
3
+ [![CI](https://github.com/decantr-ai/decantr/actions/workflows/ci.yml/badge.svg)](https://github.com/decantr-ai/decantr/actions/workflows/ci.yml)
4
+ [![npm version](https://img.shields.io/npm/v/decantr)](https://www.npmjs.com/package/decantr)
5
+ [![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](LICENSE)
6
+
7
+ AI-first web framework. Zero dependencies. Native JS/CSS/HTML. v0.4.2
8
+
9
+ Decantr is designed for LLMs to generate, read, and maintain — not for human readability. Every API is optimized for token efficiency: terse atomic CSS atoms, proxy-based tag functions, and a machine-readable registry so agents can look up props and exports without parsing source files.
10
+
11
+ ## Quick Start
12
+
13
+ ```bash
14
+ npx decantr init my-app
15
+ cd my-app
16
+ npm install
17
+ npx decantr dev
18
+ ```
19
+
20
+ ## Architecture
21
+
22
+ ```
23
+ decantr/core — h(), text(), cond(), list(), mount(), onMount, onDestroy, ErrorBoundary, Portal, Suspense, Transition
24
+ decantr/state — createSignal, createEffect, createMemo, createStore, batch, createContext, createHistory, createRoot, on
25
+ decantr/data — createQuery, createMutation, createEntityStore, createURLSignal, createWebSocket, createPersisted
26
+ decantr/router — createRouter, link, navigate, useRoute, useSearchParams (hash + history modes, nested routes, guards)
27
+ decantr/form — createForm, validators, useFormField, fieldArray
28
+ decantr/css — css(), define(), setStyle(), setMode(), setShape(), 1000+ atomic CSS utilities
29
+ decantr/tags — Proxy-based tag functions (div, p, span...) — ~25% fewer tokens than h()
30
+ decantr/components — 100+ UI components (form, display, layout, overlay, feedback, chart, typography)
31
+ decantr/test — render, fire, flush + node:test re-exports
32
+ ```
33
+
34
+ ## Features
35
+
36
+ - **Zero dependencies** — Pure JavaScript, CSS, HTML
37
+ - **Signal-based reactivity** — Fine-grained DOM updates, no virtual DOM
38
+ - **Direct DOM rendering** — `h()` / `tags` create real elements, no diffing
39
+ - **100+ components** — Form, display, layout, overlay, feedback, chart, typography
40
+ - **Atomic CSS engine** — 1000+ `_`-prefixed utility atoms via `css()`
41
+ - **Style + Mode system** — 5 visual styles x light/dark/auto modes, 170+ design tokens
42
+ - **Machine-readable registry** — JSON specs for 100+ components, 49 patterns, 7 archetypes, recipes
43
+ - **Router** — Hash or History API, nested routes, guards, lazy loading
44
+ - **Form system** — Reactive forms with 10 built-in validators and field arrays
45
+ - **Build tooling** — Tree shaking, code splitting, source maps, CSS purging, incremental builds
46
+
47
+ ## Component Pattern
48
+
49
+ Every component is a function that returns an HTMLElement:
50
+
51
+ ```javascript
52
+ import { tags } from 'decantr/tags';
53
+ import { text } from 'decantr/core';
54
+ import { createSignal } from 'decantr/state';
55
+ import { css } from 'decantr/css';
56
+
57
+ const { div, button, span } = tags;
58
+
59
+ export function Counter({ initial = 0 } = {}) {
60
+ const [count, setCount] = createSignal(initial);
61
+ return div({ class: css('_flex _gap2 _p4 _aic') },
62
+ button({ onclick: () => setCount(c => c - 1) }, '-'),
63
+ span(text(() => String(count()))),
64
+ button({ onclick: () => setCount(c => c + 1) }, '+')
65
+ );
66
+ }
67
+ ```
68
+
69
+ ## CLI Commands
70
+
71
+ ```bash
72
+ decantr init [name] # Scaffold a new project
73
+ decantr dev # Start dev server with hot reload
74
+ decantr build # Production build
75
+ decantr test # Run tests
76
+ decantr test --watch # Watch mode
77
+ decantr validate # Validate decantr.essence.json
78
+ decantr lint # Code quality gates
79
+ decantr a11y # Accessibility audit
80
+ decantr doctor # Check project health
81
+ decantr generate # Generate code from essence
82
+ decantr migrate # Migrate essence between versions
83
+ ```
84
+
85
+ ## MCP Server
86
+
87
+ Decantr ships a built-in [Model Context Protocol](https://modelcontextprotocol.io) server that exposes 9 read-only tools for querying the component registry, resolving atomic CSS classes, validating project essence files, and searching across the full design system. The server runs locally via stdio — no data is collected, transmitted, or stored externally.
88
+
89
+ ### Start the server
90
+
91
+ ```bash
92
+ npx decantr mcp
93
+ ```
94
+
95
+ ### Integration
96
+
97
+ **Claude Code** (`~/.claude.json`):
98
+ ```json
99
+ {
100
+ "mcpServers": {
101
+ "decantr": {
102
+ "command": "npx",
103
+ "args": ["decantr", "mcp"]
104
+ }
105
+ }
106
+ }
107
+ ```
108
+
109
+ **Cursor** (`.cursor/mcp.json`):
110
+ ```json
111
+ {
112
+ "mcpServers": {
113
+ "decantr": {
114
+ "command": "npx",
115
+ "args": ["decantr", "mcp"]
116
+ }
117
+ }
118
+ }
119
+ ```
120
+
121
+ **Generic MCP client** (stdio transport):
122
+ ```json
123
+ {
124
+ "command": "npx",
125
+ "args": ["decantr", "mcp"],
126
+ "transport": "stdio"
127
+ }
128
+ ```
129
+
130
+ ### Examples
131
+
132
+ **1. Look up a component**
133
+
134
+ Prompt: *"What props does the Button component accept?"*
135
+
136
+ Tool call: `lookup_component` with `{ "name": "Button" }`
137
+
138
+ Response:
139
+ ```json
140
+ {
141
+ "found": true,
142
+ "name": "Button",
143
+ "props": {
144
+ "variant": { "type": "string", "values": ["solid", "outline", "ghost", "link", "destructive"], "default": "solid" },
145
+ "size": { "type": "string", "values": ["xs", "sm", "md", "lg"], "default": "md" },
146
+ "disabled": { "type": "boolean", "default": false },
147
+ "loading": { "type": "boolean", "default": false }
148
+ }
149
+ }
150
+ ```
151
+
152
+ **2. Resolve atoms**
153
+
154
+ Prompt: *"What CSS does `_flex _col _gap4 _p4` produce?"*
155
+
156
+ Tool call: `resolve_atoms` with `{ "atoms": "_flex _col _gap4 _p4" }`
157
+
158
+ Response:
159
+ ```json
160
+ {
161
+ "total": 4,
162
+ "valid": 4,
163
+ "invalid": 0,
164
+ "atoms": [
165
+ { "atom": "_flex", "css": "display:flex", "valid": true },
166
+ { "atom": "_col", "css": "flex-direction:column", "valid": true },
167
+ { "atom": "_gap4", "css": "gap:var(--d-space-4)", "valid": true },
168
+ { "atom": "_p4", "css": "padding:var(--d-space-4)", "valid": true }
169
+ ]
170
+ }
171
+ ```
172
+
173
+ **3. Search the registry**
174
+
175
+ Prompt: *"Find everything related to tables"*
176
+
177
+ Tool call: `search_registry` with `{ "query": "table" }`
178
+
179
+ Response:
180
+ ```json
181
+ {
182
+ "query": "table",
183
+ "total": 5,
184
+ "results": [
185
+ { "type": "component", "name": "DataTable", "score": 80 },
186
+ { "type": "pattern", "id": "data-table", "name": "Data Table", "score": 90 },
187
+ { "type": "pattern", "id": "pricing-table", "name": "Pricing Table", "score": 80 },
188
+ { "type": "component", "name": "Table", "score": 80 },
189
+ { "type": "pattern", "id": "table-of-contents", "name": "Table of Contents", "score": 80 }
190
+ ]
191
+ }
192
+ ```
193
+
194
+ ### Privacy
195
+
196
+ The MCP server runs locally via stdio. It reads only local registry JSON files shipped with the package. No data is collected, transmitted, or stored externally.
197
+
198
+ ## Documentation
199
+
200
+ ### Tutorial
201
+
202
+ A step-by-step guide from zero to deployed app:
203
+
204
+ 1. [Install & Setup](docs/tutorial/01-install.md) — Prerequisites, scaffolding, project structure
205
+ 2. [Your First Page](docs/tutorial/02-first-page.md) — Tag functions, atomic CSS, page pattern
206
+ 3. [Components](docs/tutorial/03-components.md) — Button, Card, DataTable, and 100+ built-in components
207
+ 4. [Styling](docs/tutorial/04-styling.md) — Atoms, styles, modes, design tokens, responsive prefixes
208
+ 5. [State](docs/tutorial/05-state.md) — Signals, effects, memos, stores, conditional and list rendering
209
+ 6. [Routing](docs/tutorial/06-routing.md) — Routes, guards, nested routes, lazy loading, navigation
210
+ 7. [Data Fetching](docs/tutorial/07-data.md) — Queries, mutations, caching, WebSocket, URL-driven state
211
+ 8. [Build & Deploy](docs/tutorial/08-deploy.md) — Production builds, static hosting, SPA routing config
212
+
213
+ ### Cookbook
214
+
215
+ Standalone recipes for common features:
216
+
217
+ - [SaaS Dashboard](docs/cookbook/dashboard.md) — Sidebar layout, KPI cards, charts, data tables, real-time updates
218
+ - [Authentication](docs/cookbook/auth.md) — Login, registration, protected routes, token management
219
+ - [Internationalization](docs/cookbook/i18n.md) — Multi-language support, locale switcher, RTL layouts
220
+ - [Data Fetching Patterns](docs/cookbook/data-fetching.md) — Caching, optimistic updates, infinite scroll, offline support
221
+ - [Forms](docs/cookbook/forms.md) — Validation, field arrays, multi-step forms, transforms
222
+
223
+ ## Requirements
224
+
225
+ - Node.js >= 20.0.0
226
+
227
+ ## License
228
+
229
+ MIT
package/cli/art.js ADDED
@@ -0,0 +1,127 @@
1
+ // Truecolor detection
2
+ const NO_COLOR = 'NO_COLOR' in process.env || !process.stdout.isTTY;
3
+ const TRUECOLOR = !NO_COLOR && (
4
+ process.env.FORCE_COLOR === '3' ||
5
+ process.env.COLORTERM === 'truecolor' ||
6
+ process.env.COLORTERM === '24bit' ||
7
+ /^(iTerm\.app|WezTerm|vscode)$/.test(process.env.TERM_PROGRAM || '')
8
+ );
9
+
10
+ // Basic ANSI fallbacks
11
+ const WINE = '\x1b[35m';
12
+ const DIM = '\x1b[2m';
13
+ const BOLD = '\x1b[1m';
14
+ const RESET = '\x1b[0m';
15
+ const CYAN = '\x1b[36m';
16
+ const GREEN = '\x1b[32m';
17
+
18
+ // Auradecantism brand palette (RGB tuples from src/css/styles/auradecantism.js)
19
+ const P = {
20
+ primary: [254, 68, 116], // #FE4474
21
+ accent: [10, 243, 235], // #0AF3EB
22
+ tertiary: [101, 0, 198], // #6500C6
23
+ success: [0, 195, 136], // #00C388
24
+ };
25
+
26
+ function fg(rgb) { return `\x1b[38;2;${rgb[0]};${rgb[1]};${rgb[2]}m`; }
27
+
28
+ function lerp(a, b, t) {
29
+ return [
30
+ Math.round(a[0] + (b[0] - a[0]) * t),
31
+ Math.round(a[1] + (b[1] - a[1]) * t),
32
+ Math.round(a[2] + (b[2] - a[2]) * t),
33
+ ];
34
+ }
35
+
36
+ function gradientText(str, from, to) {
37
+ if (NO_COLOR) return str;
38
+ if (!TRUECOLOR) return `${CYAN}${str}${RESET}`;
39
+ const n = str.length;
40
+ return str.split('').map((ch, i) =>
41
+ `${fg(lerp(from, to, n > 1 ? i / (n - 1) : 0))}${ch}`
42
+ ).join('') + RESET;
43
+ }
44
+
45
+ // Bottle geometry
46
+ const BOTTLE = [
47
+ ' ╭─────╮',
48
+ ' │ │',
49
+ ' ╰──┬──╯',
50
+ ' │',
51
+ ' ╭──┴──╮',
52
+ ' ╱ ╲',
53
+ ' ╱ ╲',
54
+ null, // "decantr" line — special handling
55
+ ' │ │',
56
+ ' ╲ ╱',
57
+ ' ╰───────╯',
58
+ ];
59
+
60
+ function buildBottle() {
61
+ if (NO_COLOR) {
62
+ return '\n' + BOTTLE.map((ln, i) =>
63
+ i === 7 ? ' │ decantr │' : ln
64
+ ).join('\n');
65
+ }
66
+ const n = BOTTLE.length;
67
+ return '\n' + BOTTLE.map((ln, i) => {
68
+ const t = i / (n - 1);
69
+ const c = TRUECOLOR ? fg(lerp(P.primary, P.tertiary, t)) : WINE;
70
+ if (i === 7) {
71
+ const nc = TRUECOLOR ? fg(P.accent) : CYAN;
72
+ return `${c} │ ${RESET}${BOLD}${nc}decantr${RESET}${c} │${RESET}`;
73
+ }
74
+ return `${c}${ln}${RESET}`;
75
+ }).join('\n');
76
+ }
77
+
78
+ const messages = [
79
+ 'Letting the code breathe...',
80
+ 'Decanting your project...',
81
+ 'Swirling the dependencies...',
82
+ 'Pouring the components...',
83
+ 'A fine vintage of JavaScript...',
84
+ 'Uncorking fresh signals...',
85
+ 'Aerating the DOM...',
86
+ 'Notes of CSS on the palate...',
87
+ 'Bottle-aged to perfection...',
88
+ 'Full-bodied, zero dependencies...'
89
+ ];
90
+
91
+ export function art() {
92
+ return buildBottle();
93
+ }
94
+
95
+ export function tagline() {
96
+ return messages[Math.floor(Math.random() * messages.length)];
97
+ }
98
+
99
+ export function welcome(version) {
100
+ const name = NO_COLOR ? 'decantr'
101
+ : TRUECOLOR ? `${BOLD}${fg(P.primary)}decantr${RESET}`
102
+ : `${BOLD}decantr${RESET}`;
103
+ const tl = gradientText(tagline(), P.accent, P.primary);
104
+ return `${art()}
105
+
106
+ ${name} ${DIM}v${version}${RESET} ${DIM}— AI-first web framework${RESET}
107
+ ${tl}
108
+ `;
109
+ }
110
+
111
+ export function success(msg) {
112
+ if (NO_COLOR) return `✓ ${msg}`;
113
+ const check = TRUECOLOR ? `${fg(P.success)}✓${RESET}` : `${GREEN}✓${RESET}`;
114
+ return `${check} ${msg}`;
115
+ }
116
+
117
+ export function info(msg) {
118
+ if (NO_COLOR) return `→ ${msg}`;
119
+ const arrow = TRUECOLOR ? `${fg(P.accent)}→${RESET}` : `${CYAN}→${RESET}`;
120
+ return `${arrow} ${msg}`;
121
+ }
122
+
123
+ export function heading(msg) {
124
+ if (NO_COLOR) return `\n ${msg}\n`;
125
+ const styled = TRUECOLOR ? `${BOLD}${fg(P.primary)}${msg}${RESET}` : `${BOLD}${msg}${RESET}`;
126
+ return `\n ${styled}\n`;
127
+ }
@@ -0,0 +1,61 @@
1
+ /**
2
+ * decantr a11y — Accessibility audit
3
+ *
4
+ * Static analysis for common WCAG violations in project source files.
5
+ * Scans all .js files under src/ and reports errors, warnings, and info.
6
+ */
7
+
8
+ import { readdir } from 'node:fs/promises';
9
+ import { join, extname } from 'node:path';
10
+ import { auditFiles, formatIssues } from '../../tools/a11y-audit.js';
11
+ import { heading, success } from '../art.js';
12
+
13
+ export async function run() {
14
+ const cwd = process.cwd();
15
+ console.log(heading('Running accessibility audit...'));
16
+
17
+ // Collect all .js files from src/
18
+ const files = await collectJsFiles(join(cwd, 'src'));
19
+
20
+ if (files.length === 0) {
21
+ console.log(' No source files found in src/');
22
+ return;
23
+ }
24
+
25
+ console.log(` Scanning ${files.length} file(s)...\n`);
26
+
27
+ const issues = await auditFiles(files);
28
+ console.log(formatIssues(issues));
29
+
30
+ const errors = issues.filter(i => i.severity === 'error');
31
+ if (errors.length === 0) {
32
+ console.log(` ${success('Accessibility audit passed')}\n`);
33
+ } else {
34
+ process.exitCode = 1;
35
+ }
36
+ }
37
+
38
+ /**
39
+ * Recursively collect .js files from a directory.
40
+ * Skips node_modules, dist, and hidden directories.
41
+ * @param {string} dir
42
+ * @returns {Promise<string[]>}
43
+ */
44
+ async function collectJsFiles(dir) {
45
+ const results = [];
46
+ try {
47
+ const entries = await readdir(dir, { withFileTypes: true });
48
+ for (const entry of entries) {
49
+ const full = join(dir, entry.name);
50
+ if (entry.isDirectory()) {
51
+ if (entry.name === 'node_modules' || entry.name === 'dist' || entry.name.startsWith('.')) continue;
52
+ results.push(...await collectJsFiles(full));
53
+ } else if (extname(entry.name) === '.js') {
54
+ results.push(full);
55
+ }
56
+ }
57
+ } catch {
58
+ // Skip inaccessible directories
59
+ }
60
+ return results;
61
+ }
@@ -0,0 +1,225 @@
1
+ import { readFile } from 'node:fs/promises';
2
+ import { join } from 'node:path';
3
+ import { parseArgs } from 'node:util';
4
+ import { audit } from '../../tools/audit.js';
5
+
6
+ const BOLD = '\x1b[1m';
7
+ const DIM = '\x1b[2m';
8
+ const RED = '\x1b[31m';
9
+ const GREEN = '\x1b[32m';
10
+ const YELLOW = '\x1b[33m';
11
+ const CYAN = '\x1b[36m';
12
+ const RESET = '\x1b[0m';
13
+
14
+ const CHECK = `${GREEN}\u2713${RESET}`;
15
+ const CROSS = `${RED}\u2717${RESET}`;
16
+ const WARN = `${YELLOW}\u26A0${RESET}`;
17
+
18
+ function formatSize(bytes) {
19
+ if (bytes < 1024) return `${bytes} B`;
20
+ if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KB`;
21
+ return `${(bytes / (1024 * 1024)).toFixed(2)} MB`;
22
+ }
23
+
24
+ function sectionHeader(title) {
25
+ return ` ${BOLD}${CYAN}${title}${RESET}`;
26
+ }
27
+
28
+ function printEssence(report) {
29
+ console.log(sectionHeader('Essence'));
30
+ if (!report.essence) {
31
+ console.log(` ${CROSS} No decantr.essence.json found`);
32
+ return;
33
+ }
34
+ const e = report.essence;
35
+ console.log(` ${e.valid ? CHECK : CROSS} ${e.valid ? 'Valid' : 'Invalid'}`);
36
+ if (e.terroir) console.log(` ${DIM}terroir:${RESET} ${e.terroir}`);
37
+ if (e.stagesCompleted != null || e.stagesSkipped != null) {
38
+ const completed = e.stagesCompleted || 0;
39
+ const skipped = e.stagesSkipped || 0;
40
+ const total = completed + skipped;
41
+ console.log(` ${DIM}stages:${RESET} ${completed}/${total} completed${skipped > 0 ? ` ${YELLOW}(${skipped} skipped)${RESET}` : ''}`);
42
+ }
43
+ console.log();
44
+ }
45
+
46
+ function printCoverage(report) {
47
+ console.log(sectionHeader('Coverage'));
48
+ const c = report.coverage;
49
+ if (!c) { console.log(` ${DIM}No coverage data${RESET}\n`); return; }
50
+
51
+ const pct = c.frameworkDerivedPct != null ? c.frameworkDerivedPct : 0;
52
+ const pctColor = pct >= 80 ? GREEN : pct >= 50 ? YELLOW : RED;
53
+ console.log(` ${DIM}framework-derived:${RESET} ${pctColor}${pct}%${RESET}`);
54
+
55
+ if (c.components) {
56
+ const { used, total } = c.components;
57
+ console.log(` ${DIM}components:${RESET} ${used}/${total}`);
58
+ }
59
+ if (c.patterns) {
60
+ const { used, total } = c.patterns;
61
+ console.log(` ${DIM}patterns:${RESET} ${used}/${total}`);
62
+ }
63
+ if (c.atomCalls != null) {
64
+ console.log(` ${DIM}atom calls:${RESET} ${c.atomCalls}`);
65
+ }
66
+ if (c.violations != null && c.violations > 0) {
67
+ console.log(` ${CROSS} ${RED}${c.violations} violation(s)${RESET}`);
68
+ }
69
+ console.log();
70
+ }
71
+
72
+ function printQuality(report) {
73
+ console.log(sectionHeader('Quality'));
74
+ const q = report.quality;
75
+ if (!q) { console.log(` ${DIM}No quality data${RESET}\n`); return; }
76
+
77
+ const sections = [
78
+ { key: 'hardcodedCSS', label: 'hardcoded CSS' },
79
+ { key: 'missingAria', label: 'missing ARIA' },
80
+ { key: 'leakedListeners', label: 'leaked listeners' }
81
+ ];
82
+
83
+ let clean = true;
84
+ for (const { key, label } of sections) {
85
+ const items = q[key];
86
+ if (!Array.isArray(items) || items.length === 0) continue;
87
+ clean = false;
88
+ console.log(` ${CROSS} ${RED}${items.length}${RESET} ${label}`);
89
+ for (const item of items) {
90
+ const loc = item.file ? `${DIM}${item.file}${item.line != null ? `:${item.line}` : ''}${RESET}` : '';
91
+ const msg = item.message || item.detail || '';
92
+ console.log(` ${DIM}-${RESET} ${loc}${msg ? ` ${msg}` : ''}`);
93
+ }
94
+ }
95
+
96
+ if (clean) {
97
+ console.log(` ${CHECK} No violations`);
98
+ }
99
+ console.log();
100
+ }
101
+
102
+ function printGaps(report) {
103
+ console.log(sectionHeader('Gaps'));
104
+ const g = report.gaps;
105
+ if (!g) { console.log(` ${DIM}No gap data${RESET}\n`); return; }
106
+
107
+ let hasGaps = false;
108
+
109
+ if (Array.isArray(g.unusedPatterns) && g.unusedPatterns.length > 0) {
110
+ hasGaps = true;
111
+ console.log(` ${WARN} ${YELLOW}${g.unusedPatterns.length}${RESET} unused pattern(s): ${DIM}${g.unusedPatterns.join(', ')}${RESET}`);
112
+ }
113
+ if (Array.isArray(g.missingPages) && g.missingPages.length > 0) {
114
+ hasGaps = true;
115
+ console.log(` ${WARN} ${YELLOW}${g.missingPages.length}${RESET} missing page(s): ${DIM}${g.missingPages.join(', ')}${RESET}`);
116
+ }
117
+ if (Array.isArray(g.unimplementedTannins) && g.unimplementedTannins.length > 0) {
118
+ hasGaps = true;
119
+ console.log(` ${WARN} ${YELLOW}${g.unimplementedTannins.length}${RESET} unimplemented tannin(s): ${DIM}${g.unimplementedTannins.join(', ')}${RESET}`);
120
+ }
121
+
122
+ if (!hasGaps) {
123
+ console.log(` ${CHECK} No gaps detected`);
124
+ }
125
+ console.log();
126
+ }
127
+
128
+ function printBundle(report) {
129
+ console.log(sectionHeader('Bundle'));
130
+ const b = report.bundle;
131
+ if (!b) { console.log(` ${DIM}No bundle data (run decantr build first)${RESET}\n`); return; }
132
+
133
+ const rows = [];
134
+ for (const asset of ['js', 'css', 'html']) {
135
+ const entry = b[asset];
136
+ if (!entry) continue;
137
+ rows.push({
138
+ type: asset.toUpperCase(),
139
+ raw: entry.raw != null ? formatSize(entry.raw) : '-',
140
+ gzip: entry.gzip != null ? formatSize(entry.gzip) : '-',
141
+ brotli: entry.brotli != null ? formatSize(entry.brotli) : '-'
142
+ });
143
+ }
144
+
145
+ if (rows.length === 0) {
146
+ console.log(` ${DIM}No bundle data (run decantr build first)${RESET}\n`);
147
+ return;
148
+ }
149
+
150
+ // Column widths
151
+ const typeW = 6;
152
+ const colW = 12;
153
+ const header = ` ${''.padEnd(typeW)}${'Raw'.padStart(colW)}${'Gzip'.padStart(colW)}${'Brotli'.padStart(colW)}`;
154
+ console.log(`${DIM}${header}${RESET}`);
155
+ for (const row of rows) {
156
+ console.log(` ${BOLD}${row.type.padEnd(typeW)}${RESET}${row.raw.padStart(colW)}${row.gzip.padStart(colW)}${row.brotli.padStart(colW)}`);
157
+ }
158
+
159
+ if (b.total != null) {
160
+ console.log(`${DIM} ${'------'.padEnd(typeW)}${'--------'.padStart(colW)}${'--------'.padStart(colW)}${'--------'.padStart(colW)}${RESET}`);
161
+ const total = b.total;
162
+ console.log(` ${BOLD}${'Total'.padEnd(typeW)}${RESET}${(total.raw != null ? formatSize(total.raw) : '-').padStart(colW)}${(total.gzip != null ? formatSize(total.gzip) : '-').padStart(colW)}${(total.brotli != null ? formatSize(total.brotli) : '-').padStart(colW)}`);
163
+ }
164
+ console.log();
165
+ }
166
+
167
+ export async function run() {
168
+ const { values } = parseArgs({
169
+ options: {
170
+ json: { type: 'boolean', default: false },
171
+ ci: { type: 'boolean', default: false }
172
+ },
173
+ strict: false
174
+ });
175
+
176
+ const cwd = process.cwd();
177
+ console.log(`\n ${BOLD}decantr audit${RESET}\n`);
178
+
179
+ let report;
180
+ try {
181
+ report = await audit(cwd);
182
+ } catch (err) {
183
+ console.error(` ${CROSS} Audit failed: ${err.message}`);
184
+ process.exitCode = 1;
185
+ return;
186
+ }
187
+
188
+ if (values.json) {
189
+ console.log(JSON.stringify(report, null, 2));
190
+ if (values.ci && hasViolations(report)) process.exitCode = 1;
191
+ return;
192
+ }
193
+
194
+ printEssence(report);
195
+ printCoverage(report);
196
+ printQuality(report);
197
+ printGaps(report);
198
+ printBundle(report);
199
+
200
+ // Summary line
201
+ const violations = countViolations(report);
202
+ if (violations === 0) {
203
+ console.log(` ${CHECK} ${GREEN}Audit passed${RESET}\n`);
204
+ } else {
205
+ console.log(` ${CROSS} ${RED}${violations} violation(s) found${RESET}\n`);
206
+ }
207
+
208
+ if (values.ci && hasViolations(report)) {
209
+ process.exitCode = 1;
210
+ }
211
+ }
212
+
213
+ function countViolations(report) {
214
+ let count = 0;
215
+ const q = report.quality;
216
+ if (!q) return 0;
217
+ if (Array.isArray(q.hardcodedCSS)) count += q.hardcodedCSS.length;
218
+ if (Array.isArray(q.missingAria)) count += q.missingAria.length;
219
+ if (Array.isArray(q.leakedListeners)) count += q.leakedListeners.length;
220
+ return count;
221
+ }
222
+
223
+ function hasViolations(report) {
224
+ return countViolations(report) > 0;
225
+ }
@@ -0,0 +1,38 @@
1
+ import { readFile } from 'node:fs/promises';
2
+ import { join } from 'node:path';
3
+
4
+ export async function run() {
5
+ const cwd = process.cwd();
6
+ let config = {
7
+ build: {
8
+ outDir: 'dist',
9
+ inline: false,
10
+ sourcemap: true,
11
+ analyze: true,
12
+ incremental: true,
13
+ codeSplit: true,
14
+ purgeCSS: true,
15
+ treeShake: true
16
+ }
17
+ };
18
+
19
+ try {
20
+ const raw = await readFile(join(cwd, 'decantr.config.json'), 'utf-8');
21
+ const userConfig = JSON.parse(raw);
22
+ config.build = { ...config.build, ...(userConfig.build || {}) };
23
+ } catch (e) {
24
+ // Use defaults
25
+ }
26
+
27
+ // CLI flag overrides
28
+ const args = process.argv.slice(2);
29
+ if (args.includes('--no-sourcemap')) config.build.sourcemap = false;
30
+ if (args.includes('--no-analyze')) config.build.analyze = false;
31
+ if (args.includes('--no-incremental')) config.build.incremental = false;
32
+ if (args.includes('--no-code-split')) config.build.codeSplit = false;
33
+ if (args.includes('--no-purge')) config.build.purgeCSS = false;
34
+ if (args.includes('--no-tree-shake')) config.build.treeShake = false;
35
+
36
+ const { build } = await import('../../tools/builder.js');
37
+ await build(cwd, config.build);
38
+ }
@@ -0,0 +1,18 @@
1
+ import { readFile } from 'node:fs/promises';
2
+ import { join } from 'node:path';
3
+
4
+ export async function run() {
5
+ const cwd = process.cwd();
6
+ let config = { dev: { port: 3000 } };
7
+
8
+ try {
9
+ const raw = await readFile(join(cwd, 'decantr.config.json'), 'utf-8');
10
+ config = JSON.parse(raw);
11
+ } catch (e) {
12
+ // Use defaults
13
+ }
14
+
15
+ const port = config.dev?.port || 3000;
16
+ const { startDevServer } = await import('../../tools/dev-server.js');
17
+ startDevServer(cwd, port);
18
+ }