@astryxdesign/cli 0.6.3-canary.e78c3ce → 0.6.3-canary.e86aa19

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (176) hide show
  1. package/README.md +19 -17
  2. package/api/component/_adapter.d.mts +6 -12
  3. package/api/component/_adapter.mjs +20 -10
  4. package/api/component/component.mjs +60 -12
  5. package/api/component/list/list.mjs +3 -2
  6. package/api/docs/_adapter.d.mts +209 -28
  7. package/api/docs/_adapter.mjs +667 -35
  8. package/api/docs/detail/detail.mjs +11 -3
  9. package/api/docs/detail/section/section.mjs +20 -3
  10. package/api/docs/docs.d.mts +5 -3
  11. package/api/docs/docs.doc.mjs +35 -15
  12. package/api/docs/docs.mjs +44 -8
  13. package/api/docs/docs.test.mjs +158 -4
  14. package/api/docs/docs.type.d.mts +165 -2
  15. package/api/docs/docs.type.mjs +101 -3
  16. package/api/docs/index/index.mjs +11 -3
  17. package/api/docs/index/index.test.mjs +1 -1
  18. package/api/docs/integration-tree.test.mjs +547 -0
  19. package/api/docs/integrationDocs.test.mjs +14 -14
  20. package/api/docs/list/list.mjs +25 -5
  21. package/api/docs/node/node.d.mts +43 -0
  22. package/api/docs/node/node.mjs +192 -0
  23. package/api/doctor/doctor.d.mts +30 -2
  24. package/api/doctor/doctor.mjs +146 -7
  25. package/api/doctor/doctor.test.mjs +89 -1
  26. package/api/index.d.mts +1 -1
  27. package/api/index.mjs +1 -0
  28. package/api/init/init.doc.mjs +1 -1
  29. package/api/integration/add-contribution.d.mts +2 -1
  30. package/api/integration/add-contribution.mjs +107 -5
  31. package/api/integration/add-contribution.test.mjs +137 -0
  32. package/api/integration/authoring-checks.mjs +108 -31
  33. package/api/integration/authoring-checks.test.mjs +163 -2
  34. package/api/integration/authoring-checks.type.d.mts +3 -1
  35. package/api/integration/authoring-checks.type.mjs +3 -1
  36. package/api/integration/integration-authoring.type.d.mts +2 -0
  37. package/api/integration/integration-authoring.type.mjs +2 -0
  38. package/api/integration/integrationAdd.doc.mjs +6 -0
  39. package/api/integration/integrationAddDoc.doc.mjs +7 -1
  40. package/api/integration/integrationDocConflicts.doc.mjs +1 -1
  41. package/api/integration/integrationPackCheck.doc.mjs +1 -1
  42. package/api/integration/integrationTemplateConflicts.doc.mjs +6 -5
  43. package/api/integration/pack-check.mjs +34 -0
  44. package/api/integration/pack-check.test.mjs +121 -0
  45. package/api/integration/pack-check.type.d.mts +26 -2
  46. package/api/integration/pack-check.type.mjs +14 -1
  47. package/api/integration/validate-integration.test.mjs +3 -3
  48. package/api/integration/validateIntegration.doc.mjs +1 -1
  49. package/api/json/index.ts +2 -0
  50. package/api/layout/_adapter.mjs +20 -5
  51. package/api/search/search.d.mts +26 -1
  52. package/api/search/search.doc.mjs +4 -3
  53. package/api/search/search.mjs +308 -29
  54. package/api/search/search.test.mjs +91 -2
  55. package/api/search/search.type.d.mts +13 -1
  56. package/api/search/search.type.mjs +4 -1
  57. package/api/template/list/list.mjs +1 -0
  58. package/api/template/template-integration.test.mjs +1022 -3
  59. package/api/template/template.doc.mjs +27 -7
  60. package/api/template/template.mjs +45 -8
  61. package/api/template/template.type.d.mts +6 -8
  62. package/api/template/template.type.mjs +3 -2
  63. package/api/theme/template/template.test.mjs +5 -0
  64. package/api/theme/themeTemplate.doc.mjs +1 -1
  65. package/assets/docs/getting-started.doc.mjs +2 -2
  66. package/assets/docs/principles.doc.mjs +6 -6
  67. package/assets/docs/styling-libraries.doc.mjs +2 -2
  68. package/assets/docs/styling.doc.mjs +4 -4
  69. package/assets/docs/theme.doc.mjs +3 -3
  70. package/assets/docs/tokens.doc.mjs +1 -1
  71. package/assets/docs/tree/api.doc.mjs +30 -0
  72. package/assets/docs/tree/cli.doc.mjs +23 -0
  73. package/assets/docs/tree/commands.doc.mjs +25 -0
  74. package/assets/docs/{cli-integrations.doc.mjs → tree/integrations.doc.mjs} +37 -9
  75. package/assets/docs/tree/integrations.test.mjs +62 -0
  76. package/assets/docs/tree/writing-docs.doc.mjs +286 -0
  77. package/authoring/debug/parse.d.mts +3 -3
  78. package/authoring/doctypes/base/graph-fields.doc.mjs +7 -5
  79. package/authoring/doctypes/base/type.ts +6 -5
  80. package/authoring/doctypes/command/command.doc.mjs +1 -1
  81. package/authoring/doctypes/command/type.ts +2 -2
  82. package/authoring/doctypes/enum/enum.doc.mjs +1 -1
  83. package/authoring/doctypes/enum/type.ts +1 -1
  84. package/authoring/doctypes/function/function.doc.mjs +3 -2
  85. package/authoring/doctypes/function/type.ts +3 -2
  86. package/authoring/doctypes/namespace/namespace.doc.mjs +5 -9
  87. package/authoring/doctypes/reference/reference.doc.mjs +3 -3
  88. package/authoring/doctypes/reference/type.ts +3 -0
  89. package/authoring/doctypes/schema/schema.doc.mjs +1 -1
  90. package/authoring/doctypes/schema/type.ts +1 -1
  91. package/authoring/doctypes/template/parse.d.mts +2 -0
  92. package/authoring/doctypes/template/parse.mjs +4 -0
  93. package/authoring/doctypes/template/parse.test.mjs +18 -0
  94. package/authoring/doctypes/template/template.doc.mjs +6 -0
  95. package/authoring/doctypes/template/type.ts +8 -0
  96. package/authoring/identity/identity.doc.mjs +2 -2
  97. package/authoring/integration/type.ts +1 -1
  98. package/clients/cli/commands/build-theme.mjs +1 -1
  99. package/clients/cli/commands/build.mjs +5 -2
  100. package/clients/cli/commands/component/index.mjs +1 -1
  101. package/clients/cli/commands/component-ownership.test.mjs +3 -3
  102. package/clients/cli/commands/discover.broken-integration.test.mjs +7 -8
  103. package/clients/cli/commands/discover.mjs +1 -1
  104. package/clients/cli/commands/docs.doc.mjs +20 -8
  105. package/clients/cli/commands/docs.mjs +175 -18
  106. package/clients/cli/commands/docs.test.mjs +114 -7
  107. package/clients/cli/commands/doctor-integration-docs.doc.mjs +2 -2
  108. package/clients/cli/commands/doctor-integration-templates.doc.mjs +14 -7
  109. package/clients/cli/commands/doctor-integration.test.mjs +68 -1
  110. package/clients/cli/commands/doctor.mjs +24 -9
  111. package/clients/cli/commands/integration-add.doc.mjs +10 -0
  112. package/clients/cli/commands/integration.mjs +1 -0
  113. package/clients/cli/commands/search.mjs +11 -2
  114. package/clients/cli/commands/setup-nudge.test.mjs +6 -0
  115. package/clients/cli/commands/template.doc.mjs +23 -6
  116. package/clients/cli/commands/template.mjs +4 -91
  117. package/clients/cli/commands/text-json-parity.test.mjs +2 -1
  118. package/clients/cli/index.mjs +8 -0
  119. package/clients/cli/lib/manifest.mjs +9 -2
  120. package/foundation/agent-docs/agent-docs.mjs +1 -0
  121. package/foundation/agent-docs/agent-docs.test.mjs +10 -1
  122. package/foundation/config/project.d.mts +12 -11
  123. package/foundation/config/project.mjs +73 -59
  124. package/foundation/config/project.test.mjs +141 -5
  125. package/foundation/discovery/authoring-self-docs.test.mjs +17 -7
  126. package/foundation/discovery/cli-self-docs.d.mts +40 -34
  127. package/foundation/discovery/cli-self-docs.mjs +91 -115
  128. package/foundation/discovery/cli-self-docs.test.mjs +104 -166
  129. package/foundation/discovery/component-discovery.d.mts +38 -0
  130. package/foundation/discovery/component-discovery.mjs +48 -0
  131. package/foundation/discovery/docs-discovery.d.mts +105 -8
  132. package/foundation/discovery/docs-discovery.mjs +177 -13
  133. package/foundation/discovery/docs-discovery.test.mjs +56 -17
  134. package/foundation/discovery/docs-output-budget.d.mts +2 -2
  135. package/foundation/discovery/docs-output-budget.mjs +1 -1
  136. package/foundation/discovery/docs-section-key.d.mts +9 -2
  137. package/foundation/discovery/docs-section-key.mjs +15 -1
  138. package/foundation/discovery/template-adapter.d.mts +92 -6
  139. package/foundation/discovery/template-adapter.mjs +453 -17
  140. package/foundation/discovery/template-adapter.test.mjs +42 -0
  141. package/foundation/doc-compiler/bundle.mjs +61 -1
  142. package/foundation/doc-compiler/bundle.test.mjs +21 -10
  143. package/foundation/doc-compiler/compile.d.mts +113 -132
  144. package/foundation/doc-compiler/compile.mjs +52 -6
  145. package/foundation/doc-compiler/diagnostics.mjs +28 -0
  146. package/foundation/doc-compiler/doc-loads.test.mjs +2 -2
  147. package/foundation/doc-compiler/inputs.d.mts +2 -2
  148. package/foundation/doc-compiler/inputs.mjs +6 -1
  149. package/foundation/doc-compiler/ir.mjs +7 -0
  150. package/foundation/doc-compiler/lenses.d.mts +3 -2
  151. package/foundation/doc-compiler/lenses.mjs +2 -1
  152. package/foundation/doc-compiler/links.d.mts +135 -0
  153. package/foundation/doc-compiler/links.mjs +253 -0
  154. package/foundation/doc-compiler/links.test.mjs +139 -0
  155. package/foundation/doc-compiler/lower-doc.test.mjs +5 -2
  156. package/foundation/doc-compiler/read.d.mts +3 -2
  157. package/foundation/doc-compiler/read.mjs +6 -1
  158. package/foundation/doc-compiler/tree.d.mts +288 -0
  159. package/foundation/doc-compiler/tree.mjs +876 -0
  160. package/foundation/doc-compiler/tree.test.mjs +598 -0
  161. package/foundation/integrations/autolink.test.mjs +1 -1
  162. package/foundation/integrations/cli-requirement.d.mts +45 -0
  163. package/foundation/integrations/cli-requirement.mjs +154 -0
  164. package/foundation/integrations/cli-requirement.test.mjs +109 -0
  165. package/foundation/integrations/contribution-inventory.d.mts +2 -1
  166. package/foundation/integrations/contribution-inventory.mjs +10 -4
  167. package/foundation/integrations/contribution-inventory.test.mjs +19 -0
  168. package/foundation/integrations/integration-warnings.d.mts +9 -2
  169. package/foundation/integrations/integration-warnings.mjs +51 -26
  170. package/foundation/integrations/integration-warnings.test.mjs +74 -1
  171. package/foundation/integrations/validate-contributions.mjs +25 -27
  172. package/foundation/response/response-types.doc.mjs +13 -8
  173. package/foundation/xle/expand.mjs +1 -1
  174. package/foundation/xle/xle.test.mjs +13 -0
  175. package/package.json +9 -9
  176. package/assets/docs/cli.doc.mjs +0 -15
