@astryxdesign/cli 0.6.3 → 0.6.4-canary.078fd25

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 (728) hide show
  1. package/CHANGELOG.md +194 -0
  2. package/README.md +152 -107
  3. package/api/blog/blog.doc.mjs +1 -0
  4. package/api/build/_adapter.d.mts +50 -0
  5. package/api/build/_adapter.mjs +60 -0
  6. package/api/build/build.doc.mjs +22 -10
  7. package/api/build/build.test.mjs +219 -8
  8. package/api/build/build.type.d.mts +91 -2
  9. package/api/build/build.type.mjs +52 -8
  10. package/api/build/help/help.d.mts +12 -5
  11. package/api/build/help/help.mjs +69 -6
  12. package/api/build/kit/kit.d.mts +4 -1
  13. package/api/build/kit/kit.mjs +208 -53
  14. package/api/build/kit/rank.d.mts +44 -0
  15. package/api/build/kit/rank.mjs +432 -0
  16. package/api/build/kit/rank.test.mjs +196 -0
  17. package/api/component/_adapter.d.mts +31 -12
  18. package/api/component/_adapter.mjs +79 -15
  19. package/api/component/component.d.mts +6 -3
  20. package/api/component/component.doc.mjs +49 -19
  21. package/api/component/component.mjs +339 -22
  22. package/api/component/component.test.mjs +38 -0
  23. package/api/component/component.type.d.mts +47 -11
  24. package/api/component/component.type.mjs +76 -24
  25. package/api/component/detail/blocks/blocks.d.mts +2 -1
  26. package/api/component/detail/blocks/blocks.mjs +4 -3
  27. package/api/component/list/list.d.mts +0 -5
  28. package/api/component/list/list.mjs +40 -11
  29. package/api/discover/_adapter.d.mts +114 -6
  30. package/api/discover/_adapter.mjs +372 -17
  31. package/api/discover/_adapter.test.mjs +215 -0
  32. package/api/discover/_catalog-view.d.mts +115 -0
  33. package/api/discover/_catalog-view.mjs +203 -0
  34. package/api/discover/_catalog-view.test.mjs +128 -0
  35. package/api/discover/detail/detail.d.mts +18 -6
  36. package/api/discover/detail/detail.mjs +67 -13
  37. package/api/discover/detail/detail.test.mjs +85 -0
  38. package/api/discover/detail/item/item.d.mts +26 -0
  39. package/api/discover/detail/item/item.mjs +78 -0
  40. package/api/discover/detail/item/item.test.mjs +73 -0
  41. package/api/discover/discover.d.mts +3 -9
  42. package/api/discover/discover.doc.mjs +62 -18
  43. package/api/discover/discover.mjs +220 -36
  44. package/api/discover/discover.test.mjs +11 -2
  45. package/api/discover/discover.type.d.mts +150 -11
  46. package/api/discover/discover.type.mjs +107 -17
  47. package/api/discover/list/list.d.mts +20 -6
  48. package/api/discover/list/list.mjs +45 -12
  49. package/api/discover/list/list.test.mjs +46 -0
  50. package/api/discover/search/search.d.mts +18 -16
  51. package/api/discover/search/search.mjs +102 -56
  52. package/api/discover/search/search.test.mjs +144 -10
  53. package/api/docs/_adapter.d.mts +277 -41
  54. package/api/docs/_adapter.mjs +993 -108
  55. package/api/docs/compiled-topics.test.mjs +78 -0
  56. package/api/docs/detail/detail.mjs +22 -63
  57. package/api/docs/detail/section/section.d.mts +1 -1
  58. package/api/docs/detail/section/section.mjs +54 -19
  59. package/api/docs/detail/section/section.test.mjs +50 -0
  60. package/api/docs/docOverlays.test.mjs +27 -1
  61. package/api/docs/docs.d.mts +10 -3
  62. package/api/docs/docs.doc.mjs +55 -16
  63. package/api/docs/docs.mjs +53 -10
  64. package/api/docs/docs.type.d.mts +221 -5
  65. package/api/docs/docs.type.mjs +153 -11
  66. package/api/docs/index/index.d.mts +18 -0
  67. package/api/docs/index/index.mjs +40 -0
  68. package/api/docs/index/index.test.mjs +62 -0
  69. package/api/docs/list/list.mjs +28 -12
  70. package/api/docs/node/node.d.mts +43 -0
  71. package/api/docs/node/node.mjs +192 -0
  72. package/api/docs/reference-blocks.test.mjs +406 -0
  73. package/api/doctor/doctor.d.mts +104 -1
  74. package/api/doctor/doctor.doc.mjs +18 -8
  75. package/api/doctor/doctor.mjs +635 -7
  76. package/api/doctor/doctor.test.mjs +732 -11
  77. package/api/doctor/doctor.type.d.mts +1 -1
  78. package/api/doctor/doctor.type.mjs +1 -1
  79. package/api/gap-report/gap-report.doc.mjs +27 -14
  80. package/api/hook/_adapter.mjs +19 -5
  81. package/api/hook/hook.doc.mjs +7 -3
  82. package/api/hook/hook.type.d.mts +3 -3
  83. package/api/hook/hook.type.mjs +11 -11
  84. package/api/hook/list/list.d.mts +2 -2
  85. package/api/hook/list/list.mjs +69 -17
  86. package/api/index.d.mts +2 -1
  87. package/api/index.mjs +6 -3
  88. package/api/init/init.doc.mjs +22 -12
  89. package/api/init/init.test.mjs +41 -1
  90. package/api/init/remove/remove.mjs +1 -1
  91. package/api/init/run/run.mjs +20 -10
  92. package/api/integration/add-contribution.component-names.test.mjs +120 -0
  93. package/api/integration/add-contribution.d.mts +2 -1
  94. package/api/integration/add-contribution.mjs +130 -15
  95. package/api/integration/add-contribution.test.mjs +258 -7
  96. package/api/integration/add-helpers.d.mts +5 -2
  97. package/api/integration/add-helpers.mjs +36 -9
  98. package/api/integration/add-theme.mjs +56 -65
  99. package/api/integration/add-theme.test.mjs +139 -21
  100. package/api/integration/authoring-checks.mjs +138 -28
  101. package/api/integration/authoring-checks.test.mjs +179 -7
  102. package/api/integration/authoring-checks.type.mjs +6 -1
  103. package/api/integration/integration-authoring.type.d.mts +3 -1
  104. package/api/integration/integration-authoring.type.mjs +2 -0
  105. package/api/integration/integration-block-exports.test.mjs +10 -6
  106. package/api/integration/integrationAdd.doc.mjs +14 -4
  107. package/api/integration/integrationAddAgentDoc.doc.mjs +1 -0
  108. package/api/integration/integrationAddCodemod.doc.mjs +1 -0
  109. package/api/integration/integrationAddComponent.doc.mjs +2 -1
  110. package/api/integration/integrationAddDoc.doc.mjs +8 -1
  111. package/api/integration/integrationAddTemplate.doc.mjs +1 -0
  112. package/api/integration/integrationAddTheme.doc.mjs +6 -5
  113. package/api/integration/integrationComponentConflicts.doc.mjs +1 -0
  114. package/api/integration/integrationDocConflicts.doc.mjs +2 -1
  115. package/api/integration/integrationPackCheck.doc.mjs +5 -4
  116. package/api/integration/integrationTemplateConflicts.doc.d.mts +3 -0
  117. package/api/integration/integrationTemplateConflicts.doc.mjs +4 -0
  118. package/api/integration/pack-check.lifecycle-output.test.mjs +107 -0
  119. package/api/integration/pack-check.mjs +160 -11
  120. package/api/integration/pack-check.test.mjs +477 -47
  121. package/api/integration/pack-check.type.d.mts +26 -2
  122. package/api/integration/pack-check.type.mjs +15 -2
  123. package/api/integration/summarizeIssues.doc.mjs +1 -0
  124. package/api/integration/template-conflict-compatibility.test.mjs +73 -0
  125. package/api/integration/validate-integration-fixes.test.mjs +1389 -0
  126. package/api/integration/validate-integration.mjs +52 -102
  127. package/api/integration/validate-integration.test.mjs +179 -26
  128. package/api/integration/validate-unread-theme-folders.test.mjs +110 -0
  129. package/api/integration/validateIntegration.doc.mjs +3 -2
  130. package/api/json/assertResponse.doc.mjs +2 -1
  131. package/api/json/envelope-types.test.mjs +76 -0
  132. package/api/json/index.ts +2 -0
  133. package/api/json/isError.doc.mjs +2 -1
  134. package/api/json/parseResponse.doc.mjs +3 -2
  135. package/api/layout/_adapter.mjs +20 -5
  136. package/api/layout/expand/expand.mjs +7 -5
  137. package/api/layout/expand/expand.path-safety.test.mjs +53 -0
  138. package/api/layout/grammar/grammar.mjs +2 -1
  139. package/api/layout/layoutCheck.doc.mjs +1 -0
  140. package/api/layout/layoutExpand.doc.mjs +2 -1
  141. package/api/layout/layoutGrammar.doc.mjs +1 -0
  142. package/api/search/search-return-type.test.mjs +54 -0
  143. package/api/search/search.d.mts +89 -12
  144. package/api/search/search.doc.mjs +8 -2
  145. package/api/search/search.mjs +697 -97
  146. package/api/search/search.type.d.mts +15 -3
  147. package/api/search/search.type.mjs +5 -2
  148. package/api/swizzle/copy/copy.mjs +28 -11
  149. package/api/swizzle/swizzle.doc.mjs +8 -5
  150. package/api/swizzle/swizzle.type.d.mts +2 -2
  151. package/api/swizzle/swizzle.type.mjs +2 -2
  152. package/api/template/copy/copy.mjs +18 -24
  153. package/api/template/copy/copy.test.mjs +26 -0
  154. package/api/template/list/list.mjs +1 -0
  155. package/api/template/table-floating-bulk-actions.test.mjs +66 -0
  156. package/api/template/template-integration.test.mjs +1072 -3
  157. package/api/template/template-suffix.test.mjs +41 -21
  158. package/api/template/template.d.mts +1 -1
  159. package/api/template/template.doc.mjs +32 -9
  160. package/api/template/template.mjs +45 -8
  161. package/api/template/template.type.d.mts +12 -14
  162. package/api/template/template.type.mjs +15 -14
  163. package/api/theme/_adapter.d.mts +2 -3
  164. package/api/theme/_adapter.mjs +4 -5
  165. package/api/theme/add/add.binary.test.mjs +84 -0
  166. package/api/theme/add/add.mjs +31 -22
  167. package/api/theme/add/add.rollback.test.mjs +158 -0
  168. package/api/theme/add/add.staging.test.mjs +83 -0
  169. package/api/theme/add/add.test.mjs +14 -1
  170. package/api/theme/build/build.family.test.mjs +7 -12
  171. package/api/theme/build/build.mjs +140 -59
  172. package/api/theme/build/build.public-component-vars.test.mjs +1 -1
  173. package/api/theme/build/build.receipt-doc.test.mjs +111 -0
  174. package/api/theme/build/build.rollback.test.mjs +148 -0
  175. package/api/theme/build/build.test.mjs +127 -0
  176. package/api/theme/build/font-warning.mjs +3 -3
  177. package/api/theme/build/font-warning.test.mjs +5 -2
  178. package/api/theme/generateTonalPalette.doc.mjs +2 -2
  179. package/api/theme/integration-themes.test.mjs +39 -28
  180. package/api/theme/list/list.test.mjs +19 -20
  181. package/api/theme/listThemes.doc.mjs +6 -5
  182. package/api/theme/palette/generate/generate.mjs +8 -3
  183. package/api/theme/palette/generate/generate.test.mjs +96 -0
  184. package/api/theme/palette/generate/generator.d.mts +10 -13
  185. package/api/theme/palette/generate/generator.mjs +15 -4
  186. package/api/theme/palette/generate/generator.test.mjs +10 -0
  187. package/api/theme/template/template.mjs +11 -2
  188. package/api/theme/template/template.test.mjs +20 -0
  189. package/api/theme/theme.type.d.mts +170 -11
  190. package/api/theme/theme.type.mjs +94 -27
  191. package/api/theme/themeAdd.doc.mjs +12 -12
  192. package/api/theme/themeBuild.doc.mjs +21 -17
  193. package/api/theme/themeList.doc.mjs +6 -3
  194. package/api/theme/themeListAvailable.doc.mjs +6 -3
  195. package/api/theme/themePaletteGenerate.doc.mjs +16 -8
  196. package/api/theme/themeTargets.doc.mjs +4 -2
  197. package/api/theme/themeTemplate.doc.mjs +8 -3
  198. package/api/upgrade/_adapter.d.mts +32 -5
  199. package/api/upgrade/_adapter.mjs +139 -22
  200. package/api/upgrade/list/list.mjs +2 -1
  201. package/api/upgrade/list/list.test.mjs +73 -0
  202. package/api/upgrade/project-context.test.mjs +272 -0
  203. package/api/upgrade/provider-agreement.test.mjs +152 -0
  204. package/api/upgrade/run/files-changed.test.mjs +111 -0
  205. package/api/upgrade/run/run.mjs +358 -59
  206. package/api/upgrade/status/status.mjs +2 -2
  207. package/api/upgrade/upgrade.doc.mjs +32 -23
  208. package/api/upgrade/upgrade.type.d.mts +43 -5
  209. package/api/upgrade/upgrade.type.mjs +29 -13
  210. package/assets/codemods/__tests__/registry.test.mjs +1 -0
  211. package/assets/codemods/__tests__/runner.test.mjs +332 -8
  212. package/assets/codemods/file-count.test.mjs +163 -0
  213. package/assets/codemods/integration-discovery.mjs +48 -4
  214. package/assets/codemods/integration-discovery.test.mjs +73 -0
  215. package/assets/codemods/integration-runner.mjs +59 -7
  216. package/assets/codemods/integration-runner.protection.test.mjs +153 -0
  217. package/assets/codemods/registry.mjs +1 -0
  218. package/assets/codemods/run-codemod.mjs +177 -34
  219. package/assets/codemods/runner.mjs +353 -104
  220. package/assets/codemods/term-log.mjs +32 -8
  221. package/assets/codemods/term-log.test.mjs +19 -1
  222. package/assets/codemods/transform-prop.mjs +109 -0
  223. package/assets/codemods/transform-prop.test.mjs +95 -0
  224. package/assets/codemods/transforms/v0.0.14/__tests__/rename-status-variants.test.mjs +86 -165
  225. package/assets/codemods/transforms/v0.0.14/rename-status-variants.mjs +72 -210
  226. package/assets/codemods/transforms/v0.1.8/__tests__/rename-avatar-size-scale.test.mjs +83 -115
  227. package/assets/codemods/transforms/v0.1.8/rename-avatar-size-scale.mjs +57 -186
  228. package/assets/codemods/transforms/v0.3.0/__tests__/unwrap-authoring-factories.test.mjs +27 -5
  229. package/assets/codemods/transforms/v0.3.0/unwrap-authoring-factories.mjs +20 -5
  230. package/assets/codemods/transforms/v0.6.0/__tests__/next-codemods.test.mjs +47 -0
  231. package/assets/codemods/transforms/v0.6.0/rename-resizable-pixel-bounds.mjs +57 -10
  232. package/assets/codemods/transforms/v0.6.4/__tests__/migrate-native-picker-to-presentation.test.mjs +63 -0
  233. package/assets/codemods/transforms/v0.6.4/__tests__/migrate-theme-catalog-to-descriptors.test.mjs +220 -0
  234. package/assets/codemods/transforms/v0.6.4/index.mjs +31 -0
  235. package/assets/codemods/transforms/v0.6.4/migrate-native-picker-to-presentation.mjs +148 -0
  236. package/assets/codemods/transforms/v0.6.4/migrate-theme-catalog-to-descriptors.mjs +141 -0
  237. package/assets/docs/README.md +12 -1
  238. package/assets/docs/authoring.doc.mjs +14 -0
  239. package/assets/docs/browser-support.doc.mjs +11 -11
  240. package/assets/docs/color.doc.mjs +8 -2
  241. package/assets/docs/elevation.doc.mjs +6 -4
  242. package/assets/docs/getting-started.doc.mjs +6 -17
  243. package/assets/docs/icons.doc.mjs +2 -21
  244. package/assets/docs/illustrations.doc.mjs +7 -15
  245. package/assets/docs/internationalization.doc.mjs +7 -5
  246. package/assets/docs/layout.doc.dense.mjs +132 -84
  247. package/assets/docs/layout.doc.mjs +134 -78
  248. package/assets/docs/migration.doc.mjs +19 -21
  249. package/assets/docs/motion.doc.mjs +16 -3
  250. package/assets/docs/principles.doc.dense.mjs +5 -5
  251. package/assets/docs/principles.doc.mjs +14 -6
  252. package/assets/docs/principles.doc.zh.mjs +6 -6
  253. package/assets/docs/shape.doc.mjs +8 -3
  254. package/assets/docs/spacing.doc.mjs +7 -2
  255. package/assets/docs/styling-libraries.doc.mjs +10 -6
  256. package/assets/docs/styling.doc.mjs +22 -26
  257. package/assets/docs/theme.doc.dense.mjs +58 -18
  258. package/assets/docs/theme.doc.mjs +60 -50
  259. package/assets/docs/theme.doc.zh.mjs +9 -8
  260. package/assets/docs/tokens.doc.dense.mjs +2 -2
  261. package/assets/docs/tokens.doc.mjs +390 -9
  262. package/assets/docs/tokens.doc.zh.mjs +2 -2
  263. package/assets/docs/tree/add-a-component.doc.mjs +75 -0
  264. package/assets/docs/tree/add-a-theme.doc.mjs +85 -0
  265. package/assets/docs/tree/add-a-topic.doc.mjs +144 -0
  266. package/assets/docs/tree/agent-guidance.doc.mjs +138 -0
  267. package/assets/docs/tree/api.doc.mjs +30 -0
  268. package/assets/docs/tree/block-template.doc.mjs +130 -0
  269. package/assets/docs/tree/build-the-template.doc.mjs +28 -0
  270. package/assets/docs/tree/building-blocks.doc.mjs +46 -0
  271. package/assets/docs/tree/check-your-docs.doc.mjs +137 -0
  272. package/assets/docs/tree/checks.doc.mjs +119 -0
  273. package/assets/docs/tree/cli.doc.mjs +23 -0
  274. package/assets/docs/tree/codemods.doc.mjs +147 -0
  275. package/assets/docs/tree/commands.doc.mjs +25 -0
  276. package/assets/docs/tree/component-family.doc.mjs +113 -0
  277. package/assets/docs/tree/component-imports.doc.mjs +69 -0
  278. package/assets/docs/tree/component-lookups.doc.mjs +149 -0
  279. package/assets/docs/tree/components.doc.mjs +23 -0
  280. package/assets/docs/tree/configuration.doc.mjs +23 -0
  281. package/assets/docs/tree/debug-and-gap-reports.doc.mjs +182 -0
  282. package/assets/docs/tree/define-the-theme.doc.mjs +118 -0
  283. package/assets/docs/tree/describe-the-component.doc.mjs +57 -0
  284. package/assets/docs/tree/docs.doc.mjs +21 -0
  285. package/assets/docs/tree/document-the-template.doc.mjs +28 -0
  286. package/assets/docs/tree/document-the-theme.doc.mjs +68 -0
  287. package/assets/docs/tree/export-template-assets.doc.mjs +147 -0
  288. package/assets/docs/tree/extend-or-replace.doc.mjs +103 -0
  289. package/assets/docs/tree/fonts-and-assets.doc.mjs +106 -0
  290. package/assets/docs/tree/generate-a-palette.doc.mjs +66 -0
  291. package/assets/docs/tree/grade-template-with-agent.doc.mjs +105 -0
  292. package/assets/docs/tree/help.doc.mjs +16 -0
  293. package/assets/docs/tree/integrations.doc.mjs +40 -0
  294. package/assets/docs/tree/links.doc.mjs +98 -0
  295. package/assets/docs/tree/package-and-test.doc.mjs +32 -0
  296. package/assets/docs/tree/page-template.doc.mjs +71 -0
  297. package/assets/docs/tree/publishing.doc.mjs +111 -0
  298. package/assets/docs/tree/quick-start.doc.mjs +272 -0
  299. package/assets/docs/tree/replace-a-core-component.doc.mjs +104 -0
  300. package/assets/docs/tree/replace-a-core-template.doc.mjs +172 -0
  301. package/assets/docs/tree/sections-and-placement.doc.mjs +108 -0
  302. package/assets/docs/tree/see-it-in-an-app.doc.mjs +59 -0
  303. package/assets/docs/tree/ship.doc.mjs +16 -0
  304. package/assets/docs/tree/short-and-findable.doc.mjs +108 -0
  305. package/assets/docs/tree/single-component.doc.mjs +165 -0
  306. package/assets/docs/tree/start-a-template.doc.mjs +143 -0
  307. package/assets/docs/tree/subcomponent.doc.mjs +115 -0
  308. package/assets/docs/tree/template-assets.doc.mjs +64 -0
  309. package/assets/docs/tree/template-doc-overview.doc.mjs +109 -0
  310. package/assets/docs/tree/template-fonts.doc.mjs +102 -0
  311. package/assets/docs/tree/template-grading-rubric.doc.mjs +452 -0
  312. package/assets/docs/tree/template-icons.doc.mjs +97 -0
  313. package/assets/docs/tree/template-images-media.doc.mjs +127 -0
  314. package/assets/docs/tree/template-styles.doc.mjs +93 -0
  315. package/assets/docs/tree/templates.doc.mjs +34 -0
  316. package/assets/docs/tree/test-in-an-app.doc.mjs +115 -0
  317. package/assets/docs/tree/test-template-in-app.doc.mjs +128 -0
  318. package/assets/docs/tree/themes.doc.mjs +39 -0
  319. package/assets/docs/tree/troubleshooting.doc.mjs +149 -0
  320. package/assets/docs/tree/upgrading.doc.mjs +103 -0
  321. package/assets/docs/tree/use-a-theme-in-an-app.doc.mjs +51 -0
  322. package/assets/docs/tree/verify-packed-template.doc.mjs +77 -0
  323. package/assets/docs/tree/versioning.doc.mjs +161 -0
  324. package/assets/docs/tree/write-good-templates.doc.mjs +64 -0
  325. package/assets/docs/tree/write-the-template-file.doc.mjs +154 -0
  326. package/assets/docs/typography.doc.mjs +24 -4
  327. package/assets/docs/working-with-ai.doc.mjs +34 -26
  328. package/assets/templates/blocks/components/CheckboxList/CheckboxListSelectAllPattern.doc.mjs +1 -1
  329. package/assets/templates/blocks/components/CheckboxList/CheckboxListSelectAllPattern.tsx +1 -3
  330. package/assets/templates/blocks/components/DateInput/DateInputDateRange.tsx +1 -1
  331. package/assets/templates/blocks/components/InternationalizationProvider/InternationalizationProvider01ShippedLocale.tsx +1 -1
  332. package/assets/templates/blocks/components/Item/ItemDocumentTabs.doc.mjs +14 -0
  333. package/assets/templates/blocks/components/Item/ItemDocumentTabs.tsx +100 -0
  334. package/assets/templates/blocks/components/ScrollableArea/ScrollableAreaInlineRail.doc.mjs +14 -0
  335. package/assets/templates/blocks/components/ScrollableArea/ScrollableAreaInlineRail.tsx +61 -0
  336. package/assets/templates/blocks/components/ScrollableArea/ScrollableAreaOverscrollChaining.doc.mjs +14 -0
  337. package/assets/templates/blocks/components/ScrollableArea/ScrollableAreaOverscrollChaining.tsx +126 -0
  338. package/assets/templates/blocks/components/ScrollableArea/ScrollableAreaShowcase.doc.mjs +15 -0
  339. package/assets/templates/blocks/components/ScrollableArea/ScrollableAreaShowcase.tsx +86 -0
  340. package/assets/templates/blocks/components/ScrollableArea/ScrollableAreaStickyGroupHeaders.doc.mjs +14 -0
  341. package/assets/templates/blocks/components/ScrollableArea/ScrollableAreaStickyGroupHeaders.tsx +99 -0
  342. package/assets/templates/blocks/components/ScrollableArea/ScrollableAreaStickyPassthrough.doc.mjs +14 -0
  343. package/assets/templates/blocks/components/ScrollableArea/ScrollableAreaStickyPassthrough.tsx +122 -0
  344. package/assets/templates/blocks/components/ScrollableArea/ScrollableAreaTwoAxisBoard.doc.mjs +14 -0
  345. package/assets/templates/blocks/components/ScrollableArea/ScrollableAreaTwoAxisBoard.tsx +95 -0
  346. package/assets/templates/blocks/components/Table/TableBulkActionsTable.doc.mjs +14 -0
  347. package/assets/templates/blocks/components/Table/TableBulkActionsTable.tsx +71 -0
  348. package/assets/templates/blocks/components/Table/TableFloatingBulkActionsTable.doc.mjs +19 -0
  349. package/assets/templates/blocks/components/Table/TableFloatingBulkActionsTable.tsx +139 -0
  350. package/assets/templates/blocks/components/TimeInput/TimeInputConstrained.tsx +1 -0
  351. package/assets/templates/blocks/components/Timer/TimerFormats.doc.mjs +14 -0
  352. package/assets/templates/blocks/components/Timer/TimerFormats.tsx +34 -0
  353. package/assets/templates/blocks/components/Timer/TimerInline.doc.mjs +14 -0
  354. package/assets/templates/blocks/components/Timer/TimerInline.tsx +14 -0
  355. package/assets/templates/blocks/components/Timer/TimerShowcase.doc.mjs +13 -0
  356. package/assets/templates/blocks/components/Timer/TimerShowcase.tsx +47 -0
  357. package/assets/templates/blocks/components/Timer/TimerTypography.doc.mjs +14 -0
  358. package/assets/templates/blocks/components/Timer/TimerTypography.tsx +31 -0
  359. package/assets/templates/blocks/components/Toolbar/ToolbarTableFilter.doc.mjs +19 -3
  360. package/assets/templates/blocks/components/Toolbar/ToolbarTableFilter.tsx +383 -65
  361. package/assets/templates/pages/table-tree/page.tsx +1704 -0
  362. package/assets/templates/pages/table-tree/template.doc.mjs +12 -0
  363. package/assets/templates/themes/butter/butterTheme.doc.mjs +11 -0
  364. package/assets/templates/themes/chocolate/chocolateTheme.doc.mjs +11 -0
  365. package/assets/templates/themes/gothic/gothicTheme.doc.mjs +11 -0
  366. package/assets/templates/themes/matcha/matchaTheme.doc.mjs +11 -0
  367. package/assets/templates/themes/neutral/neutralTheme.doc.mjs +11 -0
  368. package/assets/templates/themes/stone/stoneTheme.doc.mjs +11 -0
  369. package/assets/templates/themes/y2k/y2kTheme.doc.mjs +11 -0
  370. package/authoring/_shared/contract.ts +22 -0
  371. package/authoring/codemod/codemod.doc.mjs +7 -2
  372. package/authoring/codemod/parse.d.mts +8 -8
  373. package/authoring/codemod/parse.mjs +8 -6
  374. package/authoring/codemod/type.ts +12 -0
  375. package/authoring/config/config.doc.mjs +11 -3
  376. package/authoring/config/debug-composition.test.mjs +92 -0
  377. package/authoring/config/parse.d.mts +15 -13
  378. package/authoring/config/parse.mjs +27 -8
  379. package/authoring/config/parse.test.mjs +8 -0
  380. package/authoring/config/type.ts +29 -6
  381. package/authoring/debug/debug.doc.d.mts +11 -0
  382. package/authoring/debug/debug.doc.mjs +182 -0
  383. package/authoring/debug/parse.d.mts +8 -8
  384. package/authoring/debug/parse.mjs +3 -3
  385. package/authoring/discover/discover.doc.d.mts +13 -0
  386. package/authoring/discover/discover.doc.mjs +138 -0
  387. package/authoring/discover/parse.d.mts +24 -0
  388. package/authoring/discover/parse.mjs +128 -0
  389. package/authoring/discover/parse.test.mjs +124 -0
  390. package/authoring/discover/type.ts +87 -0
  391. package/authoring/doctypes/_schema.d.mts +792 -24
  392. package/authoring/doctypes/_schema.mjs +549 -39
  393. package/authoring/doctypes/base/graph-fields.doc.d.mts +9 -0
  394. package/authoring/doctypes/base/graph-fields.doc.mjs +64 -0
  395. package/authoring/doctypes/base/type.ts +43 -0
  396. package/authoring/doctypes/command/command.doc.mjs +4 -3
  397. package/authoring/doctypes/command/parse.d.mts +2 -2
  398. package/authoring/doctypes/command/parse.mjs +1 -1
  399. package/authoring/doctypes/command/type.ts +5 -4
  400. package/authoring/doctypes/component/component.doc.mjs +12 -3
  401. package/authoring/doctypes/component/parse.d.mts +2 -2
  402. package/authoring/doctypes/component/parse.mjs +1 -1
  403. package/authoring/doctypes/component/type.ts +14 -5
  404. package/authoring/doctypes/doctypes-new.test.mjs +48 -6
  405. package/authoring/doctypes/enum/enum.doc.mjs +1 -1
  406. package/authoring/doctypes/enum/parse.d.mts +2 -2
  407. package/authoring/doctypes/enum/parse.mjs +1 -1
  408. package/authoring/doctypes/enum/type.ts +4 -2
  409. package/authoring/doctypes/function/function.doc.mjs +7 -2
  410. package/authoring/doctypes/function/parse.d.mts +2 -2
  411. package/authoring/doctypes/function/parse.mjs +1 -1
  412. package/authoring/doctypes/function/type.ts +9 -4
  413. package/authoring/doctypes/hook/hook.doc.mjs +4 -0
  414. package/authoring/doctypes/hook/parse.d.mts +2 -2
  415. package/authoring/doctypes/hook/parse.mjs +1 -1
  416. package/authoring/doctypes/hook/type.ts +5 -4
  417. package/authoring/doctypes/legacy.d.mts +8 -6
  418. package/authoring/doctypes/legacy.mjs +5 -4
  419. package/authoring/doctypes/load-contract.test.mjs +233 -0
  420. package/authoring/doctypes/namespace/namespace.doc.d.mts +9 -0
  421. package/authoring/doctypes/namespace/namespace.doc.mjs +128 -0
  422. package/authoring/doctypes/namespace/parse.d.mts +12 -0
  423. package/authoring/doctypes/namespace/parse.mjs +25 -0
  424. package/authoring/doctypes/namespace/parse.test.mjs +163 -0
  425. package/authoring/doctypes/namespace/type.ts +74 -0
  426. package/authoring/doctypes/parse.d.mts +22 -18
  427. package/authoring/doctypes/parse.mjs +22 -11
  428. package/authoring/doctypes/parse.test.mjs +77 -3
  429. package/authoring/doctypes/reference/parse.d.mts +2 -2
  430. package/authoring/doctypes/reference/parse.mjs +8 -5
  431. package/authoring/doctypes/reference/reference.doc.mjs +55 -6
  432. package/authoring/doctypes/reference/type.ts +75 -7
  433. package/authoring/doctypes/schema/parse.d.mts +2 -2
  434. package/authoring/doctypes/schema/parse.mjs +1 -1
  435. package/authoring/doctypes/schema/schema.doc.mjs +2 -2
  436. package/authoring/doctypes/schema/type.ts +4 -4
  437. package/authoring/doctypes/template/parse.d.mts +94 -1
  438. package/authoring/doctypes/template/parse.mjs +40 -2
  439. package/authoring/doctypes/template/parse.test.mjs +26 -2
  440. package/authoring/doctypes/template/template.doc.mjs +13 -3
  441. package/authoring/doctypes/template/type.ts +13 -2
  442. package/authoring/doctypes/theme/parse.d.mts +35 -0
  443. package/authoring/doctypes/theme/parse.mjs +76 -0
  444. package/authoring/doctypes/theme/theme.doc.d.mts +9 -0
  445. package/authoring/doctypes/theme/theme.doc.mjs +79 -0
  446. package/authoring/doctypes/theme/type.ts +42 -0
  447. package/authoring/doctypes/types.ts +12 -10
  448. package/authoring/gap-report/gap-report.doc.d.mts +12 -0
  449. package/authoring/gap-report/gap-report.doc.mjs +183 -0
  450. package/authoring/gap-report/parse.d.mts +10 -10
  451. package/authoring/gap-report/parse.mjs +6 -6
  452. package/authoring/gap-report/type.ts +1 -1
  453. package/authoring/identity/identity.doc.d.mts +9 -0
  454. package/authoring/identity/identity.doc.mjs +61 -0
  455. package/authoring/identity/type.ts +132 -0
  456. package/authoring/index.d.mts +3 -0
  457. package/authoring/index.d.ts +62 -17
  458. package/authoring/index.mjs +4 -1
  459. package/authoring/integration/integration.doc.mjs +22 -13
  460. package/authoring/integration/parse.d.mts +2 -2
  461. package/authoring/integration/parse.mjs +1 -1
  462. package/authoring/integration/parse.test.mjs +10 -1
  463. package/authoring/integration/schema.d.mts +6 -4
  464. package/authoring/integration/schema.mjs +9 -3
  465. package/authoring/integration/type.ts +19 -8
  466. package/authoring/shadcn/receipt.d.mts +6 -6
  467. package/clients/cli/__tests__/cliManifest.test.ts +27 -29
  468. package/clients/cli/command-load-failure.test.mjs +83 -0
  469. package/clients/cli/commands/blog.doc.mjs +1 -1
  470. package/clients/cli/commands/blog.mjs +23 -8
  471. package/clients/cli/commands/blog.test.mjs +42 -1
  472. package/clients/cli/commands/build-theme.adaptations.test.mjs +100 -1
  473. package/clients/cli/commands/build-theme.ascii-output.test.mjs +161 -0
  474. package/clients/cli/commands/build-theme.flag-docs.test.mjs +110 -0
  475. package/clients/cli/commands/build-theme.mjs +16 -50
  476. package/clients/cli/commands/build-theme.path-safety.test.mjs +86 -1
  477. package/clients/cli/commands/build-theme.variants.test.mjs +76 -0
  478. package/clients/cli/commands/build.doc.mjs +16 -8
  479. package/clients/cli/commands/build.exit-codes-doc.test.mjs +50 -0
  480. package/clients/cli/commands/build.mjs +137 -114
  481. package/clients/cli/commands/build.playbook.test.mjs +75 -0
  482. package/clients/cli/commands/build.text-fields.test.mjs +81 -0
  483. package/clients/cli/commands/component/index.mjs +153 -61
  484. package/clients/cli/commands/component-batch.test.mjs +341 -0
  485. package/clients/cli/commands/component-ownership.test.mjs +92 -3
  486. package/clients/cli/commands/component-package.test.mjs +46 -0
  487. package/clients/cli/commands/component-resolution.test.mjs +21 -0
  488. package/clients/cli/commands/component.doc.mjs +28 -10
  489. package/clients/cli/commands/component.test.mjs +19 -0
  490. package/clients/cli/commands/detail-levels.test.mjs +2 -2
  491. package/clients/cli/commands/discover.broken-integration.test.mjs +52 -5
  492. package/clients/cli/commands/discover.components-flag.test.mjs +97 -0
  493. package/clients/cli/commands/discover.doc.mjs +55 -9
  494. package/clients/cli/commands/discover.mjs +393 -118
  495. package/clients/cli/commands/discover.sources.test.mjs +267 -0
  496. package/clients/cli/commands/discover.text-projection.test.mjs +103 -0
  497. package/clients/cli/commands/docs.doc.mjs +28 -6
  498. package/clients/cli/commands/docs.mjs +295 -38
  499. package/clients/cli/commands/doctor-integration-components.doc.mjs +1 -1
  500. package/clients/cli/commands/doctor-integration-docs.doc.mjs +6 -5
  501. package/clients/cli/commands/doctor-integration-templates.doc.mjs +15 -8
  502. package/clients/cli/commands/doctor-integration-validate.doc.mjs +1 -1
  503. package/clients/cli/commands/doctor-integration.doc.mjs +1 -1
  504. package/clients/cli/commands/doctor-integration.package-json.test.mjs +53 -0
  505. package/clients/cli/commands/doctor-integration.test.mjs +143 -8
  506. package/clients/cli/commands/doctor.doc.mjs +4 -2
  507. package/clients/cli/commands/doctor.mjs +108 -37
  508. package/clients/cli/commands/doctor.test.mjs +42 -0
  509. package/clients/cli/commands/gap-report.doc.mjs +27 -15
  510. package/clients/cli/commands/gap-report.test.mjs +72 -0
  511. package/clients/cli/commands/hook/index.mjs +7 -17
  512. package/clients/cli/commands/hook.doc.mjs +1 -1
  513. package/clients/cli/commands/hook.text-projection.test.mjs +45 -0
  514. package/clients/cli/commands/init.doc.mjs +24 -10
  515. package/clients/cli/commands/init.flag-help.test.mjs +153 -0
  516. package/clients/cli/commands/integration-add.controls.test.mjs +132 -0
  517. package/clients/cli/commands/integration-add.doc.mjs +39 -13
  518. package/clients/cli/commands/integration-authoring.test.mjs +74 -19
  519. package/clients/cli/commands/integration-pack.doc.mjs +6 -10
  520. package/clients/cli/commands/integration-real-world.test.mjs +4 -10
  521. package/clients/cli/commands/integration-verify.doc.mjs +22 -0
  522. package/clients/cli/commands/integration.doc.mjs +5 -5
  523. package/clients/cli/commands/integration.mjs +75 -43
  524. package/clients/cli/commands/interactive-guard.test.mjs +101 -24
  525. package/clients/cli/commands/json-contract.test.mjs +33 -0
  526. package/clients/cli/commands/layout-check.doc.mjs +15 -4
  527. package/clients/cli/commands/layout-expand.doc.mjs +22 -5
  528. package/clients/cli/commands/layout-grammar.doc.mjs +1 -1
  529. package/clients/cli/commands/layout.doc.mjs +3 -3
  530. package/clients/cli/commands/layout.mjs +21 -9
  531. package/clients/cli/commands/layout.path-help.test.mjs +33 -0
  532. package/clients/cli/commands/layout.stdin-cap.test.mjs +47 -0
  533. package/clients/cli/commands/layout.text-fields.test.mjs +39 -0
  534. package/clients/cli/commands/manifest.doc.mjs +2 -2
  535. package/clients/cli/commands/no-prompt-wording.test.mjs +94 -0
  536. package/clients/cli/commands/search.doc.mjs +16 -6
  537. package/clients/cli/commands/search.mjs +49 -11
  538. package/clients/cli/commands/search.test.mjs +92 -0
  539. package/clients/cli/commands/setup-nudge.test.mjs +6 -0
  540. package/clients/cli/commands/swizzle.doc.mjs +4 -3
  541. package/clients/cli/commands/swizzle.path-safety.test.mjs +42 -0
  542. package/clients/cli/commands/template.doc.mjs +53 -14
  543. package/clients/cli/commands/template.flag-help.test.mjs +117 -0
  544. package/clients/cli/commands/template.mjs +4 -91
  545. package/clients/cli/commands/template.path-help.test.mjs +40 -0
  546. package/clients/cli/commands/text-json-parity.test.mjs +725 -0
  547. package/clients/cli/commands/theme-add.doc.mjs +5 -4
  548. package/clients/cli/commands/theme-build.doc.mjs +8 -7
  549. package/clients/cli/commands/theme-list.doc.mjs +2 -2
  550. package/clients/cli/commands/theme-palette-generate.doc.mjs +12 -7
  551. package/clients/cli/commands/theme-palette-generate.test.mjs +19 -0
  552. package/clients/cli/commands/theme-palette.doc.mjs +2 -3
  553. package/clients/cli/commands/theme-targets.behavior.test.mjs +4 -3
  554. package/clients/cli/commands/theme-targets.doc.mjs +3 -3
  555. package/clients/cli/commands/theme-template.behavior.test.mjs +12 -0
  556. package/clients/cli/commands/theme-template.doc.mjs +2 -2
  557. package/clients/cli/commands/theme.doc.mjs +3 -2
  558. package/clients/cli/commands/upgrade.ascii-output.test.mjs +87 -0
  559. package/clients/cli/commands/upgrade.doc.mjs +83 -12
  560. package/clients/cli/commands/upgrade.file-protection.test.mjs +228 -0
  561. package/clients/cli/commands/upgrade.flag-help.test.mjs +188 -0
  562. package/clients/cli/commands/upgrade.hook-output.test.mjs +88 -0
  563. package/clients/cli/commands/upgrade.mjs +29 -7
  564. package/clients/cli/formatters/index.mjs +164 -1
  565. package/clients/cli/formatters/index.test.mjs +97 -0
  566. package/clients/cli/index.mjs +47 -34
  567. package/clients/cli/latest-version-env.test.mjs +50 -0
  568. package/clients/cli/lib/cli-error.test.mjs +7 -0
  569. package/clients/cli/lib/component-format.mjs +9 -9
  570. package/clients/cli/lib/component-format.test.mjs +1 -1
  571. package/clients/cli/lib/define-command.mjs +56 -6
  572. package/clients/cli/lib/define-command.test.mjs +54 -0
  573. package/clients/cli/lib/doc-text-ascii.test.mjs +82 -0
  574. package/clients/cli/lib/exit-codes.test.mjs +113 -0
  575. package/clients/cli/lib/hook-format.mjs +19 -10
  576. package/clients/cli/lib/json-shim.mjs +62 -16
  577. package/clients/cli/lib/json-shim.test.mjs +83 -0
  578. package/clients/cli/lib/manifest.d.ts +2 -0
  579. package/clients/cli/lib/manifest.mjs +53 -6
  580. package/clients/cli/lib/manifest.test.mjs +22 -2
  581. package/clients/cli/lib/parse-error-format.test.mjs +81 -0
  582. package/foundation/agent-docs/agent-docs.d.mts +7 -2
  583. package/foundation/agent-docs/agent-docs.mjs +83 -13
  584. package/foundation/agent-docs/agent-docs.path-safety.test.mjs +266 -4
  585. package/foundation/config/integration-debug.test.mjs +28 -3
  586. package/foundation/config/project-themes.test.mjs +11 -19
  587. package/foundation/config/project.d.mts +20 -11
  588. package/foundation/config/project.mjs +263 -91
  589. package/foundation/config/project.test.mjs +270 -21
  590. package/foundation/discovery/authoring-self-docs.d.mts +87 -0
  591. package/foundation/discovery/authoring-self-docs.mjs +237 -0
  592. package/foundation/discovery/authoring-self-docs.test.mjs +174 -0
  593. package/foundation/discovery/authoring-surface.d.mts +74 -0
  594. package/foundation/discovery/authoring-surface.mjs +525 -0
  595. package/foundation/discovery/authoring-surface.test.mjs +392 -0
  596. package/foundation/discovery/cli-self-docs.d.mts +119 -0
  597. package/foundation/discovery/cli-self-docs.mjs +504 -0
  598. package/foundation/discovery/cli-self-docs.test.mjs +395 -0
  599. package/foundation/discovery/component-discovery.d.mts +39 -1
  600. package/foundation/discovery/component-discovery.mjs +50 -1
  601. package/foundation/discovery/component-loader.d.mts +35 -38
  602. package/foundation/discovery/component-loader.mjs +53 -222
  603. package/foundation/discovery/docs-discovery.d.mts +119 -11
  604. package/foundation/discovery/docs-discovery.mjs +427 -108
  605. package/foundation/discovery/docs-discovery.test.mjs +386 -20
  606. package/foundation/discovery/docs-output-budget.d.mts +28 -0
  607. package/foundation/discovery/docs-output-budget.mjs +50 -0
  608. package/foundation/discovery/docs-section-key.d.mts +116 -0
  609. package/foundation/discovery/docs-section-key.mjs +322 -0
  610. package/foundation/discovery/docs-section-key.test.mjs +246 -0
  611. package/foundation/discovery/template-adapter.d.mts +113 -11
  612. package/foundation/discovery/template-adapter.fixture-refs.test.mjs +248 -0
  613. package/foundation/discovery/template-adapter.integration-isolation.test.mjs +94 -0
  614. package/foundation/discovery/template-adapter.mjs +774 -83
  615. package/foundation/discovery/template-adapter.test.mjs +57 -0
  616. package/foundation/discovery/template-conflict-release.d.mts +13 -0
  617. package/foundation/discovery/template-conflict-release.mjs +40 -0
  618. package/foundation/discovery/template-conflict-release.test.mjs +40 -0
  619. package/foundation/discovery/theme-discovery.d.mts +67 -7
  620. package/foundation/discovery/theme-discovery.mjs +916 -186
  621. package/foundation/discovery/theme-discovery.test.mjs +613 -219
  622. package/foundation/discovery/theming-targets.test.mjs +4 -0
  623. package/foundation/doc-compiler/bundle.d.mts +47 -0
  624. package/foundation/doc-compiler/bundle.mjs +278 -0
  625. package/foundation/doc-compiler/bundle.test.mjs +266 -0
  626. package/foundation/doc-compiler/compile.d.mts +343 -0
  627. package/foundation/doc-compiler/compile.mjs +558 -0
  628. package/foundation/doc-compiler/diagnostics.d.mts +126 -0
  629. package/foundation/doc-compiler/diagnostics.mjs +305 -0
  630. package/foundation/doc-compiler/doc-compiler.test.mjs +714 -0
  631. package/foundation/doc-compiler/doc-loads.test.mjs +1643 -0
  632. package/foundation/doc-compiler/import.d.mts +24 -0
  633. package/foundation/doc-compiler/import.mjs +59 -0
  634. package/foundation/doc-compiler/inputs.d.mts +102 -0
  635. package/foundation/doc-compiler/inputs.mjs +291 -0
  636. package/foundation/doc-compiler/inputs.test.mjs +298 -0
  637. package/foundation/doc-compiler/ir.d.mts +22 -0
  638. package/foundation/doc-compiler/ir.mjs +471 -0
  639. package/foundation/doc-compiler/lenses.d.mts +36 -0
  640. package/foundation/doc-compiler/lenses.mjs +173 -0
  641. package/foundation/doc-compiler/links.d.mts +162 -0
  642. package/foundation/doc-compiler/links.mjs +294 -0
  643. package/foundation/doc-compiler/links.test.mjs +192 -0
  644. package/foundation/doc-compiler/lower-doc.test.mjs +495 -0
  645. package/foundation/doc-compiler/overlays.d.mts +37 -0
  646. package/foundation/doc-compiler/overlays.mjs +206 -0
  647. package/foundation/doc-compiler/parse-readable.d.mts +9 -0
  648. package/foundation/doc-compiler/parse-readable.mjs +29 -0
  649. package/foundation/doc-compiler/read.d.mts +127 -0
  650. package/foundation/doc-compiler/read.mjs +325 -0
  651. package/foundation/doc-compiler/read.test.mjs +313 -0
  652. package/foundation/doc-compiler/source.d.mts +33 -0
  653. package/foundation/doc-compiler/source.mjs +128 -0
  654. package/foundation/doc-compiler/tree.d.mts +292 -0
  655. package/foundation/doc-compiler/tree.mjs +881 -0
  656. package/foundation/fs/file-protection.d.mts +33 -0
  657. package/foundation/fs/file-protection.mjs +825 -0
  658. package/foundation/fs/file-protection.test.mjs +250 -0
  659. package/foundation/fs/module-loader.d.mts +1 -0
  660. package/foundation/fs/module-loader.mjs +50 -1
  661. package/foundation/fs/module-loader.stdout.test.mjs +332 -0
  662. package/foundation/fs/path-safety.d.mts +3 -2
  663. package/foundation/fs/path-safety.mjs +49 -19
  664. package/foundation/fs/path-safety.test.mjs +50 -0
  665. package/foundation/fs/publish-file-hardlink-unavailable.test.mjs +129 -94
  666. package/foundation/identity/provider-identity.d.mts +90 -0
  667. package/foundation/identity/provider-identity.mjs +320 -0
  668. package/foundation/identity/provider-identity.test.mjs +254 -0
  669. package/foundation/identity/providers.d.mts +7 -0
  670. package/foundation/identity/providers.mjs +16 -0
  671. package/foundation/integrations/autolink.d.mts +58 -1
  672. package/foundation/integrations/autolink.mjs +143 -45
  673. package/foundation/integrations/autolink.test.mjs +1 -1
  674. package/foundation/integrations/cli-requirement.d.mts +65 -0
  675. package/foundation/integrations/cli-requirement.mjs +189 -0
  676. package/foundation/integrations/cli-requirement.test.mjs +89 -0
  677. package/foundation/integrations/contribution-fixes.d.mts +145 -0
  678. package/foundation/integrations/contribution-fixes.mjs +1284 -0
  679. package/foundation/integrations/contribution-inventory.d.mts +3 -2
  680. package/foundation/integrations/contribution-inventory.mjs +28 -25
  681. package/foundation/integrations/contribution-inventory.test.mjs +86 -27
  682. package/foundation/integrations/integration-warnings.d.mts +9 -2
  683. package/foundation/integrations/integration-warnings.mjs +52 -21
  684. package/foundation/integrations/integration-warnings.test.mjs +74 -1
  685. package/foundation/integrations/integrations.d.mts +63 -3
  686. package/foundation/integrations/integrations.mjs +122 -9
  687. package/foundation/integrations/integrations.test.mjs +415 -1
  688. package/foundation/integrations/provider-conflicts.test.mjs +125 -0
  689. package/foundation/integrations/provider-ledger.test.mjs +275 -0
  690. package/foundation/integrations/provider-resolution.d.mts +152 -0
  691. package/foundation/integrations/provider-resolution.mjs +576 -0
  692. package/foundation/integrations/provider-resolution.test.mjs +369 -0
  693. package/foundation/integrations/theme-descriptor.d.mts +8 -0
  694. package/foundation/integrations/theme-descriptor.mjs +44 -0
  695. package/foundation/integrations/validate-contributions.d.mts +2 -0
  696. package/foundation/integrations/validate-contributions.mjs +131 -29
  697. package/foundation/response/base.d.ts +8 -4
  698. package/foundation/response/batch.type.d.mts +33 -0
  699. package/foundation/response/batch.type.mjs +34 -0
  700. package/foundation/response/error-codes.d.mts +3 -1
  701. package/foundation/response/error-codes.d.ts +2 -0
  702. package/foundation/response/error-codes.doc.mjs +19 -12
  703. package/foundation/response/error-codes.mjs +8 -2
  704. package/foundation/response/error-codes.test.mjs +166 -14
  705. package/foundation/response/json-contract.test.mjs +57 -17
  706. package/foundation/response/json.d.mts +4 -2
  707. package/foundation/response/json.mjs +8 -10
  708. package/foundation/response/response-types.doc.d.mts +7 -2
  709. package/foundation/response/response-types.doc.mjs +69 -25
  710. package/foundation/response/response-types.doc.test.mjs +181 -0
  711. package/foundation/response/response.doc.mjs +12 -11
  712. package/foundation/text/string-utils.d.mts +8 -0
  713. package/foundation/text/string-utils.mjs +40 -10
  714. package/foundation/xle/expand.d.mts +2 -0
  715. package/foundation/xle/expand.mjs +4 -3
  716. package/foundation/xle/expand.test.mjs +54 -0
  717. package/foundation/xle/xle.test.mjs +13 -0
  718. package/package.json +10 -11
  719. package/api/docs/docs.test.mjs +0 -83
  720. package/api/docs/integrationDocs.test.mjs +0 -208
  721. package/api/search/search.test.mjs +0 -389
  722. package/assets/docs/cli-integrations.doc.mjs +0 -367
  723. package/assets/templates/themes/manifest.json +0 -95
  724. package/clients/cli/commands/docs.test.mjs +0 -102
  725. package/clients/cli/lib/update-check.mjs +0 -83
  726. package/clients/cli/lib/update-check.test.mjs +0 -137
  727. package/clients/cli/update-hint-commands.test.mjs +0 -54
  728. package/foundation/agent-docs/agent-docs.test.mjs +0 -1141
@@ -1,13 +1,15 @@
1
1
  // Copyright (c) Meta Platforms, Inc. and affiliates.
2
2
 
3
3
  /**
4
- * @file Theme catalog discovery shared by Project, theme list/add, and
4
+ * @file Theme descriptor discovery shared by Project, theme list/add, and
5
5
  * integration validation.
6
6
  *
7
- * A theme root uses the same layout as the CLI's generated bundle:
8
- * `manifest.json` beside one directory per slug. Manifest `entry` and `files`
9
- * paths are relative to that slug directory and are confined there before any
10
- * caller reads or copies them.
7
+ * A theme root contains one directory per lower-kebab slug. Each directory has
8
+ * a theme source and mandatory same-stem `.doc.mjs`; the directory is the
9
+ * complete copy and pack boundary. Descriptor metadata is parsed without
10
+ * executing theme source. A dot-folder, or a folder holding neither a
11
+ * descriptor nor a `<name>Theme` source, is not a theme. Dot entries and files
12
+ * npm never publishes belong to no theme.
11
13
  *
12
14
  * @input a bundled or integration-owned theme root
13
15
  * @output validated source-theme records with package ownership
@@ -15,14 +17,42 @@
15
17
  */
16
18
 
17
19
  import * as fs from 'node:fs';
20
+ import {createRequire} from 'node:module';
18
21
  import * as path from 'node:path';
22
+ import {lowerDoc} from '../doc-compiler/compile.mjs';
23
+ import {packageSource} from '../doc-compiler/source.mjs';
19
24
  import {CLI_ROOT} from '../fs/paths.mjs';
20
25
  import {assertWithin, PathSafetyError} from '../fs/path-safety.mjs';
21
26
 
22
27
  export const BUNDLED_THEME_PACKAGE = '@astryxdesign/cli';
23
28
  export const THEMES_DIR = path.join(CLI_ROOT, 'assets', 'templates', 'themes');
24
- export const THEME_MANIFEST_BASENAME = 'manifest.json';
25
- export const MANIFEST_PATH = path.join(THEMES_DIR, THEME_MANIFEST_BASENAME);
29
+ export const THEME_DOC_SUFFIX = '.doc.mjs';
30
+
31
+ const require = createRequire(import.meta.url);
32
+
33
+ /** @type {typeof import('@babel/parser') | undefined} */
34
+ let babelParser;
35
+ /** @type {any} */
36
+ let jscodeshiftApi;
37
+
38
+ /**
39
+ * The descriptor parser, loaded when the first descriptor is read.
40
+ * @returns {typeof import('@babel/parser')}
41
+ */
42
+ function descriptorParser() {
43
+ babelParser ??= require('@babel/parser');
44
+ return /** @type {typeof import('@babel/parser')} */ (babelParser);
45
+ }
46
+
47
+ /**
48
+ * The theme source parser, loaded when the first integration theme source is
49
+ * checked. Bundled themes never need it.
50
+ * @returns {any}
51
+ */
52
+ function sourceParser() {
53
+ jscodeshiftApi ??= require('jscodeshift');
54
+ return jscodeshiftApi;
55
+ }
26
56
 
27
57
  /**
28
58
  * @typedef {object} DiscoveredTheme
@@ -32,22 +62,14 @@ export const MANIFEST_PATH = path.join(THEMES_DIR, THEME_MANIFEST_BASENAME);
32
62
  * @property {boolean} maintained
33
63
  * @property {string} entry
34
64
  * @property {string} exportName
35
- * @property {string[]} files
65
+ * @property {string[]} files what `theme add` copies, relative to sourceDir,
66
+ * entry first
36
67
  * @property {string} package
37
68
  * @property {string} sourceDir absolute directory holding this theme's files
38
69
  * @property {boolean} bundled
70
+ * @property {string} docPath absolute descriptor path
39
71
  */
40
72
 
41
- /** @param {unknown} value @param {string} field @param {string} owner */
42
- function requiredString(value, field, owner) {
43
- if (typeof value !== 'string' || value.trim().length === 0) {
44
- throw new Error(
45
- `Theme catalog for ${owner} has an invalid ${field}; expected a non-empty string.`,
46
- );
47
- }
48
- return value;
49
- }
50
-
51
73
  /**
52
74
  * Resolve one authored relative path without allowing POSIX or Windows escape
53
75
  * syntax, even when discovery runs on the other platform.
@@ -75,26 +97,45 @@ function resolveThemePath(value, root, label) {
75
97
  }
76
98
  }
77
99
 
78
- const THEME_MODULE_EXTENSIONS = ['.mjs', '.js', '.mts', '.ts', '.tsx', '.jsx'];
100
+ export const THEME_MODULE_EXTENSIONS = [
101
+ '.mjs',
102
+ '.js',
103
+ '.mts',
104
+ '.ts',
105
+ '.tsx',
106
+ '.jsx',
107
+ ];
108
+
109
+ /** A theme directory's name: lower-kebab, starting with a letter. */
110
+ export const THEME_SLUG_RE = /^[a-z][a-z0-9]*(?:-[a-z0-9]+)*$/u;
79
111
 
80
112
  /**
81
- * Resolve a local theme module only when the target is a listed file confined
82
- * to the theme directory.
83
113
  * @param {unknown} specifier
84
114
  * @param {string} fromFile
85
- * @param {string} themeDir
86
- * @param {Set<string>} allowedFiles
115
+ * @returns {string[]}
87
116
  */
88
- function resolveLocalThemeModule(specifier, fromFile, themeDir, allowedFiles) {
89
- if (typeof specifier !== 'string' || !specifier.startsWith('.')) return null;
117
+ function localThemeModuleCandidates(specifier, fromFile) {
118
+ if (typeof specifier !== 'string' || !specifier.startsWith('.')) return [];
90
119
  const base = path.resolve(path.dirname(fromFile), specifier);
91
- const candidates = [
120
+ return [
92
121
  base,
93
122
  ...THEME_MODULE_EXTENSIONS.map(extension => `${base}${extension}`),
94
123
  ...THEME_MODULE_EXTENSIONS.map(extension =>
95
124
  path.join(base, `index${extension}`),
96
125
  ),
97
126
  ];
127
+ }
128
+
129
+ /**
130
+ * Resolve a local theme module only when the target is a listed file confined
131
+ * to the theme directory.
132
+ * @param {unknown} specifier
133
+ * @param {string} fromFile
134
+ * @param {string} themeDir
135
+ * @param {Set<string>} allowedFiles
136
+ */
137
+ function resolveLocalThemeModule(specifier, fromFile, themeDir, allowedFiles) {
138
+ const candidates = localThemeModuleCandidates(specifier, fromFile);
98
139
  for (const candidate of candidates) {
99
140
  try {
100
141
  if (!fs.statSync(candidate).isFile()) continue;
@@ -111,30 +152,16 @@ function resolveLocalThemeModule(specifier, fromFile, themeDir, allowedFiles) {
111
152
  }
112
153
 
113
154
  class ThemeModuleReferenceError extends Error {}
155
+ class ThemeRuntimeExportError extends Error {}
114
156
 
115
157
  /**
116
- * Validate that every local static dependency is copied with the theme.
158
+ * Every string specifier a module imports, re-exports, or imports dynamically.
117
159
  * @param {string} file
118
160
  * @param {any} jscodeshift
119
- * @param {string} themeDir
120
- * @param {Set<string>} allowedFiles
121
- * @param {string} owner
122
- * @param {string} entry
123
- * @param {Set<string>} [seen]
161
+ * @returns {string[]}
124
162
  */
125
- function validateThemeModuleGraph(
126
- file,
127
- jscodeshift,
128
- themeDir,
129
- allowedFiles,
130
- owner,
131
- entry,
132
- seen = new Set(),
133
- ) {
134
- if (seen.has(file)) return;
135
- seen.add(file);
136
-
137
- const parser = /\.(?:ts|tsx|mts)$/u.test(file) ? 'tsx' : 'babel';
163
+ function moduleSpecifiers(file, jscodeshift) {
164
+ const parser = /\.(?:ts|tsx|mts|cts)$/u.test(file) ? 'tsx' : 'babel';
138
165
  const j = jscodeshift.withParser(parser);
139
166
  const root = j(fs.readFileSync(file, 'utf-8'));
140
167
  /** @type {string[]} */
@@ -170,7 +197,34 @@ function validateThemeModuleGraph(
170
197
  }
171
198
  });
172
199
 
173
- for (const specifier of specifiers) {
200
+ return specifiers;
201
+ }
202
+
203
+ /**
204
+ * Validate that every local static dependency is copied with the theme.
205
+ * @param {string} file
206
+ * @param {any} jscodeshift
207
+ * @param {string} themeDir
208
+ * @param {Set<string>} allowedFiles
209
+ * @param {string} owner
210
+ * @param {string} entry
211
+ * @param {string} descriptorPath
212
+ * @param {Set<string>} [seen]
213
+ */
214
+ function validateThemeModuleGraph(
215
+ file,
216
+ jscodeshift,
217
+ themeDir,
218
+ allowedFiles,
219
+ owner,
220
+ entry,
221
+ descriptorPath,
222
+ seen = new Set(),
223
+ ) {
224
+ if (seen.has(file)) return;
225
+ seen.add(file);
226
+
227
+ for (const specifier of moduleSpecifiers(file, jscodeshift)) {
174
228
  if (!specifier.startsWith('.')) continue;
175
229
  const target = resolveLocalThemeModule(
176
230
  specifier,
@@ -179,8 +233,19 @@ function validateThemeModuleGraph(
179
233
  allowedFiles,
180
234
  );
181
235
  if (!target) {
236
+ const referencesDescriptor = localThemeModuleCandidates(
237
+ specifier,
238
+ file,
239
+ ).some(
240
+ candidate => path.resolve(candidate) === path.resolve(descriptorPath),
241
+ );
242
+ if (referencesDescriptor) {
243
+ throw new ThemeModuleReferenceError(
244
+ `Theme entry "${entry}" from ${owner} must not import its descriptor "${path.basename(descriptorPath)}". Theme descriptors are authoring metadata, not runtime modules.`,
245
+ );
246
+ }
182
247
  throw new ThemeModuleReferenceError(
183
- `Theme catalog for ${owner} entry "${entry}" references local module "${specifier}" that must resolve to a listed file inside the theme directory.`,
248
+ `Theme entry "${entry}" from ${owner} references local module "${specifier}" that must resolve to a file inside the theme directory.`,
184
249
  );
185
250
  }
186
251
  if (THEME_MODULE_EXTENSIONS.includes(path.extname(target))) {
@@ -191,6 +256,7 @@ function validateThemeModuleGraph(
191
256
  allowedFiles,
192
257
  owner,
193
258
  entry,
259
+ descriptorPath,
194
260
  seen,
195
261
  );
196
262
  }
@@ -428,223 +494,887 @@ function moduleExportsName(
428
494
  }
429
495
 
430
496
  /**
431
- * Read and validate one theme catalog.
432
- * @param {string} themeRoot absolute catalog root
433
- * @param {string} owner package that owns the catalog
434
- * @param {{bundled?: boolean}} [options]
435
- * @returns {DiscoveredTheme[]}
497
+ * An expression without the parentheses around it.
498
+ * @param {any} node
499
+ * @returns {any}
436
500
  */
437
- export function discoverThemeCatalog(themeRoot, owner, {bundled = false} = {}) {
438
- if (!fs.existsSync(themeRoot) || !fs.statSync(themeRoot).isDirectory()) {
439
- throw new Error(
440
- `Declared themes root does not exist on disk: ${themeRoot}`,
441
- );
501
+ function unparenthesized(node) {
502
+ let current = node;
503
+ while (current?.type === 'ParenthesizedExpression') {
504
+ current = current.expression;
442
505
  }
506
+ return current;
507
+ }
443
508
 
444
- const manifestPath = resolveThemePath(
445
- THEME_MANIFEST_BASENAME,
446
- themeRoot,
447
- 'theme catalog manifest',
509
+ /**
510
+ * Convert one static literal used by ThemeDoc. Theme descriptors intentionally
511
+ * contain data only so synchronous bundled-theme APIs stay synchronous.
512
+ * @param {any} node
513
+ * @param {string} label
514
+ * @returns {string | boolean}
515
+ */
516
+ function staticThemeValue(node, label) {
517
+ if (node?.type === 'StringLiteral' || node?.type === 'BooleanLiteral') {
518
+ return node.value;
519
+ }
520
+ throw new Error(
521
+ `${label} must use static string and boolean values in its default export.`,
448
522
  );
449
- if (!fs.existsSync(manifestPath) || !fs.statSync(manifestPath).isFile()) {
450
- throw new Error(
451
- `Theme root for ${owner} must contain ${THEME_MANIFEST_BASENAME}.`,
452
- );
523
+ }
524
+
525
+ /**
526
+ * How messages about a theme descriptor name it.
527
+ * @param {string} docPath
528
+ * @param {string} owner
529
+ */
530
+ export function themeDescriptorLabel(docPath, owner) {
531
+ return `Theme descriptor ${path.basename(docPath)} for ${owner}`;
532
+ }
533
+
534
+ /**
535
+ * Read one strongly typed theme descriptor without executing it, and compile
536
+ * it.
537
+ * @param {string} docPath
538
+ * @param {string} owner
539
+ * @returns {import('../../authoring/doctypes/theme/type').ThemeDoc}
540
+ */
541
+ function readThemeDoc(docPath, owner) {
542
+ const label = themeDescriptorLabel(docPath, owner);
543
+ const value = readThemeDescriptorValue(docPath, label);
544
+ // Read statically, never executed, then compiled like every other doc.
545
+ const {node, failed, failure} = lowerDoc({
546
+ id: `${owner}:themes:${path.basename(docPath)}`,
547
+ root: 'themes',
548
+ provider: owner,
549
+ source: packageSource(docPath),
550
+ lang: null,
551
+ file: {file: path.basename(docPath), doc: value},
552
+ label,
553
+ });
554
+ if (!node || failed) throw failure;
555
+ return node.doc;
556
+ }
557
+
558
+ /**
559
+ * Descriptor reads in this process, by path and label, reused while the file
560
+ * keeps its size and mtime.
561
+ * @type {Map<string, {size: number, mtimeMs: number, read: {value: Record<string, string | boolean>} | {error: unknown}}>}
562
+ */
563
+ const descriptorReads = new Map();
564
+
565
+ /**
566
+ * The static value a theme descriptor default-exports, read from its source
567
+ * without executing it. Throws when the file is not one static ThemeDoc
568
+ * object.
569
+ * @param {string} docPath
570
+ * @param {string} label
571
+ * @returns {Record<string, string | boolean>}
572
+ */
573
+ export function readThemeDescriptorValue(docPath, label) {
574
+ const {size, mtimeMs} = fs.statSync(docPath);
575
+ const key = `${docPath}\0${label}`;
576
+ let cached = descriptorReads.get(key);
577
+ if (!cached || cached.size !== size || cached.mtimeMs !== mtimeMs) {
578
+ /** @type {{value: Record<string, string | boolean>} | {error: unknown}} */
579
+ let read;
580
+ try {
581
+ read = {
582
+ value: readDescriptorSource(fs.readFileSync(docPath, 'utf-8'), label),
583
+ };
584
+ } catch (error) {
585
+ read = {error};
586
+ }
587
+ cached = {size, mtimeMs, read};
588
+ descriptorReads.set(key, cached);
453
589
  }
590
+ if ('error' in cached.read) throw cached.read.error;
591
+ return {...cached.read.value};
592
+ }
454
593
 
455
- /** @type {unknown} */
456
- let parsed;
594
+ /**
595
+ * @param {string} source
596
+ * @param {string} label
597
+ * @returns {Record<string, string | boolean>}
598
+ */
599
+ function readDescriptorSource(source, label) {
600
+ /** @type {any} */
601
+ let ast;
457
602
  try {
458
- parsed = JSON.parse(fs.readFileSync(manifestPath, 'utf-8'));
603
+ ast = descriptorParser().parse(source, {
604
+ sourceType: 'module',
605
+ tokens: true,
606
+ createParenthesizedExpressions: true,
607
+ });
459
608
  } catch (error) {
460
609
  const message = error instanceof Error ? error.message : String(error);
461
- throw new Error(`Theme catalog for ${owner} is unreadable: ${message}`, {
462
- cause: error,
610
+ throw new Error(`${label} could not be parsed: ${message}`, {cause: error});
611
+ }
612
+
613
+ const statements = ast.program.body;
614
+ const defaults = statements.filter(
615
+ (/** @type {any} */ statement) =>
616
+ statement.type === 'ExportDefaultDeclaration',
617
+ );
618
+ const unsupported = statements.filter(
619
+ (/** @type {any} */ statement) =>
620
+ statement.type !== 'ExportDefaultDeclaration' &&
621
+ statement.type !== 'EmptyStatement',
622
+ );
623
+ if (unsupported.length > 0 || ast.program.directives.length > 0) {
624
+ throw new Error(
625
+ `${label} must contain only its static default-exported ThemeDoc object.`,
626
+ );
627
+ }
628
+ const object =
629
+ defaults.length === 1 && unparenthesized(defaults[0].declaration);
630
+ if (object?.type !== 'ObjectExpression') {
631
+ throw new Error(`${label} must default-export one static ThemeDoc object.`);
632
+ }
633
+ if (!declaresThemeDoc(ast, defaults[0])) {
634
+ throw new Error(
635
+ `${label} must declare its public ThemeDoc type from @astryxdesign/cli/authoring.`,
636
+ );
637
+ }
638
+
639
+ /** @type {Record<string, string | boolean>} */
640
+ const value = {};
641
+ for (const property of object.properties) {
642
+ if (property.type !== 'ObjectProperty' || property.computed) {
643
+ throw new Error(`${label} must contain only static object properties.`);
644
+ }
645
+ const key =
646
+ property.key.type === 'Identifier'
647
+ ? property.key.name
648
+ : property.key.type === 'StringLiteral'
649
+ ? property.key.value
650
+ : null;
651
+ if (key === null) {
652
+ throw new Error(`${label} has an invalid property name.`);
653
+ }
654
+ if (Object.hasOwn(value, key)) {
655
+ throw new Error(`${label} declares "${key}" more than once.`);
656
+ }
657
+ // Defined, not assigned, so `__proto__` stays a key the parser rejects.
658
+ Object.defineProperty(value, key, {
659
+ value: staticThemeValue(unparenthesized(property.value), label),
660
+ enumerable: true,
661
+ writable: true,
662
+ configurable: true,
463
663
  });
464
664
  }
665
+ return value;
666
+ }
667
+
668
+ /** `import('@astryxdesign/cli/authoring').ThemeDoc`, without whitespace. */
669
+ const THEME_DOC_TYPE =
670
+ /^import\((['"])@astryxdesign\/cli\/authoring\1\)\.ThemeDoc$/u;
671
+
672
+ /** @param {any} comment */
673
+ function isJsdoc(comment) {
674
+ return comment.type === 'CommentBlock' && comment.value.startsWith('*');
675
+ }
676
+
677
+ /** @param {string} ch */
678
+ const isSpace = ch => ch === ' ' || ch === '\t';
679
+
680
+ /**
681
+ * The tags of one JSDoc comment, found the way TypeScript's JSDoc scanner
682
+ * finds them: an `@` starts a tag at the start of a line (after the margin
683
+ * `*`), or after a space and before a non-space, never inside a backtick span
684
+ * of a tag's text. Each tag's text runs to the next tag, margins removed.
685
+ * @param {string} value the comment's body, as Babel gives it
686
+ * @returns {{name: string, text: string}[]}
687
+ */
688
+ function jsdocTags(value) {
689
+ const body = value.slice(1);
690
+ /** @type {number[]} */
691
+ const starts = [];
692
+ let lineStart = true;
693
+ let sawAsterisk = true;
694
+ let inTag = false;
695
+ let backticks = false;
696
+ for (let index = 0; index < body.length; index++) {
697
+ const ch = body[index];
698
+ if (ch === '\n' || ch === '\r') {
699
+ lineStart = true;
700
+ sawAsterisk = false;
701
+ backticks = false;
702
+ continue;
703
+ }
704
+ if (lineStart) {
705
+ if (isSpace(ch)) continue;
706
+ if (ch === '*' && !sawAsterisk) {
707
+ sawAsterisk = true;
708
+ continue;
709
+ }
710
+ lineStart = false;
711
+ if (ch === '@') {
712
+ starts.push(index);
713
+ inTag = true;
714
+ continue;
715
+ }
716
+ }
717
+ if (ch === '`' && inTag) {
718
+ backticks = !backticks;
719
+ } else if (
720
+ ch === '@' &&
721
+ !backticks &&
722
+ isSpace(body[index - 1] ?? '') &&
723
+ !/\s/u.test(body[index + 1] ?? ' ')
724
+ ) {
725
+ starts.push(index);
726
+ inTag = true;
727
+ backticks = false;
728
+ }
729
+ }
730
+ return starts.map((start, position) => {
731
+ const raw = body.slice(start + 1, starts[position + 1] ?? body.length);
732
+ const name = /^[\w$]*/u.exec(raw)?.[0] ?? '';
733
+ const text = raw.slice(name.length).replace(/(\r?\n)[ \t]*\*?/gu, '$1');
734
+ return {name, text};
735
+ });
736
+ }
465
737
 
466
- if (parsed == null || typeof parsed !== 'object' || Array.isArray(parsed)) {
467
- throw new Error(`Theme catalog for ${owner} must be a JSON object.`);
738
+ /**
739
+ * The braced type at the start of a tag's text, and what follows it.
740
+ * @param {string} text
741
+ * @returns {{type: string, rest: string} | null}
742
+ */
743
+ function bracedType(text) {
744
+ const open = text.search(/\S/u);
745
+ if (open === -1 || text[open] !== '{') return null;
746
+ let depth = 0;
747
+ /** @type {string | null} */
748
+ let quote = null;
749
+ for (let index = open; index < text.length; index++) {
750
+ const ch = text[index];
751
+ if (quote) {
752
+ if (ch === quote) quote = null;
753
+ } else if (ch === "'" || ch === '"' || ch === '`') {
754
+ quote = ch;
755
+ } else if (ch === '{') {
756
+ depth++;
757
+ } else if (ch === '}' && --depth === 0) {
758
+ return {
759
+ type: text.slice(open + 1, index).replace(/\s+/gu, ''),
760
+ rest: text.slice(index + 1),
761
+ };
762
+ }
468
763
  }
469
- const catalog = /** @type {{version?: unknown, themes?: unknown}} */ (parsed);
470
- if (catalog.version !== 1) {
471
- throw new Error(`Theme catalog for ${owner} must use version 1.`);
764
+ return null;
765
+ }
766
+
767
+ /**
768
+ * Split a type at top-level occurrences of one operator.
769
+ * @param {string} type
770
+ * @param {string} operator
771
+ */
772
+ function splitType(type, operator) {
773
+ /** @type {string[]} */
774
+ const parts = [];
775
+ let depth = 0;
776
+ /** @type {string | null} */
777
+ let quote = null;
778
+ let last = 0;
779
+ for (let index = 0; index < type.length; index++) {
780
+ const ch = type[index];
781
+ if (quote) {
782
+ if (ch === quote) quote = null;
783
+ } else if (ch === "'" || ch === '"' || ch === '`') {
784
+ quote = ch;
785
+ } else if ('([{<'.includes(ch)) {
786
+ depth++;
787
+ } else if (')]}>'.includes(ch)) {
788
+ depth--;
789
+ } else if (ch === operator && depth === 0) {
790
+ parts.push(type.slice(last, index));
791
+ last = index + 1;
792
+ }
472
793
  }
473
- if (!Array.isArray(catalog.themes)) {
474
- throw new Error(`Theme catalog for ${owner} must contain a themes array.`);
794
+ parts.push(type.slice(last));
795
+ return parts;
796
+ }
797
+
798
+ /** @param {string} type */
799
+ function wrappedInParens(type) {
800
+ if (!type.startsWith('(') || !type.endsWith(')')) return false;
801
+ let depth = 0;
802
+ for (let index = 0; index < type.length; index++) {
803
+ if (type[index] === '(') depth++;
804
+ else if (type[index] === ')' && --depth === 0) {
805
+ return index === type.length - 1;
806
+ }
807
+ }
808
+ return false;
809
+ }
810
+
811
+ /**
812
+ * Whether a JSDoc type, whitespace removed, checks an object literal exactly
813
+ * as ThemeDoc does: ThemeDoc or one of its aliases, optionally parenthesized,
814
+ * marked `!`, `?` or `=`, joined with `null` or `undefined`, or intersected
815
+ * with `{}`.
816
+ * @param {string} type
817
+ * @param {Map<string, string>} aliases alias name to the type it names
818
+ * @param {Set<string>} [seen]
819
+ * @returns {boolean}
820
+ */
821
+ function isThemeDocType(type, aliases, seen = new Set()) {
822
+ let current = type;
823
+ for (;;) {
824
+ if (/^[!?]/u.test(current)) current = current.slice(1);
825
+ else if (current.endsWith('=')) current = current.slice(0, -1);
826
+ else if (wrappedInParens(current)) current = current.slice(1, -1);
827
+ else break;
828
+ }
829
+ const members = splitType(current, '|').filter(
830
+ member => member !== 'null' && member !== 'undefined',
831
+ );
832
+ if (members.length !== 1) return false;
833
+ if (members[0] !== current) return isThemeDocType(members[0], aliases, seen);
834
+ const parts = splitType(current, '&').filter(part => part !== '{}');
835
+ if (parts.length !== 1) return false;
836
+ if (parts[0] !== current) return isThemeDocType(parts[0], aliases, seen);
837
+ if (THEME_DOC_TYPE.test(current)) return true;
838
+ const named = aliases.get(current);
839
+ if (named === undefined || seen.has(current)) return false;
840
+ seen.add(current);
841
+ return isThemeDocType(named, aliases, seen);
842
+ }
843
+
844
+ const AUTHORING = String.raw`(['"])@astryxdesign\/cli\/authoring\2`;
845
+
846
+ /**
847
+ * The type names the file's JSDoc binds with `@typedef` or `@import`, from any
848
+ * JSDoc comment in the module, as TypeScript reads them.
849
+ * @param {any[]} comments
850
+ * @returns {Map<string, string>}
851
+ */
852
+ function jsdocAliases(comments) {
853
+ /** @type {Map<string, string>} */
854
+ const aliases = new Map();
855
+ const theme = "import('@astryxdesign/cli/authoring').ThemeDoc";
856
+ for (const comment of comments.filter(isJsdoc)) {
857
+ for (const tag of jsdocTags(comment.value)) {
858
+ if (tag.name === 'typedef') {
859
+ const typed = bracedType(tag.text);
860
+ const name = typed && /^\s*([$A-Z_a-z][$\w]*)/u.exec(typed.rest)?.[1];
861
+ if (typed && name) aliases.set(name, typed.type);
862
+ } else if (tag.name === 'import') {
863
+ const named = new RegExp(
864
+ String.raw`^\s*\{([^{}]*)\}\s*from\s*${AUTHORING}`,
865
+ 'u',
866
+ ).exec(tag.text);
867
+ for (const specifier of named?.[1].split(',') ?? []) {
868
+ const match =
869
+ /^\s*(?:type\s+)?ThemeDoc(?:\s+as\s+([$A-Z_a-z][$\w]*))?\s*$/u.exec(
870
+ specifier,
871
+ );
872
+ if (match) aliases.set(match[1] ?? 'ThemeDoc', theme);
873
+ }
874
+ const namespace = new RegExp(
875
+ String.raw`^\s*\*\s*as\s+([$A-Z_a-z][$\w]*)\s+from\s*${AUTHORING}`,
876
+ 'u',
877
+ ).exec(tag.text)?.[1];
878
+ if (namespace) aliases.set(`${namespace}.ThemeDoc`, theme);
879
+ }
880
+ }
881
+ }
882
+ return aliases;
883
+ }
884
+
885
+ /**
886
+ * Whether the JSDoc TypeScript reads for the default export types it as the
887
+ * public ThemeDoc: `@type` in the last JSDoc comment between the previous
888
+ * token and `export`, or `@type` or `@satisfies` in the last one between the
889
+ * previous token and the opening parenthesis of a JSDoc cast of the object.
890
+ * Line comments, strings, code spans, and JSDoc anywhere else never type it.
891
+ * @param {any} ast parsed with tokens and parenthesized expressions
892
+ * @param {any} exportDefault
893
+ */
894
+ function declaresThemeDoc(ast, exportDefault) {
895
+ const tokens = ast.tokens.filter(
896
+ (/** @type {any} */ token) => typeof token.type !== 'string',
897
+ );
898
+ const comments = ast.comments ?? [];
899
+ /** @param {number} start */
900
+ const jsdocBefore = start => {
901
+ const after =
902
+ tokens.filter((/** @type {any} */ token) => token.end <= start).at(-1)
903
+ ?.end ?? 0;
904
+ return comments
905
+ .filter(
906
+ (/** @type {any} */ comment) =>
907
+ isJsdoc(comment) && comment.start >= after && comment.end <= start,
908
+ )
909
+ .at(-1);
910
+ };
911
+ /** @type {Array<{comment: any, tags: string[]}>} */
912
+ const attached = [
913
+ {comment: jsdocBefore(exportDefault.start), tags: ['type']},
914
+ ];
915
+ for (
916
+ let node = exportDefault.declaration;
917
+ node.type === 'ParenthesizedExpression';
918
+ node = node.expression
919
+ ) {
920
+ attached.push({
921
+ comment: jsdocBefore(node.start),
922
+ tags: ['type', 'satisfies'],
923
+ });
924
+ }
925
+ const aliases = jsdocAliases(comments);
926
+ return attached.some(({comment, tags}) => {
927
+ if (!comment) return false;
928
+ const found = jsdocTags(comment.value);
929
+ return tags.some(name => {
930
+ const tag = found.find(candidate => candidate.name === name);
931
+ const typed = tag && bracedType(tag.text);
932
+ return typed != null && isThemeDocType(typed.type, aliases);
933
+ });
934
+ });
935
+ }
936
+
937
+ /** A module named for the theme it exports, such as `oceanTheme.ts`. */
938
+ const THEME_SOURCE_RE = /^[$A-Z_a-z][$\w]*Theme\.(?:mjs|js|mts|ts|tsx|jsx)$/u;
939
+
940
+ /**
941
+ * Whether an entry at any depth of a themes root is outside every theme: a dot
942
+ * entry, or a name npm never publishes (npm-packlist's defaults).
943
+ * @param {string} name
944
+ */
945
+ export function isIgnoredThemeEntry(name) {
946
+ return (
947
+ name.startsWith('.') ||
948
+ name === 'node_modules' ||
949
+ name === 'CVS' ||
950
+ name === 'npm-debug.log' ||
951
+ name.endsWith('.orig')
952
+ );
953
+ }
954
+
955
+ /**
956
+ * Every entry below one folder of a theme root, without following symlinks
957
+ * and without {@link isIgnoredThemeEntry} entries.
958
+ * @param {string} folder
959
+ * @returns {{files: string[], symlinks: string[]}} sorted POSIX paths
960
+ * relative to the folder
961
+ */
962
+ function listThemeFolder(folder) {
963
+ /** @type {string[]} */
964
+ const files = [];
965
+ /** @type {string[]} */
966
+ const symlinks = [];
967
+ /** @param {string} directory */
968
+ function walk(directory) {
969
+ for (const entry of fs.readdirSync(directory, {withFileTypes: true})) {
970
+ if (isIgnoredThemeEntry(entry.name)) continue;
971
+ const full = path.join(directory, entry.name);
972
+ const relative = path.relative(folder, full).split(path.sep).join('/');
973
+ if (entry.isSymbolicLink()) symlinks.push(relative);
974
+ else if (entry.isDirectory()) walk(full);
975
+ else if (entry.isFile()) files.push(relative);
976
+ }
977
+ }
978
+ walk(folder);
979
+ return {files: files.sort(), symlinks: symlinks.sort()};
980
+ }
981
+
982
+ /**
983
+ * The first entry that makes a folder a theme: a `.doc.mjs` descriptor or a
984
+ * `<name>Theme` source, at any depth, linked or not.
985
+ * @param {{files: string[], symlinks: string[]}} listing
986
+ * @returns {string | undefined}
987
+ */
988
+ function themeEvidence({files, symlinks}) {
989
+ return [...files, ...symlinks].sort().find(file => {
990
+ const name = path.posix.basename(file);
991
+ return name.endsWith(THEME_DOC_SUFFIX) || THEME_SOURCE_RE.test(name);
992
+ });
993
+ }
994
+
995
+ /**
996
+ * Whether discovery reads a folder under a theme root as a theme. Dot-folders
997
+ * and folders with neither a descriptor nor a `<name>Theme` source are not.
998
+ * @param {string} folder absolute path
999
+ */
1000
+ export function isThemeFolder(folder) {
1001
+ return (
1002
+ !isIgnoredThemeEntry(path.basename(folder)) &&
1003
+ themeEvidence(listThemeFolder(folder)) !== undefined
1004
+ );
1005
+ }
1006
+
1007
+ /**
1008
+ * The files one theme folder ships: every regular file below it except
1009
+ * {@link isIgnoredThemeEntry} entries. What pack-check requires and what
1010
+ * `theme add` copies.
1011
+ * @param {string} folder absolute path
1012
+ * @returns {string[]} sorted POSIX paths relative to the folder
1013
+ */
1014
+ export function listThemeFiles(folder) {
1015
+ return listThemeFolder(folder).files;
1016
+ }
1017
+
1018
+ /** @param {string[]} files */
1019
+ function quoted(files) {
1020
+ return files.map(file => `"${file}"`).join(', ');
1021
+ }
1022
+
1023
+ /**
1024
+ * The files `theme add` has always copied after a bundled theme's entry, in
1025
+ * copy order. SYNC: scripts/generate-cli-themes.mjs bundles these.
1026
+ * @param {string} id the theme's export name without `Theme`
1027
+ */
1028
+ function bundledThemeArtifacts(id) {
1029
+ return [
1030
+ 'icons.tsx',
1031
+ `${id}Palettes.ts`,
1032
+ `${id}Palettes.generated.ts`,
1033
+ `${id}PaletteRefs.generated.ts`,
1034
+ `${id}Palettes.generated.receipt.json`,
1035
+ 'palette.config.json',
1036
+ ];
1037
+ }
1038
+
1039
+ /**
1040
+ * The files `theme add` copies, entry first. A bundled theme's descriptor is
1041
+ * the CLI's own metadata and stays behind; an integration theme copies its
1042
+ * whole directory.
1043
+ * @param {string[]} files every regular file in the theme directory, sorted
1044
+ * @param {string} entry
1045
+ * @param {string} descriptor
1046
+ * @param {string} exportName
1047
+ * @param {boolean} bundled
1048
+ */
1049
+ function copiedThemeFiles(files, entry, descriptor, exportName, bundled) {
1050
+ const rest = files.filter(
1051
+ file => file !== entry && !(bundled && file === descriptor),
1052
+ );
1053
+ if (!bundled) return [entry, ...rest];
1054
+ const order = bundledThemeArtifacts(exportName.replace(/Theme$/u, ''));
1055
+ return [
1056
+ entry,
1057
+ ...order.filter(file => rest.includes(file)),
1058
+ ...rest.filter(file => !order.includes(file)),
1059
+ ];
1060
+ }
1061
+
1062
+ /**
1063
+ * Discover and validate one theme root.
1064
+ * @param {string} themeRoot absolute root containing one directory per slug
1065
+ * @param {string} owner package that owns the root
1066
+ * @param {{bundled?: boolean}} [options] a bundled root is the CLI's own; its
1067
+ * sources are checked by the CLI's tests rather than on every read
1068
+ * @returns {DiscoveredTheme[]}
1069
+ */
1070
+ export function discoverThemeDirectory(
1071
+ themeRoot,
1072
+ owner,
1073
+ {bundled = false} = {},
1074
+ ) {
1075
+ if (!fs.existsSync(themeRoot) || !fs.statSync(themeRoot).isDirectory()) {
1076
+ throw new Error(
1077
+ `Declared themes root does not exist on disk: ${themeRoot}`,
1078
+ );
1079
+ }
1080
+ if (fs.existsSync(path.join(themeRoot, 'manifest.json'))) {
1081
+ throw new Error(
1082
+ `Theme root for ${owner} contains manifest.json, the theme catalog Astryx 0.6 wrote; each theme now carries a strongly typed same-stem .doc.mjs descriptor instead. To convert it, run \`astryx upgrade --from 0.6.3 --path . --apply\` in the package, or add each theme's descriptor and delete manifest.json.`,
1083
+ );
475
1084
  }
476
1085
 
477
1086
  /** @type {DiscoveredTheme[]} */
478
1087
  const themes = [];
479
1088
  const slugs = new Set();
480
- for (const [index, raw] of catalog.themes.entries()) {
481
- if (raw == null || typeof raw !== 'object' || Array.isArray(raw)) {
482
- throw new Error(
483
- `Theme catalog for ${owner} has an invalid entry at index ${index}.`,
484
- );
485
- }
486
- const entry = /** @type {Record<string, unknown>} */ (raw);
487
- const slug = requiredString(entry.slug, `themes[${index}].slug`, owner);
488
- if (!/^[a-z][a-z0-9]*(?:-[a-z0-9]+)*$/u.test(slug)) {
1089
+ const rootEntries = fs
1090
+ .readdirSync(themeRoot, {withFileTypes: true})
1091
+ .filter(entry => !isIgnoredThemeEntry(entry.name));
1092
+ const rootDescriptor = rootEntries.find(
1093
+ entry => entry.isFile() && entry.name.endsWith(THEME_DOC_SUFFIX),
1094
+ );
1095
+ if (rootDescriptor) {
1096
+ throw new Error(
1097
+ `Theme descriptor "${rootDescriptor.name}" for ${owner} must be inside a lower-kebab theme directory.`,
1098
+ );
1099
+ }
1100
+ const rootSymlink = rootEntries.find(entry => entry.isSymbolicLink());
1101
+ if (rootSymlink) {
1102
+ throw new Error(
1103
+ `Theme root for ${owner} contains symlink "${rootSymlink.name}"; theme directories must stay inside the declared root.`,
1104
+ );
1105
+ }
1106
+ const directories = rootEntries
1107
+ .filter(entry => entry.isDirectory())
1108
+ .sort((a, b) => a.name.localeCompare(b.name));
1109
+
1110
+ for (const directory of directories) {
1111
+ const slug = directory.name;
1112
+ const listing = listThemeFolder(path.join(themeRoot, slug));
1113
+ const evidence = themeEvidence(listing);
1114
+ if (evidence === undefined) continue;
1115
+ if (!THEME_SLUG_RE.test(slug)) {
489
1116
  throw new Error(
490
- `Theme catalog for ${owner} has invalid slug "${slug}"; use lowercase kebab-case starting with a letter.`,
1117
+ `Theme root for ${owner} has invalid directory "${slug}"; use lowercase kebab-case starting with a letter.`,
491
1118
  );
492
1119
  }
493
1120
  const normalizedSlug = slug.toLowerCase();
494
1121
  if (slugs.has(normalizedSlug)) {
495
1122
  throw new Error(
496
- `Theme catalog for ${owner} declares duplicate slug "${slug}".`,
1123
+ `Theme root for ${owner} declares duplicate slug "${slug}".`,
497
1124
  );
498
1125
  }
499
1126
  slugs.add(normalizedSlug);
500
1127
 
501
- const displayName = requiredString(
502
- entry.displayName,
503
- `theme "${slug}" displayName`,
504
- owner,
1128
+ const sourceDir = resolveThemePath(
1129
+ slug,
1130
+ themeRoot,
1131
+ `theme "${slug}" directory`,
505
1132
  );
506
- if (typeof entry.description !== 'string') {
507
- throw new Error(
508
- `Theme catalog for ${owner} has an invalid description for "${slug}".`,
509
- );
510
- }
511
- if (typeof entry.maintained !== 'boolean') {
1133
+ const theme = `Theme "${slug}" for ${owner}`;
1134
+ if (listing.symlinks.length > 0) {
512
1135
  throw new Error(
513
- `Theme catalog for ${owner} has an invalid maintained flag for "${slug}".`,
1136
+ `${theme} contains symlink "${listing.symlinks[0]}"; theme files must be regular files inside the theme directory.`,
514
1137
  );
515
1138
  }
516
- const entryFile = requiredString(
517
- entry.entry,
518
- `theme "${slug}" entry`,
519
- owner,
520
- );
521
- const exportName = requiredString(
522
- entry.exportName,
523
- `theme "${slug}" exportName`,
524
- owner,
1139
+ const descriptors = listing.files.filter(file =>
1140
+ file.endsWith(THEME_DOC_SUFFIX),
525
1141
  );
526
- if (!/^[$A-Z_a-z][$\w]*$/u.test(exportName)) {
1142
+ const docs = descriptors.filter(file => !file.includes('/'));
1143
+ if (docs.length !== 1) {
1144
+ const detail =
1145
+ docs.length > 1
1146
+ ? `: ${quoted(docs)}`
1147
+ : evidence.includes('/')
1148
+ ? `; "${evidence}" is in a subfolder`
1149
+ : ` beside "${evidence}"`;
527
1150
  throw new Error(
528
- `Theme catalog for ${owner} has invalid exportName "${exportName}".`,
1151
+ `${theme} must contain exactly one same-stem .doc.mjs descriptor; found ${docs.length}${detail}.`,
529
1152
  );
530
1153
  }
531
- if (!Array.isArray(entry.files) || entry.files.length === 0) {
1154
+ const nested = descriptors.find(file => file.includes('/'));
1155
+ if (nested) {
532
1156
  throw new Error(
533
- `Theme catalog for ${owner} theme "${slug}" must list at least one file.`,
1157
+ `${theme} contains more than one .doc.mjs descriptor: "${docs[0]}" and "${nested}".`,
534
1158
  );
535
1159
  }
536
1160
 
537
- const files = entry.files.map((file, fileIndex) =>
538
- requiredString(file, `theme "${slug}" files[${fileIndex}]`, owner),
1161
+ const docPath = resolveThemePath(
1162
+ docs[0],
1163
+ sourceDir,
1164
+ `theme "${slug}" descriptor`,
539
1165
  );
540
- if (new Set(files).size !== files.length) {
1166
+ const label = themeDescriptorLabel(docPath, owner);
1167
+ const exportName = docs[0].slice(0, -THEME_DOC_SUFFIX.length);
1168
+ if (!/^[$A-Z_a-z][$\w]*$/u.test(exportName)) {
541
1169
  throw new Error(
542
- `Theme catalog for ${owner} theme "${slug}" lists a file more than once.`,
1170
+ `${label} has stem "${exportName}", which is not a valid runtime export name.`,
543
1171
  );
544
1172
  }
545
- if (!files.includes(entryFile)) {
1173
+ const sources = THEME_MODULE_EXTENSIONS.map(
1174
+ extension => `${exportName}${extension}`,
1175
+ ).filter(file => listing.files.includes(file));
1176
+ if (sources.length !== 1) {
546
1177
  throw new Error(
547
- `Theme catalog for ${owner} theme "${slug}" must include entry "${entryFile}" in files.`,
1178
+ `${theme} must contain exactly one same-stem source for ${docs[0]}; found ${sources.length}${sources.length > 1 ? `: ${quoted(sources)}` : ''}.`,
548
1179
  );
549
1180
  }
550
- if (!/\.(?:ts|tsx|mjs|js)$/u.test(entryFile)) {
1181
+
1182
+ const doc = readThemeDoc(docPath, owner);
1183
+ if (doc.name !== slug) {
551
1184
  throw new Error(
552
- `Theme catalog for ${owner} theme "${slug}" entry must be source code.`,
1185
+ `${label} names "${doc.name}" but its directory is "${slug}".`,
553
1186
  );
554
1187
  }
555
-
556
- const sourceDir = resolveThemePath(
557
- slug,
558
- themeRoot,
559
- `theme "${slug}" directory`,
1188
+ const entry = sources[0];
1189
+ const entryPath = resolveThemePath(
1190
+ entry,
1191
+ sourceDir,
1192
+ `theme "${slug}" entry`,
560
1193
  );
561
- if (!fs.existsSync(sourceDir) || !fs.statSync(sourceDir).isDirectory()) {
562
- throw new Error(
563
- `Theme catalog for ${owner} is missing directory "${slug}".`,
1194
+
1195
+ if (!bundled) {
1196
+ const allowedFiles = new Set(
1197
+ listing.files
1198
+ .map(file =>
1199
+ resolveThemePath(file, sourceDir, `theme "${slug}" file`),
1200
+ )
1201
+ .filter(file => file !== docPath),
564
1202
  );
565
- }
566
- for (const file of files) {
567
- const source = resolveThemePath(file, sourceDir, `theme "${slug}" file`);
568
- if (!fs.existsSync(source) || !fs.statSync(source).isFile()) {
1203
+ const jscodeshift = sourceParser();
1204
+ try {
1205
+ validateThemeModuleGraph(
1206
+ entryPath,
1207
+ jscodeshift,
1208
+ sourceDir,
1209
+ allowedFiles,
1210
+ owner,
1211
+ entry,
1212
+ docPath,
1213
+ );
1214
+ if (
1215
+ !moduleExportsName(
1216
+ entryPath,
1217
+ exportName,
1218
+ jscodeshift,
1219
+ sourceDir,
1220
+ allowedFiles,
1221
+ )
1222
+ ) {
1223
+ throw new ThemeRuntimeExportError(
1224
+ `Theme "${slug}" for ${owner} entry "${entry}" does not export "${exportName}".`,
1225
+ );
1226
+ }
1227
+ } catch (error) {
1228
+ if (
1229
+ error instanceof ThemeModuleReferenceError ||
1230
+ error instanceof ThemeRuntimeExportError
1231
+ ) {
1232
+ throw error;
1233
+ }
1234
+ const message = error instanceof Error ? error.message : String(error);
569
1235
  throw new Error(
570
- `Theme catalog for ${owner} theme "${slug}" is missing file "${file}".`,
1236
+ `Theme "${slug}" for ${owner} entry "${entry}" could not be parsed: ${message}`,
1237
+ {cause: error},
571
1238
  );
572
1239
  }
573
1240
  }
574
1241
 
575
1242
  themes.push({
576
1243
  slug,
577
- displayName,
578
- description: entry.description,
579
- maintained: entry.maintained,
580
- entry: entryFile,
1244
+ displayName: doc.displayName,
1245
+ description: doc.description,
1246
+ maintained: doc.maintained,
1247
+ entry,
581
1248
  exportName,
582
- files,
1249
+ files: copiedThemeFiles(
1250
+ listing.files,
1251
+ entry,
1252
+ docs[0],
1253
+ exportName,
1254
+ bundled,
1255
+ ),
583
1256
  package: owner,
584
1257
  sourceDir,
585
1258
  bundled,
1259
+ docPath,
586
1260
  });
587
1261
  }
588
1262
 
589
1263
  return themes;
590
1264
  }
591
1265
 
592
- /** @returns {DiscoveredTheme[]} */
593
- export function discoverBundledThemes() {
594
- return discoverThemeCatalog(THEMES_DIR, BUNDLED_THEME_PACKAGE, {
595
- bundled: true,
1266
+ /** A JavaScript or TypeScript module. */
1267
+ const MODULE_FILE_RE = /\.(?:[cm]?[jt]s|[jt]sx)$/u;
1268
+
1269
+ /**
1270
+ * Whether a folder's files look like an attempt at a theme: a doc file of any
1271
+ * suffix, a file named for a theme, an index module, or a module named for
1272
+ * the folder.
1273
+ * @param {string} folder
1274
+ * @param {string[]} files
1275
+ */
1276
+ function looksLikeTheme(folder, files) {
1277
+ const own = path.basename(folder).toLowerCase();
1278
+ return files.some(file => {
1279
+ const name = path.posix.basename(file).toLowerCase();
1280
+ const stem = name.replace(/\.[^.]+$/u, '');
1281
+ return (
1282
+ /\.doc\.[^.]+$/u.test(name) ||
1283
+ /theme/u.test(name) ||
1284
+ (MODULE_FILE_RE.test(name) && (stem === 'index' || stem === own))
1285
+ );
596
1286
  });
597
1287
  }
598
1288
 
599
1289
  /**
600
- * @param {import('../integrations/integrations.mjs').LoadedIntegration} integration
601
- * @returns {Promise<DiscoveredTheme[]>}
1290
+ * Folders under a themes root that look like themes (see looksLikeTheme)
1291
+ * but that discovery does not read as themes, and that no module elsewhere
1292
+ * under the root imports, by a relative path or through the package's own
1293
+ * name. Discovery skips them silently, as the released catalog skipped
1294
+ * unlisted folders; doctor warns.
1295
+ * @param {string} themeRoot
1296
+ * @param {{packageDir?: string, packageName?: string}} [owner]
1297
+ * @returns {string[]} absolute folder paths, sorted
602
1298
  */
603
- export async function discoverIntegrationThemes(integration) {
604
- if (!integration.themes) return [];
605
- const themes = discoverThemeCatalog(integration.themes, integration.name);
606
- const jscodeshift = (await import('jscodeshift')).default;
607
- for (const theme of themes) {
608
- const allowedFiles = new Set(
609
- theme.files.map(file =>
610
- resolveThemePath(file, theme.sourceDir, `theme "${theme.slug}" file`),
611
- ),
612
- );
613
- const entryPath = resolveThemePath(
614
- theme.entry,
615
- theme.sourceDir,
616
- `theme "${theme.slug}" entry`,
1299
+ export function unreadThemeFolders(themeRoot, {packageDir, packageName} = {}) {
1300
+ if (!fs.existsSync(themeRoot) || !fs.statSync(themeRoot).isDirectory()) {
1301
+ return [];
1302
+ }
1303
+ const folders = fs
1304
+ .readdirSync(themeRoot, {withFileTypes: true})
1305
+ .filter(entry => entry.isDirectory() && !isIgnoredThemeEntry(entry.name))
1306
+ .map(entry => path.join(themeRoot, entry.name))
1307
+ .sort();
1308
+ const unread = folders.filter(folder => {
1309
+ if (isThemeFolder(folder)) return false;
1310
+ const {files} = listThemeFolder(folder);
1311
+ return (
1312
+ files.some(file => MODULE_FILE_RE.test(file)) &&
1313
+ looksLikeTheme(folder, files)
617
1314
  );
618
- let exportsName;
1315
+ });
1316
+ if (unread.length === 0) return [];
1317
+
1318
+ const modules = [
1319
+ ...fs
1320
+ .readdirSync(themeRoot, {withFileTypes: true})
1321
+ .filter(
1322
+ entry =>
1323
+ entry.isFile() &&
1324
+ !isIgnoredThemeEntry(entry.name) &&
1325
+ MODULE_FILE_RE.test(entry.name),
1326
+ )
1327
+ .map(entry => path.join(themeRoot, entry.name)),
1328
+ ...folders.flatMap(folder =>
1329
+ listThemeFolder(folder)
1330
+ .files.filter(file => MODULE_FILE_RE.test(file))
1331
+ .map(file => path.join(folder, file)),
1332
+ ),
1333
+ ];
1334
+ const self = packageName ? `${packageName}/` : null;
1335
+ /** @type {string[]} */
1336
+ const imported = [];
1337
+ for (const file of modules) {
1338
+ let specifiers;
619
1339
  try {
620
- validateThemeModuleGraph(
621
- entryPath,
622
- jscodeshift,
623
- theme.sourceDir,
624
- allowedFiles,
625
- theme.package,
626
- theme.entry,
627
- );
628
- exportsName = moduleExportsName(
629
- entryPath,
630
- theme.exportName,
631
- jscodeshift,
632
- theme.sourceDir,
633
- allowedFiles,
634
- );
635
- } catch (error) {
636
- if (error instanceof ThemeModuleReferenceError) throw error;
637
- const message = error instanceof Error ? error.message : String(error);
638
- throw new Error(
639
- `Theme catalog for ${theme.package} entry "${theme.entry}" could not be parsed: ${message}`,
640
- {cause: error},
641
- );
1340
+ specifiers = moduleSpecifiers(file, sourceParser());
1341
+ } catch {
1342
+ continue;
642
1343
  }
643
- if (!exportsName) {
644
- throw new Error(
645
- `Theme catalog for ${theme.package} entry "${theme.entry}" does not export "${theme.exportName}".`,
646
- );
1344
+ for (const specifier of specifiers) {
1345
+ if (specifier.startsWith('.')) {
1346
+ imported.push(path.resolve(path.dirname(file), specifier));
1347
+ } else if (self && packageDir && specifier.startsWith(self)) {
1348
+ imported.push(path.resolve(packageDir, specifier.slice(self.length)));
1349
+ }
647
1350
  }
648
1351
  }
649
- return themes;
1352
+ return unread.filter(
1353
+ folder =>
1354
+ !imported.some(
1355
+ target => target === folder || target.startsWith(folder + path.sep),
1356
+ ),
1357
+ );
1358
+ }
1359
+
1360
+ /** @type {DiscoveredTheme[] | null} */
1361
+ let bundledThemeCache = null;
1362
+
1363
+ /** @returns {DiscoveredTheme[]} */
1364
+ export function discoverBundledThemes() {
1365
+ bundledThemeCache ??= discoverThemeDirectory(
1366
+ THEMES_DIR,
1367
+ BUNDLED_THEME_PACKAGE,
1368
+ {bundled: true},
1369
+ );
1370
+ return bundledThemeCache.map(theme => ({...theme, files: [...theme.files]}));
1371
+ }
1372
+
1373
+ /**
1374
+ * @param {import('../integrations/integrations.mjs').LoadedIntegration} integration
1375
+ * @returns {Promise<DiscoveredTheme[]>}
1376
+ */
1377
+ export async function discoverIntegrationThemes(integration) {
1378
+ if (!integration.themes) return [];
1379
+ return discoverThemeDirectory(integration.themes, integration.name);
650
1380
  }