@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
@@ -41,7 +41,7 @@ export const doc = {
41
41
  examples: [
42
42
  {label: 'Scaffold a theme', cli: 'astryx theme add matcha'},
43
43
  {
44
- label: 'Select an integration theme',
44
+ label: 'Pick the owner when two packages ship the same slug',
45
45
  cli: 'astryx theme add ocean --package @acme/themes',
46
46
  },
47
47
  ],
@@ -35,12 +35,13 @@ export const doc = {
35
35
  {
36
36
  flag: '--preview <path>',
37
37
  param: 'options.preview',
38
- description: 'Write a standardized self-contained HTML preview',
38
+ description: 'Write a self-contained HTML preview page; the path must end in .html',
39
39
  },
40
40
  {
41
41
  flag: '-f, --overwrite',
42
42
  param: 'options.overwrite',
43
- description: 'Replace existing candidate and receipt files',
43
+ description:
44
+ 'Replace existing candidate, receipt, and preview files. Without it, if any of them exists, nothing is written',
44
45
  },
45
46
  ],
46
47
  examples: [
@@ -8,8 +8,7 @@ export const doc = {
8
8
  namespace: 'cli/commands',
9
9
  summary: 'Create and work with theme-owned color palettes',
10
10
  description:
11
- 'Palette authoring tools. The initial generate command creates reviewable candidates. ' +
12
- 'Palette inspection and diagnostic commands are intentionally deferred to follow-up work.',
11
+ 'Palette authoring tools. generate writes a palette candidate for you to review before a theme uses it.',
13
12
  subcommands: ['generate'],
14
13
  examples: [
15
14
  {
@@ -20,8 +20,8 @@ export const doc = {
20
20
  'the component that declares it, and the props and states that are legal override keys ' +
21
21
  'under it. This is the whole themeable surface in one command: what auditing a theme, or ' +
22
22
  'answering "which key paints this pixel?", used to need one `astryx component <Name>` per ' +
23
- 'component to assemble. Pass a component name to scope it; pass any substring to search ' +
24
- 'keys. `--json` for a list a repo can lint its own theme against.',
23
+ 'component to assemble. Pass a component name to scope it; pass any other text to search ' +
24
+ 'target keys, classes, and components. `--json` for a list a repo can lint its own theme against.',
25
25
  fn: 'themeTargets',
26
26
  args: [{name: 'filter', param: 'filter', required: false}],
27
27
  examples: [
@@ -13,7 +13,8 @@ export const doc = {
13
13
  name: 'theme',
14
14
  displayName: 'astryx theme',
15
15
  namespace: 'cli/commands',
16
- summary: 'Theme tools: build, export, and manage themes',
16
+ summary:
17
+ 'Create and build themes: add a shipped one, compile to CSS, or list what a theme can override',
17
18
  description:
18
19
  'The theme command group. Running astryx theme with no subcommand prints the ' +
19
20
  'subcommand list; the work happens in the subcommands: compile a theme (build), ' +
@@ -84,4 +84,18 @@ describe('upgrade human output is ASCII', () => {
84
84
  'minSize: 200',
85
85
  );
86
86
  });
87
+
88
+ // Every fixture above has a real src/, so the completion line for a project
89
+ // whose source is somewhere else was never reached — this suite was green on
90
+ // that path by luck, not by coverage.
91
+ it('reports a source directory that does not exist', async () => {
92
+ fs.rmSync(path.join(tmpDir, 'src'), {recursive: true, force: true});
93
+ fs.mkdirSync(path.join(tmpDir, 'app'), {recursive: true});
94
+ write('app/panel.tsx', 'export const x = 1;\n');
95
+
96
+ expect(await nonAsciiLines(['upgrade', '--from', '0.5.0'])).toEqual([]);
97
+ expect(
98
+ await nonAsciiLines(['upgrade', '--from', '0.5.0', '--apply']),
99
+ ).toEqual([]);
100
+ });
87
101
  });
@@ -13,7 +13,8 @@ export const doc = {
13
13
  name: 'upgrade',
14
14
  displayName: 'astryx upgrade',
15
15
  namespace: 'cli/commands',
16
- summary: 'Migrate versions and update ShadCN-copied compositions',
16
+ summary:
17
+ 'Update your code after upgrading Astryx, and refresh ShadCN-copied components',
17
18
  description:
18
19
  'Migrates project source from a previous Astryx version to the installed one by ' +
19
20
  'running the registered codemods, and refreshes the fully rendered managed ' +
@@ -33,7 +34,7 @@ export const doc = {
33
34
  {
34
35
  flag: '--apply',
35
36
  param: 'options.apply',
36
- description: 'Write changes to disk (default: dry-run)',
37
+ description: 'Write changes to disk; without it, the run is a dry run',
37
38
  default: false,
38
39
  },
39
40
  {
@@ -80,7 +81,8 @@ export const doc = {
80
81
  flag: '--registry',
81
82
  param: 'options.registry',
82
83
  description:
83
- 'Only reconcile ShadCN-copied compositions; --from is not required. ' +
84
+ 'Only update ShadCN-copied compositions from their install receipts: unchanged files are updated, ' +
85
+ 'edits that do not overlap are merged, and conflicts are left untouched; --from is not required. ' +
84
86
  'Combining it with --list, --from, --force, --codemod, --skip-codemod, --integration or --install-deps exits 1 with ERR_INVALID_ARGUMENT',
85
87
  default: false,
86
88
  },
@@ -113,4 +115,61 @@ export const doc = {
113
115
  },
114
116
  ],
115
117
  related: ['init', 'doctor'],
118
+ notes: [
119
+ {type: 'heading', level: 3, text: 'Protected files'},
120
+ {
121
+ type: 'prose',
122
+ text:
123
+ 'Codemods never write to a file your project marks as generated, vendored, or ignored. ' +
124
+ 'upgrade reads these marks from the files on disk, so the answer is the same with any version control, or none. ' +
125
+ 'A file is protected when:',
126
+ },
127
+ {
128
+ type: 'list',
129
+ style: 'unordered',
130
+ items: [
131
+ 'a `.gitattributes` file marks it `linguist-generated` or `linguist-vendored`',
132
+ 'its leading comment says `@generated`, `@partially-generated`, or `Code generated ... DO NOT EDIT.`',
133
+ 'a `.gitignore`, or the `.hgignore` at the project root, excludes it',
134
+ 'it is an installed dependency (such as anything in `node_modules`), is inside `.git`, `.hg`, or `.sl`, is a symbolic link, or is outside the project',
135
+ ],
136
+ },
137
+ {
138
+ type: 'prose',
139
+ text:
140
+ 'Rules work as they do in Git: a later rule wins, so `linguist-generated=false` or a `!` line in an ignore file ' +
141
+ 'returns a file to normal handling. A folder name such as `dist` or `generated` protects nothing by itself. ' +
142
+ 'If a protection file cannot be read or parsed, upgrade stops with ERR_CODEMOD_PROTECTION_SOURCE before it writes anything.',
143
+ },
144
+ {
145
+ type: 'code',
146
+ lang: 'text',
147
+ code:
148
+ '# .gitattributes\n' +
149
+ 'generated/** linguist-generated=true\n' +
150
+ 'vendor/** linguist-vendored=true\n' +
151
+ '\n' +
152
+ '# A later rule returns one authored file to normal handling\n' +
153
+ 'generated/hand-authored.ts linguist-generated=false',
154
+ },
155
+ {
156
+ type: 'prose',
157
+ text:
158
+ 'When a codemod would change a protected file, upgrade makes the change only in memory and leaves the file as it is. ' +
159
+ 'The rest of the upgrade goes ahead. With --apply, your other files are written, then upgrade runs the ' +
160
+ '`hooks.postCodemod` commands from astryx.config (see `astryx docs authoring config`) and checks the protected ' +
161
+ 'files again. If one still needs the change, the run is incomplete: it exits 1, prints ' +
162
+ 'ERR_CODEMOD_PROTECTED with each file and the rule that protects it, and does not refresh the agent docs. ' +
163
+ 'Regenerate or edit those files, then run the same upgrade again. A dry run reports the same files and writes nothing.',
164
+ },
165
+ {
166
+ type: 'prose',
167
+ text:
168
+ 'With --json, the receipt says `complete: false` and `errorCode: "ERR_CODEMOD_PROTECTED"`. `modifiedFiles` lists the files ' +
169
+ 'upgrade changed (or would change), and `protectedFiles` lists each blocked file with its `reasons`, `declarations` ' +
170
+ '(the rules that protect it), `codemods`, and `commands`. A generated file can name the command that rebuilds it on a ' +
171
+ '`Command:` line in its header, such as `// Command: pnpm run gen:panel`; upgrade prints it as ' +
172
+ '`Regenerate with: <command>` and lists it in `commands`.',
173
+ },
174
+ ],
116
175
  };
@@ -0,0 +1,175 @@
1
+ // Copyright (c) Meta Platforms, Inc. and affiliates.
2
+
3
+ /**
4
+ * @file A write that fails on the filesystem reports ERR_WRITE_FAILED.
5
+ *
6
+ * An unwritable target produced `{"error": "EACCES: permission denied, open
7
+ * '/abs/host/path/readonly/x.tsx'", "code": "ERR_UNKNOWN"}` — the raw Node
8
+ * errno error, with the wrong code and an absolute host path in the message.
9
+ * ERR_WRITE_FAILED is in the frozen registry for exactly this case, and every
10
+ * other Astryx message names its target relative to the project.
11
+ */
12
+
13
+ import {describe, it, expect, beforeEach, afterEach} from 'vitest';
14
+ import * as fs from 'node:fs';
15
+ import * as os from 'node:os';
16
+ import * as path from 'node:path';
17
+ import {fileURLToPath} from 'node:url';
18
+ import {runCli} from '../../../test-utils/run-cli.mjs';
19
+
20
+ const REPO = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '../../../../..');
21
+
22
+ // chmod means nothing to root, so the unwritable directory would be writable
23
+ // and the command would succeed. Skip rather than assert something false.
24
+ const asRoot = typeof process.getuid === 'function' && process.getuid() === 0;
25
+
26
+ let dir;
27
+ let readonlyDir;
28
+
29
+ beforeEach(() => {
30
+ dir = fs.mkdtempSync(path.join(os.tmpdir(), 'astryx-write-fail-'));
31
+ fs.writeFileSync(
32
+ path.join(dir, 'package.json'),
33
+ JSON.stringify({
34
+ name: 'scratch',
35
+ version: '1.0.0',
36
+ dependencies: {'@astryxdesign/core': '0.6.3'},
37
+ }),
38
+ );
39
+ fs.mkdirSync(path.join(dir, 'node_modules', '@astryxdesign'), {recursive: true});
40
+ fs.symlinkSync(
41
+ path.join(REPO, 'packages', 'core'),
42
+ path.join(dir, 'node_modules', '@astryxdesign', 'core'),
43
+ 'dir',
44
+ );
45
+ readonlyDir = path.join(dir, 'readonly');
46
+ fs.mkdirSync(readonlyDir);
47
+ fs.chmodSync(readonlyDir, 0o500);
48
+ });
49
+
50
+ afterEach(() => {
51
+ try {
52
+ fs.chmodSync(readonlyDir, 0o700);
53
+ } catch {
54
+ // already gone
55
+ }
56
+ fs.rmSync(dir, {recursive: true, force: true});
57
+ });
58
+
59
+ /** @param {string[]} args */
60
+ const json = async args => {
61
+ const {status, stdout} = await runCli(['--json', ...args], {cwd: dir});
62
+ return {status, body: JSON.parse(stdout)};
63
+ };
64
+
65
+ describe.skipIf(asRoot)('a failed write reports ERR_WRITE_FAILED', () => {
66
+ it('template into an unwritable directory', async () => {
67
+ const {status, body} = await json(['template', 'ai-chat', 'readonly/x.tsx']);
68
+
69
+ expect(status).toBe(1);
70
+ expect(body.code).toBe('ERR_WRITE_FAILED');
71
+ expect(body.error).toContain('readonly/x.tsx');
72
+ expect(body.error).toContain('EACCES');
73
+ // The whole point of the relative form: no absolute host path escapes.
74
+ expect(body.error).not.toContain(dir);
75
+ expect(fs.existsSync(path.join(readonlyDir, 'x.tsx'))).toBe(false);
76
+ });
77
+
78
+ it('swizzle into an unwritable directory', async () => {
79
+ const {status, body} = await json([
80
+ 'swizzle',
81
+ 'Button',
82
+ '--output',
83
+ 'readonly/sub',
84
+ ]);
85
+
86
+ expect(status).toBe(1);
87
+ expect(body.code).toBe('ERR_WRITE_FAILED');
88
+ expect(body.error).toContain('readonly/sub');
89
+ expect(body.error).not.toContain(dir);
90
+ expect(fs.existsSync(path.join(readonlyDir, 'sub'))).toBe(false);
91
+ });
92
+
93
+ it('still writes normally into a writable directory', async () => {
94
+ const {status, body} = await json(['template', 'ai-chat', 'src/page.tsx']);
95
+
96
+ expect(status, JSON.stringify(body).slice(0, 200)).toBe(0);
97
+ expect(body.type).toBe('template.copy');
98
+ expect(fs.existsSync(path.join(dir, 'src', 'page.tsx'))).toBe(true);
99
+ });
100
+ });
101
+
102
+ describe.skipIf(asRoot)('a swizzle that fails part-way undoes what it wrote', () => {
103
+ it('restores replaced files, removes created ones, and says nothing was written', async () => {
104
+ // Learn the component's files, in copy order, from a successful swizzle.
105
+ const probe = await json(['swizzle', 'Button', '--output', 'probe']);
106
+ expect(probe.status, JSON.stringify(probe.body).slice(0, 200)).toBe(0);
107
+ const files = probe.body.data.files;
108
+ expect(files.length).toBeGreaterThan(1);
109
+ const outDir = path.join(dir, 'out', path.basename(probe.body.data.outputDir));
110
+
111
+ // The first file exists (replaceable), the middle ones do not, and the
112
+ // last one is read-only, so --overwrite fails after writing the others.
113
+ const first = files[0];
114
+ const middle = files.slice(1, -1);
115
+ const last = files[files.length - 1];
116
+ fs.mkdirSync(outDir, {recursive: true});
117
+ fs.writeFileSync(path.join(outDir, first), 'before first\n');
118
+ fs.writeFileSync(path.join(outDir, last), 'before last\n');
119
+ fs.chmodSync(path.join(outDir, last), 0o444);
120
+
121
+ const {status, body} = await json([
122
+ 'swizzle',
123
+ 'Button',
124
+ '--output',
125
+ 'out',
126
+ '--overwrite',
127
+ ]);
128
+ fs.chmodSync(path.join(outDir, last), 0o644);
129
+
130
+ expect(status).toBe(1);
131
+ expect(body.code).toBe('ERR_WRITE_FAILED');
132
+ expect(body.error).toContain(last);
133
+ expect(body.error).toContain('Nothing was written.');
134
+ expect(body.error).not.toContain(dir);
135
+ expect(fs.readFileSync(path.join(outDir, first), 'utf8')).toBe('before first\n');
136
+ expect(fs.readFileSync(path.join(outDir, last), 'utf8')).toBe('before last\n');
137
+ for (const file of middle) {
138
+ expect(fs.existsSync(path.join(outDir, file))).toBe(false);
139
+ }
140
+ });
141
+
142
+ it('undoes the copy when a destination cannot be read back', async () => {
143
+ const probe = await json(['swizzle', 'Button', '--output', 'probe']);
144
+ expect(probe.status, JSON.stringify(probe.body).slice(0, 200)).toBe(0);
145
+ const files = probe.body.data.files;
146
+ const outDir = path.join(dir, 'out', path.basename(probe.body.data.outputDir));
147
+
148
+ // A directory where the last file goes: the rollback snapshot cannot read
149
+ // it, and the earlier files are already written when the copy reaches it.
150
+ const first = files[0];
151
+ const middle = files.slice(1, -1);
152
+ const last = files[files.length - 1];
153
+ fs.mkdirSync(path.join(outDir, last), {recursive: true});
154
+ fs.writeFileSync(path.join(outDir, first), 'before first\n');
155
+
156
+ const {status, body} = await json([
157
+ 'swizzle',
158
+ 'Button',
159
+ '--output',
160
+ 'out',
161
+ '--overwrite',
162
+ ]);
163
+
164
+ expect(status).toBe(1);
165
+ expect(body.code).toBe('ERR_WRITE_FAILED');
166
+ expect(body.error).toContain(last);
167
+ expect(body.error).toContain('Nothing was written.');
168
+ expect(body.error).not.toContain(dir);
169
+ expect(fs.readFileSync(path.join(outDir, first), 'utf8')).toBe('before first\n');
170
+ expect(fs.statSync(path.join(outDir, last)).isDirectory()).toBe(true);
171
+ for (const file of middle) {
172
+ expect(fs.existsSync(path.join(outDir, file))).toBe(false);
173
+ }
174
+ });
175
+ });
@@ -24,7 +24,7 @@ import {emit, section, text, records} from './formatters/index.mjs';
24
24
  import {ERROR_CODES} from '../../foundation/response/error-codes.mjs';
25
25
  import {levenshteinDistance} from '../../foundation/text/string-utils.mjs';
26
26
  import {installJsonShim} from './lib/json-shim.mjs';
27
- import {addExitCodesHelp, markReportsResult} from './lib/define-command.mjs';
27
+ import {addDocHelp, markReportsResult} from './lib/define-command.mjs';
28
28
  import {doc as manifestDoc} from './commands/manifest.doc.mjs';
29
29
  import {isAstryxInitialized} from '../../foundation/agent-docs/agent-docs.mjs';
30
30
  import * as debug from '../../foundation/debug/index.mjs';
@@ -95,6 +95,7 @@ export const JSON_SUPPORTED = new Set([
95
95
  'theme targets',
96
96
  'theme palette generate',
97
97
  'integration add',
98
+ 'integration verify',
98
99
  'integration pack',
99
100
  'upgrade',
100
101
  'manifest',
@@ -342,16 +343,24 @@ export async function createProgram() {
342
343
  .name('astryx')
343
344
  .description('Design system CLI — components, themes, and tooling')
344
345
  .version(pkg.version)
345
- .option('--zh', 'Output docs in Chinese Simplified')
346
- .option('--dense', 'Output docs in compressed dense format (token-efficient)')
346
+ // These four change only the reads named in their text; every other
347
+ // command ignores them.
348
+ .option(
349
+ '--zh',
350
+ 'Simplified Chinese for component reads and for docs topics that have a translation (English otherwise)',
351
+ )
352
+ .option('--dense', 'Token-efficient dense text for component <Name> and docs <topic> reads')
347
353
  .addOption(
348
354
  new Option(
349
355
  '--lang <locale>',
350
- 'Output docs in specified language/format (en, zh, dense)',
356
+ 'Language or format for component and docs reads: en (default), zh (as --zh), or dense (as --dense)',
351
357
  ).choices(['en', 'zh', 'dense']),
352
358
  )
353
359
  .addOption(
354
- new Option('--detail <level>', 'Output detail level (full, compact, brief)')
360
+ new Option(
361
+ '--detail <level>',
362
+ 'Detail level for component, hook, and docs tree reads (e.g. docs cli/commands/build). Lists default to brief',
363
+ )
355
364
  .choices(['full', 'compact', 'brief'])
356
365
  .default('full'),
357
366
  )
@@ -471,6 +480,19 @@ export async function createProgram() {
471
480
  const fullName = fullCommandName(actionCommand, program);
472
481
  if (JSON_SUPPORTED.has(fullName)) return;
473
482
  process.__xdsJsonHandled = true;
483
+ // A group given a word it does not have reports an unknown subcommand and
484
+ // lists the ones it has, in JSON as in text, even when a flag follows it.
485
+ const extras = actionCommand.commands.length > 0 ? actionCommand.args : [];
486
+ const unknown = extras.find(arg => !String(arg).startsWith('-'));
487
+ if (unknown != null) {
488
+ cliError(`unknown subcommand '${fullName} ${unknown}'`, {
489
+ code: ERROR_CODES.ERR_UNKNOWN_SUBCOMMAND,
490
+ suggestions: actionCommand.commands.map(child => ({
491
+ name: child.name(),
492
+ reason: 'available subcommand',
493
+ })),
494
+ });
495
+ }
474
496
  debug.setOutcome('rejected', {
475
497
  exitCode: 1,
476
498
  code: ERROR_CODES.ERR_INVALID_OPTION,
@@ -605,7 +627,7 @@ export async function createProgram() {
605
627
  text(`Run \`${getCliInvocation()} manifest --json\` for the full structured manifest.`),
606
628
  );
607
629
  });
608
- addExitCodesHelp(manifestCommand, manifestDoc.exitCodes);
630
+ addDocHelp(manifestCommand, manifestDoc);
609
631
  markReportsResult(manifestCommand);
610
632
 
611
633
  // Hidden command used by package.json postinstall scripts
@@ -25,6 +25,8 @@
25
25
  */
26
26
 
27
27
  import {recordCommandResult} from '../../../foundation/debug/index.mjs';
28
+ import {routeSegment} from '../../../foundation/discovery/docs-section-key.mjs';
29
+ import {formatCliCommand} from '../../../foundation/env/package-manager.mjs';
28
30
  import {text} from '../formatters/index.mjs';
29
31
 
30
32
  /**
@@ -143,10 +145,11 @@ export function defineCommand(parent, doc, {fn, action} = {}) {
143
145
  cmd.addOption(option);
144
146
  }
145
147
 
146
- // Help ends with the documented exit codes. `choices` stay in the option
147
- // text: Commander `.choices()` would replace the api layer's
148
- // ERR_INVALID_ARGUMENT validation.
149
- addExitCodesHelp(cmd, doc.exitCodes);
148
+ // Help ends with the documented exit codes, the examples, and the docs
149
+ // route that reads the whole command. `choices` stay in the option text:
150
+ // Commander `.choices()` would replace the api layer's ERR_INVALID_ARGUMENT
151
+ // validation.
152
+ addDocHelp(cmd, doc);
150
153
 
151
154
  if (action) {
152
155
  // The recording seam. An action's job ends at "here is what I answered
@@ -165,6 +168,27 @@ export function defineCommand(parent, doc, {fn, action} = {}) {
165
168
  return cmd;
166
169
  }
167
170
 
171
+ /**
172
+ * End `cmd`'s help with what its CommandDoc says: the exit codes, then the
173
+ * examples, then `More:`, the `astryx docs` route that reads the whole command.
174
+ * @param {import('commander').Command} cmd
175
+ * @param {import('@astryxdesign/cli/authoring').CommandDoc} doc
176
+ */
177
+ export function addDocHelp(cmd, doc) {
178
+ addExitCodesHelp(cmd, doc.exitCodes);
179
+ // Rendered when help is shown, so the run prefix (npx astryx, pnpm astryx,
180
+ // ...) is looked up then, not on every start.
181
+ cmd.addHelpText('after', () => {
182
+ const examples = (doc.examples ?? []).flatMap(({label, cli}) => [
183
+ ...(label ? [` # ${label}`] : []),
184
+ ` ${formatCliCommand(cli)}`,
185
+ ]);
186
+ const more = `More: ${formatCliCommand(`docs cli/commands/${routeSegment(doc.name)}`)}`;
187
+ const blocks = examples.length > 0 ? [['Examples:', ...examples].join('\n'), more] : [more];
188
+ return `\n${text(blocks.join('\n\n')).toString()}`;
189
+ });
190
+ }
191
+
168
192
  /**
169
193
  * End `cmd`'s help with a CommandDoc's exit codes.
170
194
  * @param {import('commander').Command} cmd
@@ -7,6 +7,7 @@
7
7
  import {Command} from 'commander';
8
8
  import {describe, it, expect} from 'vitest';
9
9
  import {defineCommand} from './define-command.mjs';
10
+ import {formatCliCommand} from '../../../foundation/env/package-manager.mjs';
10
11
  import {doc as searchCommand} from '../commands/search.doc.mjs';
11
12
  import {doc as searchFn} from '../../../api/search/search.doc.mjs';
12
13
 
@@ -46,4 +47,57 @@ describe('defineCommand', () => {
46
47
  expect(cmd.name()).toBe('build');
47
48
  expect(cmd.registeredArguments.map(a => a.name())).toEqual(['file']);
48
49
  });
50
+
51
+ it('ends help with the exit codes, the examples, and the docs route', () => {
52
+ const program = new Command();
53
+ const group = program.command('grp');
54
+ const cmd = defineCommand(
55
+ group,
56
+ {
57
+ type: 'command',
58
+ name: 'grp sub',
59
+ displayName: 'astryx grp sub',
60
+ summary: 'Sub.',
61
+ examples: [
62
+ {label: 'Run it', cli: 'astryx grp sub x'},
63
+ {cli: 'astryx grp sub y --json'},
64
+ ],
65
+ exitCodes: [{code: 0, when: 'it works'}],
66
+ },
67
+ {action: () => {}},
68
+ );
69
+ let out = '';
70
+ cmd.configureOutput({writeOut: s => (out += s)});
71
+ cmd.outputHelp();
72
+ const stem = formatCliCommand('');
73
+ expect(out.slice(out.indexOf('\nExit codes:\n'))).toBe(
74
+ [
75
+ '',
76
+ 'Exit codes:',
77
+ ' 0 it works',
78
+ '',
79
+ 'Examples:',
80
+ ' # Run it',
81
+ ` ${stem} grp sub x`,
82
+ ` ${stem} grp sub y --json`,
83
+ '',
84
+ `More: ${stem} docs cli/commands/grp-sub`,
85
+ '',
86
+ ].join('\n'),
87
+ );
88
+ });
89
+
90
+ it('still names the docs route when a command has no examples', () => {
91
+ const program = new Command();
92
+ const cmd = defineCommand(
93
+ program,
94
+ {type: 'command', name: 'solo', summary: 'Solo.', exitCodes: [{code: 0, when: 'ok'}]},
95
+ {action: () => {}},
96
+ );
97
+ let out = '';
98
+ cmd.configureOutput({writeOut: s => (out += s)});
99
+ cmd.outputHelp();
100
+ expect(out).not.toContain('Examples:');
101
+ expect(out.endsWith(`\n\nMore: ${formatCliCommand('docs cli/commands/solo')}\n`)).toBe(true);
102
+ });
49
103
  });
@@ -41,7 +41,7 @@ describe('command exit codes', () => {
41
41
  });
42
42
 
43
43
  it.each(commandDocs.map((d) => [d.name, d]))(
44
- '`astryx %s --help` lists the documented exit codes',
44
+ '`astryx %s --help` lists the documented exit codes, then the examples and the docs route',
45
45
  async (name, doc) => {
46
46
  const {status, stdout} = await runCli([...name.split(' '), '--help']);
47
47
  expect(status).toBe(0);
@@ -51,6 +51,22 @@ describe('command exit codes', () => {
51
51
  for (const {code, when} of doc.exitCodes) {
52
52
  expect(section).toContain(`\n ${code} ${when}\n`);
53
53
  }
54
+ // Examples follow the exit codes, each under its label, and a `More:`
55
+ // line names the route that reads the whole command.
56
+ const examples = section.indexOf('\nExamples:\n');
57
+ expect(examples > 0, stdout).toBe((doc.examples ?? []).length > 0);
58
+ for (const {label, cli} of doc.examples ?? []) {
59
+ const line = ` ${cli.replace(/^astryx\s+/, '')}\n`;
60
+ expect(section.slice(examples), stdout).toContain(
61
+ label ? `\n # ${label}\n` : line,
62
+ );
63
+ expect(section.slice(examples)).toContain(line);
64
+ }
65
+ const route = `docs cli/commands/${name.replace(/ /g, '-')}`;
66
+ expect(section, stdout).toMatch(
67
+ new RegExp(`\\n\\nMore: \\S.* ${route}\\n`),
68
+ );
69
+ expect(section.indexOf('\nMore: ')).toBeGreaterThan(examples);
54
70
  },
55
71
  );
56
72
 
@@ -33,10 +33,12 @@
33
33
  * shows help because the invocation failed (`help <unknown>`, or a
34
34
  * command group with no subcommand), which exits 1.
35
35
  *
36
- * Non-JSON behavior is preserved exactly: every code path that printed
37
- * to stderr before still prints to stderr. Commander writes its
38
- * "error: ..." line via configureOutput.writeErr, which we pass
39
- * through verbatim outside of --json mode.
36
+ * Commander writes its own "error: ..." line via configureOutput.writeErr.
37
+ * The shim drops that line in both modes. Under --json the error envelope
38
+ * replaces it; in text mode `handleCommanderError` writes the Astryx line
39
+ * instead (`Error: <message>`, the same message the envelope carries), so
40
+ * a parse failure reads like every other CLI error. Other stderr output,
41
+ * such as help printed as the failure report, still passes through.
40
42
  */
41
43
 
42
44
  import {API_VERSION, isJsonMode, toErrorEnvelope} from '../../../foundation/response/json.mjs';
@@ -249,11 +251,20 @@ function applyShimRecursively(cmd) {
249
251
  });
250
252
  cmd.configureOutput({
251
253
  writeOut: (str) => process.stdout.write(str),
254
+ // Commander's own "error: ..." line never reaches the user. Under --json a
255
+ // consumer parsing both streams must not see it alongside the envelope;
256
+ // in text mode it is Commander's format, not Astryx's, so an invalid
257
+ // global option (`--lang zh-Hans`) printed `error: option '--lang
258
+ // <locale>' argument 'zh-Hans' is invalid…` where every other CLI error
259
+ // prints `Error: …`. handleCommanderError writes the Astryx line below,
260
+ // for both modes, from the same message.
261
+ //
262
+ // ONLY that line. Commander also writes HELP through this channel when it
263
+ // shows help because the invocation failed (a command group with no
264
+ // subcommand), and that output is still wanted in text mode.
252
265
  writeErr: (str) => {
253
- // Suppress Commander's "error: ..." stderr line when --json is
254
- // active, so a JSON consumer parsing both streams doesn't see
255
- // it alongside the envelope. Non-JSON callers are unaffected.
256
266
  if (jsonActive()) return;
267
+ if (/^error:\s/i.test(str)) return;
257
268
  process.stderr.write(str);
258
269
  },
259
270
  });
@@ -432,16 +443,15 @@ export function handleCommanderError(err) {
432
443
  code: commanderCodeToErrorCode(code, message),
433
444
  });
434
445
 
435
- // Real error paths.
446
+ // Real error paths. Strip Commander's "error: " prefix once: in the envelope
447
+ // the key is already `error`, and in text mode the Astryx prefix replaces it.
448
+ const cleaned = message.replace(/^error:\s*/i, '');
436
449
  if (jsonActive()) {
437
- // Strip Commander's "error: " prefix — the envelope key is `error`
438
- // already, doubled "error" is noise.
439
- const cleaned = message.replace(/^error:\s*/i, '');
440
450
  emitJsonError(cleaned, undefined, commanderCodeToErrorCode(code, cleaned));
441
451
  } else {
442
- // Non-JSON mode: Commander already wrote the "error: ..." line
443
- // to stderr via configureOutput.writeErr before throwing the
444
- // CommanderError. Nothing to do — exit with the original code.
452
+ // Commander's own line was suppressed above, so a parse failure reads the
453
+ // same as every other CLI error — the `Error: …` line cliError prints.
454
+ process.stderr.write(`Error: ${cleaned}\n`);
445
455
  }
446
456
  process.exit(exitCode || 1);
447
457
  }