@astryxdesign/cli 0.7.2 → 0.7.3-canary.0bf23bd

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 (337) hide show
  1. package/README.md +104 -71
  2. package/api/build/_adapter.d.mts +58 -0
  3. package/api/build/_adapter.mjs +115 -3
  4. package/api/build/blocks/blocks.d.mts +42 -0
  5. package/api/build/blocks/blocks.mjs +169 -0
  6. package/api/build/blocks/blocks.test.mjs +108 -0
  7. package/api/build/blocks/blocks.weights.json +1 -0
  8. package/api/build/build.mjs +25 -6
  9. package/api/build/kit/answer.d.mts +14 -0
  10. package/api/build/kit/answer.mjs +85 -0
  11. package/api/build/kit/answer.test.mjs +38 -0
  12. package/api/build/kit/kind.d.mts +25 -0
  13. package/api/build/kit/kind.mjs +170 -0
  14. package/api/build/kit/kit.d.mts +2 -4
  15. package/api/build/kit/kit.mjs +8 -398
  16. package/api/build/kit/rank.d.mts +3 -32
  17. package/api/build/kit/rank.mjs +25 -342
  18. package/api/build/kit/rank.test.mjs +2 -1
  19. package/api/build/kit/signals.d.mts +60 -0
  20. package/api/build/kit/signals.mjs +284 -0
  21. package/api/build/kit/start.d.mts +32 -0
  22. package/api/build/kit/start.mjs +163 -0
  23. package/api/build/kit/words.d.mts +72 -0
  24. package/api/build/kit/words.mjs +202 -0
  25. package/api/build/tree.d.mts +20 -0
  26. package/api/build/tree.mjs +52 -0
  27. package/api/build/tree.test.mjs +177 -0
  28. package/api/build/tree.weights.json +1 -0
  29. package/api/component/component.mjs +17 -3
  30. package/api/component/component.test.mjs +15 -0
  31. package/api/component/doc-links.test.mjs +2 -2
  32. package/api/discover/_adapter.mjs +36 -9
  33. package/api/discover/_adapter.test.mjs +42 -0
  34. package/api/docs/_adapter.d.mts +2 -2
  35. package/api/docs/_adapter.mjs +78 -23
  36. package/api/docs/docs-homes.test.mjs +127 -0
  37. package/api/docs/docs.doc.mjs +3 -3
  38. package/api/docs/docs.test.mjs +35 -19
  39. package/api/docs/integration-tree.test.mjs +20 -20
  40. package/api/docs/integrationDocs.test.mjs +1 -1
  41. package/api/docs/namespace-section.test.mjs +6 -6
  42. package/api/docs/reference-blocks.test.mjs +2 -2
  43. package/api/doctor/doctor.mjs +102 -24
  44. package/api/doctor/doctor.test.mjs +37 -12
  45. package/api/hook/hook.mjs +1 -1
  46. package/api/init/init.doc.mjs +20 -6
  47. package/api/init/init.test.mjs +18 -2
  48. package/api/init/init.type.d.mts +3 -3
  49. package/api/init/init.type.mjs +3 -3
  50. package/api/init/remove/remove.mjs +17 -4
  51. package/api/init/remove/remove.test.mjs +114 -2
  52. package/api/init/run/run.mjs +69 -6
  53. package/api/integration/_adapter.d.mts +8 -0
  54. package/api/integration/_adapter.mjs +21 -1
  55. package/api/integration/authoring-checks.mjs +83 -9
  56. package/api/integration/authoring-checks.test.mjs +220 -0
  57. package/api/integration/pack-check.mjs +123 -4
  58. package/api/integration/pack-check.test.mjs +87 -0
  59. package/api/integration/validate-integration-case-disk.test.mjs +111 -0
  60. package/api/integration/validate-integration-gaps.test.mjs +1964 -0
  61. package/api/integration/validate-integration.d.mts +11 -2
  62. package/api/integration/validate-integration.mjs +250 -32
  63. package/api/integration/validate-integration.type.d.mts +59 -1
  64. package/api/integration/validate-integration.type.mjs +46 -2
  65. package/api/integration/validateIntegration.doc.mjs +1 -1
  66. package/api/search/ORDERING.md +28 -0
  67. package/api/search/_adapter.d.mts +82 -2
  68. package/api/search/_adapter.mjs +157 -10
  69. package/api/search/_adapter.test.mjs +173 -0
  70. package/api/search/_candidate-score.d.mts +40 -0
  71. package/api/search/_candidate-score.mjs +238 -0
  72. package/api/search/_cards.json +6098 -0
  73. package/api/search/_compound.d.mts +50 -0
  74. package/api/search/_compound.mjs +170 -0
  75. package/api/search/_doc-searcher.d.mts +4 -0
  76. package/api/search/_doc-searcher.mjs +23 -0
  77. package/api/search/_doc-searcher.test.mjs +36 -0
  78. package/api/search/_doc-searcher.weights.json +1 -0
  79. package/api/search/_doc-text.d.mts +30 -0
  80. package/api/search/_doc-text.mjs +87 -0
  81. package/api/search/_entries.d.mts +52 -0
  82. package/api/search/_entries.mjs +214 -0
  83. package/api/search/_features.d.mts +35 -0
  84. package/api/search/_features.mjs +155 -0
  85. package/api/search/_gather-components.d.mts +26 -0
  86. package/api/search/_gather-components.mjs +284 -0
  87. package/api/search/_gather-docs.d.mts +20 -0
  88. package/api/search/_gather-docs.mjs +254 -0
  89. package/api/search/_gather-hooks.d.mts +12 -0
  90. package/api/search/_gather-hooks.mjs +52 -0
  91. package/api/search/_gather-templates.d.mts +25 -0
  92. package/api/search/_gather-templates.mjs +107 -0
  93. package/api/search/_lexical.d.mts +135 -0
  94. package/api/search/_lexical.mjs +351 -0
  95. package/api/search/_main-searcher.d.mts +19 -0
  96. package/api/search/_main-searcher.mjs +115 -0
  97. package/api/search/_main-searcher.test.mjs +106 -0
  98. package/api/search/_main-searcher.weights.json +1 -0
  99. package/api/search/_module-doc.d.mts +32 -0
  100. package/api/search/_module-doc.mjs +78 -0
  101. package/api/search/_order.d.mts +32 -0
  102. package/api/search/_order.mjs +162 -0
  103. package/api/search/_order.test.mjs +72 -0
  104. package/api/search/_priority.d.mts +50 -0
  105. package/api/search/_priority.mjs +139 -0
  106. package/api/search/_query-words.d.mts +25 -0
  107. package/api/search/_query-words.mjs +92 -0
  108. package/api/search/_query.d.mts +65 -0
  109. package/api/search/_query.mjs +176 -0
  110. package/api/search/_result.d.mts +97 -0
  111. package/api/search/_result.mjs +150 -0
  112. package/api/search/_score.d.mts +60 -0
  113. package/api/search/_score.mjs +283 -0
  114. package/api/search/_synonyms.d.mts +21 -0
  115. package/api/search/_synonyms.mjs +67 -0
  116. package/api/search/_template-searcher.d.mts +4 -0
  117. package/api/search/_template-searcher.mjs +23 -0
  118. package/api/search/_template-searcher.test.mjs +40 -0
  119. package/api/search/_template-searcher.weights.json +1 -0
  120. package/api/search/_titles.d.mts +33 -0
  121. package/api/search/_titles.mjs +65 -0
  122. package/api/search/_words.d.mts +64 -0
  123. package/api/search/_words.mjs +157 -0
  124. package/api/search/search.d.mts +22 -305
  125. package/api/search/search.doc.mjs +8 -6
  126. package/api/search/search.mjs +56 -2482
  127. package/api/search/search.test.mjs +14 -14
  128. package/api/search/search.type.d.mts +6 -1
  129. package/api/search/search.type.mjs +6 -1
  130. package/api/search/tree.d.mts +15 -0
  131. package/api/search/tree.mjs +40 -0
  132. package/api/search/tree.test.mjs +66 -0
  133. package/api/search/tree.weights.json +1 -0
  134. package/api/theme/build/build.packed-old-core.test.mjs +1 -0
  135. package/api/upgrade/_adapter.d.mts +57 -9
  136. package/api/upgrade/_adapter.mjs +198 -28
  137. package/api/upgrade/registry/registry.d.mts +10 -4
  138. package/api/upgrade/registry/registry.mjs +33 -16
  139. package/api/upgrade/registry/registry.test.mjs +85 -0
  140. package/api/upgrade/run/run.d.mts +4 -2
  141. package/api/upgrade/run/run.mjs +13 -2
  142. package/api/upgrade/run/run.test.mjs +417 -3
  143. package/api/upgrade/upgrade.doc.mjs +10 -5
  144. package/api/upgrade/upgrade.mjs +13 -2
  145. package/api/upgrade/upgrade.test.mjs +194 -0
  146. package/api/upgrade/upgrade.type.d.mts +5 -3
  147. package/api/upgrade/upgrade.type.mjs +8 -6
  148. package/assets/codemods/transforms/v0.7.0/__tests__/validate-data-token-ownership.test.mjs +11 -0
  149. package/assets/codemods/transforms/v0.7.0/validate-data-token-ownership.mjs +3 -3
  150. package/assets/docs/author-a-theme.doc.mjs +2 -2
  151. package/assets/docs/is-my-code-good.doc.mjs +287 -0
  152. package/assets/docs/tree/add-a-component.doc.mjs +1 -1
  153. package/assets/docs/tree/add-a-theme.doc.mjs +1 -1
  154. package/assets/docs/tree/add-a-topic.doc.mjs +2 -2
  155. package/assets/docs/tree/agent-guidance.doc.mjs +1 -1
  156. package/assets/docs/tree/api.doc.mjs +6 -0
  157. package/assets/docs/tree/{integrations.doc.mjs → authoring.doc.mjs} +15 -6
  158. package/assets/docs/tree/block-template.doc.mjs +1 -1
  159. package/assets/docs/tree/build-the-template.doc.mjs +1 -1
  160. package/assets/docs/tree/building-blocks.doc.mjs +2 -2
  161. package/assets/docs/tree/check-the-props.doc.mjs +1 -1
  162. package/assets/docs/tree/check-your-docs.doc.mjs +2 -2
  163. package/assets/docs/tree/checks.doc.mjs +2 -2
  164. package/assets/docs/tree/cli-command.doc.mjs +1 -1
  165. package/assets/docs/tree/cli.doc.mjs +1 -1
  166. package/assets/docs/tree/codemods.doc.mjs +1 -1
  167. package/assets/docs/tree/component-family.doc.mjs +1 -1
  168. package/assets/docs/tree/component-imports.doc.mjs +1 -1
  169. package/assets/docs/tree/components.doc.mjs +1 -1
  170. package/assets/docs/tree/configuration.doc.mjs +1 -1
  171. package/assets/docs/tree/debug-and-gap-reports.doc.mjs +1 -1
  172. package/assets/docs/tree/define-the-theme.doc.mjs +1 -1
  173. package/assets/docs/tree/describe-the-component.doc.mjs +1 -1
  174. package/assets/docs/tree/docs.doc.mjs +1 -1
  175. package/assets/docs/tree/document-the-template.doc.mjs +1 -1
  176. package/assets/docs/tree/document-the-theme.doc.mjs +1 -1
  177. package/assets/docs/tree/export-template-assets.doc.mjs +1 -1
  178. package/assets/docs/tree/extend-or-replace.doc.mjs +1 -1
  179. package/assets/docs/{authoring.doc.mjs → tree/field-reference.doc.mjs} +4 -3
  180. package/assets/docs/tree/fonts-and-assets.doc.mjs +1 -1
  181. package/assets/docs/tree/generate-a-palette.doc.mjs +1 -1
  182. package/assets/docs/tree/global-integrations.doc.mjs +2 -2
  183. package/assets/docs/tree/grade-template-with-agent.doc.mjs +4 -4
  184. package/assets/docs/tree/help.doc.mjs +2 -2
  185. package/assets/docs/tree/host-entry.doc.mjs +128 -0
  186. package/assets/docs/tree/links.doc.mjs +1 -1
  187. package/assets/docs/tree/package-and-test.doc.mjs +1 -1
  188. package/assets/docs/tree/page-template.doc.mjs +1 -1
  189. package/assets/docs/tree/publishing.doc.mjs +1 -1
  190. package/assets/docs/tree/quick-start.doc.mjs +1 -1
  191. package/assets/docs/tree/replace-a-core-component.doc.mjs +1 -1
  192. package/assets/docs/tree/replace-a-core-template.doc.mjs +1 -1
  193. package/assets/docs/tree/sections-and-placement.doc.mjs +1 -1
  194. package/assets/docs/tree/see-it-in-an-app.doc.mjs +1 -1
  195. package/assets/docs/tree/ship.doc.mjs +2 -2
  196. package/assets/docs/tree/short-and-findable.doc.mjs +1 -1
  197. package/assets/docs/tree/single-component.doc.mjs +1 -1
  198. package/assets/docs/tree/start-a-template.doc.mjs +1 -1
  199. package/assets/docs/tree/subcomponent.doc.mjs +1 -1
  200. package/assets/docs/tree/template-assets.doc.mjs +1 -1
  201. package/assets/docs/tree/template-doc-overview.doc.mjs +2 -2
  202. package/assets/docs/tree/template-fonts.doc.mjs +1 -1
  203. package/assets/docs/tree/template-grading-rubric.doc.mjs +17 -38
  204. package/assets/docs/tree/template-icons.doc.mjs +1 -1
  205. package/assets/docs/tree/template-images-media.doc.mjs +1 -1
  206. package/assets/docs/tree/template-styles.doc.mjs +1 -1
  207. package/assets/docs/tree/templates.doc.mjs +1 -1
  208. package/assets/docs/tree/test-in-an-app.doc.mjs +1 -1
  209. package/assets/docs/tree/test-template-in-app.doc.mjs +1 -1
  210. package/assets/docs/tree/themes.doc.mjs +1 -1
  211. package/assets/docs/tree/tokens-data.doc.mjs +2 -2
  212. package/assets/docs/tree/troubleshooting.doc.mjs +1 -1
  213. package/assets/docs/tree/upgrading.doc.mjs +1 -1
  214. package/assets/docs/tree/use-a-theme-in-an-app.doc.mjs +1 -1
  215. package/assets/docs/tree/verify-packed-template.doc.mjs +1 -1
  216. package/assets/docs/tree/versioning.doc.mjs +1 -1
  217. package/assets/docs/tree/write-good-templates.doc.mjs +1 -1
  218. package/assets/docs/tree/write-the-template-file.doc.mjs +1 -1
  219. package/assets/docs/use-integrations.doc.mjs +120 -0
  220. package/assets/docs/working-with-ai.doc.mjs +6 -2
  221. package/assets/templates/themes/butter/butterTheme.ts +4 -2
  222. package/assets/templates/themes/stone/stoneTheme.ts +4 -2
  223. package/assets/templates/themes/y2k/y2kTheme.ts +4 -2
  224. package/authoring/debug/parse.d.mts +1 -1
  225. package/authoring/doctypes/schema/schema.doc.mjs +1 -1
  226. package/authoring/integration/integration.doc.mjs +1 -1
  227. package/authoring/integration/type.ts +1 -1
  228. package/clients/cli/bin/astryx.mjs +7 -78
  229. package/clients/cli/command-result-coverage.test.mjs +6 -5
  230. package/clients/cli/commands/build-theme.color-scheme.test.mjs +1 -1
  231. package/clients/cli/commands/{component/index.mjs → component.mjs} +37 -37
  232. package/clients/cli/commands/component.test.mjs +6 -6
  233. package/clients/cli/commands/docs.doc.mjs +2 -2
  234. package/clients/cli/commands/docs.mjs +1 -1
  235. package/clients/cli/commands/docs.test.mjs +14 -14
  236. package/clients/cli/commands/doctor.mjs +30 -0
  237. package/clients/cli/commands/doctor.test.mjs +65 -0
  238. package/clients/cli/commands/{hook/index.mjs → hook.mjs} +22 -22
  239. package/clients/cli/commands/import-hint-correctness.test.mjs +1 -1
  240. package/clients/cli/commands/init.behavior.test.mjs +230 -1
  241. package/clients/cli/commands/init.doc.mjs +28 -9
  242. package/clients/cli/commands/init.flag-help.test.mjs +1 -0
  243. package/clients/cli/commands/init.mjs +2 -2
  244. package/clients/cli/commands/integration-check.test.mjs +48 -0
  245. package/clients/cli/commands/integration.doc.mjs +1 -1
  246. package/clients/cli/commands/manifest.doc.mjs +1 -1
  247. package/clients/cli/commands/manifest.mjs +67 -0
  248. package/clients/cli/commands/outside-project.test.mjs +2 -1
  249. package/clients/cli/commands/postinstall.doc.mjs +23 -0
  250. package/clients/cli/commands/postinstall.mjs +31 -0
  251. package/clients/cli/commands/setup-nudge.test.mjs +61 -0
  252. package/clients/cli/commands/{build-theme.mjs → theme.mjs} +1 -1
  253. package/clients/cli/commands/upgrade.doc.mjs +4 -2
  254. package/clients/cli/commands/upgrade.mjs +9 -2
  255. package/clients/cli/commands/upgrade.subfolder-path.test.mjs +97 -0
  256. package/clients/cli/index.mjs +29 -86
  257. package/clients/cli/lib/component-format.mjs +1 -1
  258. package/clients/cli/lib/define-command.mjs +12 -7
  259. package/clients/cli/lib/exit-codes.test.mjs +5 -0
  260. package/clients/cli/lib/manifest.test.mjs +12 -0
  261. package/clients/cli/lib/welcome-box.mjs +50 -0
  262. package/clients/cli/run.mjs +108 -0
  263. package/foundation/agent-docs/agent-doc-state.d.mts +20 -7
  264. package/foundation/agent-docs/agent-doc-state.mjs +35 -8
  265. package/foundation/agent-docs/agent-docs.d.mts +100 -12
  266. package/foundation/agent-docs/agent-docs.mjs +1012 -100
  267. package/foundation/agent-docs/agent-docs.path-safety.test.mjs +1239 -17
  268. package/foundation/agent-docs/agent-docs.test.mjs +683 -39
  269. package/foundation/config/project.mjs +20 -22
  270. package/foundation/config/project.test.mjs +56 -0
  271. package/foundation/discovery/authoring-self-docs.d.mts +7 -0
  272. package/foundation/discovery/authoring-self-docs.mjs +16 -4
  273. package/foundation/discovery/authoring-self-docs.test.mjs +15 -3
  274. package/foundation/discovery/authoring-surface.d.mts +1 -1
  275. package/foundation/discovery/authoring-surface.mjs +2 -2
  276. package/foundation/discovery/authoring-surface.test.mjs +1 -1
  277. package/foundation/discovery/cli-self-docs.d.mts +2 -1
  278. package/foundation/discovery/cli-self-docs.mjs +4 -3
  279. package/foundation/discovery/cli-self-docs.test.mjs +2 -2
  280. package/foundation/discovery/component-discovery.d.mts +37 -0
  281. package/foundation/discovery/component-discovery.mjs +80 -5
  282. package/foundation/discovery/component-loader.d.mts +1 -1
  283. package/foundation/discovery/docs-discovery.mjs +14 -1
  284. package/foundation/discovery/module-exports.d.mts +41 -0
  285. package/foundation/discovery/module-exports.mjs +253 -0
  286. package/foundation/discovery/runner-allowlist.d.mts +2 -4
  287. package/foundation/discovery/runner-allowlist.mjs +2 -4
  288. package/foundation/discovery/runner-commands.d.mts +77 -0
  289. package/foundation/discovery/runner-commands.mjs +294 -0
  290. package/foundation/discovery/runner-commands.test.mjs +128 -0
  291. package/foundation/discovery/stray-pointers.test.mjs +1 -1
  292. package/foundation/discovery/template-adapter.mjs +18 -1
  293. package/foundation/discovery/template-needs.mjs +1 -44
  294. package/foundation/discovery/theme-discovery.mjs +147 -3
  295. package/foundation/doc-compiler/doc-loads.test.mjs +33 -9
  296. package/foundation/doc-compiler/read.mjs +59 -1
  297. package/foundation/doc-compiler/read.test.mjs +11 -2
  298. package/foundation/doc-compiler/split-topic-keywords.test.mjs +3 -2
  299. package/foundation/doc-compiler/tree.d.mts +32 -6
  300. package/foundation/doc-compiler/tree.mjs +77 -43
  301. package/foundation/doc-compiler/tree.test.mjs +7 -7
  302. package/foundation/fs/paths.d.mts +70 -8
  303. package/foundation/fs/paths.mjs +556 -38
  304. package/foundation/fs/paths.test.mjs +430 -54
  305. package/foundation/fs/read-record.d.mts +18 -0
  306. package/foundation/fs/read-record.mjs +131 -0
  307. package/foundation/fs/read-record.test.mjs +77 -0
  308. package/foundation/integrations/autolink.d.mts +2 -1
  309. package/foundation/integrations/autolink.mjs +4 -13
  310. package/foundation/integrations/computer-integrations.d.mts +6 -3
  311. package/foundation/integrations/computer-integrations.mjs +38 -9
  312. package/foundation/integrations/contribution-inventory.d.mts +15 -20
  313. package/foundation/integrations/contribution-inventory.mjs +41 -5
  314. package/foundation/integrations/integrations.d.mts +27 -1
  315. package/foundation/integrations/integrations.mjs +61 -2
  316. package/foundation/integrations/validate-contributions.d.mts +115 -0
  317. package/foundation/integrations/validate-contributions.mjs +1168 -13
  318. package/foundation/release/handoff.d.mts +23 -4
  319. package/foundation/release/handoff.mjs +175 -8
  320. package/foundation/release/run-context.mjs +14 -0
  321. package/foundation/response/response-types.doc.mjs +1 -1
  322. package/foundation/route/route.d.mts +285 -0
  323. package/foundation/route/route.mjs +324 -0
  324. package/foundation/route/route.test.mjs +153 -0
  325. package/foundation/runtime/hosted-runtime.test.mjs +403 -0
  326. package/foundation/runtime/locked-versions.d.mts +32 -0
  327. package/foundation/runtime/locked-versions.mjs +65 -0
  328. package/foundation/runtime/package-root.d.mts +91 -0
  329. package/foundation/runtime/package-root.mjs +174 -0
  330. package/foundation/runtime/package-set.d.mts +62 -0
  331. package/foundation/runtime/package-set.mjs +130 -0
  332. package/foundation/runtime/package-set.test.mjs +432 -0
  333. package/host/door-conformance.test.mjs +274 -0
  334. package/host/index.d.ts +116 -0
  335. package/host/index.mjs +295 -0
  336. package/host/index.test.mjs +302 -0
  337. package/package.json +14 -9
