@astryxdesign/cli 0.6.4 → 0.6.5-canary.01972bc

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 (405) hide show
  1. package/CHANGELOG.md +56 -0
  2. package/README.md +103 -95
  3. package/api/build/_adapter.d.mts +36 -2
  4. package/api/build/_adapter.mjs +41 -10
  5. package/api/build/build.doc.mjs +8 -3
  6. package/api/build/build.test.mjs +60 -2
  7. package/api/build/kit/kit.mjs +109 -26
  8. package/api/build/kit/rank.d.mts +24 -8
  9. package/api/build/kit/rank.mjs +277 -97
  10. package/api/build/kit/rank.test.mjs +231 -48
  11. package/api/component/_adapter.d.mts +25 -0
  12. package/api/component/_adapter.mjs +59 -5
  13. package/api/component/component.d.mts +6 -3
  14. package/api/component/component.doc.mjs +37 -17
  15. package/api/component/component.mjs +249 -9
  16. package/api/component/component.type.d.mts +25 -0
  17. package/api/component/component.type.mjs +44 -0
  18. package/api/discover/_adapter.d.mts +114 -6
  19. package/api/discover/_adapter.mjs +372 -17
  20. package/api/discover/_adapter.test.mjs +215 -0
  21. package/api/discover/_catalog-view.d.mts +115 -0
  22. package/api/discover/_catalog-view.mjs +203 -0
  23. package/api/discover/_catalog-view.test.mjs +128 -0
  24. package/api/discover/detail/detail.d.mts +18 -6
  25. package/api/discover/detail/detail.mjs +67 -13
  26. package/api/discover/detail/detail.test.mjs +85 -0
  27. package/api/discover/detail/item/item.d.mts +26 -0
  28. package/api/discover/detail/item/item.mjs +78 -0
  29. package/api/discover/detail/item/item.test.mjs +73 -0
  30. package/api/discover/discover.d.mts +3 -9
  31. package/api/discover/discover.doc.mjs +61 -18
  32. package/api/discover/discover.mjs +220 -36
  33. package/api/discover/discover.test.mjs +11 -2
  34. package/api/discover/discover.type.d.mts +147 -8
  35. package/api/discover/discover.type.mjs +102 -12
  36. package/api/discover/list/list.d.mts +20 -6
  37. package/api/discover/list/list.mjs +45 -12
  38. package/api/discover/list/list.test.mjs +46 -0
  39. package/api/discover/search/search.d.mts +18 -16
  40. package/api/discover/search/search.mjs +102 -56
  41. package/api/discover/search/search.test.mjs +144 -10
  42. package/api/docs/_adapter.d.mts +8 -3
  43. package/api/docs/_adapter.mjs +14 -6
  44. package/api/docs/docOverlays.test.mjs +27 -1
  45. package/api/docs/docs.doc.mjs +2 -2
  46. package/api/docs/docs.test.mjs +54 -18
  47. package/api/docs/integration-tree.test.mjs +17 -0
  48. package/api/docs/integrationDocs.test.mjs +27 -5
  49. package/api/doctor/doctor.d.mts +8 -3
  50. package/api/doctor/doctor.doc.mjs +17 -8
  51. package/api/doctor/doctor.mjs +90 -9
  52. package/api/doctor/doctor.test.mjs +122 -10
  53. package/api/doctor/doctor.type.d.mts +1 -1
  54. package/api/doctor/doctor.type.mjs +1 -1
  55. package/api/error.d.mts +22 -0
  56. package/api/error.mjs +42 -0
  57. package/api/gap-report/gap-report.doc.mjs +19 -10
  58. package/api/hook/hook.doc.mjs +6 -3
  59. package/api/index.d.mts +1 -0
  60. package/api/index.mjs +5 -3
  61. package/api/init/init.doc.mjs +17 -12
  62. package/api/integration/add-contribution.d.mts +2 -1
  63. package/api/integration/add-contribution.mjs +7 -3
  64. package/api/integration/add-contribution.test.mjs +3 -3
  65. package/api/integration/add-helpers.d.mts +5 -2
  66. package/api/integration/add-helpers.mjs +36 -9
  67. package/api/integration/add-theme.mjs +266 -23
  68. package/api/integration/add-theme.test.mjs +247 -0
  69. package/api/integration/authoring-checks.mjs +12 -9
  70. package/api/integration/authoring-checks.type.d.mts +8 -0
  71. package/api/integration/authoring-checks.type.mjs +7 -3
  72. package/api/integration/integration-authoring.type.d.mts +4 -1
  73. package/api/integration/integration-authoring.type.mjs +6 -1
  74. package/api/integration/integrationAdd.doc.mjs +6 -0
  75. package/api/integration/integrationAddTheme.doc.mjs +10 -0
  76. package/api/integration/integrationComponentConflicts.doc.mjs +1 -1
  77. package/api/integration/integrationDocConflicts.doc.mjs +1 -1
  78. package/api/integration/integrationPackCheck.doc.mjs +3 -3
  79. package/api/integration/integrationTemplateConflicts.doc.mjs +1 -1
  80. package/api/integration/pack-check.lifecycle-output.test.mjs +107 -0
  81. package/api/integration/pack-check.mjs +92 -10
  82. package/api/integration/pack-check.test.mjs +140 -1
  83. package/api/integration/pack-check.type.mjs +1 -1
  84. package/api/integration/validate-integration.d.mts +4 -2
  85. package/api/integration/validate-integration.mjs +7 -2
  86. package/api/integration/validate-integration.test.mjs +55 -0
  87. package/api/integration/validate-integration.type.d.mts +5 -0
  88. package/api/integration/validate-integration.type.mjs +5 -1
  89. package/api/integration/validateIntegration.doc.mjs +1 -1
  90. package/api/json/assertResponse.doc.mjs +1 -1
  91. package/api/json/isError.doc.mjs +1 -1
  92. package/api/layout/expand/expand.mjs +12 -7
  93. package/api/layout/expand/expand.receipt.test.mjs +74 -0
  94. package/api/layout/layout.type.d.mts +1 -0
  95. package/api/layout/layout.type.mjs +1 -0
  96. package/api/layout/layoutExpand.doc.mjs +1 -1
  97. package/api/search/search.d.mts +51 -1
  98. package/api/search/search.doc.mjs +2 -2
  99. package/api/search/search.mjs +299 -17
  100. package/api/search/search.test.mjs +216 -18
  101. package/api/swizzle/copy/copy.mjs +66 -3
  102. package/api/swizzle/swizzle.doc.mjs +11 -5
  103. package/api/template/copy/copy.mjs +15 -9
  104. package/api/template/copy/copy.receipt.test.mjs +77 -0
  105. package/api/template/copy/copy.test.mjs +9 -0
  106. package/api/template/show/show.mjs +15 -4
  107. package/api/template/show/show.test.mjs +76 -0
  108. package/api/template/template-integration.test.mjs +14 -0
  109. package/api/template/template.d.mts +1 -1
  110. package/api/template/template.doc.mjs +8 -3
  111. package/api/template/template.mjs +1 -0
  112. package/api/template/template.type.d.mts +2 -0
  113. package/api/template/template.type.mjs +2 -0
  114. package/api/theme/add/add.mjs +17 -25
  115. package/api/theme/add/add.rollback.test.mjs +158 -0
  116. package/api/theme/add/add.staging.test.mjs +40 -23
  117. package/api/theme/build/build.d.mts +24 -0
  118. package/api/theme/build/build.family.test.mjs +7 -12
  119. package/api/theme/build/build.mjs +243 -26
  120. package/api/theme/build/build.project-core.test.mjs +165 -0
  121. package/api/theme/build/build.rollback.test.mjs +148 -0
  122. package/api/theme/generateTonalPalette.doc.mjs +1 -2
  123. package/api/theme/listThemes.doc.mjs +1 -1
  124. package/api/theme/themeAdd.doc.mjs +9 -10
  125. package/api/theme/themeBuild.doc.mjs +13 -13
  126. package/api/theme/themeList.doc.mjs +1 -1
  127. package/api/theme/themeListAvailable.doc.mjs +2 -1
  128. package/api/theme/themePaletteGenerate.doc.mjs +15 -8
  129. package/api/theme/themeTargets.doc.mjs +3 -2
  130. package/api/theme/themeTemplate.doc.mjs +2 -1
  131. package/api/upgrade/run/files-changed.test.mjs +111 -0
  132. package/api/upgrade/run/run.mjs +25 -6
  133. package/api/upgrade/run/run.test.mjs +45 -1
  134. package/api/upgrade/upgrade.doc.mjs +24 -22
  135. package/api/upgrade/upgrade.type.d.mts +1 -0
  136. package/api/upgrade/upgrade.type.mjs +3 -2
  137. package/assets/codemods/__tests__/runner.test.mjs +3 -1
  138. package/assets/codemods/file-count.test.mjs +163 -0
  139. package/assets/codemods/integration-runner.mjs +3 -3
  140. package/assets/codemods/runner.mjs +5 -4
  141. package/assets/docs/README.md +4 -2
  142. package/assets/docs/browser-support.doc.mjs +11 -11
  143. package/assets/docs/color.doc.mjs +8 -2
  144. package/assets/docs/elevation.doc.mjs +6 -4
  145. package/assets/docs/getting-started.doc.mjs +5 -16
  146. package/assets/docs/icons.doc.mjs +3 -21
  147. package/assets/docs/illustrations.doc.mjs +7 -15
  148. package/assets/docs/internationalization.doc.mjs +7 -5
  149. package/assets/docs/layout.doc.dense.mjs +130 -82
  150. package/assets/docs/layout.doc.mjs +133 -77
  151. package/assets/docs/migration.doc.mjs +19 -21
  152. package/assets/docs/motion.doc.mjs +16 -3
  153. package/assets/docs/principles.doc.dense.mjs +5 -5
  154. package/assets/docs/principles.doc.mjs +8 -0
  155. package/assets/docs/principles.doc.zh.mjs +6 -6
  156. package/assets/docs/shape.doc.mjs +8 -3
  157. package/assets/docs/spacing.doc.mjs +7 -2
  158. package/assets/docs/styling-libraries.doc.mjs +6 -2
  159. package/assets/docs/styling.doc.mjs +19 -23
  160. package/assets/docs/theme.doc.dense.mjs +58 -18
  161. package/assets/docs/theme.doc.mjs +57 -47
  162. package/assets/docs/theme.doc.zh.mjs +9 -8
  163. package/assets/docs/tokens.doc.dense.mjs +2 -2
  164. package/assets/docs/tokens.doc.mjs +389 -8
  165. package/assets/docs/tokens.doc.zh.mjs +2 -2
  166. package/assets/docs/tree/add-a-component.doc.mjs +75 -0
  167. package/assets/docs/tree/add-a-theme.doc.mjs +85 -0
  168. package/assets/docs/tree/add-a-topic.doc.mjs +144 -0
  169. package/assets/docs/tree/agent-guidance.doc.mjs +138 -0
  170. package/assets/docs/tree/block-template.doc.mjs +130 -0
  171. package/assets/docs/tree/build-the-template.doc.mjs +28 -0
  172. package/assets/docs/tree/building-blocks.doc.mjs +46 -0
  173. package/assets/docs/tree/check-your-docs.doc.mjs +137 -0
  174. package/assets/docs/tree/checks.doc.mjs +119 -0
  175. package/assets/docs/tree/codemods.doc.mjs +147 -0
  176. package/assets/docs/tree/component-family.doc.mjs +113 -0
  177. package/assets/docs/tree/component-imports.doc.mjs +69 -0
  178. package/assets/docs/tree/component-lookups.doc.mjs +149 -0
  179. package/assets/docs/tree/components.doc.mjs +23 -0
  180. package/assets/docs/tree/configuration.doc.mjs +23 -0
  181. package/assets/docs/tree/debug-and-gap-reports.doc.mjs +182 -0
  182. package/assets/docs/tree/define-the-theme.doc.mjs +118 -0
  183. package/assets/docs/tree/describe-the-component.doc.mjs +57 -0
  184. package/assets/docs/tree/docs.doc.mjs +21 -0
  185. package/assets/docs/tree/document-the-template.doc.mjs +28 -0
  186. package/assets/docs/tree/document-the-theme.doc.mjs +68 -0
  187. package/assets/docs/tree/export-template-assets.doc.mjs +147 -0
  188. package/assets/docs/tree/extend-or-replace.doc.mjs +103 -0
  189. package/assets/docs/tree/fonts-and-assets.doc.mjs +106 -0
  190. package/assets/docs/tree/generate-a-palette.doc.mjs +66 -0
  191. package/assets/docs/tree/grade-template-with-agent.doc.mjs +105 -0
  192. package/assets/docs/tree/help.doc.mjs +16 -0
  193. package/assets/docs/tree/integrations.doc.mjs +25 -451
  194. package/assets/docs/tree/links.doc.mjs +98 -0
  195. package/assets/docs/tree/package-and-test.doc.mjs +32 -0
  196. package/assets/docs/tree/page-template.doc.mjs +71 -0
  197. package/assets/docs/tree/publishing.doc.mjs +111 -0
  198. package/assets/docs/tree/quick-start.doc.mjs +272 -0
  199. package/assets/docs/tree/replace-a-core-component.doc.mjs +104 -0
  200. package/assets/docs/tree/replace-a-core-template.doc.mjs +172 -0
  201. package/assets/docs/tree/sections-and-placement.doc.mjs +108 -0
  202. package/assets/docs/tree/see-it-in-an-app.doc.mjs +59 -0
  203. package/assets/docs/tree/ship.doc.mjs +16 -0
  204. package/assets/docs/tree/short-and-findable.doc.mjs +108 -0
  205. package/assets/docs/tree/single-component.doc.mjs +165 -0
  206. package/assets/docs/tree/start-a-template.doc.mjs +143 -0
  207. package/assets/docs/tree/subcomponent.doc.mjs +115 -0
  208. package/assets/docs/tree/template-assets.doc.mjs +64 -0
  209. package/assets/docs/tree/template-doc-overview.doc.mjs +121 -0
  210. package/assets/docs/tree/template-fonts.doc.mjs +102 -0
  211. package/assets/docs/tree/template-grading-rubric.doc.mjs +452 -0
  212. package/assets/docs/tree/template-icons.doc.mjs +97 -0
  213. package/assets/docs/tree/template-images-media.doc.mjs +127 -0
  214. package/assets/docs/tree/template-styles.doc.mjs +93 -0
  215. package/assets/docs/tree/templates.doc.mjs +34 -0
  216. package/assets/docs/tree/test-in-an-app.doc.mjs +115 -0
  217. package/assets/docs/tree/test-template-in-app.doc.mjs +128 -0
  218. package/assets/docs/tree/themes.doc.mjs +39 -0
  219. package/assets/docs/tree/troubleshooting.doc.mjs +153 -0
  220. package/assets/docs/tree/upgrading.doc.mjs +103 -0
  221. package/assets/docs/tree/use-a-theme-in-an-app.doc.mjs +51 -0
  222. package/assets/docs/tree/verify-packed-template.doc.mjs +77 -0
  223. package/assets/docs/tree/versioning.doc.mjs +162 -0
  224. package/assets/docs/tree/write-good-templates.doc.mjs +64 -0
  225. package/assets/docs/tree/write-the-template-file.doc.mjs +154 -0
  226. package/assets/docs/typography.doc.mjs +24 -4
  227. package/assets/docs/working-with-ai.doc.mjs +30 -22
  228. package/assets/templates/blocks/components/InternationalizationProvider/InternationalizationProvider01ShippedLocale.tsx +1 -1
  229. package/assets/templates/pages/ai-chat/template.doc.mjs +16 -1
  230. package/assets/templates/pages/ai-chat-landing/template.doc.mjs +9 -1
  231. package/assets/templates/pages/blank/template.doc.mjs +3 -1
  232. package/assets/templates/pages/canvas-editor/template.doc.mjs +9 -1
  233. package/assets/templates/pages/centered-hero/template.doc.mjs +3 -1
  234. package/assets/templates/pages/checkout-wizard/template.doc.mjs +1 -0
  235. package/assets/templates/pages/classic-gallery/template.doc.mjs +3 -1
  236. package/assets/templates/pages/contact-form/template.doc.mjs +16 -1
  237. package/assets/templates/pages/dashboard/template.doc.mjs +15 -1
  238. package/assets/templates/pages/dashboard-alert-rail/template.doc.mjs +23 -1
  239. package/assets/templates/pages/dashboard-cohort-funnel/template.doc.mjs +10 -1
  240. package/assets/templates/pages/dashboard-comparison/template.doc.mjs +9 -1
  241. package/assets/templates/pages/dashboard-composition/template.doc.mjs +10 -1
  242. package/assets/templates/pages/dashboard-progress/template.doc.mjs +17 -1
  243. package/assets/templates/pages/dashboard-scorecard/template.doc.mjs +2 -1
  244. package/assets/templates/pages/detail-page/template.doc.mjs +8 -0
  245. package/assets/templates/pages/documentation/template.doc.mjs +10 -1
  246. package/assets/templates/pages/documentation-design/template.doc.mjs +10 -1
  247. package/assets/templates/pages/documentation-technical/template.doc.mjs +10 -1
  248. package/assets/templates/pages/editor/template.doc.mjs +9 -1
  249. package/assets/templates/pages/file-explorer/template.doc.mjs +3 -1
  250. package/assets/templates/pages/form-two-column/template.doc.mjs +17 -1
  251. package/assets/templates/pages/form-wizard/template.doc.mjs +14 -1
  252. package/assets/templates/pages/form-wizard-dialog/template.doc.mjs +1 -0
  253. package/assets/templates/pages/gallery-hero/template.doc.mjs +10 -1
  254. package/assets/templates/pages/ide/template.doc.mjs +9 -1
  255. package/assets/templates/pages/incident-console/template.doc.mjs +10 -1
  256. package/assets/templates/pages/kanban-board/template.doc.mjs +11 -1
  257. package/assets/templates/pages/library/template.doc.mjs +15 -1
  258. package/assets/templates/pages/login/template.doc.mjs +10 -1
  259. package/assets/templates/pages/login-card/template.doc.mjs +11 -1
  260. package/assets/templates/pages/login-split/template.doc.mjs +10 -1
  261. package/assets/templates/pages/login-sso/template.doc.mjs +10 -1
  262. package/assets/templates/pages/messaging-shell/template.doc.mjs +11 -1
  263. package/assets/templates/pages/mixed-gallery/template.doc.mjs +10 -1
  264. package/assets/templates/pages/payment-form/template.doc.mjs +3 -1
  265. package/assets/templates/pages/product-detail/template.doc.mjs +9 -1
  266. package/assets/templates/pages/product-gallery/template.doc.mjs +10 -1
  267. package/assets/templates/pages/settings/template.doc.mjs +3 -1
  268. package/assets/templates/pages/settings-dialog/template.doc.mjs +9 -1
  269. package/assets/templates/pages/settings-sidebar/template.doc.mjs +9 -1
  270. package/assets/templates/pages/shell-nav/template.doc.mjs +10 -1
  271. package/assets/templates/pages/shell-side-nav/template.doc.mjs +14 -1
  272. package/assets/templates/pages/shell-top-nav/template.doc.mjs +11 -1
  273. package/assets/templates/pages/side-gallery/template.doc.mjs +3 -1
  274. package/assets/templates/pages/table/template.doc.mjs +11 -1
  275. package/assets/templates/pages/table-filter/template.doc.mjs +21 -1
  276. package/assets/templates/pages/table-grouped/template.doc.mjs +15 -1
  277. package/assets/templates/pages/table-inbox/template.doc.mjs +18 -6
  278. package/assets/templates/pages/table-page/template.doc.mjs +20 -1
  279. package/assets/templates/pages/table-tree/template.doc.mjs +14 -1
  280. package/assets/templates/pages/theme-showcase/template.doc.mjs +10 -1
  281. package/assets/templates/pages/work-item-detail/template.doc.mjs +10 -0
  282. package/assets/templates/themes/butter/icons.tsx +2 -0
  283. package/assets/templates/themes/chocolate/icons.tsx +2 -0
  284. package/assets/templates/themes/gothic/icons.tsx +2 -0
  285. package/assets/templates/themes/matcha/icons.tsx +2 -0
  286. package/assets/templates/themes/neutral/icons.tsx +2 -0
  287. package/assets/templates/themes/stone/icons.tsx +2 -0
  288. package/assets/templates/themes/y2k/icons.tsx +2 -0
  289. package/authoring/config/config.doc.mjs +9 -1
  290. package/authoring/config/parse.d.mts +2 -0
  291. package/authoring/config/parse.mjs +19 -0
  292. package/authoring/config/parse.test.mjs +8 -0
  293. package/authoring/config/type.ts +11 -0
  294. package/authoring/discover/discover.doc.d.mts +13 -0
  295. package/authoring/discover/discover.doc.mjs +138 -0
  296. package/authoring/discover/parse.d.mts +24 -0
  297. package/authoring/discover/parse.mjs +128 -0
  298. package/authoring/discover/parse.test.mjs +124 -0
  299. package/authoring/discover/type.ts +87 -0
  300. package/authoring/doctypes/_schema.d.mts +3 -2
  301. package/authoring/doctypes/_schema.mjs +6 -0
  302. package/authoring/doctypes/base/graph-fields.doc.mjs +3 -3
  303. package/authoring/doctypes/base/type.ts +4 -2
  304. package/authoring/doctypes/component/component.doc.mjs +6 -0
  305. package/authoring/doctypes/component/type.ts +8 -0
  306. package/authoring/doctypes/reference/reference.doc.mjs +7 -0
  307. package/authoring/doctypes/reference/type.ts +5 -0
  308. package/authoring/doctypes/schema/schema.doc.mjs +2 -2
  309. package/authoring/doctypes/template/parse.d.mts +2 -0
  310. package/authoring/doctypes/template/parse.mjs +1 -0
  311. package/authoring/doctypes/template/parse.test.mjs +21 -0
  312. package/authoring/doctypes/template/template.doc.mjs +7 -1
  313. package/authoring/doctypes/template/type.ts +12 -2
  314. package/authoring/index.d.mts +1 -0
  315. package/authoring/index.d.ts +10 -0
  316. package/authoring/index.mjs +1 -0
  317. package/authoring/integration/integration.doc.mjs +12 -10
  318. package/clients/cli/commands/component/index.mjs +152 -55
  319. package/clients/cli/commands/component-batch.test.mjs +341 -0
  320. package/clients/cli/commands/component-ownership.test.mjs +89 -0
  321. package/clients/cli/commands/component.doc.mjs +27 -9
  322. package/clients/cli/commands/discover.doc.mjs +53 -9
  323. package/clients/cli/commands/discover.mjs +393 -118
  324. package/clients/cli/commands/discover.sources.test.mjs +267 -0
  325. package/clients/cli/commands/docs.doc.mjs +1 -1
  326. package/clients/cli/commands/docs.mjs +60 -17
  327. package/clients/cli/commands/docs.test.mjs +113 -24
  328. package/clients/cli/commands/doctor-integration-docs.doc.mjs +3 -2
  329. package/clients/cli/commands/doctor-integration.test.mjs +53 -0
  330. package/clients/cli/commands/doctor.doc.mjs +3 -1
  331. package/clients/cli/commands/doctor.mjs +53 -9
  332. package/clients/cli/commands/gap-report.doc.mjs +10 -9
  333. package/clients/cli/commands/init.doc.mjs +9 -6
  334. package/clients/cli/commands/integration-add.doc.mjs +17 -7
  335. package/clients/cli/commands/integration-authoring.test.mjs +70 -9
  336. package/clients/cli/commands/integration-pack.doc.mjs +5 -9
  337. package/clients/cli/commands/integration-real-world.test.mjs +1 -1
  338. package/clients/cli/commands/integration-verify.doc.mjs +22 -0
  339. package/clients/cli/commands/integration.doc.mjs +4 -4
  340. package/clients/cli/commands/integration.mjs +76 -43
  341. package/clients/cli/commands/layout-expand.doc.mjs +3 -1
  342. package/clients/cli/commands/layout.expand-receipt.test.mjs +94 -0
  343. package/clients/cli/commands/layout.mjs +16 -0
  344. package/clients/cli/commands/manifest.doc.mjs +1 -1
  345. package/clients/cli/commands/search.doc.mjs +10 -3
  346. package/clients/cli/commands/search.mjs +21 -2
  347. package/clients/cli/commands/search.test.mjs +21 -4
  348. package/clients/cli/commands/swizzle.doc.mjs +1 -1
  349. package/clients/cli/commands/template.copy-receipt.test.mjs +60 -0
  350. package/clients/cli/commands/template.doc.mjs +1 -1
  351. package/clients/cli/commands/template.mjs +19 -5
  352. package/clients/cli/commands/template.show-media.test.mjs +62 -0
  353. package/clients/cli/commands/text-json-parity.test.mjs +7 -1
  354. package/clients/cli/commands/theme-add.doc.mjs +1 -1
  355. package/clients/cli/commands/theme-palette-generate.doc.mjs +3 -2
  356. package/clients/cli/commands/theme-palette.doc.mjs +1 -2
  357. package/clients/cli/commands/theme-targets.doc.mjs +2 -2
  358. package/clients/cli/commands/theme.doc.mjs +2 -1
  359. package/clients/cli/commands/upgrade.ascii-output.test.mjs +14 -0
  360. package/clients/cli/commands/upgrade.doc.mjs +62 -3
  361. package/clients/cli/commands/write-failure.test.mjs +175 -0
  362. package/clients/cli/index.mjs +28 -6
  363. package/clients/cli/lib/define-command.mjs +28 -4
  364. package/clients/cli/lib/define-command.test.mjs +54 -0
  365. package/clients/cli/lib/exit-codes.test.mjs +17 -1
  366. package/clients/cli/lib/json-shim.mjs +24 -14
  367. package/clients/cli/lib/manifest.mjs +48 -156
  368. package/clients/cli/lib/manifest.test.mjs +103 -9
  369. package/clients/cli/lib/parse-error-format.test.mjs +81 -0
  370. package/foundation/agent-docs/agent-docs.mjs +1 -1
  371. package/foundation/agent-docs/agent-docs.test.mjs +3 -2
  372. package/foundation/discovery/authoring-self-docs.mjs +1 -0
  373. package/foundation/discovery/authoring-self-docs.test.mjs +6 -2
  374. package/foundation/discovery/cli-self-docs.mjs +16 -2
  375. package/foundation/discovery/cli-self-docs.test.mjs +20 -0
  376. package/foundation/discovery/docs-discovery.mjs +5 -1
  377. package/foundation/discovery/docs-discovery.test.mjs +21 -0
  378. package/foundation/discovery/docs-section-key.d.mts +1 -1
  379. package/foundation/discovery/docs-section-key.mjs +1 -1
  380. package/foundation/discovery/template-adapter.d.mts +14 -0
  381. package/foundation/discovery/template-adapter.fixture-refs.test.mjs +37 -1
  382. package/foundation/discovery/template-adapter.mjs +21 -1
  383. package/foundation/doc-compiler/doc-loads.test.mjs +5 -4
  384. package/foundation/doc-compiler/inputs.test.mjs +0 -1
  385. package/foundation/doc-compiler/tree.d.mts +4 -0
  386. package/foundation/doc-compiler/tree.mjs +6 -1
  387. package/foundation/doc-compiler/tree.test.mjs +65 -14
  388. package/foundation/integrations/cli-requirement.d.mts +75 -11
  389. package/foundation/integrations/cli-requirement.mjs +120 -23
  390. package/foundation/integrations/cli-requirement.test.mjs +141 -9
  391. package/foundation/integrations/contribution-inventory.mjs +1 -1
  392. package/foundation/integrations/integrations.d.mts +14 -1
  393. package/foundation/integrations/integrations.mjs +41 -1
  394. package/foundation/integrations/integrations.test.mjs +31 -0
  395. package/foundation/response/batch.type.d.mts +33 -0
  396. package/foundation/response/batch.type.mjs +34 -0
  397. package/foundation/response/error-codes.doc.mjs +6 -8
  398. package/foundation/response/error-codes.test.mjs +30 -5
  399. package/foundation/response/response-types.doc.d.mts +5 -4
  400. package/foundation/response/response-types.doc.mjs +49 -19
  401. package/foundation/response/response-types.doc.test.mjs +23 -0
  402. package/foundation/response/response.doc.mjs +11 -10
  403. package/package.json +9 -9
  404. package/assets/docs/tree/integrations.test.mjs +0 -62
  405. package/assets/docs/tree/writing-docs.doc.mjs +0 -286
