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

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 (276) hide show
  1. package/README.md +22 -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/detail/section/section.mjs +9 -15
  9. package/api/docs/detail/section/section.test.mjs +15 -6
  10. package/api/docs/docs.doc.mjs +1 -0
  11. package/api/docs/list/list.mjs +3 -7
  12. package/api/doctor/doctor.d.mts +25 -3
  13. package/api/doctor/doctor.doc.mjs +1 -0
  14. package/api/doctor/doctor.mjs +186 -14
  15. package/api/doctor/doctor.test.mjs +332 -7
  16. package/api/gap-report/gap-report.doc.mjs +1 -0
  17. package/api/hook/_adapter.mjs +19 -5
  18. package/api/hook/hook.doc.mjs +1 -0
  19. package/api/hook/list/list.d.mts +1 -1
  20. package/api/hook/list/list.mjs +69 -17
  21. package/api/init/init.doc.mjs +1 -0
  22. package/api/integration/add-contribution.mjs +7 -7
  23. package/api/integration/add-contribution.test.mjs +37 -3
  24. package/api/integration/add-theme.mjs +34 -64
  25. package/api/integration/add-theme.test.mjs +105 -21
  26. package/api/integration/authoring-checks.test.mjs +8 -4
  27. package/api/integration/integration-block-exports.test.mjs +10 -6
  28. package/api/integration/integrationAdd.doc.mjs +1 -0
  29. package/api/integration/integrationAddAgentDoc.doc.mjs +1 -0
  30. package/api/integration/integrationAddCodemod.doc.mjs +1 -0
  31. package/api/integration/integrationAddComponent.doc.mjs +1 -0
  32. package/api/integration/integrationAddDoc.doc.mjs +1 -0
  33. package/api/integration/integrationAddTemplate.doc.mjs +1 -0
  34. package/api/integration/integrationAddTheme.doc.mjs +6 -5
  35. package/api/integration/integrationComponentConflicts.doc.mjs +1 -0
  36. package/api/integration/integrationDocConflicts.doc.mjs +1 -0
  37. package/api/integration/integrationPackCheck.doc.mjs +1 -0
  38. package/api/integration/integrationTemplateConflicts.doc.mjs +1 -0
  39. package/api/integration/pack-check.test.mjs +17 -47
  40. package/api/integration/summarizeIssues.doc.mjs +1 -0
  41. package/api/integration/validate-integration-fixes.test.mjs +1389 -0
  42. package/api/integration/validate-integration.mjs +50 -102
  43. package/api/integration/validate-integration.test.mjs +85 -23
  44. package/api/integration/validate-unread-theme-folders.test.mjs +110 -0
  45. package/api/integration/validateIntegration.doc.mjs +1 -0
  46. package/api/json/assertResponse.doc.mjs +1 -0
  47. package/api/json/isError.doc.mjs +1 -0
  48. package/api/json/parseResponse.doc.mjs +1 -0
  49. package/api/layout/layoutCheck.doc.mjs +1 -0
  50. package/api/layout/layoutExpand.doc.mjs +1 -0
  51. package/api/layout/layoutGrammar.doc.mjs +1 -0
  52. package/api/search/search.doc.mjs +1 -0
  53. package/api/search/search.mjs +16 -6
  54. package/api/swizzle/swizzle.doc.mjs +1 -0
  55. package/api/template/template-suffix.test.mjs +41 -21
  56. package/api/template/template.doc.mjs +1 -0
  57. package/api/theme/_adapter.d.mts +2 -3
  58. package/api/theme/_adapter.mjs +4 -5
  59. package/api/theme/add/add.binary.test.mjs +10 -17
  60. package/api/theme/add/add.test.mjs +14 -1
  61. package/api/theme/generateTonalPalette.doc.mjs +1 -0
  62. package/api/theme/integration-themes.test.mjs +39 -28
  63. package/api/theme/list/list.test.mjs +19 -20
  64. package/api/theme/listThemes.doc.mjs +6 -5
  65. package/api/theme/themeAdd.doc.mjs +4 -3
  66. package/api/theme/themeBuild.doc.mjs +1 -0
  67. package/api/theme/themeList.doc.mjs +6 -3
  68. package/api/theme/themeListAvailable.doc.mjs +5 -3
  69. package/api/theme/themePaletteGenerate.doc.mjs +1 -0
  70. package/api/theme/themeTargets.doc.mjs +1 -0
  71. package/api/theme/themeTemplate.doc.mjs +1 -0
  72. package/api/upgrade/_adapter.d.mts +32 -5
  73. package/api/upgrade/_adapter.mjs +97 -69
  74. package/api/upgrade/provider-agreement.test.mjs +152 -0
  75. package/api/upgrade/run/run.mjs +356 -59
  76. package/api/upgrade/upgrade.doc.mjs +6 -1
  77. package/api/upgrade/upgrade.type.d.mts +34 -0
  78. package/api/upgrade/upgrade.type.mjs +15 -0
  79. package/assets/codemods/__tests__/runner.test.mjs +330 -8
  80. package/assets/codemods/integration-discovery.mjs +8 -2
  81. package/assets/codemods/integration-discovery.test.mjs +15 -0
  82. package/assets/codemods/integration-runner.mjs +56 -4
  83. package/assets/codemods/integration-runner.protection.test.mjs +153 -0
  84. package/assets/codemods/run-codemod.mjs +177 -34
  85. package/assets/codemods/runner.mjs +350 -102
  86. package/assets/codemods/transforms/next/__tests__/migrate-native-picker-to-presentation.test.mjs +63 -0
  87. package/assets/codemods/transforms/next/__tests__/migrate-theme-catalog-to-descriptors.test.mjs +220 -0
  88. package/assets/codemods/transforms/next/index.mjs +19 -1
  89. package/assets/codemods/transforms/next/migrate-native-picker-to-presentation.mjs +148 -0
  90. package/assets/codemods/transforms/next/migrate-theme-catalog-to-descriptors.mjs +141 -0
  91. package/assets/docs/cli-integrations.doc.mjs +30 -30
  92. package/assets/docs/cli.doc.mjs +15 -0
  93. package/assets/docs/theme.doc.mjs +1 -1
  94. package/assets/templates/blocks/components/DateInput/DateInputDateRange.tsx +1 -1
  95. package/assets/templates/blocks/components/Item/ItemDocumentTabs.doc.mjs +14 -0
  96. package/assets/templates/blocks/components/Item/ItemDocumentTabs.tsx +100 -0
  97. package/assets/templates/blocks/components/TimeInput/TimeInputConstrained.tsx +1 -0
  98. package/assets/templates/themes/butter/butterTheme.doc.mjs +11 -0
  99. package/assets/templates/themes/chocolate/chocolateTheme.doc.mjs +11 -0
  100. package/assets/templates/themes/gothic/gothicTheme.doc.mjs +11 -0
  101. package/assets/templates/themes/matcha/matchaTheme.doc.mjs +11 -0
  102. package/assets/templates/themes/neutral/neutralTheme.doc.mjs +11 -0
  103. package/assets/templates/themes/stone/stoneTheme.doc.mjs +11 -0
  104. package/assets/templates/themes/y2k/y2kTheme.doc.mjs +11 -0
  105. package/authoring/codemod/codemod.doc.mjs +1 -1
  106. package/authoring/codemod/type.ts +12 -0
  107. package/authoring/config/config.doc.mjs +1 -1
  108. package/authoring/debug/debug.doc.d.mts +11 -0
  109. package/authoring/debug/debug.doc.mjs +182 -0
  110. package/authoring/doctypes/_schema.d.mts +71 -141
  111. package/authoring/doctypes/_schema.mjs +29 -2
  112. package/authoring/doctypes/base/graph-fields.doc.mjs +1 -1
  113. package/authoring/doctypes/command/command.doc.mjs +1 -1
  114. package/authoring/doctypes/command/type.ts +1 -1
  115. package/authoring/doctypes/component/type.ts +2 -2
  116. package/authoring/doctypes/doctypes-new.test.mjs +48 -6
  117. package/authoring/doctypes/enum/enum.doc.mjs +1 -1
  118. package/authoring/doctypes/enum/type.ts +1 -1
  119. package/authoring/doctypes/function/function.doc.mjs +1 -1
  120. package/authoring/doctypes/function/type.ts +1 -1
  121. package/authoring/doctypes/hook/type.ts +2 -2
  122. package/authoring/doctypes/load-contract.test.mjs +3 -2
  123. package/authoring/doctypes/namespace/namespace.doc.mjs +2 -2
  124. package/authoring/doctypes/namespace/parse.test.mjs +23 -25
  125. package/authoring/doctypes/namespace/type.ts +5 -2
  126. package/authoring/doctypes/parse.d.mts +4 -2
  127. package/authoring/doctypes/parse.mjs +10 -5
  128. package/authoring/doctypes/reference/reference.doc.mjs +6 -4
  129. package/authoring/doctypes/reference/type.ts +9 -10
  130. package/authoring/doctypes/schema/schema.doc.mjs +1 -1
  131. package/authoring/doctypes/schema/type.ts +1 -2
  132. package/authoring/doctypes/template/template.doc.mjs +3 -3
  133. package/authoring/doctypes/theme/parse.d.mts +35 -0
  134. package/authoring/doctypes/theme/parse.mjs +76 -0
  135. package/authoring/doctypes/theme/theme.doc.d.mts +9 -0
  136. package/authoring/doctypes/theme/theme.doc.mjs +79 -0
  137. package/authoring/doctypes/theme/type.ts +42 -0
  138. package/authoring/doctypes/types.ts +2 -1
  139. package/authoring/gap-report/gap-report.doc.d.mts +12 -0
  140. package/authoring/gap-report/gap-report.doc.mjs +183 -0
  141. package/authoring/index.d.mts +1 -0
  142. package/authoring/index.d.ts +7 -4
  143. package/authoring/index.mjs +2 -1
  144. package/authoring/integration/integration.doc.mjs +2 -2
  145. package/authoring/integration/type.ts +3 -9
  146. package/clients/cli/__tests__/cliManifest.test.ts +27 -29
  147. package/clients/cli/commands/blog.doc.mjs +1 -1
  148. package/clients/cli/commands/build-theme.adaptations.test.mjs +100 -1
  149. package/clients/cli/commands/build-theme.mjs +10 -44
  150. package/clients/cli/commands/build.doc.mjs +1 -1
  151. package/clients/cli/commands/component.doc.mjs +1 -1
  152. package/clients/cli/commands/discover.doc.mjs +1 -1
  153. package/clients/cli/commands/docs.doc.mjs +1 -1
  154. package/clients/cli/commands/docs.mjs +15 -58
  155. package/clients/cli/commands/docs.test.mjs +11 -14
  156. package/clients/cli/commands/doctor-integration-components.doc.mjs +1 -1
  157. package/clients/cli/commands/doctor-integration-docs.doc.mjs +1 -1
  158. package/clients/cli/commands/doctor-integration-templates.doc.mjs +1 -1
  159. package/clients/cli/commands/doctor-integration-validate.doc.mjs +1 -1
  160. package/clients/cli/commands/doctor-integration.doc.mjs +1 -1
  161. package/clients/cli/commands/doctor-integration.test.mjs +15 -7
  162. package/clients/cli/commands/doctor.doc.mjs +1 -1
  163. package/clients/cli/commands/gap-report.doc.mjs +1 -1
  164. package/clients/cli/commands/hook.doc.mjs +1 -1
  165. package/clients/cli/commands/init.doc.mjs +1 -1
  166. package/clients/cli/commands/integration-add.controls.test.mjs +1 -1
  167. package/clients/cli/commands/integration-add.doc.mjs +1 -1
  168. package/clients/cli/commands/integration-pack.doc.mjs +1 -1
  169. package/clients/cli/commands/integration-real-world.test.mjs +3 -9
  170. package/clients/cli/commands/integration.doc.mjs +1 -1
  171. package/clients/cli/commands/layout-check.doc.mjs +1 -1
  172. package/clients/cli/commands/layout-expand.doc.mjs +1 -1
  173. package/clients/cli/commands/layout-grammar.doc.mjs +1 -1
  174. package/clients/cli/commands/layout.doc.mjs +1 -1
  175. package/clients/cli/commands/manifest.doc.mjs +1 -1
  176. package/clients/cli/commands/search.doc.mjs +1 -1
  177. package/clients/cli/commands/swizzle.doc.mjs +1 -1
  178. package/clients/cli/commands/template.doc.mjs +1 -1
  179. package/clients/cli/commands/text-json-parity.test.mjs +718 -0
  180. package/clients/cli/commands/theme-add.doc.mjs +2 -2
  181. package/clients/cli/commands/theme-build.doc.mjs +1 -1
  182. package/clients/cli/commands/theme-list.doc.mjs +2 -2
  183. package/clients/cli/commands/theme-palette-generate.doc.mjs +3 -3
  184. package/clients/cli/commands/theme-palette.doc.mjs +1 -1
  185. package/clients/cli/commands/theme-targets.behavior.test.mjs +4 -3
  186. package/clients/cli/commands/theme-targets.doc.mjs +1 -1
  187. package/clients/cli/commands/theme-template.doc.mjs +1 -1
  188. package/clients/cli/commands/theme.doc.mjs +1 -1
  189. package/clients/cli/commands/upgrade.doc.mjs +3 -2
  190. package/clients/cli/commands/upgrade.file-protection.test.mjs +228 -0
  191. package/clients/cli/commands/upgrade.mjs +29 -7
  192. package/clients/cli/formatters/index.mjs +2 -0
  193. package/clients/cli/formatters/index.test.mjs +6 -0
  194. package/clients/cli/lib/hook-format.mjs +14 -5
  195. package/foundation/config/project-themes.test.mjs +11 -19
  196. package/foundation/config/project.d.mts +8 -0
  197. package/foundation/config/project.mjs +50 -21
  198. package/foundation/config/project.test.mjs +3 -16
  199. package/foundation/discovery/authoring-self-docs.d.mts +6 -0
  200. package/foundation/discovery/authoring-self-docs.mjs +17 -7
  201. package/foundation/discovery/authoring-self-docs.test.mjs +3 -2
  202. package/foundation/discovery/authoring-surface.d.mts +74 -0
  203. package/foundation/discovery/authoring-surface.mjs +525 -0
  204. package/foundation/discovery/authoring-surface.test.mjs +392 -0
  205. package/foundation/discovery/cli-self-docs.d.mts +113 -0
  206. package/foundation/discovery/cli-self-docs.mjs +514 -0
  207. package/foundation/discovery/cli-self-docs.test.mjs +437 -0
  208. package/foundation/discovery/component-loader.d.mts +35 -38
  209. package/foundation/discovery/component-loader.mjs +53 -222
  210. package/foundation/discovery/docs-discovery.d.mts +3 -2
  211. package/foundation/discovery/docs-discovery.mjs +14 -17
  212. package/foundation/discovery/docs-discovery.test.mjs +15 -15
  213. package/foundation/discovery/docs-section-key.d.mts +19 -8
  214. package/foundation/discovery/docs-section-key.mjs +119 -32
  215. package/foundation/discovery/docs-section-key.test.mjs +41 -19
  216. package/foundation/discovery/template-adapter.d.mts +7 -0
  217. package/foundation/discovery/template-adapter.mjs +27 -40
  218. package/foundation/discovery/template-adapter.test.mjs +15 -0
  219. package/foundation/discovery/theme-discovery.d.mts +67 -7
  220. package/foundation/discovery/theme-discovery.mjs +916 -186
  221. package/foundation/discovery/theme-discovery.test.mjs +613 -219
  222. package/foundation/doc-compiler/bundle.d.mts +47 -0
  223. package/foundation/doc-compiler/bundle.mjs +218 -0
  224. package/foundation/doc-compiler/bundle.test.mjs +255 -0
  225. package/foundation/doc-compiler/compile.d.mts +200 -0
  226. package/foundation/doc-compiler/compile.mjs +259 -9
  227. package/foundation/doc-compiler/diagnostics.d.mts +126 -0
  228. package/foundation/doc-compiler/diagnostics.mjs +277 -0
  229. package/foundation/doc-compiler/doc-compiler.test.mjs +28 -1
  230. package/foundation/doc-compiler/doc-loads.test.mjs +1642 -0
  231. package/foundation/doc-compiler/import.d.mts +24 -0
  232. package/foundation/doc-compiler/import.mjs +59 -0
  233. package/foundation/doc-compiler/inputs.d.mts +102 -0
  234. package/foundation/doc-compiler/inputs.mjs +286 -0
  235. package/foundation/doc-compiler/inputs.test.mjs +299 -0
  236. package/foundation/doc-compiler/ir.d.mts +13 -0
  237. package/foundation/doc-compiler/ir.mjs +189 -12
  238. package/foundation/doc-compiler/lower-doc.test.mjs +492 -0
  239. package/foundation/doc-compiler/overlays.d.mts +37 -0
  240. package/foundation/doc-compiler/overlays.mjs +206 -0
  241. package/foundation/doc-compiler/parse-readable.d.mts +9 -0
  242. package/foundation/doc-compiler/parse-readable.mjs +29 -0
  243. package/foundation/doc-compiler/read.d.mts +126 -0
  244. package/foundation/doc-compiler/read.mjs +320 -0
  245. package/foundation/doc-compiler/read.test.mjs +313 -0
  246. package/foundation/doc-compiler/source.d.mts +33 -0
  247. package/foundation/doc-compiler/source.mjs +128 -0
  248. package/foundation/fs/file-protection.d.mts +33 -0
  249. package/foundation/fs/file-protection.mjs +825 -0
  250. package/foundation/fs/file-protection.test.mjs +250 -0
  251. package/foundation/integrations/autolink.d.mts +58 -1
  252. package/foundation/integrations/autolink.mjs +143 -57
  253. package/foundation/integrations/contribution-fixes.d.mts +145 -0
  254. package/foundation/integrations/contribution-fixes.mjs +1284 -0
  255. package/foundation/integrations/contribution-inventory.d.mts +1 -1
  256. package/foundation/integrations/contribution-inventory.mjs +17 -20
  257. package/foundation/integrations/contribution-inventory.test.mjs +67 -27
  258. package/foundation/integrations/integrations.d.mts +3 -0
  259. package/foundation/integrations/integrations.mjs +11 -97
  260. package/foundation/integrations/provider-ledger.test.mjs +275 -0
  261. package/foundation/integrations/provider-resolution.d.mts +152 -0
  262. package/foundation/integrations/provider-resolution.mjs +576 -0
  263. package/foundation/integrations/provider-resolution.test.mjs +369 -0
  264. package/foundation/integrations/theme-descriptor.d.mts +8 -0
  265. package/foundation/integrations/theme-descriptor.mjs +44 -0
  266. package/foundation/integrations/validate-contributions.mjs +104 -10
  267. package/foundation/response/error-codes.d.mts +3 -1
  268. package/foundation/response/error-codes.d.ts +2 -0
  269. package/foundation/response/error-codes.doc.mjs +12 -2
  270. package/foundation/response/error-codes.mjs +7 -1
  271. package/foundation/response/error-codes.test.mjs +55 -11
  272. package/foundation/response/response-types.doc.mjs +1 -1
  273. package/foundation/response/response.doc.mjs +1 -1
  274. package/foundation/text/string-utils.mjs +22 -10
  275. package/package.json +10 -9
  276. package/assets/templates/themes/manifest.json +0 -95
