@astryxdesign/cli 0.6.4-canary.f0355e3 → 0.6.4

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 (331) hide show
  1. package/README.md +96 -99
  2. package/api/build/build.doc.mjs +1 -6
  3. package/api/build/build.test.mjs +0 -22
  4. package/api/build/kit/kit.mjs +5 -44
  5. package/api/component/_adapter.d.mts +0 -25
  6. package/api/component/_adapter.mjs +5 -59
  7. package/api/component/component.d.mts +3 -6
  8. package/api/component/component.doc.mjs +17 -37
  9. package/api/component/component.mjs +9 -249
  10. package/api/component/component.type.d.mts +0 -25
  11. package/api/component/component.type.mjs +0 -44
  12. package/api/discover/_adapter.d.mts +6 -114
  13. package/api/discover/_adapter.mjs +17 -372
  14. package/api/discover/detail/detail.d.mts +6 -18
  15. package/api/discover/detail/detail.mjs +13 -67
  16. package/api/discover/detail/detail.test.mjs +0 -85
  17. package/api/discover/discover.d.mts +9 -3
  18. package/api/discover/discover.doc.mjs +18 -61
  19. package/api/discover/discover.mjs +36 -220
  20. package/api/discover/discover.test.mjs +2 -11
  21. package/api/discover/discover.type.d.mts +8 -147
  22. package/api/discover/discover.type.mjs +12 -102
  23. package/api/discover/list/list.d.mts +6 -20
  24. package/api/discover/list/list.mjs +12 -45
  25. package/api/discover/list/list.test.mjs +0 -46
  26. package/api/discover/search/search.d.mts +16 -18
  27. package/api/discover/search/search.mjs +56 -102
  28. package/api/discover/search/search.test.mjs +10 -144
  29. package/api/docs/_adapter.d.mts +3 -8
  30. package/api/docs/_adapter.mjs +6 -14
  31. package/api/docs/docOverlays.test.mjs +1 -27
  32. package/api/docs/docs.doc.mjs +2 -2
  33. package/api/docs/docs.test.mjs +243 -0
  34. package/api/docs/integration-tree.test.mjs +555 -0
  35. package/api/docs/integrationDocs.test.mjs +314 -0
  36. package/api/doctor/doctor.d.mts +3 -8
  37. package/api/doctor/doctor.doc.mjs +8 -17
  38. package/api/doctor/doctor.mjs +9 -90
  39. package/api/doctor/doctor.test.mjs +10 -122
  40. package/api/doctor/doctor.type.d.mts +1 -1
  41. package/api/doctor/doctor.type.mjs +1 -1
  42. package/api/gap-report/gap-report.doc.mjs +10 -19
  43. package/api/hook/hook.doc.mjs +3 -6
  44. package/api/index.d.mts +2 -1
  45. package/api/index.mjs +5 -5
  46. package/api/init/init.doc.mjs +12 -17
  47. package/api/integration/add-helpers.d.mts +2 -5
  48. package/api/integration/add-helpers.mjs +9 -36
  49. package/api/integration/add-theme.mjs +1 -22
  50. package/api/integration/add-theme.test.mjs +0 -34
  51. package/api/integration/authoring-checks.mjs +2 -2
  52. package/api/integration/integrationPackCheck.doc.mjs +3 -3
  53. package/api/integration/pack-check.mjs +9 -82
  54. package/api/integration/pack-check.test.mjs +0 -90
  55. package/api/integration/pack-check.type.mjs +1 -1
  56. package/api/json/assertResponse.doc.mjs +1 -1
  57. package/api/json/index.ts +1 -0
  58. package/api/json/isError.doc.mjs +1 -1
  59. package/api/layout/_adapter.d.mts +34 -0
  60. package/api/layout/_adapter.mjs +148 -0
  61. package/api/layout/check/check.d.mts +16 -0
  62. package/api/layout/check/check.mjs +40 -0
  63. package/api/layout/expand/expand.d.mts +22 -0
  64. package/api/layout/expand/expand.mjs +155 -0
  65. package/api/layout/expand/expand.path-safety.test.mjs +53 -0
  66. package/api/layout/grammar/grammar.d.mts +13 -0
  67. package/api/layout/grammar/grammar.mjs +87 -0
  68. package/api/layout/layout.d.mts +6 -0
  69. package/api/layout/layout.mjs +17 -0
  70. package/api/layout/layout.test.mjs +297 -0
  71. package/api/layout/layout.type.d.mts +89 -0
  72. package/api/layout/layout.type.mjs +103 -0
  73. package/api/layout/layoutCheck.doc.d.mts +11 -0
  74. package/api/layout/layoutCheck.doc.mjs +85 -0
  75. package/api/layout/layoutExpand.doc.d.mts +11 -0
  76. package/api/layout/layoutExpand.doc.mjs +107 -0
  77. package/api/layout/layoutGrammar.doc.d.mts +11 -0
  78. package/api/layout/layoutGrammar.doc.mjs +57 -0
  79. package/api/search/search.d.mts +1 -27
  80. package/api/search/search.doc.mjs +2 -2
  81. package/api/search/search.mjs +16 -228
  82. package/api/search/search.test.mjs +512 -0
  83. package/api/swizzle/swizzle.doc.mjs +5 -7
  84. package/api/template/copy/copy.mjs +1 -1
  85. package/api/template/copy/copy.test.mjs +0 -9
  86. package/api/template/template-integration.test.mjs +65 -1
  87. package/api/template/template.doc.mjs +1 -2
  88. package/api/template/template.mjs +1 -1
  89. package/api/theme/add/add.mjs +25 -17
  90. package/api/theme/add/add.staging.test.mjs +23 -40
  91. package/api/theme/build/build.family.test.mjs +12 -7
  92. package/api/theme/build/build.mjs +18 -8
  93. package/api/theme/generateTonalPalette.doc.mjs +2 -1
  94. package/api/theme/listThemes.doc.mjs +1 -1
  95. package/api/theme/themeAdd.doc.mjs +10 -9
  96. package/api/theme/themeBuild.doc.mjs +13 -13
  97. package/api/theme/themeList.doc.mjs +1 -1
  98. package/api/theme/themeListAvailable.doc.mjs +1 -2
  99. package/api/theme/themePaletteGenerate.doc.mjs +8 -15
  100. package/api/theme/themeTargets.doc.mjs +2 -3
  101. package/api/theme/themeTemplate.doc.mjs +1 -2
  102. package/api/upgrade/run/run.mjs +4 -6
  103. package/api/upgrade/upgrade.doc.mjs +22 -24
  104. package/api/upgrade/upgrade.type.mjs +2 -2
  105. package/assets/codemods/__tests__/runner.test.mjs +1 -3
  106. package/assets/codemods/integration-runner.mjs +3 -3
  107. package/assets/codemods/runner.mjs +4 -5
  108. package/assets/docs/README.md +2 -4
  109. package/assets/docs/browser-support.doc.mjs +11 -11
  110. package/assets/docs/color.doc.mjs +2 -8
  111. package/assets/docs/elevation.doc.mjs +4 -6
  112. package/assets/docs/getting-started.doc.mjs +16 -5
  113. package/assets/docs/icons.doc.mjs +21 -2
  114. package/assets/docs/illustrations.doc.mjs +15 -7
  115. package/assets/docs/internationalization.doc.mjs +5 -7
  116. package/assets/docs/layout.doc.dense.mjs +82 -130
  117. package/assets/docs/layout.doc.mjs +77 -133
  118. package/assets/docs/migration.doc.mjs +21 -19
  119. package/assets/docs/motion.doc.mjs +3 -16
  120. package/assets/docs/principles.doc.dense.mjs +5 -5
  121. package/assets/docs/principles.doc.mjs +0 -8
  122. package/assets/docs/principles.doc.zh.mjs +6 -6
  123. package/assets/docs/shape.doc.mjs +3 -8
  124. package/assets/docs/spacing.doc.mjs +2 -7
  125. package/assets/docs/styling-libraries.doc.mjs +2 -6
  126. package/assets/docs/styling.doc.mjs +23 -19
  127. package/assets/docs/theme.doc.dense.mjs +18 -58
  128. package/assets/docs/theme.doc.mjs +46 -56
  129. package/assets/docs/theme.doc.zh.mjs +8 -9
  130. package/assets/docs/tokens.doc.dense.mjs +2 -2
  131. package/assets/docs/tokens.doc.mjs +8 -389
  132. package/assets/docs/tokens.doc.zh.mjs +2 -2
  133. package/assets/docs/tree/integrations.doc.mjs +451 -25
  134. package/assets/docs/tree/integrations.test.mjs +62 -0
  135. package/assets/docs/tree/writing-docs.doc.mjs +286 -0
  136. package/assets/docs/typography.doc.mjs +4 -24
  137. package/assets/docs/working-with-ai.doc.mjs +22 -30
  138. package/assets/templates/blocks/components/InternationalizationProvider/InternationalizationProvider01ShippedLocale.tsx +1 -1
  139. package/authoring/config/config.doc.mjs +2 -10
  140. package/authoring/config/parse.d.mts +0 -2
  141. package/authoring/config/parse.mjs +0 -19
  142. package/authoring/config/parse.test.mjs +0 -8
  143. package/authoring/config/type.ts +2 -13
  144. package/authoring/doctypes/_schema.d.mts +2 -3
  145. package/authoring/doctypes/_schema.mjs +0 -6
  146. package/authoring/doctypes/base/graph-fields.doc.mjs +3 -3
  147. package/authoring/doctypes/base/type.ts +2 -4
  148. package/authoring/doctypes/command/command.doc.mjs +1 -1
  149. package/authoring/doctypes/command/type.ts +1 -1
  150. package/authoring/doctypes/component/component.doc.mjs +0 -6
  151. package/authoring/doctypes/component/type.ts +0 -8
  152. package/authoring/doctypes/reference/reference.doc.mjs +0 -7
  153. package/authoring/doctypes/reference/type.ts +0 -5
  154. package/authoring/doctypes/schema/schema.doc.mjs +2 -2
  155. package/authoring/doctypes/template/template.doc.mjs +1 -1
  156. package/authoring/doctypes/template/type.ts +2 -2
  157. package/authoring/index.d.mts +0 -1
  158. package/authoring/index.d.ts +0 -10
  159. package/authoring/index.mjs +0 -1
  160. package/authoring/integration/integration.doc.mjs +10 -12
  161. package/clients/cli/command-result-coverage.test.mjs +7 -7
  162. package/clients/cli/commands/component/index.mjs +55 -152
  163. package/clients/cli/commands/component-ownership.test.mjs +0 -89
  164. package/clients/cli/commands/component.doc.mjs +9 -27
  165. package/clients/cli/commands/debug-result-summary.test.mjs +2 -2
  166. package/clients/cli/commands/discover.doc.mjs +9 -53
  167. package/clients/cli/commands/discover.mjs +118 -393
  168. package/clients/cli/commands/docs.doc.mjs +1 -1
  169. package/clients/cli/commands/docs.mjs +17 -60
  170. package/clients/cli/commands/docs.test.mjs +294 -0
  171. package/clients/cli/commands/doctor-integration-docs.doc.mjs +2 -3
  172. package/clients/cli/commands/doctor-integration.test.mjs +0 -53
  173. package/clients/cli/commands/doctor.doc.mjs +1 -3
  174. package/clients/cli/commands/doctor.mjs +5 -49
  175. package/clients/cli/commands/gap-report.doc.mjs +9 -10
  176. package/clients/cli/commands/init.doc.mjs +6 -9
  177. package/clients/cli/commands/integration-add.doc.mjs +9 -9
  178. package/clients/cli/commands/integration-authoring.test.mjs +10 -61
  179. package/clients/cli/commands/integration-pack.doc.mjs +9 -5
  180. package/clients/cli/commands/integration-real-world.test.mjs +1 -1
  181. package/clients/cli/commands/integration.doc.mjs +4 -4
  182. package/clients/cli/commands/integration.mjs +43 -74
  183. package/clients/cli/commands/layout-check.doc.mjs +65 -0
  184. package/clients/cli/commands/layout-expand.doc.mjs +83 -0
  185. package/clients/cli/commands/layout-grammar.doc.mjs +30 -0
  186. package/clients/cli/commands/layout.doc.mjs +34 -0
  187. package/clients/cli/commands/layout.error-codes.test.mjs +66 -0
  188. package/clients/cli/commands/layout.exit-parity.test.mjs +41 -0
  189. package/clients/cli/commands/layout.mjs +275 -0
  190. package/clients/cli/commands/layout.path-help.test.mjs +33 -0
  191. package/clients/cli/commands/layout.stdin-cap.test.mjs +47 -0
  192. package/clients/cli/commands/layout.text-fields.test.mjs +39 -0
  193. package/clients/cli/commands/manifest.doc.mjs +1 -1
  194. package/clients/cli/commands/search.doc.mjs +3 -10
  195. package/clients/cli/commands/search.mjs +2 -21
  196. package/clients/cli/commands/search.test.mjs +4 -21
  197. package/clients/cli/commands/swizzle.doc.mjs +1 -1
  198. package/clients/cli/commands/template.doc.mjs +1 -1
  199. package/clients/cli/commands/text-json-parity.test.mjs +16 -5
  200. package/clients/cli/commands/theme-add.doc.mjs +1 -1
  201. package/clients/cli/commands/theme-palette-generate.doc.mjs +2 -3
  202. package/clients/cli/commands/theme-palette.doc.mjs +2 -1
  203. package/clients/cli/commands/theme-targets.doc.mjs +2 -2
  204. package/clients/cli/commands/theme.doc.mjs +1 -2
  205. package/clients/cli/commands/upgrade.doc.mjs +3 -62
  206. package/clients/cli/index.mjs +10 -28
  207. package/clients/cli/lib/define-command.mjs +4 -28
  208. package/clients/cli/lib/define-command.test.mjs +0 -54
  209. package/clients/cli/lib/exit-codes.test.mjs +9 -18
  210. package/clients/cli/lib/json-shim.mjs +14 -24
  211. package/clients/cli/lib/json-shim.test.mjs +20 -6
  212. package/clients/cli/lib/manifest.mjs +13 -18
  213. package/clients/cli/lib/manifest.test.mjs +2 -5
  214. package/foundation/agent-docs/agent-docs.mjs +1 -1
  215. package/foundation/agent-docs/agent-docs.test.mjs +1159 -0
  216. package/foundation/discovery/authoring-self-docs.mjs +0 -1
  217. package/foundation/discovery/authoring-self-docs.test.mjs +2 -6
  218. package/foundation/discovery/cli-self-docs.mjs +2 -16
  219. package/foundation/discovery/cli-self-docs.test.mjs +0 -20
  220. package/foundation/discovery/docs-discovery.mjs +1 -5
  221. package/foundation/discovery/docs-discovery.test.mjs +0 -21
  222. package/foundation/discovery/docs-section-key.d.mts +1 -1
  223. package/foundation/discovery/docs-section-key.mjs +1 -1
  224. package/foundation/discovery/template-adapter.mjs +1 -1
  225. package/foundation/doc-compiler/doc-loads.test.mjs +14 -3
  226. package/foundation/doc-compiler/tree.d.mts +0 -4
  227. package/foundation/doc-compiler/tree.mjs +1 -6
  228. package/foundation/doc-compiler/tree.test.mjs +598 -0
  229. package/foundation/integrations/cli-requirement.d.mts +6 -26
  230. package/foundation/integrations/cli-requirement.mjs +11 -46
  231. package/foundation/integrations/cli-requirement.test.mjs +2 -7
  232. package/foundation/integrations/contribution-inventory.mjs +1 -1
  233. package/foundation/integrations/integrations.d.mts +1 -14
  234. package/foundation/integrations/integrations.mjs +1 -41
  235. package/foundation/integrations/integrations.test.mjs +0 -31
  236. package/foundation/response/error-codes.doc.mjs +8 -6
  237. package/foundation/response/error-codes.test.mjs +5 -30
  238. package/foundation/response/response-types.doc.d.mts +3 -4
  239. package/foundation/response/response-types.doc.mjs +27 -40
  240. package/foundation/response/response-types.doc.test.mjs +0 -23
  241. package/foundation/response/response.doc.mjs +10 -11
  242. package/foundation/xle/browser.d.mts +3 -3
  243. package/foundation/xle/browser.mjs +3 -3
  244. package/foundation/xle/expand.mjs +2 -2
  245. package/foundation/xle/parse.mjs +1 -1
  246. package/foundation/xle/print.mjs +2 -2
  247. package/foundation/xle/splice.mjs +1 -1
  248. package/package.json +9 -9
  249. package/api/discover/_adapter.test.mjs +0 -215
  250. package/api/discover/_catalog-view.d.mts +0 -115
  251. package/api/discover/_catalog-view.mjs +0 -203
  252. package/api/discover/_catalog-view.test.mjs +0 -128
  253. package/api/discover/detail/item/item.d.mts +0 -26
  254. package/api/discover/detail/item/item.mjs +0 -78
  255. package/api/discover/detail/item/item.test.mjs +0 -73
  256. package/api/integration/pack-check.lifecycle-output.test.mjs +0 -107
  257. package/api/theme/add/add.rollback.test.mjs +0 -158
  258. package/api/theme/build/build.rollback.test.mjs +0 -148
  259. package/api/upgrade/run/files-changed.test.mjs +0 -111
  260. package/assets/codemods/file-count.test.mjs +0 -163
  261. package/assets/docs/tree/add-a-component.doc.mjs +0 -75
  262. package/assets/docs/tree/add-a-theme.doc.mjs +0 -85
  263. package/assets/docs/tree/add-a-topic.doc.mjs +0 -144
  264. package/assets/docs/tree/agent-guidance.doc.mjs +0 -138
  265. package/assets/docs/tree/block-template.doc.mjs +0 -130
  266. package/assets/docs/tree/build-the-template.doc.mjs +0 -28
  267. package/assets/docs/tree/building-blocks.doc.mjs +0 -46
  268. package/assets/docs/tree/check-your-docs.doc.mjs +0 -137
  269. package/assets/docs/tree/checks.doc.mjs +0 -119
  270. package/assets/docs/tree/codemods.doc.mjs +0 -147
  271. package/assets/docs/tree/component-family.doc.mjs +0 -113
  272. package/assets/docs/tree/component-imports.doc.mjs +0 -69
  273. package/assets/docs/tree/component-lookups.doc.mjs +0 -149
  274. package/assets/docs/tree/components.doc.mjs +0 -23
  275. package/assets/docs/tree/configuration.doc.mjs +0 -23
  276. package/assets/docs/tree/debug-and-gap-reports.doc.mjs +0 -182
  277. package/assets/docs/tree/define-the-theme.doc.mjs +0 -118
  278. package/assets/docs/tree/describe-the-component.doc.mjs +0 -57
  279. package/assets/docs/tree/docs.doc.mjs +0 -21
  280. package/assets/docs/tree/document-the-template.doc.mjs +0 -28
  281. package/assets/docs/tree/document-the-theme.doc.mjs +0 -68
  282. package/assets/docs/tree/export-template-assets.doc.mjs +0 -147
  283. package/assets/docs/tree/extend-or-replace.doc.mjs +0 -103
  284. package/assets/docs/tree/fonts-and-assets.doc.mjs +0 -106
  285. package/assets/docs/tree/generate-a-palette.doc.mjs +0 -66
  286. package/assets/docs/tree/grade-template-with-agent.doc.mjs +0 -105
  287. package/assets/docs/tree/help.doc.mjs +0 -16
  288. package/assets/docs/tree/links.doc.mjs +0 -98
  289. package/assets/docs/tree/package-and-test.doc.mjs +0 -32
  290. package/assets/docs/tree/page-template.doc.mjs +0 -71
  291. package/assets/docs/tree/publishing.doc.mjs +0 -111
  292. package/assets/docs/tree/quick-start.doc.mjs +0 -272
  293. package/assets/docs/tree/replace-a-core-component.doc.mjs +0 -104
  294. package/assets/docs/tree/replace-a-core-template.doc.mjs +0 -172
  295. package/assets/docs/tree/sections-and-placement.doc.mjs +0 -108
  296. package/assets/docs/tree/see-it-in-an-app.doc.mjs +0 -59
  297. package/assets/docs/tree/ship.doc.mjs +0 -16
  298. package/assets/docs/tree/short-and-findable.doc.mjs +0 -108
  299. package/assets/docs/tree/single-component.doc.mjs +0 -165
  300. package/assets/docs/tree/start-a-template.doc.mjs +0 -143
  301. package/assets/docs/tree/subcomponent.doc.mjs +0 -115
  302. package/assets/docs/tree/template-assets.doc.mjs +0 -64
  303. package/assets/docs/tree/template-doc-overview.doc.mjs +0 -109
  304. package/assets/docs/tree/template-fonts.doc.mjs +0 -102
  305. package/assets/docs/tree/template-grading-rubric.doc.mjs +0 -452
  306. package/assets/docs/tree/template-icons.doc.mjs +0 -97
  307. package/assets/docs/tree/template-images-media.doc.mjs +0 -127
  308. package/assets/docs/tree/template-styles.doc.mjs +0 -93
  309. package/assets/docs/tree/templates.doc.mjs +0 -34
  310. package/assets/docs/tree/test-in-an-app.doc.mjs +0 -115
  311. package/assets/docs/tree/test-template-in-app.doc.mjs +0 -128
  312. package/assets/docs/tree/themes.doc.mjs +0 -39
  313. package/assets/docs/tree/troubleshooting.doc.mjs +0 -149
  314. package/assets/docs/tree/upgrading.doc.mjs +0 -103
  315. package/assets/docs/tree/use-a-theme-in-an-app.doc.mjs +0 -51
  316. package/assets/docs/tree/verify-packed-template.doc.mjs +0 -77
  317. package/assets/docs/tree/versioning.doc.mjs +0 -161
  318. package/assets/docs/tree/write-good-templates.doc.mjs +0 -64
  319. package/assets/docs/tree/write-the-template-file.doc.mjs +0 -154
  320. package/authoring/discover/discover.doc.d.mts +0 -13
  321. package/authoring/discover/discover.doc.mjs +0 -138
  322. package/authoring/discover/parse.d.mts +0 -24
  323. package/authoring/discover/parse.mjs +0 -128
  324. package/authoring/discover/parse.test.mjs +0 -124
  325. package/authoring/discover/type.ts +0 -87
  326. package/clients/cli/commands/component-batch.test.mjs +0 -341
  327. package/clients/cli/commands/discover.sources.test.mjs +0 -267
  328. package/clients/cli/commands/integration-verify.doc.mjs +0 -22
  329. package/clients/cli/lib/parse-error-format.test.mjs +0 -81
  330. package/foundation/response/batch.type.d.mts +0 -33
  331. package/foundation/response/batch.type.mjs +0 -34
