@astryxdesign/cli 0.6.3-canary.ebaebc4 → 0.6.3-canary.ee13359

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 (501) hide show
  1. package/README.md +114 -77
  2. package/api/blog/blog.doc.mjs +1 -0
  3. package/api/build/build.doc.mjs +4 -3
  4. package/api/build/build.test.mjs +14 -3
  5. package/api/build/build.type.d.mts +35 -1
  6. package/api/build/build.type.mjs +24 -3
  7. package/api/build/help/help.d.mts +8 -5
  8. package/api/build/help/help.mjs +56 -6
  9. package/api/component/_adapter.d.mts +6 -12
  10. package/api/component/_adapter.mjs +20 -10
  11. package/api/component/component.doc.mjs +13 -3
  12. package/api/component/component.mjs +91 -14
  13. package/api/component/component.test.mjs +38 -0
  14. package/api/component/component.type.d.mts +16 -5
  15. package/api/component/component.type.mjs +13 -5
  16. package/api/component/detail/blocks/blocks.d.mts +2 -1
  17. package/api/component/detail/blocks/blocks.mjs +4 -3
  18. package/api/component/list/list.d.mts +0 -5
  19. package/api/component/list/list.mjs +40 -11
  20. package/api/discover/discover.doc.mjs +2 -1
  21. package/api/docs/_adapter.d.mts +210 -34
  22. package/api/docs/_adapter.mjs +674 -134
  23. package/api/docs/detail/detail.mjs +11 -3
  24. package/api/docs/detail/section/section.mjs +24 -13
  25. package/api/docs/detail/section/section.test.mjs +15 -6
  26. package/api/docs/docs.d.mts +5 -3
  27. package/api/docs/docs.doc.mjs +36 -15
  28. package/api/docs/docs.mjs +44 -8
  29. package/api/docs/docs.test.mjs +158 -4
  30. package/api/docs/docs.type.d.mts +165 -2
  31. package/api/docs/docs.type.mjs +101 -3
  32. package/api/docs/index/index.mjs +11 -3
  33. package/api/docs/index/index.test.mjs +1 -1
  34. package/api/docs/integration-tree.test.mjs +547 -0
  35. package/api/docs/integrationDocs.test.mjs +14 -14
  36. package/api/docs/list/list.mjs +28 -12
  37. package/api/docs/node/node.d.mts +43 -0
  38. package/api/docs/node/node.mjs +192 -0
  39. package/api/doctor/doctor.d.mts +54 -4
  40. package/api/doctor/doctor.doc.mjs +1 -0
  41. package/api/doctor/doctor.mjs +327 -16
  42. package/api/doctor/doctor.test.mjs +420 -7
  43. package/api/gap-report/gap-report.doc.mjs +8 -4
  44. package/api/hook/_adapter.mjs +19 -5
  45. package/api/hook/hook.doc.mjs +1 -0
  46. package/api/hook/list/list.d.mts +1 -1
  47. package/api/hook/list/list.mjs +69 -17
  48. package/api/index.d.mts +1 -1
  49. package/api/index.mjs +1 -0
  50. package/api/init/init.doc.mjs +6 -1
  51. package/api/init/init.test.mjs +41 -1
  52. package/api/init/remove/remove.mjs +1 -1
  53. package/api/init/run/run.mjs +20 -10
  54. package/api/integration/add-contribution.component-names.test.mjs +120 -0
  55. package/api/integration/add-contribution.d.mts +2 -1
  56. package/api/integration/add-contribution.mjs +125 -12
  57. package/api/integration/add-contribution.test.mjs +254 -3
  58. package/api/integration/add-theme.mjs +34 -64
  59. package/api/integration/add-theme.test.mjs +105 -21
  60. package/api/integration/authoring-checks.mjs +108 -31
  61. package/api/integration/authoring-checks.test.mjs +171 -6
  62. package/api/integration/authoring-checks.type.d.mts +3 -1
  63. package/api/integration/authoring-checks.type.mjs +3 -1
  64. package/api/integration/integration-authoring.type.d.mts +2 -0
  65. package/api/integration/integration-authoring.type.mjs +2 -0
  66. package/api/integration/integration-block-exports.test.mjs +10 -6
  67. package/api/integration/integrationAdd.doc.mjs +14 -4
  68. package/api/integration/integrationAddAgentDoc.doc.mjs +1 -0
  69. package/api/integration/integrationAddCodemod.doc.mjs +1 -0
  70. package/api/integration/integrationAddComponent.doc.mjs +2 -1
  71. package/api/integration/integrationAddDoc.doc.mjs +8 -1
  72. package/api/integration/integrationAddTemplate.doc.mjs +1 -0
  73. package/api/integration/integrationAddTheme.doc.mjs +6 -5
  74. package/api/integration/integrationComponentConflicts.doc.mjs +1 -0
  75. package/api/integration/integrationDocConflicts.doc.mjs +2 -1
  76. package/api/integration/integrationPackCheck.doc.mjs +2 -1
  77. package/api/integration/integrationTemplateConflicts.doc.mjs +7 -5
  78. package/api/integration/pack-check.mjs +34 -0
  79. package/api/integration/pack-check.test.mjs +138 -47
  80. package/api/integration/pack-check.type.d.mts +26 -2
  81. package/api/integration/pack-check.type.mjs +14 -1
  82. package/api/integration/summarizeIssues.doc.mjs +1 -0
  83. package/api/integration/validate-integration-fixes.test.mjs +1389 -0
  84. package/api/integration/validate-integration.mjs +52 -102
  85. package/api/integration/validate-integration.test.mjs +179 -26
  86. package/api/integration/validate-unread-theme-folders.test.mjs +110 -0
  87. package/api/integration/validateIntegration.doc.mjs +3 -2
  88. package/api/json/assertResponse.doc.mjs +1 -0
  89. package/api/json/envelope-types.test.mjs +76 -0
  90. package/api/json/index.ts +2 -0
  91. package/api/json/isError.doc.mjs +1 -0
  92. package/api/json/parseResponse.doc.mjs +3 -2
  93. package/api/layout/_adapter.mjs +20 -5
  94. package/api/layout/expand/expand.mjs +7 -5
  95. package/api/layout/expand/expand.path-safety.test.mjs +53 -0
  96. package/api/layout/grammar/grammar.mjs +2 -1
  97. package/api/layout/layoutCheck.doc.mjs +1 -0
  98. package/api/layout/layoutExpand.doc.mjs +2 -1
  99. package/api/layout/layoutGrammar.doc.mjs +1 -0
  100. package/api/search/search-return-type.test.mjs +54 -0
  101. package/api/search/search.d.mts +28 -10
  102. package/api/search/search.doc.mjs +8 -2
  103. package/api/search/search.mjs +334 -38
  104. package/api/search/search.test.mjs +124 -1
  105. package/api/search/search.type.d.mts +13 -1
  106. package/api/search/search.type.mjs +4 -1
  107. package/api/swizzle/copy/copy.mjs +28 -11
  108. package/api/swizzle/swizzle.doc.mjs +2 -1
  109. package/api/template/copy/copy.mjs +17 -23
  110. package/api/template/copy/copy.test.mjs +17 -0
  111. package/api/template/list/list.mjs +1 -0
  112. package/api/template/template-integration.test.mjs +1022 -3
  113. package/api/template/template-suffix.test.mjs +41 -21
  114. package/api/template/template.doc.mjs +30 -8
  115. package/api/template/template.mjs +45 -8
  116. package/api/template/template.type.d.mts +6 -8
  117. package/api/template/template.type.mjs +3 -2
  118. package/api/theme/_adapter.d.mts +2 -3
  119. package/api/theme/_adapter.mjs +4 -5
  120. package/api/theme/add/add.binary.test.mjs +84 -0
  121. package/api/theme/add/add.mjs +20 -3
  122. package/api/theme/add/add.staging.test.mjs +66 -0
  123. package/api/theme/add/add.test.mjs +14 -1
  124. package/api/theme/build/build.mjs +114 -37
  125. package/api/theme/build/build.public-component-vars.test.mjs +1 -1
  126. package/api/theme/build/build.receipt-doc.test.mjs +111 -0
  127. package/api/theme/build/font-warning.mjs +3 -3
  128. package/api/theme/build/font-warning.test.mjs +5 -2
  129. package/api/theme/generateTonalPalette.doc.mjs +1 -0
  130. package/api/theme/integration-themes.test.mjs +39 -28
  131. package/api/theme/list/list.test.mjs +19 -20
  132. package/api/theme/listThemes.doc.mjs +6 -5
  133. package/api/theme/palette/generate/generate.mjs +7 -2
  134. package/api/theme/palette/generate/generate.test.mjs +96 -0
  135. package/api/theme/palette/generate/generator.mjs +8 -1
  136. package/api/theme/palette/generate/generator.test.mjs +10 -0
  137. package/api/theme/template/template.mjs +11 -2
  138. package/api/theme/template/template.test.mjs +20 -0
  139. package/api/theme/themeAdd.doc.mjs +4 -3
  140. package/api/theme/themeBuild.doc.mjs +8 -4
  141. package/api/theme/themeList.doc.mjs +6 -3
  142. package/api/theme/themeListAvailable.doc.mjs +5 -3
  143. package/api/theme/themePaletteGenerate.doc.mjs +1 -0
  144. package/api/theme/themeTargets.doc.mjs +1 -0
  145. package/api/theme/themeTemplate.doc.mjs +6 -2
  146. package/api/upgrade/_adapter.d.mts +32 -5
  147. package/api/upgrade/_adapter.mjs +124 -73
  148. package/api/upgrade/list/list.mjs +2 -1
  149. package/api/upgrade/list/list.test.mjs +73 -0
  150. package/api/upgrade/provider-agreement.test.mjs +152 -0
  151. package/api/upgrade/run/run.mjs +356 -59
  152. package/api/upgrade/status/status.mjs +2 -2
  153. package/api/upgrade/upgrade.doc.mjs +8 -2
  154. package/api/upgrade/upgrade.type.d.mts +38 -0
  155. package/api/upgrade/upgrade.type.mjs +16 -0
  156. package/assets/codemods/__tests__/runner.test.mjs +330 -8
  157. package/assets/codemods/integration-discovery.mjs +8 -2
  158. package/assets/codemods/integration-discovery.test.mjs +15 -0
  159. package/assets/codemods/integration-runner.mjs +56 -4
  160. package/assets/codemods/integration-runner.protection.test.mjs +153 -0
  161. package/assets/codemods/run-codemod.mjs +177 -34
  162. package/assets/codemods/runner.mjs +350 -102
  163. package/assets/codemods/term-log.mjs +32 -8
  164. package/assets/codemods/term-log.test.mjs +19 -1
  165. package/assets/codemods/transform-prop.mjs +109 -0
  166. package/assets/codemods/transform-prop.test.mjs +95 -0
  167. package/assets/codemods/transforms/next/__tests__/migrate-native-picker-to-presentation.test.mjs +63 -0
  168. package/assets/codemods/transforms/next/__tests__/migrate-theme-catalog-to-descriptors.test.mjs +220 -0
  169. package/assets/codemods/transforms/next/index.mjs +19 -1
  170. package/assets/codemods/transforms/next/migrate-native-picker-to-presentation.mjs +148 -0
  171. package/assets/codemods/transforms/next/migrate-theme-catalog-to-descriptors.mjs +141 -0
  172. package/assets/codemods/transforms/v0.0.14/__tests__/rename-status-variants.test.mjs +86 -165
  173. package/assets/codemods/transforms/v0.0.14/rename-status-variants.mjs +72 -210
  174. package/assets/codemods/transforms/v0.1.8/__tests__/rename-avatar-size-scale.test.mjs +83 -115
  175. package/assets/codemods/transforms/v0.1.8/rename-avatar-size-scale.mjs +57 -186
  176. package/assets/codemods/transforms/v0.6.0/__tests__/next-codemods.test.mjs +47 -0
  177. package/assets/codemods/transforms/v0.6.0/rename-resizable-pixel-bounds.mjs +57 -10
  178. package/assets/docs/getting-started.doc.mjs +2 -2
  179. package/assets/docs/principles.doc.mjs +6 -6
  180. package/assets/docs/styling-libraries.doc.mjs +2 -2
  181. package/assets/docs/styling.doc.mjs +4 -4
  182. package/assets/docs/theme.doc.mjs +4 -4
  183. package/assets/docs/tokens.doc.mjs +1 -1
  184. package/assets/docs/tree/api.doc.mjs +30 -0
  185. package/assets/docs/tree/cli.doc.mjs +23 -0
  186. package/assets/docs/tree/commands.doc.mjs +25 -0
  187. package/assets/docs/{cli-integrations.doc.mjs → tree/integrations.doc.mjs} +66 -38
  188. package/assets/docs/tree/integrations.test.mjs +62 -0
  189. package/assets/docs/tree/writing-docs.doc.mjs +286 -0
  190. package/assets/templates/blocks/components/DateInput/DateInputDateRange.tsx +1 -1
  191. package/assets/templates/blocks/components/Item/ItemDocumentTabs.doc.mjs +14 -0
  192. package/assets/templates/blocks/components/Item/ItemDocumentTabs.tsx +100 -0
  193. package/assets/templates/blocks/components/ScrollableArea/ScrollableAreaInlineRail.doc.mjs +14 -0
  194. package/assets/templates/blocks/components/ScrollableArea/ScrollableAreaInlineRail.tsx +61 -0
  195. package/assets/templates/blocks/components/ScrollableArea/ScrollableAreaOverscrollChaining.doc.mjs +14 -0
  196. package/assets/templates/blocks/components/ScrollableArea/ScrollableAreaOverscrollChaining.tsx +126 -0
  197. package/assets/templates/blocks/components/ScrollableArea/ScrollableAreaShowcase.doc.mjs +15 -0
  198. package/assets/templates/blocks/components/ScrollableArea/ScrollableAreaShowcase.tsx +86 -0
  199. package/assets/templates/blocks/components/ScrollableArea/ScrollableAreaStickyGroupHeaders.doc.mjs +14 -0
  200. package/assets/templates/blocks/components/ScrollableArea/ScrollableAreaStickyGroupHeaders.tsx +99 -0
  201. package/assets/templates/blocks/components/ScrollableArea/ScrollableAreaStickyPassthrough.doc.mjs +14 -0
  202. package/assets/templates/blocks/components/ScrollableArea/ScrollableAreaStickyPassthrough.tsx +122 -0
  203. package/assets/templates/blocks/components/ScrollableArea/ScrollableAreaTwoAxisBoard.doc.mjs +14 -0
  204. package/assets/templates/blocks/components/ScrollableArea/ScrollableAreaTwoAxisBoard.tsx +95 -0
  205. package/assets/templates/blocks/components/TimeInput/TimeInputConstrained.tsx +1 -0
  206. package/assets/templates/pages/table-tree/page.tsx +1704 -0
  207. package/assets/templates/pages/table-tree/template.doc.mjs +12 -0
  208. package/assets/templates/themes/butter/butterTheme.doc.mjs +11 -0
  209. package/assets/templates/themes/chocolate/chocolateTheme.doc.mjs +11 -0
  210. package/assets/templates/themes/gothic/gothicTheme.doc.mjs +11 -0
  211. package/assets/templates/themes/matcha/matchaTheme.doc.mjs +11 -0
  212. package/assets/templates/themes/neutral/neutralTheme.doc.mjs +11 -0
  213. package/assets/templates/themes/stone/stoneTheme.doc.mjs +11 -0
  214. package/assets/templates/themes/y2k/y2kTheme.doc.mjs +11 -0
  215. package/authoring/codemod/codemod.doc.mjs +1 -1
  216. package/authoring/codemod/type.ts +12 -0
  217. package/authoring/config/config.doc.mjs +2 -2
  218. package/authoring/config/debug-composition.test.mjs +92 -0
  219. package/authoring/config/type.ts +15 -3
  220. package/authoring/debug/debug.doc.d.mts +11 -0
  221. package/authoring/debug/debug.doc.mjs +182 -0
  222. package/authoring/debug/parse.d.mts +3 -3
  223. package/authoring/doctypes/_schema.d.mts +71 -141
  224. package/authoring/doctypes/_schema.mjs +29 -2
  225. package/authoring/doctypes/base/graph-fields.doc.mjs +8 -6
  226. package/authoring/doctypes/base/type.ts +6 -5
  227. package/authoring/doctypes/command/command.doc.mjs +1 -1
  228. package/authoring/doctypes/command/type.ts +2 -2
  229. package/authoring/doctypes/component/type.ts +2 -2
  230. package/authoring/doctypes/doctypes-new.test.mjs +48 -6
  231. package/authoring/doctypes/enum/enum.doc.mjs +1 -1
  232. package/authoring/doctypes/enum/type.ts +1 -1
  233. package/authoring/doctypes/function/function.doc.mjs +3 -2
  234. package/authoring/doctypes/function/type.ts +3 -2
  235. package/authoring/doctypes/hook/type.ts +2 -2
  236. package/authoring/doctypes/load-contract.test.mjs +3 -2
  237. package/authoring/doctypes/namespace/namespace.doc.mjs +6 -10
  238. package/authoring/doctypes/namespace/parse.test.mjs +23 -25
  239. package/authoring/doctypes/namespace/type.ts +5 -2
  240. package/authoring/doctypes/parse.d.mts +4 -2
  241. package/authoring/doctypes/parse.mjs +10 -5
  242. package/authoring/doctypes/reference/reference.doc.mjs +7 -5
  243. package/authoring/doctypes/reference/type.ts +12 -10
  244. package/authoring/doctypes/schema/schema.doc.mjs +1 -1
  245. package/authoring/doctypes/schema/type.ts +1 -2
  246. package/authoring/doctypes/template/parse.d.mts +2 -0
  247. package/authoring/doctypes/template/parse.mjs +4 -0
  248. package/authoring/doctypes/template/parse.test.mjs +18 -0
  249. package/authoring/doctypes/template/template.doc.mjs +9 -3
  250. package/authoring/doctypes/template/type.ts +8 -0
  251. package/authoring/doctypes/theme/parse.d.mts +35 -0
  252. package/authoring/doctypes/theme/parse.mjs +76 -0
  253. package/authoring/doctypes/theme/theme.doc.d.mts +9 -0
  254. package/authoring/doctypes/theme/theme.doc.mjs +79 -0
  255. package/authoring/doctypes/theme/type.ts +42 -0
  256. package/authoring/doctypes/types.ts +2 -1
  257. package/authoring/gap-report/gap-report.doc.d.mts +12 -0
  258. package/authoring/gap-report/gap-report.doc.mjs +183 -0
  259. package/authoring/identity/identity.doc.mjs +2 -2
  260. package/authoring/index.d.mts +1 -0
  261. package/authoring/index.d.ts +7 -4
  262. package/authoring/index.mjs +2 -1
  263. package/authoring/integration/integration.doc.mjs +2 -2
  264. package/authoring/integration/type.ts +4 -10
  265. package/clients/cli/__tests__/cliManifest.test.ts +27 -29
  266. package/clients/cli/command-load-failure.test.mjs +83 -0
  267. package/clients/cli/commands/blog.doc.mjs +1 -1
  268. package/clients/cli/commands/blog.mjs +23 -8
  269. package/clients/cli/commands/blog.test.mjs +42 -1
  270. package/clients/cli/commands/build-theme.adaptations.test.mjs +100 -1
  271. package/clients/cli/commands/build-theme.ascii-output.test.mjs +161 -0
  272. package/clients/cli/commands/build-theme.flag-docs.test.mjs +110 -0
  273. package/clients/cli/commands/build-theme.mjs +16 -50
  274. package/clients/cli/commands/build-theme.path-safety.test.mjs +86 -1
  275. package/clients/cli/commands/build-theme.variants.test.mjs +76 -0
  276. package/clients/cli/commands/build.doc.mjs +5 -2
  277. package/clients/cli/commands/build.exit-codes-doc.test.mjs +50 -0
  278. package/clients/cli/commands/build.mjs +37 -49
  279. package/clients/cli/commands/build.playbook.test.mjs +75 -0
  280. package/clients/cli/commands/build.text-fields.test.mjs +40 -0
  281. package/clients/cli/commands/component/index.mjs +3 -8
  282. package/clients/cli/commands/component-ownership.test.mjs +3 -3
  283. package/clients/cli/commands/component-package.test.mjs +46 -0
  284. package/clients/cli/commands/component-resolution.test.mjs +21 -0
  285. package/clients/cli/commands/component.doc.mjs +1 -1
  286. package/clients/cli/commands/component.test.mjs +19 -0
  287. package/clients/cli/commands/detail-levels.test.mjs +2 -2
  288. package/clients/cli/commands/discover.broken-integration.test.mjs +52 -5
  289. package/clients/cli/commands/discover.components-flag.test.mjs +97 -0
  290. package/clients/cli/commands/discover.doc.mjs +5 -3
  291. package/clients/cli/commands/discover.mjs +4 -4
  292. package/clients/cli/commands/discover.text-projection.test.mjs +103 -0
  293. package/clients/cli/commands/docs.doc.mjs +21 -9
  294. package/clients/cli/commands/docs.mjs +182 -68
  295. package/clients/cli/commands/docs.test.mjs +120 -16
  296. package/clients/cli/commands/doctor-integration-components.doc.mjs +1 -1
  297. package/clients/cli/commands/doctor-integration-docs.doc.mjs +3 -3
  298. package/clients/cli/commands/doctor-integration-templates.doc.mjs +15 -8
  299. package/clients/cli/commands/doctor-integration-validate.doc.mjs +1 -1
  300. package/clients/cli/commands/doctor-integration.doc.mjs +1 -1
  301. package/clients/cli/commands/doctor-integration.package-json.test.mjs +53 -0
  302. package/clients/cli/commands/doctor-integration.test.mjs +83 -8
  303. package/clients/cli/commands/doctor.doc.mjs +1 -1
  304. package/clients/cli/commands/doctor.mjs +30 -25
  305. package/clients/cli/commands/doctor.test.mjs +42 -0
  306. package/clients/cli/commands/gap-report.doc.mjs +17 -6
  307. package/clients/cli/commands/gap-report.test.mjs +72 -0
  308. package/clients/cli/commands/hook/index.mjs +7 -17
  309. package/clients/cli/commands/hook.doc.mjs +1 -1
  310. package/clients/cli/commands/hook.text-projection.test.mjs +45 -0
  311. package/clients/cli/commands/init.doc.mjs +20 -9
  312. package/clients/cli/commands/init.flag-help.test.mjs +153 -0
  313. package/clients/cli/commands/integration-add.controls.test.mjs +132 -0
  314. package/clients/cli/commands/integration-add.doc.mjs +32 -6
  315. package/clients/cli/commands/integration-pack.doc.mjs +1 -1
  316. package/clients/cli/commands/integration-real-world.test.mjs +3 -9
  317. package/clients/cli/commands/integration.doc.mjs +1 -1
  318. package/clients/cli/commands/integration.mjs +1 -0
  319. package/clients/cli/commands/interactive-guard.test.mjs +101 -24
  320. package/clients/cli/commands/json-contract.test.mjs +33 -0
  321. package/clients/cli/commands/layout-check.doc.mjs +15 -4
  322. package/clients/cli/commands/layout-expand.doc.mjs +22 -5
  323. package/clients/cli/commands/layout-grammar.doc.mjs +1 -1
  324. package/clients/cli/commands/layout.doc.mjs +3 -3
  325. package/clients/cli/commands/layout.mjs +21 -9
  326. package/clients/cli/commands/layout.path-help.test.mjs +33 -0
  327. package/clients/cli/commands/layout.stdin-cap.test.mjs +47 -0
  328. package/clients/cli/commands/layout.text-fields.test.mjs +39 -0
  329. package/clients/cli/commands/manifest.doc.mjs +1 -1
  330. package/clients/cli/commands/no-prompt-wording.test.mjs +94 -0
  331. package/clients/cli/commands/search.doc.mjs +7 -4
  332. package/clients/cli/commands/search.mjs +28 -9
  333. package/clients/cli/commands/search.test.mjs +75 -0
  334. package/clients/cli/commands/setup-nudge.test.mjs +6 -0
  335. package/clients/cli/commands/swizzle.doc.mjs +3 -2
  336. package/clients/cli/commands/swizzle.path-safety.test.mjs +42 -0
  337. package/clients/cli/commands/template.doc.mjs +52 -13
  338. package/clients/cli/commands/template.flag-help.test.mjs +117 -0
  339. package/clients/cli/commands/template.mjs +4 -91
  340. package/clients/cli/commands/template.path-help.test.mjs +40 -0
  341. package/clients/cli/commands/text-json-parity.test.mjs +719 -0
  342. package/clients/cli/commands/theme-add.doc.mjs +4 -3
  343. package/clients/cli/commands/theme-build.doc.mjs +8 -7
  344. package/clients/cli/commands/theme-list.doc.mjs +2 -2
  345. package/clients/cli/commands/theme-palette-generate.doc.mjs +3 -3
  346. package/clients/cli/commands/theme-palette-generate.test.mjs +19 -0
  347. package/clients/cli/commands/theme-palette.doc.mjs +1 -1
  348. package/clients/cli/commands/theme-targets.behavior.test.mjs +4 -3
  349. package/clients/cli/commands/theme-targets.doc.mjs +1 -1
  350. package/clients/cli/commands/theme-template.behavior.test.mjs +12 -0
  351. package/clients/cli/commands/theme-template.doc.mjs +2 -2
  352. package/clients/cli/commands/theme.doc.mjs +1 -1
  353. package/clients/cli/commands/upgrade.ascii-output.test.mjs +87 -0
  354. package/clients/cli/commands/upgrade.doc.mjs +20 -8
  355. package/clients/cli/commands/upgrade.file-protection.test.mjs +228 -0
  356. package/clients/cli/commands/upgrade.flag-help.test.mjs +188 -0
  357. package/clients/cli/commands/upgrade.hook-output.test.mjs +88 -0
  358. package/clients/cli/commands/upgrade.mjs +29 -7
  359. package/clients/cli/formatters/index.mjs +2 -0
  360. package/clients/cli/formatters/index.test.mjs +6 -0
  361. package/clients/cli/index.mjs +21 -30
  362. package/clients/cli/latest-version-env.test.mjs +50 -0
  363. package/clients/cli/lib/cli-error.test.mjs +7 -0
  364. package/clients/cli/lib/component-format.mjs +9 -9
  365. package/clients/cli/lib/component-format.test.mjs +1 -1
  366. package/clients/cli/lib/define-command.mjs +32 -6
  367. package/clients/cli/lib/doc-text-ascii.test.mjs +82 -0
  368. package/clients/cli/lib/exit-codes.test.mjs +97 -0
  369. package/clients/cli/lib/hook-format.mjs +19 -10
  370. package/clients/cli/lib/json-shim.mjs +38 -2
  371. package/clients/cli/lib/json-shim.test.mjs +83 -0
  372. package/clients/cli/lib/manifest.d.ts +2 -0
  373. package/clients/cli/lib/manifest.mjs +31 -2
  374. package/clients/cli/lib/manifest.test.mjs +17 -0
  375. package/foundation/agent-docs/agent-docs.d.mts +4 -0
  376. package/foundation/agent-docs/agent-docs.mjs +70 -3
  377. package/foundation/agent-docs/agent-docs.path-safety.test.mjs +266 -4
  378. package/foundation/agent-docs/agent-docs.test.mjs +10 -1
  379. package/foundation/config/integration-debug.test.mjs +28 -3
  380. package/foundation/config/project-themes.test.mjs +11 -19
  381. package/foundation/config/project.d.mts +20 -11
  382. package/foundation/config/project.mjs +246 -89
  383. package/foundation/config/project.test.mjs +270 -21
  384. package/foundation/discovery/authoring-self-docs.d.mts +6 -0
  385. package/foundation/discovery/authoring-self-docs.mjs +17 -7
  386. package/foundation/discovery/authoring-self-docs.test.mjs +20 -9
  387. package/foundation/discovery/authoring-surface.d.mts +74 -0
  388. package/foundation/discovery/authoring-surface.mjs +525 -0
  389. package/foundation/discovery/authoring-surface.test.mjs +392 -0
  390. package/foundation/discovery/cli-self-docs.d.mts +119 -0
  391. package/foundation/discovery/cli-self-docs.mjs +490 -0
  392. package/foundation/discovery/cli-self-docs.test.mjs +375 -0
  393. package/foundation/discovery/component-discovery.d.mts +38 -0
  394. package/foundation/discovery/component-discovery.mjs +48 -0
  395. package/foundation/discovery/component-loader.d.mts +35 -38
  396. package/foundation/discovery/component-loader.mjs +53 -222
  397. package/foundation/discovery/docs-discovery.d.mts +108 -10
  398. package/foundation/discovery/docs-discovery.mjs +191 -30
  399. package/foundation/discovery/docs-discovery.test.mjs +71 -32
  400. package/foundation/discovery/docs-output-budget.d.mts +2 -2
  401. package/foundation/discovery/docs-output-budget.mjs +1 -1
  402. package/foundation/discovery/docs-section-key.d.mts +28 -10
  403. package/foundation/discovery/docs-section-key.mjs +134 -33
  404. package/foundation/discovery/docs-section-key.test.mjs +41 -19
  405. package/foundation/discovery/template-adapter.d.mts +113 -11
  406. package/foundation/discovery/template-adapter.fixture-refs.test.mjs +248 -0
  407. package/foundation/discovery/template-adapter.integration-isolation.test.mjs +94 -0
  408. package/foundation/discovery/template-adapter.mjs +772 -82
  409. package/foundation/discovery/template-adapter.test.mjs +57 -0
  410. package/foundation/discovery/theme-discovery.d.mts +67 -7
  411. package/foundation/discovery/theme-discovery.mjs +916 -186
  412. package/foundation/discovery/theme-discovery.test.mjs +613 -219
  413. package/foundation/doc-compiler/bundle.d.mts +47 -0
  414. package/foundation/doc-compiler/bundle.mjs +278 -0
  415. package/foundation/doc-compiler/bundle.test.mjs +266 -0
  416. package/foundation/doc-compiler/compile.d.mts +220 -39
  417. package/foundation/doc-compiler/compile.mjs +311 -15
  418. package/foundation/doc-compiler/diagnostics.d.mts +126 -0
  419. package/foundation/doc-compiler/diagnostics.mjs +305 -0
  420. package/foundation/doc-compiler/doc-compiler.test.mjs +28 -1
  421. package/foundation/doc-compiler/doc-loads.test.mjs +1642 -0
  422. package/foundation/doc-compiler/import.d.mts +24 -0
  423. package/foundation/doc-compiler/import.mjs +59 -0
  424. package/foundation/doc-compiler/inputs.d.mts +102 -0
  425. package/foundation/doc-compiler/inputs.mjs +291 -0
  426. package/foundation/doc-compiler/inputs.test.mjs +299 -0
  427. package/foundation/doc-compiler/ir.d.mts +13 -0
  428. package/foundation/doc-compiler/ir.mjs +196 -12
  429. package/foundation/doc-compiler/lenses.d.mts +3 -2
  430. package/foundation/doc-compiler/lenses.mjs +2 -1
  431. package/foundation/doc-compiler/links.d.mts +135 -0
  432. package/foundation/doc-compiler/links.mjs +253 -0
  433. package/foundation/doc-compiler/links.test.mjs +139 -0
  434. package/foundation/doc-compiler/lower-doc.test.mjs +495 -0
  435. package/foundation/doc-compiler/overlays.d.mts +37 -0
  436. package/foundation/doc-compiler/overlays.mjs +206 -0
  437. package/foundation/doc-compiler/parse-readable.d.mts +9 -0
  438. package/foundation/doc-compiler/parse-readable.mjs +29 -0
  439. package/foundation/doc-compiler/read.d.mts +127 -0
  440. package/foundation/doc-compiler/read.mjs +325 -0
  441. package/foundation/doc-compiler/read.test.mjs +313 -0
  442. package/foundation/doc-compiler/source.d.mts +33 -0
  443. package/foundation/doc-compiler/source.mjs +128 -0
  444. package/foundation/doc-compiler/tree.d.mts +288 -0
  445. package/foundation/doc-compiler/tree.mjs +876 -0
  446. package/foundation/doc-compiler/tree.test.mjs +598 -0
  447. package/foundation/fs/file-protection.d.mts +33 -0
  448. package/foundation/fs/file-protection.mjs +825 -0
  449. package/foundation/fs/file-protection.test.mjs +250 -0
  450. package/foundation/fs/module-loader.d.mts +1 -0
  451. package/foundation/fs/module-loader.mjs +50 -1
  452. package/foundation/fs/module-loader.stdout.test.mjs +332 -0
  453. package/foundation/fs/path-safety.d.mts +3 -2
  454. package/foundation/fs/path-safety.mjs +49 -19
  455. package/foundation/fs/path-safety.test.mjs +50 -0
  456. package/foundation/fs/publish-file-hardlink-unavailable.test.mjs +129 -94
  457. package/foundation/integrations/autolink.d.mts +58 -1
  458. package/foundation/integrations/autolink.mjs +143 -52
  459. package/foundation/integrations/autolink.test.mjs +1 -1
  460. package/foundation/integrations/cli-requirement.d.mts +45 -0
  461. package/foundation/integrations/cli-requirement.mjs +154 -0
  462. package/foundation/integrations/cli-requirement.test.mjs +109 -0
  463. package/foundation/integrations/contribution-fixes.d.mts +145 -0
  464. package/foundation/integrations/contribution-fixes.mjs +1284 -0
  465. package/foundation/integrations/contribution-inventory.d.mts +3 -2
  466. package/foundation/integrations/contribution-inventory.mjs +27 -24
  467. package/foundation/integrations/contribution-inventory.test.mjs +86 -27
  468. package/foundation/integrations/integration-warnings.d.mts +9 -2
  469. package/foundation/integrations/integration-warnings.mjs +51 -26
  470. package/foundation/integrations/integration-warnings.test.mjs +74 -1
  471. package/foundation/integrations/integrations.d.mts +3 -0
  472. package/foundation/integrations/integrations.mjs +11 -97
  473. package/foundation/integrations/provider-ledger.test.mjs +275 -0
  474. package/foundation/integrations/provider-resolution.d.mts +152 -0
  475. package/foundation/integrations/provider-resolution.mjs +576 -0
  476. package/foundation/integrations/provider-resolution.test.mjs +369 -0
  477. package/foundation/integrations/theme-descriptor.d.mts +8 -0
  478. package/foundation/integrations/theme-descriptor.mjs +44 -0
  479. package/foundation/integrations/validate-contributions.mjs +121 -29
  480. package/foundation/response/base.d.ts +8 -4
  481. package/foundation/response/error-codes.d.mts +3 -1
  482. package/foundation/response/error-codes.d.ts +2 -0
  483. package/foundation/response/error-codes.doc.mjs +13 -4
  484. package/foundation/response/error-codes.mjs +8 -2
  485. package/foundation/response/error-codes.test.mjs +137 -10
  486. package/foundation/response/json-contract.test.mjs +11 -0
  487. package/foundation/response/json.d.mts +4 -2
  488. package/foundation/response/json.mjs +8 -10
  489. package/foundation/response/response-types.doc.mjs +22 -17
  490. package/foundation/response/response-types.doc.test.mjs +158 -0
  491. package/foundation/response/response.doc.mjs +1 -1
  492. package/foundation/text/string-utils.mjs +22 -10
  493. package/foundation/xle/expand.d.mts +2 -0
  494. package/foundation/xle/expand.mjs +4 -3
  495. package/foundation/xle/expand.test.mjs +54 -0
  496. package/foundation/xle/xle.test.mjs +13 -0
  497. package/package.json +10 -9
  498. package/assets/templates/themes/manifest.json +0 -95
  499. package/clients/cli/lib/update-check.mjs +0 -83
  500. package/clients/cli/lib/update-check.test.mjs +0 -137
  501. package/clients/cli/update-hint-commands.test.mjs +0 -54
