@astryxdesign/cli 0.6.3-canary.db4e378 → 0.6.3-canary.ddb63c3

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 (236) hide show
  1. package/README.md +20 -1
  2. package/api/blog/blog.doc.mjs +1 -0
  3. package/api/build/build.doc.mjs +1 -0
  4. package/api/component/component.doc.mjs +1 -0
  5. package/api/discover/discover.doc.mjs +1 -0
  6. package/api/docs/_adapter.d.mts +3 -8
  7. package/api/docs/_adapter.mjs +13 -105
  8. package/api/docs/docs.doc.mjs +1 -0
  9. package/api/docs/list/list.mjs +3 -7
  10. package/api/doctor/doctor.d.mts +25 -3
  11. package/api/doctor/doctor.doc.mjs +1 -0
  12. package/api/doctor/doctor.mjs +185 -13
  13. package/api/doctor/doctor.test.mjs +326 -1
  14. package/api/gap-report/gap-report.doc.mjs +1 -0
  15. package/api/hook/_adapter.mjs +19 -5
  16. package/api/hook/hook.doc.mjs +1 -0
  17. package/api/hook/list/list.d.mts +1 -1
  18. package/api/hook/list/list.mjs +69 -17
  19. package/api/init/init.doc.mjs +1 -0
  20. package/api/integration/add-contribution.mjs +7 -7
  21. package/api/integration/add-contribution.test.mjs +37 -3
  22. package/api/integration/add-theme.mjs +34 -64
  23. package/api/integration/add-theme.test.mjs +105 -21
  24. package/api/integration/authoring-checks.test.mjs +8 -4
  25. package/api/integration/integration-block-exports.test.mjs +10 -6
  26. package/api/integration/integrationAdd.doc.mjs +1 -0
  27. package/api/integration/integrationAddAgentDoc.doc.mjs +1 -0
  28. package/api/integration/integrationAddCodemod.doc.mjs +1 -0
  29. package/api/integration/integrationAddComponent.doc.mjs +1 -0
  30. package/api/integration/integrationAddDoc.doc.mjs +1 -0
  31. package/api/integration/integrationAddTemplate.doc.mjs +1 -0
  32. package/api/integration/integrationAddTheme.doc.mjs +6 -5
  33. package/api/integration/integrationComponentConflicts.doc.mjs +1 -0
  34. package/api/integration/integrationDocConflicts.doc.mjs +1 -0
  35. package/api/integration/integrationPackCheck.doc.mjs +1 -0
  36. package/api/integration/integrationTemplateConflicts.doc.mjs +1 -0
  37. package/api/integration/pack-check.test.mjs +17 -47
  38. package/api/integration/summarizeIssues.doc.mjs +1 -0
  39. package/api/integration/validate-integration-fixes.test.mjs +1389 -0
  40. package/api/integration/validate-integration.mjs +50 -102
  41. package/api/integration/validate-integration.test.mjs +85 -23
  42. package/api/integration/validate-unread-theme-folders.test.mjs +110 -0
  43. package/api/integration/validateIntegration.doc.mjs +1 -0
  44. package/api/json/assertResponse.doc.mjs +1 -0
  45. package/api/json/isError.doc.mjs +1 -0
  46. package/api/json/parseResponse.doc.mjs +1 -0
  47. package/api/layout/layoutCheck.doc.mjs +1 -0
  48. package/api/layout/layoutExpand.doc.mjs +1 -0
  49. package/api/layout/layoutGrammar.doc.mjs +1 -0
  50. package/api/search/search.doc.mjs +1 -0
  51. package/api/search/search.mjs +16 -6
  52. package/api/swizzle/swizzle.doc.mjs +1 -0
  53. package/api/template/template-suffix.test.mjs +41 -21
  54. package/api/template/template.doc.mjs +1 -0
  55. package/api/theme/_adapter.d.mts +2 -3
  56. package/api/theme/_adapter.mjs +4 -5
  57. package/api/theme/add/add.binary.test.mjs +10 -17
  58. package/api/theme/add/add.test.mjs +14 -1
  59. package/api/theme/generateTonalPalette.doc.mjs +1 -0
  60. package/api/theme/integration-themes.test.mjs +39 -28
  61. package/api/theme/list/list.test.mjs +19 -20
  62. package/api/theme/listThemes.doc.mjs +6 -5
  63. package/api/theme/themeAdd.doc.mjs +4 -3
  64. package/api/theme/themeBuild.doc.mjs +1 -0
  65. package/api/theme/themeList.doc.mjs +6 -3
  66. package/api/theme/themeListAvailable.doc.mjs +5 -3
  67. package/api/theme/themePaletteGenerate.doc.mjs +1 -0
  68. package/api/theme/themeTargets.doc.mjs +1 -0
  69. package/api/theme/themeTemplate.doc.mjs +1 -0
  70. package/api/upgrade/_adapter.d.mts +12 -3
  71. package/api/upgrade/_adapter.mjs +81 -66
  72. package/api/upgrade/provider-agreement.test.mjs +152 -0
  73. package/api/upgrade/run/run.mjs +1 -0
  74. package/api/upgrade/upgrade.doc.mjs +1 -0
  75. package/assets/codemods/integration-discovery.mjs +8 -2
  76. package/assets/codemods/integration-discovery.test.mjs +15 -0
  77. package/assets/codemods/runner.mjs +115 -5
  78. package/assets/codemods/transforms/next/__tests__/migrate-theme-catalog-to-descriptors.test.mjs +216 -0
  79. package/assets/codemods/transforms/next/index.mjs +11 -1
  80. package/assets/codemods/transforms/next/migrate-theme-catalog-to-descriptors.mjs +141 -0
  81. package/assets/docs/cli-integrations.doc.mjs +17 -30
  82. package/assets/docs/cli.doc.mjs +15 -0
  83. package/assets/docs/theme.doc.mjs +1 -1
  84. package/assets/templates/themes/butter/butterTheme.doc.mjs +11 -0
  85. package/assets/templates/themes/chocolate/chocolateTheme.doc.mjs +11 -0
  86. package/assets/templates/themes/gothic/gothicTheme.doc.mjs +11 -0
  87. package/assets/templates/themes/matcha/matchaTheme.doc.mjs +11 -0
  88. package/assets/templates/themes/neutral/neutralTheme.doc.mjs +11 -0
  89. package/assets/templates/themes/stone/stoneTheme.doc.mjs +11 -0
  90. package/assets/templates/themes/y2k/y2kTheme.doc.mjs +11 -0
  91. package/authoring/codemod/codemod.doc.mjs +1 -1
  92. package/authoring/config/config.doc.mjs +1 -1
  93. package/authoring/debug/debug.doc.d.mts +11 -0
  94. package/authoring/debug/debug.doc.mjs +182 -0
  95. package/authoring/doctypes/base/graph-fields.doc.mjs +1 -1
  96. package/authoring/doctypes/command/command.doc.mjs +1 -1
  97. package/authoring/doctypes/command/type.ts +1 -1
  98. package/authoring/doctypes/component/type.ts +2 -2
  99. package/authoring/doctypes/doctypes-new.test.mjs +48 -6
  100. package/authoring/doctypes/enum/enum.doc.mjs +1 -1
  101. package/authoring/doctypes/enum/type.ts +1 -1
  102. package/authoring/doctypes/function/function.doc.mjs +1 -1
  103. package/authoring/doctypes/function/type.ts +1 -1
  104. package/authoring/doctypes/hook/type.ts +2 -2
  105. package/authoring/doctypes/load-contract.test.mjs +2 -1
  106. package/authoring/doctypes/parse.d.mts +4 -2
  107. package/authoring/doctypes/parse.mjs +7 -3
  108. package/authoring/doctypes/reference/reference.doc.mjs +1 -1
  109. package/authoring/doctypes/reference/type.ts +2 -2
  110. package/authoring/doctypes/schema/schema.doc.mjs +1 -1
  111. package/authoring/doctypes/schema/type.ts +1 -2
  112. package/authoring/doctypes/template/template.doc.mjs +3 -3
  113. package/authoring/doctypes/theme/parse.d.mts +35 -0
  114. package/authoring/doctypes/theme/parse.mjs +76 -0
  115. package/authoring/doctypes/theme/theme.doc.d.mts +9 -0
  116. package/authoring/doctypes/theme/theme.doc.mjs +79 -0
  117. package/authoring/doctypes/theme/type.ts +42 -0
  118. package/authoring/doctypes/types.ts +2 -1
  119. package/authoring/gap-report/gap-report.doc.d.mts +12 -0
  120. package/authoring/gap-report/gap-report.doc.mjs +183 -0
  121. package/authoring/index.d.mts +1 -0
  122. package/authoring/index.d.ts +6 -4
  123. package/authoring/index.mjs +2 -1
  124. package/authoring/integration/integration.doc.mjs +2 -2
  125. package/authoring/integration/type.ts +3 -9
  126. package/clients/cli/__tests__/cliManifest.test.ts +27 -29
  127. package/clients/cli/commands/blog.doc.mjs +1 -1
  128. package/clients/cli/commands/build.doc.mjs +1 -1
  129. package/clients/cli/commands/component.doc.mjs +1 -1
  130. package/clients/cli/commands/discover.doc.mjs +1 -1
  131. package/clients/cli/commands/docs.doc.mjs +1 -1
  132. package/clients/cli/commands/doctor-integration-components.doc.mjs +1 -1
  133. package/clients/cli/commands/doctor-integration-docs.doc.mjs +1 -1
  134. package/clients/cli/commands/doctor-integration-templates.doc.mjs +1 -1
  135. package/clients/cli/commands/doctor-integration-validate.doc.mjs +1 -1
  136. package/clients/cli/commands/doctor-integration.doc.mjs +1 -1
  137. package/clients/cli/commands/doctor-integration.test.mjs +15 -7
  138. package/clients/cli/commands/doctor.doc.mjs +1 -1
  139. package/clients/cli/commands/gap-report.doc.mjs +1 -1
  140. package/clients/cli/commands/hook.doc.mjs +1 -1
  141. package/clients/cli/commands/init.doc.mjs +1 -1
  142. package/clients/cli/commands/integration-add.controls.test.mjs +1 -1
  143. package/clients/cli/commands/integration-add.doc.mjs +1 -1
  144. package/clients/cli/commands/integration-pack.doc.mjs +1 -1
  145. package/clients/cli/commands/integration-real-world.test.mjs +3 -9
  146. package/clients/cli/commands/integration.doc.mjs +1 -1
  147. package/clients/cli/commands/layout-check.doc.mjs +1 -1
  148. package/clients/cli/commands/layout-expand.doc.mjs +1 -1
  149. package/clients/cli/commands/layout-grammar.doc.mjs +1 -1
  150. package/clients/cli/commands/layout.doc.mjs +1 -1
  151. package/clients/cli/commands/manifest.doc.mjs +1 -1
  152. package/clients/cli/commands/search.doc.mjs +1 -1
  153. package/clients/cli/commands/swizzle.doc.mjs +1 -1
  154. package/clients/cli/commands/template.doc.mjs +1 -1
  155. package/clients/cli/commands/theme-add.doc.mjs +2 -2
  156. package/clients/cli/commands/theme-build.doc.mjs +1 -1
  157. package/clients/cli/commands/theme-list.doc.mjs +2 -2
  158. package/clients/cli/commands/theme-palette-generate.doc.mjs +3 -3
  159. package/clients/cli/commands/theme-palette.doc.mjs +1 -1
  160. package/clients/cli/commands/theme-targets.doc.mjs +1 -1
  161. package/clients/cli/commands/theme-template.doc.mjs +1 -1
  162. package/clients/cli/commands/theme.doc.mjs +1 -1
  163. package/clients/cli/commands/upgrade.doc.mjs +1 -1
  164. package/clients/cli/lib/hook-format.mjs +14 -5
  165. package/foundation/config/project-themes.test.mjs +11 -19
  166. package/foundation/config/project.d.mts +8 -0
  167. package/foundation/config/project.mjs +50 -21
  168. package/foundation/config/project.test.mjs +3 -16
  169. package/foundation/discovery/authoring-self-docs.d.mts +6 -0
  170. package/foundation/discovery/authoring-self-docs.mjs +17 -7
  171. package/foundation/discovery/authoring-surface.d.mts +74 -0
  172. package/foundation/discovery/authoring-surface.mjs +525 -0
  173. package/foundation/discovery/authoring-surface.test.mjs +392 -0
  174. package/foundation/discovery/cli-self-docs.d.mts +113 -0
  175. package/foundation/discovery/cli-self-docs.mjs +514 -0
  176. package/foundation/discovery/cli-self-docs.test.mjs +437 -0
  177. package/foundation/discovery/component-loader.d.mts +35 -38
  178. package/foundation/discovery/component-loader.mjs +53 -222
  179. package/foundation/discovery/docs-discovery.d.mts +3 -2
  180. package/foundation/discovery/docs-discovery.mjs +8 -12
  181. package/foundation/discovery/docs-discovery.test.mjs +8 -5
  182. package/foundation/discovery/template-adapter.d.mts +7 -0
  183. package/foundation/discovery/template-adapter.mjs +27 -40
  184. package/foundation/discovery/template-adapter.test.mjs +15 -0
  185. package/foundation/discovery/theme-discovery.d.mts +67 -7
  186. package/foundation/discovery/theme-discovery.mjs +916 -186
  187. package/foundation/discovery/theme-discovery.test.mjs +613 -219
  188. package/foundation/doc-compiler/bundle.d.mts +47 -0
  189. package/foundation/doc-compiler/bundle.mjs +218 -0
  190. package/foundation/doc-compiler/bundle.test.mjs +255 -0
  191. package/foundation/doc-compiler/compile.d.mts +200 -0
  192. package/foundation/doc-compiler/compile.mjs +254 -5
  193. package/foundation/doc-compiler/diagnostics.d.mts +126 -0
  194. package/foundation/doc-compiler/diagnostics.mjs +277 -0
  195. package/foundation/doc-compiler/doc-compiler.test.mjs +28 -1
  196. package/foundation/doc-compiler/doc-loads.test.mjs +1642 -0
  197. package/foundation/doc-compiler/import.d.mts +24 -0
  198. package/foundation/doc-compiler/import.mjs +59 -0
  199. package/foundation/doc-compiler/inputs.d.mts +102 -0
  200. package/foundation/doc-compiler/inputs.mjs +286 -0
  201. package/foundation/doc-compiler/inputs.test.mjs +299 -0
  202. package/foundation/doc-compiler/ir.d.mts +13 -0
  203. package/foundation/doc-compiler/ir.mjs +189 -12
  204. package/foundation/doc-compiler/lower-doc.test.mjs +492 -0
  205. package/foundation/doc-compiler/overlays.d.mts +37 -0
  206. package/foundation/doc-compiler/overlays.mjs +206 -0
  207. package/foundation/doc-compiler/parse-readable.d.mts +9 -0
  208. package/foundation/doc-compiler/parse-readable.mjs +29 -0
  209. package/foundation/doc-compiler/read.d.mts +126 -0
  210. package/foundation/doc-compiler/read.mjs +320 -0
  211. package/foundation/doc-compiler/read.test.mjs +313 -0
  212. package/foundation/doc-compiler/source.d.mts +33 -0
  213. package/foundation/doc-compiler/source.mjs +128 -0
  214. package/foundation/integrations/autolink.d.mts +58 -1
  215. package/foundation/integrations/autolink.mjs +143 -57
  216. package/foundation/integrations/contribution-fixes.d.mts +145 -0
  217. package/foundation/integrations/contribution-fixes.mjs +1284 -0
  218. package/foundation/integrations/contribution-inventory.d.mts +1 -1
  219. package/foundation/integrations/contribution-inventory.mjs +17 -20
  220. package/foundation/integrations/contribution-inventory.test.mjs +67 -27
  221. package/foundation/integrations/integrations.d.mts +3 -0
  222. package/foundation/integrations/integrations.mjs +11 -97
  223. package/foundation/integrations/provider-ledger.test.mjs +275 -0
  224. package/foundation/integrations/provider-resolution.d.mts +152 -0
  225. package/foundation/integrations/provider-resolution.mjs +576 -0
  226. package/foundation/integrations/provider-resolution.test.mjs +369 -0
  227. package/foundation/integrations/theme-descriptor.d.mts +8 -0
  228. package/foundation/integrations/theme-descriptor.mjs +44 -0
  229. package/foundation/integrations/validate-contributions.mjs +104 -10
  230. package/foundation/response/error-codes.doc.mjs +2 -2
  231. package/foundation/response/error-codes.mjs +1 -1
  232. package/foundation/response/response-types.doc.mjs +1 -1
  233. package/foundation/response/response.doc.mjs +1 -1
  234. package/foundation/text/string-utils.mjs +22 -10
  235. package/package.json +9 -9
  236. package/assets/templates/themes/manifest.json +0 -95