@@ -0,0 +1,555 @@
1
+ // Copyright (c) Meta Platforms, Inc. and affiliates.
2
+
3
+ /**
4
+ * @file Integration docs in the docs tree (spec:AST-046, spec:AST-047): an
5
+ * integration's namespace docs and placed guides join the CLI's tree with the
6
+ * same moves, links, search, and doctor checks, named by its provider id.
7
+ */
8
+
9
+ import * as fs from 'node:fs';
10
+ import * as path from 'node:path';
11
+ import {afterEach, beforeEach, describe, expect, it} from 'vitest';
12
+ import {docs} from './docs.mjs';
13
+ import {loadDocsCatalog} from './_adapter.mjs';
14
+ import {search} from '../search/search.mjs';
15
+ import {checkDocsTree} from '../doctor/doctor.mjs';
16
+ import {integrationDocConflicts} from '../integration/authoring-checks.mjs';
17
+
18
+ const SLOW = 60_000;
19
+
20
+ /** @type {string} */
21
+ let tmpDir;
22
+
23
+ const NAMESPACE = {
24
+ type: 'namespace',
25
+ name: 'acme',
26
+ title: 'Acme',
27
+ summary: 'Everything about the Acme kit.',
28
+ slots: {guides: {title: 'Guides', accepts: {kinds: ['generic']}}},
29
+ };
30
+
31
+ /**
32
+ * A guide placed in the acme namespace.
33
+ * @param {string} name
34
+ * @param {object} [fields]
35
+ */
36
+ function guide(name, fields = {}) {
37
+ return {
38
+ type: 'generic',
39
+ name,
40
+ title: `Acme ${name}`,
41
+ description: `How to ${name} the Acme kit.`,
42
+ placement: {parent: 'namespace:acme', slot: 'guides'},
43
+ sections: [
44
+ {title: 'Overview', content: [{type: 'prose', text: `Do ${name}.`}]},
45
+ ],
46
+ ...fields,
47
+ };
48
+ }
49
+
50
+ /** The docs of a small integration: a namespace, two guides, and a topic. */
51
+ function kit() {
52
+ return {
53
+ 'acme.doc.mjs': NAMESPACE,
54
+ 'setup.doc.mjs': guide('setup', {
55
+ placement: {parent: 'namespace:acme', slot: 'guides', order: 1},
56
+ sections: [
57
+ {
58
+ title: 'Install',
59
+ content: [
60
+ {
61
+ type: 'prose',
62
+ text: 'Frobnicate the kit, then check it with {@link @astryxdesign/cli:command:doctor}.',
63
+ },
64
+ {type: 'prose', text: 'Next: {@link generic:deploy}.'},
65
+ ],
66
+ },
67
+ {
68
+ title: 'Configure',
69
+ content: [{type: 'prose', text: 'Add it to astryx.config.'}],
70
+ },
71
+ ],
72
+ }),
73
+ 'deploy.doc.mjs': guide('deploy', {
74
+ placement: {parent: 'namespace:acme', slot: 'guides', order: 2},
75
+ }),
76
+ 'notes.doc.mjs': {
77
+ type: 'generic',
78
+ name: 'acme-notes',
79
+ title: 'Acme notes',
80
+ description: 'Notes about the kit.',
81
+ sections: [
82
+ {
83
+ title: 'Start',
84
+ content: [{type: 'prose', text: 'Start at {@link generic:setup}.'}],
85
+ },
86
+ ],
87
+ },
88
+ };
89
+ }
90
+
91
+ /** A guide whose only link names no doc. */
92
+ const broken = () =>
93
+ guide('broken', {
94
+ sections: [
95
+ {
96
+ title: 'Overview',
97
+ content: [{type: 'prose', text: 'See {@link generic:nope}.'}],
98
+ },
99
+ ],
100
+ });
101
+
102
+ /**
103
+ * A consumer project that configures `@acme/kit`, whose manifest names a
104
+ * provider id that differs from its package name.
105
+ * @param {Record<string, object>} files
106
+ */
107
+ function scaffold(files) {
108
+ fs.writeFileSync(
109
+ path.join(tmpDir, 'package.json'),
110
+ JSON.stringify({name: 'consumer'}),
111
+ );
112
+ fs.writeFileSync(
113
+ path.join(tmpDir, 'astryx.config.mjs'),
114
+ "export default {integrations: ['@acme/kit']};\n",
115
+ );
116
+ const pkgDir = path.join(tmpDir, 'node_modules', '@acme', 'kit');
117
+ fs.mkdirSync(path.join(pkgDir, 'docs'), {recursive: true});
118
+ fs.writeFileSync(
119
+ path.join(pkgDir, 'package.json'),
120
+ JSON.stringify({name: '@acme/kit', version: '1.0.0'}),
121
+ );
122
+ fs.writeFileSync(
123
+ path.join(pkgDir, 'astryx.integration.mjs'),
124
+ "export default {docs: './docs', providerId: '@acme/tree-provider'};\n",
125
+ );
126
+ for (const [file, doc] of Object.entries(files)) {
127
+ fs.writeFileSync(
128
+ path.join(pkgDir, 'docs', file),
129
+ `export const docs = ${JSON.stringify(doc, null, 2)};\n`,
130
+ );
131
+ }
132
+ }
133
+
134
+ beforeEach(() => {
135
+ tmpDir = fs.mkdtempSync(
136
+ path.join(process.cwd(), '.astryx-integration-tree-test-'),
137
+ );
138
+ });
139
+
140
+ afterEach(() => {
141
+ fs.rmSync(tmpDir, {recursive: true, force: true});
142
+ });
143
+
144
+ describe('integration docs in the docs tree', () => {
145
+ it('names a package whose docs did not load, instead of dropping them silently', async () => {
146
+ scaffold({
147
+ ...kit(),
148
+ 'broken.doc.mjs': {
149
+ type: 'generic',
150
+ name: 'broken',
151
+ title: 'Broken',
152
+ description: 'A topic whose block no topic may hold.',
153
+ sections: [
154
+ {
155
+ title: 'Only',
156
+ content: [
157
+ {
158
+ type: 'workflow',
159
+ steps: [{title: 'Set up', references: ['generic:setup']}],
160
+ },
161
+ ],
162
+ },
163
+ ],
164
+ },
165
+ });
166
+ const listed = await docs(undefined, undefined, {cwd: tmpDir});
167
+ expect(listed.meta?.notLoaded).toEqual([
168
+ expect.objectContaining({package: '@acme/kit', message: expect.any(String)}),
169
+ ]);
170
+ }, 60_000);
171
+
172
+ it('lists an integration namespace beside the CLI, named by its provider id', async () => {
173
+ scaffold(kit());
174
+ const listed = await docs(undefined, undefined, {cwd: tmpDir});
175
+ expect(listed.meta.namespaces).toContainEqual({
176
+ topic: 'acme',
177
+ description: 'Everything about the Acme kit.',
178
+ package: '@acme/kit',
179
+ });
180
+ const top = await docs('acme', undefined, {cwd: tmpDir});
181
+ expect(top.type).toBe('docs.node');
182
+ expect(top.data).toMatchObject({
183
+ id: 'astryx:artifact:v1/%40acme%2Ftree-provider/namespace/acme',
184
+ package: '@acme/kit',
185
+ links: {up: 'astryx docs'},
186
+ });
187
+ expect(
188
+ top.data.slots[0].children.map((/** @type {any} */ c) => c.route),
189
+ ).toEqual(['acme/setup', 'acme/deploy']);
190
+ }, SLOW);
191
+
192
+ it('reads a placed guide one level at a time, with its links resolved', async () => {
193
+ scaffold(kit());
194
+ const index = await docs('acme/setup', undefined, {cwd: tmpDir, index: true});
195
+ expect(index.type).toBe('docs.index');
196
+ expect(index.data.links.up).toBe('astryx docs acme');
197
+ const install = await docs('acme/setup', 'install', {cwd: tmpDir});
198
+ const [prose, next] = install.data.content;
199
+ expect(prose.text).toBe(
200
+ 'Frobnicate the kit, then check it with `astryx docs cli/commands/doctor`.',
201
+ );
202
+ expect(next.text).toBe('Next: `astryx docs acme/deploy`.');
203
+ const notes = await docs('acme-notes', undefined, {cwd: tmpDir});
204
+ expect(notes.data.sections[0].content[0].text).toBe(
205
+ 'Start at `astryx docs acme/setup`.',
206
+ );
207
+ }, SLOW);
208
+
209
+ it('finds a section of the guide by search, with its package and parent', async () => {
210
+ scaffold(kit());
211
+ const {data} = await search('frobnicate', {cwd: tmpDir, type: 'doc'});
212
+ expect(data.results[0]).toMatchObject({
213
+ name: 'acme/setup',
214
+ section: 'install',
215
+ command: 'astryx docs acme/setup install',
216
+ parent: 'astryx docs acme/setup --index',
217
+ package: '@acme/kit',
218
+ });
219
+ }, SLOW);
220
+
221
+ it('passes doctor, and warns on a link that names no doc', async () => {
222
+ scaffold(kit());
223
+ const ok = await checkDocsTree({
224
+ docsCatalog: await loadDocsCatalog(tmpDir),
225
+ });
226
+ expect(ok).toMatchObject({id: 'docs-tree', status: 'pass'});
227
+ scaffold({...kit(), 'broken.doc.mjs': broken()});
228
+ const bad = await checkDocsTree({
229
+ docsCatalog: await loadDocsCatalog(tmpDir),
230
+ });
231
+ expect(bad.status).toBe('warn');
232
+ expect(bad.message).toContain('acme/broken');
233
+ expect(bad.message).toContain('"generic:nope" names no doc');
234
+ }, SLOW);
235
+
236
+ it("keeps the CLI's routes: an integration namespace or topic that claims one is withdrawn, and both doctors say so", async () => {
237
+ const flat = (/** @type {string} */ name) => ({
238
+ type: 'generic',
239
+ name,
240
+ title: `Acme ${name}`,
241
+ description: `Acme's ${name} notes.`,
242
+ sections: [
243
+ {title: 'One', content: [{type: 'prose', text: 'One.'}]},
244
+ {title: 'Two', content: [{type: 'prose', text: 'Two.'}]},
245
+ ],
246
+ });
247
+ scaffold({
248
+ ...kit(),
249
+ 'unorganized.doc.mjs': {...NAMESPACE, name: 'unorganized', title: 'Mine', summary: 'Mine.'},
250
+ 'tokens.doc.mjs': {...NAMESPACE, name: 'tokens', title: 'Acme tokens', summary: 'Acme tokens.'},
251
+ 'cli.doc.mjs': flat('cli'),
252
+ });
253
+ const listed = await docs(undefined, undefined, {cwd: tmpDir});
254
+ expect(listed.meta.namespaces).toEqual(
255
+ expect.arrayContaining([
256
+ expect.objectContaining({topic: 'cli', package: '@astryxdesign/cli'}),
257
+ expect.objectContaining({topic: 'unorganized', package: '@astryxdesign/cli'}),
258
+ ]),
259
+ );
260
+ expect(listed.meta.namespaces.filter(n => ['tokens', 'unorganized'].includes(n.topic) && n.package === '@acme/kit')).toEqual([]);
261
+ expect(listed.data.map(entry => entry.topic)).not.toContain('cli');
262
+ // Each name opens the CLI's doc, and a CLI topic keeps its home.
263
+ expect((await docs('cli', undefined, {cwd: tmpDir})).data).toMatchObject({route: 'cli', package: '@astryxdesign/cli'});
264
+ expect((await docs('unorganized', undefined, {cwd: tmpDir})).data).toMatchObject({route: 'unorganized', package: '@astryxdesign/cli'});
265
+ const tokens = await docs('tokens', undefined, {cwd: tmpDir});
266
+ expect(tokens.type).toBe('docs.detail');
267
+ expect(tokens.data.links.up).toBe('astryx docs unorganized');
268
+ // The project doctor and the package doctor both name the claims.
269
+ const check = await checkDocsTree({docsCatalog: await loadDocsCatalog(tmpDir)});
270
+ expect(check.status).toBe('warn');
271
+ expect(check.message).toContain('takes the route "unorganized", which the CLI\'s own docs keep');
272
+ expect(check.message).toContain('takes the route "tokens"');
273
+ expect(check.message).toContain('both have the route "cli"');
274
+ const result = await integrationDocConflicts('@acme/kit', {cwd: tmpDir});
275
+ const graph = result.data.issues.filter(issue => issue.code === 'invalid_doc_graph').map(issue => issue.message).join('\n');
276
+ expect(graph).toContain('takes the route "unorganized"');
277
+ expect(graph).toContain('takes the route "tokens"');
278
+ expect(graph).toContain('route "cli"');
279
+ }, SLOW);
280
+
281
+ it("keeps the CLI's routes from a claim that differs only in case", async () => {
282
+ const flat = (/** @type {string} */ name) => ({
283
+ type: 'generic',
284
+ name,
285
+ title: `Acme ${name}`,
286
+ description: `Acme's ${name} notes.`,
287
+ sections: [
288
+ {title: 'One', content: [{type: 'prose', text: 'One.'}]},
289
+ {title: 'Two', content: [{type: 'prose', text: 'Two.'}]},
290
+ ],
291
+ });
292
+ scaffold({...kit(), 'caps-cli.doc.mjs': flat('CLI')});
293
+ // Another spelling, in a nested folder of the package's docs.
294
+ const nested = path.join(tmpDir, 'node_modules', '@acme', 'kit', 'docs', 'nested');
295
+ fs.mkdirSync(nested, {recursive: true});
296
+ fs.writeFileSync(
297
+ path.join(nested, 'caps-unorganized.doc.mjs'),
298
+ `export const docs = ${JSON.stringify(flat('Unorganized'), null, 2)};\n`,
299
+ );
300
+ fs.writeFileSync(
301
+ path.join(nested, 'caps-tokens.doc.mjs'),
302
+ `export const docs = ${JSON.stringify(flat('TOKENS'), null, 2)};\n`,
303
+ );
304
+ expect((await docs('cli', undefined, {cwd: tmpDir})).data).toMatchObject({route: 'cli', package: '@astryxdesign/cli'});
305
+ expect((await docs('unorganized', undefined, {cwd: tmpDir})).data).toMatchObject({route: 'unorganized', package: '@astryxdesign/cli'});
306
+ expect((await docs('theme', undefined, {cwd: tmpDir, index: true})).data.links.up).toBe('astryx docs unorganized');
307
+ const listed = await docs(undefined, undefined, {cwd: tmpDir});
308
+ expect(listed.data.map(entry => entry.topic.toLowerCase())).not.toContain('cli');
309
+ // Search does not offer a topic whose route another doc owns.
310
+ const found = await search("Acme's CLI notes", {cwd: tmpDir, type: 'doc', limit: 20});
311
+ expect(
312
+ found.data.results.filter(hit => hit.package === '@acme/kit' && /^cli$/i.test(hit.name)),
313
+ ).toEqual([]);
314
+ const check = await checkDocsTree({docsCatalog: await loadDocsCatalog(tmpDir)});
315
+ expect(check.status).toBe('warn');
316
+ expect(check.message).toContain('route "CLI"');
317
+ expect(check.message).toContain('takes the route "Unorganized"');
318
+ // `TOKENS` is the CLI's topic `tokens` in another spelling: the CLI's wins.
319
+ const tokens = await docs('TOKENS', undefined, {cwd: tmpDir, index: true});
320
+ expect(tokens.data).toMatchObject({name: 'tokens', links: {up: 'astryx docs unorganized'}});
321
+ expect(tokens.data.title).not.toBe('Acme TOKENS');
322
+ const result = await integrationDocConflicts('@acme/kit', {cwd: tmpDir});
323
+ const graph = result.data.issues
324
+ .filter(issue => issue.code === 'invalid_doc_graph')
325
+ .map(issue => issue.message)
326
+ .join('\n');
327
+ expect(graph).toContain('route "CLI"');
328
+ expect(graph).toContain('takes the route "Unorganized"');
329
+ }, SLOW);
330
+
331
+ it("keeps a replaced name for the topic that replaces it, over another integration's namespace", async () => {
332
+ scaffold({...kit(), 'tokens-ns.doc.mjs': {...NAMESPACE, name: 'tokens', title: 'Kit tokens', summary: 'Kit tokens.'}});
333
+ const two = path.join(tmpDir, 'node_modules', '@acme', 'two');
334
+ fs.mkdirSync(path.join(two, 'docs'), {recursive: true});
335
+ fs.writeFileSync(path.join(two, 'package.json'), JSON.stringify({name: '@acme/two', version: '1.0.0'}));
336
+ fs.writeFileSync(path.join(two, 'astryx.integration.mjs'), "export default {docs: './docs'};\n");
337
+ fs.writeFileSync(
338
+ path.join(two, 'docs', 'acme-tokens.doc.mjs'),
339
+ `export const docs = ${JSON.stringify({
340
+ type: 'generic',
341
+ name: 'acme-tokens',
342
+ title: 'Acme tokens',
343
+ description: 'Acme tokens.',
344
+ replaces: 'tokens',
345
+ sections: [{title: 'One', content: [{type: 'prose', text: 'One.'}]}],
346
+ }, null, 2)};\n`,
347
+ );
348
+ fs.writeFileSync(
349
+ path.join(tmpDir, 'astryx.config.mjs'),
350
+ "export default {integrations: ['@acme/kit', '@acme/two']};\n",
351
+ );
352
+ const listed = await docs(undefined, undefined, {cwd: tmpDir});
353
+ expect(listed.meta.namespaces.map(entry => entry.topic)).not.toContain('tokens');
354
+ const read = await docs('tokens', undefined, {cwd: tmpDir, index: true});
355
+ expect(read.data.name).toBe('acme-tokens');
356
+ const check = await checkDocsTree({docsCatalog: await loadDocsCatalog(tmpDir)});
357
+ expect(check.status).toBe('warn');
358
+ expect(check.message).toContain('which the topic "acme-tokens" also answers to');
359
+ }, SLOW);
360
+
361
+ it('keeps every name a topic replaced twice answers to, and the CLI\'s links to it still open', async () => {
362
+ scaffold({...kit(), 'tokens-ns.doc.mjs': {...NAMESPACE, name: 'tokens', title: 'Kit tokens', summary: 'Kit tokens.'}});
363
+ /** @param {string} pkg @param {string} name @param {string} replaces */
364
+ const replacement = (pkg, name, replaces) => {
365
+ const dir = path.join(tmpDir, 'node_modules', '@acme', pkg);
366
+ fs.mkdirSync(path.join(dir, 'docs'), {recursive: true});
367
+ fs.writeFileSync(path.join(dir, 'package.json'), JSON.stringify({name: `@acme/${pkg}`, version: '1.0.0'}));
368
+ fs.writeFileSync(path.join(dir, 'astryx.integration.mjs'), "export default {docs: './docs'};\n");
369
+ fs.writeFileSync(
370
+ path.join(dir, 'docs', `${name}.doc.mjs`),
371
+ `export const docs = ${JSON.stringify({
372
+ type: 'generic',
373
+ name,
374
+ title: `Tokens by ${pkg}`,
375
+ description: `Tokens by ${pkg}.`,
376
+ replaces,
377
+ sections: [{title: 'One', content: [{type: 'prose', text: 'One.'}]}],
378
+ }, null, 2)};\n`,
379
+ );
380
+ };
381
+ replacement('two', 'acme-tokens', 'tokens');
382
+ replacement('three', 'b-tokens', 'tokens');
383
+ replacement('four', 'super-tokens', 'b-tokens');
384
+ fs.writeFileSync(
385
+ path.join(tmpDir, 'astryx.config.mjs'),
386
+ "export default {integrations: ['@acme/kit', '@acme/two', '@acme/three', '@acme/four']};\n",
387
+ );
388
+ const listed = await docs(undefined, undefined, {cwd: tmpDir});
389
+ expect(listed.meta.namespaces.map(entry => entry.topic)).not.toContain('tokens');
390
+ for (const name of ['tokens', 'acme-tokens', 'b-tokens', 'super-tokens']) {
391
+ expect((await docs(name, undefined, {cwd: tmpDir, index: true})).data.name).toBe('super-tokens');
392
+ }
393
+ // A walk over every topic: the CLI's own links to `tokens` still open it.
394
+ /** @type {string[]} */
395
+ const raw = [];
396
+ for (const entry of listed.data) {
397
+ const read = await docs(entry.topic, undefined, {cwd: tmpDir});
398
+ if (/\{@link [^}]*tokens\}/.test(JSON.stringify(read.data))) raw.push(entry.topic);
399
+ }
400
+ expect(raw).toEqual([]);
401
+ const principles = await docs('principles', undefined, {cwd: tmpDir});
402
+ expect(JSON.stringify(principles.data)).toContain('astryx docs super-tokens');
403
+ const check = await checkDocsTree({docsCatalog: await loadDocsCatalog(tmpDir)});
404
+ expect(check.message).toContain('takes the route "tokens", which the topic "super-tokens" also answers to');
405
+ expect(check.message).not.toContain('names no doc');
406
+ }, SLOW);
407
+
408
+ it("keeps the CLI's routes from a replacement that answers to one, in one package or across a chain", async () => {
409
+ const flat = (/** @type {string} */ name, /** @type {object} */ extra = {}) => ({
410
+ type: 'generic',
411
+ name,
412
+ title: `Acme ${name}`,
413
+ description: `Acme's ${name} notes.`,
414
+ sections: [
415
+ {title: 'One', content: [{type: 'prose', text: 'One.'}]},
416
+ {title: 'Two', content: [{type: 'prose', text: 'Two.'}]},
417
+ ],
418
+ ...extra,
419
+ });
420
+ scaffold({
421
+ ...kit(),
422
+ 'a-cli.doc.mjs': flat('cli'),
423
+ 'a-unorganized.doc.mjs': flat('Unorganized'),
424
+ 'z-b2.doc.mjs': flat('b2', {replaces: 'cli'}),
425
+ 'z-u2.doc.mjs': flat('u2', {replaces: 'Unorganized'}),
426
+ });
427
+ // Another package carries the chain on: c3 replaces b2, which replaced cli.
428
+ const three = path.join(tmpDir, 'node_modules', '@acme', 'three');
429
+ fs.mkdirSync(path.join(three, 'docs'), {recursive: true});
430
+ fs.writeFileSync(path.join(three, 'package.json'), JSON.stringify({name: '@acme/three', version: '1.0.0'}));
431
+ fs.writeFileSync(path.join(three, 'astryx.integration.mjs'), "export default {docs: './docs'};\n");
432
+ fs.writeFileSync(
433
+ path.join(three, 'docs', 'c3.doc.mjs'),
434
+ `export const docs = ${JSON.stringify(flat('c3', {replaces: 'b2'}), null, 2)};\n`,
435
+ );
436
+ fs.writeFileSync(
437
+ path.join(tmpDir, 'astryx.config.mjs'),
438
+ "export default {integrations: ['@acme/kit', '@acme/three']};\n",
439
+ );
440
+ for (const name of ['cli', 'CLI']) {
441
+ expect((await docs(name, undefined, {cwd: tmpDir})).data).toMatchObject({route: 'cli', package: '@astryxdesign/cli'});
442
+ }
443
+ for (const name of ['unorganized', 'UNORGANIZED']) {
444
+ expect((await docs(name, undefined, {cwd: tmpDir})).data).toMatchObject({route: 'unorganized', package: '@astryxdesign/cli'});
445
+ }
446
+ // The replacement still answers to its own names.
447
+ for (const name of ['b2', 'c3']) {
448
+ expect((await docs(name, undefined, {cwd: tmpDir, index: true})).data.name).toBe('c3');
449
+ }
450
+ const listed = await docs(undefined, undefined, {cwd: tmpDir});
451
+ for (const entry of listed.data) {
452
+ const read = await docs(entry.topic, undefined, {cwd: tmpDir});
453
+ expect(read.type).toBe('docs.detail');
454
+ expect(read.data.name).toBe(entry.topic);
455
+ }
456
+ const check = await checkDocsTree({docsCatalog: await loadDocsCatalog(tmpDir)});
457
+ expect(check.status).toBe('warn');
458
+ expect(check.message).toContain('The topic "c3" answers to "cli" through replaces');
459
+ expect(check.message).toContain('The topic "u2" answers to "unorganized" through replaces');
460
+ const inPackage = await integrationDocConflicts('@acme/kit', {cwd: tmpDir});
461
+ const graph = inPackage.data.issues
462
+ .filter(issue => issue.code === 'invalid_doc_graph')
463
+ .map(issue => issue.message)
464
+ .join('\n');
465
+ expect(graph).toContain('answers to "cli" through replaces');
466
+ }, SLOW);
467
+
468
+ it('runs the same checks inside the package: doctor integration docs', async () => {
469
+ scaffold({...kit(), 'broken.doc.mjs': broken()});
470
+ const result = await integrationDocConflicts('@acme/kit', {cwd: tmpDir});
471
+ expect(result.data.issues).toContainEqual(
472
+ expect.objectContaining({
473
+ code: 'invalid_doc_graph',
474
+ severity: 'warning',
475
+ message: expect.stringContaining('"generic:nope" names no doc'),
476
+ }),
477
+ );
478
+ }, SLOW);
479
+
480
+ it("resolves an extension's links against the extension's provider, not the base topic's", async () => {
481
+ scaffold({
482
+ ...kit(),
483
+ 'integrations.doc.mjs': guide('integrations'),
484
+ 'theme-acme.doc.mjs': {
485
+ type: 'generic',
486
+ name: 'theme-acme',
487
+ title: 'Acme theming',
488
+ description: 'Acme notes on theming.',
489
+ extends: 'theme',
490
+ sections: [
491
+ {
492
+ title: 'Acme theming notes',
493
+ content: [
494
+ {
495
+ type: 'prose',
496
+ text: 'See {@link generic:integrations}, and {@link @astryxdesign/cli:generic:tokens} for the tokens.',
497
+ },
498
+ ],
499
+ },
500
+ ],
501
+ },
502
+ });
503
+ const {data} = await docs('theme', 'acme-theming-notes', {cwd: tmpDir});
504
+ // `generic:integrations` is the extension's own guide, never the CLI's
505
+ // `cli/integrations`; a provider-qualified link reaches the CLI's topic.
506
+ expect(data.content[0].text).toBe(
507
+ 'See `astryx docs acme/integrations`, and `astryx docs tokens` for the tokens.',
508
+ );
509
+ // The CLI's own sections of the topic still resolve against the CLI.
510
+ const full = await docs('theme', undefined, {cwd: tmpDir});
511
+ expect(JSON.stringify(full.data)).not.toContain('{@link');
512
+ }, SLOW);
513
+
514
+ it("warns, in the project and in the package, on an extension link that names no doc of its provider", async () => {
515
+ scaffold({
516
+ ...kit(),
517
+ 'theme-acme.doc.mjs': {
518
+ type: 'generic',
519
+ name: 'theme-acme',
520
+ title: 'Acme theming',
521
+ description: 'Acme notes on theming.',
522
+ extends: 'theme',
523
+ sections: [
524
+ {
525
+ title: 'Acme theming notes',
526
+ content: [{type: 'prose', text: 'See {@link generic:tokens}.'}],
527
+ },
528
+ ],
529
+ },
530
+ });
531
+ const check = await checkDocsTree({
532
+ docsCatalog: await loadDocsCatalog(tmpDir),
533
+ });
534
+ expect(check.status).toBe('warn');
535
+ expect(check.message).toContain('"generic:tokens" names no doc');
536
+ const result = await integrationDocConflicts('@acme/kit', {cwd: tmpDir});
537
+ expect(result.data.issues).toContainEqual(
538
+ expect.objectContaining({
539
+ code: 'invalid_doc_graph',
540
+ message: expect.stringContaining('"generic:tokens" names no doc'),
541
+ }),
542
+ );
543
+ }, SLOW);
544
+
545
+ it('keeps the CLI route when an integration claims it, and names the claim', async () => {
546
+ scaffold({'cli.doc.mjs': {...NAMESPACE, name: 'cli', title: 'Not the CLI'}});
547
+ const top = await docs('cli', undefined, {cwd: tmpDir});
548
+ expect(top.data.package).toBe('@astryxdesign/cli');
549
+ const check = await checkDocsTree({
550
+ docsCatalog: await loadDocsCatalog(tmpDir),
551
+ });
552
+ expect(check.status).toBe('warn');
553
+ expect(check.message).toContain('both have the route "cli"');
554
+ }, SLOW);
555
+ });