@@ -10,24 +10,47 @@
10
10
  * @output Catalog access, the compiler input for a topic, and the compiled
11
11
  * node for it: lowered (overlaid, extensions merged, keys stamped) or linked
12
12
  * (token references resolved too), memoized per catalog.
13
- * @position Sits beside docs.mjs (api/docs/). Loads authored files and hands
14
- * them to foundation/doc-compiler, so no leaf, doctor check or search loads,
15
- * merges, or resolves docs on its own. Discovery itself lives in
13
+ * @position Sits beside docs.mjs (api/docs/). Hands topics to
14
+ * foundation/doc-compiler (which loads their files) and memoizes the nodes
15
+ * per catalog, so no leaf, doctor check or search loads, merges, or resolves
16
+ * docs on its own. Discovery itself lives in
16
17
  * foundation/discovery/docs-discovery, which the catalog comes from.
17
18
  */
18
19
 
19
- import * as fs from 'node:fs';
20
- import * as path from 'node:path';
21
- import {pathToFileURL} from 'node:url';
22
20
  import {Project} from '../../foundation/config/project.mjs';
23
21
  import {DocsCatalog} from '../../foundation/discovery/docs-discovery.mjs';
24
22
  import {
25
23
  linkReferenceTopic,
26
24
  lowerReferenceTopic,
27
25
  } from '../../foundation/doc-compiler/compile.mjs';
