@astryxdesign/cli 0.6.3-canary.db4e378 → 0.6.3-canary.db52d98

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 (397) hide show
  1. package/README.md +38 -15
  2. package/api/blog/blog.doc.mjs +1 -0
  3. package/api/build/_adapter.d.mts +50 -0
  4. package/api/build/_adapter.mjs +60 -0
  5. package/api/build/build.doc.mjs +14 -7
  6. package/api/build/build.test.mjs +184 -6
  7. package/api/build/build.type.d.mts +57 -2
  8. package/api/build/build.type.mjs +30 -7
  9. package/api/build/help/help.d.mts +4 -0
  10. package/api/build/help/help.mjs +24 -11
  11. package/api/build/kit/kit.d.mts +4 -1
  12. package/api/build/kit/kit.mjs +165 -49
  13. package/api/build/kit/rank.d.mts +44 -0
  14. package/api/build/kit/rank.mjs +432 -0
  15. package/api/build/kit/rank.test.mjs +196 -0
  16. package/api/component/_adapter.d.mts +6 -12
  17. package/api/component/_adapter.mjs +20 -10
  18. package/api/component/component.doc.mjs +1 -0
  19. package/api/component/component.mjs +60 -12
  20. package/api/component/list/list.mjs +3 -2
  21. package/api/discover/discover.doc.mjs +1 -0
  22. package/api/docs/_adapter.d.mts +252 -34
  23. package/api/docs/_adapter.mjs +923 -132
  24. package/api/docs/detail/detail.mjs +11 -3
  25. package/api/docs/detail/section/section.mjs +24 -13
  26. package/api/docs/detail/section/section.test.mjs +15 -6
  27. package/api/docs/docs.d.mts +5 -3
  28. package/api/docs/docs.doc.mjs +39 -17
  29. package/api/docs/docs.mjs +44 -8
  30. package/api/docs/docs.test.mjs +158 -4
  31. package/api/docs/docs.type.d.mts +181 -2
  32. package/api/docs/docs.type.mjs +117 -3
  33. package/api/docs/index/index.mjs +11 -3
  34. package/api/docs/index/index.test.mjs +1 -1
  35. package/api/docs/integration-tree.test.mjs +555 -0
  36. package/api/docs/integrationDocs.test.mjs +14 -14
  37. package/api/docs/list/list.mjs +28 -12
  38. package/api/docs/node/node.d.mts +43 -0
  39. package/api/docs/node/node.mjs +192 -0
  40. package/api/docs/reference-blocks.test.mjs +406 -0
  41. package/api/doctor/doctor.d.mts +54 -4
  42. package/api/doctor/doctor.doc.mjs +1 -0
  43. package/api/doctor/doctor.mjs +331 -16
  44. package/api/doctor/doctor.test.mjs +420 -7
  45. package/api/gap-report/gap-report.doc.mjs +1 -0
  46. package/api/hook/_adapter.mjs +19 -5
  47. package/api/hook/hook.doc.mjs +1 -0
  48. package/api/hook/list/list.d.mts +1 -1
  49. package/api/hook/list/list.mjs +69 -17
  50. package/api/index.d.mts +1 -1
  51. package/api/index.mjs +1 -0
  52. package/api/init/init.doc.mjs +2 -1
  53. package/api/integration/add-contribution.d.mts +2 -1
  54. package/api/integration/add-contribution.mjs +114 -12
  55. package/api/integration/add-contribution.test.mjs +174 -3
  56. package/api/integration/add-theme.mjs +34 -64
  57. package/api/integration/add-theme.test.mjs +105 -21
  58. package/api/integration/authoring-checks.mjs +138 -28
  59. package/api/integration/authoring-checks.test.mjs +179 -7
  60. package/api/integration/authoring-checks.type.mjs +6 -1
  61. package/api/integration/integration-authoring.type.d.mts +2 -0
  62. package/api/integration/integration-authoring.type.mjs +2 -0
  63. package/api/integration/integration-block-exports.test.mjs +10 -6
  64. package/api/integration/integrationAdd.doc.mjs +7 -0
  65. package/api/integration/integrationAddAgentDoc.doc.mjs +1 -0
  66. package/api/integration/integrationAddCodemod.doc.mjs +1 -0
  67. package/api/integration/integrationAddComponent.doc.mjs +1 -0
  68. package/api/integration/integrationAddDoc.doc.mjs +8 -1
  69. package/api/integration/integrationAddTemplate.doc.mjs +1 -0
  70. package/api/integration/integrationAddTheme.doc.mjs +6 -5
  71. package/api/integration/integrationComponentConflicts.doc.mjs +1 -0
  72. package/api/integration/integrationDocConflicts.doc.mjs +2 -1
  73. package/api/integration/integrationPackCheck.doc.mjs +2 -1
  74. package/api/integration/integrationTemplateConflicts.doc.d.mts +3 -0
  75. package/api/integration/integrationTemplateConflicts.doc.mjs +4 -0
  76. package/api/integration/pack-check.mjs +34 -0
  77. package/api/integration/pack-check.test.mjs +138 -47
  78. package/api/integration/pack-check.type.d.mts +26 -2
  79. package/api/integration/pack-check.type.mjs +14 -1
  80. package/api/integration/summarizeIssues.doc.mjs +1 -0
  81. package/api/integration/template-conflict-compatibility.test.mjs +73 -0
  82. package/api/integration/validate-integration-fixes.test.mjs +1389 -0
  83. package/api/integration/validate-integration.mjs +50 -102
  84. package/api/integration/validate-integration.test.mjs +88 -26
  85. package/api/integration/validate-unread-theme-folders.test.mjs +110 -0
  86. package/api/integration/validateIntegration.doc.mjs +2 -1
  87. package/api/json/assertResponse.doc.mjs +1 -0
  88. package/api/json/index.ts +2 -0
  89. package/api/json/isError.doc.mjs +1 -0
  90. package/api/json/parseResponse.doc.mjs +1 -0
  91. package/api/layout/_adapter.mjs +20 -5
  92. package/api/layout/layoutCheck.doc.mjs +1 -0
  93. package/api/layout/layoutExpand.doc.mjs +1 -0
  94. package/api/layout/layoutGrammar.doc.mjs +1 -0
  95. package/api/search/search.d.mts +59 -1
  96. package/api/search/search.doc.mjs +5 -3
  97. package/api/search/search.mjs +458 -77
  98. package/api/search/search.test.mjs +91 -2
  99. package/api/search/search.type.d.mts +13 -1
  100. package/api/search/search.type.mjs +4 -1
  101. package/api/swizzle/swizzle.doc.mjs +1 -0
  102. package/api/template/list/list.mjs +1 -0
  103. package/api/template/table-floating-bulk-actions.test.mjs +66 -0
  104. package/api/template/template-integration.test.mjs +1072 -3
  105. package/api/template/template-suffix.test.mjs +41 -21
  106. package/api/template/template.doc.mjs +28 -7
  107. package/api/template/template.mjs +45 -8
  108. package/api/template/template.type.d.mts +6 -8
  109. package/api/template/template.type.mjs +3 -2
  110. package/api/theme/_adapter.d.mts +2 -3
  111. package/api/theme/_adapter.mjs +4 -5
  112. package/api/theme/add/add.binary.test.mjs +10 -17
  113. package/api/theme/add/add.test.mjs +14 -1
  114. package/api/theme/generateTonalPalette.doc.mjs +1 -0
  115. package/api/theme/integration-themes.test.mjs +39 -28
  116. package/api/theme/list/list.test.mjs +19 -20
  117. package/api/theme/listThemes.doc.mjs +6 -5
  118. package/api/theme/template/template.test.mjs +5 -0
  119. package/api/theme/themeAdd.doc.mjs +4 -3
  120. package/api/theme/themeBuild.doc.mjs +1 -0
  121. package/api/theme/themeList.doc.mjs +6 -3
  122. package/api/theme/themeListAvailable.doc.mjs +5 -3
  123. package/api/theme/themePaletteGenerate.doc.mjs +1 -0
  124. package/api/theme/themeTargets.doc.mjs +1 -0
  125. package/api/theme/themeTemplate.doc.mjs +2 -1
  126. package/api/upgrade/_adapter.d.mts +32 -5
  127. package/api/upgrade/_adapter.mjs +97 -69
  128. package/api/upgrade/provider-agreement.test.mjs +152 -0
  129. package/api/upgrade/run/run.mjs +356 -59
  130. package/api/upgrade/upgrade.doc.mjs +6 -1
  131. package/api/upgrade/upgrade.type.d.mts +34 -0
  132. package/api/upgrade/upgrade.type.mjs +15 -0
  133. package/assets/codemods/__tests__/runner.test.mjs +330 -8
  134. package/assets/codemods/integration-discovery.mjs +8 -2
  135. package/assets/codemods/integration-discovery.test.mjs +15 -0
  136. package/assets/codemods/integration-runner.mjs +56 -4
  137. package/assets/codemods/integration-runner.protection.test.mjs +153 -0
  138. package/assets/codemods/run-codemod.mjs +177 -34
  139. package/assets/codemods/runner.mjs +350 -102
  140. package/assets/codemods/transforms/next/__tests__/migrate-native-picker-to-presentation.test.mjs +63 -0
  141. package/assets/codemods/transforms/next/__tests__/migrate-theme-catalog-to-descriptors.test.mjs +220 -0
  142. package/assets/codemods/transforms/next/index.mjs +19 -1
  143. package/assets/codemods/transforms/next/migrate-native-picker-to-presentation.mjs +148 -0
  144. package/assets/codemods/transforms/next/migrate-theme-catalog-to-descriptors.mjs +141 -0
  145. package/assets/docs/getting-started.doc.mjs +2 -2
  146. package/assets/docs/layout.doc.dense.mjs +2 -2
  147. package/assets/docs/layout.doc.mjs +1 -1
  148. package/assets/docs/principles.doc.mjs +6 -6
  149. package/assets/docs/styling-libraries.doc.mjs +3 -3
  150. package/assets/docs/styling.doc.mjs +4 -4
  151. package/assets/docs/theme.doc.mjs +5 -5
  152. package/assets/docs/tokens.doc.mjs +1 -1
  153. package/assets/docs/tree/api.doc.mjs +30 -0
  154. package/assets/docs/tree/cli.doc.mjs +23 -0
  155. package/assets/docs/tree/commands.doc.mjs +25 -0
  156. package/assets/docs/{cli-integrations.doc.mjs → tree/integrations.doc.mjs} +66 -38
  157. package/assets/docs/tree/integrations.test.mjs +62 -0
  158. package/assets/docs/tree/writing-docs.doc.mjs +286 -0
  159. package/assets/docs/working-with-ai.doc.mjs +3 -3
  160. package/assets/templates/blocks/components/CheckboxList/CheckboxListSelectAllPattern.doc.mjs +1 -1
  161. package/assets/templates/blocks/components/CheckboxList/CheckboxListSelectAllPattern.tsx +1 -3
  162. package/assets/templates/blocks/components/DateInput/DateInputDateRange.tsx +1 -1
  163. package/assets/templates/blocks/components/Item/ItemDocumentTabs.doc.mjs +14 -0
  164. package/assets/templates/blocks/components/Item/ItemDocumentTabs.tsx +100 -0
  165. package/assets/templates/blocks/components/Table/TableBulkActionsTable.doc.mjs +14 -0
  166. package/assets/templates/blocks/components/Table/TableBulkActionsTable.tsx +71 -0
  167. package/assets/templates/blocks/components/Table/TableFloatingBulkActionsTable.doc.mjs +19 -0
  168. package/assets/templates/blocks/components/Table/TableFloatingBulkActionsTable.tsx +139 -0
  169. package/assets/templates/blocks/components/TimeInput/TimeInputConstrained.tsx +1 -0
  170. package/assets/templates/themes/butter/butterTheme.doc.mjs +11 -0
  171. package/assets/templates/themes/chocolate/chocolateTheme.doc.mjs +11 -0
  172. package/assets/templates/themes/gothic/gothicTheme.doc.mjs +11 -0
  173. package/assets/templates/themes/matcha/matchaTheme.doc.mjs +11 -0
  174. package/assets/templates/themes/neutral/neutralTheme.doc.mjs +11 -0
  175. package/assets/templates/themes/stone/stoneTheme.doc.mjs +11 -0
  176. package/assets/templates/themes/y2k/y2kTheme.doc.mjs +11 -0
  177. package/authoring/codemod/codemod.doc.mjs +1 -1
  178. package/authoring/codemod/type.ts +12 -0
  179. package/authoring/config/config.doc.mjs +1 -1
  180. package/authoring/debug/debug.doc.d.mts +11 -0
  181. package/authoring/debug/debug.doc.mjs +182 -0
  182. package/authoring/debug/parse.d.mts +3 -3
  183. package/authoring/doctypes/_schema.d.mts +119 -117
  184. package/authoring/doctypes/_schema.mjs +54 -3
  185. package/authoring/doctypes/base/graph-fields.doc.mjs +8 -6
  186. package/authoring/doctypes/base/type.ts +6 -5
  187. package/authoring/doctypes/command/command.doc.mjs +1 -1
  188. package/authoring/doctypes/command/type.ts +2 -2
  189. package/authoring/doctypes/component/type.ts +2 -2
  190. package/authoring/doctypes/doctypes-new.test.mjs +48 -6
  191. package/authoring/doctypes/enum/enum.doc.mjs +1 -1
  192. package/authoring/doctypes/enum/type.ts +1 -1
  193. package/authoring/doctypes/function/function.doc.mjs +3 -2
  194. package/authoring/doctypes/function/type.ts +3 -2
  195. package/authoring/doctypes/hook/type.ts +2 -2
  196. package/authoring/doctypes/load-contract.test.mjs +28 -2
  197. package/authoring/doctypes/namespace/namespace.doc.mjs +6 -10
  198. package/authoring/doctypes/namespace/parse.test.mjs +23 -25
  199. package/authoring/doctypes/namespace/type.ts +5 -2
  200. package/authoring/doctypes/parse.d.mts +4 -2
  201. package/authoring/doctypes/parse.mjs +10 -5
  202. package/authoring/doctypes/reference/reference.doc.mjs +35 -6
  203. package/authoring/doctypes/reference/type.ts +30 -13
  204. package/authoring/doctypes/schema/schema.doc.mjs +1 -1
  205. package/authoring/doctypes/schema/type.ts +1 -2
  206. package/authoring/doctypes/template/parse.d.mts +2 -0
  207. package/authoring/doctypes/template/parse.mjs +4 -0
  208. package/authoring/doctypes/template/parse.test.mjs +18 -0
  209. package/authoring/doctypes/template/template.doc.mjs +9 -3
  210. package/authoring/doctypes/template/type.ts +8 -0
  211. package/authoring/doctypes/theme/parse.d.mts +35 -0
  212. package/authoring/doctypes/theme/parse.mjs +76 -0
  213. package/authoring/doctypes/theme/theme.doc.d.mts +9 -0
  214. package/authoring/doctypes/theme/theme.doc.mjs +79 -0
  215. package/authoring/doctypes/theme/type.ts +42 -0
  216. package/authoring/doctypes/types.ts +2 -1
  217. package/authoring/gap-report/gap-report.doc.d.mts +12 -0
  218. package/authoring/gap-report/gap-report.doc.mjs +183 -0
  219. package/authoring/identity/identity.doc.mjs +2 -2
  220. package/authoring/index.d.mts +1 -0
  221. package/authoring/index.d.ts +7 -4
  222. package/authoring/index.mjs +2 -1
  223. package/authoring/integration/integration.doc.mjs +2 -2
  224. package/authoring/integration/type.ts +4 -10
  225. package/clients/cli/__tests__/cliManifest.test.ts +27 -29
  226. package/clients/cli/commands/blog.doc.mjs +1 -1
  227. package/clients/cli/commands/build-theme.adaptations.test.mjs +100 -1
  228. package/clients/cli/commands/build-theme.mjs +11 -45
  229. package/clients/cli/commands/build.doc.mjs +12 -7
  230. package/clients/cli/commands/build.mjs +105 -67
  231. package/clients/cli/commands/build.text-fields.test.mjs +41 -0
  232. package/clients/cli/commands/component/index.mjs +1 -1
  233. package/clients/cli/commands/component-ownership.test.mjs +3 -3
  234. package/clients/cli/commands/component.doc.mjs +1 -1
  235. package/clients/cli/commands/discover.broken-integration.test.mjs +7 -8
  236. package/clients/cli/commands/discover.doc.mjs +1 -1
  237. package/clients/cli/commands/discover.mjs +1 -1
  238. package/clients/cli/commands/docs.doc.mjs +21 -9
  239. package/clients/cli/commands/docs.mjs +184 -70
  240. package/clients/cli/commands/docs.test.mjs +120 -16
  241. package/clients/cli/commands/doctor-integration-components.doc.mjs +1 -1
  242. package/clients/cli/commands/doctor-integration-docs.doc.mjs +3 -3
  243. package/clients/cli/commands/doctor-integration-templates.doc.mjs +15 -8
  244. package/clients/cli/commands/doctor-integration-validate.doc.mjs +1 -1
  245. package/clients/cli/commands/doctor-integration.doc.mjs +1 -1
  246. package/clients/cli/commands/doctor-integration.test.mjs +90 -8
  247. package/clients/cli/commands/doctor.doc.mjs +1 -1
  248. package/clients/cli/commands/doctor.mjs +53 -16
  249. package/clients/cli/commands/gap-report.doc.mjs +1 -1
  250. package/clients/cli/commands/hook.doc.mjs +1 -1
  251. package/clients/cli/commands/init.doc.mjs +1 -1
  252. package/clients/cli/commands/integration-add.controls.test.mjs +1 -1
  253. package/clients/cli/commands/integration-add.doc.mjs +11 -1
  254. package/clients/cli/commands/integration-pack.doc.mjs +1 -1
  255. package/clients/cli/commands/integration-real-world.test.mjs +3 -9
  256. package/clients/cli/commands/integration.doc.mjs +1 -1
  257. package/clients/cli/commands/integration.mjs +1 -0
  258. package/clients/cli/commands/layout-check.doc.mjs +1 -1
  259. package/clients/cli/commands/layout-expand.doc.mjs +1 -1
  260. package/clients/cli/commands/layout-grammar.doc.mjs +1 -1
  261. package/clients/cli/commands/layout.doc.mjs +1 -1
  262. package/clients/cli/commands/manifest.doc.mjs +1 -1
  263. package/clients/cli/commands/search.doc.mjs +1 -1
  264. package/clients/cli/commands/search.mjs +11 -2
  265. package/clients/cli/commands/setup-nudge.test.mjs +6 -0
  266. package/clients/cli/commands/swizzle.doc.mjs +1 -1
  267. package/clients/cli/commands/template.doc.mjs +24 -7
  268. package/clients/cli/commands/template.mjs +4 -91
  269. package/clients/cli/commands/text-json-parity.test.mjs +719 -0
  270. package/clients/cli/commands/theme-add.doc.mjs +2 -2
  271. package/clients/cli/commands/theme-build.doc.mjs +1 -1
  272. package/clients/cli/commands/theme-list.doc.mjs +2 -2
  273. package/clients/cli/commands/theme-palette-generate.doc.mjs +3 -3
  274. package/clients/cli/commands/theme-palette.doc.mjs +1 -1
  275. package/clients/cli/commands/theme-targets.behavior.test.mjs +4 -3
  276. package/clients/cli/commands/theme-targets.doc.mjs +1 -1
  277. package/clients/cli/commands/theme-template.doc.mjs +1 -1
  278. package/clients/cli/commands/theme.doc.mjs +1 -1
  279. package/clients/cli/commands/upgrade.doc.mjs +3 -2
  280. package/clients/cli/commands/upgrade.file-protection.test.mjs +228 -0
  281. package/clients/cli/commands/upgrade.mjs +29 -7
  282. package/clients/cli/formatters/index.mjs +2 -0
  283. package/clients/cli/formatters/index.test.mjs +6 -0
  284. package/clients/cli/index.mjs +8 -0
  285. package/clients/cli/lib/hook-format.mjs +14 -5
  286. package/clients/cli/lib/manifest.mjs +9 -2
  287. package/foundation/agent-docs/agent-docs.d.mts +3 -2
  288. package/foundation/agent-docs/agent-docs.mjs +13 -9
  289. package/foundation/agent-docs/agent-docs.test.mjs +19 -1
  290. package/foundation/config/project-themes.test.mjs +11 -19
  291. package/foundation/config/project.d.mts +20 -11
  292. package/foundation/config/project.mjs +123 -80
  293. package/foundation/config/project.test.mjs +144 -21
  294. package/foundation/discovery/authoring-self-docs.d.mts +18 -0
  295. package/foundation/discovery/authoring-self-docs.mjs +37 -15
  296. package/foundation/discovery/authoring-self-docs.test.mjs +25 -9
  297. package/foundation/discovery/authoring-surface.d.mts +74 -0
  298. package/foundation/discovery/authoring-surface.mjs +525 -0
  299. package/foundation/discovery/authoring-surface.test.mjs +392 -0
  300. package/foundation/discovery/cli-self-docs.d.mts +119 -0
  301. package/foundation/discovery/cli-self-docs.mjs +490 -0
  302. package/foundation/discovery/cli-self-docs.test.mjs +375 -0
  303. package/foundation/discovery/component-discovery.d.mts +38 -0
  304. package/foundation/discovery/component-discovery.mjs +48 -0
  305. package/foundation/discovery/component-loader.d.mts +35 -38
  306. package/foundation/discovery/component-loader.mjs +53 -222
  307. package/foundation/discovery/docs-discovery.d.mts +112 -11
  308. package/foundation/discovery/docs-discovery.mjs +231 -36
  309. package/foundation/discovery/docs-discovery.test.mjs +111 -32
  310. package/foundation/discovery/docs-output-budget.d.mts +2 -2
  311. package/foundation/discovery/docs-output-budget.mjs +1 -1
  312. package/foundation/discovery/docs-section-key.d.mts +28 -10
  313. package/foundation/discovery/docs-section-key.mjs +134 -33
  314. package/foundation/discovery/docs-section-key.test.mjs +41 -19
  315. package/foundation/discovery/template-adapter.d.mts +99 -6
  316. package/foundation/discovery/template-adapter.mjs +480 -57
  317. package/foundation/discovery/template-adapter.test.mjs +57 -0
  318. package/foundation/discovery/template-conflict-release.d.mts +13 -0
  319. package/foundation/discovery/template-conflict-release.mjs +40 -0
  320. package/foundation/discovery/template-conflict-release.test.mjs +40 -0
  321. package/foundation/discovery/theme-discovery.d.mts +67 -7
  322. package/foundation/discovery/theme-discovery.mjs +916 -186
  323. package/foundation/discovery/theme-discovery.test.mjs +613 -219
  324. package/foundation/doc-compiler/bundle.d.mts +47 -0
  325. package/foundation/doc-compiler/bundle.mjs +278 -0
  326. package/foundation/doc-compiler/bundle.test.mjs +266 -0
  327. package/foundation/doc-compiler/compile.d.mts +220 -39
  328. package/foundation/doc-compiler/compile.mjs +311 -15
  329. package/foundation/doc-compiler/diagnostics.d.mts +126 -0
  330. package/foundation/doc-compiler/diagnostics.mjs +305 -0
  331. package/foundation/doc-compiler/doc-compiler.test.mjs +28 -1
  332. package/foundation/doc-compiler/doc-loads.test.mjs +1642 -0
  333. package/foundation/doc-compiler/import.d.mts +24 -0
  334. package/foundation/doc-compiler/import.mjs +59 -0
  335. package/foundation/doc-compiler/inputs.d.mts +102 -0
  336. package/foundation/doc-compiler/inputs.mjs +291 -0
  337. package/foundation/doc-compiler/inputs.test.mjs +299 -0
  338. package/foundation/doc-compiler/ir.d.mts +13 -0
  339. package/foundation/doc-compiler/ir.mjs +196 -12
  340. package/foundation/doc-compiler/lenses.d.mts +8 -5
  341. package/foundation/doc-compiler/lenses.mjs +50 -4
  342. package/foundation/doc-compiler/links.d.mts +162 -0
  343. package/foundation/doc-compiler/links.mjs +294 -0
  344. package/foundation/doc-compiler/links.test.mjs +192 -0
  345. package/foundation/doc-compiler/lower-doc.test.mjs +495 -0
  346. package/foundation/doc-compiler/overlays.d.mts +37 -0
  347. package/foundation/doc-compiler/overlays.mjs +206 -0
  348. package/foundation/doc-compiler/parse-readable.d.mts +9 -0
  349. package/foundation/doc-compiler/parse-readable.mjs +29 -0
  350. package/foundation/doc-compiler/read.d.mts +127 -0
  351. package/foundation/doc-compiler/read.mjs +325 -0
  352. package/foundation/doc-compiler/read.test.mjs +313 -0
  353. package/foundation/doc-compiler/source.d.mts +33 -0
  354. package/foundation/doc-compiler/source.mjs +128 -0
  355. package/foundation/doc-compiler/tree.d.mts +288 -0
  356. package/foundation/doc-compiler/tree.mjs +876 -0
  357. package/foundation/doc-compiler/tree.test.mjs +598 -0
  358. package/foundation/fs/file-protection.d.mts +33 -0
  359. package/foundation/fs/file-protection.mjs +825 -0
  360. package/foundation/fs/file-protection.test.mjs +250 -0
  361. package/foundation/integrations/autolink.d.mts +58 -1
  362. package/foundation/integrations/autolink.mjs +143 -57
  363. package/foundation/integrations/autolink.test.mjs +1 -1
  364. package/foundation/integrations/cli-requirement.d.mts +45 -0
  365. package/foundation/integrations/cli-requirement.mjs +154 -0
  366. package/foundation/integrations/cli-requirement.test.mjs +84 -0
  367. package/foundation/integrations/contribution-fixes.d.mts +145 -0
  368. package/foundation/integrations/contribution-fixes.mjs +1284 -0
  369. package/foundation/integrations/contribution-inventory.d.mts +3 -2
  370. package/foundation/integrations/contribution-inventory.mjs +27 -24
  371. package/foundation/integrations/contribution-inventory.test.mjs +86 -27
  372. package/foundation/integrations/integration-warnings.d.mts +9 -2
  373. package/foundation/integrations/integration-warnings.mjs +51 -26
  374. package/foundation/integrations/integration-warnings.test.mjs +74 -1
  375. package/foundation/integrations/integrations.d.mts +3 -0
  376. package/foundation/integrations/integrations.mjs +11 -97
  377. package/foundation/integrations/provider-ledger.test.mjs +275 -0
  378. package/foundation/integrations/provider-resolution.d.mts +152 -0
  379. package/foundation/integrations/provider-resolution.mjs +576 -0
  380. package/foundation/integrations/provider-resolution.test.mjs +369 -0
  381. package/foundation/integrations/theme-descriptor.d.mts +8 -0
  382. package/foundation/integrations/theme-descriptor.mjs +44 -0
  383. package/foundation/integrations/validate-contributions.mjs +121 -29
  384. package/foundation/response/error-codes.d.mts +3 -1
  385. package/foundation/response/error-codes.d.ts +2 -0
  386. package/foundation/response/error-codes.doc.mjs +12 -2
  387. package/foundation/response/error-codes.mjs +7 -1
  388. package/foundation/response/error-codes.test.mjs +55 -11
  389. package/foundation/response/response-types.doc.d.mts +5 -1
  390. package/foundation/response/response-types.doc.mjs +20 -11
  391. package/foundation/response/response.doc.mjs +1 -1
  392. package/foundation/text/string-utils.d.mts +8 -0
  393. package/foundation/text/string-utils.mjs +40 -10
  394. package/foundation/xle/expand.mjs +1 -1
  395. package/foundation/xle/xle.test.mjs +13 -0
  396. package/package.json +10 -9
  397. package/assets/templates/themes/manifest.json +0 -95
