@astryxdesign/cli 0.1.2-canary.ae2d146 → 0.1.2-canary.b6820d0
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 +1 -0
- package/README.md +55 -49
- package/package.json +7 -19
- package/src/api/component.mjs +12 -253
- package/src/api/discover.mjs +7 -14
- package/src/api/doctor.mjs +25 -10
- package/src/api/layout.mjs +10 -11
- package/src/api/layout.test.mjs +1 -4
- package/src/api/template.mjs +84 -242
- package/src/codemods/__tests__/registry.test.mjs +0 -1
- package/src/codemods/registry.mjs +0 -1
- package/src/codemods/runner.mjs +96 -73
- package/src/codemods/transforms/{v0.1.0 → v0.0.15}/drop-xds-prefix-imports.mjs +4 -15
- package/src/codemods/transforms/v0.0.15/index.mjs +13 -0
- package/src/codemods/transforms/v0.1.0/__tests__/migrate-xds-config-surfaces.test.mjs +116 -0
- package/src/codemods/transforms/v0.1.0/index.mjs +6 -43
- package/src/codemods/transforms/v0.1.0/migrate-xds-config-surfaces.mjs +230 -0
- package/src/commands/agent-docs.mjs +1 -1
- package/src/commands/component/index.mjs +10 -44
- package/src/commands/discover.mjs +16 -5
- package/src/commands/doctor.test.mjs +3 -3
- package/src/commands/gap-report.mjs +464 -0
- package/src/commands/gap-report.test.mjs +168 -0
- package/src/commands/json-contract.test.mjs +32 -0
- package/src/commands/swizzle-gap-safety.test.mjs +273 -0
- package/src/commands/swizzle.mjs +235 -224
- package/src/commands/swizzle.path-safety.test.mjs +3 -3
- package/src/commands/template.mjs +37 -33
- package/src/commands/upgrade.mjs +170 -349
- package/src/config.mjs +20 -7
- package/src/config.test.mjs +13 -89
- package/src/index.mjs +3 -7
- package/src/lib/component-discovery.mjs +0 -177
- package/src/lib/config-schema.mjs +55 -41
- package/src/lib/config.mjs +113 -0
- package/src/lib/config.test.mjs +91 -0
- package/src/lib/error-codes.mjs +12 -7
- package/src/lib/integrations.mjs +115 -92
- package/src/lib/integrations.test.mjs +107 -88
- package/src/lib/manifest.mjs +2 -5
- package/src/types/api.d.ts +6 -2
- package/src/types/base.d.ts +9 -3
- package/src/types/component.d.ts +2 -27
- package/src/types/config.d.ts +85 -56
- package/src/types/error-codes.d.ts +3 -5
- package/src/types/gap-report.d.ts +29 -0
- package/src/types/index.d.ts +1 -0
- package/src/types/swizzle.d.ts +2 -9
- package/src/types/template.d.ts +7 -10
- package/src/types/upgrade.d.ts +0 -29
- package/src/utils/github.mjs +237 -0
- package/src/utils/interactive.mjs +2 -2
- package/templates/blocks/components/HoverCard/HoverCardInteractiveContent.doc.mjs +1 -1
- package/templates/blocks/components/HoverCard/HoverCardInteractiveContent.tsx +4 -9
- package/src/api/template-integration.test.mjs +0 -225
- package/src/api/validate-integration.mjs +0 -370
- package/src/api/validate-integration.test.mjs +0 -222
- package/src/codemod.mjs +0 -93
- package/src/codemod.test.mjs +0 -134
- package/src/codemods/__tests__/runner.test.mjs +0 -103
- package/src/codemods/integration-discovery.mjs +0 -168
- package/src/codemods/integration-discovery.test.mjs +0 -234
- package/src/codemods/integration-runner.mjs +0 -109
- package/src/codemods/run-codemod.mjs +0 -207
- package/src/codemods/transforms/v0.1.0/__tests__/migrate-xds-css-surfaces.test.mjs +0 -67
- package/src/codemods/transforms/v0.1.0/__tests__/migrate-xds-declare-module.test.mjs +0 -61
- package/src/codemods/transforms/v0.1.0/__tests__/v0.1.0-ordering.test.mjs +0 -104
- package/src/codemods/transforms/v0.1.0/migrate-xds-css-surfaces.mjs +0 -77
- package/src/codemods/transforms/v0.1.0/migrate-xds-declare-module.mjs +0 -78
- package/src/codemods/transforms/v0.1.3/__tests__/migrate-layout-components-to-experimental.test.mjs +0 -360
- package/src/codemods/transforms/v0.1.3/index.mjs +0 -19
- package/src/codemods/transforms/v0.1.3/migrate-layout-components-to-experimental.mjs +0 -260
- package/src/commands/component-ownership.test.mjs +0 -227
- package/src/commands/swizzle.routing.test.mjs +0 -279
- package/src/commands/upgrade.config-ordering.test.mjs +0 -273
- package/src/commands/upgrade.integration-policy.test.mjs +0 -204
- package/src/commands/validate-integration.mjs +0 -110
- package/src/commands/validate-integration.test.mjs +0 -124
- package/src/integration.mjs +0 -19
- package/src/lib/config-cache.mjs +0 -91
- package/src/lib/config-cache.test.mjs +0 -83
- package/src/lib/integration-warnings.mjs +0 -62
- package/src/lib/integration-warnings.test.mjs +0 -102
- package/src/lib/module-loader.mjs +0 -80
- package/src/lib/module-loader.test.mjs +0 -106
- package/src/lib/project.mjs +0 -502
- package/src/lib/project.test.mjs +0 -308
- package/src/template.mjs +0 -73
- package/src/template.test.mjs +0 -127
- package/src/types/codemod.d.ts +0 -81
- package/src/types/integration.d.ts +0 -29
- package/src/types/template-api.d.ts +0 -54
- package/src/types/validate-integration.d.ts +0 -24
- package/templates/blocks/components/DateRangeInput/DateRangeInputWithPresets.doc.mjs +0 -14
- package/templates/blocks/components/DateRangeInput/DateRangeInputWithPresets.tsx +0 -46
- package/templates/blocks/components/DateRangeInput/DateRangeInputWithValidation.doc.mjs +0 -14
- package/templates/blocks/components/DateRangeInput/DateRangeInputWithValidation.tsx +0 -52
- package/templates/blocks/components/DateTimeInput/DateTimeInputWithValidation.doc.mjs +0 -14
- package/templates/blocks/components/DateTimeInput/DateTimeInputWithValidation.tsx +0 -43
- package/templates/blocks/components/Tab/TabWithSelectedIcon.doc.mjs +0 -13
- package/templates/blocks/components/Tab/TabWithSelectedIcon.tsx +0 -39
- /package/src/codemods/transforms/{v0.1.0 → v0.0.15}/__tests__/drop-xds-prefix-imports.test.mjs +0 -0
package/CHANGELOG.md
CHANGED
package/README.md
CHANGED
|
@@ -62,7 +62,8 @@ Options:
|
|
|
62
62
|
| `upgrade` | Run codemods to migrate between versions |
|
|
63
63
|
| `theme build` | Compile a defineTheme file to production CSS and JS |
|
|
64
64
|
| `discover` | Discover external packages and components |
|
|
65
|
-
| `
|
|
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) |
|
|
66
67
|
|
|
67
68
|
### Global options
|
|
68
69
|
|
|
@@ -160,6 +161,7 @@ if (isError(result)) {
|
|
|
160
161
|
| `ERR_INVALID_VERSION` | A `--from`/`--to` value was not a valid semver string. |
|
|
161
162
|
| `ERR_DEP_MISSING` | A required external dependency (e.g. jscodeshift) is missing. |
|
|
162
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). |
|
|
163
165
|
|
|
164
166
|
## Capability manifest (agent discovery)
|
|
165
167
|
|
|
@@ -170,7 +172,7 @@ discriminators each command can emit. Think of it as an OpenAPI spec for the CLI
|
|
|
170
172
|
|
|
171
173
|
```bash
|
|
172
174
|
astryx manifest --json # dedicated surface — type: "manifest"
|
|
173
|
-
|
|
175
|
+
xds --json # bare invocation — embeds the same payload under data.manifest
|
|
174
176
|
```
|
|
175
177
|
|
|
176
178
|
Shape:
|
|
@@ -180,7 +182,7 @@ Shape:
|
|
|
180
182
|
"apiVersion": 1,
|
|
181
183
|
"type": "manifest",
|
|
182
184
|
"data": {
|
|
183
|
-
"name": "
|
|
185
|
+
"name": "xds",
|
|
184
186
|
"version": "0.0.14",
|
|
185
187
|
"description": "Design system CLI — components, themes, and tooling",
|
|
186
188
|
"globalOptions": [
|
|
@@ -247,14 +249,14 @@ the `JSON_SUPPORTED` allowlist and a small declarative `RESPONSE_TYPES` map in
|
|
|
247
249
|
`src/lib/manifest.mjs`, guarded by a drift test (`manifest.test.mjs`) so adding a
|
|
248
250
|
command without describing it fails CI.
|
|
249
251
|
|
|
250
|
-
**Backwards-compat:** the bare `
|
|
252
|
+
**Backwards-compat:** the bare `xds --json` envelope keeps `type: "help"` and its
|
|
251
253
|
original shallow fields (`name`, `version`, `commands` as a `string[]` of names,
|
|
252
254
|
`jsonSupported`); the full structured manifest is additive under `data.manifest`.
|
|
253
255
|
For the standalone manifest envelope (`type: "manifest"`), use `astryx manifest --json`.
|
|
254
256
|
|
|
255
257
|
## Programmatic API
|
|
256
258
|
|
|
257
|
-
The same logic that powers `
|
|
259
|
+
The same logic that powers `xds --json` is available as importable, type-safe functions:
|
|
258
260
|
|
|
259
261
|
```typescript
|
|
260
262
|
import {
|
|
@@ -267,20 +269,20 @@ import {
|
|
|
267
269
|
AstryxError,
|
|
268
270
|
} from '@astryxdesign/cli/api';
|
|
269
271
|
|
|
270
|
-
// Same result as:
|
|
272
|
+
// Same result as: xds --json component Button
|
|
271
273
|
const btn = await component('Button');
|
|
272
274
|
btn.type; // 'component.detail'
|
|
273
275
|
btn.data.name; // 'Button' (typed as ComponentDoc)
|
|
274
276
|
|
|
275
|
-
// Same result as:
|
|
277
|
+
// Same result as: xds --json component --list
|
|
276
278
|
const list = await component(undefined, {list: true});
|
|
277
279
|
list.data; // Record<string, string[]>
|
|
278
280
|
|
|
279
|
-
// Same result as:
|
|
281
|
+
// Same result as: xds --json docs principles
|
|
280
282
|
const principles = await docs('principles');
|
|
281
|
-
principles.data.title; // 'Principles'
|
|
283
|
+
principles.data.title; // 'XDS Principles'
|
|
282
284
|
|
|
283
|
-
// Same result as:
|
|
285
|
+
// Same result as: xds --json hook useMediaQuery
|
|
284
286
|
const useMediaQuery = await hook('useMediaQuery');
|
|
285
287
|
useMediaQuery.data.params; // typed as HookParamDoc[]
|
|
286
288
|
|
|
@@ -294,7 +296,7 @@ try {
|
|
|
294
296
|
}
|
|
295
297
|
```
|
|
296
298
|
|
|
297
|
-
The CLI command handlers are thin wrappers around these functions: they parse args, call the API, then format the output (JSON or text). This guarantees that `@astryxdesign/cli/api` and `
|
|
299
|
+
The CLI command handlers are thin wrappers around these functions: they parse args, call the API, then format the output (JSON or text). This guarantees that `@astryxdesign/cli/api` and `xds --json` always return identical data.
|
|
298
300
|
|
|
299
301
|
### Consumer utilities
|
|
300
302
|
|
|
@@ -324,41 +326,43 @@ detail.data.name; // already narrowed
|
|
|
324
326
|
|
|
325
327
|
Every response has a `type` string that uniquely identifies it:
|
|
326
328
|
|
|
327
|
-
| Command
|
|
328
|
-
|
|
|
329
|
-
| `
|
|
330
|
-
| `
|
|
331
|
-
| `
|
|
332
|
-
| `
|
|
333
|
-
| `
|
|
334
|
-
| `
|
|
335
|
-
| `
|
|
336
|
-
| `
|
|
337
|
-
| `
|
|
338
|
-
| `
|
|
339
|
-
| `
|
|
340
|
-
| `
|
|
341
|
-
| `
|
|
342
|
-
| `
|
|
343
|
-
| `
|
|
344
|
-
| `
|
|
345
|
-
| `
|
|
346
|
-
| `
|
|
347
|
-
| `
|
|
348
|
-
| `
|
|
349
|
-
| `
|
|
350
|
-
| `
|
|
351
|
-
| `
|
|
352
|
-
| `
|
|
353
|
-
| `
|
|
354
|
-
| `
|
|
355
|
-
| `
|
|
356
|
-
| `
|
|
357
|
-
| `
|
|
358
|
-
| `
|
|
359
|
-
| `
|
|
360
|
-
|
|
|
361
|
-
|
|
|
329
|
+
| Command | Type | Response |
|
|
330
|
+
| ---------------------------------------------- | --------------------------- | --------------------------------- |
|
|
331
|
+
| `xds --json component [--list]` | `component.list` | `ComponentListResponse` |
|
|
332
|
+
| `xds --json component --list --detail compact` | `component.brief` | `ComponentBriefResponse` |
|
|
333
|
+
| `xds --json component --list --detail full` | `component.full` | `ComponentFullResponse` |
|
|
334
|
+
| `xds --json component <name>` | `component.detail` | `ComponentDetailResponse` |
|
|
335
|
+
| `xds --json component <name> --props` | `component.detail.props` | `ComponentDetailPropsResponse` |
|
|
336
|
+
| `xds --json component <name> --source` | `component.detail.source` | `ComponentDetailSourceResponse` |
|
|
337
|
+
| `xds --json component <name> --showcase` | `component.detail.showcase` | `ComponentDetailShowcaseResponse` |
|
|
338
|
+
| `xds --json component <name> --blocks` | `component.detail.blocks` | `ComponentDetailBlocksResponse` |
|
|
339
|
+
| `xds --json discover` | `discover.list` | `DiscoverListResponse` |
|
|
340
|
+
| `xds --json discover @scope/name` | `discover.detail` | `DiscoverDetailResponse` |
|
|
341
|
+
| `xds --json discover @scope/name/Comp` | `discover.detail.doc` | `DiscoverDetailDocResponse` |
|
|
342
|
+
| `xds --json discover <search>` | `discover.search` | `DiscoverSearchResponse` |
|
|
343
|
+
| `xds --json docs` | `docs.list` | `DocsListResponse` |
|
|
344
|
+
| `xds --json docs <topic>` | `docs.detail` | `DocsDetailResponse` |
|
|
345
|
+
| `xds --json docs <topic> <section>` | `docs.detail.section` | `DocsDetailSectionResponse` |
|
|
346
|
+
| `xds --json template [--list]` | `template.list` | `TemplateListResponse` |
|
|
347
|
+
| `xds --json template <name>` | `template.show` | `TemplateShowResponse` |
|
|
348
|
+
| `xds --json template <name> --skeleton` | `template.skeleton` | `TemplateSkeletonResponse` |
|
|
349
|
+
| `xds --json template <name> [path]` | `template.copy` | `TemplateCopyResponse` |
|
|
350
|
+
| `xds --json hook [--list]` | `hook.list` | `HookListResponse` |
|
|
351
|
+
| `xds --json hook --list --detail compact` | `hook.brief` | `HookBriefResponse` |
|
|
352
|
+
| `xds --json hook --list --detail full` | `hook.full` | `HookFullResponse` |
|
|
353
|
+
| `xds --json hook <name>` | `hook.detail` | `HookDetailResponse` |
|
|
354
|
+
| `xds --json hook <name> --params` | `hook.detail.params` | `HookDetailParamsResponse` |
|
|
355
|
+
| `xds --json search <query>` | `search` | `SearchResponse` |
|
|
356
|
+
| `xds --json swizzle [--list]` | `swizzle.list` | `SwizzleListResponse` |
|
|
357
|
+
| `xds --json swizzle <component>` | `swizzle.copy` | `SwizzleCopyResponse` |
|
|
358
|
+
| `xds --json theme build <file>` | `theme.build` | `ThemeBuildResponse` |
|
|
359
|
+
| `xds --json upgrade --list` | `upgrade.list` | `UpgradeListResponse` |
|
|
360
|
+
| `xds --json upgrade [--apply]` | `upgrade.run` | `UpgradeRunResponse` |
|
|
361
|
+
| `xds --json gap-report --list-categories` | `gap-report.categories` | `GapReportCategoriesResponse` |
|
|
362
|
+
| `xds --json gap-report --component X ...` | `gap-report.file` | `GapReportFileResponse` |
|
|
363
|
+
| `xds --json doctor` | `doctor` | `DoctorResponse` |
|
|
364
|
+
| any error | — | `CLIError` |
|
|
365
|
+
| unsupported command | — | `CLIUnsupportedError` |
|
|
362
366
|
|
|
363
367
|
## Doctor
|
|
364
368
|
|
|
@@ -379,7 +383,7 @@ astryx doctor — diagnosing your setup
|
|
|
379
383
|
@astryxdesign/core v0.0.14 is in step with @astryxdesign/cli v0.0.14.
|
|
380
384
|
⚠ Theme packages
|
|
381
385
|
No @astryxdesign/theme-* packages are installed.
|
|
382
|
-
→ fix: Install a theme, e.g. `npm install @astryxdesign/theme-neutral`, then import its CSS or set
|
|
386
|
+
→ fix: Install a theme, e.g. `npm install @astryxdesign/theme-neutral`, then import its CSS or set xds.theme.
|
|
383
387
|
ℹ astryx.config.mjs
|
|
384
388
|
No astryx.config.mjs found — using defaults.
|
|
385
389
|
ℹ AI agent docs
|
|
@@ -404,7 +408,7 @@ No failures — but review the ⚠ warnings above when you can.
|
|
|
404
408
|
| Version alignment | pass / warn / info | Installed `@astryxdesign/core` is in step with `@astryxdesign/cli` |
|
|
405
409
|
| Theme packages | pass / warn | An `@astryxdesign/theme-*` package is installed and a theme is wired |
|
|
406
410
|
| astryx.config.mjs | pass / fail / info | Config (if present) loads cleanly with a valid shape |
|
|
407
|
-
| AI agent docs | pass / warn / info | Agent docs exist and contain the
|
|
411
|
+
| AI agent docs | pass / warn / info | Agent docs exist and contain the XDS section markers |
|
|
408
412
|
| Peer dependencies | pass / warn / info | `@astryxdesign/core`'s peer deps (react, …) are installed |
|
|
409
413
|
| Package manager | info | Reports the detected package manager |
|
|
410
414
|
|
|
@@ -430,6 +434,8 @@ export default {
|
|
|
430
434
|
templates: {
|
|
431
435
|
get: async id => fetchTemplateFromAPI(id),
|
|
432
436
|
},
|
|
433
|
-
|
|
437
|
+
gapReport: {
|
|
438
|
+
url: 'https://your-api.com/gaps',
|
|
439
|
+
},
|
|
434
440
|
};
|
|
435
441
|
```
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@astryxdesign/cli",
|
|
3
|
-
"version": "0.1.2-canary.
|
|
3
|
+
"version": "0.1.2-canary.b6820d0",
|
|
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",
|
|
@@ -43,18 +43,6 @@
|
|
|
43
43
|
"types": "./src/types/config.d.ts",
|
|
44
44
|
"import": "./src/config.mjs"
|
|
45
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
46
|
"./xle": {
|
|
59
47
|
"types": "./src/lib/xle/browser.d.ts",
|
|
60
48
|
"import": "./src/lib/xle/browser.mjs"
|
|
@@ -75,9 +63,9 @@
|
|
|
75
63
|
"zod": "^4.4.3"
|
|
76
64
|
},
|
|
77
65
|
"peerDependencies": {
|
|
78
|
-
"@astryxdesign/core": "0.1.2-canary.
|
|
79
|
-
"@astryxdesign/lab": "0.1.2-canary.
|
|
80
|
-
"@astryxdesign/theme-neutral": "0.1.2-canary.
|
|
66
|
+
"@astryxdesign/core": "0.1.2-canary.b6820d0",
|
|
67
|
+
"@astryxdesign/lab": "0.1.2-canary.b6820d0",
|
|
68
|
+
"@astryxdesign/theme-neutral": "0.1.2-canary.b6820d0",
|
|
81
69
|
"gpt-tokenizer": "^2.0.0"
|
|
82
70
|
},
|
|
83
71
|
"peerDependenciesMeta": {
|
|
@@ -92,9 +80,9 @@
|
|
|
92
80
|
}
|
|
93
81
|
},
|
|
94
82
|
"devDependencies": {
|
|
95
|
-
"@astryxdesign/core": "0.1.2-canary.
|
|
96
|
-
"@astryxdesign/lab": "0.1.2-canary.
|
|
97
|
-
"@astryxdesign/theme-neutral": "0.1.2-canary.
|
|
83
|
+
"@astryxdesign/core": "0.1.2-canary.b6820d0",
|
|
84
|
+
"@astryxdesign/lab": "0.1.2-canary.b6820d0",
|
|
85
|
+
"@astryxdesign/theme-neutral": "0.1.2-canary.b6820d0",
|
|
98
86
|
"gpt-tokenizer": "^2.0.0"
|
|
99
87
|
},
|
|
100
88
|
"scripts": {
|
package/src/api/component.mjs
CHANGED
|
@@ -11,48 +11,18 @@ 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,
|
|
15
14
|
discoverComponents,
|
|
16
15
|
discoverExternalComponentsGrouped,
|
|
17
|
-
discoverIntegrationComponents,
|
|
18
16
|
findComponentReadme,
|
|
19
17
|
findComponentSource,
|
|
20
18
|
findExternalComponentDoc,
|
|
21
|
-
findIntegrationComponentDoc,
|
|
22
|
-
findIntegrationComponentSource,
|
|
23
19
|
resolveImportPath,
|
|
24
20
|
} from '../lib/component-discovery.mjs';
|
|
25
|
-
import {Project} from '../lib/project.mjs';
|
|
26
21
|
import {loadDocs} from '../lib/component-loader.mjs';
|
|
27
22
|
import {searchComponents} from '../lib/string-utils.mjs';
|
|
28
23
|
import {AstryxError} from './error.mjs';
|
|
29
24
|
import {findShowcase, findRelatedBlocks} from './template.mjs';
|
|
30
25
|
|
|
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
|
-
|
|
56
26
|
/**
|
|
57
27
|
* Resolve an external package by name from the discovered externals list.
|
|
58
28
|
* @param {string} packageName - e.g. '@acme/xds-widgets'
|
|
@@ -161,13 +131,8 @@ export async function component(name, options = {}) {
|
|
|
161
131
|
return {type: 'component.full', data: {[match[0]]: entries}};
|
|
162
132
|
}
|
|
163
133
|
|
|
164
|
-
// Default: brief —
|
|
165
|
-
|
|
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
|
-
};
|
|
134
|
+
// Default: brief — names only
|
|
135
|
+
return {type: 'component.list', data: {[match[0]]: match[1]}};
|
|
171
136
|
}
|
|
172
137
|
|
|
173
138
|
// All components — merge core + external packages with grouped subcategories
|
|
@@ -214,189 +179,43 @@ export async function component(name, options = {}) {
|
|
|
214
179
|
return {type: 'component.full', data: result};
|
|
215
180
|
}
|
|
216
181
|
|
|
217
|
-
// Default: brief —
|
|
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.
|
|
182
|
+
// Default: brief — names only (with externals merged in)
|
|
251
183
|
const externals = discoverExternalPackages(cwd);
|
|
252
184
|
for (const ext of externals) {
|
|
253
|
-
if (seenIntegration.has(ext.name)) continue;
|
|
254
185
|
const grouped = discoverExternalComponentsGrouped(ext.docsDir);
|
|
255
186
|
const groupKeys = Object.keys(grouped);
|
|
256
187
|
if (groupKeys.length === 0) continue;
|
|
257
188
|
|
|
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.
|
|
258
191
|
const hasGroups = groupKeys.some(
|
|
259
192
|
k => grouped[k].length > 1 || grouped[k][0] !== k,
|
|
260
193
|
);
|
|
261
194
|
|
|
262
195
|
if (hasGroups) {
|
|
263
196
|
for (const [group, members] of Object.entries(grouped)) {
|
|
264
|
-
|
|
265
|
-
name: n,
|
|
266
|
-
package: ext.name,
|
|
267
|
-
}));
|
|
197
|
+
components[`${group} (${ext.name})`] = members;
|
|
268
198
|
}
|
|
269
199
|
} else {
|
|
200
|
+
// All ungrouped — single flat list under the package category
|
|
270
201
|
const allComps = Object.values(grouped).flat().sort();
|
|
271
202
|
if (allComps.length > 0) {
|
|
272
|
-
|
|
273
|
-
name: n,
|
|
274
|
-
package: ext.name,
|
|
275
|
-
}));
|
|
203
|
+
components[`${ext.category} (${ext.name})`] = allComps;
|
|
276
204
|
}
|
|
277
205
|
}
|
|
278
206
|
}
|
|
279
|
-
return {type: 'component.list', data:
|
|
207
|
+
return {type: 'component.list', data: components};
|
|
280
208
|
}
|
|
281
209
|
|
|
282
210
|
// ── Single component ───────────────────────────────────────────
|
|
283
211
|
|
|
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
|
-
|
|
292
212
|
const dirName = name.replace(/^XDS/, '');
|
|
293
213
|
|
|
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
|
-
|
|
353
214
|
// When scoped to a specific package, search that package first.
|
|
354
215
|
// This is critical for components that exist in both core and an external
|
|
355
216
|
// package (e.g. AppShell, Button, SideNav) — the package scope ensures
|
|
356
217
|
// the external package's docs are returned, not core's.
|
|
357
218
|
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.
|
|
400
219
|
const ext = resolveExternalPackage(packageScope, cwd);
|
|
401
220
|
if (!ext) {
|
|
402
221
|
throw new AstryxError(`External package "${packageScope}" not found`, undefined, ERROR_CODES.ERR_UNKNOWN_PACKAGE);
|
|
@@ -425,49 +244,11 @@ export async function component(name, options = {}) {
|
|
|
425
244
|
const p = docs.props || (docs.components ? docs.components.flatMap(c => c.props || []) : []);
|
|
426
245
|
return {type: 'component.detail.props', data: p};
|
|
427
246
|
}
|
|
428
|
-
return {
|
|
429
|
-
type: 'component.detail',
|
|
430
|
-
data: withOwnership(docs, {package: ext.name, sourcePath: null}, dirName),
|
|
431
|
-
};
|
|
247
|
+
return {type: 'component.detail', data: docs};
|
|
432
248
|
}
|
|
433
249
|
throw new AstryxError(`No component "${name}" in package "${packageScope}"`, undefined, ERROR_CODES.ERR_UNKNOWN_COMPONENT);
|
|
434
250
|
}
|
|
435
251
|
|
|
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
|
-
|
|
471
252
|
if (source) {
|
|
472
253
|
const sourcePath = findComponentSource(coreDir, dirName);
|
|
473
254
|
if (!sourcePath) {
|
|
@@ -493,10 +274,6 @@ export async function component(name, options = {}) {
|
|
|
493
274
|
|
|
494
275
|
let readmePath = findComponentReadme(coreDir, dirName);
|
|
495
276
|
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;
|
|
500
277
|
|
|
501
278
|
if (!readmePath) {
|
|
502
279
|
const externals = discoverExternalPackages(cwd);
|
|
@@ -504,8 +281,6 @@ export async function component(name, options = {}) {
|
|
|
504
281
|
const extDocPath = findExternalComponentDoc(ext.docsDir, dirName);
|
|
505
282
|
if (extDocPath) {
|
|
506
283
|
readmePath = extDocPath;
|
|
507
|
-
resolvedOwnerPackage = ext.name;
|
|
508
|
-
resolvedSourcePath = null;
|
|
509
284
|
break;
|
|
510
285
|
}
|
|
511
286
|
}
|
|
@@ -524,8 +299,6 @@ export async function component(name, options = {}) {
|
|
|
524
299
|
if (topScore >= 90 && topTied.length === 1 && gap >= 20) {
|
|
525
300
|
resolvedName = topTied[0].name;
|
|
526
301
|
readmePath = findComponentReadme(coreDir, resolvedName);
|
|
527
|
-
resolvedOwnerPackage = CORE_PACKAGE;
|
|
528
|
-
resolvedSourcePath = findComponentSource(coreDir, resolvedName);
|
|
529
302
|
} else {
|
|
530
303
|
const threshold = Math.max(topScore - 20, 1);
|
|
531
304
|
const candidates = results.filter(r => r.score >= threshold).slice(0, 5);
|
|
@@ -609,14 +382,7 @@ export async function component(name, options = {}) {
|
|
|
609
382
|
if (props) {
|
|
610
383
|
return {type: 'component.detail.props', data: matchingComponent.props || []};
|
|
611
384
|
}
|
|
612
|
-
return {
|
|
613
|
-
type: 'component.detail',
|
|
614
|
-
data: withOwnership(
|
|
615
|
-
scoped,
|
|
616
|
-
{package: resolvedOwnerPackage, sourcePath: resolvedSourcePath},
|
|
617
|
-
dirName,
|
|
618
|
-
),
|
|
619
|
-
};
|
|
385
|
+
return {type: 'component.detail', data: scoped};
|
|
620
386
|
}
|
|
621
387
|
|
|
622
388
|
if (props) {
|
|
@@ -624,12 +390,5 @@ export async function component(name, options = {}) {
|
|
|
624
390
|
return {type: 'component.detail.props', data: p};
|
|
625
391
|
}
|
|
626
392
|
|
|
627
|
-
return {
|
|
628
|
-
type: 'component.detail',
|
|
629
|
-
data: withOwnership(
|
|
630
|
-
docs,
|
|
631
|
-
{package: resolvedOwnerPackage, sourcePath: resolvedSourcePath},
|
|
632
|
-
resolvedName,
|
|
633
|
-
),
|
|
634
|
-
};
|
|
393
|
+
return {type: 'component.detail', data: docs};
|
|
635
394
|
}
|