@@ -7,18 +7,19 @@
7
7
  * lets an AI agent drive the entire CLI WITHOUT scraping `--help` text. It is
8
8
  * to the CLI what an OpenAPI spec is to an HTTP API.
9
9
  *
10
- * Most of the manifest is DERIVED from Commander metadata (program.commands,
11
- * cmd.options, cmd.registeredArguments, cmd.description()) so it cannot drift
12
- * out of sync with the real command definitions. The two pieces Commander does
13
- * not know about — whether a command supports `--json`, and which response
14
- * `type` discriminators it can emit — are layered on from:
10
+ * Its per-command facts come from the command's own definitions: names,
11
+ * arguments and options from Commander metadata (program.commands, cmd.options,
12
+ * cmd.registeredArguments, cmd.description()); examples from its CommandDoc;
13
+ * response `type` discriminators from the returns of the FunctionDoc it wraps
14
+ * (both attached by `defineCommand`); `--json` support from the JSON_SUPPORTED
15
+ * allowlist in index.mjs. The hand-kept lists are CLI_LAYER_RESPONSE_TYPES,
16
+ * for envelopes no API function returns, and ROOT_RESPONSE_TYPES, for the two
17
+ * (help and version) that no single command owns.
15
18
  *
16
- * - JSON_SUPPORTED (the allowlist in index.mjs), and
17
- * - RESPONSE_TYPES (the declarative map below).
18
- *
19
- * A drift-guard test (manifest.test.mjs) asserts every registered command
20
- * appears in the manifest and every JSON-supported command has a response-type
21
- * entry, so adding a command without describing it fails CI.
19
+ * Drift-guard tests (manifest.test.mjs) assert every registered command appears
20
+ * in the manifest, every JSON-supported command has response types, and the
21
+ * examples and response types equal the docs, so adding a command without
22
+ * describing it fails CI.
22
23
  *
