@astryxdesign/cli 0.6.4 → 0.6.5-canary.00f1ed9

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
@@ -12,6 +12,7 @@ import {cliError} from '../lib/cli-error.mjs';
12
12
  import {defineCommand} from '../lib/define-command.mjs';
13
13
  import {doc as integrationGroup} from './integration.doc.mjs';
14
14
  import {doc as integrationAddCommand} from './integration-add.doc.mjs';
15
+ import {doc as integrationVerifyCommand} from './integration-verify.doc.mjs';
15
16
  import {doc as integrationPackCommand} from './integration-pack.doc.mjs';
16
17
  import {doc as integrationAddFn} from '../../../api/integration/integrationAdd.doc.mjs';
17
18
  import {doc as integrationPackCheckFn} from '../../../api/integration/integrationPackCheck.doc.mjs';
@@ -24,8 +25,16 @@ import {doc as integrationPackCheckFn} from '../../../api/integration/integratio
24
25
  */
25
26
  function showGroupOrUnknown(command, label, options, invoked) {
26
27
  const extras = invoked?.args ?? [];
27
- if (extras.length > 0) {
28
- return cliError(`unknown subcommand '${label} ${String(extras[0])}'`, {
28
+ // allowUnknownOption keeps an unknown flag among the args: a word there is
29
+ // an unknown subcommand, and a flag alone is an unknown option.
30
+ const word = extras.find(arg => !String(arg).startsWith('-'));
31
+ if (word == null && extras.length > 0) {
32
+ return cliError(`unknown option '${String(extras[0])}'`, {
33
+ code: ERROR_CODES.ERR_INVALID_OPTION,
34
+ });
35
+ }
36
+ if (word != null) {
37
+ return cliError(`unknown subcommand '${label} ${String(word)}'`, {
29
38
  suggestions: (command.commands ?? []).map(child => ({
30
39
  name: child.name(),
31
40
  reason: 'available subcommand',
@@ -37,6 +46,48 @@ function showGroupOrUnknown(command, label, options, invoked) {
37
46
  return NO_RESULT_SET;
38
47
  }
39
48
 
49
+ /**
50
+ * Run `integration verify` and print its result, as text or JSON.
51
+ * @param {import('commander').Command} program
52
+ */
53
+ async function verifyPackage(program) {
54
+ const result = await integrationPackCheck({cwd: process.cwd()});
55
+ if (program.opts().json) {
56
+ jsonOut(result);
57
+ } else {
58
+ const {name, version, packable, tarball, inventory, issues} = result.data;
59
+ const identity = [name, version].filter(Boolean).join('@');
60
+ const blocks = [
61
+ section(
62
+ packable
63
+ ? 'Integration package ready'
64
+ : 'Integration package check failed',
65
+ ),
66
+ text(`${packable ? '[ok]' : '[fail]'} ${identity || 'local package'}`),
67
+ ];
68
+ if (tarball != null) {
69
+ blocks.push(
70
+ text(
71
+ `${tarball.fileCount} packed files; ${inventory.packedFiles}/${inventory.expectedFiles} required files present.`,
72
+ ),
73
+ );
74
+ }
75
+ if (issues.length > 0) {
76
+ blocks.push(
77
+ list(
78
+ issues.map(
79
+ issue =>
80
+ `[${issue.severity === 'error' ? 'fail' : 'warn'}] ${issue.message}`,
81
+ ),
82
+ ),
83
+ );
84
+ }
85
+ emit(...blocks);
86
+ }
87
+ if (!result.data.packable) process.exitCode = 1;
88
+ return NO_RESULT_SET;
89
+ }
90
+
40
91
  /** @param {import('commander').Command} program */
41
92
  export function registerIntegration(program) {
42
93
  /** @type {import('commander').Command} */
@@ -45,6 +96,10 @@ export function registerIntegration(program) {
45
96
  action: (options, command) =>
46
97
  showGroupOrUnknown(integration, 'integration', options, command),
47
98
  });
99
+ // An unknown subcommand given with a flag reports the unknown subcommand
100
+ // and lists the ones the group has, not the unknown option. Each subcommand
101
+ // still parses its own options.
102
+ integration.allowUnknownOption(true);
48
103
 
49
104
  defineCommand(integration, integrationAddCommand, {
50
105
  fn: integrationAddFn,
@@ -60,6 +115,7 @@ export function registerIntegration(program) {
60
115
  extends: options.extends,
61
116
  parent: options.parent,
62
117
  to: options.to,
118
+ from: options.from,
63
119
  });
64
120
  } catch (error) {
65
121
  const err =
@@ -102,53 +158,30 @@ export function registerIntegration(program) {
102
158
  },
103
159
  });
104
160
 
161
+ defineCommand(integration, integrationVerifyCommand, {
162
+ fn: integrationPackCheckFn,
163
+ action: async () => verifyPackage(program),
164
+ });
165
+ // The name this check had before `integration verify`. It stays as a
166
+ // deprecated alias, listed as one in help, so scripts that call it keep
167
+ // working; it runs the same check with the same output and exit codes.
105
168
  defineCommand(integration, integrationPackCommand, {
106
169
  fn: integrationPackCheckFn,
107
170
  action: async options => {
108
171
  if (!options.check) {
109
- return cliError('Pass --check to verify the integration tarball.', {
110
- code: ERROR_CODES.ERR_INVALID_ARGUMENT,
111
- });
172
+ // One way forward: the new name. `npm pack` is what builds a tarball.
173
+ return cliError(
174
+ '`integration pack` is now `integration verify`: run `astryx integration verify` to check the package the way npm will publish it. To build the tarball, run `npm pack`.',
175
+ {code: ERROR_CODES.ERR_INVALID_ARGUMENT},
176
+ );
112
177
  }
113
-
114
- const result = await integrationPackCheck({cwd: process.cwd()});
115
- if (program.opts().json) {
116
- jsonOut(result);
117
- } else {
118
- const {name, version, packable, tarball, inventory, issues} =
119
- result.data;
120
- const identity = [name, version].filter(Boolean).join('@');
121
- const blocks = [
122
- section(
123
- packable
124
- ? 'Integration package ready'
125
- : 'Integration package check failed',
126
- ),
127
- text(
128
- `${packable ? '[ok]' : '[fail]'} ${identity || 'local package'}`,
129
- ),
130
- ];
131
- if (tarball != null) {
132
- blocks.push(
133
- text(
134
- `${tarball.fileCount} packed files; ${inventory.packedFiles}/${inventory.expectedFiles} required files present.`,
135
- ),
136
- );
137
- }
138
- if (issues.length > 0) {
139
- blocks.push(
140
- list(
141
- issues.map(
142
- issue =>
143
- `[${issue.severity === 'error' ? 'fail' : 'warn'}] ${issue.message}`,
144
- ),
145
- ),
146
- );
147
- }
148
- emit(...blocks);
178
+ // stderr, and only in text: JSON callers get the same envelope as verify.
179
+ if (!program.opts().json) {
180
+ console.error(
181
+ 'Note: `integration pack --check` is deprecated. Run `astryx integration verify`: it runs the same check.',
182
+ );
149
183
  }
150
- if (!result.data.packable) process.exitCode = 1;
151
- return NO_RESULT_SET;
184
+ return verifyPackage(program);
152
185
  },
153
186
  });
154
187
  }
@@ -18,7 +18,9 @@ export const doc = {
18
18
  description:
19
19
  'Parses and validates a compressed XLE/XLO expression, then expands it into ' +
20
20
  'ready-to-use XDS TSX, routing children into slots, scaffolding typed useState for ' +
21
- 'interactive controls, and splicing referenced blocks. Writes to a path, or returns the code.',
21
+ 'interactive controls, and splicing referenced blocks. Writes to a path, or returns the code. ' +
22
+ 'Astryx demo media in a spliced block becomes a placeholder, and the output says how many ' +
23
+ 'references were replaced (as a closing line comment when it prints the code).',
22
24
  fn: 'layoutExpand',
23
25
  args: [
24
26
  {
@@ -0,0 +1,94 @@
1
+ // Copyright (c) Meta Platforms, Inc. and affiliates.
2
+
3
+ /**
4
+ * @file `astryx layout expand` tells the caller, in JSON and in text, when demo
5
+ * media in a spliced template block was replaced with placeholders. Printed
6
+ * code stays verbatim and valid TSX: the disclosure follows it as a comment.
7
+ */
8
+
9
+ import {describe, it, expect, beforeEach, afterEach} from 'vitest';
10
+ import * as fs from 'node:fs';
11
+ import * as path from 'node:path';
12
+ import ts from 'typescript';
13
+ import {runCli} from '../../../test-utils/run-cli.mjs';
14
+ import {discoverTemplates} from '../../../api/template/template.mjs';
15
+
16
+ const SLOW = 60_000;
17
+ const DISCLOSURE = /demo media/i;
18
+
19
+ /** @param {string} name */
20
+ const hintKey = name => name.toLowerCase().replace(/[^a-z0-9]/g, '');
21
+
22
+ /** @param {string} source */
23
+ const tsxErrors = source =>
24
+ /** @type {any} */ (
25
+ ts.createSourceFile('out.tsx', source, ts.ScriptTarget.Latest, true, ts.ScriptKind.TSX)
26
+ ).parseDiagnostics.map((/** @type {ts.Diagnostic} */ d) =>
27
+ ts.flattenDiagnosticMessageText(d.messageText, ' '),
28
+ );
29
+
30
+ /** A `{hint}` for a template block whose source carries demo media. */
31
+ async function mediaBlockHint() {
32
+ const blocks = /** @type {Array<{dirName: string, type: string, filePath: string}>} */ (
33
+ await discoverTemplates()
34
+ ).filter(t => t.type === 'block' && t.filePath && fs.existsSync(t.filePath));
35
+ const seen = new Map();
36
+ for (const t of blocks) seen.set(hintKey(t.dirName), (seen.get(hintKey(t.dirName)) ?? 0) + 1);
37
+ const found = blocks.find(
38
+ t =>
39
+ seen.get(hintKey(t.dirName)) === 1 &&
40
+ /\/template-assets\/[\w.-]+\.\w+/.test(fs.readFileSync(t.filePath, 'utf-8')),
41
+ );
42
+ if (!found) throw new Error('no template block carries demo media');
43
+ return hintKey(found.dirName);
44
+ }
45
+
46
+ describe('layout expand discloses replaced demo media', () => {
47
+ let cwd;
48
+ beforeEach(() => {
49
+ // Inside the workspace so @astryxdesign/core resolves.
50
+ cwd = fs.mkdtempSync(path.join(process.cwd(), '.xle-media-receipt-'));
51
+ });
52
+ afterEach(() => fs.rmSync(cwd, {recursive: true, force: true}));
53
+
54
+ it('--json carries demoMediaReplaced; the text receipt states it for the written file', async () => {
55
+ const expr = `V > {${await mediaBlockHint()}}`;
56
+ const json = await runCli(['layout', 'expand', expr, './a', '--json'], cwd);
57
+ expect(json.status).toBe(0);
58
+ const {data} = JSON.parse(json.stdout);
59
+ expect(data.demoMediaReplaced).toBeGreaterThan(0);
60
+
61
+ const human = await runCli(['layout', 'expand', expr, './b'], cwd);
62
+ expect(human.status).toBe(0);
63
+ expect(human.stdout).toContain(
64
+ `Replaced ${data.demoMediaReplaced} Astryx demo media reference`,
65
+ );
66
+ expect(human.stdout).toContain(path.join('b', 'GeneratedLayout.tsx'));
67
+ }, SLOW);
68
+
69
+ it('prints the code verbatim, then states the count in a comment that keeps it valid TSX', async () => {
70
+ const expr = `V > {${await mediaBlockHint()}}`;
71
+ const {data} = JSON.parse((await runCli(['layout', 'expand', expr, '--json'], cwd)).stdout);
72
+
73
+ const human = await runCli(['layout', 'expand', expr], cwd);
74
+ expect(human.status).toBe(0);
75
+ expect(human.stdout.startsWith(data.code)).toBe(true);
76
+ expect(human.stdout.slice(data.code.length).trim()).toMatch(
77
+ new RegExp(`^// Replaced ${data.demoMediaReplaced} Astryx demo media references? in the code above: [^\\n]+$`),
78
+ );
79
+ expect(tsxErrors(human.stdout)).toEqual([]);
80
+ }, SLOW);
81
+
82
+ it('says nothing about demo media when none was replaced', async () => {
83
+ const json = await runCli(['layout', 'expand', 'V > B"Save"', '--json'], cwd);
84
+ const {data} = JSON.parse(json.stdout);
85
+ expect(data.demoMediaReplaced).toBe(0);
86
+
87
+ const written = await runCli(['layout', 'expand', 'V > B"Save"', './a'], cwd);
88
+ expect(written.status).toBe(0);
89
+ expect(written.stdout).not.toMatch(DISCLOSURE);
90
+ const printed = await runCli(['layout', 'expand', 'V > B"Save"'], cwd);
91
+ expect(printed.status).toBe(0);
92
+ expect(printed.stdout).toBe(`${data.code}\n`);
93
+ }, SLOW);
94
+ });
@@ -120,6 +120,20 @@ async function readExpression(expr, options = {}) {
120
120
  return expr ?? '';
121
121
  }
122
122
 
123
+ /**
124
+ * The disclosure for demo media replaced in spliced template blocks, or '' when
125
+ * none was. After printed code it is a line comment, so piped output stays TSX.
126
+ * @param {LayoutExpandResponse['data']} data
127
+ * @returns {string}
128
+ */
129
+ function demoMediaNotice({demoMediaReplaced, written}) {
130
+ if (demoMediaReplaced === 0) return '';
131
+ const notice =
132
+ `Replaced ${demoMediaReplaced} Astryx demo media reference${demoMediaReplaced === 1 ? '' : 's'} in ${written ?? 'the code above'}: ` +
133
+ 'images now show a neutral placeholder and videos have an empty source. Supply your own media there.';
134
+ return written ? notice : `// ${notice}`;
135
+ }
136
+
123
137
  /**
124
138
  * @param {import('commander').Command} program
125
139
  */
@@ -181,6 +195,8 @@ export function registerLayout(program) {
181
195
  // Raw expanded TSX (no target path) — preformatted, emitted verbatim.
182
196
  out.push(code(result.data.code));
183
197
  }
198
+ const mediaNotice = demoMediaNotice(result.data);
199
+ if (mediaNotice) out.push(text(mediaNotice));
184
200
  emit(...out);
185
201
  return NO_RESULT_SET;
186
202
  },
@@ -20,7 +20,7 @@ export const doc = {
20
20
  'tooling to introspect. Intended to be run with --json.',
21
21
  examples: [
22
22
  {label: 'Full manifest', cli: 'astryx manifest --json'},
23
- {label: 'Shorthand', cli: 'astryx --json'},
23
+ {label: 'The same manifest, under data.manifest (type "help")', cli: 'astryx --json'},
24
24
  ],
25
25
  exitCodes: [
26
26
  {code: 0, when: 'success'},
@@ -16,9 +16,12 @@ export const doc = {
16
16
  summary: 'Search components, hooks, docs, and templates in one ranked list',
17
17
  description:
18
18
  'Terminal front-end to search(): prints one ranked, greppable list across ' +
19
- 'every content domain, each row carrying a follow-up command to act on it.',
19
+ 'every content domain, each row carrying a follow-up command to act on it. ' +
20
+ 'Outside an app, where @astryxdesign/core is not installed, it searches the docs.',
20
21
  fn: 'search',
21
- args: [{name: 'query', param: 'query', required: true}],
22
+ // Every word after `search` is the query: `astryx search dark mode` searches
23
+ // for "dark mode", with no quotes needed.
24
+ args: [{name: 'query', param: 'query', required: true, variadic: true}],
22
25
  options: [
23
26
  {
24
27
  flag: '--type <domain>',
@@ -38,6 +41,10 @@ export const doc = {
38
41
  ],
39
42
  examples: [
40
43
  {label: 'Ranked results', cli: 'astryx search button'},
44
+ {
45
+ label: 'Several words, no quotes',
46
+ cli: 'astryx search dark mode --type doc',
47
+ },
41
48
  {
42
49
  label: 'Filter + JSON',
43
50
  cli: 'astryx search "data table" --type template --json',
@@ -47,7 +54,7 @@ export const doc = {
47
54
  {code: 0, when: 'success (including zero matches)'},
48
55
  {
49
56
  code: 1,
50
- when: 'invalid --type, a --limit that is not a positive integer, or @astryxdesign/core cannot be found',
57
+ when: 'invalid --type, a --limit that is not a positive integer, or --type component, hook, or template where @astryxdesign/core cannot be found',
51
58
  },
52
59
  ],
53
60
  related: ['component', 'hook', 'docs', 'template', 'build'],
@@ -14,6 +14,7 @@
14
14
  *
15
15
  * Usage:
16
16
  * astryx search button Ranked results across all domains
17
+ * astryx search dark mode Every word is part of the query
17
18
  * astryx search modal --type component Filter to a single domain
18
19
  * astryx search forms --limit 5 Cap the result count
19
20
  * astryx search button --verbose Also print score / reason
@@ -31,6 +32,7 @@ import {cliError} from '../lib/cli-error.mjs';
31
32
  import {defineCommand} from '../lib/define-command.mjs';
32
33
  import {search as searchApi} from '../../../api/search/search.mjs';
33
34
  import {Project} from '../../../foundation/config/project.mjs';
35
+ import {findCoreDir} from '../../../foundation/fs/paths.mjs';
34
36
  import {warnOnIntegrationIssues} from '../../../foundation/integrations/integration-warnings.mjs';
35
37
  import {doc as searchCommand} from './search.doc.mjs';
36
38
  import {doc as searchFn} from '../../../api/search/search.doc.mjs';
@@ -42,10 +44,13 @@ export function registerSearch(program) {
42
44
  defineCommand(program, searchCommand, {
43
45
  fn: searchFn,
44
46
  action: async (
45
- /** @type {string} */ query,
47
+ /** @type {string[] | string} */ words,
46
48
  /** @type {{type?: import('../../../api/search/search.type.mjs').SearchDomain, limit?: string, verbose?: boolean}} */ options,
47
49
  ) => {
48
50
  const json = program.opts().json || false;
51
+ // The query is variadic: `astryx search dark mode` is one query, "dark
52
+ // mode". Taking only the first word dropped the rest without a word.
53
+ const query = Array.isArray(words) ? words.join(' ') : words;
49
54
 
50
55
  try {
51
56
  const project = await Project.load(process.cwd());
@@ -99,12 +104,25 @@ export function registerSearch(program) {
99
104
  // ── Text output ──────────────────────────────────────────────
100
105
  const run = getCliInvocation();
101
106
  const {query: q, matchCount, results} = result.data;
107
+ // Outside an app an open search covers the docs alone. Say so, so a
108
+ // docs-only list does not read as "Astryx has no such component".
109
+ const note =
110
+ !options.type && !findCoreDir(process.cwd())
111
+ ? [
112
+ text(
113
+ '@astryxdesign/core is not installed here, so only the docs were searched.',
114
+ ),
115
+ ]
116
+ : [];
102
117
 
103
118
  // No matches is a valid, successful outcome — clean message, exit 0.
104
119
  if (results.length === 0) {
105
120
  emit(
106
121
  text(`No results for "${q}".`),
107
- text(`Try a broader term, or browse: ${run} component --list`),
122
+ ...note,
123
+ text(
124
+ `Try a broader term, or browse: ${run} ${note.length > 0 ? 'docs' : 'component --list'}`,
125
+ ),
108
126
  );
109
127
  return answered;
110
128
  }
@@ -155,6 +173,7 @@ export function registerSearch(program) {
155
173
  fields,
156
174
  format: {command: formatCliCommand, parent: formatCliCommand},
157
175
  }),
176
+ ...note,
158
177
  );
159
178
  return answered;
160
179
  },
@@ -191,6 +191,18 @@ describe('search CLI — exit codes + JSON contract', () => {
191
191
  expect(r.stdout).toContain('No results');
192
192
  });
193
193
 
194
+ it('takes every word after `search` as one query', async () => {
195
+ // Commander took only the first word, so `search dark mode` searched for
196
+ // "dark" and dropped "mode" without a word.
197
+ const json = await runCli(['--json', 'search', 'dark', 'mode', '--type', 'doc'], REPO_ROOT);
198
+ expect(json.status).toBe(0);
199
+ const env = JSON.parse(json.stdout);
200
+ expect(env.data.query).toBe('dark mode');
201
+ expect(env.data.results[0]).toMatchObject({name: 'theme', section: 'light-dark-mode'});
202
+ const text = await runCli(['search', 'dark', 'mode', '--type', 'doc'], REPO_ROOT);
203
+ expect(text.stdout).toContain('Results for "dark mode"');
204
+ }, SCAN_TIMEOUT);
205
+
194
206
  it('exits 1 for an invalid --type', async () => {
195
207
  const r = await runCli(['search', 'x', '--type', 'bogus'], REPO_ROOT);
196
208
  expect(r.status).toBe(1);
@@ -299,14 +311,19 @@ describe('search CLI — exit codes + JSON contract', () => {
299
311
  expect(r.stdout).toContain('reason:');
300
312
  });
301
313
 
302
- it('exits 1 with ERR_CORE_NOT_FOUND when no @astryxdesign/core is reachable', async () => {
314
+ it('searches the docs when no @astryxdesign/core is reachable, and exits 1 for --type component', async () => {
303
315
  const empty = fs.mkdtempSync(path.join(os.tmpdir(), 'astryx-search-cli-no-core-'));
304
316
  try {
305
- const json = await runCli(['--json', 'search', 'button'], empty);
317
+ const open = await runCli(['--json', 'search', 'make', 'an', 'integration'], empty);
318
+ expect(open.status).toBe(0);
319
+ expect(JSON.parse(open.stdout).data.results[0]).toMatchObject({domain: 'doc'});
320
+ // The text says the search covered the docs alone.
321
+ const text = await runCli(['search', 'button'], empty);
322
+ expect(text.status).toBe(0);
323
+ expect(text.stdout).toContain('only the docs were searched');
324
+ const json = await runCli(['--json', 'search', 'button', '--type', 'component'], empty);
306
325
  expect(json.status).toBe(1);
307
326
  expect(JSON.parse(json.stdout)).toMatchObject({code: 'ERR_CORE_NOT_FOUND'});
308
- const text = await runCli(['search', 'button'], empty);
309
- expect(text.status).toBe(1);
310
327
  } finally {
311
328
  fs.rmSync(empty, {recursive: true, force: true});
312
329
  }
@@ -46,7 +46,7 @@ export const doc = {
46
46
  ],
47
47
  examples: [
48
48
  {label: 'List swizzlable components', cli: 'astryx swizzle --list'},
49
- {label: 'Eject a component', cli: 'astryx swizzle XDSButton'},
49
+ {label: 'Eject a component', cli: 'astryx swizzle Button'},
50
50
  ],
51
51
  exitCodes: [
52
52
  {code: 0, when: 'success'},
@@ -0,0 +1,60 @@
1
+ // Copyright (c) Meta Platforms, Inc. and affiliates.
2
+
3
+ /**
4
+ * @file `astryx template <name> <path>` tells the caller, in JSON and in text,
5
+ * when demo media in the copied file was replaced with placeholders.
6
+ */
7
+
8
+ import {describe, it, expect, beforeEach, afterEach} from 'vitest';
9
+ import * as fs from 'node:fs';
10
+ import * as os from 'node:os';
11
+ import * as path from 'node:path';
12
+ import {runCli} from '../../../test-utils/run-cli.mjs';
13
+ import {discoverTemplates} from '../../../api/template/template.mjs';
14
+
15
+ const SLOW = 60_000;
16
+
17
+ /** A page template whose source carries demo media, and one that carries none. */
18
+ async function pickPages() {
19
+ const pages = /** @type {Array<{dirName: string, type: string, filePath: string}>} */ (
20
+ await discoverTemplates()
21
+ ).filter(t => t.type === 'page' && fs.existsSync(t.filePath));
22
+ const hasMedia = (/** @type {{filePath: string}} */ t) =>
23
+ /\/template-assets\/[\w.-]+\.\w+/.test(fs.readFileSync(t.filePath, 'utf-8'));
24
+ const withMedia = pages.find(hasMedia);
25
+ const without = pages.find(t => !hasMedia(t));
26
+ if (!withMedia || !without) throw new Error('catalog lacks a page with and without demo media');
27
+ return {withMedia: withMedia.dirName, without: without.dirName};
28
+ }
29
+
30
+ describe('template copy receipt discloses replaced demo media', () => {
31
+ let dir;
32
+ beforeEach(() => {
33
+ dir = fs.mkdtempSync(path.join(os.tmpdir(), 'tmpl-receipt-cli-'));
34
+ });
35
+ afterEach(() => fs.rmSync(dir, {recursive: true, force: true}));
36
+
37
+ it('--json carries demoMediaReplaced and the text states the same count', async () => {
38
+ const {withMedia} = await pickPages();
39
+ const json = await runCli(['template', withMedia, './a', '--json'], dir);
40
+ expect(json.status).toBe(0);
41
+ const {data} = JSON.parse(json.stdout);
42
+ expect(data.demoMediaReplaced).toBeGreaterThan(0);
43
+
44
+ const human = await runCli(['template', withMedia, './b'], dir);
45
+ expect(human.status).toBe(0);
46
+ expect(human.stdout).toContain(
47
+ `Replaced ${data.demoMediaReplaced} Astryx demo media reference`,
48
+ );
49
+ expect(human.stdout).toContain('b/page.tsx');
50
+ }, SLOW);
51
+
52
+ it('says nothing about media when none was replaced', async () => {
53
+ const {without} = await pickPages();
54
+ const json = await runCli(['template', without, './a', '--json'], dir);
55
+ expect(JSON.parse(json.stdout).data.demoMediaReplaced).toBe(0);
56
+ const human = await runCli(['template', without, './b'], dir);
57
+ expect(human.status).toBe(0);
58
+ expect(human.stdout).not.toMatch(/demo media/i);
59
+ }, SLOW);
60
+ });
@@ -14,7 +14,7 @@ export const doc = {
14
14
  name: 'template',
15
15
  displayName: 'astryx template',
16
16
  namespace: 'cli/commands',
17
- summary: 'Inject a page or block template',
17
+ summary: 'List, show, or scaffold page and block templates',
18
18
  description:
19
19
  'One entry point for the template family: with no name it lists the discovered ' +
20
20
  'templates; with a name it shows the source or a layout skeleton, or scaffolds it ' +
@@ -156,16 +156,30 @@ export function registerTemplate(program) {
156
156
  }
157
157
 
158
158
  case 'template.show': {
159
- // Source must survive piping byte-for-byte.
160
- emit(code(result.data.source));
159
+ // Source must survive piping byte-for-byte, so the note about
160
+ // replaced demo media goes to stderr, in the copy receipt's words.
161
+ // (Text mode only: --json returned above with the count in data.)
162
+ const {source, demoMediaReplaced} = result.data;
163
+ emit(code(source));
164
+ if (demoMediaReplaced > 0) {
165
+ console.error(
166
+ `Replaced ${demoMediaReplaced} Astryx demo media reference${demoMediaReplaced === 1 ? '' : 's'}: ` +
167
+ 'images now show a neutral placeholder and videos have an empty source. Supply your own media there.',
168
+ );
169
+ }
161
170
  break;
162
171
  }
163
172
 
164
173
  case 'template.copy': {
174
+ const {outputDir, fileName, demoMediaReplaced} = result.data;
175
+ const file = `${outputDir}/${fileName}`;
165
176
  emit(
166
- text(
167
- `Copied template to ${result.data.outputDir}/${result.data.fileName}`,
168
- ),
177
+ text(`Copied template to ${file}`),
178
+ demoMediaReplaced > 0 &&
179
+ text(
180
+ `Replaced ${demoMediaReplaced} Astryx demo media reference${demoMediaReplaced === 1 ? '' : 's'} in ${file}: ` +
181
+ 'images now show a neutral placeholder and videos have an empty source. Supply your own media there.',
182
+ ),
169
183
  );
170
184
  break;
171
185
  }
@@ -0,0 +1,62 @@
1
+ // Copyright (c) Meta Platforms, Inc. and affiliates.
2
+
3
+ /**
4
+ * @file `astryx template <name>` says how many demo media references it
5
+ * replaced: in `--json` as `demoMediaReplaced`, and in text on stderr, so that
6
+ * stdout stays exactly the source a copy writes and can be piped into a file.
7
+ */
8
+
9
+ import {describe, it, expect, beforeAll, afterAll} from 'vitest';
10
+ import * as fs from 'node:fs';
11
+ import * as os from 'node:os';
12
+ import * as path from 'node:path';
13
+ import {runCli} from '../../../test-utils/run-cli.mjs';
14
+ import {discoverTemplates} from '../../../api/template/template.mjs';
15
+
16
+ const SLOW = 60_000;
17
+
18
+ /** A page template whose source carries demo media, and one that carries none. */
19
+ async function pickPages() {
20
+ const pages = /** @type {Array<{dirName: string, type: string, filePath: string}>} */ (
21
+ await discoverTemplates()
22
+ ).filter(t => t.type === 'page' && fs.existsSync(t.filePath));
23
+ const hasMedia = (/** @type {{filePath: string}} */ t) =>
24
+ /\/template-assets\/[\w.-]+\.\w+/.test(fs.readFileSync(t.filePath, 'utf-8'));
25
+ const withMedia = pages.find(hasMedia);
26
+ const without = pages.find(t => !hasMedia(t));
27
+ if (!withMedia || !without) throw new Error('catalog lacks a page with and without demo media');
28
+ return {withMedia: withMedia.dirName, without: without.dirName};
29
+ }
30
+
31
+ describe('template show discloses replaced demo media', () => {
32
+ /** @type {string} */
33
+ let dir;
34
+ beforeAll(() => {
35
+ dir = fs.mkdtempSync(path.join(os.tmpdir(), 'tmpl-show-cli-'));
36
+ });
37
+ afterAll(() => fs.rmSync(dir, {recursive: true, force: true}));
38
+
39
+ it('--json carries demoMediaReplaced, and text states it on stderr with stdout left as the source', async () => {
40
+ const {withMedia} = await pickPages();
41
+ const json = await runCli(['template', withMedia, '--type', 'page', '--json'], dir);
42
+ expect(json.status).toBe(0);
43
+ const {data} = JSON.parse(json.stdout);
44
+ expect(data.demoMediaReplaced).toBeGreaterThan(0);
45
+
46
+ const human = await runCli(['template', withMedia, '--type', 'page'], dir);
47
+ expect(human.status).toBe(0);
48
+ expect(human.stdout.trimEnd()).toBe(data.source.trimEnd());
49
+ expect(human.stderr).toContain(
50
+ `Replaced ${data.demoMediaReplaced} Astryx demo media reference`,
51
+ );
52
+ }, SLOW);
53
+
54
+ it('says nothing about media when none was replaced', async () => {
55
+ const {without} = await pickPages();
56
+ const json = await runCli(['template', without, '--type', 'page', '--json'], dir);
57
+ expect(JSON.parse(json.stdout).data.demoMediaReplaced).toBe(0);
58
+ const human = await runCli(['template', without, '--type', 'page'], dir);
59
+ expect(human.status).toBe(0);
60
+ expect(human.stderr).not.toMatch(/demo media/i);
61
+ }, SLOW);
62
+ });
@@ -372,9 +372,15 @@ const CASES = [
372
372
  skipFieldChecks: true,
373
373
  },
374
374
  {
375
+ name: 'integration verify',
376
+ args: ['integration', 'verify'],
377
+ // Needs a packable project; pack itself may exit differently.
378
+ skipFieldChecks: true,
379
+ },
380
+ {
381
+ // The deprecated alias of `integration verify`.
375
382
  name: 'integration pack',
376
383
  args: ['integration', 'pack', '--check'],
377
- // Needs a packable project; pack itself may exit differently.
378
384
  skipFieldChecks: true,
379
385
  },
380
386
  // ── Layout command: remove these cases when the layout command is deleted ──