@@ -34,8 +34,8 @@ import {
34
34
  docsIndexBytes,
35
35
  oversizedDocSections,
36
36
  } from '../../foundation/discovery/docs-output-budget.mjs';
37
- import {compileTopic, overlayLanguages} from '../docs/_adapter.mjs';
38
- import {detailView} from '../../foundation/doc-compiler/lenses.mjs';
37
+ import {compileTopic, lowerTopic, overlayLanguages} from '../docs/_adapter.mjs';
38
+ import {detailView, indexView} from '../../foundation/doc-compiler/lenses.mjs';
39
39
  import {semverCompare, isValidSemver, satisfiesRange} from '../../foundation/env/semver.mjs';
40
40
 
41
41
  /**
@@ -721,38 +721,144 @@ function joinProblems(problems) {
721
721
  : `${problems.length} problems: ${problems.join('; ')}`;
722
722
  }
723
723
 
724
+ /** How the self-doc audit's problems are fixed. */
725
+ const AUTHORING_DOCS_FIX =
726
+ 'List every authoring self-doc in AUTHORING_SELF_DOCS, fix the one that fails to load, and split a section that is too large.';
727
+
728
+ /** How the public-surface audit's problems are fixed. */
729
+ const AUTHORING_SURFACE_FIX =
730
+ '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.';
731
+
732
+ /** Types one problem names before it counts the rest. */
733
+ const NAMED_TYPES = 40;
734
+
735
+ /**
736
+ * @param {string[]} names
737
+ * @returns {string}
738
+ */
739
+ function nameTypes(names) {
740
+ return names.length <= NAMED_TYPES
741
+ ? names.join(', ')
742
+ : `${names.slice(0, NAMED_TYPES).join(', ')} and ${names.length - NAMED_TYPES} more`;
743
+ }
744
+
745
+ /**
746
+ * The section keys `astryx docs authoring --index` lists, read the way that
747
+ * command reads them.
748
+ * @returns {Promise<{keys: Set<string>} | {keys: null, error: string}>}
749
+ */
750
+ async function authoringTopicKeys() {
751
+ try {
752
+ const catalog = DocsCatalog.fromBuiltins();
753
+ const entry = catalog.resolve('authoring');
754
+ if (!entry) return {keys: null, error: 'it is not a built-in topic'};
755
+ const index = indexView(await lowerTopic(catalog, entry));
756
+ return {keys: new Set(index.sections.map(section => section.id))};
757
+ } catch (err) {
758
+ return {
759
+ keys: null,
760
+ error: err instanceof Error ? err.message : String(err),
761
+ };
762
+ }
763
+ }
764
+
765
+ /**
766
+ * @param {Awaited<ReturnType<typeof import('../../foundation/discovery/authoring-surface.mjs').auditAuthoringSurface>>} surface
767
+ * @returns {string[]}
768
+ */
769
+ function surfaceProblems(surface) {
770
+ /** @type {string[]} */
771
+ const problems = [];
772
+ if (surface.types === 0 && surface.untraced.length === 0) {
773
+ problems.push(
774
+ '@astryxdesign/cli/authoring exports no types, so nothing was compared with `astryx docs authoring`',
775
+ );
776
+ }
777
+ for (const {module, names, reason, source, key} of surface.unreadable) {
778
+ const why =
779
+ reason === 'no-self-doc'
780
+ ? `no self-doc sits beside ${module}`
781
+ : reason === 'unregistered'
782
+ ? `${source} is not listed in AUTHORING_SELF_DOCS`
783
+ : reason === 'failed'
784
+ ? `${source} does not load`
785
+ : `${source} renders section "${key}", which the topic's index does not list`;
786
+ problems.push(
787
+ `${nameTypes(names)} from ${module} ${names.length === 1 ? 'has' : 'have'} no doc in \`astryx docs authoring\`: ${why}`,
788
+ );
789
+ }
790
+ for (const {name, reason} of surface.untraced) {
791
+ problems.push(
792
+ `${name} cannot be traced to the module that declares it: ${reason}`,
793
+ );
794
+ }
795
+ for (const {source, subject} of surface.unmatched) {
796
+ problems.push(
797
+ subject
798
+ ? `${source} documents ${subject}, which @astryxdesign/cli/authoring does not export`
799
+ : `${source} documents no type @astryxdesign/cli/authoring exports`,
800
+ );
801
+ }
802
+ return problems;
803
+ }
804
+
724
805
  /**
725
806
  * Every authoring self-doc is reachable from `astryx docs authoring`, loads,
726
- * and fits in one read. The audit is imported here, inside the try, so a
727
- * malformed self-doc is reported rather than taking Doctor down.
807
+ * and fits in one read, and every type `@astryxdesign/cli/authoring` exports
808
+ * has its doc there: the self-doc beside the module that declares it. The
809
+ * audits are imported here, inside the try, so a malformed self-doc is
810
+ * reported rather than taking Doctor down.
728
811
  * @param {DoctorContext} [_ctx]
812
+ * @param {{root?: string, sources?: string[], topicKeys?: Set<string> | null}} [options]
813
+ * Another authoring tree, list, or topic index to audit (for tests).
729
814
  * @returns {Promise<DoctorCheck>}
730
815
  */
