@astryxdesign/cli 0.1.1 → 0.1.2-canary.03536f1
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/CHANGELOG.md +50 -0
- package/docs/theme.doc.mjs +2 -2
- package/docs/working-with-ai.doc.mjs +6 -6
- package/package.json +31 -8
- package/src/api/component.mjs +253 -12
- package/src/api/discover.mjs +87 -28
- package/src/api/doctor.mjs +7 -22
- package/src/api/index.mjs +1 -0
- package/src/api/layout.mjs +302 -0
- package/src/api/layout.test.mjs +241 -0
- package/src/api/template-integration.test.mjs +225 -0
- package/src/api/template.mjs +421 -122
- package/src/api/template.test.mjs +2 -0
- package/src/api/theme-add.mjs +182 -0
- package/src/api/validate-integration.mjs +370 -0
- package/src/api/validate-integration.test.mjs +222 -0
- package/src/codemod.mjs +93 -0
- package/src/codemod.test.mjs +134 -0
- package/src/codemods/__tests__/registry.test.mjs +2 -0
- package/src/codemods/__tests__/runner.test.mjs +103 -0
- package/src/codemods/integration-discovery.mjs +168 -0
- package/src/codemods/integration-discovery.test.mjs +234 -0
- package/src/codemods/integration-runner.mjs +109 -0
- package/src/codemods/registry.mjs +2 -0
- package/src/codemods/run-codemod.mjs +207 -0
- package/src/codemods/runner.mjs +73 -96
- package/src/codemods/transforms/v0.1.0/index.mjs +0 -9
- package/src/codemods/transforms/v0.1.2/__tests__/rename-text-color-active-to-accent.test.mjs +120 -0
- package/src/codemods/transforms/v0.1.2/index.mjs +19 -0
- package/src/codemods/transforms/v0.1.2/rename-text-color-active-to-accent.mjs +136 -0
- package/src/codemods/transforms/v0.1.3/__tests__/migrate-layout-components-to-experimental.test.mjs +360 -0
- package/src/codemods/transforms/v0.1.3/index.mjs +19 -0
- package/src/codemods/transforms/v0.1.3/migrate-layout-components-to-experimental.mjs +260 -0
- package/src/commands/build-theme.import-path.test.mjs +22 -2
- package/src/commands/build-theme.mjs +185 -16
- package/src/commands/component/index.mjs +44 -10
- package/src/commands/component-ownership.test.mjs +227 -0
- package/src/commands/discover.mjs +5 -16
- package/src/commands/doctor.test.mjs +3 -3
- package/src/commands/init.mjs +34 -8
- package/src/commands/init.next-steps.test.mjs +46 -0
- package/src/commands/json-contract.test.mjs +0 -32
- package/src/commands/layout.mjs +139 -0
- package/src/commands/swizzle.mjs +241 -224
- package/src/commands/swizzle.path-safety.test.mjs +3 -3
- package/src/commands/swizzle.routing.test.mjs +279 -0
- package/src/commands/template.mjs +33 -37
- package/src/commands/upgrade.config-ordering.test.mjs +273 -0
- package/src/commands/upgrade.integration-policy.test.mjs +204 -0
- package/src/commands/upgrade.mjs +349 -239
- package/src/commands/validate-integration.mjs +110 -0
- package/src/commands/validate-integration.test.mjs +124 -0
- package/src/config.mjs +18 -0
- package/src/config.test.mjs +100 -0
- package/src/index.mjs +13 -3
- package/src/integration.mjs +19 -0
- package/src/lib/component-discovery.mjs +177 -0
- package/src/lib/config-cache.mjs +91 -0
- package/src/lib/config-cache.test.mjs +83 -0
- package/src/lib/config-schema.mjs +105 -0
- package/src/lib/error-codes.mjs +18 -12
- package/src/lib/integration-warnings.mjs +62 -0
- package/src/lib/integration-warnings.test.mjs +102 -0
- package/src/lib/integrations.mjs +132 -0
- package/src/lib/integrations.test.mjs +135 -0
- package/src/lib/levenshtein.mjs +29 -0
- package/src/lib/manifest.mjs +15 -2
- package/src/lib/module-loader.mjs +80 -0
- package/src/lib/module-loader.test.mjs +106 -0
- package/src/lib/package-scanner.mjs +31 -7
- package/src/lib/project.mjs +502 -0
- package/src/lib/project.test.mjs +308 -0
- package/src/lib/string-utils.mjs +5 -14
- package/src/lib/xle/browser.d.ts +100 -0
- package/src/lib/xle/browser.mjs +120 -0
- package/src/lib/xle/expand.mjs +622 -0
- package/src/lib/xle/parse.mjs +581 -0
- package/src/lib/xle/print.mjs +174 -0
- package/src/lib/xle/registry-core.mjs +170 -0
- package/src/lib/xle/registry.mjs +237 -0
- package/src/lib/xle/splice.mjs +137 -0
- package/src/lib/xle/validate.mjs +356 -0
- package/src/lib/xle/xle.test.mjs +333 -0
- package/src/template.mjs +73 -0
- package/src/template.test.mjs +127 -0
- package/src/types/api.d.ts +2 -6
- package/src/types/base.d.ts +3 -9
- package/src/types/codemod.d.ts +81 -0
- package/src/types/component.d.ts +27 -2
- package/src/types/config.d.ts +70 -0
- package/src/types/error-codes.d.ts +5 -3
- package/src/types/index.d.ts +0 -1
- package/src/types/integration.d.ts +29 -0
- package/src/types/swizzle.d.ts +9 -2
- package/src/types/template-api.d.ts +54 -0
- package/src/types/template.d.ts +10 -7
- package/src/types/upgrade.d.ts +29 -0
- package/src/types/validate-integration.d.ts +24 -0
- package/src/utils/github.mjs +0 -252
- package/src/utils/interactive.mjs +2 -2
- package/templates/blocks/components/Card/ClickableCardWithNestedButton.doc.mjs +1 -1
- package/templates/blocks/components/Card/SelectableCardMulti.doc.mjs +1 -1
- package/templates/blocks/components/ChatComposerDrawer/ChatComposerDrawerWithProgress.tsx +1 -1
- package/templates/blocks/components/ChatDictationButton/ChatDictationButtonShowcase.tsx +3 -3
- package/templates/blocks/components/CommandPaletteEmpty/CommandPaletteEmptyShowcase.doc.mjs +15 -0
- package/templates/blocks/components/CommandPaletteEmpty/CommandPaletteEmptyShowcase.tsx +26 -0
- package/templates/blocks/components/DateInput/DateInputDateRange.doc.mjs +2 -2
- package/templates/blocks/components/DateRangeInput/DateRangeInputWithPresets.doc.mjs +14 -0
- package/templates/blocks/components/DateRangeInput/DateRangeInputWithPresets.tsx +46 -0
- package/templates/blocks/components/DateRangeInput/DateRangeInputWithValidation.doc.mjs +14 -0
- package/templates/blocks/components/DateRangeInput/DateRangeInputWithValidation.tsx +52 -0
- package/templates/blocks/components/DateTimeInput/DateTimeInputWithValidation.doc.mjs +14 -0
- package/templates/blocks/components/DateTimeInput/DateTimeInputWithValidation.tsx +43 -0
- package/templates/blocks/components/FileInput/FileInputShowcase.tsx +4 -2
- package/templates/blocks/components/HoverCard/HoverCardInteractiveContent.doc.mjs +1 -1
- package/templates/blocks/components/HoverCard/HoverCardInteractiveContent.tsx +9 -4
- package/templates/blocks/components/LinkProvider/LinkProviderCustomLink.doc.mjs +14 -0
- package/templates/blocks/components/LinkProvider/LinkProviderCustomLink.tsx +43 -0
- package/templates/blocks/components/MultiSelector/MultiSelectorShowcase.tsx +6 -2
- package/templates/blocks/components/NumberInput/NumberInputShowcase.tsx +6 -2
- package/templates/blocks/components/Outline/OutlineShowcase.doc.mjs +15 -0
- package/templates/blocks/components/Outline/OutlineShowcase.tsx +22 -0
- package/templates/blocks/components/RadioList/RadioListShowcase.tsx +8 -1
- package/templates/blocks/components/SegmentedControl/SegmentedControlShowcase.tsx +3 -1
- package/templates/blocks/components/Selector/SelectorShowcase.tsx +4 -1
- package/templates/blocks/components/Slider/SliderShowcase.tsx +10 -1
- package/templates/blocks/components/Tab/TabShowcase.tsx +3 -1
- package/templates/blocks/components/Tab/TabWithSelectedIcon.doc.mjs +13 -0
- package/templates/blocks/components/Tab/TabWithSelectedIcon.tsx +39 -0
- package/templates/blocks/components/TabList/TabListShowcase.tsx +3 -1
- package/templates/blocks/components/TabMenu/TabMenuShowcase.tsx +3 -1
- package/templates/blocks/components/Table/ColumnResizeHookUsage.doc.mjs +14 -0
- package/templates/blocks/components/Table/ColumnResizeHookUsage.tsx +59 -0
- package/templates/blocks/components/Table/StickyColumnsHookUsage.doc.mjs +14 -0
- package/templates/blocks/components/Table/StickyColumnsHookUsage.tsx +104 -0
- package/templates/blocks/components/Text/TextColors.tsx +20 -5
- package/templates/blocks/components/TextArea/TextAreaShowcase.tsx +4 -2
- package/templates/blocks/components/TextInput/TextInputShowcase.tsx +4 -2
- package/templates/blocks/components/Thumbnail/ThumbnailDisabled.tsx +11 -6
- package/templates/blocks/components/Thumbnail/ThumbnailGallery.tsx +43 -7
- package/templates/blocks/components/Thumbnail/ThumbnailRemovable.tsx +40 -3
- package/templates/blocks/components/Thumbnail/ThumbnailShowcase.tsx +6 -5
- package/templates/blocks/components/Thumbnail/ThumbnailStates.tsx +11 -6
- package/templates/blocks/components/Timestamp/TimestampColors.tsx +2 -2
- package/templates/blocks/components/ToggleButton/ToggleButtonGroup.doc.mjs +1 -1
- package/templates/blocks/components/Tokenizer/TokenizerShowcase.tsx +8 -6
- package/templates/blocks/components/Toolbar/ToolbarTableFilter.doc.mjs +2 -2
- package/templates/blocks/components/Toolbar/ToolbarTableFilter.tsx +23 -14
- package/templates/blocks/components/Typeahead/TypeaheadShowcase.tsx +6 -2
- package/templates/pages/shell-nav/page.tsx +321 -0
- package/templates/pages/shell-nav/template.doc.mjs +12 -0
- package/templates/pages/shell-side-nav/page.tsx +242 -0
- package/templates/pages/shell-side-nav/template.doc.mjs +12 -0
- package/templates/pages/shell-top-nav/page.tsx +224 -0
- package/templates/pages/shell-top-nav/template.doc.mjs +12 -0
- package/templates/pages/theme-showcase/page.tsx +8 -19
- package/templates/themes/butter/butterTheme.ts +916 -0
- package/templates/themes/butter/icons.tsx +77 -0
- package/templates/themes/chocolate/chocolateTheme.ts +230 -0
- package/templates/themes/chocolate/icons.tsx +77 -0
- package/templates/themes/gothic/gothicTheme.ts +657 -0
- package/templates/themes/gothic/icons.tsx +77 -0
- package/templates/themes/manifest.json +90 -0
- package/templates/themes/matcha/icons.tsx +67 -0
- package/templates/themes/matcha/matchaTheme.ts +247 -0
- package/templates/themes/neutral/icons.tsx +77 -0
- package/templates/themes/neutral/neutralTheme.ts +603 -0
- package/templates/themes/stone/icons.tsx +77 -0
- package/templates/themes/stone/stoneTheme.ts +652 -0
- package/templates/themes/y2k/icons.tsx +67 -0
- package/templates/themes/y2k/y2kTheme.ts +617 -0
- package/src/codemods/transforms/v0.1.0/__tests__/migrate-xds-config-surfaces.test.mjs +0 -116
- package/src/codemods/transforms/v0.1.0/migrate-xds-config-surfaces.mjs +0 -230
- package/src/commands/gap-report.mjs +0 -456
- package/src/commands/gap-report.test.mjs +0 -163
- package/src/commands/swizzle-gap-safety.test.mjs +0 -273
- package/src/lib/config.mjs +0 -86
- package/src/lib/config.test.mjs +0 -42
- package/src/types/gap-report.d.ts +0 -29
- package/templates/blocks/components/MoreMenu/MoreMenuInToolbar.doc.mjs +0 -14
- package/templates/blocks/components/MoreMenu/MoreMenuInToolbar.tsx +0 -57
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,55 @@
|
|
|
1
1
|
# @xds/cli
|
|
2
2
|
|
|
3
|
+
# 0.1.2
|
|
4
|
+
|
|
5
|
+
#### Breaking Changes
|
|
6
|
+
|
|
7
|
+
- `Text`, `Heading`, `Link`, and `Timestamp` rename the `color="active"` value to `color="accent"`, now mapping to the dedicated `--color-text-accent` token (legible accent text ink) instead of `--color-accent`. Run `astryx upgrade` to migrate call sites automatically. (#2863)
|
|
8
|
+
|
|
9
|
+
#### New Features
|
|
10
|
+
|
|
11
|
+
- Let `astryx.config.mjs` integrations contribute package docs, gap-report hooks, template fetching hooks, upgrade codemods, and post-codemod hooks.
|
|
12
|
+
- Add `astryx theme add <slug> [path]` (and `astryx theme list`) to scaffold a theme's source into your project as editable files you own, with theme sources bundled into the CLI
|
|
13
|
+
|
|
14
|
+
#### Fixes
|
|
15
|
+
|
|
16
|
+
- align `astryx init` theme instructions with the runtime built-theme recommendation (#3080)
|
|
17
|
+
`astryx init` now points users at the pre-built theme path (`@astryxdesign/theme-neutral/built` + `theme.css`) and the base CSS imports, matching the runtime `<Theme>` console guidance, instead of the slower runtime style-injection import that left apps unstyled.
|
|
18
|
+
- `astryx theme build` now derives every output file (.css/.js/.d.ts) from the theme name so they share one naming scheme, shows import paths as bare `./<name>` specifiers (instead of a cwd-rooted `./src/...` path that was wrong when your file already lives under src/), and no longer warns about the `variant` prop on `card`
|
|
19
|
+
|
|
20
|
+
#### Documentation
|
|
21
|
+
|
|
22
|
+
- Rename the ClickableCard and SelectableCard examples to follow the "Component — Variant" title convention (`Clickable Card — Nested Button`, `Selectable Card — Multi-select`), and add playground defaults to both card docs so their docsite previews show realistic card content (#2877)
|
|
23
|
+
- Declare playground scaffolds for the Chat sub-components so they preview at a realistic width (ChatComposer and ChatComposerDrawer wrap in a sized container, and the drawer seeds default content), and drop the redundant visible value label from the ChatComposerDrawer "With Progress" example while keeping the accessible label (#2877)
|
|
24
|
+
- Rename the DateInput "Date Range" example to "Min/Max Constraints" — it demos a single input constrained to a min/max window, not a date-range picker (#2692)
|
|
25
|
+
- Wire local state into more showcase examples that were frozen (static value + no-op onChange): TextInput, TextArea, NumberInput, SegmentedControl, RadioList, Tab, TabList, and TabMenu. Follows the same fix as the Slider/Selector/MultiSelector showcases so the docsite previews are actually interactive
|
|
26
|
+
- Wire local state into the Typeahead, Tokenizer, and FileInput showcase examples (static value + no-op onChange → frozen previews). Completes the interactive-showcase fixes started for Slider/Selector/MultiSelector (#3187-#3189) and the input/tab batch
|
|
27
|
+
- Wire local state into the Slider, Selector, and MultiSelector showcase examples so they are interactive — they were controlled components with a static value and a no-op/missing onChange, so the docsite previews appeared frozen (#3187, #3188, #3189)
|
|
28
|
+
- Add a LinkProvider example block showing how to swap in a framework router link (e.g. Next.js Link) for client-side routing (#2733)
|
|
29
|
+
- Add a showcase block for Outline so its docs page has a hero preview, alongside the existing example blocks (#2871)
|
|
30
|
+
- Remove the "MoreMenu — In Toolbar" example block — it rendered incorrectly and was redundant with the other MoreMenu examples (#2870)
|
|
31
|
+
- Add rendered example blocks for the two column-axis Table plugin hooks,
|
|
32
|
+
shown on their own subcomponent pages:
|
|
33
|
+
- Move the "ToggleButton — Group" example to the ToggleButtonGroup page, where it belongs (it demonstrates grouped toggle behavior) (#2842)
|
|
34
|
+
- Make the Toolbar "Table Filter" example use real Selector controls for its Status and Priority filters instead of buttons styled to look like dropdowns, and add meaningful playground defaults plus richer slot options (buttons, icon buttons, tabs, segmented controls, selectors) to the Toolbar docs (#2877).
|
|
35
|
+
|
|
36
|
+
#### Other Changes
|
|
37
|
+
|
|
38
|
+
- `useTableStickyColumns — Pinned Columns` (on /components/useTableStickyColumns)
|
|
39
|
+
- `useTableColumnResize — Draggable Columns` (on /components/useTableColumnResize)
|
|
40
|
+
|
|
41
|
+
#### Contributors
|
|
42
|
+
|
|
43
|
+
Thanks to everyone who contributed to this release:
|
|
44
|
+
|
|
45
|
+
- @cixzhang
|
|
46
|
+
- @durvesh1992
|
|
47
|
+
- @ejhammond
|
|
48
|
+
- @humbertovirtudes
|
|
49
|
+
- @rubyycheung
|
|
50
|
+
|
|
51
|
+
---
|
|
52
|
+
|
|
3
53
|
# 0.1.1
|
|
4
54
|
|
|
5
55
|
#### New Features
|
package/docs/theme.doc.mjs
CHANGED
|
@@ -53,7 +53,7 @@ function App() {
|
|
|
53
53
|
},
|
|
54
54
|
{
|
|
55
55
|
type: 'prose',
|
|
56
|
-
text: 'Each theme ships as its own npm package. Install the one you want, then wrap your app in `<Theme
|
|
56
|
+
text: 'Each theme ships as its own npm package. Install the one you want, then wrap your app in `<Theme>`. The same pattern works for every theme; just swap the package and import name.',
|
|
57
57
|
},
|
|
58
58
|
{
|
|
59
59
|
type: 'prose',
|
|
@@ -261,7 +261,7 @@ const brandTheme = defineTheme({
|
|
|
261
261
|
content: [
|
|
262
262
|
{
|
|
263
263
|
type: 'prose',
|
|
264
|
-
text: 'The `components` field in defineTheme uses semantic component keys and style keys
|
|
264
|
+
text: 'The `components` field in defineTheme uses semantic component keys and style keys, not raw CSS selectors. Use `base` for all instances, `variant:value` or `stateName` for specific props/states, and let the theme pipeline choose the underlying selector. For raw external CSS escape hatches, prefer the data-attribute selector surface documented in `astryx docs styling`.',
|
|
265
265
|
},
|
|
266
266
|
{
|
|
267
267
|
type: 'code',
|
|
@@ -34,11 +34,11 @@ export const docs = {
|
|
|
34
34
|
type: 'code',
|
|
35
35
|
lang: 'text',
|
|
36
36
|
label: 'Paste this into your AI',
|
|
37
|
-
code: 'Install @astryxdesign/cli and run `npx astryx
|
|
37
|
+
code: 'Install @astryxdesign/cli and run `npx astryx init --features agents` to set up your Astryx context. Read the generated file.',
|
|
38
38
|
},
|
|
39
39
|
{
|
|
40
40
|
type: 'prose',
|
|
41
|
-
text: 'That\'s it. The
|
|
41
|
+
text: 'That\'s it. The `init --features agents` command generates everything your AI needs (component index, behavioral rules, CLI reference) pulled from your installed version. After a version bump, run it again to update in place.',
|
|
42
42
|
},
|
|
43
43
|
{
|
|
44
44
|
type: 'prose',
|
|
@@ -48,9 +48,9 @@ export const docs = {
|
|
|
48
48
|
type: 'code',
|
|
49
49
|
lang: 'bash',
|
|
50
50
|
label: 'Manual options',
|
|
51
|
-
code: `npx astryx
|
|
52
|
-
npx astryx
|
|
53
|
-
npx astryx
|
|
51
|
+
code: `npx astryx init --features agents --agent claude # CLAUDE.md
|
|
52
|
+
npx astryx init --features agents --agent cursor # .cursorrules
|
|
53
|
+
npx astryx init --features agents --agent codex # AGENTS.md (Copilot, Codex, etc.)`,
|
|
54
54
|
},
|
|
55
55
|
],
|
|
56
56
|
},
|
|
@@ -88,7 +88,7 @@ npx astryx agent-docs --agent codex # AGENTS.md (Copilot, Codex, etc.)`,
|
|
|
88
88
|
lang: 'bash',
|
|
89
89
|
label: 'Install as a Cursor user rule',
|
|
90
90
|
code: `mkdir -p ~/.cursor/rules
|
|
91
|
-
npx astryx
|
|
91
|
+
npx astryx init --features agents --agent-docs-path ~/.cursor/rules/xds.mdc`,
|
|
92
92
|
},
|
|
93
93
|
],
|
|
94
94
|
},
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@astryxdesign/cli",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.2-canary.03536f1",
|
|
4
4
|
"displayName": "CLI",
|
|
5
5
|
"description": "Scaffold projects, browse templates, generate themes, and get agent-ready docs from the command line.",
|
|
6
6
|
"author": "Meta Open Source",
|
|
@@ -38,6 +38,26 @@
|
|
|
38
38
|
"./api": {
|
|
39
39
|
"types": "./src/types/api.d.ts",
|
|
40
40
|
"import": "./src/api/index.mjs"
|
|
41
|
+
},
|
|
42
|
+
"./config": {
|
|
43
|
+
"types": "./src/types/config.d.ts",
|
|
44
|
+
"import": "./src/config.mjs"
|
|
45
|
+
},
|
|
46
|
+
"./integration": {
|
|
47
|
+
"types": "./src/types/integration.d.ts",
|
|
48
|
+
"import": "./src/integration.mjs"
|
|
49
|
+
},
|
|
50
|
+
"./template": {
|
|
51
|
+
"types": "./src/types/template-api.d.ts",
|
|
52
|
+
"import": "./src/template.mjs"
|
|
53
|
+
},
|
|
54
|
+
"./codemod": {
|
|
55
|
+
"types": "./src/types/codemod.d.ts",
|
|
56
|
+
"import": "./src/codemod.mjs"
|
|
57
|
+
},
|
|
58
|
+
"./xle": {
|
|
59
|
+
"types": "./src/lib/xle/browser.d.ts",
|
|
60
|
+
"import": "./src/lib/xle/browser.mjs"
|
|
41
61
|
}
|
|
42
62
|
},
|
|
43
63
|
"files": [
|
|
@@ -51,12 +71,14 @@
|
|
|
51
71
|
"@clack/prompts": "^1.5.1",
|
|
52
72
|
"commander": "^12.1.0",
|
|
53
73
|
"jiti": "^2.7.0",
|
|
54
|
-
"jscodeshift": "^17.3.0"
|
|
74
|
+
"jscodeshift": "^17.3.0",
|
|
75
|
+
"zod": "^4.4.3"
|
|
55
76
|
},
|
|
56
77
|
"peerDependencies": {
|
|
57
|
-
"@astryxdesign/core": "
|
|
58
|
-
"@astryxdesign/lab": "
|
|
59
|
-
"@astryxdesign/theme-neutral": "
|
|
78
|
+
"@astryxdesign/core": "0.1.2-canary.03536f1",
|
|
79
|
+
"@astryxdesign/lab": "0.1.2-canary.03536f1",
|
|
80
|
+
"@astryxdesign/theme-neutral": "0.1.2-canary.03536f1",
|
|
81
|
+
"gpt-tokenizer": "^2.0.0"
|
|
60
82
|
},
|
|
61
83
|
"peerDependenciesMeta": {
|
|
62
84
|
"@astryxdesign/core": {
|
|
@@ -70,9 +92,10 @@
|
|
|
70
92
|
}
|
|
71
93
|
},
|
|
72
94
|
"devDependencies": {
|
|
73
|
-
"@astryxdesign/core": "
|
|
74
|
-
"@astryxdesign/lab": "
|
|
75
|
-
"@astryxdesign/theme-neutral": "
|
|
95
|
+
"@astryxdesign/core": "0.1.2-canary.03536f1",
|
|
96
|
+
"@astryxdesign/lab": "0.1.2-canary.03536f1",
|
|
97
|
+
"@astryxdesign/theme-neutral": "0.1.2-canary.03536f1",
|
|
98
|
+
"gpt-tokenizer": "^2.0.0"
|
|
76
99
|
},
|
|
77
100
|
"scripts": {
|
|
78
101
|
"astryx": "node bin/astryx.mjs",
|
package/src/api/component.mjs
CHANGED
|
@@ -11,18 +11,48 @@ import * as fs from 'node:fs';
|
|
|
11
11
|
import {ERROR_CODES} from '../lib/error-codes.mjs';
|
|
12
12
|
import {findCoreDir, discoverExternalPackages} from '../utils/paths.mjs';
|
|
13
13
|
import {
|
|
14
|
+
CORE_PACKAGE,
|
|
14
15
|
discoverComponents,
|
|
15
16
|
discoverExternalComponentsGrouped,
|
|
17
|
+
discoverIntegrationComponents,
|
|
16
18
|
findComponentReadme,
|
|
17
19
|
findComponentSource,
|
|
18
20
|
findExternalComponentDoc,
|
|
21
|
+
findIntegrationComponentDoc,
|
|
22
|
+
findIntegrationComponentSource,
|
|
19
23
|
resolveImportPath,
|
|
20
24
|
} from '../lib/component-discovery.mjs';
|
|
25
|
+
import {Project} from '../lib/project.mjs';
|
|
21
26
|
import {loadDocs} from '../lib/component-loader.mjs';
|
|
22
27
|
import {searchComponents} from '../lib/string-utils.mjs';
|
|
23
28
|
import {AstryxError} from './error.mjs';
|
|
24
29
|
import {findShowcase, findRelatedBlocks} from './template.mjs';
|
|
25
30
|
|
|
31
|
+
/**
|
|
32
|
+
* Load the configured integrations for `cwd`, swallowing any config errors so
|
|
33
|
+
* component discovery never hard-fails on a malformed/absent integration. An
|
|
34
|
+
* empty list means "core only".
|
|
35
|
+
* @param {string} cwd
|
|
36
|
+
* @returns {Promise<Array<{name: string, components?: string, issuesUrl?: string}>>}
|
|
37
|
+
*/
|
|
38
|
+
async function loadIntegrationsSafely(cwd) {
|
|
39
|
+
try {
|
|
40
|
+
const project = await Project.load(cwd);
|
|
41
|
+
return project.loadedIntegrations;
|
|
42
|
+
} catch {
|
|
43
|
+
return [];
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Resolve a loaded integration by package name.
|
|
49
|
+
* @param {Array<{name: string}>} loadedIntegrations
|
|
50
|
+
* @param {string} packageName
|
|
51
|
+
*/
|
|
52
|
+
function findLoadedIntegration(loadedIntegrations, packageName) {
|
|
53
|
+
return loadedIntegrations.find(i => i.name === packageName) ?? null;
|
|
54
|
+
}
|
|
55
|
+
|
|
26
56
|
/**
|
|
27
57
|
* Resolve an external package by name from the discovered externals list.
|
|
28
58
|
* @param {string} packageName - e.g. '@acme/xds-widgets'
|
|
@@ -131,8 +161,13 @@ export async function component(name, options = {}) {
|
|
|
131
161
|
return {type: 'component.full', data: {[match[0]]: entries}};
|
|
132
162
|
}
|
|
133
163
|
|
|
134
|
-
// Default: brief —
|
|
135
|
-
|
|
164
|
+
// Default: brief — package-qualified object list for the category.
|
|
165
|
+
// Pre-1.0 JSON contract: members are {name, package} objects, not bare
|
|
166
|
+
// strings, so consumers can disambiguate ownership.
|
|
167
|
+
return {
|
|
168
|
+
type: 'component.list',
|
|
169
|
+
data: {[match[0]]: match[1].map(n => ({name: n, package: CORE_PACKAGE}))},
|
|
170
|
+
};
|
|
136
171
|
}
|
|
137
172
|
|
|
138
173
|
// All components — merge core + external packages with grouped subcategories
|
|
@@ -179,43 +214,189 @@ export async function component(name, options = {}) {
|
|
|
179
214
|
return {type: 'component.full', data: result};
|
|
180
215
|
}
|
|
181
216
|
|
|
182
|
-
// Default: brief —
|
|
217
|
+
// Default: brief — package-qualified object list (core + integrations).
|
|
218
|
+
// Pre-1.0 JSON contract: each group's members are {name, package} objects.
|
|
219
|
+
/** @type {Record<string, Array<{name: string, package: string}>>} */
|
|
220
|
+
const listData = {};
|
|
221
|
+
for (const [cat, comps] of Object.entries(components)) {
|
|
222
|
+
listData[cat] = comps.map(n => ({name: n, package: CORE_PACKAGE}));
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
// Integration components (authoritative source: loadedIntegrations).
|
|
226
|
+
const loadedIntegrations = await loadIntegrationsSafely(cwd);
|
|
227
|
+
const seenIntegration = new Set();
|
|
228
|
+
for (const integration of loadedIntegrations) {
|
|
229
|
+
seenIntegration.add(integration.name);
|
|
230
|
+
const owned = discoverIntegrationComponents(integration);
|
|
231
|
+
// Group integration components by their doc `group`, falling back to the
|
|
232
|
+
// package name. Keys are package-qualified so they never collide with
|
|
233
|
+
// core groups or each other.
|
|
234
|
+
/** @type {Map<string, Array<{name: string, package: string}>>} */
|
|
235
|
+
const byGroup = new Map();
|
|
236
|
+
for (const rec of owned) {
|
|
237
|
+
const groupLabel = rec.group ?? integration.name;
|
|
238
|
+
const key = `${groupLabel} (${integration.name})`;
|
|
239
|
+
if (!byGroup.has(key)) byGroup.set(key, []);
|
|
240
|
+
byGroup.get(key).push({name: rec.name, package: integration.name});
|
|
241
|
+
}
|
|
242
|
+
for (const [key, members] of byGroup) {
|
|
243
|
+
members.sort((a, b) => a.name.localeCompare(b.name));
|
|
244
|
+
listData[key] = members;
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
// Back-compat: node_modules-scanned external packages (pkg.astryx.docs)
|
|
249
|
+
// that are NOT configured integrations. Preserves existing discovery for
|
|
250
|
+
// consumers that haven't adopted the config-integration flow.
|
|
183
251
|
const externals = discoverExternalPackages(cwd);
|
|
184
252
|
for (const ext of externals) {
|
|
253
|
+
if (seenIntegration.has(ext.name)) continue;
|
|
185
254
|
const grouped = discoverExternalComponentsGrouped(ext.docsDir);
|
|
186
255
|
const groupKeys = Object.keys(grouped);
|
|
187
256
|
if (groupKeys.length === 0) continue;
|
|
188
257
|
|
|
189
|
-
// If the package has subcategories (groups), emit each as a separate key.
|
|
190
|
-
// If no groups exist, fall back to the flat list under one key.
|
|
191
258
|
const hasGroups = groupKeys.some(
|
|
192
259
|
k => grouped[k].length > 1 || grouped[k][0] !== k,
|
|
193
260
|
);
|
|
194
261
|
|
|
195
262
|
if (hasGroups) {
|
|
196
263
|
for (const [group, members] of Object.entries(grouped)) {
|
|
197
|
-
|
|
264
|
+
listData[`${group} (${ext.name})`] = members.map(n => ({
|
|
265
|
+
name: n,
|
|
266
|
+
package: ext.name,
|
|
267
|
+
}));
|
|
198
268
|
}
|
|
199
269
|
} else {
|
|
200
|
-
// All ungrouped — single flat list under the package category
|
|
201
270
|
const allComps = Object.values(grouped).flat().sort();
|
|
202
271
|
if (allComps.length > 0) {
|
|
203
|
-
|
|
272
|
+
listData[`${ext.category} (${ext.name})`] = allComps.map(n => ({
|
|
273
|
+
name: n,
|
|
274
|
+
package: ext.name,
|
|
275
|
+
}));
|
|
204
276
|
}
|
|
205
277
|
}
|
|
206
278
|
}
|
|
207
|
-
return {type: 'component.list', data:
|
|
279
|
+
return {type: 'component.list', data: listData};
|
|
208
280
|
}
|
|
209
281
|
|
|
210
282
|
// ── Single component ───────────────────────────────────────────
|
|
211
283
|
|
|
284
|
+
if (typeof name !== 'string') {
|
|
285
|
+
throw new AstryxError(
|
|
286
|
+
`No component named "${String(name)}"`,
|
|
287
|
+
undefined,
|
|
288
|
+
ERROR_CODES.ERR_UNKNOWN_COMPONENT,
|
|
289
|
+
);
|
|
290
|
+
}
|
|
291
|
+
|
|
212
292
|
const dirName = name.replace(/^XDS/, '');
|
|
213
293
|
|
|
294
|
+
// Ownership-aware resolution. Build the set of OWNER packages that provide a
|
|
295
|
+
// component with this name across core + every loaded integration. This is
|
|
296
|
+
// what lets the CLI disambiguate by package and expose the owner's source +
|
|
297
|
+
// issuesUrl (the inputs the future integration-component swizzle needs).
|
|
298
|
+
const loadedIntegrations = await loadIntegrationsSafely(cwd);
|
|
299
|
+
const coreDocPath = findComponentReadme(coreDir, dirName);
|
|
300
|
+
/**
|
|
301
|
+
* @type {Array<{
|
|
302
|
+
* package: string,
|
|
303
|
+
* docPath: string,
|
|
304
|
+
* sourcePath: string|null,
|
|
305
|
+
* issuesUrl: string|undefined,
|
|
306
|
+
* integration: object|null,
|
|
307
|
+
* }>}
|
|
308
|
+
*/
|
|
309
|
+
const owners = [];
|
|
310
|
+
if (coreDocPath) {
|
|
311
|
+
owners.push({
|
|
312
|
+
package: CORE_PACKAGE,
|
|
313
|
+
docPath: coreDocPath,
|
|
314
|
+
sourcePath: findComponentSource(coreDir, dirName),
|
|
315
|
+
issuesUrl: undefined,
|
|
316
|
+
integration: null,
|
|
317
|
+
});
|
|
318
|
+
}
|
|
319
|
+
for (const integration of loadedIntegrations) {
|
|
320
|
+
const docPath = findIntegrationComponentDoc(integration, dirName);
|
|
321
|
+
if (!docPath) continue;
|
|
322
|
+
owners.push({
|
|
323
|
+
package: integration.name,
|
|
324
|
+
docPath,
|
|
325
|
+
sourcePath: findIntegrationComponentSource(integration, dirName),
|
|
326
|
+
issuesUrl: integration.issuesUrl,
|
|
327
|
+
integration,
|
|
328
|
+
});
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
/**
|
|
332
|
+
* Augment a loaded `component.detail` doc with ownership metadata. Adds
|
|
333
|
+
* `package`, the resolved `import` specifier, and `sourceAvailable` (whether
|
|
334
|
+
* a swizzleable source file exists for the owner). Existing doc fields
|
|
335
|
+
* (name, usage, props, …) are preserved.
|
|
336
|
+
* @param {object} docs
|
|
337
|
+
* @param {{package: string, sourcePath: string|null}} owner
|
|
338
|
+
* @param {string} componentName
|
|
339
|
+
*/
|
|
340
|
+
function withOwnership(docs, owner, componentName) {
|
|
341
|
+
const importSpec =
|
|
342
|
+
owner.package === CORE_PACKAGE
|
|
343
|
+
? resolveImportPath(coreDir, componentName)
|
|
344
|
+
: `${owner.package}/${componentName}`;
|
|
345
|
+
return {
|
|
346
|
+
...docs,
|
|
347
|
+
package: owner.package,
|
|
348
|
+
import: importSpec,
|
|
349
|
+
sourceAvailable: owner.sourcePath != null,
|
|
350
|
+
};
|
|
351
|
+
}
|
|
352
|
+
|
|
214
353
|
// When scoped to a specific package, search that package first.
|
|
215
354
|
// This is critical for components that exist in both core and an external
|
|
216
355
|
// package (e.g. AppShell, Button, SideNav) — the package scope ensures
|
|
217
356
|
// the external package's docs are returned, not core's.
|
|
218
357
|
if (packageScope) {
|
|
358
|
+
// Core scope: resolve from core directly.
|
|
359
|
+
if (packageScope === CORE_PACKAGE) {
|
|
360
|
+
const owner = owners.find(o => o.package === CORE_PACKAGE);
|
|
361
|
+
if (!owner) {
|
|
362
|
+
throw new AstryxError(`No component "${name}" in package "${packageScope}"`, undefined, ERROR_CODES.ERR_UNKNOWN_COMPONENT);
|
|
363
|
+
}
|
|
364
|
+
if (source) {
|
|
365
|
+
if (!owner.sourcePath) {
|
|
366
|
+
throw new AstryxError(`Source for "${name}" not found`, undefined, ERROR_CODES.ERR_NO_SOURCE);
|
|
367
|
+
}
|
|
368
|
+
return {type: 'component.detail.source', data: {component: dirName, source: fs.readFileSync(owner.sourcePath, 'utf-8')}};
|
|
369
|
+
}
|
|
370
|
+
const docs = await loadDocs(owner.docPath, {zh, dense, lang});
|
|
371
|
+
if (props) {
|
|
372
|
+
const p = docs.props || (docs.components ? docs.components.flatMap(c => c.props || []) : []);
|
|
373
|
+
return {type: 'component.detail.props', data: p};
|
|
374
|
+
}
|
|
375
|
+
return {type: 'component.detail', data: withOwnership(docs, owner, dirName)};
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
// Integration scope (authoritative): resolve from the loaded integration.
|
|
379
|
+
const integration = findLoadedIntegration(loadedIntegrations, packageScope);
|
|
380
|
+
if (integration) {
|
|
381
|
+
const owner = owners.find(o => o.package === packageScope);
|
|
382
|
+
if (!owner) {
|
|
383
|
+
throw new AstryxError(`No component "${name}" in package "${packageScope}"`, undefined, ERROR_CODES.ERR_UNKNOWN_COMPONENT);
|
|
384
|
+
}
|
|
385
|
+
if (source) {
|
|
386
|
+
if (!owner.sourcePath) {
|
|
387
|
+
throw new AstryxError(`Source for "${name}" not found in package "${packageScope}"`, undefined, ERROR_CODES.ERR_NO_SOURCE);
|
|
388
|
+
}
|
|
389
|
+
return {type: 'component.detail.source', data: {component: dirName, source: fs.readFileSync(owner.sourcePath, 'utf-8')}};
|
|
390
|
+
}
|
|
391
|
+
const docs = await loadDocs(owner.docPath, {zh, dense, lang});
|
|
392
|
+
if (props) {
|
|
393
|
+
const p = docs.props || (docs.components ? docs.components.flatMap(c => c.props || []) : []);
|
|
394
|
+
return {type: 'component.detail.props', data: p};
|
|
395
|
+
}
|
|
396
|
+
return {type: 'component.detail', data: withOwnership(docs, owner, dirName)};
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
// Legacy fallback: node_modules `pkg.astryx.docs` external package.
|
|
219
400
|
const ext = resolveExternalPackage(packageScope, cwd);
|
|
220
401
|
if (!ext) {
|
|
221
402
|
throw new AstryxError(`External package "${packageScope}" not found`, undefined, ERROR_CODES.ERR_UNKNOWN_PACKAGE);
|
|
@@ -244,11 +425,49 @@ export async function component(name, options = {}) {
|
|
|
244
425
|
const p = docs.props || (docs.components ? docs.components.flatMap(c => c.props || []) : []);
|
|
245
426
|
return {type: 'component.detail.props', data: p};
|
|
246
427
|
}
|
|
247
|
-
return {
|
|
428
|
+
return {
|
|
429
|
+
type: 'component.detail',
|
|
430
|
+
data: withOwnership(docs, {package: ext.name, sourcePath: null}, dirName),
|
|
431
|
+
};
|
|
248
432
|
}
|
|
249
433
|
throw new AstryxError(`No component "${name}" in package "${packageScope}"`, undefined, ERROR_CODES.ERR_UNKNOWN_COMPONENT);
|
|
250
434
|
}
|
|
251
435
|
|
|
436
|
+
// Ambiguity: when the name is owned by MORE THAN ONE package (core and/or
|
|
437
|
+
// integrations) and the caller did not scope with --package, refuse to guess.
|
|
438
|
+
// NOTE: legacy `pkg.astryx.docs` externals are intentionally NOT part of this
|
|
439
|
+
// ambiguity set — they retain their historical core-first fallback below so
|
|
440
|
+
// existing consumers (and tests) keep working. Only config-driven integration
|
|
441
|
+
// ownership participates here.
|
|
442
|
+
if (owners.length > 1) {
|
|
443
|
+
throw new AstryxError(
|
|
444
|
+
`Component "${dirName}" is provided by multiple packages. Re-run with --package <pkg> to choose one.`,
|
|
445
|
+
owners.map(o => ({name: o.package, reason: 'provides this component'})),
|
|
446
|
+
ERROR_CODES.ERR_UNKNOWN_COMPONENT,
|
|
447
|
+
);
|
|
448
|
+
}
|
|
449
|
+
|
|
450
|
+
// Single non-core owner (an integration provides it, core does not) — resolve
|
|
451
|
+
// from that integration so the integration component is authoritative.
|
|
452
|
+
if (owners.length === 1 && owners[0].package !== CORE_PACKAGE) {
|
|
453
|
+
const owner = owners[0];
|
|
454
|
+
if (source) {
|
|
455
|
+
if (!owner.sourcePath) {
|
|
456
|
+
throw new AstryxError(`Source for "${name}" not found`, undefined, ERROR_CODES.ERR_NO_SOURCE);
|
|
457
|
+
}
|
|
458
|
+
return {type: 'component.detail.source', data: {component: dirName, source: fs.readFileSync(owner.sourcePath, 'utf-8')}};
|
|
459
|
+
}
|
|
460
|
+
if (showcase) {
|
|
461
|
+
throw new AstryxError(`No showcase found for "${name}"`, undefined, ERROR_CODES.ERR_NO_SHOWCASE);
|
|
462
|
+
}
|
|
463
|
+
const docs = await loadDocs(owner.docPath, {zh, dense, lang});
|
|
464
|
+
if (props) {
|
|
465
|
+
const p = docs.props || (docs.components ? docs.components.flatMap(c => c.props || []) : []);
|
|
466
|
+
return {type: 'component.detail.props', data: p};
|
|
467
|
+
}
|
|
468
|
+
return {type: 'component.detail', data: withOwnership(docs, owner, dirName)};
|
|
469
|
+
}
|
|
470
|
+
|
|
252
471
|
if (source) {
|
|
253
472
|
const sourcePath = findComponentSource(coreDir, dirName);
|
|
254
473
|
if (!sourcePath) {
|
|
@@ -274,6 +493,10 @@ export async function component(name, options = {}) {
|
|
|
274
493
|
|
|
275
494
|
let readmePath = findComponentReadme(coreDir, dirName);
|
|
276
495
|
let resolvedName = dirName;
|
|
496
|
+
// Track the resolving owner so the detail payload can carry ownership info.
|
|
497
|
+
// Defaults to core; the legacy-external fallback below may reassign it.
|
|
498
|
+
let resolvedOwnerPackage = CORE_PACKAGE;
|
|
499
|
+
let resolvedSourcePath = readmePath ? findComponentSource(coreDir, dirName) : null;
|
|
277
500
|
|
|
278
501
|
if (!readmePath) {
|
|
279
502
|
const externals = discoverExternalPackages(cwd);
|
|
@@ -281,6 +504,8 @@ export async function component(name, options = {}) {
|
|
|
281
504
|
const extDocPath = findExternalComponentDoc(ext.docsDir, dirName);
|
|
282
505
|
if (extDocPath) {
|
|
283
506
|
readmePath = extDocPath;
|
|
507
|
+
resolvedOwnerPackage = ext.name;
|
|
508
|
+
resolvedSourcePath = null;
|
|
284
509
|
break;
|
|
285
510
|
}
|
|
286
511
|
}
|
|
@@ -299,6 +524,8 @@ export async function component(name, options = {}) {
|
|
|
299
524
|
if (topScore >= 90 && topTied.length === 1 && gap >= 20) {
|
|
300
525
|
resolvedName = topTied[0].name;
|
|
301
526
|
readmePath = findComponentReadme(coreDir, resolvedName);
|
|
527
|
+
resolvedOwnerPackage = CORE_PACKAGE;
|
|
528
|
+
resolvedSourcePath = findComponentSource(coreDir, resolvedName);
|
|
302
529
|
} else {
|
|
303
530
|
const threshold = Math.max(topScore - 20, 1);
|
|
304
531
|
const candidates = results.filter(r => r.score >= threshold).slice(0, 5);
|
|
@@ -382,7 +609,14 @@ export async function component(name, options = {}) {
|
|
|
382
609
|
if (props) {
|
|
383
610
|
return {type: 'component.detail.props', data: matchingComponent.props || []};
|
|
384
611
|
}
|
|
385
|
-
return {
|
|
612
|
+
return {
|
|
613
|
+
type: 'component.detail',
|
|
614
|
+
data: withOwnership(
|
|
615
|
+
scoped,
|
|
616
|
+
{package: resolvedOwnerPackage, sourcePath: resolvedSourcePath},
|
|
617
|
+
dirName,
|
|
618
|
+
),
|
|
619
|
+
};
|
|
386
620
|
}
|
|
387
621
|
|
|
388
622
|
if (props) {
|
|
@@ -390,5 +624,12 @@ export async function component(name, options = {}) {
|
|
|
390
624
|
return {type: 'component.detail.props', data: p};
|
|
391
625
|
}
|
|
392
626
|
|
|
393
|
-
return {
|
|
627
|
+
return {
|
|
628
|
+
type: 'component.detail',
|
|
629
|
+
data: withOwnership(
|
|
630
|
+
docs,
|
|
631
|
+
{package: resolvedOwnerPackage, sourcePath: resolvedSourcePath},
|
|
632
|
+
resolvedName,
|
|
633
|
+
),
|
|
634
|
+
};
|
|
394
635
|
}
|