@@ -21,6 +21,10 @@ import {
21
21
  withDeclaredOfficial,
22
22
  } from './_adapter.mjs';
23
23
  import {parseDiscoverCatalog} from '../../authoring/discover/parse.mjs';
24
+ import {
25
+ _resetHostedRun,
26
+ activateHostedRun,
27
+ } from '../../foundation/runtime/package-root.mjs';
24
28
 
25
29
  /** No official packages, so a test sees only its own sources after it. */
26
30
  const NO_OFFICIAL = {packages: []};
@@ -230,6 +234,44 @@ describe('callSources', () => {
230
234
  expect(otherRequest.sources[0].status).toBe('failed');
231
235
  });
232
236
 
237
+ it("keeps a host's saved copies per host, so one tool never answers with another's", async () => {
238
+ let online = true;
239
+ const source = async () => {
240
+ if (!online) throw new Error('offline');
241
+ return catalog('Acme catalog', [pkg('@acme/one')]);
242
+ };
243
+ const options = {cacheDir, official: NO_OFFICIAL};
244
+ try {
245
+ activateHostedRun({
246
+ discoverSources: [source],
247
+ startingRoot: '/tools/acme',
248
+ });
249
+ await callSources(undefined, {}, options);
250
+ online = false;
251
+ const sameHost = await callSources(undefined, {}, options);
252
+ expect(sameHost.sources[0]).toMatchObject({
253
+ name: 'Acme catalog',
254
+ from: 'the host',
255
+ status: 'saved',
256
+ });
257
+
258
+ _resetHostedRun();
259
+ activateHostedRun({
260
+ discoverSources: [source],
261
+ startingRoot: '/tools/other',
262
+ });
263
+ const otherHost = await callSources(undefined, {}, options);
264
+ expect(otherHost.sources[0]).toMatchObject({
265
+ from: 'the host',
266
+ status: 'failed',
267
+ error: 'offline',
268
+ });
269
+ expect(otherHost.packages).toEqual([]);
270
+ } finally {
271
+ _resetHostedRun();
272
+ }
273
+ });
274
+
233
275
  it('keeps the saved copy in the cache directory, not in the project', async () => {
234
276
  await callSources(
235
277
  project({
@@ -226,7 +226,7 @@ export function topicUp(entry: import("../../foundation/discovery/docs-discovery
226
226
  export function placeLinks(tree: DocsTree, node: TreeNode): import("./docs.type.mjs").DocsLinks;
227
227
  /**
228
228
  * The moves a topic read offers: from its place in the tree, where a guide
229
- * sits in its namespace and a flat topic in the Unorganized level.
229
+ * sits in its namespace and a flat topic in its topic level.
230
230
  * @param {DocsCatalog} catalog
231
231
  * @param {import('../../foundation/discovery/docs-discovery.mjs').DocsTopicEntry} entry
232
232
  * @returns {Promise<import('./docs.type.mjs').DocsLinks>}
@@ -290,7 +290,7 @@ export function holdsOwnName(tree: DocsTree, catalog: DocsCatalog, entry: import
290
290
  /**
291
291
  * The doc that took a flat topic's route in the docs tree, when it is not
292
292
  * that topic (spec:AST-046 FR11): the CLI keeps its routes, such as `cli`
293
- * and `unorganized`, and a namespace keeps its route over a topic of the same
293
+ * and `guides`, and a namespace keeps its route over a topic of the same
294
294
  * name. Null when the topic owns its route, or the tree has no node there.
295
295
  * @param {DocsTree} tree
296
296
  * @param {import('../../foundation/discovery/docs-discovery.mjs').DocsTopicEntry} entry
@@ -52,6 +52,7 @@ import {
52
52
  cliDocSection,
53
53
  } from '../../foundation/discovery/cli-self-docs.mjs';
54
54
  import {
55
+ AUTHORING_REFERENCE_ROUTE,
55
56
  loadAuthoringSelfDocs,
56
57
  schemaFieldTable,
57
58
  selfDocSection,
@@ -385,7 +386,7 @@ export function projectTree(catalog, {fresh = false} = {}) {
385
386
  let cliTreeInputs;
386
387
 
387
388
  /**
388
- * Every flat topic in the catalog, as the tree's Unorganized level reads it.
389
+ * Every flat topic in the catalog, as the tree's topic levels read it.
389
390
  * @param {DocsCatalog} catalog
390
391
  * @returns {Promise<import('../../foundation/doc-compiler/tree.mjs').TreeTopicInput[]>}
391
392
  */
@@ -394,12 +395,13 @@ async function flatTopicInputs(catalog) {
394
395
  const topics = [];
395
396
  for (const entry of catalog.entries()) {
396
397
  const aliases = catalog.aliasesOf(entry);
397
- let {title, description} = entry;
398
- if (title == null || description == null) {
398
+ let {title, description, category} = entry;
399
+ if (title == null || description == null || category === undefined) {
399
400
  try {
400
401
  const file = await loadTopicFile(entry.path, null);
401
402
  title ??= file.doc?.title;
402
403
  description ??= file.doc?.description;
404
+ category ??= file.doc?.category;
403
405
  } catch {
404
406
  // A topic that does not load is reported where it is read.
405
407
  }
@@ -410,6 +412,7 @@ async function flatTopicInputs(catalog) {
410
412
  name: entry.name,
411
413
  title: title ?? entry.name,
412
414
  summary: description ?? '',
415
+ category: category ?? null,
413
416
  source: `${entry.package}:${entry.name}`,
414
417
  ...(aliases.length > 0 ? {aliases} : {}),
415
418
  });
@@ -419,7 +422,7 @@ async function flatTopicInputs(catalog) {
419
422
 
420
423
  /**
421
424
  * The CLI's tree, each configured integration's namespaces and guides, and
422
- * every flat topic in the generated Unorganized level.
425
+ * every flat topic in the generated level for its category.
423
426
  * @param {DocsCatalog} catalog
424
427
  * @param {boolean} fresh
425
428
  * @returns {Promise<DocsTree>}
@@ -509,23 +512,22 @@ function identitiesOf(tree) {
509
512
  }
510
513
 
511
514
  /**
512
- * The CLI's authoring docs, by kind and name. `astryx docs authoring` reads
513
- * each as one section keyed by its name, so a link to one opens that section.
514
- * Loaded once per process, like the CLI's tree files.
515
+ * The CLI's authoring docs, by kind and name. The field reference under
516
+ * `astryx docs authoring` reads each as one section keyed by its name, so a
517
+ * link to one opens that section. Loaded once per process, like the CLI's
518
+ * tree files.
515
519
  * @type {Promise<Map<string, any>> | undefined}
516
520
  */
517
521
  let authoringDocs;
518
522
 
519
523
  /**
520
- * The CLI authoring doc a link names, while `astryx docs authoring` is the
521
- * CLI's own topic.
522
- * @param {DocsCatalog} catalog
524
+ * The CLI authoring doc a link names. The `authoring` namespace is the CLI's
525
+ * own, so no integration can take its route.
523
526
  * @param {string} kind
524
527
  * @param {string} name
525
528
  * @returns {Promise<any | null>}
526
529
  */
527
- async function authoringDoc(catalog, kind, name) {
528
- if (catalog.resolve('authoring')?.package !== CLI_PROVIDER_ID) return null;
530
+ async function authoringDoc(kind, name) {
529
531
  authoringDocs ??= loadAuthoringSelfDocs().then(
530
532
  ({loaded}) =>
531
533
  new Map(loaded.map(({doc}) => [`${doc.type}\u0000${doc.name}`, doc])),
@@ -611,14 +613,14 @@ function docFinderOver(catalog, tree, fromProvider, summaries = true) {
611
613
  }
612
614
  const authored =
613
615
  provider === CLI_PROVIDER_ID
614
- ? await authoringDoc(catalog, parsed.kind, parsed.name)
616
+ ? await authoringDoc(parsed.kind, parsed.name)
615
617
  : null;
616
618
  if (authored) {
617
619
  return {
618
620
  link: {
619
621
  target,
620
622
  id: createDocId(provider, authored.type, authored.name),
621
- route: 'authoring',
623
+ route: AUTHORING_REFERENCE_ROUTE,
622
624
  title: authored.displayName ?? authored.name,
623
625
  summary: authored.description ?? '',
624
626
  command: `astryx docs authoring ${authored.name}`,
@@ -1146,6 +1148,14 @@ function packageCatalog(integration, discovered) {
1146
1148
  * @returns {Promise<Array<{severity: 'warning', message: string}>>}
1147
1149
  */
1148
1150
  export async function packageComponentLinkProblems(integration, discovered) {
1151
+ // A package with no provider id (its manifest's, else its name) links
1152
+ // nothing, as on the read path: its text reads as written, so there is
1153
+ // nothing to report.
1154
+ try {
1155
+ normalizeProviderId(integration.providerId ?? integration.name);
1156
+ } catch {
1157
+ return [];
1158
+ }
1149
1159
  const catalog = packageCatalog(integration, discovered);
1150
1160
  return (await componentLinkProblems(catalog, integration.name)).map(
1151
1161
  message => ({severity: /** @type {const} */ ('warning'), message}),
@@ -1176,11 +1186,15 @@ async function componentLinkProblems(catalog, owner) {
1176
1186
  // The components check reports a doc that does not load.
1177
1187
  continue;
1178
1188
  }
1179
- const linked = await linkComponentDoc(
1180
- catalog,
1181
- componentLinkProvider(record.package, record.providerId),
1182
- doc,
1183
- );
1189
+ let provider;
1190
+ try {
1191
+ provider = componentLinkProvider(record.package, record.providerId);
1192
+ } catch {
1193
+ // An owner that is not a provider id links nothing (as on the read
1194
+ // path); its text reads as written.
1195
+ continue;
1196
+ }
1197
+ const linked = await linkComponentDoc(catalog, provider, doc);
1184
1198
  const name = typeof doc?.name === 'string' ? doc.name : record.name;
1185
1199
  for (const problem of linked.problems) {
1186
1200
  problems.push(
@@ -1416,7 +1430,7 @@ export function placeLinks(tree, node) {
1416
1430
 
1417
1431
  /**
1418
1432
  * The moves a topic read offers: from its place in the tree, where a guide
1419
- * sits in its namespace and a flat topic in the Unorganized level.
1433
+ * sits in its namespace and a flat topic in its topic level.
1420
1434
  * @param {DocsCatalog} catalog
1421
1435
  * @param {import('../../foundation/discovery/docs-discovery.mjs').DocsTopicEntry} entry
1422
1436
  * @returns {Promise<import('./docs.type.mjs').DocsLinks>}
@@ -1514,7 +1528,7 @@ function treeArgument(catalog, tree, node) {
1514
1528
  /**
1515
1529
  * The doc that took a flat topic's route in the docs tree, when it is not
1516
1530
  * that topic (spec:AST-046 FR11): the CLI keeps its routes, such as `cli`
1517
- * and `unorganized`, and a namespace keeps its route over a topic of the same
1531
+ * and `guides`, and a namespace keeps its route over a topic of the same
1518
1532
  * name. Null when the topic owns its route, or the tree has no node there.
1519
1533
  * @param {DocsTree} tree
1520
1534
  * @param {import('../../foundation/discovery/docs-discovery.mjs').DocsTopicEntry} entry
@@ -1528,6 +1542,35 @@ export function routeOwner(tree, entry) {
1528
1542
  : node;
1529
1543
  }
1530
1544
 
1545
+ /**
1546
+ * Routes that moved, old prefix first: the integration guides moved from
1547
+ * `cli/integrations` (and, before that, the `cli-integrations` topic) into
1548
+ * `authoring`, the one home for integration authors.
1549
+ * Only the not-found error reads this, to name where a doc went; an old route
1550
+ * opens nothing.
1551
+ */
1552
+ const MOVED_ROUTES = [
1553
+ ['cli/integrations', 'authoring'],
1554
+ ['cli-integrations', 'authoring'],
1555
+ ];
1556
+
1557
+ /**
1558
+ * The node an old route moved to, or null.
1559
+ * @param {unknown} topic
1560
+ * @param {DocsTree} tree
1561
+ * @returns {TreeNode | null}
1562
+ */
1563
+ function movedRoute(topic, tree) {
1564
+ if (typeof topic !== 'string') return null;
1565
+ const wanted = topic.toLowerCase();
1566
+ for (const [from, to] of MOVED_ROUTES) {
1567
+ if (wanted !== from && !wanted.startsWith(`${from}/`)) continue;
1568
+ const node = tree.getFolded(`${to}${wanted.slice(from.length)}`);
1569
+ if (node) return node;
1570
+ }
1571
+ return null;
1572
+ }
1573
+
1531
1574
  /**
1532
1575
  * The error for a docs argument that names nothing. For a route, it suggests
1533
1576
  * the children of the deepest namespace the route reaches; otherwise, every
@@ -1560,8 +1603,7 @@ export async function unknownTopicError(topic, catalog) {
1560
1603
  }
1561
1604
  if (suggestions.length === 0 && typeof topic === 'string') {
1562
1605
  // The docs whose own name it is (`doctor` is cli/commands/doctor), or whose
1563
- // route it spells with hyphens (`cli-integrations`, the guide's name before
1564
- // it moved to cli/integrations).
1606
+ // route it spells with hyphens (`cli-host-entry` is cli/host-entry).
1565
1607
  const wanted = topic.toLowerCase();
1566
1608
  suggestions = [...tree.nodes.values()]
1567
1609
  .filter(
@@ -1580,6 +1622,19 @@ export async function unknownTopicError(topic, catalog) {
1580
1622
  ...catalog.names().map(name => ({name, reason: 'available topic'})),
1581
1623
  ];
1582
1624
  }
1625
+ // A route that moved names where it went, ahead of the usual suggestions
1626
+ // (spec:AST-046 FR9, FR6).
1627
+ const moved = movedRoute(topic, tree);
1628
+ if (moved) {
1629
+ return new AstryxError(
1630
+ `Unknown topic "${String(topic)}". It moved to "${moved.route}"${notLoaded(catalog)}`,
1631
+ [
1632
+ {name: moved.route, reason: moved.summary},
1633
+ ...suggestions.filter(entry => entry.name !== moved.route),
1634
+ ],
1635
+ ERROR_CODES.ERR_UNKNOWN_TOPIC,
1636
+ );
1637
+ }
1583
1638
  return new AstryxError(
1584
1639
  `Unknown topic "${String(topic)}"${notLoaded(catalog)}`,
1585
1640
  suggestions,
@@ -0,0 +1,127 @@
1
+ // Copyright (c) Meta Platforms, Inc. and affiliates.
2
+
3
+ /**
4
+ * @file Every Core doc has a named home in the docs tree (spec:AST-046 FR12).
5
+ *
6
+ * A reader sees a doc's home in its path (`Guides › Getting started`) and in
7
+ * its Up move. A flat Core topic sits in Guides or Foundations by the
8
+ * category it declares, so a new topic with no category, or one the levels
9
+ * do not know, fails here instead of landing in a catch-all. The integration
10
+ * guides and their field reference live under `authoring`, and the routes
11
+ * they left name where they went.
12
+ */
13
+
14
+ import {describe, expect, it} from 'vitest';
15
+ import {docs} from './docs.mjs';
16
+ import {TOPIC_LEVELS} from '../../foundation/doc-compiler/tree.mjs';
17
+
18
+ const CLI = '@astryxdesign/cli';
19
+ const SLOW = 60_000;
20
+
21
+ describe('every Core doc has a named home', () => {
22
+ it(
23
+ 'puts each flat Core topic in Guides or Foundations by its category',
24
+ async () => {
25
+ const list = await docs();
26
+ const core = list.data.filter(topic => topic.package === CLI);
27
+ expect(core.length).toBeGreaterThan(10);
28
+ /** @type {string[]} */
29
+ const unfiled = [];
30
+ for (const topic of core) {
31
+ const read = await docs(topic.topic);
32
+ const {category} = read.data;
33
+ if (category !== 'guide' && category !== 'foundations') {
34
+ unfiled.push(`${topic.topic} (category ${String(category)})`);
35
+ continue;
36
+ }
37
+ const level =
38
+ category === 'foundations'
39
+ ? TOPIC_LEVELS.foundations
40
+ : TOPIC_LEVELS.guides;
41
+ expect(read.data.links.up, topic.topic).toBe(
42
+ `astryx docs ${level.route}`,
43
+ );
44
+ }
45
+ expect(unfiled, 'Core topics with no home: give each a category').toEqual(
46
+ [],
47
+ );
48
+ },
49
+ SLOW,
50
+ );
51
+
52
+ it(
53
+ 'lists each level under a plain name, and no catch-all',
54
+ async () => {
55
+ const list = await docs();
56
+ const roots = (list.meta.namespaces ?? []).map(entry => entry.topic);
57
+ expect(roots).toEqual(expect.arrayContaining(['guides', 'foundations']));
58
+ expect(roots).not.toContain('unorganized');
59
+ const guides = await docs('guides');
60
+ expect(guides.data).toMatchObject({route: 'guides', title: 'Guides'});
61
+ expect(guides.data.slots[0].children.map(child => child.route)).toEqual(
62
+ expect.arrayContaining(['getting-started', 'principles', 'theme']),
63
+ );
64
+ const foundations = await docs('foundations');
65
+ expect(foundations.data.title).toBe('Foundations');
66
+ expect(
67
+ foundations.data.slots[0].children.map(child => child.route),
68
+ ).toEqual(expect.arrayContaining(['color', 'icons', 'spacing']));
69
+ await expect(docs('unorganized')).rejects.toMatchObject({
70
+ code: 'ERR_UNKNOWN_TOPIC',
71
+ });
72
+ },
73
+ SLOW,
74
+ );
75
+
76
+ it(
77
+ 'keeps everything for integration authors under authoring, and names where an old route went',
78
+ async () => {
79
+ const authoring = await docs('authoring');
80
+ expect(authoring.data).toMatchObject({
81
+ route: 'authoring',
82
+ kind: 'namespace',
83
+ breadcrumb: [],
84
+ links: {up: 'astryx docs'},
85
+ });
86
+ const children = authoring.data.slots.flatMap(slot =>
87
+ slot.children.map(child => child.route),
88
+ );
89
+ expect(children).toEqual(
90
+ expect.arrayContaining([
91
+ 'authoring/quick-start',
92
+ 'authoring/building-blocks',
93
+ 'authoring/ship',
94
+ 'authoring/help',
95
+ 'authoring/field-reference',
96
+ ]),
97
+ );
98
+ // Every field reference section still opens from the namespace by its
99
+ // key, as `astryx docs authoring <name>` did on the topic (FR13).
100
+ const reference = await docs('authoring/field-reference', undefined, {
101
+ index: true,
102
+ });
103
+ const keys = reference.data.sections.map(section => section.id);
104
+ expect(keys.length).toBeGreaterThan(15);
105
+ for (const key of keys) {
106
+ const section = await docs('authoring', key);
107
+ expect(section.type, key).toBe('docs.detail.section');
108
+ expect(section.data.id, key).toBe(key);
109
+ }
110
+ for (const [old, now] of [
111
+ ['cli/integrations', 'authoring'],
112
+ ['cli/integrations/quick-start', 'authoring/quick-start'],
113
+ ['cli/integrations/ship/publishing', 'authoring/ship/publishing'],
114
+ ]) {
115
+ const error = await docs(old).catch(e => e);
116
+ expect(error).toMatchObject({
117
+ code: 'ERR_UNKNOWN_TOPIC',
118
+ message: expect.stringContaining(`It moved to "${now}"`),
119
+ });
120
+ // The new route comes first, ahead of the suggestions any unknown
121
+ // route gets (FR6).
122
+ expect(error.suggestions[0]).toMatchObject({name: now});
123
+ }
124
+ },
125
+ SLOW,
126
+ );
127
+ });
@@ -28,7 +28,7 @@ export const doc = {
28
28
  '`cli/api` returns its children one level down (`depth` reads as many ' +
29
29
  'levels as asked, and `detail` how much of each doc below), a typed doc such as ' +
30
30
  "`cli/api/functions/search` returns its content, and a guide the tree " +
31
- 'places (`cli/integrations/quick-start`) reads like any topic. ' +
31
+ 'places (`authoring/quick-start`) reads like any topic. ' +
32
32
  'Every read but the list carries `links`, the commands that move from it: ' +
33
33
  '`up` to the level it sits in, `previous` and `next` to its neighbors, and, ' +
34
34
  'for a typed doc, `related` to the docs it names (its command or function, ' +
@@ -162,12 +162,12 @@ export const doc = {
162
162
  },
163
163
  {
164
164
  label: 'A namespace and everything below it, in full',
165
- code: "await docs('cli/integrations', undefined, {depth: 'all', detail: 'full'});",
165
+ code: "await docs('authoring', undefined, {depth: 'all', detail: 'full'});",
166
166
  },
167
167
  {label: 'One API function', code: "await docs('cli/api/functions/search');"},
168
168
  {
169
169
  label: 'A whole guide from the docs tree',
170
- code: "await docs('cli/integrations/quick-start');",
170
+ code: "await docs('authoring/quick-start');",
171
171
  },
172
172
  {label: 'One section by key', code: "await docs('spacing', 'scale');"},
173
173
  ],
@@ -93,7 +93,7 @@ describe('docs() dispatcher routing', () => {
93
93
 
94
94
  it("lists the docs tree's namespaces in meta, so every data entry reads as a topic", async () => {
95
95
  const res = await docs();
96
- expect(res.meta.namespaces.map(entry => entry.topic)).toEqual(['cli', 'global', 'internationalization', 'layout', 'migration', 'styling', 'styling-libraries', 'tokens', 'typography', 'unorganized']);
96
+ expect(res.meta.namespaces.map(entry => entry.topic)).toEqual(['authoring', 'cli', 'foundations', 'global', 'guides', 'internationalization', 'layout', 'migration', 'styling', 'styling-libraries', 'tokens', 'typography']);
97
97
  for (const entry of res.data) {
98
98
  expect((await docs(entry.topic)).type).toBe('docs.detail');
99
99
  }
@@ -123,7 +123,13 @@ describe('docs() dispatcher routing', () => {
123
123
  kind: 'namespace',
124
124
  package: '@astryxdesign/cli',
125
125
  breadcrumb: [{route: 'cli', title: 'Astryx CLI'}],
126
- content: [],
126
+ // Its one block points to the host entry's guide.
127
+ content: [
128
+ {
129
+ type: 'prose',
130
+ text: expect.stringContaining('`astryx docs cli/host-entry`'),
131
+ },
132
+ ],
127
133
  });
128
134
  expect(r.data.slots.flatMap(slot => slot.children.map(c => c.route))).toEqual([
129
135
  'cli/api/functions',
@@ -152,8 +158,8 @@ describe('docs() dispatcher routing', () => {
152
158
  }, SLOW);
153
159
 
154
160
  it('a guide the tree places reads like a topic: the whole doc, its index, a section', async () => {
155
- // cli/integrations is a namespace of short guides; each guide reads like a topic.
156
- const integrations = await docs('cli/integrations');
161
+ // authoring is a namespace of short guides; each guide reads like a topic.
162
+ const integrations = await docs('authoring');
157
163
  expect(integrations.type).toBe('docs.node');
158
164
  const guide = placedGuide(integrations.data);
159
165
  expect((await docs(guide)).type).toBe('docs.detail');
@@ -172,7 +178,7 @@ describe('docs() dispatcher routing', () => {
172
178
  expect(leaf.links.up).toBe('astryx docs cli/api/functions');
173
179
  expect(leaf.links.previous).toMatch(/^astryx docs cli\/api\/functions\/[a-z-]+$/);
174
180
  expect(leaf.links.next).toMatch(/^astryx docs cli\/api\/functions\/[a-z-]+$/);
175
- const integrations = (await docs('cli/integrations')).data;
181
+ const integrations = (await docs('authoring')).data;
176
182
  const guide = placedGuide(integrations);
177
183
  const siblings = integrations.slots
178
184
  .find(slot => slot.children.some(child => child.route === guide))
@@ -181,7 +187,7 @@ describe('docs() dispatcher routing', () => {
181
187
  const index = await docs(guide, undefined, {index: true});
182
188
  // A guide moves across its namespace's slot, as any tree node does.
183
189
  expect(index.data.links).toEqual({
184
- up: 'astryx docs cli/integrations',
190
+ up: 'astryx docs authoring',
185
191
  ...(at > 0 ? {previous: `astryx docs ${siblings[at - 1]}`} : {}),
186
192
  ...(at < siblings.length - 1
187
193
  ? {next: `astryx docs ${siblings[at + 1]}`}
@@ -198,16 +204,22 @@ describe('docs() dispatcher routing', () => {
198
204
  expect(end.links.previous).toBe(
199
205
  `astryx docs ${guide} ${index.data.sections.at(-2).id}`,
200
206
  );
201
- // A flat topic's home is the Unorganized level, and it moves across it.
207
+ // A flat topic's home is the level for its category, and it moves across it.
202
208
  const flat = (await docs('principles')).data.links;
203
- expect(flat.up).toBe('astryx docs unorganized');
209
+ expect(flat.up).toBe('astryx docs guides');
204
210
  expect(flat.previous).toMatch(/^astryx docs [a-z-]+$/);
205
211
  expect(flat.next).toMatch(/^astryx docs [a-z-]+$/);
206
- const home = (await docs('unorganized')).data;
207
- expect(home).toMatchObject({id: null, kind: 'namespace', links: {up: 'astryx docs'}});
208
- expect(home.slots[0].children.map(child => child.route)).toEqual(
209
- (await docs()).data.map(entry => entry.topic),
210
- );
212
+ const guides = (await docs('guides')).data;
213
+ expect(guides).toMatchObject({id: null, kind: 'namespace', links: {up: 'astryx docs'}});
214
+ const foundations = (await docs('foundations')).data;
215
+ expect(foundations).toMatchObject({id: null, kind: 'namespace', links: {up: 'astryx docs'}});
216
+ // Between them the two levels hold every flat topic, each in list order.
217
+ const topics = (await docs()).data.map(entry => entry.topic);
218
+ const held = [guides, foundations].map(level => level.slots[0].children.map(child => child.route));
219
+ for (const routes of held) {
220
+ expect(routes).toEqual(topics.filter(topic => routes.includes(topic)));
221
+ }
222
+ expect(held.flat().sort()).toEqual([...topics].sort());
211
223
  }, SLOW);
212
224
 
213
225
  it('names a node by its doc identity; a generated level has id null', async () => {
@@ -234,10 +246,9 @@ describe('docs() dispatcher routing', () => {
234
246
  }, SLOW);
235
247
 
236
248
  it("the guide's old flat name is gone, and its error names the new route", async () => {
237
- await expect(docs('cli-integrations')).rejects.toMatchObject({
238
- code: 'ERR_UNKNOWN_TOPIC',
239
- suggestions: [expect.objectContaining({name: 'cli/integrations'})],
240
- });
249
+ const error = await docs('cli-integrations').catch(e => e);
250
+ expect(error.code).toBe('ERR_UNKNOWN_TOPIC');
251
+ expect(error.suggestions[0]).toMatchObject({name: 'authoring'});
241
252
  expect((await docs()).data.map(entry => entry.topic)).not.toContain(
242
253
  'cli-integrations',
243
254
  );
@@ -247,11 +258,16 @@ describe('docs() dispatcher routing', () => {
247
258
  const err = await docs('cli', 'zzzz-nope').catch(e => e);
248
259
  expect(err).toBeInstanceOf(AstryxError);
249
260
  expect(err.code).toBe('ERR_UNKNOWN_SECTION');
250
- // It names every guide below the namespace, at any depth.
251
261
  const names = err.suggestions.map(s => s.name);
252
262
  expect(names).toContain('cli/component-lookups');
253
- expect(names).toContain('cli/integrations/quick-start');
254
263
  expect(names.every(name => name.startsWith('cli/'))).toBe(true);
264
+ // It names every guide below the namespace, at any depth.
265
+ const deep = await docs('authoring', 'zzzz-nope').catch(e => e);
266
+ expect(deep.code).toBe('ERR_UNKNOWN_SECTION');
267
+ const below = deep.suggestions.map(s => s.name);
268
+ expect(below).toContain('authoring/quick-start');
269
+ expect(below).toContain('authoring/building-blocks/components/add-a-component');
270
+ expect(below.every(name => name.startsWith('authoring/'))).toBe(true);
255
271
  }, SLOW);
256
272
 
257
273
  it('an unknown route suggests the children of the deepest namespace it reaches', async () => {