731
- export async function checkAuthoringDocs(_ctx) {
816
+ export async function checkAuthoringDocs(_ctx, options = {}) {
732
817
  const id = 'authoring-docs';
733
818
  const label = 'Authoring docs';
734
819
  try {
735
- const {auditAuthoringSelfDocs} = await import(
736
- '../../foundation/discovery/authoring-self-docs.mjs'
737
- );
738
- const audit = await auditAuthoringSelfDocs();
820
+ const {auditAuthoringSelfDocs} =
821
+ await import('../../foundation/discovery/authoring-self-docs.mjs');
822
+ const {auditAuthoringSurface} =
823
+ await import('../../foundation/discovery/authoring-surface.mjs');
824
+ const {root, sources} = options;
825
+ const audit = await auditAuthoringSelfDocs({root, sources});
739
826
  const problems = [
740
827
  ...audit.unreachable.map(
741
828
  source => `${source} is not in \`astryx docs authoring\``,
742
829
  ),
743
- ...audit.failed.map(({source, error}) => `${source} failed to load: ${error}`),
830
+ ...audit.failed.map(
831
+ ({source, error}) => `${source} failed to load: ${error}`,
832
+ ),
744
833
  ...audit.oversized.map(
745
834
  ({key, bytes}) =>
746
835
  `authoring section "${key}" is ${kilobytes(bytes)}, over the ${kilobytes(DOC_OUTPUT_BUDGET_BYTES)} one read may return`,
747
836
  ),
748
837
  ];
749
- if (problems.length > 0) {
838
+ const topic =
839
+ options.topicKeys === undefined
840
+ ? await authoringTopicKeys()
841
+ : {keys: options.topicKeys};
842
+ const surface = [
843
+ ...('error' in topic
844
+ ? [`\`astryx docs authoring\` could not be read: ${topic.error}`]
845
+ : []),
846
+ ...surfaceProblems(
847
+ await auditAuthoringSurface({root, sources, topicKeys: topic.keys}),
848
+ ),
849
+ ];
850
+ if (problems.length + surface.length > 0) {
750
851
  return {
751
852
  id,
752
853
  label,
753
854
  status: 'fail',
754
- message: joinProblems(problems),
755
- fix: 'List every authoring self-doc in AUTHORING_SELF_DOCS, fix the one that fails to load, and split a section that is too large.',
855
+ message: joinProblems([...problems, ...surface]),
856
+ fix: [
857
+ problems.length > 0 ? AUTHORING_DOCS_FIX : null,
858
+ surface.length > 0 ? AUTHORING_SURFACE_FIX : null,
859
+ ]
860
+ .filter(Boolean)
861
+ .join(' '),
756
862
  };
757
863
  }
758
864
  return {
@@ -772,6 +878,71 @@ export async function checkAuthoringDocs(_ctx) {
772
878
  }
773
879
  }
774
880
 
881
+ /** How the CLI-docs audit's problems are fixed. */
882
+ const CLI_DOCS_FIX =
883
+ "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).";
884
+
885
+ /**
886
+ * Every command, API function, schema, and enum doc the CLI ships declares a
887
+ * namespace, and the topic that namespace names reads it: `astryx docs cli`
888
+ * for `cli/commands` and `cli/api`, `astryx docs authoring` for `authoring`.
889
+ * @param {DoctorContext | Partial<DoctorContext>} _ctx
890
+ * @param {{root?: string, sources?: string[], authoringSources?: string[]}} [options]
891
+ * test seams: the CLI root, the docs to audit, and the authoring topic's list
892
+ * @returns {Promise<DoctorCheck>}
893
+ */
894
+ export async function checkCliDocs(_ctx, options = {}) {
895
+ const id = 'cli-docs';
896
+ const label = 'CLI docs';
897
+ try {
898
+ const {auditCliSelfDocs} =
899
+ await import('../../foundation/discovery/cli-self-docs.mjs');
900
+ const audit = await auditCliSelfDocs(options);
901
+ const problems = [
902
+ ...audit.missing.map(
903
+ source =>
904
+ `${source} has no namespace, so no \`astryx docs\` topic reads it`,
905
+ ),
906
+ ...audit.unknown.map(
907
+ ({source, namespace}) =>
908
+ `${source} has namespace "${namespace}", which no \`astryx docs\` topic reads`,
909
+ ),
910
+ ...audit.misfiled.map(({message}) => message),
911
+ ...audit.failed.map(
912
+ ({source, error}) => `${source} failed to load: ${error}`,
913
+ ),
914
+ ...audit.keyProblems.map(problem => `\`astryx docs cli\`: ${problem}`),
915
+ ...audit.oversized.map(
916
+ ({key, bytes}) =>
917
+ `cli section "${key}" is ${kilobytes(bytes)}, over the ${kilobytes(DOC_OUTPUT_BUDGET_BYTES)} one read may return`,
918
+ ),
919
+ ];
920
+ if (problems.length > 0) {
921
+ return {
922
+ id,
923
+ label,
924
+ status: 'fail',
925
+ message: joinProblems(problems),
926
+ fix: CLI_DOCS_FIX,
927
+ };
928
+ }
929
+ return {
930
+ id,
931
+ label,
932
+ status: 'pass',
933
+ message: `All ${audit.docs} CLI docs are readable: ${audit.sections} in \`astryx docs cli\` and ${audit.authoring} in \`astryx docs authoring\`.`,
934
+ };
935
+ } catch (err) {
936
+ return {
937
+ id,
938
+ label,
939
+ status: 'fail',
940
+ message: `The CLI docs could not be audited: ${err instanceof Error ? err.message : String(err)}`,
941
+ fix: 'Reinstall @astryxdesign/cli.',
942
+ };
943
+ }
944
+ }
945
+
775
946
  /**
776
947
  * Every topic reads progressively, in every language it ships: it loads, its
777
948
  * section index and each of its sections fit in one read, and no contributed
@@ -823,7 +994,7 @@ export async function checkDocsProgressiveDisclosure(ctx) {
823
994
  return {
824
995
  id,
825
996
  label,
826
- status: 'fail',
997
+ status: 'warn',
827
998
  message: joinProblems(problems),
828
999
  fix: 'Fix the doc each problem names; split a section that is too large into smaller ones, each with its own key.',
829
1000
  };
@@ -929,6 +1100,7 @@ export async function runChecks(options = {}) {
929
1100
  }
930
1101
  }
931
1102
  checks.push(await checkAuthoringDocs(ctx));
1103
+ checks.push(await checkCliDocs(ctx));
932
1104
  checks.push(await checkDocsProgressiveDisclosure(ctx));
933
1105
 
934
1106
  const summary = {pass: 0, warn: 0, fail: 0, info: 0};
@@ -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,
@@ -383,7 +391,7 @@ describe('checkDocsProgressiveDisclosure', () => {
383
391
  expect(c.message).toMatch(/^\d+ topics: /);
384
392
  }, SLOW);
385
393
 
386
- it('fails on an invalid doc an integration contributed', async () => {
394
+ it('warns on an invalid doc an integration contributed', async () => {
387
395
  const c = await checkDocsProgressiveDisclosure({
388
396
  docsCatalogIssues: [
389
397
  {
@@ -394,13 +402,13 @@ describe('checkDocsProgressiveDisclosure', () => {
394
402
  },
395
403
  ],
396
404
  });
397
- expect(c.status).toBe('fail');
405
+ expect(c.status).toBe('warn');
398
406
  expect(c.message).toBe('@acme/widgets: bad.doc.mjs exports no doc');
399
407
  });
400
408
 
401
- it('fails when the docs catalog cannot be built', async () => {
409
+ it('warns when the docs catalog cannot be built', async () => {
402
410
  const c = await checkDocsProgressiveDisclosure({docsCatalogError: 'boom'});
403
- expect(c.status).toBe('fail');
411
+ expect(c.status).toBe('warn');
404
412
  expect(c.message).toContain('boom');
405
413
  });
406
414
 
@@ -428,7 +436,7 @@ describe('checkDocsProgressiveDisclosure', () => {
428
436
  }),
429
437
  docsCatalogIssues: [],
430
438
  });
431
- expect(c.status).toBe('fail');
439
+ expect(c.status).toBe('warn');
432
440
  expect(c.message).toMatch(/^2 problems: /);
433
441
  expect(c.message).toContain('huge everything: 41 KB, over the 32 KB one read may return');
434
442
  expect(c.message).toContain('broken: ');
@@ -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});
@@ -487,7 +812,7 @@ describe('checkDocsProgressiveDisclosure languages', () => {
487
812
  docsCatalog: DocsCatalog.fromBuiltins({deploying: path.join(dir, 'deploying.doc.mjs')}),
488
813
  docsCatalogIssues: [],
489
814
  });
490
- expect(c.status).toBe('fail');
815
+ expect(c.status).toBe('warn');
491
816
  expect(c.message).toContain('deploying [zh]: zh overlay broken');
492
817
  expect(c.message).toContain('deploying [dense] overview: 41 KB');
493
818
  expect(c.message).not.toMatch(/deploying overview:/);
@@ -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: