@astryxdesign/cli 0.1.0-canary.797f761 → 0.1.0-canary.7f46cdb
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/README.md +117 -75
- package/docs/getting-started.doc.mjs +11 -11
- package/docs/styling.doc.mjs +1 -2
- package/docs/working-with-ai.doc.mjs +1 -1
- package/package.json +7 -7
- package/src/api/search.mjs +207 -13
- package/src/api/template.mjs +2 -1
- package/src/commands/agent-docs.mjs +23 -6
- package/src/commands/build.mjs +196 -0
- package/src/commands/init.mjs +10 -2
- package/src/commands/json-contract.test.mjs +9 -2
- package/src/commands/upgrade.mjs +254 -149
- package/src/commands/upgrade.test.mjs +40 -26
- package/src/index.mjs +1 -0
- package/src/utils/package-manager.mjs +1 -1
- package/templates/blocks/components/AppShell/AppShellContentOnly.tsx +1 -9
- package/templates/blocks/components/AppShell/AppShellShowcase.tsx +1 -10
- package/templates/blocks/components/AppShell/AppShellSideNavOnly.tsx +1 -9
- package/templates/blocks/components/AppShell/AppShellTopNavOnly.tsx +1 -9
- package/templates/blocks/components/AppShell/AppShellTopNavWithSideNav.tsx +1 -9
- package/templates/blocks/components/AppShell/AppShellWithBanner.tsx +1 -9
- package/templates/blocks/components/AspectRatio/AspectRatioShowcase.tsx +12 -19
- package/templates/blocks/components/Banner/BannerShowcase.tsx +1 -8
- package/templates/blocks/components/Blockquote/BlockquoteShowcase.tsx +1 -8
- package/templates/blocks/components/Carousel/CarouselShowcase.tsx +2 -12
- package/templates/blocks/components/ChatComposerDrawer/ChatComposerDrawerShowcase.tsx +6 -9
- package/templates/blocks/components/ChatLayout/ChatLayoutPanelChat.tsx +10 -12
- package/templates/blocks/components/ChatMessageList/ChatMessageListDensity.tsx +1 -9
- package/templates/blocks/components/ChatMessageList/ChatMessageListFullFeatured.tsx +1 -9
- package/templates/blocks/components/ChatMessageList/ChatMessageListShowcase.tsx +1 -9
- package/templates/blocks/components/ChatMessageMetadata/ChatMessageMetadataShowcase.tsx +1 -8
- package/templates/blocks/components/ChatSendButton/ChatSendButtonInComposer.tsx +1 -8
- package/templates/blocks/components/Citation/CitationInlineText.tsx +4 -4
- package/templates/blocks/components/Code/CodeInlineInParagraph.tsx +1 -8
- package/templates/blocks/components/CodeBlock/CodeBlockBashCommand.tsx +1 -1
- package/templates/blocks/components/CodeBlock/CodeBlockJSONConfig.tsx +1 -1
- package/templates/blocks/components/CommandPaletteItem/CommandPaletteItemShowcase.tsx +9 -12
- package/templates/blocks/components/ContextMenu/ContextMenuShowcase.tsx +13 -15
- package/templates/blocks/components/Divider/DividerShowcase.tsx +1 -8
- package/templates/blocks/components/Divider/DividerVertical.tsx +7 -9
- package/templates/blocks/components/Field/FieldShowcase.tsx +1 -8
- package/templates/blocks/components/FormLayout/FormLayoutHorizontal.tsx +1 -6
- package/templates/blocks/components/Grid/GridResponsiveAutoFit.tsx +1 -9
- package/templates/blocks/components/HoverCard/HoverCardInlineTextHoverCard.tsx +4 -6
- package/templates/blocks/components/HoverCard/HoverCardInteractiveContent.tsx +1 -6
- package/templates/blocks/components/HoverCard/HoverCardProfileHoverCard.tsx +2 -8
- package/templates/blocks/components/HoverCard/HoverCardShowcase.tsx +1 -8
- package/templates/blocks/components/MoreMenu/MoreMenuInToolbar.tsx +2 -12
- package/templates/blocks/components/OverflowList/OverflowListOverflowBadges.tsx +8 -11
- package/templates/blocks/components/OverflowList/OverflowListOverflowDropdownActions.tsx +9 -12
- package/templates/blocks/components/Overlay/OverlayBottomStrip.tsx +4 -17
- package/templates/blocks/components/Overlay/OverlayHoverReveal.tsx +15 -16
- package/templates/blocks/components/Overlay/OverlayShowcase.tsx +5 -21
- package/templates/blocks/components/Pagination/PaginationDotsCarousel.tsx +2 -14
- package/templates/blocks/components/Pagination/PaginationPageSize.tsx +12 -14
- package/templates/blocks/components/Pagination/PaginationVariants.tsx +1 -8
- package/templates/blocks/components/Pagination/PaginationWithTable.tsx +2 -14
- package/templates/blocks/components/Tokenizer/TokenizerClear.tsx +1 -6
- package/templates/blocks/components/Tokenizer/TokenizerCreatable.tsx +2 -7
- package/templates/blocks/components/Tokenizer/TokenizerEndContent.tsx +1 -6
- package/templates/blocks/components/Tokenizer/TokenizerIcon.tsx +1 -6
- package/templates/blocks/components/Tokenizer/TokenizerMaxEntries.tsx +1 -6
- package/templates/blocks/components/Tokenizer/TokenizerOverflow.tsx +2 -7
- package/templates/blocks/components/Tokenizer/TokenizerShowcase.tsx +1 -6
- package/templates/blocks/components/Tokenizer/TokenizerStates.tsx +4 -9
- package/templates/blocks/components/Toolbar/ToolbarCardHeader.tsx +1 -10
- package/templates/blocks/components/Toolbar/ToolbarSizes.tsx +1 -8
- package/templates/blocks/components/Toolbar/ToolbarTableFilter.tsx +1 -8
- package/templates/blocks/components/Toolbar/ToolbarThreeSlot.tsx +1 -10
- package/templates/blocks/components/Toolbar/ToolbarWithTabs.tsx +8 -11
- package/templates/pages/ai-chat/page.tsx +71 -64
- package/templates/pages/ai-chat-landing/page.tsx +8 -12
- package/templates/pages/centered-hero/page.tsx +13 -15
- package/templates/pages/classic-gallery/page.tsx +27 -34
- package/templates/pages/detail-page/page.tsx +18 -18
- package/templates/pages/documentation/page.tsx +42 -58
- package/templates/pages/documentation-design/page.tsx +82 -60
- package/templates/pages/documentation-technical/page.tsx +101 -60
- package/templates/pages/editor/page.tsx +42 -54
- package/templates/pages/file-explorer/page.tsx +13 -16
- package/templates/pages/form-two-column/page.tsx +13 -17
- package/templates/pages/gallery-hero/page.tsx +13 -15
- package/templates/pages/ide/page.tsx +188 -264
- package/templates/pages/library/page.tsx +16 -23
- package/templates/pages/login/page.tsx +14 -18
- package/templates/pages/login-card/page.tsx +14 -18
- package/templates/pages/login-split/page.tsx +50 -48
- package/templates/pages/login-sso/page.tsx +9 -13
- package/templates/pages/mixed-gallery/page.tsx +51 -45
- package/templates/pages/payment-form/page.tsx +56 -70
- package/templates/pages/product-detail/page.tsx +27 -33
- package/templates/pages/product-gallery/page.tsx +7 -13
- package/templates/pages/settings-dialog/page.tsx +35 -43
- package/templates/pages/settings-sidebar/page.tsx +39 -47
- package/templates/pages/side-gallery/page.tsx +6 -9
- package/templates/pages/table-grouped/page.tsx +11 -15
- package/templates/pages/theme-showcase/page.tsx +33 -37
package/README.md
CHANGED
|
@@ -11,7 +11,7 @@ npx astryx docs migration
|
|
|
11
11
|
npx astryx template --list
|
|
12
12
|
```
|
|
13
13
|
|
|
14
|
-
|
|
14
|
+
## Finding things: `astryx search`
|
|
15
15
|
|
|
16
16
|
When you don't know whether what you need is a component, a hook, a docs topic,
|
|
17
17
|
or a template, search across all of them at once. Results are ranked by
|
|
@@ -50,20 +50,20 @@ Options:
|
|
|
50
50
|
|
|
51
51
|
## Commands
|
|
52
52
|
|
|
53
|
-
| Command | Description
|
|
54
|
-
| ------------- |
|
|
53
|
+
| Command | Description |
|
|
54
|
+
| ------------- | ---------------------------------------------------------------------------------------------------- |
|
|
55
55
|
| `init` | Initialize the design system in your project: installs packages, sets up theming, adds AI agent docs |
|
|
56
|
-
| `component` | List components or print detailed docs, props, usage examples, and source
|
|
57
|
-
| `search` | Find components, hooks, docs, and templates in one ranked, cross-domain result set
|
|
58
|
-
| `docs` | Print reference documentation (tokens, theme, color, typography, spacing, etc.)
|
|
59
|
-
| `template` | Inject page or block templates into your project
|
|
60
|
-
| `hook` | List hooks and print hook documentation
|
|
61
|
-
| `swizzle` | Copy component source into your project for deep customization
|
|
62
|
-
| `upgrade` | Run codemods to migrate between versions
|
|
63
|
-
| `theme build` | Compile a defineTheme file to production CSS and JS
|
|
64
|
-
| `discover` | Discover external packages and components
|
|
65
|
-
| `gap-report` | Report a gap when a component doesn't meet your needs
|
|
66
|
-
| `doctor` | Diagnose your XDS setup and report problems with fixes (CI-friendly via exit code)
|
|
56
|
+
| `component` | List components or print detailed docs, props, usage examples, and source |
|
|
57
|
+
| `search` | Find components, hooks, docs, and templates in one ranked, cross-domain result set |
|
|
58
|
+
| `docs` | Print reference documentation (tokens, theme, color, typography, spacing, etc.) |
|
|
59
|
+
| `template` | Inject page or block templates into your project |
|
|
60
|
+
| `hook` | List hooks and print hook documentation |
|
|
61
|
+
| `swizzle` | Copy component source into your project for deep customization |
|
|
62
|
+
| `upgrade` | Run codemods to migrate between versions |
|
|
63
|
+
| `theme build` | Compile a defineTheme file to production CSS and JS |
|
|
64
|
+
| `discover` | Discover external packages and components |
|
|
65
|
+
| `gap-report` | Report a gap when a component doesn't meet your needs |
|
|
66
|
+
| `doctor` | Diagnose your XDS setup and report problems with fixes (CI-friendly via exit code) |
|
|
67
67
|
|
|
68
68
|
### Global options
|
|
69
69
|
|
|
@@ -122,46 +122,46 @@ if (isError(result)) {
|
|
|
122
122
|
|
|
123
123
|
### Error codes
|
|
124
124
|
|
|
125
|
-
| Code
|
|
126
|
-
|
|
|
127
|
-
| `ERR_UNKNOWN`
|
|
128
|
-
| `ERR_UNKNOWN_COMMAND`
|
|
129
|
-
| `ERR_UNKNOWN_SUBCOMMAND` | A subcommand under a group was not recognized (e.g. `astryx theme bogus`).
|
|
130
|
-
| `ERR_INVALID_OPTION`
|
|
131
|
-
| `ERR_INVALID_ARGUMENT`
|
|
132
|
-
| `ERR_MISSING_ARGUMENT`
|
|
133
|
-
| `ERR_INVALID_LANG`
|
|
134
|
-
| `ERR_INVALID_DETAIL`
|
|
135
|
-
| `ERR_NODE_VERSION`
|
|
136
|
-
| `ERR_CORE_NOT_FOUND`
|
|
137
|
-
| `ERR_UNKNOWN_COMPONENT`
|
|
138
|
-
| `ERR_UNKNOWN_HOOK`
|
|
139
|
-
| `ERR_UNKNOWN_TOPIC`
|
|
140
|
-
| `ERR_UNKNOWN_SECTION`
|
|
141
|
-
| `ERR_UNKNOWN_CATEGORY`
|
|
142
|
-
| `ERR_UNKNOWN_TEMPLATE`
|
|
143
|
-
| `ERR_UNKNOWN_PACKAGE`
|
|
144
|
-
| `ERR_UNKNOWN_AGENT`
|
|
145
|
-
| `ERR_UNKNOWN_FEATURE`
|
|
146
|
-
| `ERR_UNKNOWN_CODEMOD`
|
|
147
|
-
| `ERR_NOT_FOUND`
|
|
148
|
-
| `ERR_NO_DOC`
|
|
149
|
-
| `ERR_NO_SHOWCASE`
|
|
150
|
-
| `ERR_NO_SOURCE`
|
|
151
|
-
| `ERR_INVALID_DOC`
|
|
152
|
-
| `ERR_FILE_NOT_FOUND`
|
|
153
|
-
| `ERR_FILE_EXISTS`
|
|
154
|
-
| `ERR_PATH_TRAVERSAL`
|
|
155
|
-
| `ERR_WRITE_FAILED`
|
|
156
|
-
| `ERR_THEME_INVALID`
|
|
157
|
-
| `ERR_THEME_LOAD`
|
|
158
|
-
| `ERR_TEMPLATE_CONFIG`
|
|
159
|
-
| `ERR_TEMPLATE_GET`
|
|
160
|
-
| `ERR_VERSION_DETECT`
|
|
161
|
-
| `ERR_INVALID_VERSION`
|
|
162
|
-
| `ERR_DEP_MISSING`
|
|
163
|
-
| `ERR_GH_CLI`
|
|
164
|
-
| `ERR_GAP_REPORT_FAILED`
|
|
125
|
+
| Code | Meaning |
|
|
126
|
+
| ------------------------ | -------------------------------------------------------------------------------------- |
|
|
127
|
+
| `ERR_UNKNOWN` | Generic fallback for any error without a more specific code. |
|
|
128
|
+
| `ERR_UNKNOWN_COMMAND` | A top-level command name was not recognized (e.g. `astryx bogus`). |
|
|
129
|
+
| `ERR_UNKNOWN_SUBCOMMAND` | A subcommand under a group was not recognized (e.g. `astryx theme bogus`). |
|
|
130
|
+
| `ERR_INVALID_OPTION` | An unknown flag was passed, or `--json` was used on a command that doesn't support it. |
|
|
131
|
+
| `ERR_INVALID_ARGUMENT` | An option/argument value was rejected, or required flags were missing. |
|
|
132
|
+
| `ERR_MISSING_ARGUMENT` | A required positional argument was omitted (e.g. `astryx theme build` with no file). |
|
|
133
|
+
| `ERR_INVALID_LANG` | `--lang` was given a value outside its choices (`en`, `zh`, `dense`). |
|
|
134
|
+
| `ERR_INVALID_DETAIL` | `--detail` was given a value outside its choices (`full`, `compact`, `brief`). |
|
|
135
|
+
| `ERR_NODE_VERSION` | The running Node.js version is below the supported minimum. |
|
|
136
|
+
| `ERR_CORE_NOT_FOUND` | `@astryxdesign/core` could not be located (not installed / not in a monorepo). |
|
|
137
|
+
| `ERR_UNKNOWN_COMPONENT` | No component matched the requested name. |
|
|
138
|
+
| `ERR_UNKNOWN_HOOK` | No hook matched the requested name. |
|
|
139
|
+
| `ERR_UNKNOWN_TOPIC` | No docs topic matched the requested name. |
|
|
140
|
+
| `ERR_UNKNOWN_SECTION` | A docs topic exists but the requested section within it does not. |
|
|
141
|
+
| `ERR_UNKNOWN_CATEGORY` | A `--category` filter value did not match any known category. |
|
|
142
|
+
| `ERR_UNKNOWN_TEMPLATE` | No template matched the requested name. |
|
|
143
|
+
| `ERR_UNKNOWN_PACKAGE` | No package matched the requested name (discover). |
|
|
144
|
+
| `ERR_UNKNOWN_AGENT` | An unrecognized `--agent` value was passed (agent docs / init). |
|
|
145
|
+
| `ERR_UNKNOWN_FEATURE` | An unrecognized `--features` value was passed to `init`. |
|
|
146
|
+
| `ERR_UNKNOWN_CODEMOD` | A `--codemod` value did not match any registered codemod (upgrade). |
|
|
147
|
+
| `ERR_NOT_FOUND` | A discover/lookup query matched nothing in any package. |
|
|
148
|
+
| `ERR_NO_DOC` | A component exists but has no typed `.doc.mjs` file. |
|
|
149
|
+
| `ERR_NO_SHOWCASE` | No showcase exists for the requested component. |
|
|
150
|
+
| `ERR_NO_SOURCE` | No source file could be located for the component/template. |
|
|
151
|
+
| `ERR_INVALID_DOC` | A component's docs failed validation (malformed `.doc.mjs`). |
|
|
152
|
+
| `ERR_FILE_NOT_FOUND` | A required input file did not exist. |
|
|
153
|
+
| `ERR_FILE_EXISTS` | Refused to overwrite an existing file in non-interactive mode. |
|
|
154
|
+
| `ERR_PATH_TRAVERSAL` | A path escaped its allowed root, or a name contained traversal markers. |
|
|
155
|
+
| `ERR_WRITE_FAILED` | Writing output files failed (and was rolled back). |
|
|
156
|
+
| `ERR_THEME_INVALID` | A theme definition was missing a required property (e.g. `name`). |
|
|
157
|
+
| `ERR_THEME_LOAD` | A theme file could not be loaded / parsed into a `defineTheme` result. |
|
|
158
|
+
| `ERR_TEMPLATE_CONFIG` | `template.get` is not configured in `astryx.config.mjs` (fetch-by-id). |
|
|
159
|
+
| `ERR_TEMPLATE_GET` | A configured `template.get` threw or returned an invalid value. |
|
|
160
|
+
| `ERR_VERSION_DETECT` | The current `@astryxdesign/core` version could not be detected. |
|
|
161
|
+
| `ERR_INVALID_VERSION` | A `--from`/`--to` value was not a valid semver string. |
|
|
162
|
+
| `ERR_DEP_MISSING` | A required external dependency (e.g. jscodeshift) is missing. |
|
|
163
|
+
| `ERR_GH_CLI` | GitHub CLI (`gh`) is not installed or not authenticated. |
|
|
164
|
+
| `ERR_GAP_REPORT_FAILED` | Filing a gap report failed (disabled, or the integration errored). |
|
|
165
165
|
|
|
166
166
|
## Capability manifest (agent discovery)
|
|
167
167
|
|
|
@@ -186,25 +186,59 @@ Shape:
|
|
|
186
186
|
"version": "0.0.14",
|
|
187
187
|
"description": "Design system CLI — components, themes, and tooling",
|
|
188
188
|
"globalOptions": [
|
|
189
|
-
{
|
|
190
|
-
|
|
191
|
-
|
|
189
|
+
{
|
|
190
|
+
"flag": "--json",
|
|
191
|
+
"type": "boolean",
|
|
192
|
+
"description": "Output as typed JSON…",
|
|
193
|
+
},
|
|
194
|
+
{
|
|
195
|
+
"flag": "--lang <locale>",
|
|
196
|
+
"type": "enum",
|
|
197
|
+
"choices": ["en", "zh", "dense"],
|
|
198
|
+
},
|
|
199
|
+
{
|
|
200
|
+
"flag": "--detail <level>",
|
|
201
|
+
"type": "enum",
|
|
202
|
+
"choices": ["full", "compact", "brief"],
|
|
203
|
+
"default": "full",
|
|
204
|
+
},
|
|
192
205
|
],
|
|
193
206
|
"commands": [
|
|
194
207
|
{
|
|
195
208
|
"name": "component",
|
|
196
209
|
"description": "List components or print component docs",
|
|
197
|
-
"arguments": [
|
|
198
|
-
|
|
210
|
+
"arguments": [
|
|
211
|
+
{
|
|
212
|
+
"name": "name",
|
|
213
|
+
"required": false,
|
|
214
|
+
"variadic": false,
|
|
215
|
+
"description": "",
|
|
216
|
+
},
|
|
217
|
+
],
|
|
218
|
+
"options": [
|
|
219
|
+
{
|
|
220
|
+
"flag": "--props",
|
|
221
|
+
"type": "boolean",
|
|
222
|
+
"description": "Print only the props table",
|
|
223
|
+
},
|
|
224
|
+
],
|
|
199
225
|
"json": true,
|
|
200
|
-
"responseTypes": [
|
|
201
|
-
|
|
202
|
-
|
|
226
|
+
"responseTypes": [
|
|
227
|
+
"component.list",
|
|
228
|
+
"component.detail",
|
|
229
|
+
"component.detail.props",
|
|
230
|
+
"…",
|
|
231
|
+
],
|
|
232
|
+
"examples": ["astryx component Button --props --json"],
|
|
233
|
+
},
|
|
203
234
|
// …one entry per command; subcommands (e.g. `theme build`) nest under `subcommands`
|
|
204
235
|
],
|
|
205
236
|
"jsonSupported": ["component", "docs", "…"],
|
|
206
|
-
"responseTypes": {
|
|
207
|
-
|
|
237
|
+
"responseTypes": {
|
|
238
|
+
"component": ["component.list", "…"],
|
|
239
|
+
"theme build": ["theme.build"],
|
|
240
|
+
},
|
|
241
|
+
},
|
|
208
242
|
}
|
|
209
243
|
```
|
|
210
244
|
|
|
@@ -225,7 +259,15 @@ For the standalone manifest envelope (`type: "manifest"`), use `astryx manifest
|
|
|
225
259
|
The same logic that powers `xds --json` is available as importable, type-safe functions:
|
|
226
260
|
|
|
227
261
|
```typescript
|
|
228
|
-
import {
|
|
262
|
+
import {
|
|
263
|
+
component,
|
|
264
|
+
docs,
|
|
265
|
+
discover,
|
|
266
|
+
template,
|
|
267
|
+
hook,
|
|
268
|
+
search,
|
|
269
|
+
AstryxError,
|
|
270
|
+
} from '@astryxdesign/cli/api';
|
|
229
271
|
|
|
230
272
|
// Same result as: xds --json component Button
|
|
231
273
|
const btn = await component('Button');
|
|
@@ -359,16 +401,16 @@ No failures — but review the ⚠ warnings above when you can.
|
|
|
359
401
|
|
|
360
402
|
### Checks
|
|
361
403
|
|
|
362
|
-
| Check
|
|
363
|
-
|
|
|
364
|
-
| Node.js version
|
|
365
|
-
| @astryxdesign/core installed
|
|
366
|
-
| Version alignment
|
|
367
|
-
| Theme packages
|
|
368
|
-
| astryx.config.mjs
|
|
369
|
-
| AI agent docs
|
|
370
|
-
| Peer dependencies
|
|
371
|
-
| Package manager
|
|
404
|
+
| Check | Status it can return | What it verifies |
|
|
405
|
+
| ---------------------------- | -------------------- | -------------------------------------------------------------------- |
|
|
406
|
+
| Node.js version | pass / fail | Running Node meets the CLI's minimum |
|
|
407
|
+
| @astryxdesign/core installed | pass / fail | `@astryxdesign/core` is resolvable from the project |
|
|
408
|
+
| Version alignment | pass / warn / info | Installed `@astryxdesign/core` is in step with `@astryxdesign/cli` |
|
|
409
|
+
| Theme packages | pass / warn | An `@astryxdesign/theme-*` package is installed and a theme is wired |
|
|
410
|
+
| astryx.config.mjs | pass / fail / info | Config (if present) loads cleanly with a valid shape |
|
|
411
|
+
| AI agent docs | pass / warn / info | Agent docs exist and contain the XDS section markers |
|
|
412
|
+
| Peer dependencies | pass / warn / info | `@astryxdesign/core`'s peer deps (react, …) are installed |
|
|
413
|
+
| Package manager | info | Reports the detected package manager |
|
|
372
414
|
|
|
373
415
|
### CI gate
|
|
374
416
|
|
|
@@ -88,7 +88,7 @@ import {VStack} from '@astryxdesign/core/Layout';
|
|
|
88
88
|
export default function Page() {
|
|
89
89
|
return (
|
|
90
90
|
<VStack gap={2}>
|
|
91
|
-
<Button label="Hello
|
|
91
|
+
<Button label="Hello Astryx" onClick={() => alert('Hi!')} />
|
|
92
92
|
</VStack>
|
|
93
93
|
);
|
|
94
94
|
}`,
|
|
@@ -96,11 +96,11 @@ export default function Page() {
|
|
|
96
96
|
],
|
|
97
97
|
},
|
|
98
98
|
{
|
|
99
|
-
title: 'Customize with
|
|
99
|
+
title: 'Customize with StyleX',
|
|
100
100
|
content: [
|
|
101
101
|
{
|
|
102
102
|
type: 'prose',
|
|
103
|
-
text: '
|
|
103
|
+
text: 'Astryx components support various styling solutions, from plain CSS and `className` to Tailwind and CSS-in-JS. See the [styling docs](/docs/styling) for the full guide. Astryx also has a deep integration with [StyleX](https://stylexjs.com/), an atomic CSS-in-JS library: create styles with `stylex.create()` and pass them to components with the `xstyle` prop.',
|
|
104
104
|
},
|
|
105
105
|
{
|
|
106
106
|
type: 'code',
|
|
@@ -127,19 +127,19 @@ const overrides = stylex.create({
|
|
|
127
127
|
type: 'table',
|
|
128
128
|
headers: ['Example', 'Stack', 'Path'],
|
|
129
129
|
rows: [
|
|
130
|
-
['Next.js', 'Next.js + theme CSS', 'apps/example-nextjs'],
|
|
131
|
-
['Next.js + StyleX', 'Next.js + StyleX for custom styles', 'apps/example-nextjs-stylex'],
|
|
132
|
-
['Next.js + Tailwind', 'Next.js + Tailwind bridge', 'apps/example-nextjs-tailwind'],
|
|
133
|
-
['Next.js Source', 'Next.js importing from source', 'apps/example-nextjs-source'],
|
|
134
|
-
['Vite', 'Vite', 'apps/example-vite'],
|
|
130
|
+
['Next.js', 'Next.js + theme CSS', '[apps/example-nextjs](https://github.com/facebook/astryx/tree/main/apps/example-nextjs)'],
|
|
131
|
+
['Next.js + StyleX', 'Next.js + StyleX for custom styles', '[apps/example-nextjs-stylex](https://github.com/facebook/astryx/tree/main/apps/example-nextjs-stylex)'],
|
|
132
|
+
['Next.js + Tailwind', 'Next.js + Tailwind bridge', '[apps/example-nextjs-tailwind](https://github.com/facebook/astryx/tree/main/apps/example-nextjs-tailwind)'],
|
|
133
|
+
['Next.js Source', 'Next.js importing from source', '[apps/example-nextjs-source](https://github.com/facebook/astryx/tree/main/apps/example-nextjs-source)'],
|
|
134
|
+
['Vite', 'Vite', '[apps/example-vite](https://github.com/facebook/astryx/tree/main/apps/example-vite)'],
|
|
135
135
|
],
|
|
136
136
|
},
|
|
137
137
|
{
|
|
138
138
|
type: 'code',
|
|
139
139
|
lang: 'bash',
|
|
140
140
|
label: 'Clone and run an example',
|
|
141
|
-
code: `git clone https://github.com/
|
|
142
|
-
cd
|
|
141
|
+
code: `git clone https://github.com/facebook/astryx.git
|
|
142
|
+
cd astryx/apps/example-nextjs
|
|
143
143
|
pnpm install
|
|
144
144
|
pnpm dev`,
|
|
145
145
|
},
|
|
@@ -157,7 +157,7 @@ pnpm dev`,
|
|
|
157
157
|
lang: 'json',
|
|
158
158
|
label: 'package.json',
|
|
159
159
|
code: `"scripts": {
|
|
160
|
-
"
|
|
160
|
+
"astryx": "node node_modules/@astryxdesign/cli/bin/astryx.mjs"
|
|
161
161
|
}`,
|
|
162
162
|
},
|
|
163
163
|
{
|
package/docs/styling.doc.mjs
CHANGED
|
@@ -22,9 +22,8 @@ export const docs = {
|
|
|
22
22
|
type: 'table',
|
|
23
23
|
headers: ['Approach', 'Use for', 'Example'],
|
|
24
24
|
rows: [
|
|
25
|
-
['
|
|
25
|
+
['StyleX', 'Component-specific overrides, reusable styles, pseudo-classes, and typed tokens', 'const styles = stylex.create(...); <Button xstyle={styles.save} />'],
|
|
26
26
|
['Tailwind utilities', 'Layout, wrappers, and utility styling', 'className="flex gap-3 p-4"'],
|
|
27
|
-
['stylex.create', 'Reusable styles, pseudo-classes, typed tokens', 'stylex.create({ card: { ... } })'],
|
|
28
27
|
['className', 'Integrating with external CSS or Tailwind on components', 'className="my-card shadow-lg"'],
|
|
29
28
|
['Styling-library token aliases', 'Keeping Panda, Chakra, MUI, Emotion, styled-components, UnoCSS, CSS Modules, or Sass in sync with the system', "colors.surface = 'var(--color-background-surface)'"],
|
|
30
29
|
],
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@astryxdesign/cli",
|
|
3
|
-
"version": "0.1.0-canary.
|
|
3
|
+
"version": "0.1.0-canary.7f46cdb",
|
|
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",
|
|
@@ -54,9 +54,9 @@
|
|
|
54
54
|
"jscodeshift": "^17.3.0"
|
|
55
55
|
},
|
|
56
56
|
"peerDependencies": {
|
|
57
|
-
"@astryxdesign/core": "0.1.0-canary.
|
|
58
|
-
"@astryxdesign/lab": "0.1.0-canary.
|
|
59
|
-
"@astryxdesign/theme-neutral": "0.1.0-canary.
|
|
57
|
+
"@astryxdesign/core": "0.1.0-canary.7f46cdb",
|
|
58
|
+
"@astryxdesign/lab": "0.1.0-canary.7f46cdb",
|
|
59
|
+
"@astryxdesign/theme-neutral": "0.1.0-canary.7f46cdb"
|
|
60
60
|
},
|
|
61
61
|
"peerDependenciesMeta": {
|
|
62
62
|
"@astryxdesign/core": {
|
|
@@ -70,9 +70,9 @@
|
|
|
70
70
|
}
|
|
71
71
|
},
|
|
72
72
|
"devDependencies": {
|
|
73
|
-
"@astryxdesign/core": "0.1.0-canary.
|
|
74
|
-
"@astryxdesign/lab": "0.1.0-canary.
|
|
75
|
-
"@astryxdesign/theme-neutral": "0.1.0-canary.
|
|
73
|
+
"@astryxdesign/core": "0.1.0-canary.7f46cdb",
|
|
74
|
+
"@astryxdesign/lab": "0.1.0-canary.7f46cdb",
|
|
75
|
+
"@astryxdesign/theme-neutral": "0.1.0-canary.7f46cdb"
|
|
76
76
|
},
|
|
77
77
|
"scripts": {
|
|
78
78
|
"astryx": "node bin/astryx.mjs",
|
package/src/api/search.mjs
CHANGED
|
@@ -41,14 +41,184 @@ import {
|
|
|
41
41
|
} from '../lib/component-discovery.mjs';
|
|
42
42
|
import {discoverHooks, findHookDoc} from '../lib/hook-discovery.mjs';
|
|
43
43
|
import {levenshteinDistance} from '../lib/string-utils.mjs';
|
|
44
|
-
import {discoverTemplates} from './template.mjs';
|
|
44
|
+
import {discoverTemplates, extractComponents} from './template.mjs';
|
|
45
45
|
import {AstryxError} from './error.mjs';
|
|
46
46
|
|
|
47
47
|
const DOCS_DIR = path.join(CLI_ROOT, 'docs');
|
|
48
48
|
|
|
49
|
+
/**
|
|
50
|
+
* Synonym / intent map: product-language terms an agent is likely to type,
|
|
51
|
+
* expanded to the catalog's vocabulary so oblique queries still rank. Keys and
|
|
52
|
+
* values are matched bidirectionally (typing any value also pulls in the key
|
|
53
|
+
* and its siblings). Lowercase, single words or short phrases.
|
|
54
|
+
*/
|
|
55
|
+
const SYNONYMS = {
|
|
56
|
+
dashboard: ['overview', 'analytics', 'kpi', 'kpis', 'metrics', 'stats', 'reporting', 'insights', 'control'],
|
|
57
|
+
login: ['signin', 'auth', 'authentication', 'sso', 'credentials', 'account'],
|
|
58
|
+
signup: ['register', 'registration', 'onboarding'],
|
|
59
|
+
payment: ['checkout', 'billing', 'card', 'pay', 'purchase', 'order'],
|
|
60
|
+
pricing: ['plans', 'plan', 'tiers', 'tier', 'subscription', 'subscriptions'],
|
|
61
|
+
chat: ['messaging', 'message', 'messages', 'conversation', 'inbox', 'dm'],
|
|
62
|
+
settings: ['preferences', 'config', 'configuration', 'account'],
|
|
63
|
+
calendar: ['schedule', 'scheduling', 'events', 'event', 'month', 'agenda'],
|
|
64
|
+
table: ['list', 'rows', 'records', 'grid', 'spreadsheet', 'datatable'],
|
|
65
|
+
gallery: ['photos', 'photo', 'images', 'image', 'pictures'],
|
|
66
|
+
hero: ['banner', 'splash', 'headline', 'landing'],
|
|
67
|
+
form: ['fields', 'input', 'inputs', 'survey'],
|
|
68
|
+
profile: ['bio', 'avatar', 'user'],
|
|
69
|
+
documentation: ['docs', 'reference', 'guide', 'api'],
|
|
70
|
+
navigation: ['nav', 'menu', 'sidebar'],
|
|
71
|
+
};
|
|
72
|
+
|
|
73
|
+
// Flatten into a token -> Set(expansions) lookup (bidirectional).
|
|
74
|
+
const SYNONYM_INDEX = (() => {
|
|
75
|
+
const idx = new Map();
|
|
76
|
+
const add = (a, b) => {
|
|
77
|
+
if (!idx.has(a)) idx.set(a, new Set());
|
|
78
|
+
idx.get(a).add(b);
|
|
79
|
+
};
|
|
80
|
+
for (const [key, vals] of Object.entries(SYNONYMS)) {
|
|
81
|
+
for (const v of vals) {
|
|
82
|
+
add(key, v);
|
|
83
|
+
add(v, key);
|
|
84
|
+
for (const v2 of vals) if (v2 !== v) add(v, v2);
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
return idx;
|
|
88
|
+
})();
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* Light stemmer: strips common English suffixes so "charts"/"charting" and
|
|
92
|
+
* "chart" share a root. Deliberately crude (no Porter) — good enough to bridge
|
|
93
|
+
* plural/gerund gaps without a dependency.
|
|
94
|
+
* @param {string} w
|
|
95
|
+
* @returns {string}
|
|
96
|
+
*/
|
|
97
|
+
export function stem(w) {
|
|
98
|
+
let s = w;
|
|
99
|
+
for (const suf of ['ing', 'ed', 'ies', 'es', 's']) {
|
|
100
|
+
if (s.length > suf.length + 2 && s.endsWith(suf)) {
|
|
101
|
+
s = suf === 'ies' ? s.slice(0, -3) + 'y' : s.slice(0, -suf.length);
|
|
102
|
+
break;
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
return s;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
|
|
49
109
|
/** Valid domain filters for `--type`. */
|
|
50
110
|
export const SEARCH_DOMAINS = ['component', 'hook', 'doc', 'template'];
|
|
51
111
|
|
|
112
|
+
/**
|
|
113
|
+
* Filler words stripped from multi-word queries so natural-language phrasing
|
|
114
|
+
* ("a page where you can see business stats") ranks on its content words.
|
|
115
|
+
*/
|
|
116
|
+
const STOPWORDS = new Set([
|
|
117
|
+
'a', 'an', 'the', 'of', 'for', 'to', 'with', 'and', 'or', 'in', 'on', 'at',
|
|
118
|
+
'by', 'that', 'this', 'my', 'your', 'our', 'their', 'is', 'are', 'be', 'it',
|
|
119
|
+
'its', 'as', 'from', 'page', 'screen', 'app', 'application', 'view', 'where',
|
|
120
|
+
'you', 'can', 'some', 'like', 'just', 'basically', 'kinda', 'want', 'wants',
|
|
121
|
+
'need', 'needs', 'something', 'thing', 'things', 'build', 'make', 'create',
|
|
122
|
+
'i', 'me', 'we', 'us', 'so', 'up', 'out', 'over', 'side', 'one', 'big',
|
|
123
|
+
]);
|
|
124
|
+
|
|
125
|
+
/**
|
|
126
|
+
* Split a query into meaningful content tokens (lowercased, stopwords + very
|
|
127
|
+
* short words removed). Empty for single-word queries (callers fall back to
|
|
128
|
+
* whole-phrase scoring).
|
|
129
|
+
* @param {string} term - Already-lowercased query.
|
|
130
|
+
* @returns {string[]}
|
|
131
|
+
*/
|
|
132
|
+
export function tokenizeQuery(term) {
|
|
133
|
+
return term
|
|
134
|
+
.split(/\s+/)
|
|
135
|
+
// Strip only leading/trailing punctuation; keep joined identifiers intact
|
|
136
|
+
// (e.g. "foo_bar" stays one token) so gibberish stays gibberish.
|
|
137
|
+
.map(t => t.replace(/^[^a-z0-9]+|[^a-z0-9]+$/g, ''))
|
|
138
|
+
.filter(t => t.length >= 2 && !STOPWORDS.has(t));
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
/**
|
|
142
|
+
* Score a candidate against a query, handling multi-word natural language.
|
|
143
|
+
* Tries the whole phrase (so exact/near matches still win) AND a per-token
|
|
144
|
+
* pass (so "data table with filters" matches `table-page` via table+filter),
|
|
145
|
+
* and returns whichever is stronger.
|
|
146
|
+
*
|
|
147
|
+
* @param {string} term - Lowercased full query.
|
|
148
|
+
* @param {string[]} tokens - Content tokens from tokenizeQuery(term).
|
|
149
|
+
* @param {object} candidate
|
|
150
|
+
* @returns {{score: number, reason: string} | null}
|
|
151
|
+
*/
|
|
152
|
+
/**
|
|
153
|
+
* Minimum per-token score (in the multi-word pass) to count as a real match.
|
|
154
|
+
* 50 = a genuine name/keyword/description hit; below that is loose Levenshtein
|
|
155
|
+
* fuzz that would otherwise turn gibberish queries into noise.
|
|
156
|
+
*/
|
|
157
|
+
const MIN_TOKEN_SCORE = 50;
|
|
158
|
+
|
|
159
|
+
/**
|
|
160
|
+
* Best score for a token against a candidate, fanning out through synonyms
|
|
161
|
+
* (synonym hits are discounted so a direct hit always wins).
|
|
162
|
+
* @returns {{score: number, reason: string} | null}
|
|
163
|
+
*/
|
|
164
|
+
function bestForToken(tok, candidate) {
|
|
165
|
+
let best = scoreCandidate(tok, candidate);
|
|
166
|
+
const syns = SYNONYM_INDEX.get(tok);
|
|
167
|
+
if (syns) {
|
|
168
|
+
for (const s of syns) {
|
|
169
|
+
const h = scoreCandidate(s, candidate);
|
|
170
|
+
if (h) {
|
|
171
|
+
const score = Math.round(h.score * 0.85);
|
|
172
|
+
if (!best || score > best.score) best = {score, reason: `${h.reason} (~${tok})`};
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
return best;
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
export function scoreQuery(term, tokens, candidate) {
|
|
180
|
+
const full = scoreCandidate(term, candidate);
|
|
181
|
+
|
|
182
|
+
// 0–1 content tokens: keep whole-phrase fuzzy matching (typo tolerance for
|
|
183
|
+
// single words), but if stopwords left exactly one DIFFERENT token (e.g.
|
|
184
|
+
// "pricing page" → "pricing"), score that token too and take the stronger.
|
|
185
|
+
if (tokens.length <= 1) {
|
|
186
|
+
const single = tokens.length === 1 ? bestForToken(tokens[0], candidate) : null;
|
|
187
|
+
if (full && (!single || full.score >= single.score)) return full;
|
|
188
|
+
return single;
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
// Multi-word natural language: score each content token, counting only
|
|
192
|
+
// strong hits, then reward coverage so candidates matching more terms win.
|
|
193
|
+
let sum = 0;
|
|
194
|
+
let matched = 0;
|
|
195
|
+
const hitTerms = [];
|
|
196
|
+
for (const tok of tokens) {
|
|
197
|
+
const h = bestForToken(tok, candidate);
|
|
198
|
+
if (h && h.score >= MIN_TOKEN_SCORE) {
|
|
199
|
+
sum += h.score;
|
|
200
|
+
matched++;
|
|
201
|
+
hitTerms.push(tok);
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
if (matched === 0) return full;
|
|
205
|
+
|
|
206
|
+
// Reward the AVERAGE strength of the concepts that matched (not divided by
|
|
207
|
+
// total query length — that penalizes verbose / low-fidelity prompts), plus
|
|
208
|
+
// a bonus per additional matched concept and a coverage term. A candidate
|
|
209
|
+
// that matches several of the query's concepts beats one matching a single
|
|
210
|
+
// incidental word.
|
|
211
|
+
const avgMatched = sum / matched;
|
|
212
|
+
const coverage = matched / tokens.length;
|
|
213
|
+
const tokenScore = Math.round(avgMatched + Math.min(matched - 1, 3) * 12 + coverage * 15);
|
|
214
|
+
|
|
215
|
+
if (full && full.score >= tokenScore) return full;
|
|
216
|
+
return {
|
|
217
|
+
score: tokenScore,
|
|
218
|
+
reason: `matches ${matched}/${tokens.length} terms: ${hitTerms.join(', ')}`,
|
|
219
|
+
};
|
|
220
|
+
}
|
|
221
|
+
|
|
52
222
|
/**
|
|
53
223
|
* Score a single candidate against the search term across name, keywords,
|
|
54
224
|
* and prose signals. Returns the best (highest) score plus a human reason,
|
|
@@ -107,10 +277,13 @@ export function scoreCandidate(term, {name, keywords = [], description = '', pro
|
|
|
107
277
|
else if (dist === 2) consider(30, `keyword "${kw}" (distance ${dist})`);
|
|
108
278
|
}
|
|
109
279
|
|
|
110
|
-
// ── Prose / description signals (whole
|
|
280
|
+
// ── Prose / description signals (stem-tolerant whole word) ──────
|
|
281
|
+
// Match the term's stem as a whole word, tolerating plural/gerund suffixes
|
|
282
|
+
// so "chart" matches "charts" and "filter" matches "filtering".
|
|
111
283
|
if (term.length >= 3) {
|
|
112
|
-
const
|
|
113
|
-
const
|
|
284
|
+
const root = stem(term);
|
|
285
|
+
const escaped = root.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
286
|
+
const re = new RegExp(`\\b${escaped}(s|es|ing|ed|ies)?\\b`);
|
|
114
287
|
if (description && re.test(description.toLowerCase())) {
|
|
115
288
|
consider(50, `description mentions "${term}"`);
|
|
116
289
|
} else {
|
|
@@ -240,14 +413,30 @@ async function gatherTemplates(cwd) {
|
|
|
240
413
|
} catch {
|
|
241
414
|
return [];
|
|
242
415
|
}
|
|
243
|
-
return templates.map(t =>
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
416
|
+
return templates.map(t => {
|
|
417
|
+
// Blocks ship componentsUsed; page templates don't, so derive them from the
|
|
418
|
+
// source. Category words (e.g. "Dashboard - Analytics") are strong intent
|
|
419
|
+
// signal for pages, which otherwise only index on name + description.
|
|
420
|
+
let keywords = Array.isArray(t.componentsUsed) ? [...t.componentsUsed] : [];
|
|
421
|
+
if (t.type === 'page') {
|
|
422
|
+
if (t.filePath) {
|
|
423
|
+
try {
|
|
424
|
+
keywords = keywords.concat(extractComponents(t.filePath));
|
|
425
|
+
} catch {
|
|
426
|
+
// Best-effort: skip keyword enrichment if the source can't be read.
|
|
427
|
+
}
|
|
428
|
+
}
|
|
429
|
+
if (t.category) keywords = keywords.concat(t.category.split(/[^A-Za-z0-9]+/).filter(Boolean));
|
|
430
|
+
}
|
|
431
|
+
return {
|
|
432
|
+
domain: 'template',
|
|
433
|
+
name: t.dirName,
|
|
434
|
+
keywords,
|
|
435
|
+
description: t.description || '',
|
|
436
|
+
_displayName: t.name,
|
|
437
|
+
_kind: t.type, // 'page' | 'block'
|
|
438
|
+
};
|
|
439
|
+
});
|
|
251
440
|
}
|
|
252
441
|
|
|
253
442
|
/**
|
|
@@ -325,6 +514,7 @@ export async function search(query, options = {}) {
|
|
|
325
514
|
}
|
|
326
515
|
|
|
327
516
|
const term = String(query).trim().toLowerCase();
|
|
517
|
+
const tokens = tokenizeQuery(term);
|
|
328
518
|
|
|
329
519
|
const coreDir = findCoreDir(cwd);
|
|
330
520
|
if (!coreDir) {
|
|
@@ -342,9 +532,13 @@ export async function search(query, options = {}) {
|
|
|
342
532
|
|
|
343
533
|
const all = [...components, ...hooks, ...docTopics, ...templates];
|
|
344
534
|
|
|
535
|
+
// Score every candidate on its own merits. The consumer groups results by
|
|
536
|
+
// role (page / block / component) and takes the top of each, so there's no
|
|
537
|
+
// cross-role competition to engineer — a target page only needs to be the
|
|
538
|
+
// strongest PAGE, not outrank every component.
|
|
345
539
|
const scored = [];
|
|
346
540
|
for (const candidate of all) {
|
|
347
|
-
const hit =
|
|
541
|
+
const hit = scoreQuery(term, tokens, candidate);
|
|
348
542
|
if (hit) scored.push(toResult(candidate, hit.score, hit.reason));
|
|
349
543
|
}
|
|
350
544
|
|
package/src/api/template.mjs
CHANGED
|
@@ -95,6 +95,7 @@ async function discoverPages() {
|
|
|
95
95
|
dirName: dir.name,
|
|
96
96
|
name: doc?.name || dir.name,
|
|
97
97
|
description: doc?.description || '',
|
|
98
|
+
category: doc?.category || '',
|
|
98
99
|
isReady: doc?.isReady ?? true,
|
|
99
100
|
scaffold: doc?.scaffold ?? false,
|
|
100
101
|
filePath: path.join(dirPath, 'page.tsx'),
|
|
@@ -245,7 +246,7 @@ const UBIQUITOUS = new Set([
|
|
|
245
246
|
'StackItem', 'Icon',
|
|
246
247
|
]);
|
|
247
248
|
|
|
248
|
-
function extractComponents(pagePath) {
|
|
249
|
+
export function extractComponents(pagePath) {
|
|
249
250
|
const src = fs.readFileSync(pagePath, 'utf-8');
|
|
250
251
|
// Match JSX opening tags, e.g. `<Section` or the legacy `<XDSSection`.
|
|
251
252
|
// Templates author bare component names post un-prefix migration
|