@astryxdesign/cli 0.6.3-canary.ea2f048 → 0.6.3-canary.ee13359

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 (571) hide show
  1. package/README.md +114 -76
  2. package/api/blog/blog.doc.mjs +1 -0
  3. package/api/build/build.doc.mjs +4 -3
  4. package/api/build/build.test.mjs +14 -3
  5. package/api/build/build.type.d.mts +36 -2
  6. package/api/build/build.type.mjs +25 -4
  7. package/api/build/help/help.d.mts +8 -5
  8. package/api/build/help/help.mjs +56 -6
  9. package/api/component/_adapter.d.mts +6 -12
  10. package/api/component/_adapter.mjs +20 -10
  11. package/api/component/component.doc.mjs +13 -3
  12. package/api/component/component.mjs +91 -14
  13. package/api/component/component.test.mjs +38 -0
  14. package/api/component/component.type.d.mts +22 -11
  15. package/api/component/component.type.mjs +32 -24
  16. package/api/component/detail/blocks/blocks.d.mts +2 -1
  17. package/api/component/detail/blocks/blocks.mjs +4 -3
  18. package/api/component/list/list.d.mts +0 -5
  19. package/api/component/list/list.mjs +40 -11
  20. package/api/discover/discover.doc.mjs +2 -1
  21. package/api/discover/discover.type.d.mts +4 -4
  22. package/api/discover/discover.type.mjs +10 -10
  23. package/api/docs/_adapter.d.mts +230 -41
  24. package/api/docs/_adapter.mjs +734 -108
  25. package/api/docs/compiled-topics.test.mjs +78 -0
  26. package/api/docs/detail/detail.mjs +22 -63
  27. package/api/docs/detail/section/section.d.mts +1 -1
  28. package/api/docs/detail/section/section.mjs +54 -19
  29. package/api/docs/detail/section/section.test.mjs +50 -0
  30. package/api/docs/docs.d.mts +10 -3
  31. package/api/docs/docs.doc.mjs +54 -16
  32. package/api/docs/docs.mjs +53 -10
  33. package/api/docs/docs.test.mjs +164 -4
  34. package/api/docs/docs.type.d.mts +205 -5
  35. package/api/docs/docs.type.mjs +137 -11
  36. package/api/docs/index/index.d.mts +18 -0
  37. package/api/docs/index/index.mjs +40 -0
  38. package/api/docs/index/index.test.mjs +62 -0
  39. package/api/docs/integration-tree.test.mjs +547 -0
  40. package/api/docs/integrationDocs.test.mjs +114 -8
  41. package/api/docs/list/list.mjs +28 -12
  42. package/api/docs/node/node.d.mts +43 -0
  43. package/api/docs/node/node.mjs +192 -0
  44. package/api/doctor/doctor.d.mts +99 -1
  45. package/api/doctor/doctor.doc.mjs +1 -0
  46. package/api/doctor/doctor.mjs +544 -1
  47. package/api/doctor/doctor.test.mjs +610 -1
  48. package/api/gap-report/gap-report.doc.mjs +8 -4
  49. package/api/hook/_adapter.mjs +19 -5
  50. package/api/hook/hook.doc.mjs +1 -0
  51. package/api/hook/hook.type.d.mts +3 -3
  52. package/api/hook/hook.type.mjs +11 -11
  53. package/api/hook/list/list.d.mts +2 -2
  54. package/api/hook/list/list.mjs +69 -17
  55. package/api/index.d.mts +1 -1
  56. package/api/index.mjs +1 -0
  57. package/api/init/init.doc.mjs +6 -1
  58. package/api/init/init.test.mjs +41 -1
  59. package/api/init/remove/remove.mjs +1 -1
  60. package/api/init/run/run.mjs +20 -10
  61. package/api/integration/add-contribution.component-names.test.mjs +120 -0
  62. package/api/integration/add-contribution.d.mts +2 -1
  63. package/api/integration/add-contribution.mjs +130 -15
  64. package/api/integration/add-contribution.test.mjs +258 -7
  65. package/api/integration/add-theme.mjs +34 -64
  66. package/api/integration/add-theme.test.mjs +105 -21
  67. package/api/integration/authoring-checks.mjs +108 -31
  68. package/api/integration/authoring-checks.test.mjs +171 -6
  69. package/api/integration/authoring-checks.type.d.mts +3 -1
  70. package/api/integration/authoring-checks.type.mjs +3 -1
  71. package/api/integration/integration-authoring.type.d.mts +3 -1
  72. package/api/integration/integration-authoring.type.mjs +2 -0
  73. package/api/integration/integration-block-exports.test.mjs +10 -6
  74. package/api/integration/integrationAdd.doc.mjs +14 -4
  75. package/api/integration/integrationAddAgentDoc.doc.mjs +1 -0
  76. package/api/integration/integrationAddCodemod.doc.mjs +1 -0
  77. package/api/integration/integrationAddComponent.doc.mjs +2 -1
  78. package/api/integration/integrationAddDoc.doc.mjs +8 -1
  79. package/api/integration/integrationAddTemplate.doc.mjs +1 -0
  80. package/api/integration/integrationAddTheme.doc.mjs +6 -5
  81. package/api/integration/integrationComponentConflicts.doc.mjs +1 -0
  82. package/api/integration/integrationDocConflicts.doc.mjs +2 -1
  83. package/api/integration/integrationPackCheck.doc.mjs +2 -1
  84. package/api/integration/integrationTemplateConflicts.doc.mjs +7 -5
  85. package/api/integration/pack-check.mjs +83 -7
  86. package/api/integration/pack-check.test.mjs +387 -47
  87. package/api/integration/pack-check.type.d.mts +26 -2
  88. package/api/integration/pack-check.type.mjs +14 -1
  89. package/api/integration/summarizeIssues.doc.mjs +1 -0
  90. package/api/integration/validate-integration-fixes.test.mjs +1389 -0
  91. package/api/integration/validate-integration.mjs +52 -102
  92. package/api/integration/validate-integration.test.mjs +179 -26
  93. package/api/integration/validate-unread-theme-folders.test.mjs +110 -0
  94. package/api/integration/validateIntegration.doc.mjs +3 -2
  95. package/api/json/assertResponse.doc.mjs +1 -0
  96. package/api/json/envelope-types.test.mjs +76 -0
  97. package/api/json/index.ts +2 -0
  98. package/api/json/isError.doc.mjs +1 -0
  99. package/api/json/parseResponse.doc.mjs +3 -2
  100. package/api/layout/_adapter.mjs +20 -5
  101. package/api/layout/expand/expand.mjs +7 -5
  102. package/api/layout/expand/expand.path-safety.test.mjs +53 -0
  103. package/api/layout/grammar/grammar.mjs +2 -1
  104. package/api/layout/layoutCheck.doc.mjs +1 -0
  105. package/api/layout/layoutExpand.doc.mjs +2 -1
  106. package/api/layout/layoutGrammar.doc.mjs +1 -0
  107. package/api/search/search-return-type.test.mjs +54 -0
  108. package/api/search/search.d.mts +29 -11
  109. package/api/search/search.doc.mjs +8 -2
  110. package/api/search/search.mjs +337 -41
  111. package/api/search/search.test.mjs +124 -1
  112. package/api/search/search.type.d.mts +15 -3
  113. package/api/search/search.type.mjs +5 -2
  114. package/api/swizzle/copy/copy.mjs +28 -11
  115. package/api/swizzle/swizzle.doc.mjs +2 -1
  116. package/api/swizzle/swizzle.type.d.mts +2 -2
  117. package/api/swizzle/swizzle.type.mjs +2 -2
  118. package/api/template/copy/copy.mjs +17 -23
  119. package/api/template/copy/copy.test.mjs +17 -0
  120. package/api/template/list/list.mjs +1 -0
  121. package/api/template/template-integration.test.mjs +1022 -3
  122. package/api/template/template-suffix.test.mjs +41 -21
  123. package/api/template/template.d.mts +1 -1
  124. package/api/template/template.doc.mjs +30 -8
  125. package/api/template/template.mjs +45 -8
  126. package/api/template/template.type.d.mts +12 -14
  127. package/api/template/template.type.mjs +15 -14
  128. package/api/theme/_adapter.d.mts +2 -3
  129. package/api/theme/_adapter.mjs +4 -5
  130. package/api/theme/add/add.binary.test.mjs +84 -0
  131. package/api/theme/add/add.mjs +20 -3
  132. package/api/theme/add/add.staging.test.mjs +66 -0
  133. package/api/theme/add/add.test.mjs +14 -1
  134. package/api/theme/build/build.mjs +133 -42
  135. package/api/theme/build/build.public-component-vars.test.mjs +1 -1
  136. package/api/theme/build/build.receipt-doc.test.mjs +111 -0
  137. package/api/theme/build/build.test.mjs +127 -0
  138. package/api/theme/build/font-warning.mjs +3 -3
  139. package/api/theme/build/font-warning.test.mjs +5 -2
  140. package/api/theme/generateTonalPalette.doc.mjs +1 -0
  141. package/api/theme/integration-themes.test.mjs +39 -28
  142. package/api/theme/list/list.test.mjs +19 -20
  143. package/api/theme/listThemes.doc.mjs +6 -5
  144. package/api/theme/palette/generate/generate.mjs +8 -3
  145. package/api/theme/palette/generate/generate.test.mjs +96 -0
  146. package/api/theme/palette/generate/generator.d.mts +10 -13
  147. package/api/theme/palette/generate/generator.mjs +15 -4
  148. package/api/theme/palette/generate/generator.test.mjs +10 -0
  149. package/api/theme/template/template.mjs +11 -2
  150. package/api/theme/template/template.test.mjs +20 -0
  151. package/api/theme/theme.type.d.mts +170 -11
  152. package/api/theme/theme.type.mjs +94 -27
  153. package/api/theme/themeAdd.doc.mjs +4 -3
  154. package/api/theme/themeBuild.doc.mjs +8 -4
  155. package/api/theme/themeList.doc.mjs +6 -3
  156. package/api/theme/themeListAvailable.doc.mjs +5 -3
  157. package/api/theme/themePaletteGenerate.doc.mjs +1 -0
  158. package/api/theme/themeTargets.doc.mjs +1 -0
  159. package/api/theme/themeTemplate.doc.mjs +6 -2
  160. package/api/upgrade/_adapter.d.mts +32 -5
  161. package/api/upgrade/_adapter.mjs +139 -22
  162. package/api/upgrade/list/list.mjs +2 -1
  163. package/api/upgrade/list/list.test.mjs +73 -0
  164. package/api/upgrade/project-context.test.mjs +272 -0
  165. package/api/upgrade/provider-agreement.test.mjs +152 -0
  166. package/api/upgrade/run/run.mjs +356 -59
  167. package/api/upgrade/status/status.mjs +2 -2
  168. package/api/upgrade/upgrade.doc.mjs +12 -5
  169. package/api/upgrade/upgrade.type.d.mts +43 -5
  170. package/api/upgrade/upgrade.type.mjs +27 -11
  171. package/assets/codemods/__tests__/runner.test.mjs +330 -8
  172. package/assets/codemods/integration-discovery.mjs +48 -4
  173. package/assets/codemods/integration-discovery.test.mjs +73 -0
  174. package/assets/codemods/integration-runner.mjs +56 -4
  175. package/assets/codemods/integration-runner.protection.test.mjs +153 -0
  176. package/assets/codemods/run-codemod.mjs +177 -34
  177. package/assets/codemods/runner.mjs +350 -102
  178. package/assets/codemods/term-log.mjs +32 -8
  179. package/assets/codemods/term-log.test.mjs +19 -1
  180. package/assets/codemods/transform-prop.mjs +109 -0
  181. package/assets/codemods/transform-prop.test.mjs +95 -0
  182. package/assets/codemods/transforms/next/__tests__/migrate-native-picker-to-presentation.test.mjs +63 -0
  183. package/assets/codemods/transforms/next/__tests__/migrate-theme-catalog-to-descriptors.test.mjs +220 -0
  184. package/assets/codemods/transforms/next/index.mjs +19 -1
  185. package/assets/codemods/transforms/next/migrate-native-picker-to-presentation.mjs +148 -0
  186. package/assets/codemods/transforms/next/migrate-theme-catalog-to-descriptors.mjs +141 -0
  187. package/assets/codemods/transforms/v0.0.14/__tests__/rename-status-variants.test.mjs +86 -165
  188. package/assets/codemods/transforms/v0.0.14/rename-status-variants.mjs +72 -210
  189. package/assets/codemods/transforms/v0.1.8/__tests__/rename-avatar-size-scale.test.mjs +83 -115
  190. package/assets/codemods/transforms/v0.1.8/rename-avatar-size-scale.mjs +57 -186
  191. package/assets/codemods/transforms/v0.3.0/__tests__/unwrap-authoring-factories.test.mjs +27 -5
  192. package/assets/codemods/transforms/v0.3.0/unwrap-authoring-factories.mjs +20 -5
  193. package/assets/codemods/transforms/v0.6.0/__tests__/next-codemods.test.mjs +47 -0
  194. package/assets/codemods/transforms/v0.6.0/rename-resizable-pixel-bounds.mjs +57 -10
  195. package/assets/docs/README.md +9 -0
  196. package/assets/docs/authoring.doc.mjs +14 -0
  197. package/assets/docs/getting-started.doc.mjs +2 -2
  198. package/assets/docs/principles.doc.mjs +6 -6
  199. package/assets/docs/styling-libraries.doc.mjs +3 -3
  200. package/assets/docs/styling.doc.mjs +4 -4
  201. package/assets/docs/theme.doc.mjs +4 -4
  202. package/assets/docs/tokens.doc.mjs +1 -1
  203. package/assets/docs/tree/api.doc.mjs +30 -0
  204. package/assets/docs/tree/cli.doc.mjs +23 -0
  205. package/assets/docs/tree/commands.doc.mjs +25 -0
  206. package/assets/docs/{cli-integrations.doc.mjs → tree/integrations.doc.mjs} +124 -25
  207. package/assets/docs/tree/integrations.test.mjs +62 -0
  208. package/assets/docs/tree/writing-docs.doc.mjs +286 -0
  209. package/assets/docs/working-with-ai.doc.mjs +1 -1
  210. package/assets/templates/blocks/components/DateInput/DateInputDateRange.tsx +1 -1
  211. package/assets/templates/blocks/components/Item/ItemDocumentTabs.doc.mjs +14 -0
  212. package/assets/templates/blocks/components/Item/ItemDocumentTabs.tsx +100 -0
  213. package/assets/templates/blocks/components/ScrollableArea/ScrollableAreaInlineRail.doc.mjs +14 -0
  214. package/assets/templates/blocks/components/ScrollableArea/ScrollableAreaInlineRail.tsx +61 -0
  215. package/assets/templates/blocks/components/ScrollableArea/ScrollableAreaOverscrollChaining.doc.mjs +14 -0
  216. package/assets/templates/blocks/components/ScrollableArea/ScrollableAreaOverscrollChaining.tsx +126 -0
  217. package/assets/templates/blocks/components/ScrollableArea/ScrollableAreaShowcase.doc.mjs +15 -0
  218. package/assets/templates/blocks/components/ScrollableArea/ScrollableAreaShowcase.tsx +86 -0
  219. package/assets/templates/blocks/components/ScrollableArea/ScrollableAreaStickyGroupHeaders.doc.mjs +14 -0
  220. package/assets/templates/blocks/components/ScrollableArea/ScrollableAreaStickyGroupHeaders.tsx +99 -0
  221. package/assets/templates/blocks/components/ScrollableArea/ScrollableAreaStickyPassthrough.doc.mjs +14 -0
  222. package/assets/templates/blocks/components/ScrollableArea/ScrollableAreaStickyPassthrough.tsx +122 -0
  223. package/assets/templates/blocks/components/ScrollableArea/ScrollableAreaTwoAxisBoard.doc.mjs +14 -0
  224. package/assets/templates/blocks/components/ScrollableArea/ScrollableAreaTwoAxisBoard.tsx +95 -0
  225. package/assets/templates/blocks/components/TimeInput/TimeInputConstrained.tsx +1 -0
  226. package/assets/templates/pages/table-tree/page.tsx +1704 -0
  227. package/assets/templates/pages/table-tree/template.doc.mjs +12 -0
  228. package/assets/templates/themes/butter/butterTheme.doc.mjs +11 -0
  229. package/assets/templates/themes/chocolate/chocolateTheme.doc.mjs +11 -0
  230. package/assets/templates/themes/gothic/gothicTheme.doc.mjs +11 -0
  231. package/assets/templates/themes/matcha/matchaTheme.doc.mjs +11 -0
  232. package/assets/templates/themes/neutral/neutralTheme.doc.mjs +11 -0
  233. package/assets/templates/themes/stone/stoneTheme.doc.mjs +11 -0
  234. package/assets/templates/themes/y2k/y2kTheme.doc.mjs +11 -0
  235. package/authoring/_shared/contract.ts +22 -0
  236. package/authoring/codemod/codemod.doc.mjs +7 -2
  237. package/authoring/codemod/parse.d.mts +8 -8
  238. package/authoring/codemod/parse.mjs +8 -6
  239. package/authoring/codemod/type.ts +12 -0
  240. package/authoring/config/config.doc.mjs +2 -2
  241. package/authoring/config/debug-composition.test.mjs +92 -0
  242. package/authoring/config/parse.d.mts +13 -13
  243. package/authoring/config/parse.mjs +8 -8
  244. package/authoring/config/type.ts +18 -6
  245. package/authoring/debug/debug.doc.d.mts +11 -0
  246. package/authoring/debug/debug.doc.mjs +182 -0
  247. package/authoring/debug/parse.d.mts +8 -8
  248. package/authoring/debug/parse.mjs +3 -3
  249. package/authoring/doctypes/_schema.d.mts +718 -23
  250. package/authoring/doctypes/_schema.mjs +519 -39
  251. package/authoring/doctypes/base/graph-fields.doc.d.mts +9 -0
  252. package/authoring/doctypes/base/graph-fields.doc.mjs +64 -0
  253. package/authoring/doctypes/base/type.ts +41 -0
  254. package/authoring/doctypes/command/command.doc.mjs +4 -3
  255. package/authoring/doctypes/command/parse.d.mts +2 -2
  256. package/authoring/doctypes/command/parse.mjs +1 -1
  257. package/authoring/doctypes/command/type.ts +5 -4
  258. package/authoring/doctypes/component/component.doc.mjs +6 -3
  259. package/authoring/doctypes/component/parse.d.mts +2 -2
  260. package/authoring/doctypes/component/parse.mjs +1 -1
  261. package/authoring/doctypes/component/type.ts +6 -5
  262. package/authoring/doctypes/doctypes-new.test.mjs +48 -6
  263. package/authoring/doctypes/enum/enum.doc.mjs +1 -1
  264. package/authoring/doctypes/enum/parse.d.mts +2 -2
  265. package/authoring/doctypes/enum/parse.mjs +1 -1
  266. package/authoring/doctypes/enum/type.ts +4 -2
  267. package/authoring/doctypes/function/function.doc.mjs +7 -2
  268. package/authoring/doctypes/function/parse.d.mts +2 -2
  269. package/authoring/doctypes/function/parse.mjs +1 -1
  270. package/authoring/doctypes/function/type.ts +9 -4
  271. package/authoring/doctypes/hook/hook.doc.mjs +4 -0
  272. package/authoring/doctypes/hook/parse.d.mts +2 -2
  273. package/authoring/doctypes/hook/parse.mjs +1 -1
  274. package/authoring/doctypes/hook/type.ts +5 -4
  275. package/authoring/doctypes/legacy.d.mts +8 -6
  276. package/authoring/doctypes/legacy.mjs +5 -4
  277. package/authoring/doctypes/load-contract.test.mjs +208 -0
  278. package/authoring/doctypes/namespace/namespace.doc.d.mts +9 -0
  279. package/authoring/doctypes/namespace/namespace.doc.mjs +128 -0
  280. package/authoring/doctypes/namespace/parse.d.mts +12 -0
  281. package/authoring/doctypes/namespace/parse.mjs +25 -0
  282. package/authoring/doctypes/namespace/parse.test.mjs +163 -0
  283. package/authoring/doctypes/namespace/type.ts +74 -0
  284. package/authoring/doctypes/parse.d.mts +22 -18
  285. package/authoring/doctypes/parse.mjs +22 -11
  286. package/authoring/doctypes/parse.test.mjs +77 -3
  287. package/authoring/doctypes/reference/parse.d.mts +2 -2
  288. package/authoring/doctypes/reference/parse.mjs +8 -5
  289. package/authoring/doctypes/reference/reference.doc.mjs +20 -5
  290. package/authoring/doctypes/reference/type.ts +53 -5
  291. package/authoring/doctypes/schema/parse.d.mts +2 -2
  292. package/authoring/doctypes/schema/parse.mjs +1 -1
  293. package/authoring/doctypes/schema/schema.doc.mjs +1 -1
  294. package/authoring/doctypes/schema/type.ts +4 -4
  295. package/authoring/doctypes/template/parse.d.mts +94 -1
  296. package/authoring/doctypes/template/parse.mjs +40 -2
  297. package/authoring/doctypes/template/parse.test.mjs +26 -2
  298. package/authoring/doctypes/template/template.doc.mjs +13 -3
  299. package/authoring/doctypes/template/type.ts +13 -2
  300. package/authoring/doctypes/theme/parse.d.mts +35 -0
  301. package/authoring/doctypes/theme/parse.mjs +76 -0
  302. package/authoring/doctypes/theme/theme.doc.d.mts +9 -0
  303. package/authoring/doctypes/theme/theme.doc.mjs +79 -0
  304. package/authoring/doctypes/theme/type.ts +42 -0
  305. package/authoring/doctypes/types.ts +12 -10
  306. package/authoring/gap-report/gap-report.doc.d.mts +12 -0
  307. package/authoring/gap-report/gap-report.doc.mjs +183 -0
  308. package/authoring/gap-report/parse.d.mts +10 -10
  309. package/authoring/gap-report/parse.mjs +6 -6
  310. package/authoring/gap-report/type.ts +1 -1
  311. package/authoring/identity/identity.doc.d.mts +9 -0
  312. package/authoring/identity/identity.doc.mjs +61 -0
  313. package/authoring/identity/type.ts +132 -0
  314. package/authoring/index.d.mts +2 -0
  315. package/authoring/index.d.ts +52 -17
  316. package/authoring/index.mjs +3 -1
  317. package/authoring/integration/integration.doc.mjs +15 -8
  318. package/authoring/integration/parse.d.mts +2 -2
  319. package/authoring/integration/parse.mjs +1 -1
  320. package/authoring/integration/parse.test.mjs +10 -1
  321. package/authoring/integration/schema.d.mts +6 -4
  322. package/authoring/integration/schema.mjs +9 -3
  323. package/authoring/integration/type.ts +19 -8
  324. package/authoring/shadcn/receipt.d.mts +6 -6
  325. package/clients/cli/__tests__/cliManifest.test.ts +27 -29
  326. package/clients/cli/command-load-failure.test.mjs +83 -0
  327. package/clients/cli/commands/blog.doc.mjs +1 -1
  328. package/clients/cli/commands/blog.mjs +23 -8
  329. package/clients/cli/commands/blog.test.mjs +42 -1
  330. package/clients/cli/commands/build-theme.adaptations.test.mjs +100 -1
  331. package/clients/cli/commands/build-theme.ascii-output.test.mjs +161 -0
  332. package/clients/cli/commands/build-theme.flag-docs.test.mjs +110 -0
  333. package/clients/cli/commands/build-theme.mjs +16 -50
  334. package/clients/cli/commands/build-theme.path-safety.test.mjs +86 -1
  335. package/clients/cli/commands/build-theme.variants.test.mjs +76 -0
  336. package/clients/cli/commands/build.doc.mjs +5 -2
  337. package/clients/cli/commands/build.exit-codes-doc.test.mjs +50 -0
  338. package/clients/cli/commands/build.mjs +37 -49
  339. package/clients/cli/commands/build.playbook.test.mjs +75 -0
  340. package/clients/cli/commands/build.text-fields.test.mjs +40 -0
  341. package/clients/cli/commands/component/index.mjs +3 -8
  342. package/clients/cli/commands/component-ownership.test.mjs +3 -3
  343. package/clients/cli/commands/component-package.test.mjs +46 -0
  344. package/clients/cli/commands/component-resolution.test.mjs +21 -0
  345. package/clients/cli/commands/component.doc.mjs +1 -1
  346. package/clients/cli/commands/component.test.mjs +19 -0
  347. package/clients/cli/commands/detail-levels.test.mjs +2 -2
  348. package/clients/cli/commands/discover.broken-integration.test.mjs +52 -5
  349. package/clients/cli/commands/discover.components-flag.test.mjs +97 -0
  350. package/clients/cli/commands/discover.doc.mjs +5 -3
  351. package/clients/cli/commands/discover.mjs +4 -4
  352. package/clients/cli/commands/discover.text-projection.test.mjs +103 -0
  353. package/clients/cli/commands/docs.doc.mjs +28 -6
  354. package/clients/cli/commands/docs.mjs +238 -24
  355. package/clients/cli/commands/docs.test.mjs +193 -1
  356. package/clients/cli/commands/doctor-integration-components.doc.mjs +1 -1
  357. package/clients/cli/commands/doctor-integration-docs.doc.mjs +3 -3
  358. package/clients/cli/commands/doctor-integration-templates.doc.mjs +15 -8
  359. package/clients/cli/commands/doctor-integration-validate.doc.mjs +1 -1
  360. package/clients/cli/commands/doctor-integration.doc.mjs +1 -1
  361. package/clients/cli/commands/doctor-integration.package-json.test.mjs +53 -0
  362. package/clients/cli/commands/doctor-integration.test.mjs +83 -8
  363. package/clients/cli/commands/doctor.doc.mjs +1 -1
  364. package/clients/cli/commands/doctor.mjs +30 -25
  365. package/clients/cli/commands/doctor.test.mjs +42 -0
  366. package/clients/cli/commands/gap-report.doc.mjs +17 -6
  367. package/clients/cli/commands/gap-report.test.mjs +72 -0
  368. package/clients/cli/commands/hook/index.mjs +7 -17
  369. package/clients/cli/commands/hook.doc.mjs +1 -1
  370. package/clients/cli/commands/hook.text-projection.test.mjs +45 -0
  371. package/clients/cli/commands/init.doc.mjs +20 -9
  372. package/clients/cli/commands/init.flag-help.test.mjs +153 -0
  373. package/clients/cli/commands/integration-add.controls.test.mjs +132 -0
  374. package/clients/cli/commands/integration-add.doc.mjs +32 -6
  375. package/clients/cli/commands/integration-authoring.test.mjs +13 -9
  376. package/clients/cli/commands/integration-pack.doc.mjs +1 -1
  377. package/clients/cli/commands/integration-real-world.test.mjs +3 -9
  378. package/clients/cli/commands/integration.doc.mjs +1 -1
  379. package/clients/cli/commands/integration.mjs +1 -0
  380. package/clients/cli/commands/interactive-guard.test.mjs +101 -24
  381. package/clients/cli/commands/json-contract.test.mjs +33 -0
  382. package/clients/cli/commands/layout-check.doc.mjs +15 -4
  383. package/clients/cli/commands/layout-expand.doc.mjs +22 -5
  384. package/clients/cli/commands/layout-grammar.doc.mjs +1 -1
  385. package/clients/cli/commands/layout.doc.mjs +3 -3
  386. package/clients/cli/commands/layout.mjs +21 -9
  387. package/clients/cli/commands/layout.path-help.test.mjs +33 -0
  388. package/clients/cli/commands/layout.stdin-cap.test.mjs +47 -0
  389. package/clients/cli/commands/layout.text-fields.test.mjs +39 -0
  390. package/clients/cli/commands/manifest.doc.mjs +1 -1
  391. package/clients/cli/commands/no-prompt-wording.test.mjs +94 -0
  392. package/clients/cli/commands/search.doc.mjs +7 -4
  393. package/clients/cli/commands/search.mjs +28 -9
  394. package/clients/cli/commands/search.test.mjs +75 -0
  395. package/clients/cli/commands/setup-nudge.test.mjs +6 -0
  396. package/clients/cli/commands/swizzle.doc.mjs +3 -2
  397. package/clients/cli/commands/swizzle.path-safety.test.mjs +42 -0
  398. package/clients/cli/commands/template.doc.mjs +52 -13
  399. package/clients/cli/commands/template.flag-help.test.mjs +117 -0
  400. package/clients/cli/commands/template.mjs +4 -91
  401. package/clients/cli/commands/template.path-help.test.mjs +40 -0
  402. package/clients/cli/commands/text-json-parity.test.mjs +719 -0
  403. package/clients/cli/commands/theme-add.doc.mjs +4 -3
  404. package/clients/cli/commands/theme-build.doc.mjs +8 -7
  405. package/clients/cli/commands/theme-list.doc.mjs +2 -2
  406. package/clients/cli/commands/theme-palette-generate.doc.mjs +9 -5
  407. package/clients/cli/commands/theme-palette-generate.test.mjs +19 -0
  408. package/clients/cli/commands/theme-palette.doc.mjs +1 -1
  409. package/clients/cli/commands/theme-targets.behavior.test.mjs +4 -3
  410. package/clients/cli/commands/theme-targets.doc.mjs +1 -1
  411. package/clients/cli/commands/theme-template.behavior.test.mjs +12 -0
  412. package/clients/cli/commands/theme-template.doc.mjs +2 -2
  413. package/clients/cli/commands/theme.doc.mjs +1 -1
  414. package/clients/cli/commands/upgrade.ascii-output.test.mjs +87 -0
  415. package/clients/cli/commands/upgrade.doc.mjs +22 -10
  416. package/clients/cli/commands/upgrade.file-protection.test.mjs +228 -0
  417. package/clients/cli/commands/upgrade.flag-help.test.mjs +188 -0
  418. package/clients/cli/commands/upgrade.hook-output.test.mjs +88 -0
  419. package/clients/cli/commands/upgrade.mjs +29 -7
  420. package/clients/cli/formatters/index.mjs +164 -1
  421. package/clients/cli/formatters/index.test.mjs +97 -0
  422. package/clients/cli/index.mjs +21 -30
  423. package/clients/cli/latest-version-env.test.mjs +50 -0
  424. package/clients/cli/lib/cli-error.test.mjs +7 -0
  425. package/clients/cli/lib/component-format.mjs +9 -9
  426. package/clients/cli/lib/component-format.test.mjs +1 -1
  427. package/clients/cli/lib/define-command.mjs +32 -6
  428. package/clients/cli/lib/doc-text-ascii.test.mjs +82 -0
  429. package/clients/cli/lib/exit-codes.test.mjs +97 -0
  430. package/clients/cli/lib/hook-format.mjs +19 -10
  431. package/clients/cli/lib/json-shim.mjs +38 -2
  432. package/clients/cli/lib/json-shim.test.mjs +83 -0
  433. package/clients/cli/lib/manifest.d.ts +2 -0
  434. package/clients/cli/lib/manifest.mjs +36 -2
  435. package/clients/cli/lib/manifest.test.mjs +17 -0
  436. package/foundation/agent-docs/agent-docs.d.mts +4 -0
  437. package/foundation/agent-docs/agent-docs.mjs +70 -3
  438. package/foundation/agent-docs/agent-docs.path-safety.test.mjs +266 -4
  439. package/foundation/agent-docs/agent-docs.test.mjs +10 -1
  440. package/foundation/config/integration-debug.test.mjs +28 -3
  441. package/foundation/config/project-themes.test.mjs +11 -19
  442. package/foundation/config/project.d.mts +20 -11
  443. package/foundation/config/project.mjs +263 -91
  444. package/foundation/config/project.test.mjs +270 -21
  445. package/foundation/discovery/authoring-self-docs.d.mts +75 -0
  446. package/foundation/discovery/authoring-self-docs.mjs +224 -0
  447. package/foundation/discovery/authoring-self-docs.test.mjs +165 -0
  448. package/foundation/discovery/authoring-surface.d.mts +74 -0
  449. package/foundation/discovery/authoring-surface.mjs +525 -0
  450. package/foundation/discovery/authoring-surface.test.mjs +392 -0
  451. package/foundation/discovery/cli-self-docs.d.mts +119 -0
  452. package/foundation/discovery/cli-self-docs.mjs +490 -0
  453. package/foundation/discovery/cli-self-docs.test.mjs +375 -0
  454. package/foundation/discovery/component-discovery.d.mts +39 -1
  455. package/foundation/discovery/component-discovery.mjs +50 -1
  456. package/foundation/discovery/component-loader.d.mts +35 -38
  457. package/foundation/discovery/component-loader.mjs +53 -222
  458. package/foundation/discovery/docs-discovery.d.mts +116 -11
  459. package/foundation/discovery/docs-discovery.mjs +389 -108
  460. package/foundation/discovery/docs-discovery.test.mjs +325 -20
  461. package/foundation/discovery/docs-output-budget.d.mts +28 -0
  462. package/foundation/discovery/docs-output-budget.mjs +50 -0
  463. package/foundation/discovery/docs-section-key.d.mts +116 -0
  464. package/foundation/discovery/docs-section-key.mjs +322 -0
  465. package/foundation/discovery/docs-section-key.test.mjs +246 -0
  466. package/foundation/discovery/template-adapter.d.mts +113 -11
  467. package/foundation/discovery/template-adapter.fixture-refs.test.mjs +248 -0
  468. package/foundation/discovery/template-adapter.integration-isolation.test.mjs +94 -0
  469. package/foundation/discovery/template-adapter.mjs +774 -83
  470. package/foundation/discovery/template-adapter.test.mjs +57 -0
  471. package/foundation/discovery/theme-discovery.d.mts +67 -7
  472. package/foundation/discovery/theme-discovery.mjs +916 -186
  473. package/foundation/discovery/theme-discovery.test.mjs +613 -219
  474. package/foundation/discovery/theming-targets.test.mjs +4 -0
  475. package/foundation/doc-compiler/bundle.d.mts +47 -0
  476. package/foundation/doc-compiler/bundle.mjs +278 -0
  477. package/foundation/doc-compiler/bundle.test.mjs +266 -0
  478. package/foundation/doc-compiler/compile.d.mts +343 -0
  479. package/foundation/doc-compiler/compile.mjs +558 -0
  480. package/foundation/doc-compiler/diagnostics.d.mts +126 -0
  481. package/foundation/doc-compiler/diagnostics.mjs +305 -0
  482. package/foundation/doc-compiler/doc-compiler.test.mjs +714 -0
  483. package/foundation/doc-compiler/doc-loads.test.mjs +1642 -0
  484. package/foundation/doc-compiler/import.d.mts +24 -0
  485. package/foundation/doc-compiler/import.mjs +59 -0
  486. package/foundation/doc-compiler/inputs.d.mts +102 -0
  487. package/foundation/doc-compiler/inputs.mjs +291 -0
  488. package/foundation/doc-compiler/inputs.test.mjs +299 -0
  489. package/foundation/doc-compiler/ir.d.mts +22 -0
  490. package/foundation/doc-compiler/ir.mjs +471 -0
  491. package/foundation/doc-compiler/lenses.d.mts +34 -0
  492. package/foundation/doc-compiler/lenses.mjs +128 -0
  493. package/foundation/doc-compiler/links.d.mts +135 -0
  494. package/foundation/doc-compiler/links.mjs +253 -0
  495. package/foundation/doc-compiler/links.test.mjs +139 -0
  496. package/foundation/doc-compiler/lower-doc.test.mjs +495 -0
  497. package/foundation/doc-compiler/overlays.d.mts +37 -0
  498. package/foundation/doc-compiler/overlays.mjs +206 -0
  499. package/foundation/doc-compiler/parse-readable.d.mts +9 -0
  500. package/foundation/doc-compiler/parse-readable.mjs +29 -0
  501. package/foundation/doc-compiler/read.d.mts +127 -0
  502. package/foundation/doc-compiler/read.mjs +325 -0
  503. package/foundation/doc-compiler/read.test.mjs +313 -0
  504. package/foundation/doc-compiler/source.d.mts +33 -0
  505. package/foundation/doc-compiler/source.mjs +128 -0
  506. package/foundation/doc-compiler/tree.d.mts +288 -0
  507. package/foundation/doc-compiler/tree.mjs +876 -0
  508. package/foundation/doc-compiler/tree.test.mjs +598 -0
  509. package/foundation/fs/file-protection.d.mts +33 -0
  510. package/foundation/fs/file-protection.mjs +825 -0
  511. package/foundation/fs/file-protection.test.mjs +250 -0
  512. package/foundation/fs/module-loader.d.mts +1 -0
  513. package/foundation/fs/module-loader.mjs +50 -1
  514. package/foundation/fs/module-loader.stdout.test.mjs +332 -0
  515. package/foundation/fs/path-safety.d.mts +3 -2
  516. package/foundation/fs/path-safety.mjs +49 -19
  517. package/foundation/fs/path-safety.test.mjs +50 -0
  518. package/foundation/fs/publish-file-hardlink-unavailable.test.mjs +129 -94
  519. package/foundation/identity/provider-identity.d.mts +90 -0
  520. package/foundation/identity/provider-identity.mjs +320 -0
  521. package/foundation/identity/provider-identity.test.mjs +254 -0
  522. package/foundation/identity/providers.d.mts +7 -0
  523. package/foundation/identity/providers.mjs +16 -0
  524. package/foundation/integrations/autolink.d.mts +58 -1
  525. package/foundation/integrations/autolink.mjs +143 -45
  526. package/foundation/integrations/autolink.test.mjs +1 -1
  527. package/foundation/integrations/cli-requirement.d.mts +45 -0
  528. package/foundation/integrations/cli-requirement.mjs +154 -0
  529. package/foundation/integrations/cli-requirement.test.mjs +109 -0
  530. package/foundation/integrations/contribution-fixes.d.mts +145 -0
  531. package/foundation/integrations/contribution-fixes.mjs +1284 -0
  532. package/foundation/integrations/contribution-inventory.d.mts +3 -2
  533. package/foundation/integrations/contribution-inventory.mjs +27 -24
  534. package/foundation/integrations/contribution-inventory.test.mjs +86 -27
  535. package/foundation/integrations/integration-warnings.d.mts +9 -2
  536. package/foundation/integrations/integration-warnings.mjs +52 -21
  537. package/foundation/integrations/integration-warnings.test.mjs +74 -1
  538. package/foundation/integrations/integrations.d.mts +49 -2
  539. package/foundation/integrations/integrations.mjs +81 -8
  540. package/foundation/integrations/integrations.test.mjs +384 -1
  541. package/foundation/integrations/provider-conflicts.test.mjs +125 -0
  542. package/foundation/integrations/provider-ledger.test.mjs +275 -0
  543. package/foundation/integrations/provider-resolution.d.mts +152 -0
  544. package/foundation/integrations/provider-resolution.mjs +576 -0
  545. package/foundation/integrations/provider-resolution.test.mjs +369 -0
  546. package/foundation/integrations/theme-descriptor.d.mts +8 -0
  547. package/foundation/integrations/theme-descriptor.mjs +44 -0
  548. package/foundation/integrations/validate-contributions.d.mts +2 -0
  549. package/foundation/integrations/validate-contributions.mjs +131 -29
  550. package/foundation/response/base.d.ts +8 -4
  551. package/foundation/response/error-codes.d.mts +3 -1
  552. package/foundation/response/error-codes.d.ts +2 -0
  553. package/foundation/response/error-codes.doc.mjs +13 -4
  554. package/foundation/response/error-codes.mjs +8 -2
  555. package/foundation/response/error-codes.test.mjs +137 -10
  556. package/foundation/response/json-contract.test.mjs +57 -17
  557. package/foundation/response/json.d.mts +4 -2
  558. package/foundation/response/json.mjs +8 -10
  559. package/foundation/response/response-types.doc.mjs +26 -16
  560. package/foundation/response/response-types.doc.test.mjs +158 -0
  561. package/foundation/response/response.doc.mjs +1 -1
  562. package/foundation/text/string-utils.mjs +22 -10
  563. package/foundation/xle/expand.d.mts +2 -0
  564. package/foundation/xle/expand.mjs +4 -3
  565. package/foundation/xle/expand.test.mjs +54 -0
  566. package/foundation/xle/xle.test.mjs +13 -0
  567. package/package.json +10 -11
  568. package/assets/templates/themes/manifest.json +0 -95
  569. package/clients/cli/lib/update-check.mjs +0 -83
  570. package/clients/cli/lib/update-check.test.mjs +0 -137
  571. package/clients/cli/update-hint-commands.test.mjs +0 -54
