@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
@@ -36,6 +36,8 @@ function writePackage({
36
36
  scripts,
37
37
  } = {}) {
38
38
  const pkg = {name, version};
39
+ // A theme needs a CLI that reads typed theme descriptors.
40
+ if (themes) pkg.peerDependencies = {'@astryxdesign/cli': '>=0.6.4'};
39
41
  if (files !== undefined) pkg.files = files;
40
42
  if (scripts !== undefined) pkg.scripts = scripts;
41
43
  fs.writeFileSync(
@@ -94,6 +96,38 @@ describe('integrationPackCheck', () => {
94
96
  );
95
97
  });
96
98
 
99
+ it('fails a package that ships a theme on a CLI range that cannot read it', async () => {
100
+ writePackage({files: ['astryx.integration.mjs', 'themes']});
101
+ const pkgFile = path.join(tmpDir, 'package.json');
102
+ const pkg = JSON.parse(fs.readFileSync(pkgFile, 'utf-8'));
103
+ delete pkg.peerDependencies;
104
+ fs.writeFileSync(pkgFile, `${JSON.stringify(pkg, null, 2)}\n`);
105
+
106
+ const missing = await integrationPackCheck({cwd: tmpDir});
107
+ expect(missing.data.packable).toBe(false);
108
+ expect(missing.data.issues).toContainEqual(
109
+ expect.objectContaining({
110
+ code: 'themes_need_cli',
111
+ message: expect.stringContaining('ships a theme'),
112
+ }),
113
+ );
114
+
115
+ pkg.peerDependencies = {'@astryxdesign/cli': '^0.6.3'};
116
+ fs.writeFileSync(pkgFile, `${JSON.stringify(pkg, null, 2)}\n`);
117
+ const old = await integrationPackCheck({cwd: tmpDir});
118
+ expect(old.data.issues).toContainEqual(
119
+ expect.objectContaining({code: 'themes_need_cli'}),
120
+ );
121
+
122
+ // Published 0.6.4 reads typed theme descriptors.
123
+ pkg.peerDependencies = {'@astryxdesign/cli': '>=0.6.4'};
124
+ fs.writeFileSync(pkgFile, `${JSON.stringify(pkg, null, 2)}\n`);
125
+ const current = await integrationPackCheck({cwd: tmpDir});
126
+ expect(current.data.issues).not.toContainEqual(
127
+ expect.objectContaining({code: 'themes_need_cli'}),
128
+ );
129
+ });
130
+
97
131
  it('fails when a theme entry omits its inferred runtime export', async () => {
98
132
  writePackage({files: ['astryx.integration.mjs', 'themes']});
99
133
  fs.writeFileSync(
@@ -193,6 +227,39 @@ describe('integrationPackCheck', () => {
193
227
  );
194
228
  });
195
229
 
230
+ it('resolves public imports in the packed package, not the source', async () => {
231
+ // The root export's target is left out of `files`: the source package
232
+ // resolves it, but an app that installs the tarball cannot.
233
+ fs.writeFileSync(
234
+ path.join(tmpDir, 'package.json'),
235
+ `${JSON.stringify({
236
+ name: '@acme/widgets',
237
+ version: '1.0.0',
238
+ files: ['astryx.integration.mjs'],
239
+ exports: {'.': './index.mjs'},
240
+ })}\n`,
241
+ );
242
+ fs.writeFileSync(
243
+ path.join(tmpDir, 'index.mjs'),
244
+ "export {AcmeWidget} from './components/AcmeWidget.tsx';\n",
245
+ );
246
+ await integrationAddComponent('AcmeWidget', {cwd: tmpDir});
247
+ const docFile = path.join(tmpDir, 'components', 'AcmeWidget.doc.mjs');
248
+ const doc = fs.readFileSync(docFile, 'utf-8');
249
+ expect(doc).toContain('@acme/widgets/components/AcmeWidget');
250
+ fs.writeFileSync(
251
+ docFile,
252
+ doc.replace('@acme/widgets/components/AcmeWidget', '@acme/widgets'),
253
+ );
254
+
255
+ const result = await integrationPackCheck({cwd: tmpDir});
256
+
257
+ expect(result.data.packable).toBe(false);
258
+ expect(result.data.issues).toContainEqual(
259
+ expect.objectContaining({code: 'component_export_missing'}),
260
+ );
261
+ });
262
+
196
263
  it('fails when packed template source is hidden by package exports', async () => {
197
264
  writePackage({
198
265
  manifest: "export default {templates: './templates'};\n",
@@ -307,7 +374,10 @@ describe('integrationPackCheck', () => {
307
374
  expect(await codes()).toContain('docs_tree_needs_cli');
308
375
  peer('^0.6.0 || >=0.7.0');
309
376
  expect(await codes()).toContain('docs_tree_needs_cli');
310
- peer('>=0.7.0');
377
+ peer('>=0.6.3');
378
+ expect(await codes()).toContain('docs_tree_needs_cli');
379
+ // Published 0.6.4 reads the docs tree.
380
+ peer('>=0.6.4');
311
381
  expect(await codes()).not.toContain('docs_tree_needs_cli');
312
382
  }, 120_000);
313
383
 
@@ -336,6 +406,41 @@ describe('integrationPackCheck', () => {
336
406
  expect(await codes()).not.toContain('docs_tree_needs_cli');
337
407
  }, 120_000);
338
408
 
409
+ it('fails a package whose doc section sets id on a CLI range that rejects the field', async () => {
410
+ writePackage({manifest: "export default {docs: './docs'};\n", themes: false});
411
+ fs.mkdirSync(path.join(tmpDir, 'docs'), {recursive: true});
412
+ const topic = (/** @type {string} */ section) =>
413
+ `export default {type: 'generic', name: 'notes', title: 'Notes', description: 'Notes.', sections: [${section}]};\n`;
414
+ const file = path.join(tmpDir, 'docs', 'notes.doc.mjs');
415
+ const codes = async () =>
416
+ (await integrationPackCheck({cwd: tmpDir})).data.issues.map(
417
+ (/** @type {{code: string}} */ issue) => issue.code,
418
+ );
419
+ fs.writeFileSync(
420
+ file,
421
+ topic("{title: 'Take notes', content: [{type: 'prose', text: 'Notes.'}]}"),
422
+ );
423
+ expect(await codes()).not.toContain('section_ids_need_cli');
424
+ // A fresh file name: the module loader caches a path once it is imported.
425
+ fs.rmSync(file);
426
+ fs.writeFileSync(
427
+ path.join(tmpDir, 'docs', 'notes-with-ids.doc.mjs'),
428
+ topic(
429
+ "{id: 'take-notes', title: 'Take notes', content: [{type: 'prose', text: 'Notes.'}]}",
430
+ ),
431
+ );
432
+ expect(await codes()).toContain('section_ids_need_cli');
433
+ const pkgFile = path.join(tmpDir, 'package.json');
434
+ const pkg = JSON.parse(fs.readFileSync(pkgFile, 'utf-8'));
435
+ pkg.peerDependencies = {'@astryxdesign/cli': '^0.6.3'};
436
+ fs.writeFileSync(pkgFile, `${JSON.stringify(pkg, null, 2)}\n`);
437
+ expect(await codes()).toContain('section_ids_need_cli');
438
+ // Published 0.6.4 reads a section id.
439
+ pkg.peerDependencies = {'@astryxdesign/cli': '>=0.6.4'};
440
+ fs.writeFileSync(pkgFile, `${JSON.stringify(pkg, null, 2)}\n`);
441
+ expect(await codes()).not.toContain('section_ids_need_cli');
442
+ }, 120_000);
443
+
339
444
  it('fails a package with a template that sets replaces on a CLI range that rejects the field', async () => {
340
445
  writePackage({manifest: "export default {templates: './templates'};\n", themes: false});
341
446
  fs.mkdirSync(path.join(tmpDir, 'templates'), {recursive: true});
@@ -363,10 +468,44 @@ describe('integrationPackCheck', () => {
363
468
  expect(await codes()).toContain('replaces_needs_cli');
364
469
  peer('^0.6.0');
365
470
  expect(await codes()).toContain('replaces_needs_cli');
471
+ // `replaces` still needs 0.7.0.
472
+ peer('>=0.6.4');
473
+ expect(await codes()).toContain('replaces_needs_cli');
366
474
  peer('>=0.7.0');
367
475
  expect(await codes()).not.toContain('replaces_needs_cli');
368
476
  }, 120_000);
369
477
 
478
+ it('fails a package with a template that sets keywords on a CLI range that rejects the field', async () => {
479
+ writePackage({manifest: "export default {templates: './templates'};\n", themes: false});
480
+ fs.mkdirSync(path.join(tmpDir, 'templates'), {recursive: true});
481
+ fs.writeFileSync(
482
+ path.join(tmpDir, 'templates', 'acme-health.doc.mjs'),
483
+ "export default {type: 'page', name: 'acme-health', description: 'Status tiles.', keywords: ['uptime']};\n",
484
+ );
485
+ fs.writeFileSync(
486
+ path.join(tmpDir, 'templates', 'acme-health.tsx'),
487
+ 'export default function AcmeHealth() { return null; }\n',
488
+ );
489
+ const file = path.join(tmpDir, 'package.json');
490
+ const peer = (/** @type {string | undefined} */ range) => {
491
+ const pkg = JSON.parse(fs.readFileSync(file, 'utf-8'));
492
+ if (range == null) delete pkg.peerDependencies;
493
+ else pkg.peerDependencies = {'@astryxdesign/cli': range};
494
+ fs.writeFileSync(file, `${JSON.stringify(pkg, null, 2)}\n`);
495
+ };
496
+ const codes = async () =>
497
+ (await integrationPackCheck({cwd: tmpDir})).data.issues.map(
498
+ (/** @type {{code: string}} */ issue) => issue.code,
499
+ );
500
+
501
+ peer(undefined);
502
+ expect(await codes()).toContain('keywords_needs_cli');
503
+ peer('^0.6.0');
504
+ expect(await codes()).toContain('keywords_needs_cli');
505
+ peer('>=0.7.0');
506
+ expect(await codes()).not.toContain('keywords_needs_cli');
507
+ }, 120_000);
508
+
370
509
  it('passes when package.json has no files field', async () => {
371
510
  writePackage({files: undefined});
372
511
  const result = await integrationPackCheck({cwd: tmpDir});
@@ -1,7 +1,7 @@
1
1
  // Copyright (c) Meta Platforms, Inc. and affiliates.
2
2
 
3
3
  /**
4
- * @file Colocated types for `astryx integration pack --check`.
4
+ * @file Colocated types for `astryx integration verify`.
5
5
  */
6
6
 
7
7
  /**
@@ -19,8 +19,10 @@ export function validateInstalledIntegration(spec: string, cwd?: string): Promis
19
19
  /**
20
20
  * Unified entry: validate the LOCAL integration (no `pkg`) or an INSTALLED one
21
21
  * (`pkg` given) and return the `integration.validate` envelope. The no-manifest
22
- * local case is guidance, not an error — it comes back with `name: null` and no
23
- * issues so the CLI can print a hint and stay exit-0.
22
+ * local case is guidance, not an error — it comes back with `validated: false`,
23
+ * `name: null` and no issues so the CLI can print a hint and stay exit-0.
24
+ * `validated` is what tells a machine consumer that empty `issues` means
25
+ * "nothing was checked" rather than "checked and healthy".
24
26
  *
25
27
  * This is the seam that keeps the CLI a thin wrapper: the command handler calls
26
28
  * this and only chooses how to render (human vs --json) + the exit code.
@@ -427,8 +427,10 @@ export async function validateInstalledIntegration(spec, cwd = process.cwd()) {
427
427
  /**
428
428
  * Unified entry: validate the LOCAL integration (no `pkg`) or an INSTALLED one
429
429
  * (`pkg` given) and return the `integration.validate` envelope. The no-manifest
430
- * local case is guidance, not an error — it comes back with `name: null` and no
431
- * issues so the CLI can print a hint and stay exit-0.
430
+ * local case is guidance, not an error — it comes back with `validated: false`,
431
+ * `name: null` and no issues so the CLI can print a hint and stay exit-0.
432
+ * `validated` is what tells a machine consumer that empty `issues` means
433
+ * "nothing was checked" rather than "checked and healthy".
432
434
  *
433
435
  * This is the seam that keeps the CLI a thin wrapper: the command handler calls
434
436
  * this and only chooses how to render (human vs --json) + the exit code.
@@ -445,6 +447,9 @@ export async function validateIntegration(pkg, options = {}) {
445
447
  return {
446
448
  type: 'integration.validate',
447
449
  data: {
450
+ // The one bit that separates "checked and clean" from "never checked":
451
+ // with no manifest there is nothing to validate, and issues stays [].
452
+ validated: result.found,
448
453
  name: result.found ? (result.name ?? null) : null,
449
454
  version: result.found ? (result.version ?? null) : null,
450
455
  issues: result.issues,
@@ -625,3 +625,58 @@ describe('integration diagnostics are read-only', () => {
625
625
  expect({pkg: snapshot(pkgDir), consumer: snapshot(consumer)}).toEqual(before);
626
626
  });
627
627
  });
628
+
629
+ describe('validated separates "checked and clean" from "never checked"', () => {
630
+ // `doctor integration validate --json` from a directory with no manifest
631
+ // returned {name: null, version: null, issues: []} and exit 0 — the same
632
+ // envelope a healthy, fully validated integration produces. Wire that into
633
+ // CI from the wrong directory and it is green forever.
634
+ it('is false for every check when no manifest is found', async () => {
635
+ const dir = path.join(tmpDir, 'plain');
636
+ fs.mkdirSync(dir, {recursive: true});
637
+ fs.writeFileSync(
638
+ path.join(dir, 'package.json'),
639
+ JSON.stringify({name: 'plain-app', version: '1.0.0'}),
640
+ );
641
+
642
+ const validate = await validateIntegration(undefined, {cwd: dir});
643
+ expect(validate.data.validated).toBe(false);
644
+ expect(validate.data.issues).toEqual([]);
645
+
646
+ const templates = await integrationTemplateConflicts(undefined, {cwd: dir});
647
+ const components = await integrationComponentConflicts(undefined, {cwd: dir});
648
+ const docs = await integrationDocConflicts(undefined, {cwd: dir});
649
+ expect(templates.data.validated).toBe(false);
650
+ expect(components.data.validated).toBe(false);
651
+ expect(docs.data.validated).toBe(false);
652
+ });
653
+
654
+ it('is true for a real integration, whose empty issue list then means healthy', async () => {
655
+ const dir = path.join(tmpDir, 'integration');
656
+ writePackage(dir, {manifest: 'export default {};\n'});
657
+
658
+ const validate = await validateIntegration(undefined, {cwd: dir});
659
+ expect(validate.data.validated).toBe(true);
660
+ expect(validate.data.name).toBe('@acme/widgets');
661
+
662
+ const templates = await integrationTemplateConflicts(undefined, {cwd: dir});
663
+ const components = await integrationComponentConflicts(undefined, {cwd: dir});
664
+ const docs = await integrationDocConflicts(undefined, {cwd: dir});
665
+ expect(templates.data.validated).toBe(true);
666
+ expect(components.data.validated).toBe(true);
667
+ expect(docs.data.validated).toBe(true);
668
+ });
669
+
670
+ it('is true for an installed package that could not be found — that is a real finding', async () => {
671
+ const consumer = path.join(tmpDir, 'consumer');
672
+ fs.mkdirSync(consumer, {recursive: true});
673
+ fs.writeFileSync(
674
+ path.join(consumer, 'package.json'),
675
+ JSON.stringify({name: 'consumer', version: '1.0.0'}),
676
+ );
677
+
678
+ const res = await validateIntegration('@acme/nope', {cwd: consumer});
679
+ expect(res.data.validated).toBe(true);
680
+ expect(summarizeIssues(res.data.issues).errors).toBeGreaterThan(0);
681
+ });
682
+ });
@@ -27,10 +27,15 @@ export type ValidateIntegrationOptions = {
27
27
  };
28
28
  /**
29
29
  * `astryx --json doctor integration validate [package]`.
30
+ *
31
+ * `validated` is false in exactly one case: no integration manifest was found,
32
+ * so nothing was checked. An empty `issues` then means "not looked at", not
33
+ * "healthy" — the two are otherwise byte-identical.
30
34
  */
31
35
  export type ValidateIntegrationResponse = {
32
36
  type: "integration.validate";
33
37
  data: {
38
+ validated: boolean;
34
39
  name: string | null;
35
40
  version: string | null;
36
41
  issues: import("../../foundation/integrations/issue").AstryxIntegrationIssue[];
@@ -32,9 +32,13 @@
32
32
 
33
33
  /**
34
34
  * `astryx --json doctor integration validate [package]`.
35
+ *
36
+ * `validated` is false in exactly one case: no integration manifest was found,
37
+ * so nothing was checked. An empty `issues` then means "not looked at", not
38
+ * "healthy" — the two are otherwise byte-identical.
35
39
  * @typedef {object} ValidateIntegrationResponse
36
40
  * @property {'integration.validate'} type
37
- * @property {{name: string | null, version: string | null, issues: import('../../foundation/integrations/issue').AstryxIntegrationIssue[]}} data
41
+ * @property {{validated: boolean, name: string | null, version: string | null, issues: import('../../foundation/integrations/issue').AstryxIntegrationIssue[]}} data
38
42
  */
39
43
 
40
44
  export {};
@@ -46,7 +46,7 @@ export const doc = {
46
46
  {
47
47
  type: 'integration.validate',
48
48
  description:
49
- 'The result envelope: `data.name` and `data.version` of the validated package (both null only when no local manifest is found; `data.name` is `(local package)` when the local package.json has no readable name), plus `data.issues`, an AstryxIntegrationIssue[] of {code, severity: `warning` | `error`, message}.',
49
+ 'The result envelope: `data.validated`, false only when no integration manifest was found — nothing was checked, so the empty `data.issues` means "not looked at", not "healthy"; `data.name` and `data.version` of the validated package (both null when `data.validated` is false; `data.name` is `(local package)` when the local package.json has no readable name), plus `data.issues`, an AstryxIntegrationIssue[] of {code, severity: `warning` | `error`, message}.',
50
50
  },
51
51
  ],
52
52
  examples: [
@@ -49,7 +49,7 @@ export const doc = {
49
49
  throws: [
50
50
  {
51
51
  code: 'Error',
52
- when: 'the CLI returned an error envelope (the CLI message is rethrown), or the response `type` is not expectedType',
52
+ when: 'a plain Error with no code: the CLI returned an error envelope (its message is rethrown; code and suggestions are dropped), or the response `type` is not expectedType',
53
53
  },
54
54
  ],
55
55
  examples: [
@@ -15,7 +15,7 @@ export const doc = {
15
15
  displayName: 'isError()',
16
16
  summary: 'Did the CLI return an error envelope?',
17
17
  description:
18
- 'Tests a parsed response for an `error` key. Branch on this before touching `data`: ' +
18
+ 'Tests a parsed response for an `error` key. Branch on this before touching `data`, ' +
19
19
  'and prefer the stable `code` field over matching the human-readable message, which is ' +
20
20
  'not a contract. Note this returns a plain boolean, not a TypeScript type predicate, so ' +
21
21
  'it does not narrow on its own: cast to the matching *Response type to get typed access.',
@@ -18,7 +18,7 @@ import {ERROR_CODES} from '../../../foundation/response/error-codes.mjs';
18
18
  import {assertWithin, isFilePathArg, PathSafetyError} from '../../../foundation/fs/path-safety.mjs';
19
19
  import {detectForm} from '../../../foundation/xle/parse.mjs';
20
20
  import {expand} from '../../../foundation/xle/expand.mjs';
21
- import {stripTemplateAssetRefs} from '../../template/template.mjs';
21
+ import {replaceDemoMedia} from '../../template/template.mjs';
22
22
  import {analyze, formatIssue} from '../_adapter.mjs';
23
23
 
24
24
  /** @param {string} name */
@@ -59,28 +59,32 @@ function collectHintNames(doc) {
59
59
  /**
60
60
  * Build the blockModules map expand() needs: import-mode for app components,
61
61
  * splice-mode (reading + asset-stripping the block source) for template blocks.
62
- * Only blocks actually referenced are read.
62
+ * Only blocks actually referenced are read. Also returns how many demo media
63
+ * references the splice-mode sources had replaced.
63
64
  *
64
65
  * @param {import('../../../foundation/xle/xle-ast').XLEDoc} doc
65
66
  * @param {import('../_adapter.mjs').LayoutBlock[]} blocks
66
- * @returns {Map<string, import('../../../foundation/xle/xle-ast').BlockModule>}
67
+ * @returns {{modules: Map<string, import('../../../foundation/xle/xle-ast').BlockModule>, demoMediaReplaced: number}}
67
68
  */
68
69
  function buildBlockModules(doc, blocks) {
69
70
  const referenced = collectHintNames(doc);
70
- if (referenced.size === 0) return new Map();
71
+ if (referenced.size === 0) return {modules: new Map(), demoMediaReplaced: 0};
71
72
  const byKey = new Map(blocks.map(b => [normKey(b.dirName), b]));
72
73
  /** @type {Map<string, import('../../../foundation/xle/xle-ast').BlockModule>} */
73
74
  const modules = new Map();
75
+ let demoMediaReplaced = 0;
74
76
  for (const name of referenced) {
75
77
  const block = byKey.get(normKey(name));
76
78
  if (!block) continue;
77
79
  if (block.kind === 'component') {
78
80
  modules.set(name, /** @type {import('../../../foundation/xle/xle-ast').BlockModule} */ (/** @type {unknown} */ ({mode: 'import', componentName: block.name, importPath: block.importPath, isDefault: block.isDefault})));
79
81
  } else if (block.filePath && fs.existsSync(block.filePath)) {
80
- modules.set(name, /** @type {import('../../../foundation/xle/xle-ast').BlockModule} */ ({mode: 'splice', componentName: block.dirName, source: stripTemplateAssetRefs(fs.readFileSync(block.filePath, 'utf-8'))}));
82
+ const spliced = replaceDemoMedia(fs.readFileSync(block.filePath, 'utf-8'));
83
+ demoMediaReplaced += spliced.demoMediaReplaced;
84
+ modules.set(name, /** @type {import('../../../foundation/xle/xle-ast').BlockModule} */ ({mode: 'splice', componentName: block.dirName, source: spliced.source}));
81
85
  }
82
86
  }
83
- return modules;
87
+ return {modules, demoMediaReplaced};
84
88
  }
85
89
 
86
90
  /**
@@ -115,7 +119,7 @@ export async function layoutExpand(expression, options = {}) {
115
119
  ERROR_CODES.ERR_INVALID_ARGUMENT,
116
120
  );
117
121
  }
118
- const blockModules = buildBlockModules(doc, blocks);
122
+ const {modules: blockModules, demoMediaReplaced} = buildBlockModules(doc, blocks);
119
123
  const result = expand(doc, registry, {componentName, blockModules});
120
124
 
121
125
  let written = null;
@@ -150,6 +154,7 @@ export async function layoutExpand(expression, options = {}) {
150
154
  blocksReferenced: [...blockModules.entries()].map(([name, m]) => ({name, mode: m.mode})),
151
155
  warnings: warnings.map(formatIssue),
152
156
  written,
157
+ demoMediaReplaced,
153
158
  },
154
159
  };
155
160
  }
@@ -0,0 +1,74 @@
1
+ // Copyright (c) Meta Platforms, Inc. and affiliates.
2
+
3
+ /**
4
+ * @file The layout.expand receipt discloses demo media replaced in the template
5
+ * blocks it splices. Blocks are picked by what their source contains, not by
6
+ * name, so catalog renames do not break the suite.
7
+ */
8
+
9
+ import {describe, it, expect, beforeAll} from 'vitest';
10
+ import * as fs from 'node:fs';
11
+ import {layoutExpand} from '../layout.mjs';
12
+ import {discoverTemplates} from '../../template/template.mjs';
13
+ import {buildRegistry} from '../../../foundation/xle/registry.mjs';
14
+
15
+ const SLOW = 60_000;
16
+ const FIXTURE_REF = /\/template-assets\/[\w.-]+\.\w+/g;
17
+
18
+ beforeAll(async () => {
19
+ await buildRegistry();
20
+ }, 120_000);
21
+
22
+ /** @param {string} name */
23
+ const hintKey = name => name.toLowerCase().replace(/[^a-z0-9]/g, '');
24
+
25
+ /** @type {Promise<Array<{hint: string, refs: number}>> | undefined} */
26
+ let catalog;
27
+
28
+ /**
29
+ * Template blocks a `{hint}` resolves to unambiguously, with the number of demo
30
+ * media references in each one's source.
31
+ * @returns {Promise<Array<{hint: string, refs: number}>>}
32
+ */
33
+ function catalogBlocks() {
34
+ catalog ??= discoverTemplates().then(found => {
35
+ const all = /** @type {Array<{dirName: string, type: string, filePath: string}>} */ (
36
+ found
37
+ ).filter(t => t.type === 'block' && t.filePath && fs.existsSync(t.filePath));
38
+ const seen = new Map();
39
+ for (const t of all) seen.set(hintKey(t.dirName), (seen.get(hintKey(t.dirName)) ?? 0) + 1);
40
+ return all
41
+ .filter(t => seen.get(hintKey(t.dirName)) === 1)
42
+ .map(t => ({
43
+ hint: hintKey(t.dirName),
44
+ refs: (fs.readFileSync(t.filePath, 'utf-8').match(FIXTURE_REF) ?? []).length,
45
+ }));
46
+ });
47
+ return catalog;
48
+ }
49
+
50
+ describe('layout.expand receipt - replaced demo media', () => {
51
+ it('counts the demo media of a spliced block once, however often it is referenced', async () => {
52
+ const block = (await catalogBlocks()).find(b => b.refs > 0);
53
+ if (!block) throw new Error('no template block carries demo media');
54
+ const res = await layoutExpand(`V > {${block.hint}}*3`);
55
+ expect(res.type).toBe('layout.expand');
56
+ expect(res.data.blocksReferenced).toHaveLength(1);
57
+ expect(res.data.demoMediaReplaced).toBe(block.refs);
58
+ expect(res.data.code).not.toContain('/template-assets/');
59
+ }, SLOW);
60
+
61
+ it('sums the demo media of every spliced block', async () => {
62
+ const [a, b] = (await catalogBlocks()).filter(x => x.refs > 0);
63
+ if (!b) throw new Error('fewer than two template blocks carry demo media');
64
+ const res = await layoutExpand(`V > {${a.hint}} + {${b.hint}}`);
65
+ expect(res.data.demoMediaReplaced).toBe(a.refs + b.refs);
66
+ }, SLOW);
67
+
68
+ it('reports 0 when nothing spliced carries demo media', async () => {
69
+ const plain = (await catalogBlocks()).find(b => b.refs === 0);
70
+ if (!plain) throw new Error('every template block carries demo media');
71
+ expect((await layoutExpand(`V > {${plain.hint}}`)).data.demoMediaReplaced).toBe(0);
72
+ expect((await layoutExpand('V > B"Save"')).data.demoMediaReplaced).toBe(0);
73
+ }, SLOW);
74
+ });
@@ -36,6 +36,7 @@ export type LayoutExpandResponse = {
36
36
  blocksReferenced: LayoutBlockReference[];
37
37
  warnings: string[];
38
38
  written: string | null;
39
+ demoMediaReplaced: number;
39
40
  };
40
41
  };
41
42
  /**
@@ -48,6 +48,7 @@
48
48
  * @property {LayoutBlockReference[]} data.blocksReferenced
49
49
  * @property {string[]} data.warnings
50
50
  * @property {string | null} data.written
51
+ * @property {number} data.demoMediaReplaced Astryx demo media references (images, posters, videos) replaced in the spliced template blocks: images with a neutral placeholder, videos with an empty source. Swap in your own media at those points in the code; no media is installed. 0 when no spliced block carried any.
51
52
  */
52
53
 
53
54
  /**
@@ -70,7 +70,7 @@ export const doc = {
70
70
  {
71
71
  type: 'layout.expand',
72
72
  description:
73
- 'The expansion: the parsed form, the generated TSX code, componentsUsed, states (count of useState hooks scaffolded), todos, blocksReferenced (each {name, mode}), warnings, and written (the relative output path, or null when nothing was written).',
73
+ 'The expansion: the parsed form, the generated TSX code, componentsUsed, states (count of useState hooks scaffolded), todos, blocksReferenced (each {name, mode}), warnings, written (the relative output path, or null when nothing was written), and demoMediaReplaced (how many Astryx demo media references in the spliced template blocks, such as images, posters and videos, were replaced with placeholders for you to swap for your own media).',
74
74
  },
75
75
  ],
76
76
  throws: [
@@ -24,6 +24,23 @@ export function sameWord(a: string, b: string): boolean;
24
24
  * @returns {string[]}
25
25
  */
26
26
  export function tokenizeQuery(term: string): string[];
27
+ /**
28
+ * The first title or heading that holds every word of the query, in order and
29
+ * side by side, or null.
30
+ * @param {string} term - Lowercased full query.
31
+ * @param {string[] | undefined} titles
32
+ * @returns {string | null}
33
+ */
34
+ export function headingWithPhrase(term: string, titles: string[] | undefined): string | null;
35
+ /**
36
+ * The first title or heading of two words or more that the query holds whole,
37
+ * in order and side by side, or null. A question such as "how do I add dark
38
+ * mode" names the "Dark mode" section outright, around words no title has.
39
+ * @param {string} term - Lowercased full query.
40
+ * @param {string[] | undefined} titles
41
+ * @returns {string | null}
42
+ */
43
+ export function titleInQuery(term: string, titles: string[] | undefined): string | null;
27
44
  /**
28
45
  * @param {string} term - Lowercased full query.
29
46
  * @param {string[]} tokens - Content tokens from tokenizeQuery(term).
@@ -49,6 +66,8 @@ export function scoreQuery(term: string, tokens: string[], candidate: Candidate)
49
66
  * @param {string} term - Lowercased search term.
50
67
  * @param {object} candidate
51
68
  * @param {string} candidate.name - Primary identifier (component/hook name, topic, template name).
69
+ * @param {string} [candidate.domain] - A component, hook, or template name
70
+ * also matches typed as words: `command palette` is CommandPalette.
52
71
  * @param {string[]} [candidate.keywords] - Authored intent (componentsUsed, category words).
53
72
  * @param {string[]} [candidate.weakKeywords] - Derived signal (components a page renders).
54
73
  * @param {string} [candidate.description]
@@ -57,8 +76,9 @@ export function scoreQuery(term: string, tokens: string[], candidate: Candidate)
57
76
  * @param {{fuzzy?: boolean}} [opts] - `fuzzy`: allow edit-distance (typo) matches. Default true; multi-word queries pass false.
58
77
  * @returns {{score: number, reason: string} | null}
59
78
  */
60
- export function scoreCandidate(term: string, { name, keywords, weakKeywords, description, prose, guidance, }: {
79
+ export function scoreCandidate(term: string, { name, domain, keywords, weakKeywords, description, prose, guidance, }: {
61
80
  name: string;
81
+ domain?: string | undefined;
62
82
  keywords?: string[] | undefined;
63
83
  weakKeywords?: string[] | undefined;
64
84
  description?: string | undefined;
@@ -70,6 +90,30 @@ export function scoreCandidate(term: string, { name, keywords, weakKeywords, des
70
90
  score: number;
71
91
  reason: string;
72
92
  } | null;
93
+ /**
94
+ * The components (name and keywords) a search response was scored against, or
95
+ * null when that search was narrowed away from components.
96
+ * @param {object} response
97
+ * @returns {{name: string, keywords: string[]}[] | null}
98
+ */
99
+ export function searchedComponents(response: object): {
100
+ name: string;
101
+ keywords: string[];
102
+ }[] | null;
103
+ /**
104
+ * Every component the project can use, Core's and its integrations', with the
105
+ * keywords its own doc declares: the discovery and doc reads search's own
106
+ * candidates use, without the import paths and prose a result carries. `build`
107
+ * reads it to tell a part of a page from a page when its search was narrowed
108
+ * away from components.
109
+ * @param {string} coreDir
110
+ * @param {string} cwd
111
+ * @returns {Promise<{name: string, keywords: string[]}[]>}
112
+ */
113
+ export function componentKeywords(coreDir: string, cwd: string): Promise<{
114
+ name: string;
115
+ keywords: string[];
116
+ }[]>;
73
117
  /**
74
118
  * Unified ranked search across components, hooks, docs, and templates.
75
119
  *
@@ -122,6 +166,12 @@ export type Candidate = {
122
166
  description?: string | undefined;
123
167
  prose?: string[] | undefined;
124
168
  guidance?: string[] | undefined;
169
+ /**
170
+ * - A doc's title and the headings inside it:
171
+ * the lines a reader scans to pick it. The whole query standing in one of
172
+ * them, or one of them standing whole in the query, is a top-tier match.
173
+ */
174
+ titles?: string[] | undefined;
125
175
  _import?: string | undefined;
126
176
  _title?: string | undefined;
127
177
  /**
@@ -48,7 +48,7 @@ export const doc = {
48
48
  name: 'options.cwd',
49
49
  type: 'string',
50
50
  description:
51
- "Directory to resolve @astryxdesign/core from. A docs-only search (`type: 'doc'`) does not need it.",
51
+ "Directory to resolve @astryxdesign/core from. A docs-only search (`type: 'doc'`) does not need it, and a search with no `type` covers the docs alone when core is missing.",
52
52
  },
53
53
  ],
54
54
  returns: [
@@ -65,7 +65,7 @@ export const doc = {
65
65
  },
66
66
  {
67
67
  code: 'ERR_CORE_NOT_FOUND',
68
- when: '@astryxdesign/core cannot be found from the cwd, and the search reads it: every `type` but `doc`',
68
+ when: '@astryxdesign/core cannot be found from the cwd, and `type` names a domain that reads it: `component`, `hook`, or `template`',
69
69
  },
70
70
  ],
71
71
  examples: [