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.
- package/LICENSE +21 -0
- package/README.md +134 -0
- package/bin/index.d.ts +2 -0
- package/bin/index.js +404 -0
- package/bin/registry.d.ts +37 -0
- package/bin/registry.js +69 -0
- package/package.json +89 -0
- package/registry/index.json +3914 -0
- package/registry/items/accordion.json +22 -0
- package/registry/items/address-input.json +21 -0
- package/registry/items/agent-steps.json +23 -0
- package/registry/items/alert-dialog.json +22 -0
- package/registry/items/alert-triage.json +25 -0
- package/registry/items/alert.json +21 -0
- package/registry/items/anomaly-chart.json +18 -0
- package/registry/items/api-keys.json +25 -0
- package/registry/items/aspect-ratio.json +18 -0
- package/registry/items/attachment-preview.json +23 -0
- package/registry/items/audit-log.json +26 -0
- package/registry/items/avatar.json +21 -0
- package/registry/items/badge.json +22 -0
- package/registry/items/banner.json +22 -0
- package/registry/items/benchmark-table.json +20 -0
- package/registry/items/bet-input.json +23 -0
- package/registry/items/blame-view.json +22 -0
- package/registry/items/branch-select.json +21 -0
- package/registry/items/breadcrumb.json +22 -0
- package/registry/items/bridge-status.json +24 -0
- package/registry/items/build-log.json +23 -0
- package/registry/items/bulk-action-bar.json +21 -0
- package/registry/items/bundle-treemap.json +20 -0
- package/registry/items/button-group.json +21 -0
- package/registry/items/button.json +22 -0
- package/registry/items/cache-stats.json +21 -0
- package/registry/items/calendar.json +25 -0
- package/registry/items/campaign-card.json +22 -0
- package/registry/items/candlestick-chart.json +18 -0
- package/registry/items/card-input.json +22 -0
- package/registry/items/card.json +21 -0
- package/registry/items/carousel.json +22 -0
- package/registry/items/cart.json +23 -0
- package/registry/items/chain-select.json +24 -0
- package/registry/items/changelog-entry.json +21 -0
- package/registry/items/chart.json +19 -0
- package/registry/items/checkbox.json +22 -0
- package/registry/items/checkout-summary.json +20 -0
- package/registry/items/citations.json +22 -0
- package/registry/items/code-block.json +23 -0
- package/registry/items/code-search.json +22 -0
- package/registry/items/cohort-table.json +19 -0
- package/registry/items/collapsible.json +23 -0
- package/registry/items/color-picker.json +24 -0
- package/registry/items/combobox.json +23 -0
- package/registry/items/command.json +23 -0
- package/registry/items/commit-graph.json +20 -0
- package/registry/items/commit-list.json +26 -0
- package/registry/items/composer.json +30 -0
- package/registry/items/confidence-meter.json +18 -0
- package/registry/items/connection-string.json +24 -0
- package/registry/items/container-list.json +24 -0
- package/registry/items/context-menu.json +20 -0
- package/registry/items/context-picker.json +25 -0
- package/registry/items/coordinate-input.json +21 -0
- package/registry/items/copy-button.json +22 -0
- package/registry/items/cost-breakdown.json +20 -0
- package/registry/items/coverage-report.json +22 -0
- package/registry/items/cron-schedule.json +24 -0
- package/registry/items/curl-command.json +18 -0
- package/registry/items/data-grid.json +24 -0
- package/registry/items/date-picker.json +24 -0
- package/registry/items/date-range-compare.json +24 -0
- package/registry/items/dependency-list.json +23 -0
- package/registry/items/deploy-list.json +27 -0
- package/registry/items/description-list.json +18 -0
- package/registry/items/device-list.json +24 -0
- package/registry/items/dialog.json +24 -0
- package/registry/items/diff-proposal.json +23 -0
- package/registry/items/diff-stat.json +18 -0
- package/registry/items/diff-view.json +23 -0
- package/registry/items/donut.json +19 -0
- package/registry/items/drawer.json +19 -0
- package/registry/items/dropdown-menu.json +24 -0
- package/registry/items/dropzone.json +22 -0
- package/registry/items/empty.json +19 -0
- package/registry/items/endpoint-list.json +24 -0
- package/registry/items/env-diff.json +23 -0
- package/registry/items/env-vars.json +24 -0
- package/registry/items/error-boundary.json +22 -0
- package/registry/items/eval-results.json +21 -0
- package/registry/items/feature-flag.json +23 -0
- package/registry/items/feedback.json +23 -0
- package/registry/items/field.json +19 -0
- package/registry/items/file-tree.json +21 -0
- package/registry/items/fingerprint-diff.json +23 -0
- package/registry/items/flame-graph.json +19 -0
- package/registry/items/fmt.json +16 -0
- package/registry/items/form.json +21 -0
- package/registry/items/fraud-verdict.json +24 -0
- package/registry/items/funnel.json +18 -0
- package/registry/items/gas-tracker.json +21 -0
- package/registry/items/gauge.json +18 -0
- package/registry/items/governance-proposal.json +24 -0
- package/registry/items/group.json +21 -0
- package/registry/items/health-checks.json +23 -0
- package/registry/items/heatmap-grid.json +19 -0
- package/registry/items/hover-card.json +20 -0
- package/registry/items/http-status.json +19 -0
- package/registry/items/incident-card.json +24 -0
- package/registry/items/input-file.json +22 -0
- package/registry/items/input-otp.json +19 -0
- package/registry/items/input.json +23 -0
- package/registry/items/invoice-list.json +24 -0
- package/registry/items/ip-cluster.json +22 -0
- package/registry/items/json-viewer.json +21 -0
- package/registry/items/jwt-inspector.json +25 -0
- package/registry/items/kanban.json +20 -0
- package/registry/items/kbd.json +18 -0
- package/registry/items/label-picker.json +24 -0
- package/registry/items/label.json +18 -0
- package/registry/items/leaderboard.json +23 -0
- package/registry/items/ledger-table.json +22 -0
- package/registry/items/lib-date.json +14 -0
- package/registry/items/lib-highlighter.json +16 -0
- package/registry/items/lib-styles.json +14 -0
- package/registry/items/lib-use-clipboard.json +14 -0
- package/registry/items/lib-utils.json +16 -0
- package/registry/items/lighthouse-score.json +19 -0
- package/registry/items/liquidity-position.json +23 -0
- package/registry/items/live-announcer.json +16 -0
- package/registry/items/location-picker.json +26 -0
- package/registry/items/log-viewer.json +24 -0
- package/registry/items/login-form.json +26 -0
- package/registry/items/map-embed.json +22 -0
- package/registry/items/market-table.json +23 -0
- package/registry/items/mask-input.json +19 -0
- package/registry/items/media-gallery.json +25 -0
- package/registry/items/mention-input.json +21 -0
- package/registry/items/menubar.json +21 -0
- package/registry/items/merge-conflict.json +20 -0
- package/registry/items/message.json +25 -0
- package/registry/items/migration-list.json +23 -0
- package/registry/items/mint-panel.json +23 -0
- package/registry/items/model-comparison.json +24 -0
- package/registry/items/model-select.json +20 -0
- package/registry/items/moderation-queue.json +26 -0
- package/registry/items/money-input.json +19 -0
- package/registry/items/multi-select.json +23 -0
- package/registry/items/multiplier-chart.json +18 -0
- package/registry/items/navigation-menu.json +23 -0
- package/registry/items/network-status.json +23 -0
- package/registry/items/nft-card.json +23 -0
- package/registry/items/notification-inbox.json +25 -0
- package/registry/items/number-input.json +21 -0
- package/registry/items/odds-display.json +21 -0
- package/registry/items/on-call-schedule.json +25 -0
- package/registry/items/order-book.json +19 -0
- package/registry/items/page-header.json +18 -0
- package/registry/items/pagination.json +21 -0
- package/registry/items/password-input.json +21 -0
- package/registry/items/payment-method.json +23 -0
- package/registry/items/payout-status.json +24 -0
- package/registry/items/permission-matrix.json +22 -0
- package/registry/items/pipeline.json +23 -0
- package/registry/items/popover.json +22 -0
- package/registry/items/port-table.json +22 -0
- package/registry/items/portfolio-balance.json +22 -0
- package/registry/items/postback-config.json +26 -0
- package/registry/items/presence.json +20 -0
- package/registry/items/price-ticker.json +21 -0
- package/registry/items/pricing-table.json +24 -0
- package/registry/items/primitive-collapsible.json +16 -0
- package/registry/items/primitive-dialog.json +16 -0
- package/registry/items/primitive-dismissable.json +14 -0
- package/registry/items/primitive-field.json +16 -0
- package/registry/items/primitive-focus-trap.json +14 -0
- package/registry/items/primitive-media-query.json +14 -0
- package/registry/items/primitive-popper.json +14 -0
- package/registry/items/primitive-portal.json +14 -0
- package/registry/items/primitive-radio-group.json +16 -0
- package/registry/items/primitive-router.json +14 -0
- package/registry/items/primitive-slot.json +16 -0
- package/registry/items/primitive-tabs.json +16 -0
- package/registry/items/progress.json +21 -0
- package/registry/items/prompt-input.json +25 -0
- package/registry/items/prompt-template.json +23 -0
- package/registry/items/pull-request-card.json +25 -0
- package/registry/items/query-editor.json +25 -0
- package/registry/items/query-plan.json +23 -0
- package/registry/items/queue-monitor.json +22 -0
- package/registry/items/radio-group.json +22 -0
- package/registry/items/range-slider.json +19 -0
- package/registry/items/rate-limit-meter.json +20 -0
- package/registry/items/rating.json +21 -0
- package/registry/items/reasoning-block.json +23 -0
- package/registry/items/regex-tester.json +25 -0
- package/registry/items/release-list.json +22 -0
- package/registry/items/request-builder.json +28 -0
- package/registry/items/resizable.json +22 -0
- package/registry/items/resource-meter.json +19 -0
- package/registry/items/response-viewer.json +26 -0
- package/registry/items/retrieval-results.json +23 -0
- package/registry/items/revenue-share-table.json +20 -0
- package/registry/items/review-thread.json +25 -0
- package/registry/items/risk-score.json +18 -0
- package/registry/items/root-cause-tree.json +20 -0
- package/registry/items/round-history.json +21 -0
- package/registry/items/runbook-steps.json +24 -0
- package/registry/items/schema-table.json +22 -0
- package/registry/items/schema-viewer.json +22 -0
- package/registry/items/scroll-area.json +18 -0
- package/registry/items/seed-phrase.json +22 -0
- package/registry/items/select.json +24 -0
- package/registry/items/separator.json +18 -0
- package/registry/items/service-status.json +21 -0
- package/registry/items/session-list.json +24 -0
- package/registry/items/sheet.json +24 -0
- package/registry/items/shortcut-sheet.json +19 -0
- package/registry/items/sidebar.json +24 -0
- package/registry/items/skeleton.json +21 -0
- package/registry/items/skip-link.json +19 -0
- package/registry/items/slider.json +21 -0
- package/registry/items/slo-budget.json +19 -0
- package/registry/items/sparkline.json +18 -0
- package/registry/items/spinner.json +20 -0
- package/registry/items/stack-trace.json +23 -0
- package/registry/items/staking-panel.json +24 -0
- package/registry/items/stat.json +21 -0
- package/registry/items/status-checks.json +23 -0
- package/registry/items/stepper.json +22 -0
- package/registry/items/streaming-text.json +19 -0
- package/registry/items/subscription-state.json +22 -0
- package/registry/items/suggestions.json +19 -0
- package/registry/items/swap-panel.json +22 -0
- package/registry/items/switch.json +21 -0
- package/registry/items/symbol-outline.json +22 -0
- package/registry/items/table-of-contents.json +19 -0
- package/registry/items/table.json +19 -0
- package/registry/items/tabs.json +22 -0
- package/registry/items/tag-input.json +21 -0
- package/registry/items/terminal.json +20 -0
- package/registry/items/test-results.json +24 -0
- package/registry/items/textarea.json +21 -0
- package/registry/items/theme-toggle.json +20 -0
- package/registry/items/theme.json +18 -0
- package/registry/items/thread-list.json +24 -0
- package/registry/items/ticket-card.json +24 -0
- package/registry/items/time-picker.json +23 -0
- package/registry/items/timeline.json +19 -0
- package/registry/items/timezone-select.json +18 -0
- package/registry/items/toast.json +22 -0
- package/registry/items/toggle.json +21 -0
- package/registry/items/token-amount.json +18 -0
- package/registry/items/token-approvals.json +25 -0
- package/registry/items/token-select.json +25 -0
- package/registry/items/token-usage.json +20 -0
- package/registry/items/tool-call.json +24 -0
- package/registry/items/toolbar.json +19 -0
- package/registry/items/tooltip.json +19 -0
- package/registry/items/topology-map.json +20 -0
- package/registry/items/tour.json +25 -0
- package/registry/items/traffic-split.json +20 -0
- package/registry/items/transaction-list.json +23 -0
- package/registry/items/transaction-status.json +25 -0
- package/registry/items/tree.json +21 -0
- package/registry/items/two-factor-setup.json +25 -0
- package/registry/items/typing-indicator.json +19 -0
- package/registry/items/typography.json +18 -0
- package/registry/items/upload-list.json +24 -0
- package/registry/items/uptime-strip.json +19 -0
- package/registry/items/user-menu.json +24 -0
- package/registry/items/utm-builder.json +24 -0
- package/registry/items/validator-list.json +23 -0
- package/registry/items/video-player.json +23 -0
- package/registry/items/virtual-list.json +18 -0
- package/registry/items/visually-hidden.json +19 -0
- package/registry/items/wallet-address.json +22 -0
- package/registry/items/wallet-connect.json +25 -0
- package/registry/items/webhook-inspector.json +26 -0
- 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
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[]>;
|
package/bin/registry.js
ADDED
|
@@ -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
|
+
}
|