package/README.md CHANGED
@@ -21,16 +21,16 @@ The CLI documents itself, so these commands print what the installed version doe
21
21
 
22
22
  - `astryx <command> --help`: one command's arguments and options.
23
23
  - `astryx manifest --json`: every command, option, and response type, as JSON.
24
- - `astryx docs cli --index`: one section for each command (`commands-<name>`)
25
- and each API function, plus the JSON output envelope, error codes, and
26
- response types (`api-<name>`). Read one with `astryx docs cli <key>`, for
27
- example `astryx docs cli api-search`.
24
+ - `astryx docs cli`: the CLI's docs tree, one level at a time.
25
+ `astryx docs cli/commands` lists every command, `astryx docs cli/api` lists
26
+ the API's functions, schemas, and enums, and a route such as
27
+ `astryx docs cli/api/functions/search` prints one.
28
28
  - `astryx docs authoring --index`: the authoring reference, with one section for
29
29
  each file an author writes: the `astryx.config.*` file, the
30
30
  `astryx.integration.*` manifest, codemods, and every doc type (`ComponentDoc`,
31
31
  `TemplateDoc`, `ThemeDoc`, and the rest). Read one section with
32
32
  `astryx docs authoring <section>`, for example `astryx docs authoring config`.
33
- - `astryx docs cli-integrations`: the guide to building an integration package.
33
+ - `astryx docs cli/integrations`: the guide to building an integration package.
34
34
  - `astryx docs`: every docs topic, including the design-system guides (for
35
35
  example `tokens`, `theme`, and `layout`).
36
36
 
@@ -428,24 +428,25 @@ Every response has a `type` discriminant. The full set is below (generated from
428
428
  | `component.detail.source` | One component's source file, as {component, source}. |
429
429
  | `component.detail.showcase` | One component's showcase example, as {component, aspectRatio, source}. |
430
430
  | `component.detail.blocks` | One component's example blocks, as {component, showcase, examples, related} of BlockEntry. |
431
- | `docs.list` | All reference-doc topics as DocsListEntry[] ({topic, description}), in discovery order. |
432
- | `docs.detail` | One topic's full ReferenceDoc, with token-ref blocks inlined. |
433
- | `docs.index` | One topic's section index (--index): the topic's name, title, and description, plus sections, each {id, title, summary} (pass the id as the section argument; summary is the section's one-line summary). |
434
- | `docs.detail.section` | One ReferenceSection of a topic, found by key or title, with token-ref blocks inlined. |
431
+ | `docs.list` | All reference-doc topics as DocsListEntry[] ({topic, description, package, replaces?}), in read order; meta.namespaces lists the docs tree's top-level namespaces, and meta.notLoaded each package whose docs did not load. |
432
+ | `docs.detail` | One topic's full ReferenceDoc (the JSON read of a topic, --full, --dense, or a topic with one section), with token-ref blocks inlined, plus links ({up, previous, next}: the commands that open the level it sits in and its neighbors there). |
433
+ | `docs.index` | One topic's section index, the text read of a topic with more than one section (and --index): the topic's name, title, and description, plus sections, each {id, title, summary} (pass the id as the section argument; summary is the section's one-line summary), and links ({up, previous, next}: the commands that open the level it sits in and its neighbors there). |
434
+ | `docs.detail.section` | One ReferenceSection of a topic, found by key or title, with token-ref blocks inlined, plus links ({up, previous, next}: the commands that open its topic index and the sections before and after it). |
435
+ | `docs.node` | One node of the docs tree, read by its route: its id, kind, package, title, summary, and breadcrumb, plus a namespace's slots with their children (one level down) or a typed doc's content, and links ({up, previous, next, related}: the commands that open its parent, its neighbors, and the docs it names). |
435
436
  | `blog.list` | The feed URL plus every post parsed from the RSS feed, each with slug, title, description, date, type, authors, link, and plaintext URL. |
436
437
  | `blog.detail` | One post's metadata plus the feed URL and the post's full plaintext body. |
437
438
  | `discover.list` | The configured external packages (name, category, components, version, description); when empty it carries meta.configured to tell "nothing configured" from "nothing discovered". |
438
439
  | `discover.detail` | A single external package entry, for an @scope/name query. |
439
440
  | `discover.detail.doc` | The validated ComponentDoc for one external component: an @scope/name/Component query, or a free-text term resolving to exactly one component. |
440
441
  | `discover.search` | The echoed query plus the matching {package, component} pairs, when a free-text term matches several components. |
441
- | `search` | The echoed query, `matchCount` (total matches, before `limit`), and results, a ranked SearchResultEntry[] bounded by `limit`: each {domain, name, score, reason, description, command}, plus import (components, hooks), title (docs), or displayName and kind (templates). |
442
+ | `search` | The echoed query, `matchCount` (total matches, before `limit`), and results, a ranked SearchResultEntry[] bounded by `limit`: each {domain, name, score, reason, description, command}, plus import (components, hooks), title, parent (the command that opens the level above), package (for a docs-tree hit), and, for a hit on one section, section (docs), or displayName and kind (templates). |
442
443
  | `build.help` | The how-to-build-a-page playbook, emitted when no query is given: `playbook: true`, a title, the ordered steps (title, commands, optional returns), the on-system rules, and related lookups. Commands are bare subcommands for the caller to render with its own invocation. |
443
444
  | `build.kit` | The composition kit: echoed query, hasResults, matchCount (total matched, never a cap), directMatch, pages (closest templates), blocks (drop-in patterns) and domain (idea components/hooks) as SearchResultEntry[], frame and foundation name arrays, and hint {reason, commands} when thin. |
444
445
  | `swizzle.list` | The names of swizzlable components discoverable from cwd's @astryxdesign/core. |
445
446
  | `swizzle.copy` | An eject receipt: component name, owning package, output directory, files-copied count, the written file names, whether any file uses StyleX, and an optional maintainer note. |
446
447
  | `gap-report.categories` | The fixed gap category values and human-readable labels. |
447
448
  | `gap-report.file` | An aggregate receipt: overall status, the selected package, issuesUrl (or null), deliveries in handler order, each {handlerType: project \| integration \| fallback, handler, audience, status, url, message}, and filedCount/routedOnlyCount totals. |
448
- | `template.list` | Every discovered template (page + block); each entry carries id, name, description, kind, owning package, optional category and componentsUsed, and readiness flags. |
449
+ | `template.list` | The effective discovered TemplateListEntry[] for pages and blocks. A winning replacement entry includes optional `replaces`, naming the Core id omitted from the default list. |
449
450
  | `template.show` | The resolved template's raw source plus its description, kind, and the component names it composes. |
450
451
  | `template.skeleton` | A layout skeleton (structural tags with spatial annotations) plus the template's description and the components it composes. |
451
452
  | `template.copy` | A scaffold receipt: template id, output directory, written file name, and file count. |
@@ -469,9 +470,9 @@ Every response has a `type` discriminant. The full set is below (generated from
469
470
  | `integration.add` | A contribution-writer receipt: kind, name, optional root {path, created}, integration-manifest path, every affected project-relative path, written, and dryRun. |
470
471
  | `integration.pack-check` | The packed-package check: name, version, packable, tarball {filename, fileCount, size, unpackedSize} or null, inventory {manifest, roots [{kind, path, expectedFiles, missingFiles, complete}], expectedFiles, packedFiles}, contributions {local, packed}, each null or {themes [{slug, exportName}], components, templates [{id, type, name}], codemods [{version, id}], docs, agentDocsAppend}, and issues [{code, severity, message}]. |
471
472
  | `integration.validate` | The validation result: the package name and version (both null when no local manifest is found) plus issues, an AstryxIntegrationIssue[] of {code, severity: warning \| error, message}. |
472
- | `integration.template-conflicts` | The integration identity, structural issues, and non-blocking conflicts where an integration template id is also owned by Core; each conflict includes the exact package-qualified command. |
473
+ | `integration.template-conflicts` | The integration identity, issues, and conflicts as {severity: info \| warning, relationship: replaces \| accidental, replaces?, command}. |
473
474
  | `integration.component-conflicts` | The integration identity, structural issues, and non-blocking conflicts where an integration component name is also owned by Core; each conflict includes the exact package-qualified command. |
474
- | `integration.doc-conflicts` | The integration identity, structural issues, and Core doc overlaps classified as intentional replacements, intentional extensions, or accidental same-name conflicts. |
475
+ | `integration.doc-conflicts` | The integration identity, structural issues, and Core doc overlaps. Each finding includes `severity` (`info` \| `error`) and `relationship` (`replaces` \| `extends` \| `accidental`). |
475
476
  | `layout.expand` | The expansion: parsed form, generated TSX code, componentsUsed, states (count of useState hooks scaffolded), todos, blocksReferenced (each {name, mode}), warnings, and written (the output path, or null when nothing was written). |
476
477
  | `layout.check` | The validation result: a valid flag, the detected form, errors (each with line/col, message, formatted text, and suggestions), warnings, and the expression re-printed in both canonical surfaces (compact and outline). |
477
478
  | `layout.grammar` | The XLE/XLO grammar cheatsheet: a text field with the full reference plus an aliases map (short name → canonical component) generated from this install's registry. |
@@ -667,9 +668,10 @@ wrong type fails there; a field this CLI does not know is ignored with a
667
668
  warning naming it, so a manifest written against a newer CLI still contributes
668
669
  everything this one understands.
669
670
 
670
- Discovery is resilient: a broken or misconfigured integration is skipped with a
671
- one-line warning on stderr instead of crashing the CLI, and it never corrupts a
672
- `--json` envelope. To inspect problems, run
671
+ Discovery is resilient. A manifest load failure skips that package with a warning.
672
+ An invalid contribution kind remains reportable without hiding other valid kinds,
673
+ and invalid template or component metadata is omitted without hiding valid siblings.
674
+ Warnings go to stderr and never corrupt a `--json` envelope. To inspect problems, run
673
675
  `astryx doctor integration validate <package>` for structure, then use `templates`,
674
676
  `components`, or `docs` under the same `astryx doctor integration` group to check
675
677
  Core identity overlaps before publishing. Bare `astryx doctor` checks overall
@@ -679,5 +681,5 @@ For the full authoring walkthrough (component doc format, template packaging
679
681
  and `exports` requirements, and codemod authoring), see the guide:
680
682
 
681
683
  ```bash
682
- astryx docs cli-integrations
684
+ astryx docs cli/integrations
683
685
  ```
@@ -2,9 +2,8 @@
2
2
  // DO NOT EDIT — run `pnpm sync:api-types` to regenerate.
3
3
 
4
4
  /**
5
- * A loaded component doc. `loadDocs` returns the authored `.doc.mjs` shape,
6
- * which is either a single-component or multi-component doc; this loose view
7
- * captures the fields the API reads across both forms.
5
+ * A loaded component doc. The shared validated loader accepts stamped and legacy
6
+ * component docs; this loose view captures the fields the API reads across both.
8
7
  * @typedef {object} LoadedComponentDoc
9
8
  * @property {string} [name]
10
9
  * @property {string} [description]
@@ -15,9 +14,7 @@
15
14
  * @property {string} [import] set when the doc states its own import specifier
16
15
  */
17
16
  /**
18
- * Options object for `loadDocs`, matching its declared parameter shape (used
19
- * as a cast target so `lang` (which the API may hold as `string|null`) type
20
- * checks against `loadDocs`'s `lang?: string`).
17
+ * Options object for the shared component-doc loader.
21
18
  * @typedef {{zh?: boolean, dense?: boolean, lang?: string}} LoadDocsOpts
22
19
  */
23
20
  /**
@@ -184,9 +181,8 @@ export function scopeSubComponent(docs: LoadedComponentDoc, dirName: string, cor
184
181
  } | null;
185
182
  export { CORE_PACKAGE };
186
183
  /**
187
- * A loaded component doc. `loadDocs` returns the authored `.doc.mjs` shape,
188
- * which is either a single-component or multi-component doc; this loose view
189
- * captures the fields the API reads across both forms.
184
+ * A loaded component doc. The shared validated loader accepts stamped and legacy
185
+ * component docs; this loose view captures the fields the API reads across both.
190
186
  */
191
187
  export type LoadedComponentDoc = {
192
188
  name?: string | undefined;
@@ -203,9 +199,7 @@ export type LoadedComponentDoc = {
203
199
  import?: string | undefined;
204
200
  };
205
201
  /**
206
- * Options object for `loadDocs`, matching its declared parameter shape (used
207
- * as a cast target so `lang` (which the API may hold as `string|null`) type
208
- * checks against `loadDocs`'s `lang?: string`).
202
+ * Options object for the shared component-doc loader.
209
203
  */
210
204
  export type LoadDocsOpts = {
211
205
  zh?: boolean;
@@ -35,16 +35,15 @@ import {
35
35
  resolveIntegrationImportPath as resolveIntegrationImport,
36
36
  } from '../../foundation/discovery/component-discovery.mjs';
37
37
  import {Project} from '../../foundation/config/project.mjs';
38
- import {loadDocs} from '../../foundation/discovery/component-loader.mjs';
38
+ import {loadComponentDoc as loadValidatedComponentDoc} from '../../foundation/discovery/component-loader.mjs';
39
39
  import {searchComponents} from '../../foundation/text/string-utils.mjs';
40
40
  import {AstryxError} from '../error.mjs';
41
41
 
42
42
  export {CORE_PACKAGE};
43
43
 
44
44
  /**
45
- * A loaded component doc. `loadDocs` returns the authored `.doc.mjs` shape,
46
- * which is either a single-component or multi-component doc; this loose view
47
- * captures the fields the API reads across both forms.
45
+ * A loaded component doc. The shared validated loader accepts stamped and legacy
46
+ * component docs; this loose view captures the fields the API reads across both.
48
47
  * @typedef {object} LoadedComponentDoc
49
48
  * @property {string} [name]
50
49
  * @property {string} [description]
@@ -56,9 +55,7 @@ export {CORE_PACKAGE};
56
55
  */
57
56
 
58
57
  /**
59
- * Options object for `loadDocs`, matching its declared parameter shape (used
60
- * as a cast target so `lang` (which the API may hold as `string|null`) type
61
- * checks against `loadDocs`'s `lang?: string`).
58
+ * Options object for the shared component-doc loader.
62
59
  * @typedef {{zh?: boolean, dense?: boolean, lang?: string}} LoadDocsOpts
63
60
  */
64
61
 
@@ -381,9 +378,22 @@ export async function resolveUnscopedDoc(dirName, {coreDir, cwd, name}) {
381
378
  */
382
379
  export async function loadComponentDoc(docPath, opts = {}) {
383
380
  const {zh = false, dense = false, lang = null} = opts;
384
- return /** @type {LoadedComponentDoc} */ (
385
- await loadDocs(docPath, /** @type {LoadDocsOpts} */ ({zh, dense, lang}))
386
- );
381
+ try {
382
+ return /** @type {LoadedComponentDoc} */ (
383
+ await loadValidatedComponentDoc(
384
+ docPath,
385
+ /** @type {LoadDocsOpts} */ ({zh, dense, lang}),
386
+ )
387
+ );
388
+ } catch (err) {
389
+ throw new AstryxError(
390
+ `Cannot load component metadata: ${
391
+ err instanceof Error ? err.message : String(err)
392
+ }`,
393
+ undefined,
394
+ ERROR_CODES.ERR_INVALID_DOC,
395
+ );
396
+ }
387
397
  }
388
398
 
389
399
  /**
@@ -143,14 +143,20 @@ export async function component(name, options = {}) {
143
143
  // Searches that package first — critical for names that exist in both core
144
144
  // and an external package (AppShell, Button, SideNav).
145
145
  if (packageScope) {
146
- const scoped = classifyScope(packageScope, {owners, loadedIntegrations, cwd, name});
146
+ const scoped = classifyScope(packageScope, {
147
+ owners,
148
+ loadedIntegrations,
149
+ cwd,
150
+ name,
151
+ });
147
152
 
148
153
  if (scoped.kind === 'core' || scoped.kind === 'integration') {
149
154
  const owner = scoped.owner;
150
155
  if (source) {
151
156
  return componentDetailSource(dirName, owner.sourcePath, {
152
157
  name,
153
- notFoundInPackage: scoped.kind === 'integration' ? packageScope : null,
158
+ notFoundInPackage:
159
+ scoped.kind === 'integration' ? packageScope : null,
154
160
  });
155
161
  }
156
162
  // showcase/blocks were previously dropped on the scoped path — a
@@ -187,17 +193,48 @@ export async function component(name, options = {}) {
187
193
  }
188
194
  const docs = await loadComponentDoc(extDocPath, docOpts);
189
195
  if (props) return componentDetailProps(docs);
190
- return componentDetail(docs, {package: scoped.ext.name, sourcePath: null}, dirName, coreDir);
196
+ return componentDetail(
197
+ docs,
198
+ {package: scoped.ext.name, sourcePath: null},
199
+ dirName,
200
+ coreDir,
201
+ );
202
+ }
203
+ throw new AstryxError(
204
+ `No component "${name}" in package "${packageScope}"`,
205
+ undefined,
206
+ ERROR_CODES.ERR_UNKNOWN_COMPONENT,
207
+ );
208
+ }
209
+
210
+ // Invalid integration metadata does not create ambiguity against a valid
211
+ // owner. Keep raw owners only when no doc owner is valid, so an integration-
212
+ // only component still exposes its source and returns ERR_INVALID_DOC for
213
+ // detail instead of degrading to an unrelated unknown-component error.
214
+ const validOwners = [];
215
+ for (const owner of owners) {
216
+ if (owner.package === CORE_PACKAGE) {
217
+ validOwners.push(owner);
218
+ continue;
219
+ }
220
+ try {
221
+ await loadComponentDoc(owner.docPath);
222
+ validOwners.push(owner);
223
+ } catch {
224
+ // Project/Doctor report invalid metadata; it is not an effective owner.
191
225
  }
192
- throw new AstryxError(`No component "${name}" in package "${packageScope}"`, undefined, ERROR_CODES.ERR_UNKNOWN_COMPONENT);
193
226
  }
227
+ const effectiveOwners = validOwners.length > 0 ? validOwners : owners;
194
228
 
195
- // ── Ambiguity: owned by MORE THAN ONE package, no --package ─────
196
- assertUnambiguousOwners(owners, dirName);
229
+ // ── Ambiguity: owned by MORE THAN ONE valid package, no --package ──
230
+ assertUnambiguousOwners(effectiveOwners, dirName);
197
231
 
198
232
  // ── Single non-core owner (an integration provides it, core does not) ──
199
- if (owners.length === 1 && owners[0].package !== CORE_PACKAGE) {
200
- const owner = owners[0];
233
+ if (
234
+ effectiveOwners.length === 1 &&
235
+ effectiveOwners[0].package !== CORE_PACKAGE
236
+ ) {
237
+ const owner = effectiveOwners[0];
201
238
  if (source) {
202
239
  return componentDetailSource(dirName, owner.sourcePath, {name});
203
240
  }
@@ -213,7 +250,11 @@ export async function component(name, options = {}) {
213
250
  // ── No-scope core path ─────────────────────────────────────────
214
251
  // `--source` reads core directly (no external/fuzzy fallback).
215
252
  if (source) {
216
- return componentDetailSource(dirName, resolveCoreSourcePath(coreDir, dirName), {name});
253
+ return componentDetailSource(
254
+ dirName,
255
+ resolveCoreSourcePath(coreDir, dirName),
256
+ {name},
257
+ );
217
258
  }
218
259
  if (showcase) {
219
260
  return componentDetailShowcase(dirName, {cwd, name});
@@ -232,10 +273,14 @@ export async function component(name, options = {}) {
232
273
  // scope the response to just the matching sub-component.
233
274
  const sub = scopeSubComponent(docs, dirName, coreDir);
234
275
  if (sub) {
235
- if (props) return componentDetailProps({props: sub.matchingComponent.props});
276
+ if (props)
277
+ return componentDetailProps({props: sub.matchingComponent.props});
236
278
  return componentDetail(
237
279
  sub.scoped,
238
- {package: resolved.resolvedOwnerPackage, sourcePath: resolved.resolvedSourcePath},
280
+ {
281
+ package: resolved.resolvedOwnerPackage,
282
+ sourcePath: resolved.resolvedSourcePath,
283
+ },
239
284
  dirName,
240
285
  coreDir,
241
286
  );
@@ -244,7 +289,10 @@ export async function component(name, options = {}) {
244
289
  if (props) return componentDetailProps(docs);
245
290
  return componentDetail(
246
291
  docs,
247
- {package: resolved.resolvedOwnerPackage, sourcePath: resolved.resolvedSourcePath},
292
+ {
293
+ package: resolved.resolvedOwnerPackage,
294
+ sourcePath: resolved.resolvedSourcePath,
295
+ },
248
296
  resolved.resolvedName,
249
297
  coreDir,
250
298
  );
@@ -15,7 +15,7 @@ import {
15
15
  CORE_PACKAGE,
16
16
  discoverComponents,
17
17
  discoverExternalComponentsGrouped,
18
- discoverIntegrationComponents,
18
+ discoverValidIntegrationComponents,
19
19
  findComponentReadme,
20
20
  findExternalComponentDoc,
21
21
  resolveImportPath,
@@ -228,7 +228,8 @@ export async function componentList(
228
228
  const seenIntegration = new Set();
229
229
  for (const integration of loadedIntegrations) {
230
230
  seenIntegration.add(integration.name);
231
- const owned = discoverIntegrationComponents(integration);
231
+ const {components: owned} =
232
+ await discoverValidIntegrationComponents(integration);
232
233
  // Group integration components by their doc `group`, falling back to the
233
234
  // package name. Keys are package-qualified so they never collide with
234
235
  // core groups or each other.
@@ -16,16 +16,75 @@
16
16
  */
17
17
  export function loadDocsCatalog(cwd?: string): Promise<DocsCatalog>;
18
18
  /**
19
- * One topic, lowered for `lang`: overlaid, extensions merged, keys stamped.
20
- * Memoized per catalog, so a read that references a topic twice loads it once.
21
- * Every read of the catalog shares the memoized node, so it is frozen; the
22
- * lenses hand readers copies.
19
+ * The CLI's own topics alone, for a check that runs without a project.
20
+ * @returns {DocsCatalog}
21
+ */
22
+ export function builtinCatalog(): DocsCatalog;
23
+ /**
24
+ * One topic, lowered for `lang` with every link between docs resolved
25
+ * (spec:AST-047 FR9): an inline `{@link <target>}` reads as the command that
26
+ * opens its doc, and a `reference` or `workflow` block carries the doc it
27
+ * names. Memoized per catalog and frozen, like the lowered node.
23
28
  * @param {DocsCatalog} catalog
24
- * @param {import('../../foundation/discovery/docs-discovery.mjs').DocsTopicEntry} entry
29
+ * @param {DocsTopicEntry} entry
25
30
  * @param {string | null} [lang]
26
31
  * @returns {Promise<import('../../foundation/doc-compiler/compile.mjs').CompiledReferenceNode>}
27
32
  */
28
- export function lowerTopic(catalog: DocsCatalog, entry: import("../../foundation/discovery/docs-discovery.mjs").DocsTopicEntry, lang?: string | null): Promise<import("../../foundation/doc-compiler/compile.mjs").CompiledReferenceNode>;
33
+ export function lowerTopic(catalog: DocsCatalog, entry: DocsTopicEntry, lang?: string | null): Promise<import("../../foundation/doc-compiler/compile.mjs").CompiledReferenceNode>;
34
+ /**
35
+ * Each link in a topic that names no doc.
36
+ * @param {DocsCatalog} catalog
37
+ * @param {DocsTopicEntry} entry
38
+ * @returns {Promise<LinkProblem[]>}
39
+ */
40
+ export function topicLinkProblems(catalog: DocsCatalog, entry: DocsTopicEntry): Promise<LinkProblem[]>;
41
+ /**
42
+ * The project's docs tree: the CLI's own docs plus the namespace docs and
43
+ * placed guides the configured integrations ship (spec:AST-046), built once
44
+ * per catalog. Without integration docs it is the CLI's tree, built once per
45
+ * process.
46
+ * @param {DocsCatalog} catalog
47
+ * @param {{fresh?: boolean}} [options] `fresh`: reread the CLI's tree files
48
+ * @returns {Promise<DocsTree>}
49
+ */
50
+ export function projectTree(catalog: DocsCatalog, { fresh }?: {
51
+ fresh?: boolean;
52
+ }): Promise<DocsTree>;
53
+ /**
54
+ * How a doc's links find their targets (spec:AST-047 FR9): a doc in the
55
+ * project's docs tree by its identity, or a flat topic by its provider and
56
+ * name. A target that matches neither is a problem, never a guess.
57
+ * @param {DocsCatalog} catalog
58
+ * @param {string} fromProvider the provider id of the doc the links sit in
59
+ * @returns {Promise<LinkResolver>}
60
+ */
61
+ export function linkResolver(catalog: DocsCatalog, fromProvider: string): Promise<LinkResolver>;
62
+ /**
63
+ * Every link in the project's docs that names no doc: in each topic, each
64
+ * guide the tree places, and each typed doc.
65
+ * @param {DocsCatalog} catalog
66
+ * @param {DocsTree} tree
67
+ * @param {{owner?: string}} [options] `owner`: only the docs this package owns
68
+ * @returns {Promise<string[]>}
69
+ */
70
+ export function docsLinkProblems(catalog: DocsCatalog, tree: DocsTree, { owner }?: {
71
+ owner?: string;
72
+ }): Promise<string[]>;
73
+ /**
74
+ * What \`astryx doctor integration docs\` checks in one integration's docs: the
75
+ * docs tree they build beside the CLI's (namespaces, placements, routes) and
76
+ * every link in them (spec:AST-046, spec:AST-047).
77
+ * @param {{name: string}} integration
78
+ * @param {{records: import('../../foundation/discovery/docs-discovery.mjs').DocsTopicRecord[], namespaces: import('../../foundation/doc-compiler/tree.mjs').TreeNamespaceInput[], guides: import('../../foundation/doc-compiler/tree.mjs').TreeDocInput[]}} discovered
79
+ * @returns {Promise<string[]>}
80
+ */
81
+ export function packageDocsProblems(integration: {
82
+ name: string;
83
+ }, discovered: {
84
+ records: import("../../foundation/discovery/docs-discovery.mjs").DocsTopicRecord[];
85
+ namespaces: import("../../foundation/doc-compiler/tree.mjs").TreeNamespaceInput[];
86
+ guides: import("../../foundation/doc-compiler/tree.mjs").TreeDocInput[];
87
+ }): Promise<string[]>;
29
88
  /**
30
89
  * How a token reference finds its target: the topic it names in `catalog`,
31
90
  * lowered for the same language.
@@ -43,34 +102,156 @@ export function referenceTargets(catalog: DocsCatalog, lang: string | null): (to
43
102
  */
44
103
  export function compileTopic(catalog: DocsCatalog, entry: import("../../foundation/discovery/docs-discovery.mjs").DocsTopicEntry, lang?: string | null): Promise<import("../../foundation/doc-compiler/compile.mjs").CompiledReferenceNode>;
45
104
  /**
46
- * Resolve `topic` against the project's catalog (throwing `ERR_UNKNOWN_TOPIC`
47
- * when unmatched) and lower it with any --dense/--zh overlay and any
48
- * integration extension applied. Shared by the leaves so topic normalization
49
- * and unknown-topic handling live in exactly one place.
50
- *
51
- * @param {string} topic
52
- * @param {object} [options]
53
- * @param {string} [options.lang]
54
- * @param {boolean} [options.zh]
55
- * @param {boolean} [options.dense]
56
- * @param {string} [options.cwd]
57
- * @returns {Promise<{
58
- * catalog: DocsCatalog,
59
- * node: import('../../foundation/doc-compiler/compile.mjs').CompiledReferenceNode,
60
- * lang: string | null,
61
- * }>}
62
- */
63
- export function resolveTopicDocs(topic: string, options?: {
64
- lang?: string | undefined;
65
- zh?: boolean | undefined;
66
- dense?: boolean | undefined;
67
- cwd?: string | undefined;
105
+ * A guide the docs tree places, as a topic entry the topic readers open by its
106
+ * route. It is never a flat topic: `astryx docs <route>` is its only name.
107
+ * @param {import('../../foundation/doc-compiler/tree.mjs').TreeNode} node
108
+ * @returns {import('../../foundation/discovery/docs-discovery.mjs').DocsTopicEntry}
109
+ */
110
+ export function guideEntry(node: import("../../foundation/doc-compiler/tree.mjs").TreeNode): import("../../foundation/discovery/docs-discovery.mjs").DocsTopicEntry;
111
+ /**
112
+ * What a typed doc in the docs tree prints: its content, with every link to
113
+ * another doc resolved (spec:AST-047 FR9). A namespace has no content. The
114
+ * CLI's doc modules are discovery, so this lives in the adapter
115
+ * (architecture:cli-surface INV21).
116
+ * @param {DocsCatalog} catalog
117
+ * @param {DocsTree} tree
118
+ * @param {TreeNode} node
119
+ * @returns {Promise<{content: any[], problems: LinkProblem[]}>}
120
+ */
121
+ export function nodeContent(catalog: DocsCatalog, tree: DocsTree, node: TreeNode): Promise<{
122
+ content: any[];
123
+ problems: LinkProblem[];
124
+ }>;
125
+ /**
126
+ * The command that opens the level a topic sits in when the tree cannot say:
127
+ * a guide's parent namespace, or the topic list.
128
+ * @param {import('../../foundation/discovery/docs-discovery.mjs').DocsTopicEntry} entry
129
+ * @returns {import('./docs.type.mjs').DocsCommand}
130
+ */
131
+ export function topicUp(entry: import("../../foundation/discovery/docs-discovery.mjs").DocsTopicEntry): import("./docs.type.mjs").DocsCommand;
132
+ /**
133
+ * The moves from a node's place in the tree (spec:AST-047 FR2, FR4): up to its
134
+ * parent (the topic list, at the top), and across to the nodes before and
135
+ * after it in its parent's slot.
136
+ * @param {DocsTree} tree
137
+ * @param {TreeNode} node
138
+ * @returns {import('./docs.type.mjs').DocsLinks}
139
+ */
140
+ export function placeLinks(tree: DocsTree, node: TreeNode): import("./docs.type.mjs").DocsLinks;
141
+ /**
142
+ * The moves a topic read offers: from its place in the tree, where a guide
143
+ * sits in its namespace and a flat topic in the Unorganized level.
144
+ * @param {DocsCatalog} catalog
145
+ * @param {import('../../foundation/discovery/docs-discovery.mjs').DocsTopicEntry} entry
146
+ * @returns {Promise<import('./docs.type.mjs').DocsLinks>}
147
+ */
148
+ export function topicLinks(catalog: DocsCatalog, entry: import("../../foundation/discovery/docs-discovery.mjs").DocsTopicEntry): Promise<import("./docs.type.mjs").DocsLinks>;
149
+ /**
150
+ * What a docs argument names: a topic (a flat one, or a guide the docs tree
151
+ * places), a namespace or typed doc in the tree, or nothing, as nameOwner
152
+ * decides.
153
+ * @param {unknown} topic
154
+ * @param {{cwd?: string}} [options]
155
+ * @returns {Promise<
156
+ * | {kind: 'topic', catalog: DocsCatalog, entry: import('../../foundation/discovery/docs-discovery.mjs').DocsTopicEntry}
157
+ * | {kind: 'node', catalog: DocsCatalog, tree: import('../../foundation/doc-compiler/tree.mjs').DocsTree, node: import('../../foundation/doc-compiler/tree.mjs').TreeNode}
158
+ * | {kind: 'unknown', catalog: DocsCatalog}
159
+ * >}
160
+ */
161
+ export function resolveDocsArgument(topic: unknown, { cwd }?: {
162
+ cwd?: string;
163
+ }): Promise<{
164
+ kind: "topic";
165
+ catalog: DocsCatalog;
166
+ entry: import("../../foundation/discovery/docs-discovery.mjs").DocsTopicEntry;
167
+ } | {
168
+ kind: "node";
169
+ catalog: DocsCatalog;
170
+ tree: import("../../foundation/doc-compiler/tree.mjs").DocsTree;
171
+ node: import("../../foundation/doc-compiler/tree.mjs").TreeNode;
172
+ } | {
173
+ kind: "unknown";
174
+ catalog: DocsCatalog;
175
+ }>;
176
+ /**
177
+ * Who answers to a name a reader types (spec:AST-046 FR5, FR11). The tree
178
+ * decides first: the node at that route, compared without case, holds it. A
179
+ * name with no node of its own is a topic's other name (its `replaces`
180
+ * alias), and that topic answers, unless the tree gave the topic's own route
181
+ * to another doc. Reads, the topic list, and search all ask this, so they
182
+ * agree on every name.
183
+ * @param {DocsTree} tree
184
+ * @param {DocsCatalog} catalog
185
+ * @param {string} name
186
+ * @returns {{kind: 'topic', entry: import('../../foundation/discovery/docs-discovery.mjs').DocsTopicEntry} | {kind: 'node', node: TreeNode} | null}
187
+ */
188
+ export function nameOwner(tree: DocsTree, catalog: DocsCatalog, name: string): {
189
+ kind: "topic";
190
+ entry: import("../../foundation/discovery/docs-discovery.mjs").DocsTopicEntry;
191
+ } | {
192
+ kind: "node";
193
+ node: TreeNode;
194
+ } | null;
195
+ /**
196
+ * Whether a topic answers to its own name: what the topic list and search
197
+ * offer must open that topic.
198
+ * @param {DocsTree} tree
199
+ * @param {DocsCatalog} catalog
200
+ * @param {import('../../foundation/discovery/docs-discovery.mjs').DocsTopicEntry} entry
201
+ * @returns {boolean}
202
+ */
203
+ export function holdsOwnName(tree: DocsTree, catalog: DocsCatalog, entry: import("../../foundation/discovery/docs-discovery.mjs").DocsTopicEntry): boolean;
204
+ /**
205
+ * The doc that took a flat topic's route in the docs tree, when it is not
206
+ * that topic (spec:AST-046 FR11): the CLI keeps its routes, such as `cli`
207
+ * and `unorganized`, and a namespace keeps its route over a topic of the same
208
+ * name. Null when the topic owns its route, or the tree has no node there.
209
+ * @param {DocsTree} tree
210
+ * @param {import('../../foundation/discovery/docs-discovery.mjs').DocsTopicEntry} entry
211
+ * @returns {TreeNode | null}
212
+ */
213
+ export function routeOwner(tree: DocsTree, entry: import("../../foundation/discovery/docs-discovery.mjs").DocsTopicEntry): TreeNode | null;
214
+ /**
215
+ * The error for a docs argument that names nothing. For a route, it suggests
216
+ * the children of the deepest namespace the route reaches; otherwise, every
217
+ * topic and every top-level namespace.
218
+ * @param {unknown} topic
219
+ * @param {DocsCatalog} catalog
220
+ * @returns {Promise<AstryxError>}
221
+ */
222
+ export function unknownTopicError(topic: unknown, catalog: DocsCatalog): Promise<AstryxError>;
223
+ /**
224
+ * A sentence naming the packages whose docs did not load, or nothing: a doc
225
+ * that fails to load withdraws its package's docs, so a reader who cannot
226
+ * find one learns where to look.
227
+ * @param {DocsCatalog} catalog
228
+ * @returns {string}
229
+ */
230
+ export function notLoaded(catalog: DocsCatalog): string;
231
+ /**
232
+ * Resolve a topic (a flat one, or a guide the docs tree places by its route)
233
+ * and lower it for the topic readers.
234
+ * @param {unknown} topic
235
+ * @param {{lang?: string | null, zh?: boolean, dense?: boolean, cwd?: string}} [options]
236
+ */
237
+ export function resolveTopicDocs(topic: unknown, options?: {
238
+ lang?: string | null;
239
+ zh?: boolean;
240
+ dense?: boolean;
241
+ cwd?: string;
68
242
  }): Promise<{
69
243
  catalog: DocsCatalog;
70
244
  node: import("../../foundation/doc-compiler/compile.mjs").CompiledReferenceNode;
71
245
  lang: string | null;
246
+ entry: import("../../foundation/discovery/docs-discovery.mjs").DocsTopicEntry;
72
247
  }>;
248
+ export type DocsTree = import("../../foundation/doc-compiler/tree.mjs").DocsTree;
249
+ export type TreeNode = import("../../foundation/doc-compiler/tree.mjs").TreeNode;
250
+ export type LinkProblem = import("../../foundation/doc-compiler/links.mjs").LinkProblem;
251
+ export type LinkResolver = import("../../foundation/doc-compiler/links.mjs").LinkResolver;
252
+ export type DocsTopicEntry = import("../../foundation/discovery/docs-discovery.mjs").DocsTopicEntry;
73
253
  import { DocsCatalog } from '../../foundation/discovery/docs-discovery.mjs';
254
+ import { AstryxError } from '../error.mjs';
74
255
  import { OVERLAY_LANGUAGES } from '../../foundation/doc-compiler/read.mjs';
75
256
  import { overlayLanguages } from '../../foundation/doc-compiler/read.mjs';
76
257
  export { OVERLAY_LANGUAGES, overlayLanguages };