astralyx-ui 0.1.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 (279) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +134 -0
  3. package/bin/index.d.ts +2 -0
  4. package/bin/index.js +404 -0
  5. package/bin/registry.d.ts +37 -0
  6. package/bin/registry.js +69 -0
  7. package/package.json +89 -0
  8. package/registry/index.json +3914 -0
  9. package/registry/items/accordion.json +22 -0
  10. package/registry/items/address-input.json +21 -0
  11. package/registry/items/agent-steps.json +23 -0
  12. package/registry/items/alert-dialog.json +22 -0
  13. package/registry/items/alert-triage.json +25 -0
  14. package/registry/items/alert.json +21 -0
  15. package/registry/items/anomaly-chart.json +18 -0
  16. package/registry/items/api-keys.json +25 -0
  17. package/registry/items/aspect-ratio.json +18 -0
  18. package/registry/items/attachment-preview.json +23 -0
  19. package/registry/items/audit-log.json +26 -0
  20. package/registry/items/avatar.json +21 -0
  21. package/registry/items/badge.json +22 -0
  22. package/registry/items/banner.json +22 -0
  23. package/registry/items/benchmark-table.json +20 -0
  24. package/registry/items/bet-input.json +23 -0
  25. package/registry/items/blame-view.json +22 -0
  26. package/registry/items/branch-select.json +21 -0
  27. package/registry/items/breadcrumb.json +22 -0
  28. package/registry/items/bridge-status.json +24 -0
  29. package/registry/items/build-log.json +23 -0
  30. package/registry/items/bulk-action-bar.json +21 -0
  31. package/registry/items/bundle-treemap.json +20 -0
  32. package/registry/items/button-group.json +21 -0
  33. package/registry/items/button.json +22 -0
  34. package/registry/items/cache-stats.json +21 -0
  35. package/registry/items/calendar.json +25 -0
  36. package/registry/items/campaign-card.json +22 -0
  37. package/registry/items/candlestick-chart.json +18 -0
  38. package/registry/items/card-input.json +22 -0
  39. package/registry/items/card.json +21 -0
  40. package/registry/items/carousel.json +22 -0
  41. package/registry/items/cart.json +23 -0
  42. package/registry/items/chain-select.json +24 -0
  43. package/registry/items/changelog-entry.json +21 -0
  44. package/registry/items/chart.json +19 -0
  45. package/registry/items/checkbox.json +22 -0
  46. package/registry/items/checkout-summary.json +20 -0
  47. package/registry/items/citations.json +22 -0
  48. package/registry/items/code-block.json +23 -0
  49. package/registry/items/code-search.json +22 -0
  50. package/registry/items/cohort-table.json +19 -0
  51. package/registry/items/collapsible.json +23 -0
  52. package/registry/items/color-picker.json +24 -0
  53. package/registry/items/combobox.json +23 -0
  54. package/registry/items/command.json +23 -0
  55. package/registry/items/commit-graph.json +20 -0
  56. package/registry/items/commit-list.json +26 -0
  57. package/registry/items/composer.json +30 -0
  58. package/registry/items/confidence-meter.json +18 -0
  59. package/registry/items/connection-string.json +24 -0
  60. package/registry/items/container-list.json +24 -0
  61. package/registry/items/context-menu.json +20 -0
  62. package/registry/items/context-picker.json +25 -0
  63. package/registry/items/coordinate-input.json +21 -0
  64. package/registry/items/copy-button.json +22 -0
  65. package/registry/items/cost-breakdown.json +20 -0
  66. package/registry/items/coverage-report.json +22 -0
  67. package/registry/items/cron-schedule.json +24 -0
  68. package/registry/items/curl-command.json +18 -0
  69. package/registry/items/data-grid.json +24 -0
  70. package/registry/items/date-picker.json +24 -0
  71. package/registry/items/date-range-compare.json +24 -0
  72. package/registry/items/dependency-list.json +23 -0
  73. package/registry/items/deploy-list.json +27 -0
  74. package/registry/items/description-list.json +18 -0
  75. package/registry/items/device-list.json +24 -0
  76. package/registry/items/dialog.json +24 -0
  77. package/registry/items/diff-proposal.json +23 -0
  78. package/registry/items/diff-stat.json +18 -0
  79. package/registry/items/diff-view.json +23 -0
  80. package/registry/items/donut.json +19 -0
  81. package/registry/items/drawer.json +19 -0
  82. package/registry/items/dropdown-menu.json +24 -0
  83. package/registry/items/dropzone.json +22 -0
  84. package/registry/items/empty.json +19 -0
  85. package/registry/items/endpoint-list.json +24 -0
  86. package/registry/items/env-diff.json +23 -0
  87. package/registry/items/env-vars.json +24 -0
  88. package/registry/items/error-boundary.json +22 -0
  89. package/registry/items/eval-results.json +21 -0
  90. package/registry/items/feature-flag.json +23 -0
  91. package/registry/items/feedback.json +23 -0
  92. package/registry/items/field.json +19 -0
  93. package/registry/items/file-tree.json +21 -0
  94. package/registry/items/fingerprint-diff.json +23 -0
  95. package/registry/items/flame-graph.json +19 -0
  96. package/registry/items/fmt.json +16 -0
  97. package/registry/items/form.json +21 -0
  98. package/registry/items/fraud-verdict.json +24 -0
  99. package/registry/items/funnel.json +18 -0
  100. package/registry/items/gas-tracker.json +21 -0
  101. package/registry/items/gauge.json +18 -0
  102. package/registry/items/governance-proposal.json +24 -0
  103. package/registry/items/group.json +21 -0
  104. package/registry/items/health-checks.json +23 -0
  105. package/registry/items/heatmap-grid.json +19 -0
  106. package/registry/items/hover-card.json +20 -0
  107. package/registry/items/http-status.json +19 -0
  108. package/registry/items/incident-card.json +24 -0
  109. package/registry/items/input-file.json +22 -0
  110. package/registry/items/input-otp.json +19 -0
  111. package/registry/items/input.json +23 -0
  112. package/registry/items/invoice-list.json +24 -0
  113. package/registry/items/ip-cluster.json +22 -0
  114. package/registry/items/json-viewer.json +21 -0
  115. package/registry/items/jwt-inspector.json +25 -0
  116. package/registry/items/kanban.json +20 -0
  117. package/registry/items/kbd.json +18 -0
  118. package/registry/items/label-picker.json +24 -0
  119. package/registry/items/label.json +18 -0
  120. package/registry/items/leaderboard.json +23 -0
  121. package/registry/items/ledger-table.json +22 -0
  122. package/registry/items/lib-date.json +14 -0
  123. package/registry/items/lib-highlighter.json +16 -0
  124. package/registry/items/lib-styles.json +14 -0
  125. package/registry/items/lib-use-clipboard.json +14 -0
  126. package/registry/items/lib-utils.json +16 -0
  127. package/registry/items/lighthouse-score.json +19 -0
  128. package/registry/items/liquidity-position.json +23 -0
  129. package/registry/items/live-announcer.json +16 -0
  130. package/registry/items/location-picker.json +26 -0
  131. package/registry/items/log-viewer.json +24 -0
  132. package/registry/items/login-form.json +26 -0
  133. package/registry/items/map-embed.json +22 -0
  134. package/registry/items/market-table.json +23 -0
  135. package/registry/items/mask-input.json +19 -0
  136. package/registry/items/media-gallery.json +25 -0
  137. package/registry/items/mention-input.json +21 -0
  138. package/registry/items/menubar.json +21 -0
  139. package/registry/items/merge-conflict.json +20 -0
  140. package/registry/items/message.json +25 -0
  141. package/registry/items/migration-list.json +23 -0
  142. package/registry/items/mint-panel.json +23 -0
  143. package/registry/items/model-comparison.json +24 -0
  144. package/registry/items/model-select.json +20 -0
  145. package/registry/items/moderation-queue.json +26 -0
  146. package/registry/items/money-input.json +19 -0
  147. package/registry/items/multi-select.json +23 -0
  148. package/registry/items/multiplier-chart.json +18 -0
  149. package/registry/items/navigation-menu.json +23 -0
  150. package/registry/items/network-status.json +23 -0
  151. package/registry/items/nft-card.json +23 -0
  152. package/registry/items/notification-inbox.json +25 -0
  153. package/registry/items/number-input.json +21 -0
  154. package/registry/items/odds-display.json +21 -0
  155. package/registry/items/on-call-schedule.json +25 -0
  156. package/registry/items/order-book.json +19 -0
  157. package/registry/items/page-header.json +18 -0
  158. package/registry/items/pagination.json +21 -0
  159. package/registry/items/password-input.json +21 -0
  160. package/registry/items/payment-method.json +23 -0
  161. package/registry/items/payout-status.json +24 -0
  162. package/registry/items/permission-matrix.json +22 -0
  163. package/registry/items/pipeline.json +23 -0
  164. package/registry/items/popover.json +22 -0
  165. package/registry/items/port-table.json +22 -0
  166. package/registry/items/portfolio-balance.json +22 -0
  167. package/registry/items/postback-config.json +26 -0
  168. package/registry/items/presence.json +20 -0
  169. package/registry/items/price-ticker.json +21 -0
  170. package/registry/items/pricing-table.json +24 -0
  171. package/registry/items/primitive-collapsible.json +16 -0
  172. package/registry/items/primitive-dialog.json +16 -0
  173. package/registry/items/primitive-dismissable.json +14 -0
  174. package/registry/items/primitive-field.json +16 -0
  175. package/registry/items/primitive-focus-trap.json +14 -0
  176. package/registry/items/primitive-media-query.json +14 -0
  177. package/registry/items/primitive-popper.json +14 -0
  178. package/registry/items/primitive-portal.json +14 -0
  179. package/registry/items/primitive-radio-group.json +16 -0
  180. package/registry/items/primitive-router.json +14 -0
  181. package/registry/items/primitive-slot.json +16 -0
  182. package/registry/items/primitive-tabs.json +16 -0
  183. package/registry/items/progress.json +21 -0
  184. package/registry/items/prompt-input.json +25 -0
  185. package/registry/items/prompt-template.json +23 -0
  186. package/registry/items/pull-request-card.json +25 -0
  187. package/registry/items/query-editor.json +25 -0
  188. package/registry/items/query-plan.json +23 -0
  189. package/registry/items/queue-monitor.json +22 -0
  190. package/registry/items/radio-group.json +22 -0
  191. package/registry/items/range-slider.json +19 -0
  192. package/registry/items/rate-limit-meter.json +20 -0
  193. package/registry/items/rating.json +21 -0
  194. package/registry/items/reasoning-block.json +23 -0
  195. package/registry/items/regex-tester.json +25 -0
  196. package/registry/items/release-list.json +22 -0
  197. package/registry/items/request-builder.json +28 -0
  198. package/registry/items/resizable.json +22 -0
  199. package/registry/items/resource-meter.json +19 -0
  200. package/registry/items/response-viewer.json +26 -0
  201. package/registry/items/retrieval-results.json +23 -0
  202. package/registry/items/revenue-share-table.json +20 -0
  203. package/registry/items/review-thread.json +25 -0
  204. package/registry/items/risk-score.json +18 -0
  205. package/registry/items/root-cause-tree.json +20 -0
  206. package/registry/items/round-history.json +21 -0
  207. package/registry/items/runbook-steps.json +24 -0
  208. package/registry/items/schema-table.json +22 -0
  209. package/registry/items/schema-viewer.json +22 -0
  210. package/registry/items/scroll-area.json +18 -0
  211. package/registry/items/seed-phrase.json +22 -0
  212. package/registry/items/select.json +24 -0
  213. package/registry/items/separator.json +18 -0
  214. package/registry/items/service-status.json +21 -0
  215. package/registry/items/session-list.json +24 -0
  216. package/registry/items/sheet.json +24 -0
  217. package/registry/items/shortcut-sheet.json +19 -0
  218. package/registry/items/sidebar.json +24 -0
  219. package/registry/items/skeleton.json +21 -0
  220. package/registry/items/skip-link.json +19 -0
  221. package/registry/items/slider.json +21 -0
  222. package/registry/items/slo-budget.json +19 -0
  223. package/registry/items/sparkline.json +18 -0
  224. package/registry/items/spinner.json +20 -0
  225. package/registry/items/stack-trace.json +23 -0
  226. package/registry/items/staking-panel.json +24 -0
  227. package/registry/items/stat.json +21 -0
  228. package/registry/items/status-checks.json +23 -0
  229. package/registry/items/stepper.json +22 -0
  230. package/registry/items/streaming-text.json +19 -0
  231. package/registry/items/subscription-state.json +22 -0
  232. package/registry/items/suggestions.json +19 -0
  233. package/registry/items/swap-panel.json +22 -0
  234. package/registry/items/switch.json +21 -0
  235. package/registry/items/symbol-outline.json +22 -0
  236. package/registry/items/table-of-contents.json +19 -0
  237. package/registry/items/table.json +19 -0
  238. package/registry/items/tabs.json +22 -0
  239. package/registry/items/tag-input.json +21 -0
  240. package/registry/items/terminal.json +20 -0
  241. package/registry/items/test-results.json +24 -0
  242. package/registry/items/textarea.json +21 -0
  243. package/registry/items/theme-toggle.json +20 -0
  244. package/registry/items/theme.json +18 -0
  245. package/registry/items/thread-list.json +24 -0
  246. package/registry/items/ticket-card.json +24 -0
  247. package/registry/items/time-picker.json +23 -0
  248. package/registry/items/timeline.json +19 -0
  249. package/registry/items/timezone-select.json +18 -0
  250. package/registry/items/toast.json +22 -0
  251. package/registry/items/toggle.json +21 -0
  252. package/registry/items/token-amount.json +18 -0
  253. package/registry/items/token-approvals.json +25 -0
  254. package/registry/items/token-select.json +25 -0
  255. package/registry/items/token-usage.json +20 -0
  256. package/registry/items/tool-call.json +24 -0
  257. package/registry/items/toolbar.json +19 -0
  258. package/registry/items/tooltip.json +19 -0
  259. package/registry/items/topology-map.json +20 -0
  260. package/registry/items/tour.json +25 -0
  261. package/registry/items/traffic-split.json +20 -0
  262. package/registry/items/transaction-list.json +23 -0
  263. package/registry/items/transaction-status.json +25 -0
  264. package/registry/items/tree.json +21 -0
  265. package/registry/items/two-factor-setup.json +25 -0
  266. package/registry/items/typing-indicator.json +19 -0
  267. package/registry/items/typography.json +18 -0
  268. package/registry/items/upload-list.json +24 -0
  269. package/registry/items/uptime-strip.json +19 -0
  270. package/registry/items/user-menu.json +24 -0
  271. package/registry/items/utm-builder.json +24 -0
  272. package/registry/items/validator-list.json +23 -0
  273. package/registry/items/video-player.json +23 -0
  274. package/registry/items/virtual-list.json +18 -0
  275. package/registry/items/visually-hidden.json +19 -0
  276. package/registry/items/wallet-address.json +22 -0
  277. package/registry/items/wallet-connect.json +25 -0
  278. package/registry/items/webhook-inspector.json +26 -0
  279. package/registry/items/websocket-frames.json +24 -0
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Astralyx
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,134 @@
1
+ # astralyx-ui
2
+
3
+ **253 accessible React components you copy into your repo** — with a CLI and a
4
+ registry that work out what each one needs and bring that too.
5
+
6
+ Not a dependency. There is no package to upgrade and nothing to fight when a
7
+ design changes: the code lands in your tree and it is yours.
8
+
9
+ ```bash
10
+ npm i -D astralyx-ui
11
+ npx astralyx-ui init
12
+ npx astralyx-ui add button
13
+ ```
14
+
15
+ ## What you get
16
+
17
+ - **253 components** across 28 categories — forms, data, overlays, commerce,
18
+ auth, AI, crypto, observability, developer tooling.
19
+ - **12 primitives** underneath them. Slot, Popper, FocusTrap, Dismissable and
20
+ friends — no headless-UI dependency.
21
+ - **One style contract.** Colour tokens, a surface ladder built from lightness
22
+ alone, and eight colour sets any component can be pointed at with one prop.
23
+ - **Accessibility wired, not assumed.** Real elements, real focus management,
24
+ state announced rather than implied. Enforced by a harness that renders every
25
+ component and asserts against it.
26
+
27
+ ## Getting started
28
+
29
+ `init` writes a `components.json`, drops in the two shared helpers every
30
+ component needs, and installs the theme:
31
+
32
+ ```bash
33
+ npx astralyx-ui init
34
+ ```
35
+
36
+ Then import the theme from your Tailwind entry CSS:
37
+
38
+ ```css
39
+ @import 'tailwindcss';
40
+ @import './styles/astralyx.css';
41
+ ```
42
+
43
+ Add whatever you need. Dependencies come with it — `data-grid` brings `table`,
44
+ `checkbox` and `empty` without being asked:
45
+
46
+ ```bash
47
+ npx astralyx-ui add data-grid
48
+ ```
49
+
50
+ ## Commands
51
+
52
+ | Command | What it does |
53
+ | --- | --- |
54
+ | `init` | Create `components.json`, add the theme and shared helpers |
55
+ | `add <name...>` | Add components and everything they depend on |
56
+ | `add --all` | Add every component in the registry |
57
+ | `list` | Every component, by category |
58
+ | `info <name>` | What a component would bring with it |
59
+
60
+ | Option | |
61
+ | --- | --- |
62
+ | `-y, --yes` | Accept the defaults, ask nothing |
63
+ | `-o, --overwrite` | Replace files that already exist |
64
+ | `--dry-run` | Report what would happen, change nothing |
65
+ | `--no-deps` | Do not install npm packages |
66
+ | `--cwd <dir>` | Run against another directory |
67
+ | `--registry <url>` | Use a remote registry instead of the bundled one |
68
+
69
+ ## Configuration
70
+
71
+ `components.json` decides where files land and which aliases their imports use.
72
+ Both are honoured — put components wherever you like and the imports follow:
73
+
74
+ ```json
75
+ {
76
+ "tsx": true,
77
+ "tailwind": { "css": "src/index.css" },
78
+ "aliases": {
79
+ "ui": "@/components/ui",
80
+ "primitives": "@/components/primitives",
81
+ "lib": "@/lib"
82
+ },
83
+ "paths": {
84
+ "ui": "src/components/ui",
85
+ "primitives": "src/components/primitives",
86
+ "lib": "src/lib",
87
+ "styles": "src/styles"
88
+ }
89
+ }
90
+ ```
91
+
92
+ ## Programmatic use
93
+
94
+ The registry is also a library, if you are building your own tooling on top:
95
+
96
+ ```ts
97
+ import { getRegistry, resolveItems, resolveDependencies } from 'astralyx-ui'
98
+
99
+ const { items } = await getRegistry()
100
+ const files = await resolveItems(['data-grid'])
101
+ const packages = await resolveDependencies(['data-grid'])
102
+ ```
103
+
104
+ Or read the JSON directly:
105
+
106
+ ```ts
107
+ import index from 'astralyx-ui/registry'
108
+ import button from 'astralyx-ui/registry/button'
109
+ ```
110
+
111
+ ## Requirements
112
+
113
+ React 19, Tailwind CSS v4, Node 20+. The CLI itself has **no runtime
114
+ dependencies**; a tool whose job is to add packages to your project has no
115
+ business bringing a dozen of its own.
116
+
117
+ ## Developing this repo
118
+
119
+ ```bash
120
+ npm install
121
+ npm run dev # the documentation site
122
+ npm run build # typecheck + build the site
123
+ npm run build:package # regenerate the registry and compile the CLI
124
+ npm run lint
125
+ ```
126
+
127
+ The registry is generated from source — the dependency graph is derived by
128
+ parsing imports, never hand-listed. A hand-maintained manifest is wrong the
129
+ first time someone adds an import and forgets to update it, and the failure is
130
+ silent: the CLI writes a file that cannot compile.
131
+
132
+ ## License
133
+
134
+ MIT
package/bin/index.d.ts ADDED
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env node
2
+ export {};
package/bin/index.js ADDED
@@ -0,0 +1,404 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * The astralyx-ui CLI.
4
+ *
5
+ * Components are copied into the consumer's repo rather than imported from a
6
+ * package. That is the thesis of the kit: once a component is in your tree it
7
+ * is yours, and a design change is an edit rather than an argument with someone
8
+ * else's API. The registry exists so the copy is not a manual one — it resolves
9
+ * what a component needs and brings that too.
10
+ *
11
+ * No runtime dependencies. A tool whose job is to add dependencies to your
12
+ * project has no business bringing a dozen of its own.
13
+ */
14
+ import fs from 'node:fs';
15
+ import path from 'node:path';
16
+ import readline from 'node:readline/promises';
17
+ import { fileURLToPath } from 'node:url';
18
+ import { execFileSync } from 'node:child_process';
19
+ // `.js` on purpose: this is emitted as ESM and Node resolves the specifier
20
+ // literally at runtime, extension and all.
21
+ import { getRegistry, resolveItems, } from './registry.js';
22
+ const HERE = path.dirname(fileURLToPath(import.meta.url));
23
+ const CONFIG_FILE = 'components.json';
24
+ /* ------------------------------------------------------------------ output */
25
+ // Built from a code point rather than written literally: an escape byte in
26
+ // source survives copy/paste badly and is invisible in review.
27
+ const ESC = String.fromCharCode(27);
28
+ const tty = process.stdout.isTTY && !process.env.NO_COLOR;
29
+ const paint = (code) => (text) => tty ? `${ESC}[${code}m${text}${ESC}[0m` : text;
30
+ const bold = paint('1');
31
+ const dim = paint('2');
32
+ const green = paint('32');
33
+ const yellow = paint('33');
34
+ const red = paint('31');
35
+ const cyan = paint('36');
36
+ const log = (...args) => console.log(...args);
37
+ const info = (text) => log(`${cyan('.')} ${text}`);
38
+ const done = (text) => log(`${green('+')} ${text}`);
39
+ const warn = (text) => log(`${yellow('!')} ${text}`);
40
+ function fail(message) {
41
+ console.error(`${red('x')} ${message}`);
42
+ process.exit(1);
43
+ }
44
+ /* ------------------------------------------------------------------ config */
45
+ const DEFAULT_CONFIG = {
46
+ tsx: true,
47
+ tailwind: { css: 'src/index.css' },
48
+ aliases: {
49
+ ui: '@/components/ui',
50
+ primitives: '@/components/primitives',
51
+ lib: '@/lib',
52
+ },
53
+ paths: {
54
+ ui: 'src/components/ui',
55
+ primitives: 'src/components/primitives',
56
+ lib: 'src/lib',
57
+ styles: 'src/styles',
58
+ },
59
+ };
60
+ function readConfig(cwd) {
61
+ const file = path.join(cwd, CONFIG_FILE);
62
+ if (!fs.existsSync(file))
63
+ return undefined;
64
+ try {
65
+ return { ...DEFAULT_CONFIG, ...JSON.parse(fs.readFileSync(file, 'utf8')) };
66
+ }
67
+ catch {
68
+ fail(`${CONFIG_FILE} is not valid JSON.`);
69
+ }
70
+ }
71
+ function requireConfig(cwd) {
72
+ const config = readConfig(cwd);
73
+ if (!config)
74
+ fail(`No ${CONFIG_FILE} here. Run ${bold('astralyx-ui init')} first.`);
75
+ return config;
76
+ }
77
+ /* ------------------------------------------------------------------ writing */
78
+ /** Which source folder a registry path belongs to, and the alias replacing it. */
79
+ const AREAS = [
80
+ { prefix: 'components/ui/', key: 'ui' },
81
+ { prefix: 'components/primitives/', key: 'primitives' },
82
+ { prefix: 'lib/', key: 'lib' },
83
+ { prefix: 'styles/', key: 'styles' },
84
+ ];
85
+ function targetFor(file, config, cwd) {
86
+ for (const area of AREAS) {
87
+ if (file.path.startsWith(area.prefix)) {
88
+ return path.join(cwd, config.paths[area.key], file.path.slice(area.prefix.length));
89
+ }
90
+ }
91
+ return path.join(cwd, file.path);
92
+ }
93
+ /**
94
+ * Rewrites the kit's own `@/...` imports to wherever the consumer keeps things.
95
+ *
96
+ * Longest prefix first: `@/components/ui` has to be matched before
97
+ * `@/components`, or a project that relocates only `ui` gets its primitives
98
+ * imports pointed at the wrong folder.
99
+ */
100
+ function rewriteImports(content, config) {
101
+ const map = [
102
+ ['@/components/ui', config.aliases.ui],
103
+ ['@/components/primitives', config.aliases.primitives],
104
+ ['@/lib', config.aliases.lib],
105
+ ];
106
+ map.sort((a, b) => b[0].length - a[0].length);
107
+ let out = content;
108
+ for (const [from, to] of map) {
109
+ if (from === to)
110
+ continue;
111
+ out = out.replaceAll(`'${from}/`, `'${to}/`);
112
+ }
113
+ return out;
114
+ }
115
+ /**
116
+ * Best-effort type stripping for JavaScript projects, and it is worth being
117
+ * plain that a regex is not a TypeScript compiler. Anything non-trivial is
118
+ * better served by keeping `tsx: true` and letting a real build handle it.
119
+ */
120
+ function toJs(content) {
121
+ return content
122
+ .replace(/^import type .*$/gm, '')
123
+ .replace(/^export type .*$/gm, '')
124
+ .replace(/,\s*type [A-Z]\w+/g, '');
125
+ }
126
+ /* -------------------------------------------------------------- installing */
127
+ function detectPackageManager(cwd) {
128
+ if (fs.existsSync(path.join(cwd, 'pnpm-lock.yaml')))
129
+ return 'pnpm';
130
+ if (fs.existsSync(path.join(cwd, 'yarn.lock')))
131
+ return 'yarn';
132
+ if (fs.existsSync(path.join(cwd, 'bun.lockb')))
133
+ return 'bun';
134
+ return 'npm';
135
+ }
136
+ function installDeps(deps, cwd, dry) {
137
+ if (deps.length === 0)
138
+ return;
139
+ const manager = detectPackageManager(cwd);
140
+ const args = manager === 'npm' ? ['install', ...deps] : ['add', ...deps];
141
+ info(`${dry ? 'Would install' : 'Installing'} ${deps.length} package${deps.length === 1 ? '' : 's'} with ${manager}`);
142
+ log(dim(` ${manager} ${args.join(' ')}`));
143
+ if (dry)
144
+ return;
145
+ try {
146
+ execFileSync(manager, args, { cwd, stdio: 'inherit' });
147
+ }
148
+ catch {
149
+ warn(`${manager} failed. Install these yourself: ${deps.join(' ')}`);
150
+ }
151
+ }
152
+ /** Already-satisfied packages are dropped, so a project is never downgraded. */
153
+ function missingDeps(deps, cwd) {
154
+ const file = path.join(cwd, 'package.json');
155
+ if (!fs.existsSync(file))
156
+ return deps;
157
+ const pkg = JSON.parse(fs.readFileSync(file, 'utf8'));
158
+ const present = new Set([
159
+ ...Object.keys(pkg.dependencies ?? {}),
160
+ ...Object.keys(pkg.devDependencies ?? {}),
161
+ ]);
162
+ return deps.filter((dep) => !present.has(dep.replace(/@[\^~]?[\d.].*$/, '')));
163
+ }
164
+ /* ------------------------------------------------------------------ prompts */
165
+ async function confirm(question, fallback) {
166
+ if (!process.stdin.isTTY)
167
+ return fallback;
168
+ const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
169
+ const answer = await rl.question(`${cyan('?')} ${question} ${dim(fallback ? '(Y/n)' : '(y/N)')} `);
170
+ rl.close();
171
+ if (!answer.trim())
172
+ return fallback;
173
+ return /^y/i.test(answer.trim());
174
+ }
175
+ async function ask(question, fallback) {
176
+ if (!process.stdin.isTTY)
177
+ return fallback;
178
+ const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
179
+ const answer = await rl.question(`${cyan('?')} ${question} ${dim(fallback)} `);
180
+ rl.close();
181
+ return answer.trim() || fallback;
182
+ }
183
+ /* ----------------------------------------------------------------- commands */
184
+ async function addItems(names, config, flags, cwd) {
185
+ const items = await resolveItems(names, flags.registry);
186
+ const written = [];
187
+ const skipped = [];
188
+ for (const item of items) {
189
+ for (const file of item.files ?? []) {
190
+ const target = targetFor(file, config, cwd);
191
+ if (fs.existsSync(target) && !flags.overwrite) {
192
+ skipped.push(path.relative(cwd, target));
193
+ continue;
194
+ }
195
+ let content = rewriteImports(file.content, config);
196
+ let outPath = target;
197
+ if (!config.tsx && /\.tsx?$/.test(target)) {
198
+ content = toJs(content);
199
+ outPath = target.replace(/\.tsx$/, '.jsx').replace(/\.ts$/, '.js');
200
+ }
201
+ if (!flags.dryRun) {
202
+ fs.mkdirSync(path.dirname(outPath), { recursive: true });
203
+ fs.writeFileSync(outPath, content);
204
+ }
205
+ written.push(path.relative(cwd, outPath));
206
+ }
207
+ }
208
+ for (const file of written)
209
+ done(file);
210
+ if (skipped.length) {
211
+ warn(`${skipped.length} file${skipped.length === 1 ? '' : 's'} already existed - pass --overwrite to replace`);
212
+ for (const file of skipped)
213
+ log(dim(` ${file}`));
214
+ }
215
+ const deps = missingDeps([...new Set(items.flatMap((i) => i.dependencies))], cwd);
216
+ if (deps.length && !flags.noDeps)
217
+ installDeps(deps, cwd, flags.dryRun);
218
+ return { written, skipped };
219
+ }
220
+ async function init(flags, cwd) {
221
+ if (!fs.existsSync(path.join(cwd, 'package.json'))) {
222
+ fail('No package.json here. Run this inside your project.');
223
+ }
224
+ if (readConfig(cwd) && !flags.overwrite) {
225
+ fail(`${CONFIG_FILE} already exists. Pass --overwrite to replace it.`);
226
+ }
227
+ const config = structuredClone(DEFAULT_CONFIG);
228
+ if (!flags.yes) {
229
+ config.paths.ui = await ask('Where should components go?', config.paths.ui);
230
+ config.paths.primitives = await ask('Where should primitives go?', config.paths.primitives);
231
+ config.paths.lib = await ask('Where should shared helpers go?', config.paths.lib);
232
+ config.paths.styles = await ask('Where should the theme CSS go?', config.paths.styles);
233
+ config.tailwind.css = await ask('Which file holds your Tailwind entry CSS?', config.tailwind.css);
234
+ config.aliases.ui = await ask('Import alias for components?', config.aliases.ui);
235
+ config.aliases.primitives = await ask('Import alias for primitives?', config.aliases.primitives);
236
+ config.aliases.lib = await ask('Import alias for helpers?', config.aliases.lib);
237
+ config.tsx = await confirm('TypeScript?', true);
238
+ }
239
+ if (!flags.dryRun) {
240
+ fs.writeFileSync(path.join(cwd, CONFIG_FILE), JSON.stringify(config, null, 2) + '\n');
241
+ }
242
+ done(CONFIG_FILE);
243
+ // `lib-utils` and `lib-styles` back nearly every component, and the theme
244
+ // carries the tokens they reference. Without all three, nothing compiles.
245
+ await addItems(['lib-utils', 'lib-styles', 'theme'], config, flags, cwd);
246
+ const themeFile = path.join(config.paths.styles, 'astralyx.css');
247
+ const from = path.dirname(config.tailwind.css);
248
+ let importPath = path.relative(from, themeFile).split(path.sep).join('/');
249
+ if (!importPath.startsWith('.'))
250
+ importPath = `./${importPath}`;
251
+ log();
252
+ log(`Import the theme from ${bold(config.tailwind.css)}:`);
253
+ log(dim(` @import '${importPath}';`));
254
+ log();
255
+ log(`Then add a component: ${bold('astralyx-ui add button')}`);
256
+ }
257
+ async function add(names, flags, cwd) {
258
+ const config = requireConfig(cwd);
259
+ const index = await getRegistry(flags.registry);
260
+ const wanted = flags.all
261
+ ? index.items.filter((i) => i.type === 'registry:ui').map((i) => i.name)
262
+ : names;
263
+ if (wanted.length === 0)
264
+ fail(`Nothing to add. Try ${bold('astralyx-ui list')}.`);
265
+ const known = new Set(index.items.map((i) => i.name));
266
+ const unknown = wanted.filter((n) => !known.has(n));
267
+ if (unknown.length) {
268
+ for (const name of unknown) {
269
+ const near = index.items
270
+ .map((i) => i.name)
271
+ .filter((n) => n.includes(name) || name.includes(n))
272
+ .slice(0, 4);
273
+ console.error(`${red('x')} Unknown component "${name}"` +
274
+ (near.length ? dim(` - did you mean ${near.join(', ')}?`) : ''));
275
+ }
276
+ process.exit(1);
277
+ }
278
+ await addItems(wanted, config, flags, cwd);
279
+ }
280
+ async function list(flags) {
281
+ const index = await getRegistry(flags.registry);
282
+ const ui = index.items.filter((i) => i.type === 'registry:ui');
283
+ const groups = new Map();
284
+ for (const item of ui) {
285
+ const key = item.category ?? 'Other';
286
+ groups.set(key, [...(groups.get(key) ?? []), item]);
287
+ }
288
+ for (const [category, entries] of groups) {
289
+ log();
290
+ log(`${bold(category)} ${dim(String(entries.length))}`);
291
+ const width = Math.max(...entries.map((e) => e.name.length)) + 2;
292
+ for (const entry of entries) {
293
+ const summary = (entry.description ?? '').split('. ')[0].slice(0, 72);
294
+ log(` ${entry.name.padEnd(width)}${dim(summary)}`);
295
+ }
296
+ }
297
+ log();
298
+ log(dim(`${ui.length} components - astralyx-ui add <name>`));
299
+ }
300
+ async function show(name, flags) {
301
+ const closure = await resolveItems([name], flags.registry);
302
+ const item = closure[0];
303
+ const files = closure.flatMap((i) => i.files ?? []);
304
+ const deps = [...new Set(closure.flatMap((i) => i.dependencies))];
305
+ log();
306
+ log(`${bold(item.title)} ${dim(item.name)}`);
307
+ if (item.description)
308
+ log(item.description);
309
+ log();
310
+ log(`${bold('Files')} ${dim(String(files.length))}`);
311
+ for (const file of files)
312
+ log(dim(` ${file.path}`));
313
+ log(`${bold('Packages')} ${deps.length ? deps.join(', ') : dim('none')}`);
314
+ log();
315
+ }
316
+ const USAGE = `
317
+ ${bold('astralyx-ui')} - copy components into your project
318
+
319
+ ${bold('init')} Create ${CONFIG_FILE}, add the theme and shared helpers
320
+ ${bold('add')} <name...> Add components and everything they depend on
321
+ ${bold('list')} Every component, by category
322
+ ${bold('info')} <name> What a component would bring with it
323
+
324
+ Options
325
+ -y, --yes Accept the defaults, ask nothing
326
+ -o, --overwrite Replace files that already exist
327
+ --all With add: every component in the registry
328
+ --dry-run Report what would happen, change nothing
329
+ --no-deps Do not install npm packages
330
+ --cwd <dir> Run against another directory
331
+ --registry <url> Use a remote registry instead of the bundled one
332
+
333
+ Examples
334
+ npx astralyx-ui init
335
+ npx astralyx-ui add button input dialog
336
+ npx astralyx-ui info data-grid
337
+ `;
338
+ function parse(argv) {
339
+ const flags = {
340
+ yes: false, overwrite: false, all: false, dryRun: false, noDeps: false,
341
+ };
342
+ const rest = [];
343
+ for (let i = 0; i < argv.length; i++) {
344
+ const arg = argv[i];
345
+ if (arg === '--yes' || arg === '-y')
346
+ flags.yes = true;
347
+ else if (arg === '--overwrite' || arg === '-o')
348
+ flags.overwrite = true;
349
+ else if (arg === '--all')
350
+ flags.all = true;
351
+ else if (arg === '--dry-run')
352
+ flags.dryRun = true;
353
+ else if (arg === '--no-deps')
354
+ flags.noDeps = true;
355
+ else if (arg === '--cwd')
356
+ flags.cwd = argv[++i];
357
+ else if (arg === '--registry')
358
+ flags.registry = argv[++i];
359
+ // `--help` and `--version` read as options but behave as commands, so they
360
+ // pass through to the switch rather than tripping the unknown-option guard.
361
+ else if (/^--?(h|help|v|version)$/.test(arg))
362
+ rest.push(arg);
363
+ else if (arg.startsWith('-'))
364
+ fail(`Unknown option "${arg}".`);
365
+ else
366
+ rest.push(arg);
367
+ }
368
+ return { flags, rest };
369
+ }
370
+ async function main() {
371
+ const { flags, rest } = parse(process.argv.slice(2));
372
+ const [command, ...args] = rest;
373
+ const cwd = path.resolve(flags.cwd ?? process.cwd());
374
+ if (!command || command === 'help' || command === '--help' || command === '-h') {
375
+ log(USAGE);
376
+ return;
377
+ }
378
+ if (command === 'version' || command === '--version' || command === '-v') {
379
+ const pkg = JSON.parse(fs.readFileSync(path.join(HERE, '..', 'package.json'), 'utf8'));
380
+ log(pkg.version);
381
+ return;
382
+ }
383
+ if (flags.dryRun)
384
+ warn('Dry run - nothing will be written.');
385
+ switch (command) {
386
+ case 'init':
387
+ return init(flags, cwd);
388
+ case 'add':
389
+ return add(args, flags, cwd);
390
+ case 'list':
391
+ case 'ls':
392
+ return list(flags);
393
+ case 'info':
394
+ case 'show':
395
+ if (!args[0])
396
+ fail('Which component? e.g. astralyx-ui info button');
397
+ return show(args[0], flags);
398
+ default:
399
+ fail(`Unknown command "${command}". Run ${bold('astralyx-ui help')}.`);
400
+ }
401
+ }
402
+ main().catch((error) => {
403
+ fail(error instanceof Error ? error.message : String(error));
404
+ });
@@ -0,0 +1,37 @@
1
+ export type ItemType = 'registry:ui' | 'registry:primitive' | 'registry:lib' | 'registry:theme';
2
+ export type RegistryFile = {
3
+ path: string;
4
+ type: ItemType;
5
+ content: string;
6
+ };
7
+ export type Item = {
8
+ name: string;
9
+ type: ItemType;
10
+ title: string;
11
+ description?: string;
12
+ category?: string;
13
+ dependencies: string[];
14
+ registryDependencies: string[];
15
+ files?: RegistryFile[];
16
+ };
17
+ export type Index = {
18
+ name: string;
19
+ version: string;
20
+ homepage?: string;
21
+ items: Item[];
22
+ };
23
+ export declare class RegistryError extends Error {
24
+ }
25
+ /** The catalogue: every item, without file contents. */
26
+ export declare function getRegistry(remote?: string): Promise<Index>;
27
+ /** One item, with its file contents. */
28
+ export declare function getItem(name: string, remote?: string): Promise<Item>;
29
+ /**
30
+ * Every item the named ones need, transitively.
31
+ *
32
+ * Depth-first, so a dependency always appears before whatever imports it —
33
+ * which is the order they have to be written to disk in.
34
+ */
35
+ export declare function resolveItems(names: string[], remote?: string): Promise<Item[]>;
36
+ /** npm packages the named items need, deduplicated. */
37
+ export declare function resolveDependencies(names: string[], remote?: string): Promise<string[]>;
@@ -0,0 +1,69 @@
1
+ /**
2
+ * Registry access, shared by the CLI and exported as this package's public API.
3
+ *
4
+ * The registry ships inside the package, so resolution works offline and the
5
+ * components you get always match the version that resolved them. Pass a
6
+ * `registry` URL to read a fork instead.
7
+ */
8
+ import fs from 'node:fs';
9
+ import path from 'node:path';
10
+ import { fileURLToPath } from 'node:url';
11
+ const HERE = path.dirname(fileURLToPath(import.meta.url));
12
+ const BUNDLED = path.join(HERE, '..', 'registry');
13
+ export class RegistryError extends Error {
14
+ }
15
+ /** The catalogue: every item, without file contents. */
16
+ export async function getRegistry(remote) {
17
+ if (remote) {
18
+ const base = remote.replace(/\/$/, '');
19
+ const response = await fetch(`${base}/index.json`);
20
+ if (!response.ok) {
21
+ throw new RegistryError(`Could not read registry at ${base} (${response.status}).`);
22
+ }
23
+ return (await response.json());
24
+ }
25
+ const local = path.join(BUNDLED, 'index.json');
26
+ if (!fs.existsSync(local)) {
27
+ throw new RegistryError('Bundled registry is missing - reinstall astralyx-ui.');
28
+ }
29
+ return JSON.parse(fs.readFileSync(local, 'utf8'));
30
+ }
31
+ /** One item, with its file contents. */
32
+ export async function getItem(name, remote) {
33
+ if (remote) {
34
+ const base = remote.replace(/\/$/, '');
35
+ const response = await fetch(`${base}/items/${name}.json`);
36
+ if (!response.ok)
37
+ throw new RegistryError(`Registry has no item "${name}".`);
38
+ return (await response.json());
39
+ }
40
+ const local = path.join(BUNDLED, 'items', `${name}.json`);
41
+ if (!fs.existsSync(local))
42
+ throw new RegistryError(`Registry has no item "${name}".`);
43
+ return JSON.parse(fs.readFileSync(local, 'utf8'));
44
+ }
45
+ /**
46
+ * Every item the named ones need, transitively.
47
+ *
48
+ * Depth-first, so a dependency always appears before whatever imports it —
49
+ * which is the order they have to be written to disk in.
50
+ */
51
+ export async function resolveItems(names, remote) {
52
+ const resolved = new Map();
53
+ async function visit(name) {
54
+ if (resolved.has(name))
55
+ return;
56
+ const item = await getItem(name, remote);
57
+ resolved.set(name, item);
58
+ for (const dep of item.registryDependencies)
59
+ await visit(dep);
60
+ }
61
+ for (const name of names)
62
+ await visit(name);
63
+ return [...resolved.values()];
64
+ }
65
+ /** npm packages the named items need, deduplicated. */
66
+ export async function resolveDependencies(names, remote) {
67
+ const items = await resolveItems(names, remote);
68
+ return [...new Set(items.flatMap((item) => item.dependencies))].sort();
69
+ }