@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
@@ -45,7 +45,11 @@ import {fileURLToPath, pathToFileURL} from 'node:url';
45
45
  import {parse} from '@babel/parser';
46
46
  import {createJiti} from 'jiti';
47
47
  import {getCliInvocation} from '../../../foundation/env/package-manager.mjs';
48
- import {CLI_ROOT, findCoreDir} from '../../../foundation/fs/paths.mjs';
48
+ import {
49
+ CLI_ROOT,
50
+ findCoreDir,
51
+ findInstalledPackage,
52
+ } from '../../../foundation/fs/paths.mjs';
49
53
  import {
50
54
  assertWithin,
51
55
  sanitizeName,
@@ -53,6 +57,7 @@ import {
53
57
  } from '../../../foundation/fs/path-safety.mjs';
54
58
  import {ERROR_CODES} from '../../../foundation/response/error-codes.mjs';
55
59
  import {AstryxError} from '../../error.mjs';
60
+ import {applyWrites} from '../../integration/add-helpers.mjs';
56
61
  import {logger} from '../../logger.mjs';
57
62
  import {loadComponentDoc} from '../../../foundation/discovery/component-loader.mjs';
58
63
  import {
@@ -124,6 +129,232 @@ try {
124
129
  _coreImportError = e;
125
130
  }
126
131
 
132
+ /**
133
+ * The bindings one Core provides: the namespaces interception spreads, the
134
+ * functions the build calls, and why the import failed, if it did.
135
+ * @typedef {{
136
+ * themeModule: any,
137
+ * rootModule: any,
138
+ * importError: any,
139
+ * defineTheme: any,
140
+ * generateThemeRulesSplit: any,
141
+ * generateOnMediaCSS: any,
142
+ * generateAdaptationCSS: any,
143
+ * dataTokenDefaults: any,
144
+ * }} CoreBindings
145
+ */
146
+
147
+ /**
148
+ * The bindings exactly as the CLI's own import left them, kept so each build
149
+ * can choose between them and the project's installed Core.
150
+ * @type {CoreBindings}
151
+ */
152
+ const _cliCore = {
153
+ themeModule: _coreThemeModule,
154
+ rootModule: _coreRootModule,
155
+ importError: _coreImportError,
156
+ defineTheme: _defineTheme,
157
+ generateThemeRulesSplit: _generateThemeRulesSplit,
158
+ generateOnMediaCSS: _generateOnMediaCSS,
159
+ generateAdaptationCSS: _generateAdaptationCSS,
160
+ dataTokenDefaults: _dataTokenDefaults,
161
+ };
162
+
163
+ /**
164
+ * Point the build at one Core: the module-level bindings every step reads.
165
+ * @param {CoreBindings} core
166
+ */
167
+ function setCore(core) {
168
+ _coreThemeModule = core.themeModule;
169
+ _coreRootModule = core.rootModule;
170
+ _coreImportError = core.importError;
171
+ _defineTheme = core.defineTheme;
172
+ _generateThemeRulesSplit = core.generateThemeRulesSplit;
173
+ _generateOnMediaCSS = core.generateOnMediaCSS;
174
+ _generateAdaptationCSS = core.generateAdaptationCSS;
175
+ _dataTokenDefaults = core.dataTokenDefaults;
176
+ }
177
+
178
+ /**
179
+ * The file an `import` of `subpath` reaches through a package's `exports` map,
180
+ * or null when the map has no entry for it. Conditions are taken in the map's
181
+ * own order, as Node does, accepting the ones an import matches here: `node`,
182
+ * `import`, and `default` (so `source` and `types` are skipped).
183
+ *
184
+ * @param {string} dir - The package directory.
185
+ * @param {string} subpath - `'.'` or a subpath such as `'./theme'`.
186
+ * @returns {string|null}
187
+ */
188
+ function importTarget(dir, subpath) {
189
+ let pkg;
190
+ try {
191
+ pkg = JSON.parse(fs.readFileSync(path.join(dir, 'package.json'), 'utf-8'));
192
+ } catch {
193
+ return null;
194
+ }
195
+ const map = pkg?.exports;
196
+ const bySubpath =
197
+ map &&
198
+ typeof map === 'object' &&
199
+ !Array.isArray(map) &&
200
+ Object.keys(map).some(key => key.startsWith('.'));
201
+ const entry = bySubpath ? map[subpath] : subpath === '.' ? map : undefined;
202
+ /** @param {any} value @returns {string|null} */
203
+ const pick = value => {
204
+ if (typeof value === 'string') return value;
205
+ if (!value || typeof value !== 'object' || Array.isArray(value))
206
+ return null;
207
+ for (const [condition, target] of Object.entries(value)) {
208
+ if (
209
+ condition !== 'node' &&
210
+ condition !== 'import' &&
211
+ condition !== 'default'
212
+ )
213
+ continue;
214
+ const picked = pick(target);
215
+ if (picked) return picked;
216
+ }
217
+ return null;
218
+ };
219
+ const target = pick(entry);
220
+ return target ? path.resolve(dir, target) : null;
221
+ }
222
+
223
+ /** @param {string} file @returns {string} its real path, or itself */
224
+ function realOrSelf(file) {
225
+ try {
226
+ return fs.realpathSync(file);
227
+ } catch {
228
+ return file;
229
+ }
230
+ }
231
+
232
+ /**
233
+ * The Core the project at `cwd` installed, when it differs from the one the
234
+ * CLI's own import found and loads. The app's `<Theme>` runs on the app's
235
+ * Core, so building with it keeps the CSS identical; and a CLI run one-off
236
+ * (`npx @astryxdesign/cli`) has no Core beside it at all. Null when there is
237
+ * nothing better than the CLI's own import: no installed Core (the Astryx
238
+ * repository, a bare directory), the same Core, or one that does not load.
239
+ *
240
+ * @param {string} cwd
241
+ * @returns {Promise<CoreBindings | null>}
242
+ */
243
+ async function loadProjectCore(cwd) {
244
+ const dir = findInstalledPackage(cwd, '@astryxdesign/core');
245
+ const themeFile = dir ? importTarget(dir, './theme') : null;
246
+ if (!dir || !themeFile) return null;
247
+ if (_cliCore.themeModule) {
248
+ try {
249
+ const own = fileURLToPath(
250
+ import.meta.resolve('@astryxdesign/core/theme'),
251
+ );
252
+ if (realOrSelf(own) === realOrSelf(themeFile)) return null;
253
+ } catch {
254
+ // The CLI's own Core cannot be located; load the project's.
255
+ }
256
+ }
257
+ let themeModule;
258
+ try {
259
+ themeModule = await import(pathToFileURL(themeFile).href);
260
+ } catch {
261
+ return null;
262
+ }
263
+ if (!themeModule.defineTheme || !themeModule.generateThemeRulesSplit)
264
+ return null;
265
+ let rootModule = null;
266
+ const rootFile = importTarget(dir, '.');
267
+ if (rootFile) {
268
+ try {
269
+ rootModule = await import(pathToFileURL(rootFile).href);
270
+ } catch {
271
+ // As with the CLI's own import, the theme namespace stands in for it.
272
+ }
273
+ }
274
+ return {
275
+ themeModule,
276
+ rootModule,
277
+ importError: null,
278
+ defineTheme: themeModule.defineTheme,
279
+ generateThemeRulesSplit: themeModule.generateThemeRulesSplit,
280
+ generateOnMediaCSS: themeModule.generateOnMediaCSS,
281
+ generateAdaptationCSS: themeModule.generateAdaptationCSS,
282
+ dataTokenDefaults: themeModule.dataTokenDefaults,
283
+ };
284
+ }
285
+
286
+ /** @type {Map<string, Awaited<ReturnType<typeof loadProjectCore>>>} */
287
+ const _projectCores = new Map();
288
+
289
+ /**
290
+ * Choose the Core this build generates with: the project's installed one when
291
+ * {@link loadProjectCore} finds it, else the CLI's own.
292
+ * @param {string} cwd
293
+ */
294
+ async function selectCore(cwd) {
295
+ if (!_projectCores.has(cwd))
296
+ _projectCores.set(cwd, await loadProjectCore(cwd));
297
+ setCore(_projectCores.get(cwd) ?? _cliCore);
298
+ }
299
+
300
+ /**
301
+ * Whether `cwd` sits in the Astryx repository, where Core is built from
302
+ * source rather than installed.
303
+ * @param {string} cwd
304
+ */
305
+ function inAstryxRepository(cwd) {
306
+ let dir = cwd;
307
+ for (let i = 0; i < 6; i++) {
308
+ try {
309
+ const pkg = JSON.parse(
310
+ fs.readFileSync(
311
+ path.join(dir, 'packages', 'core', 'package.json'),
312
+ 'utf-8',
313
+ ),
314
+ );
315
+ if (pkg?.name === '@astryxdesign/core') return true;
316
+ } catch {
317
+ // Not here; keep walking up.
318
+ }
319
+ const parent = path.dirname(dir);
320
+ if (parent === dir) break;
321
+ dir = parent;
322
+ }
323
+ return false;
324
+ }
325
+
326
+ /**
327
+ * Why `theme build` has no Core to generate with, and the fix for where it
328
+ * ran: the Astryx repository builds Core, an app installs it, and an installed
329
+ * Core that will not load is named.
330
+ *
331
+ * @param {string} cwd
332
+ * @param {any} importError - Why the import failed, when it threw.
333
+ * @returns {string}
334
+ */
335
+ export function coreUnavailableMessage(cwd, importError) {
336
+ const why =
337
+ '`astryx theme build` generates CSS with @astryxdesign/core, the same code the runtime <Theme> uses';
338
+ const detail = importError ? `\n Import error: ${importError.message}` : '';
339
+ if (inAstryxRepository(cwd)) {
340
+ return (
341
+ `Could not load @astryxdesign/core/theme: ${why}. Build @astryxdesign/core ` +
342
+ `first (e.g. \`pnpm -F @astryxdesign/core build\`).${detail}`
343
+ );
344
+ }
345
+ const installed = findInstalledPackage(cwd, '@astryxdesign/core');
346
+ if (installed) {
347
+ return (
348
+ `Could not load the @astryxdesign/core installed at ` +
349
+ `${path.relative(cwd, installed) || '.'}: ${why}. Reinstall it.${detail}`
350
+ );
351
+ }
352
+ return (
353
+ `This project does not have @astryxdesign/core installed: ${why}. Install ` +
354
+ `it: \`npm install @astryxdesign/core\` (or yarn/pnpm/bun).${detail}`
355
+ );
356
+ }
357
+
127
358
  /**
128
359
  * Read a package's `version` from a resolved directory. Returns `'unknown'`
129
360
  * when it can't be read so the header always has a value.
@@ -224,26 +455,15 @@ function staleBuildOutputs(writes, cwd) {
224
455
  /** @param {Array<{dest: string, content: string}>} writes */
225
456
  function writeBuildOutputs(writes) {
226
457
  if (writes.length === 0) return;
227
- /** @type {Array<{tmp: string, dest: string}>} */
228
- const staged = [];
229
458
  try {
230
- fs.mkdirSync(path.dirname(writes[0].dest), {recursive: true});
231
- for (const write of writes) {
232
- const tmp = `${write.dest}.${process.pid}.tmp`;
233
- fs.writeFileSync(tmp, write.content);
234
- staged.push({tmp, dest: write.dest});
235
- }
236
- for (const stagedWrite of staged) {
237
- fs.renameSync(stagedWrite.tmp, stagedWrite.dest);
238
- }
459
+ applyWrites(
460
+ writes.map(write => ({
461
+ path: write.dest,
462
+ contents: write.content,
463
+ createOnly: false,
464
+ })),
465
+ );
239
466
  } catch (error) {
240
- for (const stagedWrite of staged) {
241
- try {
242
- fs.rmSync(stagedWrite.tmp, {force: true});
243
- } catch {
244
- // Best effort: the command still fails and never reports success.
245
- }
246
- }
247
467
  const message = `Failed to write theme outputs: ${/** @type {Error} */ (error).message}`;
248
468
  throw new AstryxError(message, undefined, ERROR_CODES.ERR_WRITE_FAILED);
249
469
  }
@@ -2117,6 +2337,8 @@ async function themeBuildInternal(
2117
2337
 
2118
2338
  logger.log(`\nBuilding theme from ${path.relative(cwd, filePath)}...`);
2119
2339
 
2340
+ await selectCore(cwd);
2341
+
2120
2342
  // Standalone builds only need interception when an older Core could erase
2121
2343
  // adaptations. Family preparation always supplies the same recorder as its
2122
2344
  // shared loader so exact authored parent identity stays CLI-private instead
@@ -2243,13 +2465,7 @@ async function themeBuildInternal(
2243
2465
  // capability exports are checked against what the theme actually asks for.
2244
2466
  if (!_defineTheme || !_generateThemeRulesSplit) {
2245
2467
  throw new AstryxError(
2246
- 'Could not load @astryxdesign/core/theme: `astryx theme build` requires a ' +
2247
- 'built, resolvable @astryxdesign/core so it emits the same CSS as the ' +
2248
- 'runtime <Theme>. Build @astryxdesign/core first (e.g. `pnpm -F @astryxdesign/core ' +
2249
- 'build`)' +
2250
- (_coreImportError
2251
- ? `.\n Import error: ${_coreImportError.message}`
2252
- : '.'),
2468
+ coreUnavailableMessage(cwd, _coreImportError),
2253
2469
  undefined,
2254
2470
  ERROR_CODES.ERR_CORE_NOT_FOUND,
2255
2471
  );
@@ -2817,6 +3033,7 @@ export async function themeBuildFamily(
2817
3033
  error instanceof Error ? error.message : 'Invalid family key.';
2818
3034
  throw new AstryxError(message, undefined, ERROR_CODES.ERR_THEME_INVALID);
2819
3035
  }
3036
+ await selectCore(cwd);
2820
3037
  const familyInterception = interceptCore(_coreThemeModule, _coreRootModule);
2821
3038
  // prettier-ignore
2822
3039
  const familyLoader = createJiti(import.meta.url, {moduleCache: true, interopDefault: false, jsx: true, extensions: THEME_MODULE_EXTENSIONS, virtualModules: familyInterception.modules});
@@ -0,0 +1,165 @@
1
+ // Copyright (c) Meta Platforms, Inc. and affiliates.
2
+
3
+ /**
4
+ * @file `theme build` in an app generates with the Core the app installed.
5
+ *
6
+ * A CLI run one-off (`npx @astryxdesign/cli`) has no Core beside it, and the
7
+ * app's `<Theme>` runs on the app's Core either way, so the build loads the
8
+ * project's installed `@astryxdesign/core` when it differs from the CLI's own.
9
+ * When there is no Core to use, the error names the fix for where it ran:
10
+ * install it in an app, build it in the Astryx repository.
11
+ */
12
+
13
+ import {afterEach, beforeEach, describe, expect, it} from 'vitest';
14
+ import * as fs from 'node:fs';
15
+ import * as os from 'node:os';
16
+ import * as path from 'node:path';
17
+ import {fileURLToPath, pathToFileURL} from 'node:url';
18
+ import {coreUnavailableMessage, themeBuild} from './build.mjs';
19
+
20
+ const HERE = path.dirname(fileURLToPath(import.meta.url));
21
+ const REPO_ROOT = path.resolve(HERE, '../../../../..');
22
+ const CORE_DIST = path.join(REPO_ROOT, 'packages/core/dist');
23
+
24
+ /** @type {string[]} */
25
+ const tmpDirs = [];
26
+ const makeDir = () => {
27
+ const dir = fs.mkdtempSync(path.join(os.tmpdir(), 'astryx-project-core-'));
28
+ tmpDirs.push(dir);
29
+ fs.writeFileSync(
30
+ path.join(dir, 'package.json'),
31
+ '{"name":"app","type":"module"}',
32
+ );
33
+ // A plain theme object: the file itself never imports Core, so only the
34
+ // build's own Core loading can set the marker below.
35
+ fs.writeFileSync(
36
+ path.join(dir, 'ocean.mjs'),
37
+ "export default {name: 'ocean', tokens: {'--color-bg': '#0a0a0a'}};\n",
38
+ );
39
+ return dir;
40
+ };
41
+
42
+ beforeEach(() => {
43
+ delete globalThis.__astryxProjectCoreLoads;
44
+ });
45
+ afterEach(() => {
46
+ delete globalThis.__astryxProjectCoreLoads;
47
+ for (const dir of tmpDirs.splice(0))
48
+ fs.rmSync(dir, {recursive: true, force: true});
49
+ });
50
+
51
+ /**
52
+ * Install a Core in `dir` that is the built one, re-exported, and records each
53
+ * entry as it is imported. Its `exports` map lists `source` first, as Core's
54
+ * own does, so the build must skip that condition to find the runtime file.
55
+ * @param {string} dir
56
+ * @param {{broken?: boolean}} [options]
57
+ */
58
+ function installCore(dir, {broken = false} = {}) {
59
+ const core = path.join(dir, 'node_modules', '@astryxdesign', 'core');
60
+ fs.mkdirSync(core, {recursive: true});
61
+ fs.writeFileSync(
62
+ path.join(core, 'package.json'),
63
+ JSON.stringify({
64
+ name: '@astryxdesign/core',
65
+ version: '0.6.5',
66
+ type: 'module',
67
+ exports: {
68
+ '.': {types: './root.d.ts', default: './root.mjs'},
69
+ './theme': {
70
+ source: './src/theme/index.ts',
71
+ types: './theme.d.ts',
72
+ default: './theme.mjs',
73
+ },
74
+ },
75
+ }),
76
+ );
77
+ /** @param {string} entry @param {string} target */
78
+ const module = (entry, target) =>
79
+ broken
80
+ ? "throw new Error('this Core is broken');\n"
81
+ : `export * from ${JSON.stringify(pathToFileURL(path.join(CORE_DIST, target)).href)};\n` +
82
+ `globalThis.__astryxProjectCoreLoads = [...(globalThis.__astryxProjectCoreLoads ?? []), '${entry}'];\n`;
83
+ fs.writeFileSync(
84
+ path.join(core, 'theme.mjs'),
85
+ module('theme', 'theme/index.js'),
86
+ );
87
+ fs.writeFileSync(path.join(core, 'root.mjs'), module('root', 'index.js'));
88
+ return core;
89
+ }
90
+
91
+ describe('theme build generates with the Core the project installed', () => {
92
+ it('loads the app’s installed Core when it differs from the CLI’s own', async () => {
93
+ const app = makeDir();
94
+ installCore(app);
95
+ await themeBuild('ocean.mjs', {}, {cwd: app});
96
+ expect(globalThis.__astryxProjectCoreLoads).toEqual(['theme', 'root']);
97
+ // The same Core, re-exported, emits the same CSS as the CLI's own. The
98
+ // generated header differs only in the Core version it records.
99
+ const bare = makeDir();
100
+ await themeBuild('ocean.mjs', {}, {cwd: bare});
101
+ /** @param {string} dir */
102
+ const rules = dir =>
103
+ fs
104
+ .readFileSync(path.join(dir, 'ocean.css'), 'utf-8')
105
+ .replace(/^\/\*[\s\S]*?\*\/\n/, '');
106
+ expect(rules(app)).toBe(rules(bare));
107
+ expect(rules(app)).toContain('#0a0a0a');
108
+ });
109
+
110
+ it('keeps the CLI’s own Core when the project has none', async () => {
111
+ const bare = makeDir();
112
+ await themeBuild('ocean.mjs', {}, {cwd: bare});
113
+ expect(globalThis.__astryxProjectCoreLoads).toBeUndefined();
114
+ expect(fs.existsSync(path.join(bare, 'ocean.css'))).toBe(true);
115
+ });
116
+
117
+ it('keeps the CLI’s own Core when the installed one does not load', async () => {
118
+ const app = makeDir();
119
+ installCore(app, {broken: true});
120
+ await themeBuild('ocean.mjs', {}, {cwd: app});
121
+ expect(fs.existsSync(path.join(app, 'ocean.css'))).toBe(true);
122
+ });
123
+ });
124
+
125
+ describe('the error when there is no Core to generate with', () => {
126
+ it('tells an app to install Core, not to build it', () => {
127
+ const app = makeDir();
128
+ const message = coreUnavailableMessage(
129
+ app,
130
+ new Error("Cannot find package '@astryxdesign/core'"),
131
+ );
132
+ expect(message).toContain(
133
+ 'This project does not have @astryxdesign/core installed',
134
+ );
135
+ expect(message).toContain('`npm install @astryxdesign/core`');
136
+ expect(message).not.toContain('pnpm -F');
137
+ expect(message).toContain(
138
+ "Import error: Cannot find package '@astryxdesign/core'",
139
+ );
140
+ });
141
+
142
+ it('names an installed Core that does not load', () => {
143
+ const app = makeDir();
144
+ installCore(app, {broken: true});
145
+ const message = coreUnavailableMessage(
146
+ app,
147
+ new Error('this Core is broken'),
148
+ );
149
+ expect(message).toContain(
150
+ `Could not load the @astryxdesign/core installed at ${path.join('node_modules', '@astryxdesign', 'core')}`,
151
+ );
152
+ expect(message).toContain('Reinstall it.');
153
+ expect(message).not.toContain('pnpm -F');
154
+ });
155
+
156
+ it('keeps the build instruction in the Astryx repository', () => {
157
+ const message = coreUnavailableMessage(
158
+ path.join(REPO_ROOT, 'packages', 'cli'),
159
+ undefined,
160
+ );
161
+ expect(message).toContain('Build @astryxdesign/core first');
162
+ expect(message).toContain('`pnpm -F @astryxdesign/core build`');
163
+ expect(message).not.toContain('Import error');
164
+ });
165
+ });
@@ -0,0 +1,148 @@
1
+ // Copyright (c) Meta Platforms, Inc. and affiliates.
2
+
3
+ /**
4
+ * `theme build` writes its CSS, JS, and declarations as one transaction: when
5
+ * a later output fails to publish, every output it already replaced gets its
6
+ * previous bytes back and every output it created is removed. Publishing is
7
+ * forced to fail by wrapping the two calls that publish a staged file:
8
+ * `linkSync` for a new file and `renameSync` for a replacement.
9
+ *
10
+ * Separate file because vi.mock is hoisted and affects the whole module.
11
+ * `themeBuild` needs a built core; the `node` project's globalSetup builds it.
12
+ */
13
+
14
+ import {afterEach, beforeEach, describe, expect, it, vi} from 'vitest';
15
+ import * as os from 'node:os';
16
+ import * as path from 'node:path';
17
+
18
+ const failures = vi.hoisted(() => ({
19
+ /** Fail the Nth staged-file publish (1-based); 0 disables. */
20
+ publish: 0,
21
+ count: 0,
22
+ }));
23
+
24
+ vi.mock('node:fs', async importOriginal => {
25
+ const actual = /** @type {typeof import('node:fs')} */ (
26
+ await importOriginal()
27
+ );
28
+ /** @param {string} source @param {string} op */
29
+ const maybeFail = (source, op) => {
30
+ if (!path.basename(String(source)).includes('.tmp')) return;
31
+ if (failures.publish === 0) return;
32
+ failures.count++;
33
+ if (failures.count === failures.publish) {
34
+ throw Object.assign(new Error(`EIO: forced ${op} failure`), {
35
+ code: 'EIO',
36
+ });
37
+ }
38
+ };
39
+ return {
40
+ ...actual,
41
+ linkSync: vi.fn((source, destination) => {
42
+ maybeFail(source, 'link');
43
+ return actual.linkSync(source, destination);
44
+ }),
45
+ renameSync: vi.fn((source, destination) => {
46
+ maybeFail(source, 'rename');
47
+ return actual.renameSync(source, destination);
48
+ }),
49
+ };
50
+ });
51
+
52
+ const fs = await import('node:fs');
53
+ const {themeBuild} = await import('./build.mjs');
54
+
55
+ vi.setConfig({testTimeout: 30000});
56
+
57
+ let tmpDir;
58
+
59
+ beforeEach(() => {
60
+ tmpDir = fs.mkdtempSync(
61
+ path.join(os.tmpdir(), 'astryx-theme-build-rollback-'),
62
+ );
63
+ failures.publish = 0;
64
+ failures.count = 0;
65
+ });
66
+
67
+ afterEach(() => {
68
+ failures.publish = 0;
69
+ fs.rmSync(tmpDir, {recursive: true, force: true});
70
+ });
71
+
72
+ /**
73
+ * Write a source for the theme named `rollbacktheme`. Each call uses a new
74
+ * file so the loader cannot serve an earlier version from its cache.
75
+ * @param {string} file @param {string} background
76
+ */
77
+ function writeTheme(file, background) {
78
+ fs.writeFileSync(
79
+ path.join(tmpDir, file),
80
+ `export default { name: 'rollbacktheme', tokens: { '--color-bg': '${background}' } };\n`,
81
+ );
82
+ return file;
83
+ }
84
+
85
+ const OUTPUTS = ['rollbacktheme.css', 'rollbacktheme.js', 'rollbacktheme.d.ts'];
86
+
87
+ /** @returns {Map<string, Buffer | null>} */
88
+ function readOutputs() {
89
+ return new Map(
90
+ OUTPUTS.map(name => {
91
+ const file = path.join(tmpDir, name);
92
+ return [name, fs.existsSync(file) ? fs.readFileSync(file) : null];
93
+ }),
94
+ );
95
+ }
96
+
97
+ function strays() {
98
+ return fs
99
+ .readdirSync(tmpDir)
100
+ .filter(name => name.includes('.tmp') || name.includes('.restore-'));
101
+ }
102
+
103
+ describe('themeBuild rolls back a partial write', () => {
104
+ it('removes every created output when the second publish fails', async () => {
105
+ const source = writeTheme('first.mjs', '#0a0a0a');
106
+ failures.publish = 2;
107
+
108
+ await expect(themeBuild(source, {}, {cwd: tmpDir})).rejects.toMatchObject({
109
+ code: 'ERR_WRITE_FAILED',
110
+ });
111
+
112
+ for (const bytes of readOutputs().values()) expect(bytes).toBeNull();
113
+ expect(strays()).toEqual([]);
114
+ });
115
+
116
+ it('restores every replaced output when the second publish fails', async () => {
117
+ const first = await themeBuild(
118
+ writeTheme('first.mjs', '#0a0a0a'),
119
+ {},
120
+ {cwd: tmpDir},
121
+ );
122
+ expect(first?.type).toBe('theme.build');
123
+ const before = readOutputs();
124
+ for (const bytes of before.values()) expect(bytes).not.toBeNull();
125
+
126
+ const next = writeTheme('next.mjs', '#fafafa');
127
+ failures.publish = 2;
128
+ await expect(themeBuild(next, {}, {cwd: tmpDir})).rejects.toMatchObject({
129
+ code: 'ERR_WRITE_FAILED',
130
+ });
131
+
132
+ const after = readOutputs();
133
+ for (const name of OUTPUTS) {
134
+ expect(
135
+ after.get(name)?.equals(/** @type {Buffer} */ (before.get(name))),
136
+ ).toBe(true);
137
+ }
138
+
139
+ // The failed build really had different bytes to write.
140
+ failures.publish = 0;
141
+ await themeBuild(next, {}, {cwd: tmpDir});
142
+ const css = readOutputs().get('rollbacktheme.css');
143
+ expect(
144
+ css?.equals(/** @type {Buffer} */ (before.get('rollbacktheme.css'))),
145
+ ).toBe(false);
146
+ expect(strays()).toEqual([]);
147
+ });
148
+ });
@@ -29,7 +29,7 @@ export const doc = {
29
29
  name: 'input',
30
30
  type: 'TonalPaletteGenerationInput',
31
31
  description:
32
- 'Families and seeds plus optional modes, shared stops, anchors, vibrancy from 0 to 100 (default 50), and neutral profile. Only generate an accent family when one is explicitly requested; clarify whether an ambiguous accent means one theme value or a tonal family.',
32
+ 'Families and seeds plus optional modes, shared stops, anchors, vibrancy from 0 to 100 (default 50), and neutral profile.',
33
33
  required: true,
34
34
  },
35
35
  ],
@@ -72,6 +72,5 @@ export const doc = {
72
72
  code: "generateTonalPalette({stops: [12.5, 50], families: [{id: 'blue', seed: '#0074e2'}]});",
73
73
  },
74
74
  ],
75
- command: 'theme palette generate',
76
75
  related: ['themePaletteGenerate'],
77
76
  };
@@ -13,7 +13,7 @@ export const doc = {
13
13
  displayName: 'listThemes()',
14
14
  summary: 'Read the CLI bundled-theme descriptors.',
15
15
  description:
16
- 'Reads the typed same-stem descriptors under templates/themes and returns normalized entries synchronously. This low-level helper keeps its historical bundled-only contract; project-aware themeList() and themeAdd() also discover source themes from installed integrations.',
16
+ "Synchronously returns the themes bundled with the CLI, including each one's entry file, export name and file list. Bundled themes only; use themeListAvailable() to include themes from installed integrations.",
17
17
  importPath: '@astryxdesign/cli/api',
18
18
  signature: 'listThemes(): BundledTheme[]',
19
19
  keywords: ['theme', 'themes', 'descriptor', 'bundled', 'adapter', 'list'],