26
+ import {
27
+ deepFreeze,
28
+ loadTopicFile,
29
+ loadTopicInput,
30
+ OVERLAY_LANGUAGES,
31
+ overlayLanguages,
32
+ } from '../../foundation/doc-compiler/read.mjs';
33
+ import {
34
+ buildDocsTree,
35
+ loadTreeInputs,
36
+ } from '../../foundation/doc-compiler/tree.mjs';
37
+ import {sortDiagnostics} from '../../foundation/doc-compiler/diagnostics.mjs';
38
+ import {
39
+ linkBlocks,
40
+ parseLinkTarget,
41
+ } from '../../foundation/doc-compiler/links.mjs';
42
+ import {
43
+ createDocId,
44
+ normalizeProviderId,
45
+ } from '../../foundation/identity/provider-identity.mjs';
46
+ import {
47
+ cliDocIndex,
48
+ cliDocSection,
49
+ } from '../../foundation/discovery/cli-self-docs.mjs';
28
50
  import {AstryxError} from '../error.mjs';
29
51
  import {ERROR_CODES} from '../../foundation/response/error-codes.mjs';
30
- import {parseDoc} from '../../authoring/doctypes/parse.mjs';
52
+
53
+ export {OVERLAY_LANGUAGES, overlayLanguages};
31
54
 