@@ -6,15 +6,23 @@
6
6
  * invariant (the counts must always add up to the number of checks).
7
7
  */
8
8
 
9
- import {describe, it, expect, afterEach} from 'vitest';
9
+ import {describe, it, expect, afterEach, vi} from 'vitest';
10
10
  import * as fs from 'node:fs';
11
11
  import * as os from 'node:os';
12
12
  import * as path from 'node:path';
13
13
  import {fileURLToPath} from 'node:url';
14
14
  import {DocsCatalog} from '../../foundation/discovery/docs-discovery.mjs';
15
+ import {
16
+ AUTHORING_ROOT,
17
+ AUTHORING_SELF_DOCS,
18
+ auditAuthoringSelfDocs,
19
+ } from '../../foundation/discovery/authoring-self-docs.mjs';
20
+ import {docs} from '../docs/docs.mjs';
21
+ import {auditCliSelfDocs} from '../../foundation/discovery/cli-self-docs.mjs';
15
22
  import {
16
23
  doctor,
17
24
  checkAuthoringDocs,
25
+ checkCliDocs,
18
26
  checkDocsProgressiveDisclosure,
19
27
  checkImplicitIntegrations,
20
28
  checkProviderIdentity,
@@ -444,6 +452,323 @@ describe('checkAuthoringDocs', () => {
444
452
  }, SLOW);
445
453
  });