@@ -0,0 +1,66 @@
1
+ // Copyright (c) Meta Platforms, Inc. and affiliates.
2
+
3
+ import {afterEach, beforeEach, describe, expect, it} from 'vitest';
4
+ import * as fs from 'node:fs';
5
+ import * as os from 'node:os';
6
+ import * as path from 'node:path';
7
+ import {themeAdd} from './add.mjs';
8
+ import {listThemes} from '../_adapter.mjs';
9
+ import {isErrorCode} from '../../../foundation/response/error-codes.mjs';
10
+
11
+ let tmpDir;
12
+ let outsideDir;
13
+
14
+ beforeEach(() => {
15
+ tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'astryx-themeadd-staging-'));
16
+ outsideDir = fs.mkdtempSync(path.join(os.tmpdir(), 'astryx-themeadd-outside-'));
17
+ });
18
+
19
+ afterEach(() => {
20
+ fs.rmSync(tmpDir, {recursive: true, force: true});
21
+ fs.rmSync(outsideDir, {recursive: true, force: true});
22
+ });
23
+
24
+ /**
25
+ * Put a symlink where `theme add` stages the first file of `slug`.
26
+ * @param {string} slug
27
+ * @param {string} target
28
+ */
29
+ function plantStagingLink(slug, target) {
30
+ const theme = listThemes().find(entry => entry.slug === slug);
31
+ if (!theme) throw new Error(`missing bundled theme ${slug}`);
32
+ const first = theme.files[0];
33
+ const dest = path.join(tmpDir, 'src', 'themes', slug, first);
34
+ fs.mkdirSync(path.dirname(dest), {recursive: true});
35
+ fs.symlinkSync(target, `${dest}.${process.pid}.tmp`);
36
+ return dest;
37
+ }
38
+
39
+ describe('themeAdd staging writes stay inside the project', () => {
40
+ it('refuses a staging path that links outside the project', async () => {
41
+ const victim = path.join(outsideDir, 'victim.txt');
42
+ fs.writeFileSync(victim, 'outside\n');
43
+ const dest = plantStagingLink('stone', victim);
44
+
45
+ await expect(themeAdd('stone', {cwd: tmpDir})).rejects.toMatchObject({
46
+ code: 'ERR_PATH_TRAVERSAL',
47
+ });
48
+ expect(fs.readFileSync(victim, 'utf-8')).toBe('outside\n');
49
+ expect(fs.existsSync(dest)).toBe(false);
50
+ });
51
+
52
+ it('never creates a file through a dangling staging link', async () => {
53
+ const victim = path.join(outsideDir, 'created.txt');
54
+ plantStagingLink('stone', victim);
55
+
56
+ let error;
57
+ try {
58
+ await themeAdd('stone', {cwd: tmpDir});
59
+ } catch (caught) {
60
+ error = caught;
61
+ }
62
+
63
+ expect(isErrorCode(error?.code)).toBe(true);
64
+ expect(fs.existsSync(victim)).toBe(false);
65
+ });
66
+ });
@@ -30,7 +30,20 @@ describe('themeAdd (api/theme/add)', () => {
30
30
  expect(result.data.slug).toBe('neutral');
31
31
  expect(result.data.package).toBe('@astryxdesign/cli');
32
32
  expect(result.data.outputDir).toBe(path.join('src', 'themes', 'neutral'));
33
- expect(result.data.files.length).toBeGreaterThan(0);
33
+ // Exactly what the bundle has always copied, in that order: the CLI's own
34
+ // descriptor stays behind.
35
+ expect(result.data.files).toEqual([
36
+ 'neutralTheme.ts',
37
+ 'icons.tsx',
38
+ 'neutralPalettes.ts',
39
+ 'neutralPalettes.generated.ts',
40
+ 'neutralPaletteRefs.generated.ts',
41
+ 'neutralPalettes.generated.receipt.json',
42
+ 'palette.config.json',
43
+ ]);
44
+ expect(
45
+ fs.readdirSync(path.join(tmpDir, 'src', 'themes', 'neutral')).sort(),
46
+ ).toEqual([...result.data.files].sort());
34
47
  for (const f of result.data.files) {
35
48
  expect(
36
49
  fs.existsSync(path.join(tmpDir, 'src', 'themes', 'neutral', f)),
@@ -12,9 +12,10 @@
12
12
  * - A .d.ts (plus an optional .variants.d.ts for custom prop values)
13
13
  *
14
14
  * It performs the writes and returns a `theme.build` receipt — its `warnings`
15
- * carry override problems and any fonts the theme names but does not load
16
- * (font-warning.mjs) — or `null` when the theme produced no CSS (nothing to
17
- * build). Errors throw AstryxError (with
15
+ * carry override problems, every declaration core's generator dropped because
16
+ * its value could not stay one CSS declaration, and any fonts the theme names
17
+ * but does not load (font-warning.mjs) — or `null` when the theme produced no
18
+ * CSS (nothing to build). Errors throw AstryxError (with
18
19
  * a stable code). Human progress is emitted through the shared `logger`
19
20
  * (silent by default), so the CLI keeps its exact output while a programmatic
20
21
  * caller stays quiet.
@@ -223,10 +224,10 @@ function staleBuildOutputs(writes, cwd) {
223
224
  /** @param {Array<{dest: string, content: string}>} writes */
224
225
  function writeBuildOutputs(writes) {
225
226
  if (writes.length === 0) return;
226
- fs.mkdirSync(path.dirname(writes[0].dest), {recursive: true});
227
227
  /** @type {Array<{tmp: string, dest: string}>} */
228
228
  const staged = [];
229
229
  try {
230
+ fs.mkdirSync(path.dirname(writes[0].dest), {recursive: true});
230
231
  for (const write of writes) {
231
232
  const tmp = `${write.dest}.${process.pid}.tmp`;
232
233
  fs.writeFileSync(tmp, write.content);
@@ -1870,7 +1871,7 @@ function validatePrivateVars(themeDef) {
1870
1871
  if (prop.startsWith('--_')) {
1871
1872
  errors.push(
1872
1873
  `Component "${component}" (${[key, ...path].join(' ')}) sets private var "${prop}". ` +
1873
- `Private vars (--_*) are internal — use standard CSS properties ` +
1874
+ `Private vars (--_*) are internal; use standard CSS properties ` +
1874
1875
  `(e.g. borderRadius, padding) instead. The pipeline expands them automatically.`,
1875
1876
  );
1876
1877
  }
@@ -1930,20 +1931,7 @@ function getThemeComponentLayers(themeDef) {
1930
1931
  */
1931
1932
  function validateCustomHeadingTypes(themeDef) {
1932
1933
  const layers = getThemeComponentLayers(themeDef);
1933
- const customTypes = new Set();
1934
- for (const {components} of layers) {
1935
- const headingRules = components.heading;
1936
- if (!headingRules || typeof headingRules !== 'object') continue;
1937
-
1938
- for (const key of Object.keys(headingRules)) {
1939
- for (const pair of key.split('+')) {
1940
- const colon = pair.indexOf(':');
1941
- if (colon === -1 || pair.slice(0, colon) !== 'type') continue;
1942
- const value = pair.slice(colon + 1);
1943
- if (value && !BUILTIN_HEADING_TYPES.has(value)) customTypes.add(value);
1944
- }
1945
- }
1946
- }
1934
+ const customTypes = customHeadingTypes(themeDef);
1947
1935
 
1948
1936
  const errors = [];
1949
1937
  for (const type of customTypes) {
@@ -1970,10 +1958,65 @@ function validateCustomHeadingTypes(themeDef) {
1970
1958
  return errors;
1971
1959
  }
1972
1960
 
1961
+ /**
1962
+ * Custom Heading type names declared on the root or media-surface component
1963
+ * maps: the names the generated `HeadingTypeMap` augmentation enrolls.
1964
+ *
1965
+ * @param {{components?: Record<string, Record<string, unknown>>}} themeDef
1966
+ * @returns {Set<string>}
1967
+ */
1968
+ function customHeadingTypes(themeDef) {
1969
+ /** @type {Set<string>} */
1970
+ const customTypes = new Set();
1971
+ for (const {components} of getThemeComponentLayers(themeDef)) {
1972
+ const headingRules = components.heading;
1973
+ if (!headingRules || typeof headingRules !== 'object') continue;
1974
+
1975
+ for (const key of Object.keys(headingRules)) {
1976
+ for (const pair of key.split('+')) {
1977
+ const colon = pair.indexOf(':');
1978
+ if (colon === -1 || pair.slice(0, colon) !== 'type') continue;
1979
+ const value = pair.slice(colon + 1);
1980
+ if (value && !BUILTIN_HEADING_TYPES.has(value)) customTypes.add(value);
1981
+ }
1982
+ }
1983
+ }
1984
+ return customTypes;
1985
+ }
1986
+
1987
+ /**
1988
+ * Custom Heading types with no standalone rule in the generated CSS. A type
1989
+ * whose every declaration the compiler dropped still passes the source check,
1990
+ * and its augmentation would then name a type that has no styles.
1991
+ *
1992
+ * @param {Set<string>} types
1993
+ * @param {string} css - Generated root component and media-surface rules.
1994
+ * @returns {string[]}
1995
+ */
1996
+ function headingTypesWithoutEmittedRule(types, css) {
1997
+ /** @type {string[]} */
1998
+ const missing = [];
1999
+ for (const type of types) {
2000
+ const selector = `.astryx-heading[data-type="${type}"]`;
2001
+ let emitted = false;
2002
+ for (
2003
+ let at = css.indexOf(selector);
2004
+ at !== -1 && !emitted;
2005
+ at = css.indexOf(selector, at + 1)
2006
+ ) {
2007
+ // A following attribute is a combined selector, not the standalone rule.
2008
+ emitted = css[at + selector.length] !== '[';
2009
+ }
2010
+ if (!emitted) missing.push(type);
2011
+ }
2012
+ return missing;
2013
+ }
2014
+
1973
2015
  /**
1974
2016
  * Return true when a style object contains a declaration that the theme
1975
- * generator can emit. Empty pseudo blocks do not count: they produce no CSS
1976
- * and would leave the generated type augmentation without a usable rule.
2017
+ * generator can emit. Empty pseudo blocks and blank values do not count: they
2018
+ * produce no usable CSS and would leave the generated type augmentation
2019
+ * without a usable rule.
1977
2020
  *
1978
2021
  * @param {unknown} styles
1979
2022
  * @returns {boolean}
@@ -1987,7 +2030,12 @@ function hasUsableStyleDeclaration(styles) {
1987
2030
  if (hasUsableStyleDeclaration(value)) return true;
1988
2031
  continue;
1989
2032
  }
1990
- if (value !== undefined && value !== null && typeof value !== 'object') {
2033
+ if (
2034
+ value !== undefined &&
2035
+ value !== null &&
2036
+ typeof value !== 'object' &&
2037
+ String(value).trim() !== ''
2038
+ ) {
1991
2039
  return true;
1992
2040
  }
1993
2041
  }
@@ -2162,14 +2210,14 @@ async function themeBuildInternal(
2162
2210
  const noticeMessages = [];
2163
2211
  for (const w of warnings) {
2164
2212
  warningMessages.push(w);
2165
- logger.warn(` ⚠ ${w}`);
2213
+ logger.warn(` [warn] ${w}`);
2166
2214
  }
2167
2215
 
2168
2216
  // Validate no private vars are set directly
2169
2217
  const privateVarErrors = validatePrivateVars(themeDef);
2170
2218
  for (const e of privateVarErrors) {
2171
2219
  warningMessages.push(e);
2172
- logger.error(` ✗ ${e}`);
2220
+ logger.error(` [error] ${e}`);
2173
2221
  }
2174
2222
  if (privateVarErrors.length > 0) {
2175
2223
  logger.error(
@@ -2195,7 +2243,7 @@ async function themeBuildInternal(
2195
2243
  // capability exports are checked against what the theme actually asks for.
2196
2244
  if (!_defineTheme || !_generateThemeRulesSplit) {
2197
2245
  throw new AstryxError(
2198
- 'Could not load @astryxdesign/core/theme — `astryx theme build` requires a ' +
2246
+ 'Could not load @astryxdesign/core/theme: `astryx theme build` requires a ' +
2199
2247
  'built, resolvable @astryxdesign/core so it emits the same CSS as the ' +
2200
2248
  'runtime <Theme>. Build @astryxdesign/core first (e.g. `pnpm -F @astryxdesign/core ' +
2201
2249
  'build`)' +
@@ -2303,7 +2351,15 @@ async function themeBuildInternal(
2303
2351
  const scopeSelector = themeScopeStart(themeDef.name);
2304
2352
  const scopeTo = THEME_SCOPE_TO;
2305
2353
 
2306
- const {component, prose} = _generateThemeRulesSplit(resolvedTheme);
2354
+ // Older cores ignore this optional collector. Current core writes the same
2355
+ // warning text here that runtime callers receive on the console.
2356
+ /** @type {string[]} */
2357
+ const droppedDeclarations = [];
2358
+
2359
+ const {component, prose} = _generateThemeRulesSplit(
2360
+ resolvedTheme,
2361
+ droppedDeclarations,
2362
+ );
2307
2363
  const cssParts = [];
2308
2364
  // Prose element defaults always ship — the `<Theme>` runtime
2309
2365
  // (generateThemeCSS) always emits them, so the build must too, or the
@@ -2322,7 +2378,7 @@ async function themeBuildInternal(
2322
2378
  let adaptationCss;
2323
2379
  try {
2324
2380
  adaptationCss = _generateAdaptationCSS
2325
- ? _generateAdaptationCSS(resolvedTheme)
2381
+ ? _generateAdaptationCSS(resolvedTheme, droppedDeclarations)
2326
2382
  : {component: '', prose: ''};
2327
2383
  } catch (error) {
2328
2384
  const message =
@@ -2368,13 +2424,37 @@ async function themeBuildInternal(
2368
2424
  // adaptations on the same resolved leaf.
2369
2425
  let onMediaCss = '';
2370
2426
  if (_generateOnMediaCSS) {
2371
- onMediaCss = _generateOnMediaCSS(resolvedTheme);
2427
+ onMediaCss = _generateOnMediaCSS(resolvedTheme, droppedDeclarations);
2372
2428
  if (onMediaCss) {
2373
2429
  cssParts.push(`@layer astryx-theme {\n${onMediaCss}\n}`);
2374
2430
  }
2375
2431
  }
2432
+ for (const message of droppedDeclarations) {
2433
+ const w = `Declaration ${message}. The generated CSS omits it; fix the value in the theme source.`;
2434
+ warningMessages.push(w);
2435
+ logger.warn(` [warn] ${w}`);
2436
+ }
2437
+ const unemittedHeadingTypes = headingTypesWithoutEmittedRule(
2438
+ customHeadingTypes(themeDef),
2439
+ `${component.join('\n')}\n${onMediaCss}`,
2440
+ );
2441
+ if (unemittedHeadingTypes.length > 0) {
2442
+ throw new AstryxError(
2443
+ unemittedHeadingTypes
2444
+ .map(
2445
+ type =>
2446
+ `Custom Heading type "${type}" has no declaration the theme ` +
2447
+ `compiler could emit: every declaration in its standalone ` +
2448
+ `"type:${type}" rule was dropped. Give that rule at least one ` +
2449
+ 'valid declaration.',
2450
+ )
2451
+ .join('\n'),
2452
+ undefined,
2453
+ ERROR_CODES.ERR_THEME_INVALID,
2454
+ );
2455
+ }
2376
2456
  if (cssParts.length === 0) {
2377
- logger.log('No overrides found — nothing to build.');
2457
+ logger.log('No overrides found; nothing to build.');
2378
2458
  return null;
2379
2459
  }
2380
2460
  // The data-token defaults are theme-independent and go in @layer
@@ -2425,7 +2505,18 @@ async function themeBuildInternal(
2425
2505
  // Guard: relative paths must not escape cwd via `../`. Absolute paths are
2426
2506
  // trusted (the user explicitly controls where output goes, like gcc -o).
2427
2507
  if (!path.isAbsolute(options.out)) {
2428
- assertWithin(options.out, cwd, {label: 'output path'});
2508
+ try {
2509
+ assertWithin(options.out, cwd, {label: 'output path'});
2510
+ } catch (err) {
2511
+ if (err instanceof PathSafetyError) {
2512
+ throw new AstryxError(
2513
+ err.message,
2514
+ undefined,
2515
+ ERROR_CODES.ERR_PATH_TRAVERSAL,
2516
+ );
2517
+ }
2518
+ throw err;
2519
+ }
2429
2520
  }
2430
2521
  } else {
2431
2522
  outPath = path.join(path.dirname(filePath), `${baseName}.css`);
@@ -2534,10 +2625,10 @@ async function themeBuildInternal(
2534
2625
  const stale = staleBuildOutputs(writes, cwd);
2535
2626
  const upToDate = stale.length === 0;
2536
2627
  if (upToDate) {
2537
- logger.log(`\n✓ Theme outputs are up to date with ${sourceRelative}.`);
2628
+ logger.log(`\n[ok] Theme outputs are up to date with ${sourceRelative}.`);
2538
2629
  } else {
2539
2630
  logger.error(
2540
- `\n✗ ${stale.length} theme output(s) are out of date with ${sourceRelative}:`,
2631
+ `\n[fail] ${stale.length} theme output(s) are out of date with ${sourceRelative}:`,
2541
2632
  );
2542
2633
  for (const s of stale) {
2543
2634
  logger.error(
@@ -2559,17 +2650,17 @@ async function themeBuildInternal(
2559
2650
 
2560
2651
  writeBuildOutputs(writes);
2561
2652
 
2562
- logger.log(`\n✓ ${path.relative(cwd, outPath)}`);
2653
+ logger.log(`\n[ok] ${path.relative(cwd, outPath)}`);
2563
2654
  logger.log(
2564
2655
  ` ${tokenCount} token overrides, ${componentCount} component overrides`,
2565
2656
  );
2566
2657
  logger.log(` ${size} KB`);
2567
- logger.log(`✓ ${path.relative(cwd, jsPath)}`);
2568
- logger.log(`✓ ${path.relative(cwd, dtsPath)}`);
2658
+ logger.log(`[ok] ${path.relative(cwd, jsPath)}`);
2659
+ logger.log(`[ok] ${path.relative(cwd, dtsPath)}`);
2569
2660
  if (variantDtsPath && variantDecl) {
2570
2661
  const augCount = (variantDecl.match(/': true;/g) || []).length;
2571
2662
  logger.log(
2572
- `✓ ${path.relative(cwd, variantDtsPath)} (${augCount} type augmentations)`,
2663
+ `[ok] ${path.relative(cwd, variantDtsPath)} (${augCount} type augmentations)`,
2573
2664
  );
2574
2665
  }
2575
2666
 
@@ -2579,7 +2670,7 @@ async function themeBuildInternal(
2579
2670
  const cssImport = importSpecifier(relOutDir, cssBase) + '.css';
2580
2671
  const exportName = `${toIdentifier(baseName)}Theme`;
2581
2672
  logger.log(`
2582
- Install in your app (paths are relative to a file in src/ — adjust if yours lives elsewhere):
2673
+ Install in your app (paths are relative to a file in src/; adjust if yours lives elsewhere):
2583
2674
 
2584
2675
  import { ${exportName} } from '${jsImport}';
2585
2676
  import '${cssImport}';
@@ -2611,7 +2702,7 @@ Or with a <link> tag:
2611
2702
  // Adaptation rules are resolved theme writes in their own right, so a family
2612
2703
  // named only inside one needs the same notice as one named at the root.
2613
2704
  for (const family of unloadedFonts) {
2614
- const msg = `Font "${family}" is named by this theme but not loaded — add a <link> or @font-face in your app (recipe: astryx docs typography)`;
2705
+ const msg = `Font "${family}" is named by this theme but not loaded; add a <link> or @font-face in your app (recipe: astryx docs typography)`;
2615
2706
  noticeMessages.push(msg);
2616
2707
  logger.log(` note: ${msg}`);
2617
2708
  }
@@ -2905,11 +2996,11 @@ export async function themeBuildFamily(
2905
2996
  const upToDate = stale.length === 0;
2906
2997
  if (upToDate) {
2907
2998
  logger.log(
2908
- `\n✓ Theme family outputs are up to date with ${sourceRelative}.`,
2999
+ `\n[ok] Theme family outputs are up to date with ${sourceRelative}.`,
2909
3000
  );
2910
3001
  } else {
2911
3002
  logger.error(
2912
- `\n✗ ${stale.length} theme family output(s) are out of date:`,
3003
+ `\n[fail] ${stale.length} theme family output(s) are out of date:`,
2913
3004
  );
2914
3005
  for (const entry of stale) {
2915
3006
  logger.error(
@@ -2929,7 +3020,7 @@ export async function themeBuildFamily(
2929
3020
  };
2930
3021
  }
2931
3022
  writeBuildOutputs(writes);
2932
- for (const output of Object.values(outputs)) logger.log(`✓ ${output}`);
3023
+ for (const output of Object.values(outputs)) logger.log(`[ok] ${output}`);
2933
3024
  const themeBindings = members.map(member => bindings.get(member.theme.name));
2934
3025
  const relOutDir = path.relative(cwd, outDir) || '.';
2935
3026
  const jsImport = importSpecifier(relOutDir, baseName);
@@ -2947,7 +3038,7 @@ Or load the same stylesheet natively:
2947
3038
  ];
2948
3039
  const notices = unloadedFonts.map(
2949
3040
  family =>
2950
- `Font "${family}" is named by this theme family but not loaded — add a <link> or @font-face in your app (recipe: astryx docs typography)`,
3041
+ `Font "${family}" is named by this theme family but not loaded; add a <link> or @font-face in your app (recipe: astryx docs typography)`,
2951
3042
  );
2952
3043
  for (const notice of notices) logger.log(` note: ${notice}`);
2953
3044
  return {
@@ -19,7 +19,7 @@
19
19
  * next one is covered without touching this file.
20
20
  *
21
21
  * Asserted on the receipt's `warnings` rather than on a rejection: a private
22
- * var is reported (logged `✗`, collected into the receipt) and the build then
22
+ * var is reported (logged `[error]`, collected into the receipt) and the build then
23
23
  * emits its CSS and resolves anyway. Asserting a throw would pass for the
24
24
  * wrong reason — it never throws, which is why a throwaway build read as a
25
25
  * pass on the first version of #5214.
@@ -0,0 +1,111 @@
1
+ // Copyright (c) Meta Platforms, Inc. and affiliates.
2
+
3
+ /**
4
+ * Every field `themeBuild()` returns is named in its FunctionDoc and in the
5
+ * response-types doc.
6
+ *
7
+ * The receipt is a stable machine-readable response, so each field needs
8
+ * consumer documentation, not only a typedef. The fields are read off a real
9
+ * build, so a field added to the receipt without documentation fails here.
10
+ */
11
+
12
+ import {describe, it, expect, beforeAll, afterEach} from 'vitest';
13
+ import * as fs from 'node:fs';
14
+ import * as os from 'node:os';
15
+ import * as path from 'node:path';
16
+ import {themeBuild} from './build.mjs';
17
+ import {doc} from '../themeBuild.doc.mjs';
18
+ import {doc as responseTypes} from '../../../foundation/response/response-types.doc.mjs';
19
+ import {ensureCoreBuilt} from '../../../clients/cli/commands/ensure-core-built.mjs';
20
+
21
+ /** @type {string[]} */
22
+ const dirs = [];
23
+
24
+ beforeAll(() => {
25
+ ensureCoreBuilt();
26
+ }, 200_000);
27
+
28
+ afterEach(() => {
29
+ for (const dir of dirs.splice(0)) {
30
+ fs.rmSync(dir, {recursive: true, force: true});
31
+ }
32
+ });
33
+
34
+ /** @returns {string} a temp dir holding a theme that exercises every field */
35
+ function themeDir() {
36
+ const dir = fs.mkdtempSync(path.join(os.tmpdir(), 'astryx-receipt-doc-'));
37
+ dirs.push(dir);
38
+ fs.writeFileSync(
39
+ path.join(dir, 'ocean.mjs'),
40
+ `export default {
41
+ name: 'ocean',
42
+ tokens: {'--color-background-body': '#ffffff', '--font-family-body': '"Inter", sans-serif'},
43
+ components: {progressbar: {base: {color: 'red'}}},
44
+ };
45
+ `,
46
+ );
47
+ return dir;
48
+ }
49
+
50
+ /** @param {string} type */
51
+ function returnsDescription(type) {
52
+ const entry = doc.returns?.find(r => r.type === type);
53
+ expect(entry, `themeBuild doc has no ${type} return`).toBeDefined();
54
+ return /** @type {{description: string}} */ (entry).description;
55
+ }
56
+
57
+ /** @param {string} description @param {string[]} fields */
58
+ function undocumented(description, fields) {
59
+ return fields.filter(
60
+ field => !new RegExp(String.raw`\b${field}\b`).test(description),
61
+ );
62
+ }
63
+
64
+ describe('themeBuild() consumer docs name every receipt field', () => {
65
+ it('theme.build', async () => {
66
+ const dir = themeDir();
67
+ const result = await themeBuild('ocean.mjs', {}, {cwd: dir});
68
+
69
+ expect(result?.type).toBe('theme.build');
70
+ const data = /** @type {Record<string, any>} */ (result?.data);
71
+ // The theme is chosen so the optional arrays are non-empty.
72
+ expect(data.warnings.length).toBeGreaterThan(0);
73
+ expect(data.notices.length).toBeGreaterThan(0);
74
+
75
+ const fields = [
76
+ ...Object.keys(data),
77
+ ...Object.keys(data.outputs),
78
+ 'variantsDts',
79
+ ];
80
+ expect(undocumented(returnsDescription('theme.build'), fields)).toEqual([]);
81
+ });
82
+
83
+ it('theme.build in the response-types doc', async () => {
84
+ const dir = themeDir();
85
+ const result = await themeBuild('ocean.mjs', {}, {cwd: dir});
86
+ const data = /** @type {Record<string, any>} */ (result?.data);
87
+ const member = responseTypes.members.find(m => m.value === 'theme.build');
88
+ expect(member, 'response-types has no theme.build member').toBeDefined();
89
+
90
+ const fields = [
91
+ ...Object.keys(data),
92
+ ...Object.keys(data.outputs),
93
+ 'variantsDts',
94
+ ];
95
+ expect(
96
+ undocumented(/** @type {{description: string}} */ (member).description, fields),
97
+ ).toEqual([]);
98
+ });
99
+
100
+ it('theme.build.check', async () => {
101
+ const dir = themeDir();
102
+ const result = await themeBuild('ocean.mjs', {check: true}, {cwd: dir});
103
+
104
+ expect(result?.type).toBe('theme.build.check');
105
+ const data = /** @type {Record<string, any>} */ (result?.data);
106
+ const fields = [...Object.keys(data), ...Object.keys(data.stale[0])];
107
+ expect(
108
+ undocumented(returnsDescription('theme.build.check'), fields),
109
+ ).toEqual([]);
110
+ });
111
+ });
@@ -206,6 +206,133 @@ describe('themeBuild() — receipt', () => {
206
206
  });
207
207
  });
208
208
 
209
+ describe('themeBuild() — dropped declarations reach the receipt', () => {
210
+ // Core's generator refuses a declaration whose value would end it early
211
+ // (an unquoted `;` or brace, an unclosed string/comment/url). The runtime
212
+ // says so on the console; a build has a receipt, so every drop must land in
213
+ // `warnings` — a programmatic caller otherwise sees `warnings: []` while the
214
+ // CSS silently omits a value the generated JS still carries.
215
+ it('reports every dropped declaration, from every generator path, in warnings', async () => {
216
+ const themeFile = path.join(tmpDir, 'dropped.mjs');
217
+ fs.writeFileSync(
218
+ themeFile,
219
+ `export default {
220
+ name: 'dropped',
221
+ tokens: {
222
+ '--color-bg': '#0a0a0a } body { background: url(https://example.com/token) ',
223
+ },
224
+ components: {
225
+ button: {
226
+ 'variant:secondary': {
227
+ backgroundColor: 'red; background-image: url(https://example.com/component)',
228
+ ':hover': {color: '"https://example.com/pseudo'},
229
+ },
230
+ },
231
+ },
232
+ onDark: {
233
+ tokens: {'--color-bg': 'url(https://example.com/dark'},
234
+ },
235
+ adaptations: {
236
+ rules: [
237
+ {
238
+ when: {pointer: 'coarse'},
239
+ value: {tokens: {'--color-bg': 'red /* https://example.com/adaptation'}},
240
+ },
241
+ ],
242
+ },
243
+ };\n`,
244
+ );
245
+
246
+ const warnSpy = vi.spyOn(console, 'warn').mockImplementation(() => {});
247
+ let result;
248
+ try {
249
+ result = await themeBuild('dropped.mjs', {}, {cwd: tmpDir});
250
+ // Drops are collected, not printed: the programmatic API stays silent.
251
+ expect(warnSpy).not.toHaveBeenCalled();
252
+ } finally {
253
+ warnSpy.mockRestore();
254
+ }
255
+
256
+ const css = fs.readFileSync(path.join(tmpDir, 'dropped.css'), 'utf8');
257
+ expect(css).not.toContain('example.com');
258
+
259
+ const warnings = result?.data.warnings ?? [];
260
+ expect(warnings).toHaveLength(5);
261
+ expect(warnings).toEqual(
262
+ expect.arrayContaining([
263
+ expect.stringMatching(
264
+ /^Declaration dropped "--color-bg" in tokens: an unquoted "}" would close the rule/,
265
+ ),
266
+ expect.stringMatching(
267
+ /^Declaration dropped "background-color" in components\.button\["variant:secondary"\]: an unquoted ";" would end the declaration/,
268
+ ),
269
+ expect.stringMatching(
270
+ /^Declaration dropped "color" in components\.button\["variant:secondary"\]\[":hover"\]: an unclosed " string/,
271
+ ),
272
+ expect.stringMatching(
273
+ /^Declaration dropped "--color-bg" in onDark\.tokens: an unclosed url\(/,
274
+ ),
275
+ expect.stringMatching(
276
+ /^Declaration dropped "--color-bg" in adaptations\[0\]\.tokens: an unclosed \/\* comment/,
277
+ ),
278
+ ]),
279
+ );
280
+ for (const w of warnings) {
281
+ expect(w).toContain('The generated CSS omits it');
282
+ }
283
+ });
284
+
285
+ it('generates CSS for legacy null tokens without losing neighboring declarations', async () => {
286
+ fs.writeFileSync(
287
+ path.join(tmpDir, 'legacy.mjs'),
288
+ `export default {
289
+ name: 'legacy',
290
+ tokens: {'--color-background-body': null, '--spacing-4': 12},
291
+ onDark: {tokens: {'--color-background-body': null}},
292
+ components: {button: {base: {borderRadius: '4px'}}},
293
+ };\n`,
294
+ );
295
+
296
+ const result = await themeBuild('legacy.mjs', {}, {cwd: tmpDir});
297
+ const css = fs.readFileSync(path.join(tmpDir, 'legacy.css'), 'utf8');
298
+ expect(css).toContain('--color-background-body: null;');
299
+ expect(css).toContain('--spacing-4: 12;');
300
+ expect(css).toContain('border-radius: 4px;');
301
+ expect(result?.data.warnings).toEqual([]);
302
+ });
303
+
304
+ it('keeps valid CSS that carries semicolons, and reports nothing', async () => {
305
+ const themeFile = path.join(tmpDir, 'kept.mjs');
306
+ fs.writeFileSync(
307
+ themeFile,
308
+ `export default {
309
+ name: 'kept',
310
+ tokens: {'--font-family-body': 'Gill\\\\ Sans, "Segoe;UI", serif /* ; */'},
311
+ components: {
312
+ button: {
313
+ 'variant:secondary': {
314
+ backgroundImage: 'URL(data:image/svg+xml;base64,PHN2Zz4=)',
315
+ WebkitLineClamp: '2',
316
+ },
317
+ },
318
+ },
319
+ };\n`,
320
+ );
321
+
322
+ const result = await themeBuild('kept.mjs', {}, {cwd: tmpDir});
323
+ const css = fs.readFileSync(path.join(tmpDir, 'kept.css'), 'utf8');
324
+
325
+ expect(css).toContain(
326
+ '--font-family-body: Gill\\ Sans, "Segoe;UI", serif /* ; */;',
327
+ );
328
+ expect(css).toContain(
329
+ 'background-image: URL(data:image/svg+xml;base64,PHN2Zz4=);',
330
+ );
331
+ expect(css).toContain('-webkit-line-clamp: 2;');
332
+ expect(result?.data.warnings).toEqual([]);
333
+ });
334
+ });
335
+
209
336
  describe('themeBuild() — nothing to build', () => {
210
337
  it('returns null and writes nothing when the generator yields no CSS', async () => {
211
338
  const themeFile = path.join(tmpDir, 'empty.mjs');