@@ -21,6 +21,40 @@ afterEach(() => {
21
21
  fs.rmSync(tmpDir, {recursive: true, force: true});
22
22
  });
23
23
 
24
+ describe('runCodemods — ordered dry-run state', () => {
25
+ it('feeds each previewed code transform into the next transform', async () => {
26
+ const srcDir = path.join(tmpDir, 'src');
27
+ fs.mkdirSync(srcDir);
28
+ fs.writeFileSync(path.join(srcDir, 'a.ts'), 'const value = "a";\n');
29
+ const transforms = [
30
+ {
31
+ name: 'a-to-b',
32
+ meta: {title: 'a to b'},
33
+ transform: file => file.source.replace('"a"', '"b"'),
34
+ },
35
+ {
36
+ name: 'b-to-c',
37
+ meta: {title: 'b to c'},
38
+ transform: file => file.source.replace('"b"', '"c"'),
39
+ },
40
+ ];
41
+
42
+ const preview = await runCodemods([{version: '1.0.0', transforms}], {
43
+ apply: false,
44
+ path: srcDir,
45
+ root: tmpDir,
46
+ silent: true,
47
+ });
48
+
49
+ expect(preview.totalFilesChanged).toBe(2);
50
+ expect(preview.changedFiles).toEqual([
51
+ path.join(srcDir, 'a.ts'),
52
+ path.join(srcDir, 'a.ts'),
53
+ ]);
54
+ expect(fs.readFileSync(path.join(srcDir, 'a.ts'), 'utf8')).toContain('"a"');
55
+ });
56
+ });
57
+
24
58
  describe('runCodemods — unified config codemod path', () => {
25
59
  it('routes a core config codemod through the (file, api) runner to edit astryx.config.*', async () => {
26
60
  fs.writeFileSync(
@@ -195,17 +229,219 @@ describe('runCodemods — unified config codemod path', () => {
195
229
  });
196
230
  });
197
231
 
198
- describe('findSourceFiles — scan boundaries (symlink + build dirs)', () => {
232
+ describe('runCodemods — project plans honor existing-file protection', () => {
233
+ it('applies new and owned writes while blocking protected replacements and deletes', async () => {
234
+ fs.writeFileSync(
235
+ path.join(tmpDir, '.gitattributes'),
236
+ 'generated/** linguist-generated=true\n',
237
+ );
238
+ fs.mkdirSync(path.join(tmpDir, 'generated'), {recursive: true});
239
+ fs.writeFileSync(path.join(tmpDir, 'generated', 'replace.ts'), 'old\n');
240
+ fs.writeFileSync(path.join(tmpDir, 'generated', 'remove.ts'), 'old\n');
241
+ fs.writeFileSync(path.join(tmpDir, 'owned.ts'), 'old\n');
242
+
243
+ const manifests = [
244
+ {
245
+ version: '1.0.0',
246
+ transforms: [
247
+ {
248
+ name: 'project-plan',
249
+ meta: {title: 'project plan', codemodType: 'project'},
250
+ transform: async root => ({
251
+ writes: [
252
+ {
253
+ path: path.join(root, 'generated', 'replace.ts'),
254
+ contents: 'new\n',
255
+ },
256
+ {path: path.join(root, 'owned.ts'), contents: 'new\n'},
257
+ {path: path.join(root, 'created.ts'), contents: 'new\n'},
258
+ ],
259
+ deletes: [path.join(root, 'generated', 'remove.ts')],
260
+ problems: [],
261
+ }),
262
+ },
263
+ ],
264
+ },
265
+ ];
266
+
267
+ const result = await runCodemods(manifests, {
268
+ apply: true,
269
+ path: './missing',
270
+ root: tmpDir,
271
+ silent: true,
272
+ });
273
+
274
+ expect(
275
+ fs.readFileSync(path.join(tmpDir, 'generated', 'replace.ts'), 'utf8'),
276
+ ).toBe('old\n');
277
+ expect(fs.existsSync(path.join(tmpDir, 'generated', 'remove.ts'))).toBe(
278
+ true,
279
+ );
280
+ expect(fs.readFileSync(path.join(tmpDir, 'owned.ts'), 'utf8')).toBe(
281
+ 'new\n',
282
+ );
283
+ expect(fs.readFileSync(path.join(tmpDir, 'created.ts'), 'utf8')).toBe(
284
+ 'new\n',
285
+ );
286
+ expect(result.totalFilesChanged).toBe(2);
287
+ expect(result.protectedFiles.map(item => item.file).sort()).toEqual([
288
+ 'generated/remove.ts',
289
+ 'generated/replace.ts',
290
+ ]);
291
+ });
292
+
293
+ it('blocks an existing file protected by a declaration in the same plan', async () => {
294
+ fs.mkdirSync(path.join(tmpDir, 'src'), {recursive: true});
295
+ fs.writeFileSync(path.join(tmpDir, 'src', 'out.ts'), 'old\n');
296
+ const manifests = [
297
+ {
298
+ version: '1.0.0',
299
+ transforms: [
300
+ {
301
+ name: 'declare-and-replace',
302
+ meta: {title: 'declare and replace', codemodType: 'project'},
303
+ transform: async root => ({
304
+ writes: [
305
+ {
306
+ path: path.join(root, '.gitattributes'),
307
+ contents: 'src/out.ts linguist-generated\n',
308
+ },
309
+ {path: path.join(root, 'src', 'out.ts'), contents: 'new\n'},
310
+ ],
311
+ deletes: [],
312
+ problems: [],
313
+ }),
314
+ },
315
+ ],
316
+ },
317
+ ];
318
+
319
+ const result = await runCodemods(manifests, {
320
+ apply: true,
321
+ path: './missing',
322
+ root: tmpDir,
323
+ silent: true,
324
+ });
325
+
326
+ expect(fs.readFileSync(path.join(tmpDir, 'src', 'out.ts'), 'utf8')).toBe(
327
+ 'old\n',
328
+ );
329
+ expect(fs.readFileSync(path.join(tmpDir, '.gitattributes'), 'utf8')).toBe(
330
+ 'src/out.ts linguist-generated\n',
331
+ );
332
+ expect(result.protectedFiles).toEqual([
333
+ expect.objectContaining({
334
+ file: 'src/out.ts',
335
+ declaration: 'linguist-generated in .gitattributes:1',
336
+ }),
337
+ ]);
338
+ });
339
+
340
+ it('does not apply rules from a blocked staged declaration', async () => {
341
+ fs.writeFileSync(
342
+ path.join(tmpDir, '.gitattributes'),
343
+ '.gitattributes linguist-generated\n',
344
+ );
345
+ fs.mkdirSync(path.join(tmpDir, 'src'), {recursive: true});
346
+ fs.writeFileSync(path.join(tmpDir, 'src', 'out.ts'), 'old\n');
347
+ const result = await runCodemods(
348
+ [
349
+ {
350
+ version: '1.0.0',
351
+ transforms: [
352
+ {
353
+ name: 'blocked-declaration',
354
+ meta: {title: 'blocked declaration', codemodType: 'project'},
355
+ transform: async root => ({
356
+ writes: [
357
+ {
358
+ path: path.join(root, '.gitattributes'),
359
+ contents: 'src/out.ts linguist-generated\n',
360
+ },
361
+ {path: path.join(root, 'src', 'out.ts'), contents: 'new\n'},
362
+ ],
363
+ deletes: [],
364
+ problems: [],
365
+ }),
366
+ },
367
+ ],
368
+ },
369
+ ],
370
+ {apply: true, path: './missing', root: tmpDir, silent: true},
371
+ );
372
+
373
+ expect(fs.readFileSync(path.join(tmpDir, 'src', 'out.ts'), 'utf8')).toBe(
374
+ 'new\n',
375
+ );
376
+ expect(result.protectedFiles).toEqual([
377
+ expect.objectContaining({file: '.gitattributes'}),
378
+ ]);
379
+ });
380
+
381
+ it('refreshes declarations written by an earlier project codemod before later writes', async () => {
382
+ fs.mkdirSync(path.join(tmpDir, 'src'), {recursive: true});
383
+ fs.writeFileSync(path.join(tmpDir, 'src', 'out.ts'), 'const foo = 1;\n');
384
+ const manifests = [
385
+ {
386
+ version: '1.0.0',
387
+ transforms: [
388
+ {
389
+ name: 'declare-generated',
390
+ meta: {title: 'declare generated', codemodType: 'project'},
391
+ transform: async root => ({
392
+ writes: [
393
+ {
394
+ path: path.join(root, '.gitattributes'),
395
+ contents: 'src/out.ts linguist-generated=true\n',
396
+ },
397
+ ],
398
+ deletes: [],
399
+ problems: [],
400
+ }),
401
+ },
402
+ {
403
+ name: 'rename-after-declaration',
404
+ meta: {title: 'rename after declaration'},
405
+ transform: file => file.source.replace('foo', 'bar'),
406
+ },
407
+ ],
408
+ },
409
+ ];
410
+
411
+ const result = await runCodemods(manifests, {
412
+ apply: true,
413
+ path: path.join(tmpDir, 'src'),
414
+ root: tmpDir,
415
+ silent: true,
416
+ });
417
+
418
+ expect(
419
+ fs.readFileSync(path.join(tmpDir, 'src', 'out.ts'), 'utf8'),
420
+ ).toContain('foo');
421
+ expect(result.protectedFiles).toEqual([
422
+ expect.objectContaining({
423
+ file: 'src/out.ts',
424
+ declaration: 'linguist-generated in .gitattributes:1',
425
+ }),
426
+ ]);
427
+ });
428
+ });
429
+
430
+ describe('findSourceFiles — scan boundaries (symlink + declared protection)', () => {
199
431
  const manifests = [
200
432
  {
201
433
  version: '0.1.3',
202
434
  transforms: [
203
- {name: 'p', meta: {title: 'p'}, transform: f => f.source.replace(/foo/g, 'bar')},
435
+ {
436
+ name: 'p',
437
+ meta: {title: 'p'},
438
+ transform: f => f.source.replace(/foo/g, 'bar'),
439
+ },
204
440
  ],
205
441
  },
206
442
  ];
207
443
 
208
- it('does not follow a symlinked file out of the scan tree', async () => {
444
+ it('reports a symlinked file without writing through it', async () => {
209
445
  const outside = path.join(tmpDir, 'outside');
210
446
  fs.mkdirSync(path.join(tmpDir, 'src'), {recursive: true});
211
447
  fs.mkdirSync(outside, {recursive: true});
@@ -214,13 +450,45 @@ describe('findSourceFiles — scan boundaries (symlink + build dirs)', () => {
214
450
  fs.writeFileSync(path.join(tmpDir, 'src', 'real.ts'), 'const foo = 2;\n');
215
451
  fs.symlinkSync(secret, path.join(tmpDir, 'src', 'link.ts'));
216
452
 
217
- const r = await runCodemods(manifests, {apply: true, path: './src', silent: true});
453
+ const r = await runCodemods(manifests, {
454
+ apply: true,
455
+ path: './src',
456
+ silent: true,
457
+ });
218
458
  // the symlink target (outside the tree) must be untouched
219
459
  expect(fs.readFileSync(secret, 'utf-8')).toBe('const foo = 1;\n');
220
460
  expect(r.writtenFiles.map(f => path.basename(f))).toEqual(['real.ts']);
461
+ expect(r.protectedFiles).toEqual([
462
+ expect.objectContaining({file: 'src/link.ts', reason: 'symlink'}),
463
+ ]);
221
464
  });
222
465
 
223
- it('does not scan generated-output dirs (dist/build/out)', async () => {
466
+ it('reports matching descendants of a symlinked directory without writing through it', async () => {
467
+ const targetDir = path.join(tmpDir, 'target');
468
+ const srcDir = path.join(tmpDir, 'src');
469
+ fs.mkdirSync(targetDir, {recursive: true});
470
+ fs.mkdirSync(srcDir, {recursive: true});
471
+ const target = path.join(targetDir, 'nested.ts');
472
+ fs.writeFileSync(target, 'const foo = 1;\n');
473
+ fs.symlinkSync(targetDir, path.join(srcDir, 'linked'));
474
+
475
+ const result = await runCodemods(manifests, {
476
+ apply: true,
477
+ path: srcDir,
478
+ root: tmpDir,
479
+ silent: true,
480
+ });
481
+
482
+ expect(fs.readFileSync(target, 'utf8')).toContain('foo');
483
+ expect(result.protectedFiles).toEqual([
484
+ expect.objectContaining({
485
+ file: 'src/linked/nested.ts',
486
+ reason: 'symlink',
487
+ }),
488
+ ]);
489
+ });
490
+
491
+ it('does not infer protection from generated-looking directory names', async () => {
224
492
  fs.mkdirSync(path.join(tmpDir, 'src'), {recursive: true});
225
493
  fs.mkdirSync(path.join(tmpDir, 'dist'), {recursive: true});
226
494
  fs.mkdirSync(path.join(tmpDir, 'build'), {recursive: true});
@@ -228,8 +496,62 @@ describe('findSourceFiles — scan boundaries (symlink + build dirs)', () => {
228
496
  fs.writeFileSync(path.join(tmpDir, 'dist', 'b.js'), 'const foo = 2;\n');
229
497
  fs.writeFileSync(path.join(tmpDir, 'build', 'c.js'), 'const foo = 3;\n');
230
498
 
231
- const r = await runCodemods(manifests, {apply: true, path: '.', silent: true});
232
- expect(r.writtenFiles.map(f => path.basename(f))).toEqual(['a.ts']);
233
- expect(fs.readFileSync(path.join(tmpDir, 'dist', 'b.js'), 'utf-8')).toContain('foo');
499
+ const r = await runCodemods(manifests, {
500
+ apply: true,
501
+ path: '.',
502
+ silent: true,
503
+ });
504
+ expect(r.writtenFiles.map(f => path.basename(f)).sort()).toEqual([
505
+ 'a.ts',
506
+ 'b.js',
507
+ 'c.js',
508
+ ]);
509
+ });
510
+
511
+ it('changes owned files but reports generated files without writing them', async () => {
512
+ fs.mkdirSync(path.join(tmpDir, 'src'), {recursive: true});
513
+ fs.mkdirSync(path.join(tmpDir, 'dist'), {recursive: true});
514
+ fs.writeFileSync(
515
+ path.join(tmpDir, '.gitattributes'),
516
+ 'dist/** linguist-generated=true\n',
517
+ );
518
+ fs.writeFileSync(path.join(tmpDir, 'src', 'owned.ts'), 'const foo = 1;\n');
519
+ fs.writeFileSync(
520
+ path.join(tmpDir, 'dist', 'output.js'),
521
+ 'const foo = 2;\n',
522
+ );
523
+
524
+ const r = await runCodemods(manifests, {
525
+ apply: true,
526
+ path: '.',
527
+ silent: true,
528
+ });
529
+
530
+ expect(
531
+ fs.readFileSync(path.join(tmpDir, 'src', 'owned.ts'), 'utf-8'),
532
+ ).toContain('bar');
533
+ expect(
534
+ fs.readFileSync(path.join(tmpDir, 'dist', 'output.js'), 'utf-8'),
535
+ ).toContain('foo');
536
+ expect(r.protectedFiles).toEqual([
537
+ expect.objectContaining({
538
+ file: 'dist/output.js',
539
+ reason: 'generated',
540
+ declaration: 'linguist-generated in .gitattributes:1',
541
+ }),
542
+ ]);
543
+ });
544
+
545
+ it('fails closed before an owned write when a declaration is malformed', async () => {
546
+ fs.mkdirSync(path.join(tmpDir, 'src'), {recursive: true});
547
+ fs.writeFileSync(path.join(tmpDir, 'src', 'owned.ts'), 'const foo = 1;\n');
548
+ fs.writeFileSync(path.join(tmpDir, '.hgignore'), 'syntax: regexp\n[bad\n');
549
+
550
+ await expect(
551
+ runCodemods(manifests, {apply: true, path: '.', silent: true}),
552
+ ).rejects.toThrow(/file-protection source \.hgignore/);
553
+ expect(
554
+ fs.readFileSync(path.join(tmpDir, 'src', 'owned.ts'), 'utf-8'),
555
+ ).toContain('foo');
234
556
  });
235
557
  });
@@ -106,7 +106,7 @@ function collectCodemodFiles(versionDir) {
106
106
  /**
107
107
  * Discover file-based codemods contributed by configured integrations.
108
108
  *
109
- * @param {Array<{name?: string, codemods?: string, __spec?: string}>} loadedIntegrations
109
+ * @param {Array<{name?: string, codemods?: string, __spec?: string, __packageDir?: string}>} loadedIntegrations
110
110
  * @returns {Promise<Map<string, Array<{id: string, type: 'code'|'config', codemod: object, package: string}>>>}
111
111
  * Map keyed by version string; each value is the list of discovered codemods
112
112
  * for that version (across all integrations).
@@ -145,7 +145,13 @@ export async function discoverIntegrationCodemods(loadedIntegrations = []) {
145
145
  const seenInVersion = new Set();
146
146
 
147
147
  for (const {id, file} of files) {
148
- const label = `Integration "${pkgLabel}" codemod ${version}/${id} (${file})`;
148
+ const shown = integration.__packageDir
149
+ ? path
150
+ .relative(integration.__packageDir, file)
151
+ .split(path.sep)
152
+ .join('/')
153
+ : file;
154
+ const label = `Integration "${pkgLabel}" codemod ${version}/${id} (${shown})`;
149
155
 
150
156
  if (seenInVersion.has(id)) {
151
157
  throw new Error(
@@ -163,6 +163,21 @@ describe('integration codemod discovery', () => {
163
163
  ).rejects.toThrow(/is invalid/i);
164
164
  });
165
165
 
166
+ it('names a broken codemod by its path inside the package', async () => {
167
+ scaffold({
168
+ '1.1.0/no-default.mjs': `export const nope = 1;\n`,
169
+ });
170
+ const project = await Project.load(tmpDir);
171
+ const failure = await discoverIntegrationCodemods(
172
+ project.loadedIntegrations,
173
+ ).then(
174
+ () => null,
175
+ error => error,
176
+ );
177
+ expect(failure?.message).toContain('(codemods/1.1.0/no-default.mjs)');
178
+ expect(failure?.message).not.toContain(tmpDir);
179
+ });
180
+
166
181
  it('fails discovery when default export is not a codemod envelope', async () => {
167
182
  scaffold({
168
183
  '0.2.0/bad.mjs': `export default { title: 'x', transform: () => null };\n`,
@@ -24,6 +24,7 @@ import {
24
24
  runCodeCodemod,
25
25
  runConfigCodemod,
26
26
  } from './run-codemod.mjs';
27
+ import {createFileProtectionResolver} from '../../foundation/fs/file-protection.mjs';
27
28
 
28
29
  /**
29
30
  * Run file-based integration codemods, version-ordered. Config codemods run
@@ -40,18 +41,40 @@ import {
40
41
  * @param {Set<string>} [options.skipCodemods] codemod ids to exclude
41
42
  * @param {import('../../authoring/codemod/type').JscodeshiftFactory} options.jscodeshift
42
43
  * @param {boolean} [options.silent]
43
- * @returns {{totalFilesChanged: number, totalTransformsApplied: number, writtenFiles: string[], errors: Array<{file: string, codemod: string, error: string}>, skippedOptional: Array<import('../../authoring/codemod/type').CodemodEntry>}}
44
+ * @param {string} [options.root]
45
+ * @param {Map<string, string>} [options.contents] staged bytes from earlier dry-run phases
46
+ * @param {{root: string, classify: (file: string) => import('../../foundation/fs/file-protection.mjs').FileProtection[]}} [options.protection]
47
+ * @returns {{totalFilesChanged: number, totalTransformsApplied: number, changedFiles: string[], writtenFiles: string[], stagedContents: Map<string, string>, protectedFiles: import('../../authoring/codemod/type').CodemodRunResult['protectedFiles'], errors: Array<{file: string, codemod: string, error: string}>, skippedOptional: Array<import('../../authoring/codemod/type').CodemodEntry>}}
44
48
  */
45
49
  export function runIntegrationCodemods(
46
50
  versionGroups,
47
- {apply, path: srcPath, codemod, skipCodemods, jscodeshift, silent = false},
51
+ {
52
+ apply,
53
+ path: srcPath,
54
+ codemod,
55
+ skipCodemods,
56
+ jscodeshift,
57
+ silent = false,
58
+ root = process.cwd(),
59
+ protection: providedProtection,
60
+ contents: providedContents,
61
+ },
48
62
  ) {
49
63
  const log = makeLog(silent);
64
+ let protection =
65
+ providedProtection ?? createFileProtectionResolver(path.resolve(root));
66
+ let protectionWriteCount = 0;
67
+ /** In-memory pipeline state keeps ordered dry-runs equivalent to apply. */
68
+ const virtualContents = new Map(providedContents ?? []);
50
69
 
51
70
  let totalFilesChanged = 0;
52
71
  let totalTransformsApplied = 0;
53
72
  /** @type {string[]} */
73
+ const changedFiles = [];
74
+ /** @type {string[]} */
54
75
  const writtenFiles = [];
76
+ /** @type {import('../../authoring/codemod/type').CodemodRunResult['protectedFiles']} */
77
+ const protectedFiles = [];
55
78
  /** @type {Array<{file: string, codemod: string, error: string}>} */
56
79
  const errors = [];
57
80
  /** @type {Array<import('../../authoring/codemod/type').CodemodEntry>} */
@@ -79,11 +102,24 @@ export function runIntegrationCodemods(
79
102
 
80
103
  // Config codemods first.
81
104
  for (const entry of configEntries) {
105
+ if (apply && writtenFiles.length !== protectionWriteCount) {
106
+ protection = createFileProtectionResolver(path.resolve(root));
107
+ protectionWriteCount = writtenFiles.length;
108
+ }
82
109
  log.info(` ${entry.codemod.title} (v${entry.version}, ${entry.package})`);
83
- const r = runConfigCodemod(entry, {apply, log, jscodeshift});
110
+ const r = runConfigCodemod(entry, {
111
+ apply,
112
+ log,
113
+ jscodeshift,
114
+ root,
115
+ protection,
116
+ contents: virtualContents,
117
+ });
84
118
  totalFilesChanged += r.filesChanged;
85
119
  totalTransformsApplied += r.filesChanged;
120
+ changedFiles.push(...r.changedFiles);
86
121
  writtenFiles.push(...r.writtenFiles);
122
+ protectedFiles.push(...r.protectedFiles);
87
123
  errors.push(...r.errors);
88
124
  }
89
125
 
@@ -94,13 +130,26 @@ export function runIntegrationCodemods(
94
130
  ? findSourceFiles(resolvedPath)
95
131
  : [];
96
132
  for (const entry of codeEntries) {
133
+ if (apply && writtenFiles.length !== protectionWriteCount) {
134
+ protection = createFileProtectionResolver(path.resolve(root));
135
+ protectionWriteCount = writtenFiles.length;
136
+ }
97
137
  log.info(
98
138
  ` ${entry.codemod.title} (v${entry.version}, ${entry.package})`,
99
139
  );
100
- const r = runCodeCodemod(entry, files, {apply, log, jscodeshift});
140
+ const r = runCodeCodemod(entry, files, {
141
+ apply,
142
+ log,
143
+ jscodeshift,
144
+ root,
145
+ protection,
146
+ contents: virtualContents,
147
+ });
101
148
  totalFilesChanged += r.filesChanged;
102
149
  totalTransformsApplied += r.filesChanged;
150
+ changedFiles.push(...r.changedFiles);
103
151
  writtenFiles.push(...r.writtenFiles);
152
+ protectedFiles.push(...r.protectedFiles);
104
153
  errors.push(...r.errors);
105
154
  }
106
155
  }
@@ -108,7 +157,10 @@ export function runIntegrationCodemods(
108
157
  return {
109
158
  totalFilesChanged,
110
159
  totalTransformsApplied,
160
+ changedFiles,
111
161
  writtenFiles,
162
+ stagedContents: virtualContents,
163
+ protectedFiles,
112
164
  errors,
113
165
  skippedOptional,
114
166
  };
@@ -0,0 +1,153 @@
1
+ // Copyright (c) Meta Platforms, Inc. and affiliates.
2
+
3
+ import {afterEach, beforeEach, describe, expect, it} from 'vitest';
4
+ import * as fs from 'node:fs';
5
+ import * as os from 'node:os';
6
+ import * as path from 'node:path';
7
+ import {runIntegrationCodemods} from './integration-runner.mjs';
8
+
9
+ let root;
10
+ let originalCwd;
11
+
12
+ beforeEach(() => {
13
+ root = fs.mkdtempSync(
14
+ path.join(os.tmpdir(), 'astryx-integration-protection-'),
15
+ );
16
+ originalCwd = process.cwd();
17
+ process.chdir(root);
18
+ });
19
+
20
+ afterEach(() => {
21
+ process.chdir(originalCwd);
22
+ fs.rmSync(root, {recursive: true, force: true});
23
+ });
24
+
25
+ function write(relative, contents) {
26
+ const file = path.join(root, relative);
27
+ fs.mkdirSync(path.dirname(file), {recursive: true});
28
+ fs.writeFileSync(file, contents);
29
+ return file;
30
+ }
31
+
32
+ describe('integration codemod file protection', () => {
33
+ it('uses the same resolver for integration config and code codemods', async () => {
34
+ write(
35
+ '.gitattributes',
36
+ [
37
+ 'astryx.config.mjs linguist-generated=true',
38
+ 'src/generated.js linguist-vendored=true',
39
+ '',
40
+ ].join('\n'),
41
+ );
42
+ write('astryx.config.mjs', "export default {value: 'old'};\n");
43
+ write('src/generated.js', "export const value = 'old';\n");
44
+
45
+ const groups = [
46
+ {
47
+ version: '1.0.0',
48
+ codemods: [
49
+ {
50
+ id: 'config',
51
+ type: 'config',
52
+ package: '@acme/integration',
53
+ codemod: {
54
+ title: 'config',
55
+ transform: file => file.source.replace('old', 'new'),
56
+ },
57
+ },
58
+ {
59
+ id: 'code',
60
+ type: 'code',
61
+ package: '@acme/integration',
62
+ codemod: {
63
+ title: 'code',
64
+ fileExtensions: ['.js'],
65
+ transform: file => file.source.replace('old', 'new'),
66
+ },
67
+ },
68
+ ],
69
+ },
70
+ ];
71
+ const jscodeshift = (await import('jscodeshift')).default;
72
+ const result = runIntegrationCodemods(groups, {
73
+ apply: true,
74
+ path: path.join(root, 'src'),
75
+ root,
76
+ jscodeshift,
77
+ silent: true,
78
+ });
79
+
80
+ expect(result.totalFilesChanged).toBe(0);
81
+ expect(result.protectedFiles.map(item => item.file).sort()).toEqual([
82
+ 'astryx.config.mjs',
83
+ 'src/generated.js',
84
+ ]);
85
+ expect(
86
+ fs.readFileSync(path.join(root, 'astryx.config.mjs'), 'utf8'),
87
+ ).toContain('old');
88
+ expect(
89
+ fs.readFileSync(path.join(root, 'src/generated.js'), 'utf8'),
90
+ ).toContain('old');
91
+ });
92
+
93
+ it('lets a staged declaration protect another file in the same entry', async () => {
94
+ write('.gitattributes', '# authored\n');
95
+ write('src/x.js', "export const value = 'old';\n");
96
+ const groups = [
97
+ {
98
+ version: '1.0.0',
99
+ codemods: [
100
+ {
101
+ id: 'declare-and-change',
102
+ type: 'code',
103
+ package: '@acme/integration',
104
+ codemod: {
105
+ title: 'declare and change',
106
+ fileExtensions: ['', '.js'],
107
+ transform: file =>
108
+ path.basename(file.path) === '.gitattributes'
109
+ ? 'src/x.js linguist-generated\n'
110
+ : file.source.replace('old', 'new'),
111
+ },
112
+ },
113
+ ],
114
+ },
115
+ ];
116
+ const jscodeshift = (await import('jscodeshift')).default;
117
+
118
+ const preview = runIntegrationCodemods(groups, {
119
+ apply: false,
120
+ path: root,
121
+ root,
122
+ jscodeshift,
123
+ silent: true,
124
+ });
125
+ expect(preview.protectedFiles).toEqual([
126
+ expect.objectContaining({file: 'src/x.js', reason: 'generated'}),
127
+ ]);
128
+ expect(fs.readFileSync(path.join(root, '.gitattributes'), 'utf8')).toBe(
129
+ '# authored\n',
130
+ );
131
+
132
+ const result = runIntegrationCodemods(groups, {
133
+ apply: true,
134
+ path: root,
135
+ root,
136
+ jscodeshift,
137
+ silent: true,
138
+ });
139
+
140
+ expect(fs.readFileSync(path.join(root, '.gitattributes'), 'utf8')).toBe(
141
+ 'src/x.js linguist-generated\n',
142
+ );
143
+ expect(fs.readFileSync(path.join(root, 'src/x.js'), 'utf8')).toContain(
144
+ 'old',
145
+ );
146
+ expect(result.protectedFiles).toEqual([
147
+ expect.objectContaining({
148
+ file: 'src/x.js',
149
+ reason: 'generated',
150
+ }),
151
+ ]);
152
+ });
153
+ });