32
55
  /**
33
56
  * The project's topics: the built-in ones plus whatever the configured
@@ -51,141 +74,403 @@ export async function loadDocsCatalog(cwd = process.cwd()) {
51
74
  }
52
75
  }
53
76
 
54
- /** The localized overlays a docs read can apply. */
55
- export const OVERLAY_LANGUAGES = ['zh', 'dense'];
77
+ /**
78
+ * The overlay a read applies: none for the authored language.
79
+ * @param {string | null | undefined} lang
80
+ * @returns {string | null}
81
+ */
82
+ function overlayLanguage(lang) {
83
+ return lang && lang !== 'en' ? lang : null;
84
+ }
85
+
86
+ /** @type {WeakMap<DocsCatalog, Map<string, Promise<import('../../foundation/doc-compiler/compile.mjs').CompiledReferenceNode>>>} */
87
+ const loweredByCatalog = new WeakMap();
88
+
89
+ /**
90
+ * One topic, lowered for `lang` with its links as written: overlaid,
91
+ * extensions merged, keys stamped. Memoized per catalog, so a read that
92
+ * references a topic twice loads it once. Every read of the catalog shares the
93
+ * memoized node, so it is frozen; the lenses hand readers copies.
94
+ * @param {DocsCatalog} catalog
95
+ * @param {import('../../foundation/discovery/docs-discovery.mjs').DocsTopicEntry} entry
96
+ * @param {string | null} [lang]
97
+ * @returns {Promise<import('../../foundation/doc-compiler/compile.mjs').CompiledReferenceNode>}
98
+ */
99
+ function lowerRawTopic(catalog, entry, lang = null) {
100
+ const overlay = overlayLanguage(lang);
101
+ let cache = loweredByCatalog.get(catalog);
102
+ if (!cache) {
103
+ cache = new Map();
104
+ loweredByCatalog.set(catalog, cache);
105
+ }
106
+ const key = `${entry.name.toLowerCase()}\u0000${overlay ?? ''}`;
107
+ let lowered = cache.get(key);
108
+ if (!lowered) {
109
+ lowered = loadTopicInput(entry, overlay).then(input =>
110
+ deepFreeze(lowerReferenceTopic(input)),
111
+ );
112
+ cache.set(key, lowered);
113
+ }
114
+ return lowered;
115
+ }
116
+
117
+ /**
118
+ * @typedef {import('../../foundation/doc-compiler/tree.mjs').DocsTree} DocsTree
119
+ * @typedef {import('../../foundation/doc-compiler/tree.mjs').TreeNode} TreeNode
120
+ * @typedef {import('../../foundation/doc-compiler/links.mjs').LinkProblem} LinkProblem
121
+ * @typedef {import('../../foundation/doc-compiler/links.mjs').LinkResolver} LinkResolver
122
+ * @typedef {import('../../foundation/discovery/docs-discovery.mjs').DocsTopicEntry} DocsTopicEntry
123
+ */
124
+
125
+ /** @type {WeakMap<DocsCatalog, Map<string, Promise<{node: import('../../foundation/doc-compiler/compile.mjs').CompiledReferenceNode, problems: LinkProblem[]}>>>} */
126
+ const linkedByCatalog = new WeakMap();
127
+
128
+ /**
129
+ * The CLI's own topics alone, for a check that runs without a project.
130
+ * @returns {DocsCatalog}
131
+ */
132
+ export function builtinCatalog() {
133
+ return DocsCatalog.fromBuiltins();
134
+ }
56
135
 
57
136
  /**
58
- * Where the `lang` overlay of a doc file lives: `{topic}.doc.{lang}.mjs`.
59
- * @param {string} docPath
60
- * @param {string} lang
137
+ * The provider id a topic's (or an extension's) links resolve against: its
138
+ * owner's.
139
+ * @param {{providerId?: string, package: string}} entry
61
140
  * @returns {string}
62
141
  */
63
- function overlayPath(docPath, lang) {
64
- return path.join(
65
- path.dirname(docPath),
66
- `${path.basename(docPath, '.doc.mjs')}.doc.${lang}.mjs`,
67
- );
142
+ function providerOf(entry) {
143
+ return normalizeProviderId(entry.providerId ?? entry.package);
68
144
  }
69
145
 
70
146
  /**
71
- * The overlay languages a topic ships for its own file or any extension.
72
- * @param {import('../../foundation/discovery/docs-discovery.mjs').DocsTopicEntry} entry
73
- * @returns {string[]}
147
+ * One topic, lowered for `lang` with every link between docs resolved
148
+ * (spec:AST-047 FR9): an inline `{@link <target>}` reads as the command that
149
+ * opens its doc, and a `reference` or `workflow` block carries the doc it
150
+ * names. Memoized per catalog and frozen, like the lowered node.
151
+ * @param {DocsCatalog} catalog
152
+ * @param {DocsTopicEntry} entry
153
+ * @param {string | null} [lang]
154
+ * @returns {Promise<import('../../foundation/doc-compiler/compile.mjs').CompiledReferenceNode>}
74
155
  */
75
- export function overlayLanguages(entry) {
76
- const files = [entry.path, ...entry.extensions.map(ext => ext.path)];
77
- return OVERLAY_LANGUAGES.filter(lang =>
78
- files.some(file => fs.existsSync(overlayPath(file, lang))),
79
- );
156
+ export async function lowerTopic(catalog, entry, lang = null) {
157
+ return (await linkTopic(catalog, entry, lang)).node;
80
158
  }
81
159
 
82
160
  /**
83
- * The overlay a read applies: none for the authored language.
84
- * @param {string | null | undefined} lang
85
- * @returns {string | null}
161
+ * Each link in a topic that names no doc.
162
+ * @param {DocsCatalog} catalog
163
+ * @param {DocsTopicEntry} entry
164
+ * @returns {Promise<LinkProblem[]>}
86
165
  */
87
- function overlayLanguage(lang) {
88
- return lang && lang !== 'en' ? lang : null;
166
+ export async function topicLinkProblems(catalog, entry) {
167
+ return (await linkTopic(catalog, entry, null)).problems;
89
168
  }
90
169
 
91
170
  /**
92
- * Load one authored file and the overlay for `lang`. A failure is recorded on
93
- * the result, not thrown, so the compiler reports it in reading order.
94
- * @param {string} docPath
171
+ * @param {DocsCatalog} catalog
172
+ * @param {DocsTopicEntry} entry
95
173
  * @param {string | null} lang
96
- * @returns {Promise<import('../../foundation/doc-compiler/compile.mjs').AuthoredFile>}
97
174
  */
98
- async function loadAuthoredFile(docPath, lang) {
99
- const file = path.basename(docPath);
100
- let doc;
101
- try {
102
- const mod = await import(pathToFileURL(docPath).href);
103
- doc = parseDoc(mod.docs ?? mod.default, file);
104
- } catch (error) {
105
- return {file, error};
175
+ function linkTopic(catalog, entry, lang) {
176
+ let cache = linkedByCatalog.get(catalog);
177
+ if (!cache) {
178
+ cache = new Map();
179
+ linkedByCatalog.set(catalog, cache);
106
180
  }
107
- if (!lang) return {file, doc};
108
- const translationPath = overlayPath(docPath, lang);
109
- if (!fs.existsSync(translationPath)) return {file, doc};
110
- try {
111
- const translationMod = await import(pathToFileURL(translationPath).href);
112
- return {
113
- file,
114
- doc,
115
- overlay: translationMod.docsZh || translationMod.docsDense || null,
116
- };
117
- } catch (overlayError) {
118
- return {file, doc, overlayError};
181
+ const key = `${entry.name.toLowerCase()}\u0000${overlayLanguage(lang) ?? ''}`;
182
+ let linked = cache.get(key);
183
+ if (!linked) {
184
+ linked = (async () => {
185
+ const raw = await lowerRawTopic(catalog, entry, lang);
186
+ /** @type {Map<string, LinkResolver>} */
187
+ const resolvers = new Map();
188
+ /** @param {string} provider */
189
+ const resolverFor = async provider => {
190
+ let resolve = resolvers.get(provider);
191
+ if (!resolve) {
192
+ resolve = await linkResolver(catalog, provider);
193
+ resolvers.set(provider, resolve);
194
+ }
195
+ return resolve;
196
+ };
197
+ /** @type {LinkProblem[]} */
198
+ const problems = [];
199
+ const sections = [];
200
+ // Each section resolves its links against the provider that wrote it:
201
+ // an extension's sections against the extension's provider, never the
202
+ // base topic's.
203
+ for (const section of raw.doc.sections) {
204
+ const provider = raw.sectionProviders?.[section.id];
205
+ const linked = await linkBlocks(
206
+ section.content,
207
+ await resolverFor(
208
+ provider == null ? providerOf(entry) : normalizeProviderId(provider),
209
+ ),
210
+ {section: section.id ?? section.title},
211
+ );
212
+ problems.push(...linked.problems);
213
+ sections.push({...section, content: linked.content});
214
+ }
215
+ return {
216
+ node: deepFreeze({...raw, doc: {...raw.doc, sections}}),
217
+ problems,
218
+ };
219
+ })();
220
+ cache.set(key, linked);
119
221
  }
222
+ return linked;
120
223
  }
121
224
 
225
+ /** @type {WeakMap<DocsCatalog, Promise<DocsTree>>} */
226
+ const treesByCatalog = new WeakMap();
227
+
122
228
  /**
123
- * Everything the compiler needs for one topic, read from disk.
124
- * @param {import('../../foundation/discovery/docs-discovery.mjs').DocsTopicEntry} entry
125
- * @param {string | null} lang
126
- * @returns {Promise<import('../../foundation/doc-compiler/compile.mjs').ReferenceTopicInput>}
127
- */
128
- async function loadCompilerInput(entry, lang) {
129
- const extensions = [];
130
- for (const extension of entry.extensions) {
131
- extensions.push({
132
- ...(await loadAuthoredFile(extension.path, lang)),
133
- provider: extension.package,
229
+ * The project's docs tree: the CLI's own docs plus the namespace docs and
230
+ * placed guides the configured integrations ship (spec:AST-046), built once
231
+ * per catalog. Without integration docs it is the CLI's tree, built once per
232
+ * process.
233
+ * @param {DocsCatalog} catalog
234
+ * @param {{fresh?: boolean}} [options] `fresh`: reread the CLI's tree files
235
+ * @returns {Promise<DocsTree>}
236
+ */
237
+ export function projectTree(catalog, {fresh = false} = {}) {
238
+ let tree = treesByCatalog.get(catalog);
239
+ if (!tree || fresh) {
240
+ tree = buildProjectTree(catalog, fresh);
241
+ treesByCatalog.set(catalog, tree);
242
+ }
243
+ return tree;
244
+ }
245
+
246
+ /** @type {ReturnType<typeof loadTreeInputs> | undefined} */
247
+ let cliTreeInputs;
248
+
249
+ /**
250
+ * Every flat topic in the catalog, as the tree's Unorganized level reads it.
251
+ * @param {DocsCatalog} catalog
252
+ * @returns {Promise<import('../../foundation/doc-compiler/tree.mjs').TreeTopicInput[]>}
253
+ */
254
+ async function flatTopicInputs(catalog) {
255
+ /** @type {import('../../foundation/doc-compiler/tree.mjs').TreeTopicInput[]} */
256
+ const topics = [];
257
+ for (const entry of catalog.entries()) {
258
+ const aliases = catalog.aliasesOf(entry);
259
+ let {title, description} = entry;
260
+ if (title == null || description == null) {
261
+ try {
262
+ const file = await loadTopicFile(entry.path, null);
263
+ title ??= file.doc?.title;
264
+ description ??= file.doc?.description;
265
+ } catch {
266
+ // A topic that does not load is reported where it is read.
267
+ }
268
+ }
269
+ topics.push({
270
+ provider: entry.package,
271
+ providerId: entry.providerId ?? entry.package,
272
+ name: entry.name,
273
+ title: title ?? entry.name,
274
+ summary: description ?? '',
275
+ source: `${entry.package}:${entry.name}`,
276
+ ...(aliases.length > 0 ? {aliases} : {}),
134
277
  });
135
278
  }
279
+ return topics;
280
+ }
281
+
282
+ /**
283
+ * The CLI's tree, each configured integration's namespaces and guides, and
284
+ * every flat topic in the generated Unorganized level.
285
+ * @param {DocsCatalog} catalog
286
+ * @param {boolean} fresh
287
+ * @returns {Promise<DocsTree>}
288
+ */
289
+ async function buildProjectTree(catalog, fresh) {
290
+ const added = catalog.treeInputs;
291
+ if (fresh || !cliTreeInputs) cliTreeInputs = loadTreeInputs();
292
+ const cli = await cliTreeInputs;
293
+ const result = buildDocsTree({
294
+ namespaces: [...cli.namespaces, ...added.flatMap(each => each.namespaces)],
295
+ docs: [...cli.docs, ...added.flatMap(each => each.guides)],
296
+ topics: await flatTopicInputs(catalog),
297
+ });
136
298
  return {
137
- id: entry.name,
138
- provider: entry.package,
139
- replaces: entry.replaces ?? null,
140
- lang,
141
- base: await loadAuthoredFile(entry.path, lang),
142
- extensions,
299
+ ...result,
300
+ diagnostics: sortDiagnostics([...cli.diagnostics, ...result.diagnostics]),
143
301
  };
144
302
  }
145
303
 
146
- /** @type {WeakMap<DocsCatalog, Map<string, Promise<import('../../foundation/doc-compiler/compile.mjs').CompiledReferenceNode>>>} */
147
- const loweredByCatalog = new WeakMap();
304
+ /** @type {WeakMap<DocsTree, Map<string, TreeNode>>} */
305
+ const identitiesByTree = new WeakMap();
306
+
307
+ /** @param {string} provider @param {string} kind @param {string} name */
308
+ function identityKey(provider, kind, name) {
309
+ return `${provider}\u0000${kind}\u0000${kind === 'generic' ? name.toLowerCase() : name}`;
310
+ }
148
311
 
149
312
  /**
150
- * One topic, lowered for `lang`: overlaid, extensions merged, keys stamped.
151
- * Memoized per catalog, so a read that references a topic twice loads it once.
152
- * Every read of the catalog shares the memoized node, so it is frozen; the
153
- * lenses hand readers copies.
313
+ * Every tree node with an identity, by provider id, kind, and name.
314
+ * @param {DocsTree} tree
315
+ */
316
+ function identitiesOf(tree) {
317
+ let index = identitiesByTree.get(tree);
318
+ if (!index) {
319
+ index = new Map();
320
+ for (const node of tree.nodes.values()) {
321
+ if (node.id == null) continue;
322
+ let provider = node.providerId;
323
+ try {
324
+ provider = normalizeProviderId(node.providerId);
325
+ } catch {
326
+ // Kept as given; a link names it the same way.
327
+ }
328
+ index.set(identityKey(provider, node.kind, node.name), node);
329
+ }
330
+ identitiesByTree.set(tree, index);
331
+ }
332
+ return index;
333
+ }
334
+
335
+ /**
336
+ * How a doc's links find their targets (spec:AST-047 FR9): a doc in the
337
+ * project's docs tree by its identity, or a flat topic by its provider and
338
+ * name. A target that matches neither is a problem, never a guess.
154
339
  * @param {DocsCatalog} catalog
155
- * @param {import('../../foundation/discovery/docs-discovery.mjs').DocsTopicEntry} entry
156
- * @param {string | null} [lang]
157
- * @returns {Promise<import('../../foundation/doc-compiler/compile.mjs').CompiledReferenceNode>}
340
+ * @param {string} fromProvider the provider id of the doc the links sit in
341
+ * @returns {Promise<LinkResolver>}
158
342
  */
159
- export function lowerTopic(catalog, entry, lang = null) {
160
- const overlay = overlayLanguage(lang);
161
- let cache = loweredByCatalog.get(catalog);
162
- if (!cache) {
163
- cache = new Map();
164
- loweredByCatalog.set(catalog, cache);
343
+ export async function linkResolver(catalog, fromProvider) {
344
+ const identities = identitiesOf(await projectTree(catalog));
345
+ return async target => {
346
+ const parsed = parseLinkTarget(target);
347
+ if ('error' in parsed) return {problem: parsed.error};
348
+ let provider;
349
+ try {
350
+ provider = normalizeProviderId(parsed.provider ?? fromProvider);
351
+ } catch {
352
+ return {
353
+ problem: `"${target}" names "${parsed.provider}", which is not a provider id: an npm package name, or the \`providerId\` its manifest declares`,
354
+ };
355
+ }
356
+ const node = identities.get(identityKey(provider, parsed.kind, parsed.name));
357
+ if (node) {
358
+ return {
359
+ target,
360
+ id: /** @type {string} */ (node.id),
361
+ route: node.route,
362
+ title: node.title,
363
+ summary: node.summary,
364
+ command: `astryx docs ${node.route}`,
365
+ };
366
+ }
367
+ if (parsed.kind === 'generic') {
368
+ const entry = catalog.resolve(parsed.name);
369
+ if (
370
+ entry &&
371
+ !entry.tree &&
372
+ (providerOf(entry) === provider ||
373
+ catalog.aliasesOf(entry).includes(parsed.name.toLowerCase()))
374
+ ) {
375
+ let title = entry.title ?? entry.name;
376
+ let summary = entry.description ?? '';
377
+ try {
378
+ const raw = await lowerRawTopic(catalog, entry);
379
+ title = raw.doc.title ?? title;
380
+ summary = raw.doc.description ?? summary;
381
+ } catch {
382
+ // The topic budget check reports a topic that does not load; the
383
+ // link still opens it.
384
+ }
385
+ return {
386
+ target,
387
+ id: createDocId(provider, 'generic', parsed.name),
388
+ route: entry.name,
389
+ title,
390
+ summary,
391
+ command: `astryx docs ${entry.name}`,
392
+ };
393
+ }
394
+ }
395
+ return {
396
+ problem: `"${target}" names no doc. Find it with \`astryx search ${parsed.name} --type doc\`, then name it as \`[<provider>:]<kind>:<name>\`.`,
397
+ };
398
+ };
399
+ }
400
+
401
+ /**
402
+ * Every link in the project's docs that names no doc: in each topic, each
403
+ * guide the tree places, and each typed doc.
404
+ * @param {DocsCatalog} catalog
405
+ * @param {DocsTree} tree
406
+ * @param {{owner?: string}} [options] `owner`: only the docs this package owns
407
+ * @returns {Promise<string[]>}
408
+ */
409
+ export async function docsLinkProblems(catalog, tree, {owner} = {}) {
410
+ /** @type {string[]} */
411
+ const problems = [];
412
+ /** @param {string} where @param {LinkProblem[]} found */
413
+ const note = (where, found) => {
414
+ for (const problem of found) {
415
+ problems.push(
416
+ `${where}${problem.section ? ` \u00a7 ${problem.section}` : ''}: ${problem.message}`,
417
+ );
418
+ }
419
+ };
420
+ for (const entry of catalog.entries()) {
421
+ // A package owns a topic it wrote, and the sections it adds to another
422
+ // package's topic.
423
+ if (
424
+ owner != null &&
425
+ entry.package !== owner &&
426
+ !entry.extensions.some(extension => extension.package === owner)
427
+ ) {
428
+ continue;
429
+ }
430
+ try {
431
+ note(entry.name, await topicLinkProblems(catalog, entry));
432
+ } catch {
433
+ // The topic budget check reports a topic that does not load.
434
+ }
165
435
  }
166
- const key = `${entry.name.toLowerCase()}\u0000${overlay ?? ''}`;
167
- let lowered = cache.get(key);
168
- if (!lowered) {
169
- lowered = loadCompilerInput(entry, overlay).then(input =>
170
- deepFreeze(lowerReferenceTopic(input)),
171
- );
172
- cache.set(key, lowered);
436
+ for (const node of tree.nodes.values()) {
437
+ if (owner != null && node.provider !== owner) continue;
438
+ if (node.kind === 'generic' && node.ref?.topicFile) {
439
+ try {
440
+ note(node.route, await topicLinkProblems(catalog, guideEntry(node)));
441
+ } catch {
442
+ // As above.
443
+ }
444
+ } else if (node.ref?.selfDoc) {
445
+ note(node.route, (await nodeContent(catalog, tree, node)).problems);
446
+ }
173
447
  }
174
- return lowered;
448
+ return problems;
175
449
  }
176
450
 
177
451
  /**
178
- * Freeze a value and everything in it.
179
- * @template T
180
- * @param {T} value
181
- * @returns {T}
452
+ * What \`astryx doctor integration docs\` checks in one integration's docs: the
453
+ * docs tree they build beside the CLI's (namespaces, placements, routes) and
454
+ * every link in them (spec:AST-046, spec:AST-047).
455
+ * @param {{name: string}} integration
456
+ * @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
457
+ * @returns {Promise<string[]>}
182
458
  */
183
- function deepFreeze(value) {
184
- if (value !== null && typeof value === 'object' && !Object.isFrozen(value)) {
185
- Object.freeze(value);
186
- for (const child of Object.values(value)) deepFreeze(child);
187
- }
188
- return value;
459
+ export async function packageDocsProblems(integration, discovered) {
460
+ const catalog = DocsCatalog.fromBuiltins();
461
+ for (const record of discovered.records) catalog.add(record);
462
+ catalog.addTreeInputs({
463
+ namespaces: discovered.namespaces.map(input => ({...input, rank: 1})),
464
+ guides: discovered.guides.map(input => ({...input, rank: 1})),
465
+ });
466
+ const tree = await projectTree(catalog);
467
+ const problems = tree.diagnostics
468
+ .filter(d => d.severity === 'error' && d.provider === integration.name)
469
+ .map(d => `${d.source ?? integration.name}: ${d.message}`);
470
+ problems.push(
471
+ ...(await docsLinkProblems(catalog, tree, {owner: integration.name})),
472
+ );
473
+ return problems;
189
474
  }
190
475
 
191
476
  /**
@@ -217,40 +502,295 @@ export async function compileTopic(catalog, entry, lang = null) {
217
502
  }
218
503
 
219
504
  /**
220
- * Resolve `topic` against the project's catalog (throwing `ERR_UNKNOWN_TOPIC`
221
- * when unmatched) and lower it with any --dense/--zh overlay and any
222
- * integration extension applied. Shared by the leaves so topic normalization
223
- * and unknown-topic handling live in exactly one place.
224
- *
225
- * @param {string} topic
226
- * @param {object} [options]
227
- * @param {string} [options.lang]
228
- * @param {boolean} [options.zh]
229
- * @param {boolean} [options.dense]
230
- * @param {string} [options.cwd]
231
- * @returns {Promise<{
232
- * catalog: DocsCatalog,
233
- * node: import('../../foundation/doc-compiler/compile.mjs').CompiledReferenceNode,
234
- * lang: string | null,
235
- * }>}
505
+ * A guide the docs tree places, as a topic entry the topic readers open by its
506
+ * route. It is never a flat topic: `astryx docs <route>` is its only name.
507
+ * @param {import('../../foundation/doc-compiler/tree.mjs').TreeNode} node
508
+ * @returns {import('../../foundation/discovery/docs-discovery.mjs').DocsTopicEntry}
236
509
  */
237
- export async function resolveTopicDocs(topic, options = {}) {
238
- const {lang = null, zh = false, dense = false, cwd} = options;
239
- const effectiveLang = lang || (dense ? 'dense' : zh ? 'zh' : null);
240
- const catalog = await loadDocsCatalog(cwd);
510
+ export function guideEntry(node) {
511
+ return {
512
+ name: node.route,
513
+ route: node.route,
514
+ package: node.provider,
515
+ providerId: node.providerId,
516
+ path: node.ref.topicFile,
517
+ extensions: [],
518
+ tree: true,
519
+ parent: node.parent ?? undefined,
520
+ };
521
+ }
241
522
 
242
- // A public API caller could pass a non-string topic; `resolve` answers
243
- // undefined for one, which lands on the same stable code as an unknown name
244
- // rather than a raw TypeError (which downgrades to ERR_UNKNOWN).
245
- const entry = catalog.resolve(topic);
246
- if (!entry) {
247
- throw new AstryxError(
248
- `Unknown topic "${String(topic)}"`,
249
- catalog.names().map(t => ({name: t, reason: 'available topic'})),
250
- ERROR_CODES.ERR_UNKNOWN_TOPIC,
523
+ /** @type {WeakMap<DocsTree, ReturnType<typeof cliDocIndex>>} */
524
+ const typedDocIndexes = new WeakMap();
525
+
526
+ /**
527
+ * What a typed doc in the docs tree prints: its content, with every link to
528
+ * another doc resolved (spec:AST-047 FR9). A namespace has no content. The
529
+ * CLI's doc modules are discovery, so this lives in the adapter
530
+ * (architecture:cli-surface INV21).
531
+ * @param {DocsCatalog} catalog
532
+ * @param {DocsTree} tree
533
+ * @param {TreeNode} node
534
+ * @returns {Promise<{content: any[], problems: LinkProblem[]}>}
535
+ */
536
+ export async function nodeContent(catalog, tree, node) {
537
+ if (!node.ref?.selfDoc) return {content: [], problems: []};
538
+ let index = typedDocIndexes.get(tree);
539
+ if (!index) {
540
+ index = cliDocIndex(
541
+ [...tree.nodes.values()].flatMap(each =>
542
+ each.ref?.selfDoc ? [each.ref.selfDoc] : [],
543
+ ),
251
544
  );
545
+ typedDocIndexes.set(tree, index);
252
546
  }
547
+ const resolve = await linkResolver(catalog, node.providerId);
548
+ return linkBlocks(cliDocSection(node.ref.selfDoc, index).content, resolve);
549
+ }
253
550
 
551
+ /**
552
+ * The command that opens the level a topic sits in when the tree cannot say:
553
+ * a guide's parent namespace, or the topic list.
554
+ * @param {import('../../foundation/discovery/docs-discovery.mjs').DocsTopicEntry} entry
555
+ * @returns {import('./docs.type.mjs').DocsCommand}
556
+ */
557
+ export function topicUp(entry) {
558
+ return entry.tree && entry.parent
559
+ ? `astryx docs ${entry.parent}`
560
+ : 'astryx docs';
561
+ }
562
+
563
+ /**
564
+ * The moves from a node's place in the tree (spec:AST-047 FR2, FR4): up to its
565
+ * parent (the topic list, at the top), and across to the nodes before and
566
+ * after it in its parent's slot.
567
+ * @param {DocsTree} tree
568
+ * @param {TreeNode} node
569
+ * @returns {import('./docs.type.mjs').DocsLinks}
570
+ */
571
+ export function placeLinks(tree, node) {
572
+ /** @type {import('./docs.type.mjs').DocsLinks} */
573
+ const links = {
574
+ up: node.parent == null ? 'astryx docs' : `astryx docs ${node.parent}`,
575
+ };
576
+ const parent = node.parent == null ? undefined : tree.get(node.parent);
577
+ const siblings =
578
+ parent?.slots.find(slot => slot.children.includes(node.route))?.children ??
579
+ [];
580
+ const at = siblings.indexOf(node.route);
581
+ if (at > 0) links.previous = `astryx docs ${siblings[at - 1]}`;
582
+ if (at !== -1 && at < siblings.length - 1) {
583
+ links.next = `astryx docs ${siblings[at + 1]}`;
584
+ }
585
+ return links;
586
+ }
587
+
588
+ /**
589
+ * The moves a topic read offers: from its place in the tree, where a guide
590
+ * sits in its namespace and a flat topic in the Unorganized level.
591
+ * @param {DocsCatalog} catalog
592
+ * @param {import('../../foundation/discovery/docs-discovery.mjs').DocsTopicEntry} entry
593
+ * @returns {Promise<import('./docs.type.mjs').DocsLinks>}
594
+ */
595
+ export async function topicLinks(catalog, entry) {
596
+ const tree = await projectTree(catalog);
597
+ const node = tree.get(entry.tree ? (entry.route ?? entry.name) : entry.name);
598
+ const placed =
599
+ node && (entry.tree ? node.kind === 'generic' : node.ref?.flatTopic === entry.name);
600
+ return placed ? placeLinks(tree, node) : {up: topicUp(entry)};
601
+ }
602
+
603
+ /**
604
+ * What a docs argument names: a topic (a flat one, or a guide the docs tree
605
+ * places), a namespace or typed doc in the tree, or nothing, as nameOwner
606
+ * decides.
607
+ * @param {unknown} topic
608
+ * @param {{cwd?: string}} [options]
609
+ * @returns {Promise<
610
+ * | {kind: 'topic', catalog: DocsCatalog, entry: import('../../foundation/discovery/docs-discovery.mjs').DocsTopicEntry}
611
+ * | {kind: 'node', catalog: DocsCatalog, tree: import('../../foundation/doc-compiler/tree.mjs').DocsTree, node: import('../../foundation/doc-compiler/tree.mjs').TreeNode}
612
+ * | {kind: 'unknown', catalog: DocsCatalog}
613
+ * >}
614
+ */
615
+ export async function resolveDocsArgument(topic, {cwd} = {}) {
616
+ const catalog = await loadDocsCatalog(cwd);
617
+ if (typeof topic !== 'string' || topic === '')
618
+ return {kind: 'unknown', catalog};
619
+ const tree = await projectTree(catalog);
620
+ const owner = nameOwner(tree, catalog, topic);
621
+ if (owner == null) return {kind: 'unknown', catalog};
622
+ if (owner.kind === 'topic') return {kind: 'topic', catalog, entry: owner.entry};
623
+ return treeArgument(catalog, tree, owner.node);
624
+ }
625
+
626
+ /**
627
+ * Who answers to a name a reader types (spec:AST-046 FR5, FR11). The tree
628
+ * decides first: the node at that route, compared without case, holds it. A
629
+ * name with no node of its own is a topic's other name (its `replaces`
630
+ * alias), and that topic answers, unless the tree gave the topic's own route
631
+ * to another doc. Reads, the topic list, and search all ask this, so they
632
+ * agree on every name.
633
+ * @param {DocsTree} tree
634
+ * @param {DocsCatalog} catalog
635
+ * @param {string} name
636
+ * @returns {{kind: 'topic', entry: import('../../foundation/discovery/docs-discovery.mjs').DocsTopicEntry} | {kind: 'node', node: TreeNode} | null}
637
+ */
638
+ export function nameOwner(tree, catalog, name) {
639
+ const node = tree.get(name) ?? tree.getFolded?.(name);
640
+ if (node != null) {
641
+ if (node.ref?.flatTopic == null) return {kind: 'node', node};
642
+ const entry = catalog.resolve(node.ref.flatTopic);
643
+ return entry == null ? null : {kind: 'topic', entry};
644
+ }
645
+ const entry = catalog.resolve(name);
646
+ if (entry == null) return null;
647
+ const owner = routeOwner(tree, entry);
648
+ return owner == null ? {kind: 'topic', entry} : {kind: 'node', node: owner};
649
+ }
650
+
651
+ /**
652
+ * Whether a topic answers to its own name: what the topic list and search
653
+ * offer must open that topic.
654
+ * @param {DocsTree} tree
655
+ * @param {DocsCatalog} catalog
656
+ * @param {import('../../foundation/discovery/docs-discovery.mjs').DocsTopicEntry} entry
657
+ * @returns {boolean}
658
+ */
659
+ export function holdsOwnName(tree, catalog, entry) {
660
+ const owner = nameOwner(tree, catalog, entry.name);
661
+ return (
662
+ owner?.kind === 'topic' &&
663
+ owner.entry.name === entry.name &&
664
+ owner.entry.package === entry.package
665
+ );
666
+ }
667
+
668
+ /**
669
+ * What a tree node opens as: a guide the tree places reads like a topic; a
670
+ * namespace or a typed doc is a node.
671
+ * @param {DocsCatalog} catalog
672
+ * @param {DocsTree} tree
673
+ * @param {TreeNode} node
674
+ * @returns {{kind: 'topic', catalog: DocsCatalog, entry: import('../../foundation/discovery/docs-discovery.mjs').DocsTopicEntry} | {kind: 'node', catalog: DocsCatalog, tree: DocsTree, node: TreeNode}}
675
+ */
676
+ function treeArgument(catalog, tree, node) {
677
+ if (node.kind === 'generic') {
678
+ return {kind: 'topic', catalog, entry: guideEntry(node)};
679
+ }
680
+ return {kind: 'node', catalog, tree, node};
681
+ }
682
+
683
+ /**
684
+ * The doc that took a flat topic's route in the docs tree, when it is not
685
+ * that topic (spec:AST-046 FR11): the CLI keeps its routes, such as `cli`
686
+ * and `unorganized`, and a namespace keeps its route over a topic of the same
687
+ * name. Null when the topic owns its route, or the tree has no node there.
688
+ * @param {DocsTree} tree
689
+ * @param {import('../../foundation/discovery/docs-discovery.mjs').DocsTopicEntry} entry
690
+ * @returns {TreeNode | null}
691
+ */
692
+ export function routeOwner(tree, entry) {
693
+ const node = tree.get(entry.name) ?? tree.getFolded?.(entry.name);
694
+ if (node == null) return null;
695
+ return node.ref?.flatTopic === entry.name && node.provider === entry.package
696
+ ? null
697
+ : node;
698
+ }
699
+
700
+ /**
701
+ * The error for a docs argument that names nothing. For a route, it suggests
702
+ * the children of the deepest namespace the route reaches; otherwise, every
703
+ * topic and every top-level namespace.
704
+ * @param {unknown} topic
705
+ * @param {DocsCatalog} catalog
706
+ * @returns {Promise<AstryxError>}
707
+ */
708
+ export async function unknownTopicError(topic, catalog) {
709
+ const tree = await projectTree(catalog);
710
+ /** @type {Array<{name: string, reason: string}>} */
711
+ let suggestions = [];
712
+ if (typeof topic === 'string' && topic.includes('/')) {
713
+ const parts = topic.split('/');
714
+ for (
715
+ let depth = parts.length - 1;
716
+ depth > 0 && suggestions.length === 0;
717
+ depth--
718
+ ) {
719
+ const near = tree.get(parts.slice(0, depth).join('/'));
720
+ if (near) {
721
+ suggestions = near.slots.flatMap(slot =>
722
+ slot.children.map(route => ({
723
+ name: route,
724
+ reason: tree.get(route)?.summary ?? '',
725
+ })),
726
+ );
727
+ }
728
+ }
729
+ }
730
+ if (suggestions.length === 0 && typeof topic === 'string') {
731
+ // The docs whose own name it is (`doctor` is cli/commands/doctor), or whose
732
+ // route it spells with hyphens (`cli-integrations`, the guide's name before
733
+ // it moved to cli/integrations).
734
+ const wanted = topic.toLowerCase();
735
+ suggestions = [...tree.nodes.values()]
736
+ .filter(
737
+ node =>
738
+ !node.ref?.flatTopic &&
739
+ (node.name.toLowerCase() === wanted ||
740
+ node.route.replaceAll('/', '-').toLowerCase() === wanted),
741
+ )
742
+ .map(node => ({name: node.route, reason: node.summary}));
743
+ }
744
+ if (suggestions.length === 0) {
745
+ suggestions = [
746
+ ...tree
747
+ .roots()
748
+ .map(root => ({name: root.route, reason: 'docs namespace'})),
749
+ ...catalog.names().map(name => ({name, reason: 'available topic'})),
750
+ ];
751
+ }
752
+ return new AstryxError(
753
+ `Unknown topic "${String(topic)}"${notLoaded(catalog)}`,
754
+ suggestions,
755
+ ERROR_CODES.ERR_UNKNOWN_TOPIC,
756
+ );
757
+ }
758
+
759
+ /**
760
+ * A sentence naming the packages whose docs did not load, or nothing: a doc
761
+ * that fails to load withdraws its package's docs, so a reader who cannot
762
+ * find one learns where to look.
763
+ * @param {DocsCatalog} catalog
764
+ * @returns {string}
765
+ */
766
+ export function notLoaded(catalog) {
767
+ const packages = [...new Set(catalog.issues.map(issue => issue.package))];
768
+ if (packages.length === 0) return '';
769
+ return `. The docs of ${packages.join(', ')} did not load; run \`astryx doctor integration docs\` in that package to see why.`;
770
+ }
771
+
772
+ /**
773
+ * Resolve a topic (a flat one, or a guide the docs tree places by its route)
774
+ * and lower it for the topic readers.
775
+ * @param {unknown} topic
776
+ * @param {{lang?: string | null, zh?: boolean, dense?: boolean, cwd?: string}} [options]
777
+ */
778
+ export async function resolveTopicDocs(topic, options = {}) {
779
+ const {lang = null, zh = false, dense = false, cwd} = options;
780
+ const effectiveLang = lang || (dense ? 'dense' : zh ? 'zh' : null);
781
+ // A public API caller could pass a non-string topic; it lands on the same
782
+ // stable code as an unknown name rather than a raw TypeError.
783
+ const found = await resolveDocsArgument(topic, {cwd});
784
+ if (found.kind !== 'topic') {
785
+ throw found.kind === 'node'
786
+ ? new AstryxError(
787
+ `"${found.node.route}" is a ${found.node.kind === 'namespace' ? 'namespace' : `${found.node.kind} doc`} in the docs tree, not a topic. Read it with \`astryx docs ${found.node.route}\`.`,
788
+ undefined,
789
+ ERROR_CODES.ERR_UNKNOWN_TOPIC,
790
+ )
791
+ : await unknownTopicError(topic, found.catalog);
792
+ }
793
+ const {catalog, entry} = found;
254
794
  const node = await lowerTopic(catalog, entry, effectiveLang);
255
- return {catalog, node, lang: effectiveLang};
795
+ return {catalog, node, lang: effectiveLang, entry};
256
796
  }