23
24
  * DESIGN DECISION — manifest stays CLI-special; there is intentionally NO
24
25
  * `api/manifest`. It describes the CLI's own Commander tree, so it takes the live
@@ -39,153 +40,27 @@ import {commandDocsOf} from './define-command.mjs';
39
40
  import {doc as manifestDoc} from '../commands/manifest.doc.mjs';
40
41
 
41
42
  /**
42
- * Response `type` discriminators each fully-qualified command can emit in
43
- * `--json` mode. Keyed by the same name Commander reports (parent + leaf,
44
- * space-joined), e.g. `theme build`. Commander has no knowledge of these —
45
- * they come from each command's `jsonOut(...)` call sites — so we keep them
46
- * here, close to the JSON_SUPPORTED allowlist, and guard them with a test.
47
- *
43
+ * Envelopes the CLI layer builds itself, so the wrapped FunctionDoc cannot
44
+ * declare them: `manifest` wraps no API function, `theme build` batches several
45
+ * `themeBuild()` receipts, and `theme add` with no slug (or `--list`) answers
46
+ * with `themeListAvailable()`'s `theme.list` instead of calling `themeAdd()`.
47
+ * manifest.test.mjs fails once the wrapped FunctionDoc declares one of these, so
48
+ * the list only shrinks.
48
49
  * @type {Record<string, string[]>}
49
50
  */
