@astryxdesign/cli 0.6.3 → 0.6.4-canary.0e1fbdb

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
@@ -0,0 +1,1284 @@
1
+ // Copyright (c) Meta Platforms, Inc. and affiliates.
2
+
3
+ /**
4
+ * @file The `Fix:` advice for contribution metadata that no root reads, or
5
+ * that the wrong root reads.
6
+ *
7
+ * A fix must hold when followed literally. It never names a path outside the
8
+ * package, never makes the package root a root, and names only the offending
9
+ * file plus the same-stem source that moves with it. It offers a folder as a
10
+ * root only when that root would read nothing but this kind of metadata,
11
+ * overlap no other root, and orphan nothing the current root holds. Metadata
12
+ * is read statically: nothing here runs package code.
13
+ *
14
+ * @input a package directory, its resolved roots, and one misplaced file
15
+ * @output the `Fix: ...` sentence for that file
16
+ * @position packages/cli/foundation/integrations — shared by the unreachable
17
+ * scan in api/integration/validate-integration.mjs and the component checks
18
+ * in validate-contributions.mjs
19
+ */
20
+
21
+ import * as fs from 'node:fs';
22
+ import * as path from 'node:path';
23
+ import jscodeshift from 'jscodeshift';
24
+ import {isValidSemver} from '../env/semver.mjs';
25
+ import {
26
+ isThemeFolder as readsAsTheme,
27
+ THEME_DOC_SUFFIX,
28
+ THEME_MODULE_EXTENSIONS,
29
+ THEME_SLUG_RE,
30
+ } from '../discovery/theme-discovery.mjs';
31
+
32
+ /** @typedef {'components' | 'templates' | 'docs' | 'themes'} MetadataRoot */
33
+
34
+ /**
35
+ * @typedef {object} ContributionStamp
36
+ * @property {string} type
37
+ * @property {string | null} name the static `name`, when it has one
38
+ */
39
+
40
+ /**
41
+ * @typedef {object} FixContext
42
+ * @property {string} packageDir
43
+ * @property {string} manifest file name of the package's manifest
44
+ * @property {Partial<Record<MetadataRoot | 'codemods', string>>} roots
45
+ * declared roots, absolute
46
+ * @property {Map<string, ContributionStamp | null>} stamps
47
+ * @property {Map<string, string[] | null>} candidates
48
+ */
49
+
50
+ export const DOC_CANDIDATE_RE = /\.doc\.(?:ts|mjs|js)$/u;
51
+ export const TEMPLATE_CANDIDATE_RE = /\.template\.(?:ts|mjs|js)$/u;
52
+ const STATIC_DOC_TYPES = new Set([
53
+ 'component',
54
+ 'generic',
55
+ 'page',
56
+ 'block',
57
+ 'theme',
58
+ ]);
59
+ const STATIC_TEMPLATE_TYPES = new Set(['page', 'block']);
60
+ /** Stray-codemod and version-folder rules, shared with the codemod checks. */
61
+ export const CODEMOD_FILE_RE = /\.(?:ts|mjs|js)$/u;
62
+ export const CODEMOD_TEST_RE = /\.(?:test|spec|fixture)\.(?:ts|mjs|js)$/u;
63
+ export const CODEMOD_SKIP_DIRS = new Set([
64
+ 'node_modules',
65
+ '.git',
66
+ '__tests__',
67
+ '__fixtures__',
68
+ ]);
69
+ /** @type {MetadataRoot[]} */
70
+ const METADATA_ROOTS = ['components', 'templates', 'docs', 'themes'];
71
+ /** Past this many entries a folder's contents count as unknown. */
72
+ const FOLDER_SCAN_LIMIT = 5_000;
73
+
74
+ /** The manifest root that reads each kind of contribution metadata. */
75
+ export const ROOT_FOR_TYPE = /** @type {Record<string, MetadataRoot>} */ ({
76
+ component: 'components',
77
+ generic: 'docs',
78
+ page: 'templates',
79
+ block: 'templates',
80
+ theme: 'themes',
81
+ });
82
+
83
+ /** What a root holds, as a fix names it. */
84
+ const ROOT_HOLDS = /** @type {Record<MetadataRoot, string>} */ ({
85
+ components: 'components',
86
+ docs: 'topics',
87
+ templates: 'templates',
88
+ themes: 'themes',
89
+ });
90
+
91
+ const j = jscodeshift.withParser('tsx');
92
+
93
+ /** @param {any} node @returns {any} */
94
+ function unwrapStaticExpression(node) {
95
+ let current = node;
96
+ while (
97
+ current &&
98
+ [
99
+ 'TSSatisfiesExpression',
100
+ 'TSAsExpression',
101
+ 'TypeCastExpression',
102
+ 'ParenthesizedExpression',
103
+ ].includes(current.type)
104
+ ) {
105
+ current = current.expression;
106
+ }
107
+ if (current?.type === 'CallExpression' && current.arguments.length > 0) {
108
+ current = unwrapStaticExpression(current.arguments[0]);
109
+ }
110
+ return current;
111
+ }
112
+
113
+ /** @param {any} property @param {string} name */
114
+ function staticPropertyNamed(property, name) {
115
+ if (
116
+ !property ||
117
+ !['ObjectProperty', 'Property'].includes(property.type) ||
118
+ property.computed
119
+ ) {
120
+ return false;
121
+ }
122
+ return (
123
+ (property.key?.type === 'Identifier' && property.key.name === name) ||
124
+ (['Literal', 'StringLiteral'].includes(property.key?.type) &&
125
+ property.key.value === name)
126
+ );
127
+ }
128
+
129
+ /**
130
+ * Identify contribution metadata without importing it. Doctor scans files that
131
+ * the manifest does not declare, so executing those files would run code the
132
+ * package never asked Astryx to load.
133
+ *
134
+ * @param {string} file
135
+ * @param {boolean} templateOnly
136
+ * @returns {ContributionStamp | null} null when it is not contribution metadata
137
+ */
138
+ export function readContributionStamp(file, templateOnly) {
139
+ let ast;
140
+ try {
141
+ ast = j(fs.readFileSync(file, 'utf-8'));
142
+ } catch {
143
+ return null;
144
+ }
145
+ /** @type {any[]} */
146
+ const candidates = [];
147
+ ast
148
+ .find(j.ExportDefaultDeclaration)
149
+ .forEach((/** @type {any} */ exportPath) => {
150
+ candidates.push(exportPath.value.declaration);
151
+ });
152
+ ast
153
+ .find(j.ExportNamedDeclaration)
154
+ .forEach((/** @type {any} */ exportPath) => {
155
+ const declaration = exportPath.value.declaration;
156
+ if (declaration?.type !== 'VariableDeclaration') return;
157
+ for (const declarator of declaration.declarations) {
158
+ if (
159
+ declarator.id?.type === 'Identifier' &&
160
+ declarator.id.name === 'docs'
161
+ ) {
162
+ candidates.push(declarator.init);
163
+ }
164
+ }
165
+ });
166
+
167
+ const allowedTypes = templateOnly ? STATIC_TEMPLATE_TYPES : STATIC_DOC_TYPES;
168
+ for (const candidate of candidates) {
169
+ const object = unwrapStaticExpression(candidate);
170
+ if (object?.type !== 'ObjectExpression') continue;
171
+ /** @param {string} name @returns {unknown} */
172
+ const field = name =>
173
+ unwrapStaticExpression(
174
+ object.properties.find((/** @type {any} */ property) =>
175
+ staticPropertyNamed(property, name),
176
+ )?.value,
177
+ );
178
+ const type = /** @type {any} */ (field('type'));
179
+ if (
180
+ ['Literal', 'StringLiteral'].includes(type?.type) &&
181
+ allowedTypes.has(type.value)
182
+ ) {
183
+ const name = /** @type {any} */ (field('name'));
184
+ return {
185
+ type: type.value,
186
+ name: typeof name?.value === 'string' ? name.value : null,
187
+ };
188
+ }
189
+ }
190
+ return null;
191
+ }
192
+
193
+ /** @param {string} candidate @param {string} root */
194
+ export function pathIsInside(candidate, root) {
195
+ return candidate === root || candidate.startsWith(`${root}${path.sep}`);
196
+ }
197
+
198
+ /**
199
+ * @param {string} packageDir
200
+ * @param {{components?: string, templates?: string, codemods?: string, docs?: string, themes?: string, __manifestFile?: string}} integration
201
+ * @returns {FixContext}
202
+ */
203
+ export function createFixContext(packageDir, integration) {
204
+ return {
205
+ packageDir,
206
+ manifest: integration.__manifestFile
207
+ ? path.basename(integration.__manifestFile)
208
+ : 'astryx.integration.mjs',
209
+ roots: {
210
+ components: integration.components,
211
+ templates: integration.templates,
212
+ codemods: integration.codemods,
213
+ docs: integration.docs,
214
+ themes: integration.themes,
215
+ },
216
+ stamps: new Map(),
217
+ candidates: new Map(),
218
+ };
219
+ }
220
+
221
+ /**
222
+ * The static stamp of a candidate metadata file, read once per context.
223
+ * @param {FixContext} context
224
+ * @param {string} file
225
+ * @returns {ContributionStamp | null}
226
+ */
227
+ export function stampOf(context, file) {
228
+ if (!context.stamps.has(file)) {
229
+ context.stamps.set(
230
+ file,
231
+ readContributionStamp(file, TEMPLATE_CANDIDATE_RE.test(file)),
232
+ );
233
+ }
234
+ return context.stamps.get(file) ?? null;
235
+ }
236
+
237
+ /** @param {FixContext} context @param {string} file */
238
+ function shown(context, file) {
239
+ return path.relative(context.packageDir, file).split(path.sep).join('/');
240
+ }
241
+
242
+ /** @param {FixContext} context */
243
+ function declaredRoots(context) {
244
+ return /** @type {string[]} */ (Object.values(context.roots).filter(Boolean));
245
+ }
246
+
247
+ /** @param {string} file */
248
+ function isFile(file) {
249
+ try {
250
+ return fs.statSync(file).isFile();
251
+ } catch {
252
+ return false;
253
+ }
254
+ }
255
+
256
+ /**
257
+ * Every doc or template file under `dir`, or null when there are too many
258
+ * entries to know.
259
+ * @param {FixContext} context
260
+ * @param {string} dir
261
+ * @returns {string[] | null}
262
+ */
263
+ function candidatesUnder(context, dir) {
264
+ if (context.candidates.has(dir)) {
265
+ return context.candidates.get(dir) ?? null;
266
+ }
267
+ /** @type {string[] | null} */
268
+ let found = [];
269
+ let seen = 0;
270
+ /** @param {string} current */
271
+ const walk = current => {
272
+ let entries;
273
+ try {
274
+ entries = fs.readdirSync(current, {withFileTypes: true});
275
+ } catch {
276
+ return;
277
+ }
278
+ for (const entry of entries) {
279
+ if (found == null) return;
280
+ if (entry.name === '.git') continue;
281
+ seen += 1;
282
+ if (seen > FOLDER_SCAN_LIMIT) {
283
+ found = null;
284
+ return;
285
+ }
286
+ const full = path.join(current, entry.name);
287
+ if (entry.isDirectory()) {
288
+ walk(full);
289
+ } else if (
290
+ entry.isFile() &&
291
+ (DOC_CANDIDATE_RE.test(entry.name) ||
292
+ TEMPLATE_CANDIDATE_RE.test(entry.name))
293
+ ) {
294
+ found.push(full);
295
+ }
296
+ }
297
+ };
298
+ walk(dir);
299
+ context.candidates.set(dir, found);
300
+ return found;
301
+ }
302
+
303
+ /**
304
+ * A folder inside the package, not its root, that neither holds nor sits in a
305
+ * declared root.
306
+ * @param {FixContext} context
307
+ * @param {string} dir
308
+ */
309
+ function isFreeFolder(context, dir) {
310
+ return (
311
+ dir !== context.packageDir &&
312
+ pathIsInside(dir, context.packageDir) &&
313
+ !declaredRoots(context).some(
314
+ root => pathIsInside(root, dir) || pathIsInside(dir, root),
315
+ )
316
+ );
317
+ }
318
+
319
+ /**
320
+ * Whether `dir` is one complete theme: a slug folder whose only metadata is
321
+ * its theme descriptor, naming that folder, beside exactly one source that
322
+ * imports nothing outside the folder. The descriptor being fixed may lack its
323
+ * source or import from outside; the fix asks for both.
324
+ * @param {FixContext} context
325
+ * @param {string} dir
326
+ * @param {string} [fixing]
327
+ */
328
+ function isCompleteTheme(context, dir, fixing) {
329
+ const found = candidatesUnder(context, dir);
330
+ if (found?.length !== 1) return false;
331
+ const [descriptor] = found;
332
+ const stamp = stampOf(context, descriptor);
333
+ const slug = path.basename(dir);
334
+ return (
335
+ THEME_SLUG_RE.test(slug) &&
336
+ path.dirname(descriptor) === dir &&
337
+ descriptor.endsWith(THEME_DOC_SUFFIX) &&
338
+ stamp?.type === 'theme' &&
339
+ stamp.name === slug &&
340
+ (descriptor === fixing ||
341
+ (sourcesBeside(descriptor, 'theme').length === 1 &&
342
+ importsOutside(descriptor)?.length === 0))
343
+ );
344
+ }
345
+
346
+ /**
347
+ * Whether `dir` reads cleanly as a themes root: every folder in it that
348
+ * discovery reads as a theme is one complete theme, and no metadata sits
349
+ * directly in it.
350
+ * @param {FixContext} context
351
+ * @param {string} dir
352
+ * @param {{except?: string, ignore?: string, fixing?: string}} [options] a
353
+ * theme folder moving out of `dir`, a descriptor moving away, and the
354
+ * descriptor being fixed
355
+ */
356
+ function readsAsThemesRoot(context, dir, {except, ignore, fixing} = {}) {
357
+ if (!fs.existsSync(dir)) return true;
358
+ if (fs.existsSync(path.join(dir, 'manifest.json'))) return false;
359
+ let entries;
360
+ try {
361
+ entries = fs.readdirSync(dir, {withFileTypes: true});
362
+ } catch {
363
+ return false;
364
+ }
365
+ return entries.every(entry => {
366
+ const full = path.join(dir, entry.name);
367
+ if (entry.name.startsWith('.')) return true;
368
+ if (entry.isSymbolicLink()) return false;
369
+ if (entry.isDirectory()) {
370
+ return (
371
+ full === except ||
372
+ !readsAsTheme(full) ||
373
+ isCompleteTheme(context, full, fixing)
374
+ );
375
+ }
376
+ return (
377
+ full === ignore ||
378
+ !(
379
+ DOC_CANDIDATE_RE.test(entry.name) ||
380
+ TEMPLATE_CANDIDATE_RE.test(entry.name)
381
+ )
382
+ );
383
+ });
384
+ }
385
+
386
+ /**
387
+ * Whether declaring `dir` as the `key` root reads only complete metadata of
388
+ * that kind, overlaps no root, and stays inside the package below its root.
389
+ * The file being fixed may lack its source; the fix asks for it.
390
+ * @param {FixContext} context
391
+ * @param {string} dir
392
+ * @param {MetadataRoot} key
393
+ * @param {string} fixing
394
+ */
395
+ function canBeRoot(context, dir, key, fixing) {
396
+ if (!isFreeFolder(context, dir)) return false;
397
+ if (key === 'themes') return readsAsThemesRoot(context, dir, {fixing});
398
+ const found = candidatesUnder(context, dir);
399
+ if (found == null) return false;
400
+ const complete = found.every(file => {
401
+ const stamp = stampOf(context, file);
402
+ return (
403
+ stamp != null &&
404
+ ROOT_FOR_TYPE[stamp.type] === key &&
405
+ (file === fixing ||
406
+ stamp.type === 'generic' ||
407
+ sourcesBeside(file, stamp.type).length > 0)
408
+ );
409
+ });
410
+ const names = found.map(file => identity(context, file, key));
411
+ const unique = new Set(names).size === names.length;
412
+ return (
413
+ complete &&
414
+ unique &&
415
+ (key !== 'components' || uncoveredSources(dir, found).length === 0)
416
+ );
417
+ }
418
+
419
+ /**
420
+ * The name a root knows a contribution by, and must hold only once: a
421
+ * component's stem, a topic's lower-cased \`name\`, a template's path without
422
+ * its suffix. A file with no static name gets its path, which never repeats.
423
+ * @param {FixContext} context
424
+ * @param {string} file
425
+ * @param {MetadataRoot} key
426
+ */
427
+ function identity(context, file, key) {
428
+ if (key === 'components') return docStem(file);
429
+ if (key === 'docs')
430
+ return stampOf(context, file)?.name?.toLowerCase() ?? file;
431
+ return path.join(path.dirname(file), docStem(file).toLowerCase());
432
+ }
433
+
434
+ /**
435
+ * The PascalCase \`.tsx\` files directly in a folder that no doc under it
436
+ * covers. A components root there warns that Astryx ignores each one.
437
+ * @param {string} dir
438
+ * @param {string[]} found the doc and template files under it
439
+ * @returns {string[]} their file names
440
+ */
441
+ function uncoveredSources(dir, found) {
442
+ const covered = new Set(
443
+ found.filter(file => DOC_CANDIDATE_RE.test(file)).map(docStem),
444
+ );
445
+ try {
446
+ return fs
447
+ .readdirSync(dir, {withFileTypes: true})
448
+ .filter(entry => entry.isFile() || entry.isSymbolicLink())
449
+ .map(entry => entry.name)
450
+ .filter(name => {
451
+ const match = /^([A-Z][A-Za-z0-9]+)\.tsx$/u.exec(name);
452
+ return match != null && !covered.has(match[1]);
453
+ });
454
+ } catch {
455
+ return [];
456
+ }
457
+ }
458
+
459
+ /**
460
+ * Whether a folder holds an entry by this name, compared the way a
461
+ * case-insensitive file system would.
462
+ * @param {string} dir
463
+ * @param {string} name
464
+ */
465
+ function hasEntry(dir, name) {
466
+ const wanted = name.toLowerCase();
467
+ try {
468
+ return fs.readdirSync(dir).some(entry => entry.toLowerCase() === wanted);
469
+ } catch {
470
+ return false;
471
+ }
472
+ }
473
+
474
+ /**
475
+ * What already holds the name \`file\` would take once moved to the top of
476
+ * \`root\`, or null when the name is free.
477
+ * @param {FixContext} context
478
+ * @param {string} file
479
+ * @param {ContributionStamp} stamp
480
+ * @param {string} root
481
+ * @returns {string | null} e.g. \`a topic named "guide" (docs/guide.doc.mjs)\`
482
+ */
483
+ function takenIn(context, file, stamp, root) {
484
+ const key = ROOT_FOR_TYPE[stamp.type];
485
+ const stem = docStem(file);
486
+ const name = identity(context, path.join(root, path.basename(file)), key);
487
+ const same = (candidatesUnder(context, root) ?? []).find(
488
+ other =>
489
+ (key === 'templates' || DOC_CANDIDATE_RE.test(other)) &&
490
+ (key === 'docs'
491
+ ? stamp.name != null &&
492
+ stampOf(context, other)?.name?.toLowerCase() ===
493
+ stamp.name.toLowerCase()
494
+ : identity(context, other, key) === name),
495
+ );
496
+ if (same) {
497
+ const kind = {
498
+ components: 'component',
499
+ docs: 'topic',
500
+ templates: 'template',
501
+ }[/** @type {'components' | 'docs' | 'templates'} */ (key)];
502
+ const called = key === 'docs' ? `"${stamp.name}"` : stem;
503
+ return `a ${kind} named ${called} (${shown(context, same)})`;
504
+ }
505
+ const clash = [path.basename(file), ...sourcesBeside(file, stamp.type)].find(
506
+ entry => hasEntry(root, entry),
507
+ );
508
+ return clash ? `a file named ${clash}` : null;
509
+ }
510
+
511
+ /**
512
+ * The local modules a theme source reaches, read statically the way theme
513
+ * discovery reads them. Null when a file in the graph cannot be parsed.
514
+ * @param {string} entry
515
+ * @returns {Array<{importer: string, specifier: string, target: string}> | null}
516
+ */
517
+ function localImports(entry) {
518
+ /** @type {Array<{importer: string, specifier: string, target: string}>} */
519
+ const edges = [];
520
+ const seen = new Set();
521
+ const queue = [entry];
522
+ while (queue.length > 0) {
523
+ const file = /** @type {string} */ (queue.shift());
524
+ if (seen.has(file)) continue;
525
+ seen.add(file);
526
+ /** @type {string[]} */
527
+ const specifiers = [];
528
+ try {
529
+ const js = jscodeshift.withParser(
530
+ /\.(?:ts|tsx|mts)$/u.test(file) ? 'tsx' : 'babel',
531
+ );
532
+ const ast = js(fs.readFileSync(file, 'utf-8'));
533
+ /** @param {any} node */
534
+ const add = node => {
535
+ if (typeof node?.value === 'string') specifiers.push(node.value);
536
+ };
537
+ for (const type of [
538
+ js.ImportDeclaration,
539
+ js.ExportNamedDeclaration,
540
+ js.ExportAllDeclaration,
541
+ js.ImportExpression,
542
+ ]) {
543
+ ast.find(type).forEach((/** @type {any} */ found) => {
544
+ add(found.node.source);
545
+ });
546
+ }
547
+ ast.find(js.CallExpression).forEach((/** @type {any} */ call) => {
548
+ if (call.node.callee?.type === 'Import') add(call.node.arguments?.[0]);
549
+ });
550
+ } catch {
551
+ return null;
552
+ }
553
+ for (const specifier of specifiers) {
554
+ if (!specifier.startsWith('.')) continue;
555
+ const base = path.resolve(path.dirname(file), specifier);
556
+ const target = [
557
+ base,
558
+ ...THEME_MODULE_EXTENSIONS.map(extension => `${base}${extension}`),
559
+ ...THEME_MODULE_EXTENSIONS.map(extension =>
560
+ path.join(base, `index${extension}`),
561
+ ),
562
+ ].find(isFile);
563
+ if (!target) continue;
564
+ edges.push({importer: file, specifier, target});
565
+ if (THEME_MODULE_EXTENSIONS.includes(path.extname(target))) {
566
+ queue.push(target);
567
+ }
568
+ }
569
+ }
570
+ return edges;
571
+ }
572
+
573
+ /**
574
+ * A theme's imports that reach outside the folder its descriptor is in. Null
575
+ * when unknown.
576
+ * @param {string} file the theme descriptor
577
+ */
578
+ function importsOutside(file) {
579
+ const [source] = sourcesBeside(file, 'theme');
580
+ if (!source) return [];
581
+ const home = path.dirname(file);
582
+ const edges = localImports(path.join(home, source));
583
+ return edges && edges.filter(edge => !pathIsInside(edge.target, home));
584
+ }
585
+
586
+ /**
587
+ * The sentence that brings the files a theme imports from outside its folder
588
+ * into the folder: discovery rejects any import that leaves it. Files move
589
+ * with the folder keep their paths; outside files are copied to its top.
590
+ * @param {FixContext} context
591
+ * @param {string} file the theme descriptor
592
+ * @param {string} destination the theme folder, as the fix names it
593
+ * @returns {string} a sentence with a leading space, or ''
594
+ */
595
+ function themeImportNote(context, file, destination) {
596
+ const [source] = sourcesBeside(file, 'theme');
597
+ if (!source) return '';
598
+ const home = path.dirname(file);
599
+ const edges = localImports(path.join(home, source));
600
+ if (!edges) return '';
601
+ const copies = [
602
+ ...new Set(
603
+ edges
604
+ .map(edge => edge.target)
605
+ .filter(target => !pathIsInside(target, home)),
606
+ ),
607
+ ];
608
+ if (
609
+ copies.length === 0 ||
610
+ copies.some(target => !pathIsInside(target, context.packageDir))
611
+ ) {
612
+ return '';
613
+ }
614
+ const why = ': a theme can import only files inside its own folder.';
615
+ const listed = copies.map(target => shown(context, target));
616
+ const list =
617
+ listed.length === 1
618
+ ? listed[0]
619
+ : `${listed.slice(0, -1).join(', ')} and ${listed.at(-1)}`;
620
+ const names = copies.map(target => path.basename(target).toLowerCase());
621
+ if (
622
+ new Set(names).size !== names.length ||
623
+ copies.some(target => hasEntry(home, path.basename(target)))
624
+ ) {
625
+ return ` Also copy ${list} into ${destination} and point the imports of ${copies.length === 1 ? 'it at the copy' : 'them at the copies'}${why}`;
626
+ }
627
+ /** @param {string} target where a file ends up, relative to the theme folder */
628
+ const inTheme = target =>
629
+ (pathIsInside(target, home)
630
+ ? path.relative(home, target)
631
+ : path.basename(target)
632
+ )
633
+ .split(path.sep)
634
+ .join('/');
635
+ /** @type {string[]} */
636
+ const changes = [];
637
+ for (const {importer, specifier, target} of edges) {
638
+ const extension = path.extname(specifier);
639
+ let next = inTheme(target);
640
+ if (!(extension && target.endsWith(extension))) {
641
+ next = next.slice(0, next.length - path.extname(next).length);
642
+ }
643
+ next = path.posix.relative(path.posix.dirname(inTheme(importer)), next);
644
+ if (!next.startsWith('.')) next = `./${next}`;
645
+ const change = `of ${specifier} in ${path.basename(importer)} to ${next}`;
646
+ if (next !== specifier && !changes.includes(change)) changes.push(change);
647
+ }
648
+ const changed =
649
+ changes.length === 1
650
+ ? `the import ${changes[0]}`
651
+ : `the imports ${changes.slice(0, -1).join(', ')} and ${changes.at(-1)}`;
652
+ return ` Also copy ${list} into ${destination} and change ${changed}${why}`;
653
+ }
654
+
655
+ /**
656
+ * The same-stem sources beside a doc: a component's or template's `.tsx`, or
657
+ * a theme's entry modules (a theme needs exactly one).
658
+ * @param {string} file
659
+ * @param {string} type
660
+ * @returns {string[]} their file names
661
+ */
662
+ function sourcesBeside(file, type) {
663
+ if (type === 'generic') return [];
664
+ const dir = path.dirname(file);
665
+ const stem = docStem(file);
666
+ const names =
667
+ type === 'theme'
668
+ ? THEME_MODULE_EXTENSIONS.map(extension => `${stem}${extension}`)
669
+ : [`${stem}.tsx`];
670
+ return names.filter(name => isFile(path.join(dir, name)));
671
+ }
672
+
673
+ /** @param {string} file */
674
+ function docStem(file) {
675
+ return path
676
+ .basename(file)
677
+ .replace(DOC_CANDIDATE_RE, '')
678
+ .replace(TEMPLATE_CANDIDATE_RE, '');
679
+ }
680
+
681
+ /**
682
+ * The sentences asking for what the doc needs wherever it ends up: a missing
683
+ * same-stem source, and for a theme, the descriptor's `.doc.mjs` name.
684
+ * @param {string} file
685
+ * @param {string} type
686
+ * @returns {string} a sentence with a leading space, or ''
687
+ */
688
+ function sourceNote(file, type) {
689
+ // Theme discovery reads only `.doc.mjs` descriptors.
690
+ const rename =
691
+ type === 'theme' && !file.endsWith(THEME_DOC_SUFFIX)
692
+ ? ` Also rename ${path.basename(file)} to ${docStem(file)}${THEME_DOC_SUFFIX}: a theme descriptor is a ${THEME_DOC_SUFFIX} file.`
693
+ : '';
694
+ if (type === 'generic' || sourcesBeside(file, type).length > 0) return rename;
695
+ return type === 'theme'
696
+ ? `${rename} Also add its same-stem theme source, such as ${docStem(file)}.ts, beside it.`
697
+ : ` Also add ${docStem(file)}.tsx beside it.`;
698
+ }
699
+
700
+ /**
701
+ * How a fix names a folder.
702
+ * @param {FixContext} context
703
+ * @param {string} dir
704
+ */
705
+ function place(context, dir) {
706
+ return dir === context.packageDir
707
+ ? 'the package root'
708
+ : `${shown(context, dir)}/`;
709
+ }
710
+
711
+ /** @param {string} text */
712
+ function capitalize(text) {
713
+ return `${text.charAt(0).toUpperCase()}${text.slice(1)}`;
714
+ }
715
+
716
+ /**
717
+ * Whether codemods can move into `dir`: it is missing, or holds nothing the
718
+ * codemod checks would read as a stray codemod or a mis-named version.
719
+ * @param {string} dir
720
+ */
721
+ function holdsOnlyVersions(dir) {
722
+ if (!fs.existsSync(dir)) return true;
723
+ try {
724
+ return fs
725
+ .readdirSync(dir, {withFileTypes: true})
726
+ .every(entry =>
727
+ entry.isDirectory()
728
+ ? isValidSemver(entry.name) || CODEMOD_SKIP_DIRS.has(entry.name)
729
+ : !CODEMOD_FILE_RE.test(entry.name) ||
730
+ CODEMOD_TEST_RE.test(entry.name),
731
+ );
732
+ } catch {
733
+ return false;
734
+ }
735
+ }
736
+
737
+ /**
738
+ * The step that gives codemods a folder of their own, and the folder it names
739
+ * (null when codemods/ is taken and the author picks one).
740
+ * @param {FixContext} context
741
+ * @returns {{step: string, folder: string | null}}
742
+ */
743
+ function ownCodemodsFolder(context) {
744
+ const target = path.join(context.packageDir, 'codemods');
745
+ return context.roots.codemods !== target && holdsOnlyVersions(target)
746
+ ? {
747
+ step: `create codemods/, move each version folder into it, and set \`codemods: './codemods'\` in ${context.manifest}`,
748
+ folder: 'codemods',
749
+ }
750
+ : {
751
+ step: `move each version folder into a new folder of their own, and set \`codemods\` to that folder in ${context.manifest}`,
752
+ folder: null,
753
+ };
754
+ }
755
+
756
+ /**
757
+ * Declared roots other than codemods at or inside `dir`.
758
+ * @param {FixContext} context
759
+ * @param {string} dir
760
+ * @returns {Array<{kind: MetadataRoot, root: string}>}
761
+ */
762
+ function metadataRootsIn(context, dir) {
763
+ /** @type {Array<{kind: MetadataRoot, root: string}>} */
764
+ const found = [];
765
+ for (const kind of METADATA_ROOTS) {
766
+ const root = context.roots[kind];
767
+ if (root && pathIsInside(root, dir)) found.push({kind, root});
768
+ }
769
+ return found;
770
+ }
771
+
772
+ /**
773
+ * The fix for every codemods entry when the codemods root also holds the rest
774
+ * of the package: it is the package root, or it holds another root. No
775
+ * per-entry move or rename helps then.
776
+ * @param {FixContext} context
777
+ * @param {string} [stray] a stray file's name, which may itself be a codemod
778
+ * @returns {string | null}
779
+ */
780
+ export function sharedCodemodsRootFix(context, stray) {
781
+ const root = context.roots.codemods;
782
+ if (!root) return null;
783
+ const {step, folder} = ownCodemodsFolder(context);
784
+ const also =
785
+ stray == null
786
+ ? ''
787
+ : ` If it is a codemod, move it into the folder named for the version it migrates to, for example ${folder ? `${folder}/1.2.0/${stray}` : `1.2.0/${stray} in that folder`}.`;
788
+ if (root === context.packageDir) {
789
+ return `Fix: the codemods root is the package root, so everything in the package is read as a codemod or a version folder. ${capitalize(step)}.${also}`;
790
+ }
791
+ // codemods/ holding another root is fixed by moving that root out.
792
+ if (root === path.join(context.packageDir, 'codemods')) return null;
793
+ const [held] = metadataRootsIn(context, root);
794
+ if (!held) return null;
795
+ const relation =
796
+ held.root === root
797
+ ? `is also the ${held.kind} root`
798
+ : `also holds the ${held.kind} root ${place(context, held.root)}`;
799
+ return `Fix: the codemods root ${place(context, root)} ${relation}. ${capitalize(step)}.${also}`;
800
+ }
801
+
802
+ /**
803
+ * The fix for a codemods folder that holds another root, which a rename would
804
+ * break.
805
+ * @param {FixContext} context
806
+ * @param {string} folder
807
+ * @returns {string | null}
808
+ */
809
+ export function heldRootFix(context, folder) {
810
+ const [held] = metadataRootsIn(context, folder);
811
+ const codemods = context.roots.codemods;
812
+ if (!held || !codemods) return null;
813
+ return `Fix: it holds the ${held.kind} root ${place(context, held.root)}; move that root out of ${place(context, codemods)} and update \`${held.kind}\` in ${context.manifest}.`;
814
+ }
815
+
816
+ /**
817
+ * The step that stops another root from also reading `dir`, the folder a fix
818
+ * puts `key` metadata in. `dir` is undefined for a folder the author picks,
819
+ * which only a root at the package root is sure to cover.
820
+ * @param {FixContext} context
821
+ * @param {string | undefined} dir
822
+ * @param {MetadataRoot} key
823
+ * @returns {string} a sentence with a leading space, or ''
824
+ */
825
+ function overlapNote(context, dir, key) {
826
+ /** @param {string | undefined} root */
827
+ const covers = root =>
828
+ root != null &&
829
+ (dir === undefined ? root === context.packageDir : pathIsInside(dir, root));
830
+ const at = dir === undefined ? 'that folder' : place(context, dir);
831
+ let note = '';
832
+ const codemods = context.roots.codemods;
833
+ if (covers(codemods)) {
834
+ if (
835
+ dir !== undefined &&
836
+ codemods === path.join(context.packageDir, 'codemods')
837
+ ) {
838
+ return ` The codemods root codemods/ also reads ${at}; move ${at} out of codemods/ and set \`${key}\` to its new path in ${context.manifest}.`;
839
+ }
840
+ const which =
841
+ codemods === context.packageDir
842
+ ? 'is the package root, so it'
843
+ : place(context, /** @type {string} */ (codemods));
844
+ note = ` The codemods root ${which} also reads ${at}; ${ownCodemodsFolder(context).step}.`;
845
+ }
846
+ const own = path.join(context.packageDir, key);
847
+ const free = !fs.existsSync(own) && isFreeFolder(context, own);
848
+ for (const kind of METADATA_ROOTS) {
849
+ const other = context.roots[kind];
850
+ if (kind === key || other == null || !covers(other)) continue;
851
+ if (other === context.packageDir) {
852
+ note += ` The ${kind} root is the package root, so it also reads ${at}; create ${kind}/, move the ${ROOT_HOLDS[kind]} into it, and set \`${kind}: './${kind}'\` in ${context.manifest}.`;
853
+ continue;
854
+ }
855
+ // Moving this root out of the outermost root clears every root around it.
856
+ if (other === dir) {
857
+ return free
858
+ ? `${note} The ${kind} root is also ${at}; move the ${ROOT_HOLDS[key]} there into ${key}/ and set \`${key}: './${key}'\` in ${context.manifest}.`
859
+ : `${note} The ${kind} root is also ${at}; move the ${ROOT_HOLDS[key]} there into a folder of their own and set \`${key}\` to that folder in ${context.manifest}.`;
860
+ }
861
+ return free
862
+ ? `${note} The ${kind} root ${place(context, other)} also reads ${at}; move ${at} to ${key}/ and set \`${key}: './${key}'\` in ${context.manifest}.`
863
+ : `${note} The ${kind} root ${place(context, other)} also reads ${at}; move ${at} out of it and set \`${key}\` to its new path in ${context.manifest}.`;
864
+ }
865
+ return note;
866
+ }
867
+
868
+ /**
869
+ * @param {FixContext} context
870
+ * @param {string} file
871
+ * @param {ContributionStamp} stamp
872
+ */
873
+ function themeSlug(context, file, stamp) {
874
+ const themeDir = path.dirname(file);
875
+ if (stamp.name) return stamp.name;
876
+ return themeDir === context.packageDir
877
+ ? docStem(file)
878
+ : path.basename(themeDir);
879
+ }
880
+
881
+ /**
882
+ * What to move for a theme: its whole folder when the folder holds only this
883
+ * theme and does not hold the target, else the descriptor and its source.
884
+ * @param {FixContext} context
885
+ * @param {string} file
886
+ * @param {string} [target] the folder it moves to
887
+ * @returns {{what: string, folder: boolean}}
888
+ */
889
+ function themeFiles(context, file, target) {
890
+ const themeDir = path.dirname(file);
891
+ if (
892
+ themeDir !== context.packageDir &&
893
+ (target === undefined || !pathIsInside(target, themeDir)) &&
894
+ !declaredRoots(context).some(root => pathIsInside(root, themeDir))
895
+ ) {
896
+ const found = candidatesUnder(context, themeDir);
897
+ if (found?.length === 1 && found[0] === file) {
898
+ return {what: `${shown(context, themeDir)}/`, folder: true};
899
+ }
900
+ }
901
+ const sources = sourcesBeside(file, 'theme');
902
+ const doc = path.basename(file);
903
+ if (sources.length === 0) return {what: doc, folder: false};
904
+ if (sources.length > 1) {
905
+ return {
906
+ what: `${doc} and its same-stem sources (with any local files they import)`,
907
+ folder: false,
908
+ };
909
+ }
910
+ return {
911
+ what: `${doc} and ${sources[0]} (with any local files it imports)`,
912
+ folder: false,
913
+ };
914
+ }
915
+
916
+ /**
917
+ * @param {FixContext} context
918
+ * @param {string} file
919
+ * @param {ContributionStamp} stamp
920
+ * @param {string} root the themes root the theme moves under
921
+ * @param {string} [slug] the folder it takes there
922
+ */
923
+ function moveTheme(
924
+ context,
925
+ file,
926
+ stamp,
927
+ root,
928
+ slug = themeSlug(context, file, stamp),
929
+ ) {
930
+ const {what, folder} = themeFiles(context, file, path.join(root, slug));
931
+ const target = `${root === context.packageDir ? '' : `${shown(context, root)}/`}${slug}/`;
932
+ return folder ? `move ${what} to ${target}` : `move ${what} into ${target}`;
933
+ }
934
+
935
+ /**
936
+ * Moving metadata under its kind's declared root.
937
+ * @param {FixContext} context
938
+ * @param {string} file
939
+ * @param {ContributionStamp} stamp
940
+ * @param {string} root
941
+ * @param {string} [destination] the theme folder, as the fix names it
942
+ * @returns {{main: string, notes: string}}
943
+ */
944
+ function moveUnder(context, file, stamp, root, destination) {
945
+ const key = ROOT_FOR_TYPE[stamp.type];
946
+ const overlap = overlapNote(context, root, key);
947
+ if (key === 'themes') {
948
+ const slugDir = path.join(root, themeSlug(context, file, stamp));
949
+ const taken = hasEntry(root, path.basename(slugDir));
950
+ const imports = themeImportNote(
951
+ context,
952
+ file,
953
+ destination ??
954
+ (taken ? `${place(context, root)}<slug>/` : place(context, slugDir)),
955
+ );
956
+ const notes = sourceNote(file, stamp.type) + imports + overlap;
957
+ if (taken) {
958
+ return {
959
+ main: `${place(context, slugDir)} is taken, so give this theme a new lower-kebab slug: ${moveTheme(context, file, stamp, root, '<slug>')} and set \`name\` in ${path.basename(file)} to <slug>`,
960
+ notes,
961
+ };
962
+ }
963
+ return {main: moveTheme(context, file, stamp, root), notes};
964
+ }
965
+ const [source] = sourcesBeside(file, stamp.type);
966
+ const taken = takenIn(context, file, stamp, root);
967
+ if (taken) {
968
+ const name = key === 'components' ? '<Name>' : '<name>';
969
+ const suffix = path.basename(file).slice(docStem(file).length);
970
+ const renamed = source
971
+ ? `rename it and ${source} to ${name}${suffix} and ${name}.tsx`
972
+ : `rename it to ${name}${suffix}`;
973
+ const missing =
974
+ stamp.type === 'generic' || source
975
+ ? ''
976
+ : ` Also add ${name}.tsx beside it.`;
977
+ return {
978
+ main: `${place(context, root)} already has ${taken}, so give this one a new name: ${renamed}, set its \`name\` to ${name}, and move ${source ? 'them' : 'it'} under ${place(context, root)} (the ${key} root)`,
979
+ notes: missing + overlap,
980
+ };
981
+ }
982
+ return {
983
+ main: `move ${source ? `it and ${source}` : 'it'} under ${place(context, root)} (the ${key} root)`,
984
+ notes: sourceNote(file, stamp.type) + overlap,
985
+ };
986
+ }
987
+
988
+ /**
989
+ * Moving metadata whose kind has no root yet: into the conventional folder,
990
+ * when that folder would read cleanly as the new root.
991
+ * @param {FixContext} context
992
+ * @param {string} file
993
+ * @param {ContributionStamp} stamp
994
+ * @returns {{main: string, notes: string}}
995
+ */
996
+ function moveToNewRoot(context, file, stamp) {
997
+ const key = ROOT_FOR_TYPE[stamp.type];
998
+ const target = path.join(context.packageDir, key);
999
+ const declare = `set \`${key}: './${key}'\` in ${context.manifest}`;
1000
+ const pointAtIt = `set \`${key}\` to that folder in ${context.manifest}`;
1001
+ const notes = sourceNote(file, stamp.type);
1002
+ const picked = notes + overlapNote(context, undefined, key);
1003
+ if (key === 'themes') {
1004
+ const themeDir = path.dirname(file);
1005
+ const slug = themeSlug(context, file, stamp);
1006
+ const slugDir = path.join(target, slug);
1007
+ const clear =
1008
+ isFreeFolder(context, target) &&
1009
+ readsAsThemesRoot(context, target, {
1010
+ except: path.dirname(themeDir) === target ? themeDir : undefined,
1011
+ ignore: file,
1012
+ fixing: file,
1013
+ }) &&
1014
+ (slugDir === themeDir || !fs.existsSync(slugDir));
1015
+ if (clear && slugDir === themeDir) {
1016
+ return {
1017
+ main: declare,
1018
+ notes: notes + themeImportNote(context, file, place(context, themeDir)),
1019
+ };
1020
+ }
1021
+ if (clear) {
1022
+ return {
1023
+ main: `${moveTheme(context, file, stamp, target)} and ${declare}`,
1024
+ notes: notes + themeImportNote(context, file, place(context, slugDir)),
1025
+ };
1026
+ }
1027
+ const {what, folder} = themeFiles(context, file);
1028
+ return {
1029
+ main: folder
1030
+ ? `move ${what} into a folder that holds only themes, as ${slug}/, and ${pointAtIt}`
1031
+ : `move ${what} into a ${slug}/ folder inside a folder that holds only themes, and ${pointAtIt}`,
1032
+ notes:
1033
+ notes +
1034
+ themeImportNote(context, file, 'the theme folder') +
1035
+ overlapNote(context, undefined, key),
1036
+ };
1037
+ }
1038
+ const [source] = sourcesBeside(file, stamp.type);
1039
+ const what = source ? `it and ${source}` : 'it';
1040
+ const clear = fs.existsSync(target)
1041
+ ? canBeRoot(context, target, key, file) &&
1042
+ takenIn(context, file, stamp, target) == null
1043
+ : isFreeFolder(context, target);
1044
+ if (clear) return {main: `move ${what} into ${key}/ and ${declare}`, notes};
1045
+ return {
1046
+ main: `move ${what} into a folder that holds only ${ROOT_HOLDS[key]}, and ${pointAtIt}`,
1047
+ notes: picked,
1048
+ };
1049
+ }
1050
+
1051
+ /**
1052
+ * The fix for contribution metadata that sits outside every declared root.
1053
+ * @param {FixContext} context
1054
+ * @param {string} file
1055
+ * @param {ContributionStamp} stamp
1056
+ * @returns {string}
1057
+ */
1058
+ export function unreachableFix(context, file, stamp) {
1059
+ const key = ROOT_FOR_TYPE[stamp.type];
1060
+ const root = context.roots[key];
1061
+ // A theme is a directory under the themes root, so the root is its parent.
1062
+ const folder = path.dirname(key === 'themes' ? path.dirname(file) : file);
1063
+ const declare = `set \`${key}: './${shown(context, folder)}'\` in ${context.manifest}`;
1064
+ const folderCanBeRoot = canBeRoot(context, folder, key, file);
1065
+ const stays =
1066
+ sourceNote(file, stamp.type) +
1067
+ (key === 'themes'
1068
+ ? themeImportNote(context, file, place(context, path.dirname(file)))
1069
+ : '');
1070
+ if (root) {
1071
+ // Swapping roots would orphan whatever the declared root already reads.
1072
+ const swappable =
1073
+ folderCanBeRoot && candidatesUnder(context, root)?.length === 0;
1074
+ if (swappable && overlapNote(context, root, key) !== '') {
1075
+ return `Fix: ${declare}.${stays}`;
1076
+ }
1077
+ const {main, notes} = moveUnder(
1078
+ context,
1079
+ file,
1080
+ stamp,
1081
+ root,
1082
+ swappable ? 'the theme folder' : undefined,
1083
+ );
1084
+ return `Fix: ${main}${swappable ? `, or ${declare}` : ''}.${notes}`;
1085
+ }
1086
+ if (folderCanBeRoot) return `Fix: ${declare}.${stays}`;
1087
+ const {main, notes} = moveToNewRoot(context, file, stamp);
1088
+ return `Fix: ${main}.${notes}`;
1089
+ }
1090
+
1091
+ /**
1092
+ * Whether the components can move into `dir` out of a components root at the
1093
+ * package root: it is missing, or holds only component docs, a doc for every
1094
+ * source, and no other root.
1095
+ * @param {FixContext} context
1096
+ * @param {string} dir
1097
+ */
1098
+ function holdsOnlyComponents(context, dir) {
1099
+ if (!fs.existsSync(dir)) return true;
1100
+ const found = candidatesUnder(context, dir);
1101
+ return (
1102
+ found != null &&
1103
+ found.every(file => stampOf(context, file)?.type === 'component') &&
1104
+ uncoveredSources(dir, found).length === 0 &&
1105
+ !METADATA_ROOTS.some(kind => {
1106
+ const root = context.roots[kind];
1107
+ return kind !== 'components' && root != null && pathIsInside(root, dir);
1108
+ })
1109
+ );
1110
+ }
1111
+
1112
+ /**
1113
+ * The step a component doc added to the components root also needs when
1114
+ * another root reads that folder too, and would read the new doc as its own.
1115
+ * @param {FixContext} context
1116
+ * @returns {string} a sentence with a leading space, or ''
1117
+ */
1118
+ export function newComponentDocNote(context) {
1119
+ const components = context.roots.components;
1120
+ return components ? overlapNote(context, components, 'components') : '';
1121
+ }
1122
+
1123
+ /**
1124
+ * The theme beside \`file\` whose source imports it, if any: such a file is
1125
+ * part of that theme, not a codemod.
1126
+ * @param {FixContext} context
1127
+ * @param {string} file
1128
+ * @returns {{doc: string, stamp: ContributionStamp, source: string} | null}
1129
+ */
1130
+ export function themeImporting(context, file) {
1131
+ const dir = path.dirname(file);
1132
+ let entries;
1133
+ try {
1134
+ entries = fs.readdirSync(dir);
1135
+ } catch {
1136
+ return null;
1137
+ }
1138
+ for (const name of entries.filter(entry => DOC_CANDIDATE_RE.test(entry))) {
1139
+ const doc = path.join(dir, name);
1140
+ const stamp = stampOf(context, doc);
1141
+ const [source] = stamp?.type === 'theme' ? sourcesBeside(doc, 'theme') : [];
1142
+ if (!stamp || !source) continue;
1143
+ const edges = localImports(path.join(dir, source));
1144
+ if (edges?.some(edge => edge.target === file)) return {doc, stamp, source};
1145
+ }
1146
+ return null;
1147
+ }
1148
+
1149
+ /**
1150
+ * The fix for contribution metadata at the top of the codemods root, which
1151
+ * the codemod checks read as a stray codemod. `source` names that doc's theme
1152
+ * source when the stray file is the source rather than the doc.
1153
+ * @param {FixContext} context
1154
+ * @param {string} file the metadata doc
1155
+ * @param {ContributionStamp} stamp
1156
+ * @param {string} [source]
1157
+ * @param {string} [stray] a file that source imports, when the stray file is
1158
+ * that one
1159
+ * @returns {string}
1160
+ */
1161
+ export function notACodemodFix(context, file, stamp, source, stray) {
1162
+ const key = ROOT_FOR_TYPE[stamp.type];
1163
+ const root = context.roots[key];
1164
+ const codemods = context.roots.codemods;
1165
+ let lead = `Fix: ${path.basename(file)} has type: '${stamp.type}', so it is not a codemod`;
1166
+ if (source && stray) {
1167
+ lead = `Fix: ${stray} is imported by ${source}, the source of ${path.basename(file)}, which has type: '${stamp.type}', so none of them is a codemod`;
1168
+ } else if (source) {
1169
+ lead = `Fix: ${source} is the source of ${path.basename(file)}, which has type: '${stamp.type}', so neither is a codemod`;
1170
+ }
1171
+ if (root && codemods && pathIsInside(file, root)) {
1172
+ // Its own root reads it already; only the codemods root is in the way.
1173
+ const [companion] = sourcesBeside(file, stamp.type);
1174
+ return `${lead}; move ${path.basename(file)}${companion ? ` and ${companion}` : ''} out of ${place(context, codemods)} to another folder under ${place(context, root)} (the ${key} root).${sourceNote(file, stamp.type)}${overlapNote(context, root, key)}`;
1175
+ }
1176
+ const {main, notes} = root
1177
+ ? moveUnder(context, file, stamp, root)
1178
+ : moveToNewRoot(context, file, stamp);
1179
+ return `${lead}; ${main}.${notes}`;
1180
+ }
1181
+
1182
+ /**
1183
+ * The fix for a doc that the components root reads although its type says it
1184
+ * is another kind of metadata.
1185
+ * @param {FixContext} context
1186
+ * @param {string} file
1187
+ * @param {ContributionStamp} stamp a stamp whose type is not `component`
1188
+ * @returns {string}
1189
+ */
1190
+ export function notAComponentFix(context, file, stamp) {
1191
+ const components = /** @type {string} */ (context.roots.components);
1192
+ const key = ROOT_FOR_TYPE[stamp.type];
1193
+ const root = context.roots[key];
1194
+ const lead = `Fix: ${path.basename(file)} has type: '${stamp.type}', so it is not a component`;
1195
+ if (components === context.packageDir) {
1196
+ const why = `${lead}, and the components root is the package root, which reads every doc in the package`;
1197
+ const target = path.join(context.packageDir, 'components');
1198
+ return holdsOnlyComponents(context, target)
1199
+ ? `${why}; create components/, move the components into it, and set \`components: './components'\` in ${context.manifest}.${overlapNote(context, target, 'components')}`
1200
+ : `${why}; move the components into a folder that holds only components, and set \`components\` to that folder in ${context.manifest}.${overlapNote(context, undefined, 'components')}`;
1201
+ }
1202
+ if (root && pathIsInside(root, components)) {
1203
+ const {main, notes} = separateRoots(context, file, stamp, root);
1204
+ return `${lead}, and ${main}.${sourceNote(file, stamp.type)}${notes}`;
1205
+ }
1206
+ if (root && pathIsInside(file, root)) {
1207
+ return `${lead}; move it out of ${place(context, components)} (the components root) to another folder under ${place(context, root)} (the ${key} root).${sourceNote(file, stamp.type)}${overlapNote(context, root, key)}`;
1208
+ }
1209
+ const {main, notes} = root
1210
+ ? moveUnder(context, file, stamp, root)
1211
+ : moveToNewRoot(context, file, stamp);
1212
+ return `${lead}; ${main}.${notes}`;
1213
+ }
1214
+
1215
+ /**
1216
+ * The fix when a kind's root sits inside the components root, so nothing
1217
+ * placed in it escapes the components root.
1218
+ * @param {FixContext} context
1219
+ * @param {string} file
1220
+ * @param {ContributionStamp} stamp
1221
+ * @param {string} root the declared root for this kind
1222
+ * @returns {{main: string, notes: string}}
1223
+ */
1224
+ function separateRoots(context, file, stamp, root) {
1225
+ const components = /** @type {string} */ (context.roots.components);
1226
+ const key = ROOT_FOR_TYPE[stamp.type];
1227
+ const target = path.join(context.packageDir, key);
1228
+ const free = !fs.existsSync(target) && isFreeFolder(context, target);
1229
+ const picked = overlapNote(context, undefined, key);
1230
+ if (root === components) {
1231
+ const both = `the ${key} root and the components root are both ${place(context, root)}`;
1232
+ return free
1233
+ ? {
1234
+ main: `${both}; move the ${ROOT_HOLDS[key]} into ${key}/ and set \`${key}: './${key}'\` in ${context.manifest}`,
1235
+ notes: '',
1236
+ }
1237
+ : {
1238
+ main: `${both}; move the ${ROOT_HOLDS[key]} into a folder of their own and set \`${key}\` to that folder in ${context.manifest}`,
1239
+ notes: picked,
1240
+ };
1241
+ }
1242
+ const overlap = `the components root ${place(context, components)} also reads the ${key} root ${place(context, root)} inside it`;
1243
+ const carried = pathIsInside(file, root);
1244
+ // The root's contents move with it, so a name it holds is still taken.
1245
+ const slug = themeSlug(context, file, stamp);
1246
+ const taken =
1247
+ !carried &&
1248
+ (key === 'themes'
1249
+ ? hasEntry(root, slug)
1250
+ : takenIn(context, file, stamp, root) != null);
1251
+ const [source] = sourcesBeside(file, stamp.type);
1252
+ const name = '<name>';
1253
+ const suffix = path.basename(file).slice(docStem(file).length);
1254
+ const renamed = `give it a new name: ${source ? `rename it and ${source} to ${name}${suffix} and ${name}.tsx` : `rename it to ${name}${suffix}`}, set its \`name\` to ${name}, and move ${source ? 'them' : 'it'}`;
1255
+ const newSlug = taken ? '<slug>' : slug;
1256
+ const slugNote = taken
1257
+ ? ` and set \`name\` in ${path.basename(file)} to <slug>`
1258
+ : '';
1259
+ if (free) {
1260
+ let then = '';
1261
+ if (!carried && key === 'themes') {
1262
+ then = `, then ${moveTheme(context, file, stamp, target, newSlug)}${slugNote}`;
1263
+ } else if (!carried) {
1264
+ then = `, then ${taken ? renamed : 'move it'} into ${key}/`;
1265
+ }
1266
+ return {
1267
+ main: `${overlap}; move ${place(context, root)} to ${key}/ and set \`${key}: './${key}'\` in ${context.manifest}${then}`,
1268
+ notes: '',
1269
+ };
1270
+ }
1271
+ let then = '';
1272
+ if (!carried && key === 'themes') {
1273
+ const {what, folder} = themeFiles(context, file, root);
1274
+ then = folder
1275
+ ? `, then move ${what} into that root as ${newSlug}/${slugNote}`
1276
+ : `, then move ${what} into a ${newSlug}/ folder in that root${slugNote}`;
1277
+ } else if (!carried) {
1278
+ then = `, then ${taken ? renamed : 'move it'} into that root`;
1279
+ }
1280
+ return {
1281
+ main: `${overlap}; move ${place(context, root)} out of ${place(context, components)} and set \`${key}\` to its new path in ${context.manifest}${then}`,
1282
+ notes: picked,
1283
+ };
1284
+ }