446
454
 
455
+ describe('checkCliDocs', () => {
456
+ const CLI_DOCS_FIX =
457
+ 'Set `namespace` on each CLI doc to the one that reads it: cli/commands for a command, cli/api for an API function or the output schema, error codes, and response types, and authoring for a file an author writes (and list it in AUTHORING_SELF_DOCS).';
458
+
459
+ /** A doc tree under the working directory, as the CLI root. */
460
+ function writeRoot(/** @type {Record<string, any>} */ docsByPath) {
461
+ const root = fs.mkdtempSync(path.join(process.cwd(), '.astryx-doctor-cli-docs-'));
462
+ tmpDirs.push(root);
463
+ for (const [rel, doc] of Object.entries(docsByPath)) {
464
+ const file = path.join(root, rel);
465
+ fs.mkdirSync(path.dirname(file), {recursive: true});
466
+ fs.writeFileSync(file, `export const doc = ${JSON.stringify(doc)};\n`);
467
+ }
468
+ return root;
469
+ }
470
+
471
+ it(
472
+ 'passes on this repo, counting where each CLI doc is read',
473
+ async () => {
474
+ const audit = await auditCliSelfDocs();
475
+ expect(await checkCliDocs()).toEqual({
476
+ id: 'cli-docs',
477
+ label: 'CLI docs',
478
+ status: 'pass',
479
+ message: `All ${audit.docs} CLI docs are readable: ${audit.sections} in \`astryx docs cli\` and ${audit.authoring} in \`astryx docs authoring\`.`,
480
+ });
481
+ },
482
+ SLOW,
483
+ );
484
+
485
+ it(
486
+ 'fails on a doc with no namespace and one no topic reads, and names the fix',
487
+ async () => {
488
+ const root = writeRoot({
489
+ 'api/alpha/alpha.doc.mjs': {
490
+ type: 'function',
491
+ kind: 'api',
492
+ name: 'alpha',
493
+ displayName: 'alpha()',
494
+ summary: 'The alpha function.',
495
+ params: [],
496
+ returns: [{type: 'alpha', description: 'The alpha.'}],
497
+ },
498
+ 'clients/cli/commands/beta.doc.mjs': {
499
+ type: 'command',
500
+ name: 'beta',
501
+ displayName: 'astryx beta',
502
+ namespace: 'cli',
503
+ summary: 'Do beta',
504
+ },
505
+ });
506
+ expect(
507
+ await checkCliDocs(undefined, {root, authoringSources: []}),
508
+ ).toEqual({
509
+ id: 'cli-docs',
510
+ label: 'CLI docs',
511
+ status: 'fail',
512
+ message:
513
+ '2 problems: api/alpha/alpha.doc.mjs has no namespace, so no `astryx docs` topic reads it; clients/cli/commands/beta.doc.mjs has namespace "cli", which no `astryx docs` topic reads',
514
+ fix: CLI_DOCS_FIX,
515
+ });
516
+ },
517
+ SLOW,
518
+ );
519
+ });
520
+
521
+ describe('checkAuthoringDocs against the public authoring surface', () => {
522
+ const NAMESPACE = 'doctypes/namespace/namespace.doc.mjs';
523
+ const NAMESPACE_TYPES =
524
+ 'NamespaceDoc, NamespaceProviderScope, NamespaceSlotAcceptance, NamespaceSlot, NamespaceAdoptionSource, NamespaceAdoptionRule from doctypes/namespace/type.ts have no doc in `astryx docs authoring`';
525
+ const SURFACE_FIX =
526
+ 'Put a self-doc beside each module whose types @astryxdesign/cli/authoring exports and list it in AUTHORING_SELF_DOCS; export what each listed self-doc documents, or remove that self-doc.';
527
+
528
+ /** A copy of this repo's authoring tree, to break one piece of at a time. */
529
+ function copyAuthoring() {
530
+ const root = fs.mkdtempSync(
531
+ path.join(process.cwd(), '.astryx-doctor-authoring-'),
532
+ );
533
+ tmpDirs.push(root);
534
+ fs.cpSync(AUTHORING_ROOT, root, {
535
+ recursive: true,
536
+ filter: src => !src.endsWith('.test.mjs'),
537
+ });
538
+ return root;
539
+ }
540
+
541
+ /** @param {string} source */
542
+ const without = source => AUTHORING_SELF_DOCS.filter(s => s !== source);
543
+
544
+ /** @param {string} text */
545
+ const escape = text => text.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
546
+
547
+ it(
548
+ 'passes on this repo and prints exactly what it printed before',
549
+ async () => {
550
+ expect(await checkAuthoringDocs()).toEqual({
551
+ id: 'authoring-docs',
552
+ label: 'Authoring docs',
553
+ status: 'pass',
554
+ message: `All ${AUTHORING_SELF_DOCS.length} authoring schemas are readable in \`astryx docs authoring\`.`,
555
+ });
556
+ },
557
+ SLOW,
558
+ );
559
+
560
+ it(
561
+ 'passes on an unchanged copy of the tree, and writes nothing to it',
562
+ async () => {
563
+ const root = copyAuthoring();
564
+ const before = snapshot(root);
565
+ expect(await checkAuthoringDocs(undefined, {root})).toMatchObject({
566
+ status: 'pass',
567
+ });
568
+ expect(snapshot(root)).toEqual(before);
569
+ },
570
+ SLOW,
571
+ );
572
+
573
+ it(
574
+ 'fails when the NamespaceDoc self-doc is left out of AUTHORING_SELF_DOCS',
575
+ async () => {
576
+ const root = copyAuthoring();
577
+ const c = await checkAuthoringDocs(undefined, {
578
+ root,
579
+ sources: without(NAMESPACE),
580
+ });
581
+ expect(c.status).toBe('fail');
582
+ expect(c.message).toBe(
583
+ `2 problems: ${NAMESPACE} is not in \`astryx docs authoring\`; ${NAMESPACE_TYPES}: ${NAMESPACE} is not listed in AUTHORING_SELF_DOCS`,
584
+ );
585
+ },
586
+ SLOW,
587
+ );
588
+
589
+ it(
590
+ "fails when the topic's index no longer exposes the NamespaceDoc section",
591
+ async () => {
592
+ const index = await docs('authoring', undefined, {index: true});
593
+ const topicKeys = new Set(
594
+ index.data.sections
595
+ .map((/** @type {any} */ s) => s.id)
596
+ .filter((/** @type {string} */ id) => id !== 'namespace-doc'),
597
+ );
598
+ const c = await checkAuthoringDocs(undefined, {topicKeys});
599
+ expect(c).toMatchObject({
600
+ status: 'fail',
601
+ message: `${NAMESPACE_TYPES}: ${NAMESPACE} renders section "namespace-doc", which the topic's index does not list`,
602
+ fix: SURFACE_FIX,
603
+ });
604
+ },
605
+ SLOW,
606
+ );
607
+
608
+ it(
609
+ 'fails when @astryxdesign/cli/authoring stops exporting NamespaceDoc',
610
+ async () => {
611
+ const root = copyAuthoring();
612
+ const index = path.join(root, 'index.d.ts');
613
+ const before = fs.readFileSync(index, 'utf8');
614
+ const after = before.replace(
615
+ /^export type \{NamespaceDoc\} from .*\n/m,
616
+ '',
617
+ );
618
+ expect(after).not.toBe(before);
619
+ fs.writeFileSync(index, after);
620
+ expect(await checkAuthoringDocs(undefined, {root})).toMatchObject({
621
+ status: 'fail',
622
+ message: `${NAMESPACE} documents NamespaceDoc, which @astryxdesign/cli/authoring does not export`,
623
+ fix: SURFACE_FIX,
624
+ });
625
+ },
626
+ SLOW,
627
+ );
628
+
629
+ it.each([
630
+ [
631
+ 'the graph-fields doc',
632
+ 'doctypes/base/graph-fields.doc.mjs',
633
+ 'doctypes/base/type.ts',
634
+ [
635
+ 'AuthoredDocKind',
636
+ 'DocAudience',
637
+ 'DocPlacement',
638
+ 'AuthoredDocGraphFields',
639
+ ],
640
+ ],
641
+ [
642
+ 'the identity doc',
643
+ 'identity/identity.doc.mjs',
644
+ 'identity/type.ts',
645
+ [
646
+ 'ProviderId',
647
+ 'ArtifactId',
648
+ 'DocId',
649
+ 'ProviderInstance',
650
+ 'AuthoredDocEntry',
651
+ ],
652
+ ],
653
+ [
654
+ 'the semantic-block doc',
655
+ 'doctypes/reference/reference.doc.mjs',
656
+ 'doctypes/reference/type.ts',
657
+ [
658
+ 'WorkflowStep',
659
+ 'WorkflowDocBlock',
660
+ 'CollectionDocBlock',
661
+ 'ReferenceDocBlock',
662
+ ],
663
+ ],
664
+ ])(
665
+ 'fails when %s is removed, which the self-doc audit alone passes',
666
+ async (_what, source, module, names) => {
667
+ const root = copyAuthoring();
668
+ fs.rmSync(path.join(root, source));
669
+ const sources = without(source);
670
+ expect(await auditAuthoringSelfDocs({root, sources})).toEqual({
671
+ sections: sources.length,
672
+ unreachable: [],
673
+ failed: [],
674
+ oversized: [],
675
+ });
676
+ const c = await checkAuthoringDocs(undefined, {root, sources});
677
+ expect(c.status).toBe('fail');
678
+ expect(c.fix).toBe(SURFACE_FIX);
679
+ expect(c.message).toMatch(
680
+ new RegExp(
681
+ `^[A-Za-z, ]+ from ${escape(module)} have no doc in \`astryx docs authoring\`: no self-doc sits beside ${escape(module)}$`,
682
+ ),
683
+ );
684
+ for (const name of names) {
685
+ expect(c.message).toMatch(new RegExp(`(^|, )${name}(,| from)`));
686
+ }
687
+ },
688
+ SLOW,
689
+ );
690
+
691
+ it(
692
+ 'keeps the message and fix of a failure only the self-doc audit reports',
693
+ async () => {
694
+ const root = copyAuthoring();
695
+ fs.writeFileSync(
696
+ path.join(root, 'identity', 'broken.doc.mjs'),
697
+ 'export const doc = {;\n',
698
+ );
699
+ const c = await checkAuthoringDocs(undefined, {
700
+ root,
701
+ sources: [...AUTHORING_SELF_DOCS, 'identity/broken.doc.mjs'],
702
+ });
703
+ expect(c.status).toBe('fail');
704
+ expect(c.message).toMatch(/^identity\/broken\.doc\.mjs failed to load: /);
705
+ expect(c.fix).toBe(
706
+ 'List every authoring self-doc in AUTHORING_SELF_DOCS, fix the one that fails to load, and split a section that is too large.',
707
+ );
708
+ },
709
+ SLOW,
710
+ );
711
+
712
+ it(
713
+ 'reports a topic it cannot read rather than skipping the comparison',
714
+ async () => {
715
+ const spy = vi
716
+ .spyOn(DocsCatalog, 'fromBuiltins')
717
+ .mockImplementationOnce(() => {
718
+ throw new Error('no built-in docs');
719
+ });
720
+ try {
721
+ expect(await checkAuthoringDocs()).toMatchObject({
722
+ status: 'fail',
723
+ message:
724
+ '`astryx docs authoring` could not be read: no built-in docs',
725
+ fix: SURFACE_FIX,
726
+ });
727
+ } finally {
728
+ spy.mockRestore();
729
+ }
730
+ },
731
+ SLOW,
732
+ );
733
+
734
+ it(
735
+ 'fails rather than passes when the surface exports no types at all',
736
+ async () => {
737
+ const root = copyAuthoring();
738
+ fs.writeFileSync(
739
+ path.join(root, 'index.d.ts'),
740
+ "export {parseDoc} from './doctypes/parse.mjs';\n",
741
+ );
742
+ const c = await checkAuthoringDocs(undefined, {root});
743
+ expect(c.status).toBe('fail');
744
+ expect(c.message).toContain(
745
+ '@astryxdesign/cli/authoring exports no types, so nothing was compared with `astryx docs authoring`',
746
+ );
747
+ },
748
+ SLOW,
749
+ );
750
+ });
751
+
752
+ /**
753
+ * Every file under `root` with its contents.
754
+ * @param {string} root
755
+ * @returns {Record<string, string>}
756
+ */
757
+ function snapshot(root) {
758
+ /** @type {Record<string, string>} */
759
+ const out = {};
760
+ /** @param {string} dir */
761
+ const walk = dir => {
762
+ for (const entry of fs.readdirSync(dir, {withFileTypes: true})) {
763
+ const full = path.join(dir, entry.name);
764
+ if (entry.isDirectory()) walk(full);
765
+ else out[path.relative(root, full)] = fs.readFileSync(full, 'utf8');
766
+ }
767
+ };
768
+ walk(root);
769
+ return out;
770
+ }
771
+
447
772
  describe('doctor docs checks', () => {
448
773
  it('runs both docs checks and they pass on the repo', async () => {
449
774
  const r = await doctor({cwd});
@@ -10,6 +10,7 @@ export const doc = {
10
10
  type: 'function',
11
11
  kind: 'api',
12
12
  name: 'gapReport',
13
+ namespace: 'cli/api',
13
14
  displayName: 'gapReport()',
14
15
  summary: 'Route a design-system gap through the fan-out handler composition.',
15
16
  description:
@@ -15,7 +15,10 @@
15
15
  */
16
16
 
17
17
  import {findCoreDir} from '../../foundation/fs/paths.mjs';
18
- import {findHookDoc, getAllHookNames} from '../../foundation/discovery/hook-discovery.mjs';
18
+ import {
19
+ findHookDoc,
20
+ getAllHookNames,
21
+ } from '../../foundation/discovery/hook-discovery.mjs';
19
22
  import {loadDocs} from '../../foundation/discovery/component-loader.mjs';
20
23
  import {levenshteinDistance} from '../../foundation/text/string-utils.mjs';
21
24
  import {AstryxError} from '../error.mjs';
@@ -30,7 +33,11 @@ import {ERROR_CODES} from '../../foundation/response/error-codes.mjs';
30
33
  export function resolveCoreDir(cwd) {
31
34
  const coreDir = findCoreDir(cwd);
32
35
  if (!coreDir) {
33
- throw new AstryxError('Could not find @astryxdesign/core package', undefined, ERROR_CODES.ERR_CORE_NOT_FOUND);
36
+ throw new AstryxError(
37
+ 'Could not find @astryxdesign/core package',
38
+ undefined,
39
+ ERROR_CODES.ERR_CORE_NOT_FOUND,
40
+ );
34
41
  }
35
42
  return coreDir;
36
43
  }
@@ -44,7 +51,11 @@ export function resolveCoreDir(cwd) {
44
51
  * @param {{zh?: boolean, lang?: string|null}} [opts]
45
52
  * @returns {Promise<import('./hook.type.mjs').HookDoc>}
46
53
  */
47
- export async function resolveHookDoc(coreDir, name, {zh = false, lang = null} = {}) {
54
+ export async function resolveHookDoc(
55
+ coreDir,
56
+ name,
57
+ {zh = false, lang = null} = {},
58
+ ) {
48
59
  const docPath = findHookDoc(coreDir, name);
49
60
 
50
61
  if (!docPath) {
@@ -59,7 +70,10 @@ export async function resolveHookDoc(coreDir, name, {zh = false, lang = null} =
59
70
  .filter(m => m.distance <= 5)
60
71
  .sort((a, b) => a.distance - b.distance)
61
72
  .slice(0, 5)
62
- .map(m => ({name: m.name, reason: `similar name (distance ${m.distance})`}));
73
+ .map(m => ({
74
+ name: m.name,
75
+ reason: `similar name (distance ${m.distance})`,
76
+ }));
63
77
 
64
78
  throw new AstryxError(
65
79
  `No hook named "${name}"`,
@@ -68,5 +82,5 @@ export async function resolveHookDoc(coreDir, name, {zh = false, lang = null} =
68
82
  );
69
83
  }
70
84
 
71
- return loadDocs(docPath, /** @type {{zh?: boolean, dense?: boolean, lang?: string}} */ ({zh, lang}));
85
+ return loadDocs(docPath, {zh, lang, root: 'hooks'});
72
86
  }
@@ -11,6 +11,7 @@ export const doc = {
11
11
  type: 'function',
12
12
  kind: 'api',
13
13
  name: 'hook',
14
+ namespace: 'cli/api',
14
15
  displayName: 'hook()',
15
16
  summary:
16
17
  'Resolve a hook by name, or list the catalog, with an optional params-only slice.',
@@ -10,7 +10,7 @@
10
10
  * @param {string|null} [options.lang]
11
11
  * @returns {Promise<import('../hook.type.mjs').HookListResponse>}
12
12
  */
13
- export function list({ cwd, category, detail, zh, lang }?: {
13
+ export function list({ cwd, category, detail, zh, lang, }?: {
14
14
  cwd?: string | undefined;
15
15
  category?: string | undefined;
16
16
  detail?: "compact" | "full" | "names" | "brief" | undefined;
@@ -13,7 +13,10 @@
13
13
  * @position api/hook/list/list.mjs — dispatched from ../hook.mjs
14
14
  */
15
15
 
16
- import {discoverHooks, findHookDoc} from '../../../foundation/discovery/hook-discovery.mjs';
16
+ import {
17
+ discoverHooks,
18
+ findHookDoc,
19
+ } from '../../../foundation/discovery/hook-discovery.mjs';
17
20
  import {loadDocs} from '../../../foundation/discovery/component-loader.mjs';
18
21
  import {AstryxError} from '../../error.mjs';
19
22
  import {ERROR_CODES} from '../../../foundation/response/error-codes.mjs';
@@ -28,7 +31,13 @@ import {resolveCoreDir} from '../_adapter.mjs';
28
31
  * @param {string|null} [options.lang]
29
32
  * @returns {Promise<import('../hook.type.mjs').HookListResponse>}
30
33
  */
31
- export async function list({cwd = process.cwd(), category, detail = 'names', zh = false, lang = null} = {}) {
34
+ export async function list({
35
+ cwd = process.cwd(),
36
+ category,
37
+ detail = 'names',
38
+ zh = false,
39
+ lang = null,
40
+ } = {}) {
32
41
  const coreDir = resolveCoreDir(cwd);
33
42
  const hooks = discoverHooks(coreDir);
34
43
 
@@ -51,20 +60,31 @@ export async function list({cwd = process.cwd(), category, detail = 'names', zh
51
60
  const docPath = findHookDoc(coreDir, hookName);
52
61
  if (docPath) {
53
62
  try {
54
- const docs = await loadDocs(docPath, /** @type {{zh?: boolean, dense?: boolean, lang?: string}} */ ({zh, lang}));
63
+ const docs = await loadDocs(docPath, {zh, lang, root: 'hooks'});
55
64
  entries.push({
56
65
  name: hookName,
57
66
  description: docs.usage?.description || '',
58
67
  import: docs.importPath || '@astryxdesign/core/hooks',
59
68
  });
60
69
  } catch {
61
- entries.push({name: hookName, description: '', import: '@astryxdesign/core/hooks'});
70
+ entries.push({
71
+ name: hookName,
72
+ description: '',
73
+ import: '@astryxdesign/core/hooks',
74
+ });
62
75
  }
63
76
  } else {
64
- entries.push({name: hookName, description: '', import: '@astryxdesign/core/hooks'});
77
+ entries.push({
78
+ name: hookName,
79
+ description: '',
80
+ import: '@astryxdesign/core/hooks',
81
+ });
65
82
  }
66
83
  }
67
- return {type: 'hook.list', data: {detail: 'compact', components: {[match[0]]: entries}}};
84
+ return {
85
+ type: 'hook.list',
86
+ data: {detail: 'compact', components: {[match[0]]: entries}},
87
+ };
68
88
  }
69
89
 
70
90
  if (detail === 'full') {
@@ -74,19 +94,33 @@ export async function list({cwd = process.cwd(), category, detail = 'names', zh
74
94
  const docPath = findHookDoc(coreDir, hookName);
75
95
  if (docPath) {
76
96
  try {
77
- entries.push(await loadDocs(docPath, /** @type {{zh?: boolean, dense?: boolean, lang?: string}} */ ({zh, lang})));
97
+ entries.push(await loadDocs(docPath, {zh, lang, root: 'hooks'}));
78
98
  } catch {
79
- entries.push(/** @type {import('../hook.type.mjs').HookDoc} */ ({name: hookName}));
99
+ entries.push(
100
+ /** @type {import('../hook.type.mjs').HookDoc} */ ({
101
+ name: hookName,
102
+ }),
103
+ );
80
104
  }
81
105
  } else {
82
- entries.push(/** @type {import('../hook.type.mjs').HookDoc} */ ({name: hookName}));
106
+ entries.push(
107
+ /** @type {import('../hook.type.mjs').HookDoc} */ ({
108
+ name: hookName,
109
+ }),
110
+ );
83
111
  }
84
112
  }
85
- return {type: 'hook.list', data: {detail: 'full', components: {[match[0]]: entries}}};
113
+ return {
114
+ type: 'hook.list',
115
+ data: {detail: 'full', components: {[match[0]]: entries}},
116
+ };
86
117
  }
87
118
 
88
119
  // Default: names only
89
- return {type: 'hook.list', data: {detail: 'names', components: {[match[0]]: match[1]}}};
120
+ return {
121
+ type: 'hook.list',
122
+ data: {detail: 'names', components: {[match[0]]: match[1]}},
123
+ };
90
124
  }
91
125
 
92
126
  // All hooks
@@ -99,17 +133,25 @@ export async function list({cwd = process.cwd(), category, detail = 'names', zh
99
133
  const docPath = findHookDoc(coreDir, hookName);
100
134
  if (docPath) {
101
135
  try {
102
- const docs = await loadDocs(docPath, /** @type {{zh?: boolean, dense?: boolean, lang?: string}} */ ({zh, lang}));
136
+ const docs = await loadDocs(docPath, {zh, lang, root: 'hooks'});
103
137
  result[cat].push({
104
138
  name: hookName,
105
139
  description: docs.usage?.description || '',
106
140
  import: docs.importPath || '@astryxdesign/core/hooks',
107
141
  });
108
142
  } catch {
109
- result[cat].push({name: hookName, description: '', import: '@astryxdesign/core/hooks'});
143
+ result[cat].push({
144
+ name: hookName,
145
+ description: '',
146
+ import: '@astryxdesign/core/hooks',
147
+ });
110
148
  }
111
149
  } else {
112
- result[cat].push({name: hookName, description: '', import: '@astryxdesign/core/hooks'});
150
+ result[cat].push({
151
+ name: hookName,
152
+ description: '',
153
+ import: '@astryxdesign/core/hooks',
154
+ });
113
155
  }
114
156
  }
115
157
  }
@@ -125,12 +167,22 @@ export async function list({cwd = process.cwd(), category, detail = 'names', zh
125
167
  const docPath = findHookDoc(coreDir, hookName);
126
168
  if (docPath) {
127
169
  try {
128
- result[cat].push(await loadDocs(docPath, /** @type {{zh?: boolean, dense?: boolean, lang?: string}} */ ({zh, lang})));
170
+ result[cat].push(
171
+ await loadDocs(docPath, {zh, lang, root: 'hooks'}),
172
+ );
129
173
  } catch {
130
- result[cat].push(/** @type {import('../hook.type.mjs').HookDoc} */ ({name: hookName}));
174
+ result[cat].push(
175
+ /** @type {import('../hook.type.mjs').HookDoc} */ ({
176
+ name: hookName,
177
+ }),
178
+ );
131
179
  }
132
180
  } else {
133
- result[cat].push(/** @type {import('../hook.type.mjs').HookDoc} */ ({name: hookName}));
181
+ result[cat].push(
182
+ /** @type {import('../hook.type.mjs').HookDoc} */ ({
183
+ name: hookName,
184
+ }),
185
+ );
134
186
  }
135
187
  }
136
188
  }
@@ -11,6 +11,7 @@ export const doc = {
11
11
  type: 'function',
12
12
  kind: 'api',
13
13
  name: 'init',
14
+ namespace: 'cli/api',
14
15
  displayName: 'init()',
15
16
  summary:
16
17
  'Non-interactive project setup: install agent docs and point at the theme + build workflows.',
@@ -5,8 +5,7 @@
5
5
  * contribution into an integration package.
6
6
  *
7
7
  * Dispatches component, doc, template, codemod, and agent-doc. Theme
8
- * delegates to the existing `integrationAddTheme` (not modified here).
9
- * Every root-based kind shares: first add creates the manifest; one shared
8
+ * delegates to the dedicated same-stem descriptor writer. Every root-based kind shares: first add creates the manifest; one shared
10
9
  * writer; never creates `files`/`exports`; atomic staged writes; no clobber;
11
10
  * dry-run receipt predicts real run; post-write verify through the real
12
11
  * discovery/parser seam.
@@ -263,7 +262,7 @@ async function addComponent(name, options) {
263
262
  const sourcePath = projectPath(path.relative(packageDir, sourceFile));
264
263
  const extensionlessPath = sourcePath.replace(/\.tsx?$/u, '');
265
264
  const importSpecifier = `${owner}/${extensionlessPath}`;
266
- const docContents = `export default {\n type: 'component',\n name: '${name}',\n import: ${JSON.stringify(importSpecifier)},\n description: '${name} component.',\n props: [],\n};\n`;
265
+ const docContents = `/** @type {import('@astryxdesign/cli/authoring').ComponentDoc} */\nexport default {\n type: 'component',\n name: '${name}',\n displayName: '${name}',\n import: ${JSON.stringify(importSpecifier)},\n usage: {description: '${name} component.'},\n props: [],\n};\n`;
267
266
  const sourceContents = `export function ${name}() {\n return <div>${name}</div>;\n}\n`;
268
267
 
269
268
  /** @type {import('./add-helpers.mjs').WritePlan[]} */
@@ -438,7 +437,7 @@ async function addDoc(name, options) {
438
437
  : options.extends
439
438
  ? `\n extends: '${options.extends}',`
440
439
  : '';
441
- const docContents = `export default {\n type: 'generic',\n name: '${name}',\n title: '${title}',\n description: '${title} documentation.',${relationship}\n sections: [\n {\n title: 'Overview',\n content: [\n { type: 'prose', text: '${title} documentation.' },\n ],\n },\n ],\n};\n`;
440
+ const docContents = `/** @type {import('@astryxdesign/cli/authoring').ReferenceDoc} */\nexport default {\n type: 'generic',\n name: '${name}',\n title: '${title}',\n description: '${title} documentation.',${relationship}\n sections: [\n {\n title: 'Overview',\n content: [\n { type: 'prose', text: '${title} documentation.' },\n ],\n },\n ],\n};\n`;
442
441
 
443
442
  /** @type {import('./add-helpers.mjs').WritePlan[]} */
444
443
  const plans = [{path: docFile, contents: docContents, createOnly: true}];
@@ -565,8 +564,8 @@ async function addTemplate(name, options) {
565
564
  throw error;
566
565
  }
567
566
 
568
- const specFile = assertWithin(`${name}.template.mjs`, root, {
569
- label: 'template spec',
567
+ const specFile = assertWithin(`${name}.doc.mjs`, root, {
568
+ label: 'template descriptor',
570
569
  });
571
570
  const sourceFile = assertWithin(`${name}.tsx`, root, {
572
571
  label: 'template source',
@@ -589,7 +588,8 @@ async function addTemplate(name, options) {
589
588
  const pascalName = kebabToPascal(name);
590
589
  const sourcePath = projectPath(path.relative(packageDir, sourceFile));
591
590
  const extensionlessPath = sourcePath.replace(/\.tsx?$/u, '');
592
- const specContents = `export default {\n type: '${templateType}',\n name: '${name}',\n description: '${kebabToTitle(name)} template.',\n};\n`;
591
+ const blockFields = templateType === 'block' ? '\n aspectRatio: 1,' : '';
592
+ const specContents = `/** @type {import('@astryxdesign/cli/authoring').TemplateDoc} */\nexport default {\n type: '${templateType}',\n name: '${name}',\n displayName: '${kebabToTitle(name)}',\n description: '${kebabToTitle(name)} template.',${blockFields}\n};\n`;
593
593
  const sourceContents = `export default function ${pascalName}() {\n return <div>${kebabToTitle(name)}</div>;\n}\n`;
594
594
 
595
595
  /** @type {import('./add-helpers.mjs').WritePlan[]} */