50
- export const RESPONSE_TYPES = {
51
- init: ['init.run', 'init.remove'],
52
- component: [
53
- 'component.list',
54
- 'component.detail',
55
- 'component.detail.props',
56
- 'component.detail.source',
57
- 'component.detail.showcase',
58
- 'component.detail.blocks',
59
- ],
60
- docs: [
61
- 'docs.list',
62
- 'docs.index',
63
- 'docs.detail',
64
- 'docs.detail.section',
65
- 'docs.node',
66
- ],
67
- blog: ['blog.list', 'blog.detail'],
68
- discover: [
69
- 'discover.list',
70
- 'discover.detail',
71
- 'discover.detail.doc',
72
- 'discover.search',
73
- ],
74
- search: ['search'],
75
- build: ['build.help', 'build.kit'],
76
- swizzle: ['swizzle.list', 'swizzle.copy'],
77
- 'gap-report': ['gap-report.categories', 'gap-report.file'],
78
- template: [
79
- 'template.list',
80
- 'template.show',
81
- 'template.skeleton',
82
- 'template.copy',
83
- 'template.cdn',
84
- ],
85
- hook: ['hook.list', 'hook.detail', 'hook.detail.params'],
86
- 'theme build': ['theme.build', 'theme.build.check', 'theme.build.batch'],
87
- 'theme list': ['theme.list'],
88
- 'theme add': ['theme.list', 'theme.add'],
89
- 'theme template': ['theme.template'],
90
- 'theme targets': ['theme.targets'],
91
- 'theme palette generate': ['theme.palette.generate'],
92
- 'integration add': ['integration.add'],
93
- 'integration pack': ['integration.pack-check'],
94
- upgrade: ['upgrade.list', 'upgrade.status', 'upgrade.run'],
51
+ const CLI_LAYER_RESPONSE_TYPES = {
95
52
  manifest: ['manifest'],
96
- doctor: ['doctor'],
97
- 'doctor integration validate': ['integration.validate'],
98
- 'doctor integration templates': ['integration.template-conflicts'],
99
- 'doctor integration components': ['integration.component-conflicts'],
100
- 'doctor integration docs': ['integration.doc-conflicts'],
101
- 'layout expand': ['layout.expand'],
102
- 'layout check': ['layout.check'],
103
- 'layout grammar': ['layout.grammar'],
53
+ 'theme build': ['theme.build.batch'],
54
+ 'theme add': ['theme.list'],
104
55
  };
105
56
 
106
57
  /**
107
- * Example invocations per fully-qualified command. Optional, agent-facing.
108
- * @type {Record<string, string[]>}
58
+ * Response types no single command owns: `help` (a bare `astryx --json`, and
59
+ * `--help --json` on any command) and `version` (`astryx --version --json`).
60
+ * The response-types enum lists them beside the per-command response types.
61
+ * @type {readonly string[]}
109
62
  */
110
- const EXAMPLES = {
111
- component: [
112
- 'astryx component',
113
- 'astryx component XDSButton',
114
- 'astryx component XDSButton --props --json',
115
- ],
116
- docs: [
117
- 'astryx docs',
118
- 'astryx docs spacing --json',
119
- 'astryx docs theme',
120
- 'astryx docs theme quick-start',
121
- 'astryx docs cli/integrations --full',
122
- ],
123
- discover: ['astryx discover --json'],
124
- search: [
125
- 'astryx search modal --json',
126
- 'astryx search button --type component --json',
127
- ],
128
- build: ['astryx build', 'astryx build "analytics dashboard" --json'],
129
- swizzle: ['astryx swizzle XDSButton'],
130
- 'gap-report': [
131
- 'astryx gap-report --list-categories',
132
- "astryx gap-report Button --category docs_gap --reason 'Missing keyboard example'",
133
- ],
134
- template: [
135
- 'astryx template --json',
136
- 'astryx template dashboard ./src/app',
137
- 'astryx template --cdn',
138
- ],
139
- hook: ['astryx hook', 'astryx hook useFocusTrap --json'],
140
- 'theme build': [
141
- 'astryx theme build ./src/themes/ocean.ts --out ./dist/ocean.css',
142
- 'astryx theme build ./src/themes/ocean.ts --check',
143
- ],
144
- 'theme list': ['astryx theme list --json'],
145
- 'theme add': [
146
- 'astryx theme add matcha',
147
- 'astryx theme add matcha ./src/themes/matcha',
148
- ],
149
- 'theme template': ['astryx theme template', 'astryx theme template --json'],
150
- 'theme targets': [
151
- 'astryx theme targets Switch',
152
- 'astryx --json theme targets',
153
- ],
154
- 'theme palette generate': [
155
- 'astryx theme palette generate palette.config.json',
156
- 'astryx theme palette generate palette.config.json --out ocean.palette.json',
157
- ],
158
- 'integration add': [
159
- 'astryx integration add component AcmeWidget',
160
- 'astryx integration add doc deploying --dry-run --json',
161
- ],
162
- 'integration pack': ['astryx integration pack --check --json'],
163
- upgrade: ['astryx upgrade --json'],
164
- manifest: ['astryx manifest --json', 'astryx --json'],
165
- doctor: ['astryx doctor', 'astryx doctor --json'],
166
- 'doctor integration validate': [
167
- 'astryx doctor integration validate',
168
- 'astryx doctor integration validate @acme/widgets --json',
169
- ],
170
- 'doctor integration templates': [
171
- 'astryx doctor integration templates',
172
- 'astryx doctor integration templates @acme/widgets --json',
173
- ],
174
- 'doctor integration components': [
175
- 'astryx doctor integration components',
176
- 'astryx doctor integration components @acme/widgets --json',
177
- ],
178
- 'doctor integration docs': [
179
- 'astryx doctor integration docs',
180
- 'astryx doctor integration docs @acme/widgets --json',
181
- ],
182
- init: ['astryx init', 'astryx init --all --json'],
183
- 'layout expand': [
184
- `astryx layout expand 'V[g6] > C{card-callout}*4' ./src/Page.tsx`,
185
- ],
186
- 'layout check': [`astryx layout check 'A[cp6] > L > LC > S[p6]' --json`],
187
- 'layout grammar': ['astryx layout grammar'],
188
- };
63
+ export const ROOT_RESPONSE_TYPES = Object.freeze(['help', 'version']);
189
64
 
190
65
  /**
191
66
  * Map a Commander Option to a flag descriptor. Derives type from whether the
@@ -301,11 +176,17 @@ function describeCommand(cmd, root, jsonSupported) {
301
176
  const aliases = cmd.aliases ? cmd.aliases() : [];
302
177
  if (aliases && aliases.length > 0) entry.aliases = [...aliases];
303
178
 
304
- // Response types this command can emit in --json mode (only meaningful for
305
- // JSON-supported leaves). Subcommand groups (e.g. bare `theme`) have none.
306
- if (RESPONSE_TYPES[name]) entry.responseTypes = [...RESPONSE_TYPES[name]];
179
+ // Response types this command can emit in --json mode: the returns of the
180
+ // FunctionDoc it wraps, then any envelope the CLI layer builds. Subcommand
181
+ // groups (e.g. bare `theme`) have none.
182
+ const responseTypes = [
183
+ ...(docs?.fn?.returns ?? []).map(r => r.type),
184
+ ...(CLI_LAYER_RESPONSE_TYPES[name] ?? []),
185
+ ];
186
+ if (responseTypes.length > 0) entry.responseTypes = responseTypes;
307
187
 
308
- if (EXAMPLES[name]) entry.examples = [...EXAMPLES[name]];
188
+ const examples = (docs?.doc.examples ?? []).map(e => e.cli);
189
+ if (examples.length > 0) entry.examples = examples;
309
190
 
310
191
  const exitCodes = (docs?.doc.exitCodes ?? []).map(({code, when}) => ({
311
192
  code,
@@ -348,6 +229,15 @@ function describeGlobalOptions(program) {
348
229
  return opts;
349
230
  }
350
231
 
232
+ /**
233
+ * Every command in a described tree, depth first.
234
+ * @param {any[]} commands
235
+ * @returns {any[]}
236
+ */
237
+ function flattenCommands(commands) {
238
+ return commands.flatMap(c => [c, ...flattenCommands(c.subcommands || [])]);
239
+ }
240
+
351
241
  /**
352
242
  * Build the full capability manifest from a configured Commander program.
353
243
  *
@@ -378,7 +268,9 @@ export function buildManifest(program, opts = {}) {
378
268
  // Flat index of every response `type` discriminator the CLI can emit,
379
269
  // keyed by command — lets an agent know what to expect back per call.
380
270
  responseTypes: Object.fromEntries(
381
- Object.entries(RESPONSE_TYPES).map(([k, v]) => [k, [...v]]),
271
+ flattenCommands(commands)
272
+ .filter(c => c.responseTypes)
273
+ .map(c => [c.name, [...c.responseTypes]]),
382
274
  ),
383
275
  };
384
276
  }
@@ -14,11 +14,46 @@
14
14
  * surfaces emit valid, enriched JSON.
15
15
  */
16
16
 
17
+ import * as fs from 'node:fs';
18
+ import * as os from 'node:os';
19
+ import * as path from 'node:path';
20
+ import {fileURLToPath, pathToFileURL} from 'node:url';
17
21
  import {describe, it, expect} from 'vitest';
18
22
  import {program, JSON_SUPPORTED} from '../index.mjs';
19
- import {buildManifest, RESPONSE_TYPES} from './manifest.mjs';
23
+ import {buildManifest} from './manifest.mjs';
20
24
  import {runCli} from '../../../test-utils/run-cli.mjs';
21
25
 
26
+ const HERE = path.dirname(fileURLToPath(import.meta.url));
27
+
28
+ /**
29
+ * Import every `*.doc.mjs` under `dir` whose doc has the given type, keyed by
30
+ * its `name`.
31
+ * @param {string} dir @param {string} type @returns {Promise<Map<string, any>>}
32
+ */
33
+ async function loadDocs(dir, type) {
34
+ const out = new Map();
35
+ for (const entry of fs.readdirSync(dir, {withFileTypes: true, recursive: true})) {
36
+ if (!entry.isFile() || !entry.name.endsWith('.doc.mjs')) continue;
37
+ const file = path.join(entry.parentPath, entry.name);
38
+ const {doc} = await import(pathToFileURL(file).href);
39
+ if (doc?.type === type) out.set(doc.name, doc);
40
+ }
41
+ return out;
42
+ }
43
+
44
+ const commandDocs = await loadDocs(path.join(HERE, '../commands'), 'command');
45
+ const functionDocs = await loadDocs(path.join(HERE, '../../../api'), 'function');
46
+
47
+ /**
48
+ * Envelopes built in the CLI layer, which no FunctionDoc can declare. Pinned so
49
+ * an entry is dropped once its type moves behind an API function.
50
+ */
51
+ const CLI_LAYER_TYPES = {
52
+ manifest: ['manifest'],
53
+ 'theme build': ['theme.build.batch'],
54
+ 'theme add': ['theme.list'],
55
+ };
56
+
22
57
  const manifest = buildManifest(program, {jsonSupported: JSON_SUPPORTED, version: '0.0.0-test'});
23
58
 
24
59
  /** Flatten the manifest command tree into fully-qualified names. */
@@ -64,17 +99,73 @@ describe('manifest: drift guards', () => {
64
99
 
65
100
  it('declares response types for every JSON-supported command', () => {
66
101
  for (const name of JSON_SUPPORTED) {
102
+ const entry = allEntries.find((c) => c.name === name);
67
103
  expect(
68
- RESPONSE_TYPES[name],
69
- `JSON-supported command "${name}" has no response-type entry`,
70
- ).toBeDefined();
71
- expect(RESPONSE_TYPES[name].length).toBeGreaterThan(0);
104
+ entry?.responseTypes?.length,
105
+ `JSON-supported command "${name}" has no response types`,
106
+ ).toBeGreaterThan(0);
107
+ expect(manifest.responseTypes[name]).toEqual(entry.responseTypes);
72
108
  }
73
109
  });
74
110
 
75
111
  it('has no response-type entry for a command that does not exist', () => {
76
- for (const name of Object.keys(RESPONSE_TYPES)) {
77
- expect(allNames.has(name), `RESPONSE_TYPES key "${name}" is not a real command`).toBe(true);
112
+ for (const name of Object.keys(manifest.responseTypes)) {
113
+ expect(allNames.has(name), `responseTypes key "${name}" is not a real command`).toBe(true);
114
+ }
115
+ });
116
+
117
+ it('takes every command example from its CommandDoc', () => {
118
+ for (const [name, doc] of commandDocs) {
119
+ const entry = allEntries.find((c) => c.name === name);
120
+ expect(entry, `CommandDoc "${name}" has no manifest entry`).toBeDefined();
121
+ expect(entry.examples ?? [], name).toEqual((doc.examples ?? []).map((e) => e.cli));
122
+ }
123
+ });
124
+
125
+ it('lists every response type the wrapped API function returns', () => {
126
+ for (const [name, doc] of commandDocs) {
127
+ if (!doc.fn) continue;
128
+ const entry = allEntries.find((c) => c.name === name);
129
+ for (const {type} of functionDocs.get(doc.fn).returns) {
130
+ expect(entry.responseTypes, `${name} can emit ${type}`).toContain(type);
131
+ }
132
+ }
133
+ });
134
+
135
+ it('lists upgrade.registry, which `upgrade --registry --json` emits', async () => {
136
+ const cwd = fs.mkdtempSync(path.join(os.tmpdir(), 'astryx-manifest-'));
137
+ fs.writeFileSync(path.join(cwd, 'package.json'), '{"name":"app","version":"1.0.0"}');
138
+ const {status, stdout} = await runCli(['--json', 'upgrade', '--registry'], {cwd});
139
+ expect(status).toBe(0);
140
+ expect(JSON.parse(stdout).type).toBe('upgrade.registry');
141
+ expect(manifest.responseTypes.upgrade).toContain('upgrade.registry');
142
+ });
143
+
144
+ it('lists theme.list for theme add, which lists themes when given no slug', async () => {
145
+ const cwd = fs.mkdtempSync(path.join(os.tmpdir(), 'astryx-manifest-'));
146
+ fs.writeFileSync(path.join(cwd, 'package.json'), '{"name":"app","version":"1.0.0"}');
147
+ const {status, stdout} = await runCli(['--json', 'theme', 'add'], {cwd});
148
+ expect(status).toBe(0);
149
+ expect(JSON.parse(stdout).type).toBe('theme.list');
150
+ expect(manifest.responseTypes['theme add']).toContain('theme.list');
151
+ });
152
+
153
+ it('takes response types from the wrapped FunctionDoc, plus CLI-layer envelopes', () => {
154
+ for (const [name, doc] of commandDocs) {
155
+ const entry = allEntries.find((c) => c.name === name);
156
+ const returns = doc.fn ? functionDocs.get(doc.fn).returns.map((r) => r.type) : [];
157
+ const expected = [...returns, ...(CLI_LAYER_TYPES[name] ?? [])];
158
+ expect(entry.responseTypes ?? [], name).toEqual(expected);
159
+ }
160
+ });
161
+
162
+ it('pins only CLI-layer envelopes that no FunctionDoc declares', () => {
163
+ for (const [name, types] of Object.entries(CLI_LAYER_TYPES)) {
164
+ const fn = commandDocs.get(name)?.fn;
165
+ const declared = fn ? functionDocs.get(fn).returns.map((r) => r.type) : [];
166
+ for (const type of types) {
167
+ expect(declared, `${type} is declared by ${fn}(); unpin it`).not.toContain(type);
168
+ }
78
169
  }
79
170
  });
80
171
 
@@ -156,7 +247,10 @@ describe('manifest: shape', () => {
156
247
 
157
248
  it('derives arguments from Commander metadata', () => {
158
249
  const component = allEntries.find((c) => c.name === 'component');
159
- expect(component.arguments.map((a) => a.name)).toContain('name');
250
+ const names = component.arguments.find((a) => a.name === 'names');
251
+ expect(names.required).toBe(false);
252
+ expect(names.variadic).toBe(true);
253
+ expect(names.description).toContain('Two or more return one ordered batch');
160
254
  const themeBuild = allEntries.find((c) => c.name === 'theme build');
161
255
  expect(themeBuild.arguments.map((a) => a.name)).toContain('files');
162
256
  const files = themeBuild.arguments.find((a) => a.name === 'files');
@@ -193,7 +287,7 @@ describe('manifest: e2e', () => {
193
287
  // Enriched: the full structured manifest is embedded.
194
288
  expect(parsed.data.manifest).toBeDefined();
195
289
  expect(parsed.data.manifest.commands.find((c) => c.name === 'component').responseTypes)
196
- .toContain('component.list');
290
+ .toEqual(expect.arrayContaining(['component.list', 'component.batch']));
197
291
  });
198
292
  });
199
293
 
@@ -0,0 +1,81 @@
1
+ // Copyright (c) Meta Platforms, Inc. and affiliates.
2
+
3
+ /**
4
+ * @file A parse failure reads like every other Astryx error in text mode.
5
+ *
6
+ * `astryx theme list --lang zh-Hans` printed Commander's own line —
7
+ * `error: option '--lang <locale>' argument 'zh-Hans' is invalid…` — while
8
+ * every other CLI error prints `Error: …`. `--json` was already correct
9
+ * (ERR_INVALID_LANG), so text and JSON disagreed on everything except the exit
10
+ * code. Commander writes that line before any Astryx code runs, so the shim is
11
+ * the only place that can speak for it.
12
+ */
13
+
14
+ import {describe, it, expect} from 'vitest';
15
+ import {Command} from 'commander';
16
+ import {runCli} from '../../../test-utils/run-cli.mjs';
17
+ import {installJsonShim} from './json-shim.mjs';
18
+
19
+ describe('a parse error prints the Astryx error format', () => {
20
+ it.each([
21
+ ['invalid --lang value', ['theme', 'list', '--lang', 'zh-Hans'], 'ERR_INVALID_LANG'],
22
+ ['invalid --detail value', ['theme', 'list', '--detail', 'nope'], 'ERR_INVALID_DETAIL'],
23
+ ['unknown option', ['component', '--bogus-flag'], 'ERR_INVALID_OPTION'],
24
+ ['missing argument', ['theme', 'build'], 'ERR_MISSING_ARGUMENT'],
25
+ ])('%s', async (_label, args, code) => {
26
+ const human = await runCli(args);
27
+
28
+ expect(human.status).toBe(1);
29
+ expect(human.stderr).toContain('Error: ');
30
+ // Commander's own lowercase line must not reach the user.
31
+ expect(human.stderr).not.toMatch(/(^|\n)error: /);
32
+
33
+ // --json is unchanged, and the two modes agree on the exit code.
34
+ const json = await runCli(['--json', ...args]);
35
+ expect(json.status).toBe(human.status);
36
+ expect(JSON.parse(json.stdout).code).toBe(code);
37
+ expect(json.stderr).toBe('');
38
+ });
39
+
40
+ it('carries Commander\'s explanation, not just a generic line', async () => {
41
+ const {stderr} = await runCli(['theme', 'list', '--lang', 'zh-Hans']);
42
+ expect(stderr).toContain("'zh-Hans' is invalid");
43
+ expect(stderr).toContain('en, zh, dense');
44
+ });
45
+
46
+ it('leaves --help and --version at exit 0 with nothing on stderr', async () => {
47
+ for (const args of [['--help'], ['theme', '--help']]) {
48
+ const r = await runCli(args);
49
+ expect(r.status).toBe(0);
50
+ expect(r.stderr).toBe('');
51
+ }
52
+ });
53
+
54
+ // Commander writes help through the same stderr channel when it shows help
55
+ // BECAUSE the invocation failed, so suppressing that channel wholesale would
56
+ // have taken the help with it. Driven on a throwaway program rather than a
57
+ // real command, so the guard outlives whichever command happens to have a
58
+ // subcommand group today.
59
+ it('still lets help reach stderr when help IS the failure report', () => {
60
+ const program = new Command('probe');
61
+ const group = program.command('group');
62
+ group.command('leaf').action(() => {});
63
+ installJsonShim(program);
64
+
65
+ /** @type {string[]} */
66
+ const written = [];
67
+ const original = process.stderr.write;
68
+ // @ts-expect-error test double for the write signature
69
+ process.stderr.write = str => {
70
+ written.push(String(str));
71
+ return true;
72
+ };
73
+ try {
74
+ group.outputHelp({error: true});
75
+ } finally {
76
+ process.stderr.write = original;
77
+ }
78
+
79
+ expect(written.join('')).toMatch(/Usage: probe group/);
80
+ });
81
+ });
@@ -491,7 +491,7 @@ export function generateCompressedIndex(
491
491
  }
492
492
  lines.push(' docs cli commands, API reference, integration authoring (one level at a time)');
493
493
  lines.push(' swizzle <Name> eject component source for deep customization');
494
- lines.push(' upgrade --apply run after any Astryx or integration dependency bump');
494
+ lines.push(' upgrade --from <old version> --apply run after any Astryx or integration dependency bump');
495
495
  const appendCount = agentDocs.reduce(
496
496
  (count, contribution) => count + contribution.append.length,
497
497
  0,
@@ -105,7 +105,8 @@ describe('generateCompressedIndex', () => {
105
105
 
106
106
  it('includes upgrade command and migration rule', () => {
107
107
  const result = generateCompressedIndex('1.0.0');
108
- expect(result).toContain('upgrade --apply');
108
+ // `upgrade --apply` alone stops with "Missing required --from".
109
+ expect(result).toContain('upgrade --from <old version> --apply');
109
110
  expect(result).toMatch(/after any Astryx or integration dependency bump/);
110
111
  });
111
112
 
@@ -201,7 +202,7 @@ describe('generateCompressedIndex', () => {
201
202
  ],
202
203
  });
203
204
 
204
- expect(result.indexOf('upgrade --apply')).toBeLessThan(
205
+ expect(result.indexOf('upgrade --from <old version> --apply')).toBeLessThan(
205
206
  result.indexOf('INTEGRATIONS:'),
206
207
  );
207
208
  expect(result.indexOf('INTEGRATIONS:')).toBeLessThan(
@@ -33,6 +33,7 @@ export const AUTHORING_SELF_DOCS = [
33
33
  'config/config.doc.mjs',
34
34
  'debug/debug.doc.mjs',
35
35
  'gap-report/gap-report.doc.mjs',
36
+ 'discover/discover.doc.mjs',
36
37
  'codemod/codemod.doc.mjs',
37
38
  'identity/identity.doc.mjs',
38
39
  'doctypes/base/graph-fields.doc.mjs',
@@ -63,7 +63,9 @@ describe('authoring self-docs', () => {
63
63
  describe('what the authoring docs say about the docs tree', () => {
64
64
  it('marks exactly the fields the docs tree does not read yet', () => {
65
65
  const notReadYet = graphFieldsDoc.fields
66
- .filter(field => /Not read yet/.test(field.description))
66
+ .filter(field =>
67
+ /^Reserved: .*Nothing reads it today/.test(field.description),
68
+ )
67
69
  .map(field => field.name);
68
70
  // The tree reads `placement` for every guide (spec:AST-046); the other
69
71
  // graph fields are still refused by every topic reader.
@@ -79,7 +81,9 @@ describe('what the authoring docs say about the docs tree', () => {
79
81
  expect(placement.description).toMatch(
80
82
  /Read for every guide, the CLI's and each integration's/,
81
83
  );
82
- expect(graphFieldsDoc.description).toMatch(/not built yet/);
84
+ expect(graphFieldsDoc.description).toMatch(
85
+ /Nothing reads `aliases` or `audience` today/,
86
+ );
83
87
  });
84
88
 
85
89
  it('keeps graph blocks behind the separate GraphContentBlock type, and a section takes a reference block', () => {
@@ -315,12 +315,20 @@ function functionSection(fn, index) {
315
315
  }
316
316
  const params = fn.params ?? [];
317
317
  if (params.length > 0) {
318
+ // A Default column only when some parameter declares a default, so a
319
+ // function with none keeps a three-column table.
320
+ const defaults = params.some(
321
+ (/** @type {any} */ p) => typeof p.default === 'string' && p.default !== '',
322
+ );
318
323
  content.push({
319
324
  type: 'table',
320
- headers: ['Parameter', 'Type', 'Description'],
325
+ headers: defaults
326
+ ? ['Parameter', 'Type', 'Default', 'Description']
327
+ : ['Parameter', 'Type', 'Description'],
321
328
  rows: params.map((/** @type {any} */ p) => [
322
- `\`${p.name}\``,
329
+ `\`${p.name}\`${p.required ? ' (required)' : ''}`,
323
330
  `\`${p.type ?? ''}\``,
331
+ ...(defaults ? [p.default ?? ''] : []),
324
332
  p.description ?? '',
325
333
  ]),
326
334
  });
@@ -405,6 +413,12 @@ export function cliDocSection(doc, index) {
405
413
  if (doc.type === 'command') return commandSection(doc, index);
406
414
  if (doc.type === 'function') return functionSection(doc, index);
407
415
  if (doc.type === 'enum') return enumSection(doc);
416
+ if (doc.type === 'namespace')
417
+ return {
418
+ id: routeSegment(doc.name),
419
+ title: doc.title,
420
+ content: doc.blocks ?? [],
421
+ };
408
422
  return {...selfDocSection(doc), id: routeSegment(doc.name)};
409
423
  }
410
424
 
@@ -315,6 +315,26 @@ describe('cliDocSection', () => {
315
315
  ]);
316
316
  });
317
317
 
318
+ it('marks required parameters and adds a Default column when a parameter has a default', () => {
319
+ const beta = fn('beta', {
320
+ params: [
321
+ {name: 'slug', type: 'string', description: 'The slug.', required: true},
322
+ {name: 'options.limit', type: 'number', description: 'How many.', default: '20'},
323
+ ],
324
+ });
325
+ const table = cliDocSection(beta, indexOf([beta])).content.find(
326
+ (/** @type {any} */ block) => block.type === 'table',
327
+ );
328
+ expect(table).toEqual({
329
+ type: 'table',
330
+ headers: ['Parameter', 'Type', 'Default', 'Description'],
331
+ rows: [
332
+ ['`slug` (required)', '`string`', '', 'The slug.'],
333
+ ['`options.limit`', '`number`', '20', 'How many.'],
334
+ ],
335
+ });
336
+ });
337
+
318
338
  it('renders an API function, an enum, and a schema', () => {
319
339
  const alpha = fn('alpha', {
320
340
  description: 'Longer.',
@@ -626,7 +626,11 @@ function findMergeTarget(sections, section) {
626
626
  const sameTitle = sections.findIndex(
627
627
  candidate => sourceTitle(candidate) === title,
628
628
  );
629
- return sameTitle;
629
+ if (sameTitle !== -1) return sameTitle;
630
+ // A base section retitled later keeps its old key as its `id`, so an
631
+ // extension that still names it by the old title finds it by that id. A
632
+ // title variant of a section with no id stays a separate section.
633
+ return sections.findIndex(candidate => candidate.id === key);
630
634
  }
631
635
 
632
636
  /**
@@ -509,6 +509,27 @@ describe('mergeTopic', () => {
509
509
  expect(base.sections[0].content[0].text).toBe('npm i');
510
510
  });
511
511
 
512
+ it('finds a retitled base section by the key its old title derives', () => {
513
+ // A base section retitled later keeps its old key as its `id`; an
514
+ // extension that still names it by the old title replaces it.
515
+ const merged = mergeTopic(
516
+ {
517
+ ...base,
518
+ sections: [
519
+ {id: 'quick-start', title: 'Wrap your app in a theme', content: []},
520
+ {id: 'tokens', title: 'Tokens', content: []},
521
+ ],
522
+ },
523
+ {sections: [{title: 'Quick Start', content: []}]},
524
+ );
525
+ expect(merged.sections.map(section => [section.id, section.title])).toEqual(
526
+ [
527
+ ['quick-start', 'Quick Start'],
528
+ ['tokens', 'Tokens'],
529
+ ],
530
+ );
531
+ });
532
+
512
533
  it('replaces a section by stable ID even when its title changes', () => {
513
534
  const merged = mergeTopic(
514
535
  {
@@ -32,7 +32,7 @@ export function sectionTitleKey(title: unknown): string;
32
32
  export function sectionKey(section: any): string;
33
33
  /**
34
34
  * A name as a docs-tree route segment: lowercase words joined by hyphens, so
35
- * `integrationPackCheck` and `integration pack` both read naturally.
35
+ * `integrationPackCheck` and `integration verify` both read naturally.
36
36
  * @param {string} name
37
37
  * @returns {string}
38
38
  */