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.
- package/AGENTS.md +868 -0
- package/CHANGELOG.md +255 -0
- package/CLAUDE.md +178 -0
- package/LICENSE +21 -0
- package/README.md +229 -0
- package/cli/art.js +127 -0
- package/cli/commands/a11y.js +61 -0
- package/cli/commands/audit.js +225 -0
- package/cli/commands/build.js +38 -0
- package/cli/commands/dev.js +18 -0
- package/cli/commands/doctor.js +197 -0
- package/cli/commands/figma-sync.js +48 -0
- package/cli/commands/figma-tokens.js +55 -0
- package/cli/commands/generate.js +26 -0
- package/cli/commands/init.js +116 -0
- package/cli/commands/lint.js +209 -0
- package/cli/commands/mcp.js +530 -0
- package/cli/commands/migrate.js +175 -0
- package/cli/commands/test.js +38 -0
- package/cli/commands/validate.js +354 -0
- package/cli/index.js +113 -0
- package/package.json +95 -0
- package/reference/atoms.md +517 -0
- package/reference/behaviors.md +384 -0
- package/reference/build-tooling.md +275 -0
- package/reference/color-guidelines.md +965 -0
- package/reference/component-lifecycle.md +137 -0
- package/reference/compound-spacing.md +95 -0
- package/reference/decantation-process.md +499 -0
- package/reference/dev-server-routes.md +93 -0
- package/reference/form-system.md +253 -0
- package/reference/i18n.md +336 -0
- package/reference/icons.md +576 -0
- package/reference/llm-primer.md +953 -0
- package/reference/plugins.md +252 -0
- package/reference/registry-consumption.md +76 -0
- package/reference/router.md +217 -0
- package/reference/shells.md +116 -0
- package/reference/spatial-guidelines.md +541 -0
- package/reference/ssr.md +234 -0
- package/reference/state-data.md +215 -0
- package/reference/state-patterns.md +166 -0
- package/reference/state.md +194 -0
- package/reference/style-system.md +110 -0
- package/reference/tokens.md +460 -0
- package/src/app.js +19 -0
- package/src/chart/_animate.js +266 -0
- package/src/chart/_base.js +109 -0
- package/src/chart/_data.js +209 -0
- package/src/chart/_format.js +106 -0
- package/src/chart/_interact.js +364 -0
- package/src/chart/_palette.js +105 -0
- package/src/chart/_renderer.js +52 -0
- package/src/chart/_scene.js +262 -0
- package/src/chart/_shared.js +371 -0
- package/src/chart/index.js +637 -0
- package/src/chart/layouts/_layout-base.js +328 -0
- package/src/chart/layouts/cartesian.js +148 -0
- package/src/chart/layouts/hierarchy.js +562 -0
- package/src/chart/layouts/polar.js +101 -0
- package/src/chart/renderers/canvas.js +179 -0
- package/src/chart/renderers/svg.js +256 -0
- package/src/chart/renderers/webgpu.js +715 -0
- package/src/chart/types/_type-base.js +26 -0
- package/src/chart/types/area.js +134 -0
- package/src/chart/types/bar.js +173 -0
- package/src/chart/types/box-plot.js +125 -0
- package/src/chart/types/bubble.js +63 -0
- package/src/chart/types/candlestick.js +115 -0
- package/src/chart/types/chord.js +85 -0
- package/src/chart/types/combination.js +108 -0
- package/src/chart/types/funnel.js +68 -0
- package/src/chart/types/gauge.js +163 -0
- package/src/chart/types/heatmap.js +98 -0
- package/src/chart/types/histogram.js +71 -0
- package/src/chart/types/line.js +111 -0
- package/src/chart/types/org-chart.js +93 -0
- package/src/chart/types/pie.js +81 -0
- package/src/chart/types/radar.js +96 -0
- package/src/chart/types/radial.js +68 -0
- package/src/chart/types/range-area.js +55 -0
- package/src/chart/types/range-bar.js +61 -0
- package/src/chart/types/sankey.js +73 -0
- package/src/chart/types/scatter.js +66 -0
- package/src/chart/types/sparkline.js +81 -0
- package/src/chart/types/sunburst.js +69 -0
- package/src/chart/types/swimlane.js +88 -0
- package/src/chart/types/treemap.js +62 -0
- package/src/chart/types/waterfall.js +100 -0
- package/src/components/_base.js +1658 -0
- package/src/components/_behaviors.js +1140 -0
- package/src/components/_primitives.js +534 -0
- package/src/components/_qr-encoder.js +539 -0
- package/src/components/accordion.js +207 -0
- package/src/components/affix.js +62 -0
- package/src/components/alert-dialog.js +75 -0
- package/src/components/alert.js +47 -0
- package/src/components/aspect-ratio.js +24 -0
- package/src/components/avatar-group.js +55 -0
- package/src/components/avatar.js +38 -0
- package/src/components/back-top.js +75 -0
- package/src/components/badge.js +74 -0
- package/src/components/banner.js +68 -0
- package/src/components/breadcrumb.js +162 -0
- package/src/components/button.js +115 -0
- package/src/components/calendar.js +131 -0
- package/src/components/card.js +192 -0
- package/src/components/carousel.js +98 -0
- package/src/components/cascader.js +261 -0
- package/src/components/checkbox.js +80 -0
- package/src/components/chip.js +81 -0
- package/src/components/code-block.js +82 -0
- package/src/components/collapsible.js +50 -0
- package/src/components/color-palette.js +438 -0
- package/src/components/color-picker.js +314 -0
- package/src/components/combobox.js +181 -0
- package/src/components/command.js +174 -0
- package/src/components/comment.js +206 -0
- package/src/components/context-menu.js +76 -0
- package/src/components/data-table.js +724 -0
- package/src/components/date-picker.js +217 -0
- package/src/components/date-range-picker.js +244 -0
- package/src/components/datetime-picker.js +271 -0
- package/src/components/descriptions.js +68 -0
- package/src/components/drawer.js +179 -0
- package/src/components/dropdown.js +88 -0
- package/src/components/empty.js +41 -0
- package/src/components/float-button.js +90 -0
- package/src/components/form.js +106 -0
- package/src/components/hover-card.js +49 -0
- package/src/components/icon.js +87 -0
- package/src/components/image.js +97 -0
- package/src/components/index.js +117 -0
- package/src/components/input-group.js +75 -0
- package/src/components/input-number.js +155 -0
- package/src/components/input-otp.js +178 -0
- package/src/components/input.js +91 -0
- package/src/components/kbd.js +36 -0
- package/src/components/label.js +25 -0
- package/src/components/list.js +118 -0
- package/src/components/masked-input.js +236 -0
- package/src/components/mentions.js +165 -0
- package/src/components/menu.js +259 -0
- package/src/components/message.js +80 -0
- package/src/components/modal.js +147 -0
- package/src/components/navigation-menu.js +166 -0
- package/src/components/notification.js +84 -0
- package/src/components/pagination.js +104 -0
- package/src/components/placeholder.js +132 -0
- package/src/components/popconfirm.js +70 -0
- package/src/components/popover.js +58 -0
- package/src/components/progress.js +61 -0
- package/src/components/qrcode.js +251 -0
- package/src/components/radiogroup.js +120 -0
- package/src/components/range-slider.js +176 -0
- package/src/components/rate.js +186 -0
- package/src/components/resizable.js +83 -0
- package/src/components/result.js +57 -0
- package/src/components/scroll-area.js +43 -0
- package/src/components/segmented.js +97 -0
- package/src/components/select.js +165 -0
- package/src/components/separator.js +31 -0
- package/src/components/shell.js +407 -0
- package/src/components/skeleton.js +39 -0
- package/src/components/slider.js +141 -0
- package/src/components/sortable-list.js +176 -0
- package/src/components/space.js +42 -0
- package/src/components/spinner.js +112 -0
- package/src/components/splitter.js +147 -0
- package/src/components/statistic.js +136 -0
- package/src/components/steps.js +99 -0
- package/src/components/switch.js +95 -0
- package/src/components/table.js +44 -0
- package/src/components/tabs.js +216 -0
- package/src/components/tag.js +115 -0
- package/src/components/textarea.js +82 -0
- package/src/components/time-picker.js +153 -0
- package/src/components/time-range-picker.js +170 -0
- package/src/components/timeline.js +226 -0
- package/src/components/toast.js +71 -0
- package/src/components/toggle.js +213 -0
- package/src/components/tooltip.js +57 -0
- package/src/components/tour.js +159 -0
- package/src/components/transfer.js +163 -0
- package/src/components/tree-select.js +274 -0
- package/src/components/tree.js +141 -0
- package/src/components/typography.js +136 -0
- package/src/components/upload.js +118 -0
- package/src/components/visually-hidden.js +20 -0
- package/src/components/watermark.js +124 -0
- package/src/core/index.js +539 -0
- package/src/core/lifecycle.js +69 -0
- package/src/css/atoms.js +651 -0
- package/src/css/components.js +940 -0
- package/src/css/derive.js +1296 -0
- package/src/css/index.js +265 -0
- package/src/css/runtime.js +268 -0
- package/src/css/styles/addons/bioluminescent.js +93 -0
- package/src/css/styles/addons/clay.js +70 -0
- package/src/css/styles/addons/clean.js +57 -0
- package/src/css/styles/addons/command-center.js +143 -0
- package/src/css/styles/addons/dopamine.js +83 -0
- package/src/css/styles/addons/editorial.js +80 -0
- package/src/css/styles/addons/glassmorphism.js +99 -0
- package/src/css/styles/addons/liquid-glass.js +105 -0
- package/src/css/styles/addons/prismatic.js +100 -0
- package/src/css/styles/addons/retro.js +63 -0
- package/src/css/styles/auradecantism.js +96 -0
- package/src/css/theme-registry.js +444 -0
- package/src/data/entity.js +281 -0
- package/src/data/index.js +13 -0
- package/src/data/persist.js +225 -0
- package/src/data/query.js +839 -0
- package/src/data/realtime.js +299 -0
- package/src/data/url.js +177 -0
- package/src/data/worker.js +134 -0
- package/src/explorer/archetypes.js +243 -0
- package/src/explorer/atoms.js +228 -0
- package/src/explorer/charts.js +497 -0
- package/src/explorer/components.js +129 -0
- package/src/explorer/foundations.js +949 -0
- package/src/explorer/icons.js +178 -0
- package/src/explorer/patterns.js +247 -0
- package/src/explorer/recipes.js +194 -0
- package/src/explorer/shared/pattern-examples.js +1337 -0
- package/src/explorer/shared/showcase-renderer.js +958 -0
- package/src/explorer/shared/spec-table.js +41 -0
- package/src/explorer/shared/usage-links.js +87 -0
- package/src/explorer/shell-config.js +10 -0
- package/src/explorer/shells.js +551 -0
- package/src/explorer/styles.js +161 -0
- package/src/explorer/tokens.js +262 -0
- package/src/explorer/tools.js +525 -0
- package/src/form/index.js +804 -0
- package/src/i18n/index.js +251 -0
- package/src/icons/essential.js +479 -0
- package/src/icons/index.js +53 -0
- package/src/plugins/index.js +282 -0
- package/src/registry/archetypes/content-site.json +71 -0
- package/src/registry/archetypes/docs-explorer.json +23 -0
- package/src/registry/archetypes/ecommerce.json +104 -0
- package/src/registry/archetypes/financial-dashboard.json +77 -0
- package/src/registry/archetypes/index.json +41 -0
- package/src/registry/archetypes/portfolio.json +82 -0
- package/src/registry/archetypes/recipe-community.json +159 -0
- package/src/registry/archetypes/saas-dashboard.json +86 -0
- package/src/registry/architect/cross-cutting.json +45 -0
- package/src/registry/architect/domains/ecommerce.json +294 -0
- package/src/registry/architect/domains/financial-services.json +302 -0
- package/src/registry/architect/index.json +26 -0
- package/src/registry/architect/traits.json +379 -0
- package/src/registry/atoms.json +16 -0
- package/src/registry/chart-showcase.json +160 -0
- package/src/registry/chart.json +136 -0
- package/src/registry/components.json +8616 -0
- package/src/registry/core.json +216 -0
- package/src/registry/css.json +319 -0
- package/src/registry/data.json +135 -0
- package/src/registry/foundations.json +11 -0
- package/src/registry/icons.json +463 -0
- package/src/registry/index.json +101 -0
- package/src/registry/patterns/activity-feed.json +37 -0
- package/src/registry/patterns/article-content.json +27 -0
- package/src/registry/patterns/auth-form.json +37 -0
- package/src/registry/patterns/author-card.json +20 -0
- package/src/registry/patterns/card-grid.json +127 -0
- package/src/registry/patterns/category-nav.json +26 -0
- package/src/registry/patterns/chart-grid.json +36 -0
- package/src/registry/patterns/chat-interface.json +37 -0
- package/src/registry/patterns/checklist-card.json +55 -0
- package/src/registry/patterns/comparison-panel.json +27 -0
- package/src/registry/patterns/component-showcase.json +24 -0
- package/src/registry/patterns/contact-form.json +31 -0
- package/src/registry/patterns/cta-section.json +20 -0
- package/src/registry/patterns/data-table.json +37 -0
- package/src/registry/patterns/detail-header.json +83 -0
- package/src/registry/patterns/detail-panel.json +27 -0
- package/src/registry/patterns/explorer-shell.json +22 -0
- package/src/registry/patterns/filter-bar.json +33 -0
- package/src/registry/patterns/filter-sidebar.json +27 -0
- package/src/registry/patterns/form-sections.json +110 -0
- package/src/registry/patterns/goal-tracker.json +27 -0
- package/src/registry/patterns/hero.json +107 -0
- package/src/registry/patterns/index.json +47 -0
- package/src/registry/patterns/kpi-grid.json +36 -0
- package/src/registry/patterns/media-gallery.json +20 -0
- package/src/registry/patterns/order-history.json +20 -0
- package/src/registry/patterns/pagination.json +19 -0
- package/src/registry/patterns/photo-to-recipe.json +36 -0
- package/src/registry/patterns/pipeline-tracker.json +28 -0
- package/src/registry/patterns/post-list.json +27 -0
- package/src/registry/patterns/pricing-table.json +32 -0
- package/src/registry/patterns/scorecard.json +28 -0
- package/src/registry/patterns/search-bar.json +20 -0
- package/src/registry/patterns/specimen-grid.json +19 -0
- package/src/registry/patterns/stat-card.json +55 -0
- package/src/registry/patterns/stats-bar.json +55 -0
- package/src/registry/patterns/steps-card.json +55 -0
- package/src/registry/patterns/table-of-contents.json +19 -0
- package/src/registry/patterns/testimonials.json +21 -0
- package/src/registry/patterns/timeline.json +27 -0
- package/src/registry/patterns/token-inspector.json +21 -0
- package/src/registry/patterns/wizard.json +27 -0
- package/src/registry/recipe-auradecantism.json +69 -0
- package/src/registry/recipe-clean.json +65 -0
- package/src/registry/recipe-command-center.json +78 -0
- package/src/registry/router.json +73 -0
- package/src/registry/schema/README.md +197 -0
- package/src/registry/skeletons.json +259 -0
- package/src/registry/state.json +137 -0
- package/src/registry/tokens.json +40 -0
- package/src/router/hash.js +17 -0
- package/src/router/history.js +18 -0
- package/src/router/index.js +598 -0
- package/src/ssr/index.js +922 -0
- package/src/state/arrays.js +181 -0
- package/src/state/devtools.js +647 -0
- package/src/state/index.js +498 -0
- package/src/state/middleware.js +288 -0
- package/src/state/scheduler.js +206 -0
- package/src/state/store.js +300 -0
- package/src/tags/index.js +19 -0
- package/src/tannins/auth.js +396 -0
- package/src/test/dom.js +352 -0
- package/src/test/index.js +62 -0
- package/src/test/state.js +306 -0
- package/tools/a11y-audit.js +487 -0
- package/tools/analyzer.js +315 -0
- package/tools/audit.js +706 -0
- package/tools/builder.js +1422 -0
- package/tools/css-extract.js +188 -0
- package/tools/dev-server.js +316 -0
- package/tools/dts-gen.js +1260 -0
- package/tools/figma-components.js +329 -0
- package/tools/figma-patterns.js +516 -0
- package/tools/figma-plugin/code.js +453 -0
- package/tools/figma-plugin/manifest.json +14 -0
- package/tools/figma-plugin/ui.html +268 -0
- package/tools/figma-render.js +293 -0
- package/tools/figma-tokens.js +712 -0
- package/tools/figma-upload.js +318 -0
- package/tools/generate.js +738 -0
- package/tools/icons.js +133 -0
- package/tools/init-templates.js +265 -0
- package/tools/install-hooks.sh +5 -0
- package/tools/migrations/0.5.0.js +53 -0
- package/tools/migrations/0.6.0.js +95 -0
- package/tools/minify.js +170 -0
- package/tools/pre-commit +4 -0
- package/tools/registry.js +662 -0
- package/tools/reset-playground.js +61 -0
- package/tools/starter-templates/content-site/app.js +49 -0
- package/tools/starter-templates/content-site/essence.js +19 -0
- package/tools/starter-templates/content-site/pages.js +31 -0
- package/tools/starter-templates/ecommerce/app.js +50 -0
- package/tools/starter-templates/ecommerce/essence.js +19 -0
- package/tools/starter-templates/ecommerce/pages.js +31 -0
- package/tools/starter-templates/landing-page/app.js +38 -0
- package/tools/starter-templates/landing-page/essence.js +18 -0
- package/tools/starter-templates/landing-page/pages.js +21 -0
- package/tools/starter-templates/portfolio/app.js +45 -0
- package/tools/starter-templates/portfolio/essence.js +19 -0
- package/tools/starter-templates/portfolio/pages.js +33 -0
- package/tools/starter-templates/saas-dashboard/app.js +70 -0
- package/tools/starter-templates/saas-dashboard/essence.js +19 -0
- package/tools/starter-templates/saas-dashboard/pages.js +31 -0
- package/tools/verify-pack.js +203 -0
- package/types/chart.d.ts +77 -0
- package/types/components.d.ts +587 -0
- package/types/core.d.ts +89 -0
- package/types/css.d.ts +149 -0
- package/types/data.d.ts +238 -0
- package/types/form.d.ts +164 -0
- package/types/i18n.d.ts +51 -0
- package/types/icons.d.ts +27 -0
- package/types/index.d.ts +13 -0
- package/types/router.d.ts +116 -0
- package/types/ssr.d.ts +102 -0
- package/types/state.d.ts +83 -0
- package/types/tags.d.ts +62 -0
- package/types/tannins.d.ts +63 -0
- package/types/test.d.ts +48 -0
package/README.md
ADDED
|
@@ -0,0 +1,229 @@
|
|
|
1
|
+
# decantr
|
|
2
|
+
|
|
3
|
+
[](https://github.com/decantr-ai/decantr/actions/workflows/ci.yml)
|
|
4
|
+
[](https://www.npmjs.com/package/decantr)
|
|
5
|
+
[](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
|
+
}
|