@astryxdesign/cli 0.7.2-canary.fdd384f → 0.7.2

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 (450) hide show
  1. package/CHANGELOG.md +119 -0
  2. package/README.md +169 -149
  3. package/api/build/_adapter.d.mts +8 -0
  4. package/api/build/_adapter.mjs +12 -1
  5. package/api/build/build.doc.mjs +4 -0
  6. package/api/build/kit/kit.mjs +3 -11
  7. package/api/component/_adapter.d.mts +31 -0
  8. package/api/component/_adapter.mjs +119 -11
  9. package/api/component/component.doc.mjs +6 -1
  10. package/api/component/component.mjs +60 -10
  11. package/api/component/doc-links.test.mjs +386 -0
  12. package/api/discover/_adapter.d.mts +132 -12
  13. package/api/discover/_adapter.mjs +488 -27
  14. package/api/discover/_adapter.test.mjs +599 -17
  15. package/api/discover/_catalog-view.d.mts +16 -5
  16. package/api/discover/_catalog-view.mjs +32 -8
  17. package/api/discover/detail/doc/doc.mjs +9 -1
  18. package/api/discover/discover.d.mts +11 -0
  19. package/api/discover/discover.doc.mjs +9 -7
  20. package/api/discover/discover.mjs +121 -19
  21. package/api/discover/discover.type.d.mts +20 -7
  22. package/api/discover/discover.type.mjs +17 -7
  23. package/api/discover/list/list.d.mts +4 -3
  24. package/api/discover/list/list.mjs +17 -10
  25. package/api/docs/_adapter.d.mts +63 -0
  26. package/api/docs/_adapter.mjs +312 -18
  27. package/api/docs/docs.doc.mjs +2 -1
  28. package/api/docs/docs.mjs +2 -1
  29. package/api/docs/docs.test.mjs +5 -2
  30. package/api/docs/integration-tree.test.mjs +22 -0
  31. package/api/docs/list/list.mjs +12 -2
  32. package/api/docs/reference-blocks.test.mjs +1 -3
  33. package/api/doctor/_adapter.d.mts +108 -0
  34. package/api/doctor/_adapter.mjs +223 -4
  35. package/api/doctor/doctor.d.mts +199 -0
  36. package/api/doctor/doctor.doc.mjs +3 -2
  37. package/api/doctor/doctor.mjs +530 -34
  38. package/api/doctor/doctor.test.mjs +180 -14
  39. package/api/doctor/release-checks.test.mjs +156 -0
  40. package/api/doctor/theme-checks.mjs +16 -3
  41. package/api/doctor/theme-checks.test.mjs +223 -7
  42. package/api/error.d.mts +10 -0
  43. package/api/error.mjs +25 -0
  44. package/api/gap-report/gap-report.d.mts +3 -0
  45. package/api/gap-report/gap-report.doc.mjs +11 -3
  46. package/api/gap-report/gap-report.mjs +152 -48
  47. package/api/gap-report/gap-report.test.mjs +233 -0
  48. package/api/gap-report/gap-report.type.d.mts +41 -0
  49. package/api/gap-report/gap-report.type.mjs +18 -0
  50. package/api/hook/_adapter.mjs +3 -11
  51. package/api/hook/hook.doc.mjs +4 -0
  52. package/api/hook/list/list.d.mts +1 -1
  53. package/api/init/_adapter.d.mts +22 -0
  54. package/api/init/_adapter.mjs +59 -0
  55. package/api/init/init.d.mts +7 -5
  56. package/api/init/init.doc.mjs +28 -7
  57. package/api/init/init.mjs +28 -4
  58. package/api/init/init.type.d.mts +48 -3
  59. package/api/init/init.type.mjs +37 -3
  60. package/api/init/run/run.d.mts +1 -1
  61. package/api/init/run/run.mjs +15 -4
  62. package/api/init/user/user.d.mts +14 -0
  63. package/api/init/user/user.mjs +87 -0
  64. package/api/init/user/user.test.mjs +232 -0
  65. package/api/integration/_adapter.d.mts +33 -0
  66. package/api/integration/_adapter.mjs +58 -0
  67. package/api/integration/authoring-checks.mjs +100 -5
  68. package/api/integration/authoring-checks.test.mjs +769 -0
  69. package/api/integration/authoring-checks.type.d.mts +134 -0
  70. package/api/integration/authoring-checks.type.mjs +60 -1
  71. package/api/integration/integrationCheck.doc.mjs +1 -1
  72. package/api/integration/integrationComponentConflicts.doc.mjs +11 -3
  73. package/api/integration/integrationDocConflicts.doc.mjs +22 -5
  74. package/api/integration/pack-check.mjs +303 -13
  75. package/api/integration/pack-check.test.mjs +371 -10
  76. package/api/integration/validate-integration-fixes.test.mjs +67 -0
  77. package/api/integration/validate-integration.d.mts +8 -0
  78. package/api/integration/validate-integration.mjs +55 -9
  79. package/api/integration/validate-integration.test.mjs +77 -8
  80. package/api/integration/validate-integration.type.d.mts +8 -0
  81. package/api/integration/validate-integration.type.mjs +3 -1
  82. package/api/integration/validate-unread-theme-folders.test.mjs +3 -2
  83. package/api/integration/validateIntegration.doc.mjs +4 -2
  84. package/api/search/_adapter.d.mts +11 -0
  85. package/api/search/_adapter.mjs +25 -0
  86. package/api/search/search.d.mts +73 -13
  87. package/api/search/search.doc.mjs +8 -3
  88. package/api/search/search.domain-priority.test.mjs +108 -18
  89. package/api/search/search.mjs +541 -52
  90. package/api/search/search.test.mjs +143 -1
  91. package/api/search/search.type.d.mts +14 -1
  92. package/api/search/search.type.mjs +6 -1
  93. package/api/swizzle/_adapter.mjs +2 -2
  94. package/api/swizzle/copy/copy.mjs +1 -1
  95. package/api/template/template-integration.test.mjs +1 -1
  96. package/api/theme/_adapter.d.mts +16 -0
  97. package/api/theme/_adapter.mjs +32 -5
  98. package/api/theme/build/build.d.mts +2 -1
  99. package/api/theme/build/build.mjs +70 -14
  100. package/api/theme/build/build.packed-old-core.test.mjs +20 -3
  101. package/api/theme/build/font-warning.mjs +5 -3
  102. package/api/theme/build/font-warning.test.mjs +2 -1
  103. package/api/theme/remove/remove.test.mjs +2 -2
  104. package/api/theme/targets/targets.mjs +3 -10
  105. package/api/theme/themeTargets.doc.mjs +4 -0
  106. package/api/theme/use/use.test.mjs +1 -1
  107. package/api/upgrade/_adapter.d.mts +40 -2
  108. package/api/upgrade/_adapter.mjs +118 -5
  109. package/api/upgrade/run/cli-behind-core.test.mjs +438 -0
  110. package/api/upgrade/run/run.mjs +35 -2
  111. package/api/upgrade/status/status.d.mts +21 -5
  112. package/api/upgrade/status/status.mjs +45 -8
  113. package/api/upgrade/upgrade.doc.mjs +6 -3
  114. package/api/upgrade/upgrade.mjs +5 -1
  115. package/api/upgrade/upgrade.type.d.mts +23 -0
  116. package/api/upgrade/upgrade.type.mjs +16 -1
  117. package/assets/codemods/__tests__/registry.test.mjs +1 -0
  118. package/assets/codemods/registry.mjs +1 -0
  119. package/assets/codemods/runner.mjs +7 -1
  120. package/assets/codemods/transforms/next/index.mjs +1 -11
  121. package/assets/codemods/transforms/v0.7.2/index.mjs +20 -0
  122. package/assets/discover/official-packages.json +367 -0
  123. package/assets/docs/author-a-theme.doc.mjs +2 -2
  124. package/assets/docs/browser-support.doc.mjs +1 -1
  125. package/assets/docs/getting-started.doc.mjs +23 -3
  126. package/assets/docs/styling-overview.doc.mjs +1 -1
  127. package/assets/docs/theme.doc.mjs +1 -1
  128. package/assets/docs/tree/add-a-component.doc.mjs +12 -2
  129. package/assets/docs/tree/add-a-theme.doc.mjs +13 -3
  130. package/assets/docs/tree/add-a-topic.doc.mjs +54 -9
  131. package/assets/docs/tree/agent-guidance.doc.mjs +19 -5
  132. package/assets/docs/tree/block-template.doc.mjs +6 -18
  133. package/assets/docs/tree/build-the-template.doc.mjs +1 -1
  134. package/assets/docs/tree/building-blocks.doc.mjs +4 -0
  135. package/assets/docs/tree/check-the-props.doc.mjs +8 -3
  136. package/assets/docs/tree/check-your-docs.doc.mjs +30 -13
  137. package/assets/docs/tree/checks.doc.mjs +17 -17
  138. package/assets/docs/tree/ci-and-containers.doc.mjs +88 -0
  139. package/assets/docs/tree/cli-command.doc.mjs +142 -0
  140. package/assets/docs/tree/cli.doc.mjs +6 -0
  141. package/assets/docs/tree/codemods.doc.mjs +17 -7
  142. package/assets/docs/tree/commands.doc.mjs +7 -1
  143. package/assets/docs/tree/component-family.doc.mjs +3 -5
  144. package/assets/docs/tree/component-imports.doc.mjs +13 -3
  145. package/assets/docs/tree/components.doc.mjs +7 -1
  146. package/assets/docs/tree/computer-agent-guide.doc.mjs +160 -0
  147. package/assets/docs/tree/computer-integrations.doc.mjs +93 -0
  148. package/assets/docs/tree/configuration.doc.mjs +7 -3
  149. package/assets/docs/tree/debug-and-gap-reports.doc.mjs +41 -22
  150. package/assets/docs/tree/define-the-theme.doc.mjs +6 -5
  151. package/assets/docs/tree/describe-the-component.doc.mjs +2 -2
  152. package/assets/docs/tree/docs.doc.mjs +9 -3
  153. package/assets/docs/tree/document-the-template.doc.mjs +1 -1
  154. package/assets/docs/tree/document-the-theme.doc.mjs +3 -3
  155. package/assets/docs/tree/export-template-assets.doc.mjs +1 -1
  156. package/assets/docs/tree/extend-or-replace.doc.mjs +19 -9
  157. package/assets/docs/tree/fonts-and-assets.doc.mjs +2 -2
  158. package/assets/docs/tree/generate-a-palette.doc.mjs +7 -5
  159. package/assets/docs/tree/global-integrations.doc.mjs +124 -0
  160. package/assets/docs/tree/global.doc.mjs +131 -0
  161. package/assets/docs/tree/grade-template-with-agent.doc.mjs +5 -5
  162. package/assets/docs/tree/install-once.doc.mjs +105 -0
  163. package/assets/docs/tree/integrations.doc.mjs +13 -1
  164. package/assets/docs/tree/layout-spacing.doc.mjs +2 -3
  165. package/assets/docs/tree/layout.doc.mjs +6 -6
  166. package/assets/docs/tree/links.doc.mjs +6 -5
  167. package/assets/docs/tree/migration-setup.doc.mjs +3 -2
  168. package/assets/docs/tree/migration-verification.doc.mjs +0 -1
  169. package/assets/docs/tree/migration.doc.mjs +0 -1
  170. package/assets/docs/tree/monorepos.doc.mjs +84 -0
  171. package/assets/docs/tree/package-and-test.doc.mjs +1 -1
  172. package/assets/docs/tree/page-template.doc.mjs +3 -7
  173. package/assets/docs/tree/problems.doc.mjs +205 -0
  174. package/assets/docs/tree/project-global-or-both.doc.mjs +131 -0
  175. package/assets/docs/tree/publishing.doc.mjs +6 -6
  176. package/assets/docs/tree/quick-start.doc.mjs +52 -26
  177. package/assets/docs/tree/reference.doc.mjs +192 -0
  178. package/assets/docs/tree/replace-a-core-component.doc.mjs +15 -7
  179. package/assets/docs/tree/replace-a-core-template.doc.mjs +9 -11
  180. package/assets/docs/tree/scaffold.doc.mjs +1 -1
  181. package/assets/docs/tree/sections-and-placement.doc.mjs +9 -11
  182. package/assets/docs/tree/see-it-in-an-app.doc.mjs +17 -5
  183. package/assets/docs/tree/short-and-findable.doc.mjs +9 -8
  184. package/assets/docs/tree/single-component.doc.mjs +14 -6
  185. package/assets/docs/tree/start-a-template.doc.mjs +18 -8
  186. package/assets/docs/tree/subcomponent.doc.mjs +6 -6
  187. package/assets/docs/tree/template-assets.doc.mjs +2 -2
  188. package/assets/docs/tree/template-doc-overview.doc.mjs +6 -17
  189. package/assets/docs/tree/template-fonts.doc.mjs +1 -1
  190. package/assets/docs/tree/template-icons.doc.mjs +1 -1
  191. package/assets/docs/tree/template-images-media.doc.mjs +1 -1
  192. package/assets/docs/tree/template-styles.doc.mjs +2 -2
  193. package/assets/docs/tree/templates.doc.mjs +7 -1
  194. package/assets/docs/tree/test-in-an-app.doc.mjs +7 -7
  195. package/assets/docs/tree/test-template-in-app.doc.mjs +5 -5
  196. package/assets/docs/tree/themes.doc.mjs +4 -0
  197. package/assets/docs/tree/tokens-and-setup.doc.mjs +19 -15
  198. package/assets/docs/tree/troubleshooting.doc.mjs +22 -14
  199. package/assets/docs/tree/uninstall.doc.mjs +109 -0
  200. package/assets/docs/tree/updates.doc.mjs +92 -0
  201. package/assets/docs/tree/upgrading.doc.mjs +6 -6
  202. package/assets/docs/tree/use-a-theme-in-an-app.doc.mjs +14 -2
  203. package/assets/docs/tree/verify-packed-template.doc.mjs +3 -3
  204. package/assets/docs/tree/versioning.doc.mjs +9 -9
  205. package/assets/docs/tree/who-answers.doc.mjs +119 -0
  206. package/assets/docs/tree/write-the-template-file.doc.mjs +5 -5
  207. package/assets/docs/working-with-ai.doc.mjs +23 -9
  208. package/authoring/codemod/codemod.doc.mjs +7 -3
  209. package/authoring/config/config.doc.mjs +9 -6
  210. package/authoring/config/type.ts +4 -2
  211. package/authoring/debug/parse.d.mts +5 -5
  212. package/authoring/discover/discover.doc.mjs +7 -1
  213. package/authoring/doctypes/_schema.d.mts +8 -3
  214. package/authoring/doctypes/_schema.mjs +1 -0
  215. package/authoring/doctypes/base/graph-fields.doc.mjs +6 -4
  216. package/authoring/doctypes/command/command.doc.mjs +20 -12
  217. package/authoring/doctypes/command/type.ts +14 -0
  218. package/authoring/doctypes/component/component.doc.mjs +29 -26
  219. package/authoring/doctypes/component/type.ts +15 -5
  220. package/authoring/doctypes/enum/enum.doc.mjs +7 -4
  221. package/authoring/doctypes/function/function.doc.mjs +9 -8
  222. package/authoring/doctypes/hook/hook.doc.mjs +5 -4
  223. package/authoring/doctypes/hook/type.ts +4 -0
  224. package/authoring/doctypes/namespace/namespace.doc.mjs +7 -6
  225. package/authoring/doctypes/reference/reference.doc.mjs +12 -11
  226. package/authoring/doctypes/schema/schema.doc.mjs +9 -9
  227. package/authoring/doctypes/template/template.doc.mjs +2 -2
  228. package/authoring/doctypes/theme/theme.doc.mjs +2 -2
  229. package/authoring/gap-report/gap-report.doc.mjs +2 -2
  230. package/authoring/identity/identity.doc.mjs +3 -3
  231. package/authoring/index.d.ts +1 -0
  232. package/authoring/integration/integration.doc.mjs +22 -5
  233. package/authoring/integration/parse.test.mjs +11 -0
  234. package/authoring/integration/schema.d.mts +14 -3
  235. package/authoring/integration/schema.mjs +33 -2
  236. package/authoring/integration/type.ts +8 -0
  237. package/clients/cli/bin/astryx.mjs +20 -3
  238. package/clients/cli/cli-command.test.mjs +166 -0
  239. package/clients/cli/commands/blog.doc.mjs +1 -0
  240. package/clients/cli/commands/build-theme.batch-report.test.mjs +14 -2
  241. package/clients/cli/commands/build-theme.font-warning.test.mjs +3 -3
  242. package/clients/cli/commands/build-theme.mjs +25 -12
  243. package/clients/cli/commands/build.doc.mjs +1 -0
  244. package/clients/cli/commands/build.exit-codes-doc.test.mjs +4 -0
  245. package/clients/cli/commands/cli-postinstall.test.mjs +49 -1
  246. package/clients/cli/commands/component.doc.mjs +1 -0
  247. package/clients/cli/commands/discover.broken-integration.test.mjs +11 -7
  248. package/clients/cli/commands/discover.doc.mjs +9 -1
  249. package/clients/cli/commands/discover.mjs +144 -93
  250. package/clients/cli/commands/discover.no-source.test.mjs +448 -33
  251. package/clients/cli/commands/discover.sources.test.mjs +20 -1
  252. package/clients/cli/commands/docs.doc.mjs +1 -0
  253. package/clients/cli/commands/docs.mjs +49 -19
  254. package/clients/cli/commands/docs.test.mjs +2 -2
  255. package/clients/cli/commands/doctor-integration-components.doc.mjs +7 -3
  256. package/clients/cli/commands/doctor-integration-docs.doc.mjs +20 -5
  257. package/clients/cli/commands/doctor-integration-templates.doc.mjs +1 -0
  258. package/clients/cli/commands/doctor-integration-validate.doc.mjs +13 -2
  259. package/clients/cli/commands/doctor-integration.test.mjs +52 -4
  260. package/clients/cli/commands/doctor.doc.mjs +12 -2
  261. package/clients/cli/commands/doctor.mjs +102 -7
  262. package/clients/cli/commands/doctor.test.mjs +10 -4
  263. package/clients/cli/commands/gap-report.doc.mjs +15 -2
  264. package/clients/cli/commands/gap-report.mjs +24 -2
  265. package/clients/cli/commands/gap-report.test.mjs +79 -0
  266. package/clients/cli/commands/hook.doc.mjs +1 -0
  267. package/clients/cli/commands/init.behavior.test.mjs +5 -0
  268. package/clients/cli/commands/init.doc.mjs +29 -3
  269. package/clients/cli/commands/integration-add.doc.mjs +7 -0
  270. package/clients/cli/commands/integration-authoring.test.mjs +49 -3
  271. package/clients/cli/commands/integration-check.doc.mjs +10 -2
  272. package/clients/cli/commands/integration-check.test.mjs +175 -1
  273. package/clients/cli/commands/integration-pack.doc.mjs +8 -1
  274. package/clients/cli/commands/integration-verify.doc.mjs +8 -1
  275. package/clients/cli/commands/integration.mjs +15 -9
  276. package/clients/cli/commands/json-contract.test.mjs +11 -2
  277. package/clients/cli/commands/manifest.doc.mjs +1 -0
  278. package/clients/cli/commands/outside-project.test.mjs +774 -0
  279. package/clients/cli/commands/search.doc.mjs +1 -0
  280. package/clients/cli/commands/search.mjs +16 -11
  281. package/clients/cli/commands/search.test.mjs +429 -296
  282. package/clients/cli/commands/setup-nudge.test.mjs +102 -2
  283. package/clients/cli/commands/swizzle.doc.mjs +7 -0
  284. package/clients/cli/commands/swizzle.mjs +5 -3
  285. package/clients/cli/commands/swizzle.routing.test.mjs +19 -2
  286. package/clients/cli/commands/template.doc.mjs +1 -0
  287. package/clients/cli/commands/theme-add-lifecycle.test.mjs +3 -3
  288. package/clients/cli/commands/theme-add.doc.mjs +8 -1
  289. package/clients/cli/commands/theme-build.doc.mjs +1 -0
  290. package/clients/cli/commands/theme-eject.doc.mjs +1 -0
  291. package/clients/cli/commands/theme-list.behavior.test.mjs +6 -0
  292. package/clients/cli/commands/theme-list.doc.mjs +1 -0
  293. package/clients/cli/commands/theme-palette-generate.doc.mjs +1 -0
  294. package/clients/cli/commands/theme-remove.doc.mjs +7 -0
  295. package/clients/cli/commands/theme-targets.doc.mjs +1 -0
  296. package/clients/cli/commands/theme-template.doc.mjs +1 -0
  297. package/clients/cli/commands/theme-use.doc.mjs +7 -0
  298. package/clients/cli/commands/upgrade.config-ordering.test.mjs +2 -1
  299. package/clients/cli/commands/upgrade.doc.mjs +22 -1
  300. package/clients/cli/commands/upgrade.file-protection.test.mjs +3 -2
  301. package/clients/cli/commands/upgrade.hook-output.test.mjs +2 -1
  302. package/clients/cli/commands/upgrade.older-cli.test.mjs +160 -0
  303. package/clients/cli/index.mjs +111 -16
  304. package/clients/cli/lib/cli-error.mjs +4 -1
  305. package/clients/cli/lib/component-format.test.mjs +1 -1
  306. package/clients/cli/lib/define-command.mjs +2 -2
  307. package/clients/cli/lib/json-shim.mjs +3 -1
  308. package/clients/cli/lib/manifest.d.ts +9 -0
  309. package/clients/cli/lib/manifest.mjs +17 -1
  310. package/clients/cli/lib/manifest.test.mjs +25 -0
  311. package/foundation/agent-docs/agent-doc-state.d.mts +35 -6
  312. package/foundation/agent-docs/agent-doc-state.mjs +66 -6
  313. package/foundation/agent-docs/agent-docs.d.mts +4 -3
  314. package/foundation/agent-docs/agent-docs.mjs +11 -9
  315. package/foundation/agent-docs/computer-agent-text.d.mts +133 -0
  316. package/foundation/agent-docs/computer-agent-text.mjs +623 -0
  317. package/foundation/agent-docs/computer-agent-text.test.mjs +602 -0
  318. package/foundation/config/project.d.mts +35 -3
  319. package/foundation/config/project.mjs +145 -8
  320. package/foundation/config/project.test.mjs +66 -0
  321. package/foundation/config/theme-state.mjs +5 -4
  322. package/foundation/discovery/authoring-self-docs.mjs +1 -1
  323. package/foundation/discovery/cli-self-docs.mjs +20 -3
  324. package/foundation/discovery/cli-self-docs.test.mjs +3 -2
  325. package/foundation/discovery/component-replacement.mjs +2 -1
  326. package/foundation/discovery/copied-passages.d.mts +216 -0
  327. package/foundation/discovery/copied-passages.mjs +720 -0
  328. package/foundation/discovery/copied-passages.test.mjs +124 -0
  329. package/foundation/discovery/doc-consistency.d.mts +34 -0
  330. package/foundation/discovery/doc-consistency.mjs +513 -0
  331. package/foundation/discovery/doc-consistency.test.mjs +347 -0
  332. package/foundation/discovery/doc-fluff.d.mts +112 -0
  333. package/foundation/discovery/doc-fluff.mjs +328 -0
  334. package/foundation/discovery/doc-fluff.test.mjs +142 -0
  335. package/foundation/discovery/doc-types.d.mts +1 -1
  336. package/foundation/discovery/doc-types.mjs +15 -8
  337. package/foundation/discovery/doc-types.test.mjs +94 -2
  338. package/foundation/discovery/docs-discovery.d.mts +7 -0
  339. package/foundation/discovery/docs-discovery.mjs +26 -2
  340. package/foundation/discovery/project-typescript.d.mts +11 -2
  341. package/foundation/discovery/project-typescript.mjs +32 -4
  342. package/foundation/discovery/prop-drift.d.mts +26 -7
  343. package/foundation/discovery/prop-drift.mjs +173 -8
  344. package/foundation/discovery/prop-drift.test.mjs +120 -2
  345. package/foundation/discovery/runner-allowlist.d.mts +40 -0
  346. package/foundation/discovery/runner-allowlist.mjs +53 -0
  347. package/foundation/discovery/stray-pointers.d.mts +52 -0
  348. package/foundation/discovery/stray-pointers.mjs +293 -0
  349. package/foundation/discovery/stray-pointers.test.mjs +86 -0
  350. package/foundation/discovery/template-needs.mjs +5 -2
  351. package/foundation/discovery/theme-imports.mjs +8 -4
  352. package/foundation/discovery/theme-imports.test.mjs +1 -1
  353. package/foundation/discovery/type-text.mjs +1 -1
  354. package/foundation/discovery/type-text.test.mjs +1 -1
  355. package/foundation/doc-compiler/doc-loads.test.mjs +42 -2
  356. package/foundation/doc-compiler/inputs.mjs +4 -1
  357. package/foundation/doc-compiler/links.d.mts +32 -0
  358. package/foundation/doc-compiler/links.mjs +163 -26
  359. package/foundation/doc-compiler/links.test.mjs +97 -4
  360. package/foundation/doc-compiler/read.d.mts +1 -1
  361. package/foundation/doc-compiler/tree.mjs +30 -1
  362. package/foundation/doc-compiler/tree.test.mjs +8 -1
  363. package/foundation/env/cli-command.d.mts +83 -0
  364. package/foundation/env/cli-command.mjs +152 -0
  365. package/foundation/env/cli-command.test.mjs +129 -0
  366. package/foundation/env/node-version.test.mjs +15 -3
  367. package/foundation/env/package-manager.d.mts +93 -9
  368. package/foundation/env/package-manager.global.test.mjs +66 -0
  369. package/foundation/env/package-manager.mjs +184 -13
  370. package/foundation/env/package-manager.test.mjs +341 -15
  371. package/foundation/env/project-scope.d.mts +9 -0
  372. package/foundation/env/project-scope.mjs +33 -0
  373. package/foundation/env/search-path.d.mts +42 -0
  374. package/foundation/env/search-path.mjs +205 -0
  375. package/foundation/env/search-path.test.mjs +122 -0
  376. package/foundation/env/user-dirs.d.mts +39 -0
  377. package/foundation/env/user-dirs.mjs +93 -0
  378. package/foundation/env/user-dirs.test.mjs +72 -0
  379. package/foundation/env/user-settings.d.mts +46 -0
  380. package/foundation/env/user-settings.mjs +106 -0
  381. package/foundation/env/user-settings.test.mjs +64 -0
  382. package/foundation/fs/file-protection.mjs +15 -1
  383. package/foundation/fs/module-loader.d.mts +2 -1
  384. package/foundation/fs/module-loader.mjs +60 -4
  385. package/foundation/fs/module-loader.stdout.test.mjs +3 -2
  386. package/foundation/fs/module-loader.test.mjs +194 -0
  387. package/foundation/fs/path-compare.d.mts +33 -0
  388. package/foundation/fs/path-compare.mjs +73 -0
  389. package/foundation/fs/paths.d.mts +57 -1
  390. package/foundation/fs/paths.mjs +131 -3
  391. package/foundation/fs/paths.test.mjs +10 -1
  392. package/foundation/integrations/computer-integrations.d.mts +78 -0
  393. package/foundation/integrations/computer-integrations.mjs +316 -0
  394. package/foundation/integrations/computer-integrations.test.mjs +292 -0
  395. package/foundation/integrations/contribution-inventory.d.mts +11 -0
  396. package/foundation/integrations/contribution-inventory.mjs +50 -2
  397. package/foundation/integrations/integration-warnings.mjs +3 -2
  398. package/foundation/integrations/integration-warnings.test.mjs +8 -9
  399. package/foundation/integrations/integrations.d.mts +25 -1
  400. package/foundation/integrations/integrations.mjs +46 -3
  401. package/foundation/integrations/issue.ts +6 -0
  402. package/foundation/integrations/validate-contributions.d.mts +2 -0
  403. package/foundation/integrations/validate-contributions.mjs +41 -1
  404. package/foundation/release/agent-text-worker.d.mts +4 -0
  405. package/foundation/release/agent-text-worker.mjs +52 -0
  406. package/foundation/release/cache.d.mts +310 -0
  407. package/foundation/release/cache.mjs +1216 -0
  408. package/foundation/release/cache.test.mjs +920 -0
  409. package/foundation/release/choose.d.mts +207 -0
  410. package/foundation/release/choose.mjs +677 -0
  411. package/foundation/release/choose.test.mjs +427 -0
  412. package/foundation/release/core-worker.d.mts +4 -0
  413. package/foundation/release/core-worker.mjs +38 -0
  414. package/foundation/release/global-state.d.mts +259 -0
  415. package/foundation/release/global-state.mjs +472 -0
  416. package/foundation/release/global-state.test.mjs +381 -0
  417. package/foundation/release/handoff.d.mts +60 -0
  418. package/foundation/release/handoff.e2e.test.mjs +278 -0
  419. package/foundation/release/handoff.mjs +357 -0
  420. package/foundation/release/lock-lease.d.mts +23 -0
  421. package/foundation/release/lock-lease.mjs +80 -0
  422. package/foundation/release/notices.d.mts +253 -0
  423. package/foundation/release/notices.mjs +596 -0
  424. package/foundation/release/notices.test.mjs +565 -0
  425. package/foundation/release/npm-guard.d.mts +4 -0
  426. package/foundation/release/npm-guard.mjs +126 -0
  427. package/foundation/release/project.d.mts +141 -0
  428. package/foundation/release/project.mjs +466 -0
  429. package/foundation/release/project.test.mjs +288 -0
  430. package/foundation/release/refresh-worker.d.mts +4 -0
  431. package/foundation/release/refresh-worker.mjs +27 -0
  432. package/foundation/release/run-context.d.mts +47 -0
  433. package/foundation/release/run-context.mjs +149 -0
  434. package/foundation/release/update-notice-env.d.mts +28 -0
  435. package/foundation/release/update-notice-env.mjs +39 -0
  436. package/foundation/response/base.d.ts +26 -3
  437. package/foundation/response/error-codes.d.mts +3 -1
  438. package/foundation/response/error-codes.doc.mjs +12 -0
  439. package/foundation/response/error-codes.mjs +17 -0
  440. package/foundation/response/error-codes.test.mjs +2 -0
  441. package/foundation/response/json-contract.test.mjs +7 -2
  442. package/foundation/response/json.mjs +4 -1
  443. package/foundation/response/response-types.doc.mjs +17 -11
  444. package/foundation/response/response-types.doc.test.mjs +46 -0
  445. package/foundation/response/response.doc.mjs +3 -3
  446. package/foundation/xle/browser-imports.test.mjs +74 -0
  447. package/package.json +11 -11
  448. package/scripts/postinstall.mjs +24 -5
  449. /package/assets/codemods/transforms/{next → v0.7.2}/__tests__/migrate-list-edge-compensation-to-edge.test.mjs +0 -0
  450. /package/assets/codemods/transforms/{next → v0.7.2}/migrate-list-edge-compensation-to-edge.mjs +0 -0
@@ -62,6 +62,10 @@ export const doc = {
62
62
  code: 'ERR_CORE_NOT_FOUND',
63
63
  when: '@astryxdesign/core cannot be resolved from cwd',
64
64
  },
65
+ {
66
+ code: 'ERR_VERSIONS_UNAVAILABLE',
67
+ when: 'outside a project, or in a project with nothing installed, the Core of the release that answers is not beside it or cached, and cannot be installed into the per-user cache (offline, a registry without it, or an unwritable cache)',
68
+ },
65
69
  {code: 'ERR_UNKNOWN_COMPONENT', when: 'a filter matches no target'},
66
70
  ],
67
71
  examples: [
@@ -92,7 +92,7 @@ describe('themeUse', () => {
92
92
  it('names theme add when the slug is not added', async () => {
93
93
  await expect(themeUse('missing', {cwd: tmpDir})).rejects.toMatchObject({
94
94
  code: 'ERR_UNKNOWN_THEME',
95
- message: expect.stringContaining('astryx theme add missing'),
95
+ message: expect.stringContaining(' theme add missing`'),
96
96
  });
97
97
  });
98
98
  });
@@ -31,6 +31,43 @@ export function detectInstalledTargetVersion(cwd: string): {
31
31
  version: string;
32
32
  packageName: string;
33
33
  } | null;
34
+ /**
35
+ * Whether the installed Core is newer than this CLI, so part of the requested
36
+ * range has no codemods here.
37
+ *
38
+ * Core codemods ship inside the CLI. A CLI older than the installed
39
+ * `@astryxdesign/core` has none for the releases after its own version, so it
40
+ * can find no codemods, or stop partway, while the code still needs those
41
+ * releases' migrations. The returned command repeats this upgrade with the CLI
42
+ * release that matches the installed Core.
43
+ *
44
+ * Where an upgrade runs decides what it finds: the installed Core is read
45
+ * from that folder, and `--path` must stay inside it. So the command first
46
+ * changes to `cwd`, the folder this run used, then names `sourcePath`, the
47
+ * directory this run scanned, as an absolute `--path`. Both are named by their
48
+ * physical paths, with every symbolic link resolved: a shell's `cd` follows a
49
+ * link, but the CLI it starts reads its folder with every link resolved and
50
+ * checks `--path` against that. Pasted from any folder, the command finds the
51
+ * same Core and scans the same directory, even after a package runner moved
52
+ * this run (Yarn 1 runs `yarn astryx` at the project root). It is written for
53
+ * a POSIX shell, or for PowerShell on Windows (cmd.exe is not a target), with
54
+ * every argument quoted for that shell.
55
+ *
56
+ * Returns null when the CLI covers the whole range (`to` is not newer than
57
+ * both the CLI and `from`), when the CLI's own version cannot be read, or when
58
+ * the target is legacy `@xds/core`, which no CLI release shares a version with.
59
+ *
60
+ * @param {{from: string, to: string, packageName: string, options: UpgradeOptions, cwd: string, sourcePath: string}} input
61
+ * @returns {import('./upgrade.type.mjs').CliBehindCore | null}
62
+ */
63
+ export function detectCliBehindCore({ from, to, packageName, options, cwd, sourcePath, }: {
64
+ from: string;
65
+ to: string;
66
+ packageName: string;
67
+ options: UpgradeOptions;
68
+ cwd: string;
69
+ sourcePath: string;
70
+ }): import("./upgrade.type.mjs").CliBehindCore | null;
34
71
  /**
35
72
  * @param {(string | null | undefined | false)[] | undefined} files
36
73
  * @returns {string[]}
@@ -104,9 +141,9 @@ export function ensureCodemodDeps({ installDeps }?: {
104
141
  * Run the CORE registry codemods. Runs BEFORE the config is loaded so a core
105
142
  * CONFIG codemod can repair a config the strict loader would otherwise reject.
106
143
  * @param {CoreVersionManifest[]} versionManifests
107
- * @param {{apply: boolean, path: string, codemod?: string, skipCodemods: Set<string>, root?: string, protection?: {root: string, classify: (file: string) => import('../../foundation/fs/file-protection.mjs').FileProtection[]}, silent?: boolean}} options
144
+ * @param {{apply: boolean, path: string, codemod?: string, skipCodemods: Set<string>, root?: string, protection?: {root: string, classify: (file: string) => import('../../foundation/fs/file-protection.mjs').FileProtection[]}, silent?: boolean, coversTarget?: boolean}} options
108
145
  */
109
- export function runCoreCodemods(versionManifests: CoreVersionManifest[], { apply, path: srcPath, codemod, skipCodemods, root, protection, silent }: {
146
+ export function runCoreCodemods(versionManifests: CoreVersionManifest[], { apply, path: srcPath, codemod, skipCodemods, root, protection, silent, coversTarget, }: {
110
147
  apply: boolean;
111
148
  path: string;
112
149
  codemod?: string;
@@ -117,6 +154,7 @@ export function runCoreCodemods(versionManifests: CoreVersionManifest[], { apply
117
154
  classify: (file: string) => import("../../foundation/fs/file-protection.mjs").FileProtection[];
118
155
  };
119
156
  silent?: boolean;
157
+ coversTarget?: boolean;
120
158
  }): Promise<{
121
159
  totalFilesChanged: number;
122
160
  totalTransformsApplied: number;
@@ -34,7 +34,15 @@ import {
34
34
  inspectAgentDocs,
35
35
  renderAgentDocsBlock,
36
36
  } from '../../foundation/agent-docs/agent-docs.mjs';
37
- import {formatCliCommand} from '../../foundation/env/package-manager.mjs';
37
+ import {
38
+ CLI_PACKAGE,
39
+ commandInFolder,
40
+ formatCliCommand,
41
+ getDlxPrefix,
42
+ quoteCommandArgument,
43
+ } from '../../foundation/env/package-manager.mjs';
44
+ import {isValidSemver, semverGt} from '../../foundation/env/semver.mjs';
45
+ import {CLI_ROOT} from '../../foundation/fs/paths.mjs';
38
46
  import {Project, providerLedgerOf} from '../../foundation/config/project.mjs';
39
47
  import {loadIntegrations} from '../../foundation/integrations/integrations.mjs';
40
48
  import {resolveProviders} from '../../foundation/integrations/provider-resolution.mjs';
@@ -96,6 +104,99 @@ export function detectInstalledTargetVersion(cwd) {
96
104
  return null;
97
105
  }
98
106
 
107
+ /**
108
+ * The running CLI's own version, read from its package.json.
109
+ * @returns {string | null} null when it cannot be read or is not semver
110
+ */
111
+ function readRunningCliVersion() {
112
+ try {
113
+ const pkg = JSON.parse(
114
+ fs.readFileSync(path.join(CLI_ROOT, 'package.json'), 'utf-8'),
115
+ );
116
+ return isValidSemver(pkg.version) ? pkg.version : null;
117
+ } catch {
118
+ return null;
119
+ }
120
+ }
121
+
122
+ /**
123
+ * Whether the installed Core is newer than this CLI, so part of the requested
124
+ * range has no codemods here.
125
+ *
126
+ * Core codemods ship inside the CLI. A CLI older than the installed
127
+ * `@astryxdesign/core` has none for the releases after its own version, so it
128
+ * can find no codemods, or stop partway, while the code still needs those
129
+ * releases' migrations. The returned command repeats this upgrade with the CLI
130
+ * release that matches the installed Core.
131
+ *
132
+ * Where an upgrade runs decides what it finds: the installed Core is read
133
+ * from that folder, and `--path` must stay inside it. So the command first
134
+ * changes to `cwd`, the folder this run used, then names `sourcePath`, the
135
+ * directory this run scanned, as an absolute `--path`. Both are named by their
136
+ * physical paths, with every symbolic link resolved: a shell's `cd` follows a
137
+ * link, but the CLI it starts reads its folder with every link resolved and
138
+ * checks `--path` against that. Pasted from any folder, the command finds the
139
+ * same Core and scans the same directory, even after a package runner moved
140
+ * this run (Yarn 1 runs `yarn astryx` at the project root). It is written for
141
+ * a POSIX shell, or for PowerShell on Windows (cmd.exe is not a target), with
142
+ * every argument quoted for that shell.
143
+ *
144
+ * Returns null when the CLI covers the whole range (`to` is not newer than
145
+ * both the CLI and `from`), when the CLI's own version cannot be read, or when
146
+ * the target is legacy `@xds/core`, which no CLI release shares a version with.
147
+ *
148
+ * @param {{from: string, to: string, packageName: string, options: UpgradeOptions, cwd: string, sourcePath: string}} input
149
+ * @returns {import('./upgrade.type.mjs').CliBehindCore | null}
150
+ */
151
+ export function detectCliBehindCore({
152
+ from,
153
+ to,
154
+ packageName,
155
+ options,
156
+ cwd,
157
+ sourcePath,
158
+ }) {
159
+ if (packageName !== '@astryxdesign/core') return null;
160
+ const cliVersion = readRunningCliVersion();
161
+ if (!cliVersion || !semverGt(to, cliVersion) || !semverGt(to, from)) {
162
+ return null;
163
+ }
164
+ const runFolder = path.resolve(cwd);
165
+ const folder = physicalFolder(runFolder);
166
+ const scanned = path.join(folder, path.relative(runFolder, sourcePath));
167
+ const args = ['upgrade', '--from', from, '--path', scanned];
168
+ if (options.codemod) args.push('--codemod', options.codemod);
169
+ for (const name of options.skipCodemod ?? []) {
170
+ args.push('--skip-codemod', name);
171
+ }
172
+ for (const spec of options.integration ?? []) {
173
+ args.push('--integration', spec);
174
+ }
175
+ if (options.force) args.push('--force');
176
+ if (options.installDeps) args.push('--install-deps');
177
+ if (options.apply) args.push('--apply');
178
+ const upgrade = [
179
+ getDlxPrefix(cwd),
180
+ ...[`${CLI_PACKAGE}@${to}`, ...args].map(arg => quoteCommandArgument(arg)),
181
+ ].join(' ');
182
+ return {cliVersion, command: commandInFolder(folder, upgrade)};
183
+ }
184
+
185
+ /**
186
+ * A folder as the operating system reports it to a process that runs there,
187
+ * with every symbolic link on the way resolved. Falls back to the path as
188
+ * given when it cannot be resolved.
189
+ * @param {string} dir an absolute folder
190
+ * @returns {string}
191
+ */
192
+ function physicalFolder(dir) {
193
+ try {
194
+ return fs.realpathSync(dir);
195
+ } catch {
196
+ return dir;
197
+ }
198
+ }
199
+
99
200
  /**
100
201
  * @param {(string | null | undefined | false)[] | undefined} files
101
202
  * @returns {string[]}
@@ -332,11 +433,20 @@ export async function ensureCodemodDeps({installDeps} = {}) {
332
433
  * Run the CORE registry codemods. Runs BEFORE the config is loaded so a core
333
434
  * CONFIG codemod can repair a config the strict loader would otherwise reject.
334
435
  * @param {CoreVersionManifest[]} versionManifests
335
- * @param {{apply: boolean, path: string, codemod?: string, skipCodemods: Set<string>, root?: string, protection?: {root: string, classify: (file: string) => import('../../foundation/fs/file-protection.mjs').FileProtection[]}, silent?: boolean}} options
436
+ * @param {{apply: boolean, path: string, codemod?: string, skipCodemods: Set<string>, root?: string, protection?: {root: string, classify: (file: string) => import('../../foundation/fs/file-protection.mjs').FileProtection[]}, silent?: boolean, coversTarget?: boolean}} options
336
437
  */
337
438
  export async function runCoreCodemods(
338
439
  versionManifests,
339
- {apply, path: srcPath, codemod, skipCodemods, root, protection, silent},
440
+ {
441
+ apply,
442
+ path: srcPath,
443
+ codemod,
444
+ skipCodemods,
445
+ root,
446
+ protection,
447
+ silent,
448
+ coversTarget,
449
+ },
340
450
  ) {
341
451
  return runCodemods(versionManifests, {
342
452
  apply,
@@ -346,6 +456,7 @@ export async function runCoreCodemods(
346
456
  silent: silent ?? logger.silent,
347
457
  root,
348
458
  protection,
459
+ coversTarget,
349
460
  });
350
461
  }
351
462
 
@@ -367,7 +478,7 @@ export async function loadProjectContext(cwd, extraIntegrationSpecs = []) {
367
478
  // The CLI debug preflight may already have imported this config before core
368
479
  // CONFIG codemods run. Upgrade needs the bytes now on disk, not that module
369
480
  // cache entry; ordinary Project discovery remains cached by default.
370
- const project = await Project.load(cwd, {fresh: true});
481
+ const project = await Project.load(cwd, {fresh: true, core: 'project'});
371
482
  const postCodemodHooks = project.config.hooks?.postCodemod ?? [];
372
483
  const configuredSpecs = new Set(project.integrations ?? []);
373
484
  // Configured packages come from Project, which has already resolved provider
@@ -411,7 +522,9 @@ export async function loadProjectContext(cwd, extraIntegrationSpecs = []) {
411
522
  const extras =
412
523
  extraSpecs.length === 0
413
524
  ? []
414
- : await loadIntegrations(extraSpecs, {resolveProviders: false});
525
+ : // Resolved from the project, as --integration names a package it
526
+ // installed, not from whichever folder the process started in.
527
+ await loadIntegrations(extraSpecs, {cwd, resolveProviders: false});
415
528
  const {integrations, ledger} = resolveProviders(
416
529
  [
417
530
  ...candidates,
@@ -0,0 +1,438 @@
1
+ // Copyright (c) Meta Platforms, Inc. and affiliates.
2
+
3
+ /**
4
+ * @file Core codemods ship inside the CLI, so a CLI older than the installed
5
+ * Core has none for the releases after its own version. Every result past the
6
+ * up-to-date exit says so in `cliBehindCore`, with the same upgrade pinned to
7
+ * the CLI release that matches the Core; a CLI that covers the range returns
8
+ * what it always did. The pinned command changes to the folder the run used
9
+ * and names the directory it scanned as an absolute path, both with symbolic
10
+ * links resolved, so a POSIX shell runs it there, with every argument
11
+ * unchanged, wherever it is pasted, even when upgrade() reached the project
12
+ * through a link.
13
+ */
14
+
15
+ import {describe, it, expect, beforeEach, afterEach, vi} from 'vitest';
16
+ import {execFileSync, spawnSync} from 'node:child_process';
17
+ import * as fs from 'node:fs';
18
+ import * as os from 'node:os';
19
+ import * as path from 'node:path';
20
+ import {upgrade} from '../upgrade.mjs';
21
+ import {logCliBehindCore} from '../status/status.mjs';
22
+ import {logger} from '../../logger.mjs';
23
+ import {CLI_ROOT} from '../../../foundation/fs/paths.mjs';
24
+ import {
25
+ commandInFolder,
26
+ getDlxPrefix,
27
+ quoteCommandArgument,
28
+ } from '../../../foundation/env/package-manager.mjs';
29
+
30
+ const SLOW = 30_000;
31
+
32
+ const CLI_VERSION = JSON.parse(
33
+ fs.readFileSync(path.join(CLI_ROOT, 'package.json'), 'utf8'),
34
+ ).version;
35
+ const [MAJOR, MINOR] = CLI_VERSION.split('.').map(Number);
36
+ /** A Core release after this CLI: it has no codemods for it. */
37
+ const NEWER_CORE = `${MAJOR}.${MINOR + 1}.0`;
38
+
39
+ /** A source file a 0.7.0 codemod rewrites (`emptySearchResultsText`). */
40
+ const TYPEAHEAD_SOURCE =
41
+ "import {Typeahead} from '@astryxdesign/core';\n" +
42
+ 'export const x = <Typeahead emptySearchResultsText="None" />;\n';
43
+
44
+ let dir;
45
+
46
+ /** @param {string} version @param {string} [packageName] */
47
+ function installCore(version, packageName = '@astryxdesign/core') {
48
+ const core = path.join(dir, 'node_modules', ...packageName.split('/'));
49
+ fs.mkdirSync(core, {recursive: true});
50
+ fs.writeFileSync(
51
+ path.join(core, 'package.json'),
52
+ JSON.stringify({name: packageName, version}),
53
+ );
54
+ }
55
+
56
+ /** @param {string} rel @param {string} body */
57
+ function write(rel, body) {
58
+ const file = path.join(dir, rel);
59
+ fs.mkdirSync(path.dirname(file), {recursive: true});
60
+ fs.writeFileSync(file, body);
61
+ }
62
+
63
+ /**
64
+ * A path in the project as a process running there sees it: every symbolic
65
+ * link resolved.
66
+ * @param {string} rel
67
+ */
68
+ function inProject(rel) {
69
+ return path.join(fs.realpathSync(dir), rel);
70
+ }
71
+
72
+ /**
73
+ * The pinned command for this project, as the CLI prints it.
74
+ * @param {...string} args the arguments after `upgrade`
75
+ */
76
+ function pinned(...args) {
77
+ return commandInFolder(
78
+ fs.realpathSync(dir),
79
+ [
80
+ getDlxPrefix(dir),
81
+ `@astryxdesign/cli@${NEWER_CORE}`,
82
+ 'upgrade',
83
+ ...args.map(arg => quoteCommandArgument(arg)),
84
+ ].join(' '),
85
+ );
86
+ }
87
+
88
+ /**
89
+ * Stand-ins for the package runners: each prints the folder it runs in and the
90
+ * arguments the shell passed.
91
+ */
92
+ const RUNNERS = ['npx', 'pnpm', 'yarn', 'bunx']
93
+ .map(
94
+ name =>
95
+ `${name}() { "$0" -e 'process.stdout.write(JSON.stringify({cwd: process.cwd(), argv: process.argv.slice(1)}))' -- "$@"; }`,
96
+ )
97
+ .join('; ');
98
+
99
+ /**
100
+ * What a POSIX shell runs for `command` pasted in `cwd`: the folder the
101
+ * package runner ran in, and its arguments from the pinned CLI package on.
102
+ * @param {string} command
103
+ * @param {string} cwd the folder the command is pasted in
104
+ * @returns {{cwd: string, args: string[]}}
105
+ */
106
+ function runInShell(command, cwd) {
107
+ const ran = JSON.parse(
108
+ execFileSync(
109
+ '/bin/sh',
110
+ ['-c', `${RUNNERS}; ${command}`, process.execPath],
111
+ {
112
+ cwd,
113
+ encoding: 'utf8',
114
+ },
115
+ ),
116
+ );
117
+ return {
118
+ cwd: ran.cwd,
119
+ args: ran.argv.slice(ran.argv.indexOf(`@astryxdesign/cli@${NEWER_CORE}`)),
120
+ };
121
+ }
122
+
123
+ beforeEach(() => {
124
+ dir = fs.mkdtempSync(path.join(os.tmpdir(), 'upg-cli-behind-'));
125
+ write('package.json', JSON.stringify({name: 'consumer', version: '1.0.0'}));
126
+ write('src/index.tsx', TYPEAHEAD_SOURCE);
127
+ });
128
+
129
+ afterEach(() => fs.rmSync(dir, {recursive: true, force: true}));
130
+
131
+ describe('a CLI older than the installed Core', {timeout: SLOW}, () => {
132
+ // The defect: the range past this CLI has no codemods here, so the run
133
+ // reported `no_codemods` (and "already up to date") and exited 0, while the
134
+ // project still needed the newer releases' migrations.
135
+ it('marks a range this CLI has no codemods for, and names the CLI that does', async () => {
136
+ installCore(NEWER_CORE);
137
+
138
+ const res = await upgrade({from: CLI_VERSION, apply: true}, {cwd: dir});
139
+
140
+ expect(res.type).toBe('upgrade.status');
141
+ expect(res.data.status).toBe('no_codemods');
142
+ expect(res.data.cliBehindCore).toEqual({
143
+ cliVersion: CLI_VERSION,
144
+ command: pinned(
145
+ '--from',
146
+ CLI_VERSION,
147
+ '--path',
148
+ inProject('src'),
149
+ '--apply',
150
+ ),
151
+ });
152
+ });
153
+
154
+ it('runs the codemods it has for a range it covers only in part, and marks the rest', async () => {
155
+ installCore(NEWER_CORE);
156
+
157
+ const res = await upgrade({from: '0.6.8', apply: true}, {cwd: dir});
158
+
159
+ expect(res.type).toBe('upgrade.run');
160
+ expect(res.data.codemods).toBeGreaterThan(0);
161
+ expect(fs.readFileSync(path.join(dir, 'src/index.tsx'), 'utf8')).toContain(
162
+ 'emptySearchText="None"',
163
+ );
164
+ expect(res.data.cliBehindCore).toEqual({
165
+ cliVersion: CLI_VERSION,
166
+ command: pinned('--from', '0.6.8', '--path', inProject('src'), '--apply'),
167
+ });
168
+ });
169
+
170
+ it('repeats the invocation in the pinned command', async () => {
171
+ installCore(NEWER_CORE);
172
+ write('my src/index.tsx', TYPEAHEAD_SOURCE);
173
+ // An installed integration the run names, so --integration is repeated.
174
+ write(
175
+ 'node_modules/@acme/widgets/package.json',
176
+ JSON.stringify({name: '@acme/widgets', version: '1.0.0'}),
177
+ );
178
+ write(
179
+ 'node_modules/@acme/widgets/astryx.integration.mjs',
180
+ 'export default {};\n',
181
+ );
182
+
183
+ const res = await upgrade(
184
+ {
185
+ from: CLI_VERSION,
186
+ path: 'my src',
187
+ codemod: 'only-this',
188
+ skipCodemod: ['first', 'second'],
189
+ integration: ['@acme/widgets'],
190
+ force: true,
191
+ installDeps: true,
192
+ },
193
+ {cwd: dir},
194
+ );
195
+
196
+ expect(res.data.cliBehindCore?.command).toBe(
197
+ pinned(
198
+ '--from',
199
+ CLI_VERSION,
200
+ '--path',
201
+ inProject('my src'),
202
+ '--codemod',
203
+ 'only-this',
204
+ '--skip-codemod',
205
+ 'first',
206
+ '--skip-codemod',
207
+ 'second',
208
+ '--integration',
209
+ '@acme/widgets',
210
+ '--force',
211
+ '--install-deps',
212
+ ),
213
+ );
214
+ });
215
+
216
+ // Where an upgrade runs decides what it finds: the installed Core is read
217
+ // from that folder, and --path must stay inside it. The command printed
218
+ // without a folder failed when pasted anywhere else: ERR_PATH_TRAVERSAL from
219
+ // a subfolder, ERR_VERSION_DETECT from a parent. A package runner can also
220
+ // move the run: Yarn 1 runs `yarn astryx` at the project root.
221
+ it('changes to the folder the run used and names the directory it scanned as an absolute path', async () => {
222
+ installCore(NEWER_CORE);
223
+ write('app/index.tsx', TYPEAHEAD_SOURCE);
224
+ const project = fs.realpathSync(dir);
225
+
226
+ for (const [given, scanned] of [
227
+ [undefined, inProject('src')],
228
+ ['.', project],
229
+ ['app', inProject('app')],
230
+ ['./app/', inProject('app')],
231
+ ]) {
232
+ const res = await upgrade(
233
+ {from: CLI_VERSION, ...(given ? {path: given} : {})},
234
+ {cwd: dir},
235
+ );
236
+ const command = /** @type {string} */ (res.data.cliBehindCore?.command);
237
+
238
+ expect(command).toBe(pinned('--from', CLI_VERSION, '--path', scanned));
239
+ if (process.platform !== 'win32') {
240
+ for (const pastedIn of [
241
+ path.join(dir, 'app'),
242
+ path.dirname(dir),
243
+ path.parse(dir).root,
244
+ ]) {
245
+ const ran = runInShell(command, pastedIn);
246
+ expect(ran.cwd).toBe(project);
247
+ expect(ran.args[ran.args.indexOf('--path') + 1]).toBe(scanned);
248
+ }
249
+ }
250
+ }
251
+ });
252
+
253
+ it.skipIf(process.platform === 'win32')(
254
+ 'quotes the command so a POSIX shell enters the folder and passes every argument unchanged',
255
+ async () => {
256
+ fs.rmSync(dir, {recursive: true, force: true});
257
+ dir = fs.mkdtempSync(
258
+ path.join(os.tmpdir(), 'upg cost $5 `date` \'one\' "two" !x -'),
259
+ );
260
+ write(
261
+ 'package.json',
262
+ JSON.stringify({name: 'consumer', version: '1.0.0'}),
263
+ );
264
+ write('src/index.tsx', TYPEAHEAD_SOURCE);
265
+ installCore(NEWER_CORE);
266
+
267
+ const res = await upgrade(
268
+ {
269
+ from: CLI_VERSION,
270
+ skipCodemod: ["it's", 'a$b c'],
271
+ apply: true,
272
+ },
273
+ {cwd: dir},
274
+ );
275
+
276
+ const ran = runInShell(
277
+ /** @type {string} */ (res.data.cliBehindCore?.command),
278
+ path.dirname(dir),
279
+ );
280
+
281
+ expect(ran.cwd).toBe(fs.realpathSync(dir));
282
+ expect(ran.args).toEqual([
283
+ `@astryxdesign/cli@${NEWER_CORE}`,
284
+ 'upgrade',
285
+ '--from',
286
+ CLI_VERSION,
287
+ '--path',
288
+ inProject('src'),
289
+ '--skip-codemod',
290
+ "it's",
291
+ '--skip-codemod',
292
+ 'a$b c',
293
+ '--apply',
294
+ ]);
295
+ },
296
+ );
297
+ });
298
+
299
+ /** The CLI's own bin, which the stand-in runners below run. */
300
+ const BIN = path.join(CLI_ROOT, 'clients', 'cli', 'bin', 'astryx.mjs');
301
+
302
+ /**
303
+ * Stand-ins for the package runners: each drops its own words and the pinned
304
+ * package, then runs this CLI with the rest of the command.
305
+ */
306
+ const RUNS_THIS_CLI = /** @type {Array<[string, number]>} */ ([
307
+ ['npx', 1],
308
+ ['bunx', 1],
309
+ ['pnpm', 2],
310
+ ['yarn', 2],
311
+ ])
312
+ .map(
313
+ ([name, words]) =>
314
+ `${name}() { shift ${words}; "$0" ${quoteCommandArgument(BIN)} "$@"; }`,
315
+ )
316
+ .join('; ');
317
+
318
+ // A shell's `cd` follows a symbolic link, but the CLI it starts reads its
319
+ // folder with every link resolved and checks --path against that. A command
320
+ // that kept the link upgrade() was given exited 1 with ERR_PATH_TRAVERSAL.
321
+ describe.skipIf(process.platform === 'win32')(
322
+ 'a project upgrade() reaches through a symbolic link',
323
+ {timeout: SLOW},
324
+ () => {
325
+ /** @type {string} */
326
+ let base;
327
+
328
+ beforeEach(() => {
329
+ fs.rmSync(dir, {recursive: true, force: true});
330
+ base = fs.mkdtempSync(path.join(os.tmpdir(), 'upg-link-'));
331
+ dir = path.join(base, 'real', 'my app');
332
+ write(
333
+ 'package.json',
334
+ JSON.stringify({name: 'consumer', version: '1.0.0'}),
335
+ );
336
+ write('src/index.tsx', TYPEAHEAD_SOURCE);
337
+ installCore(NEWER_CORE);
338
+ });
339
+
340
+ afterEach(() => fs.rmSync(base, {recursive: true, force: true}));
341
+
342
+ /**
343
+ * Run upgrade() from `cwd`, then the command it prints, in /bin/sh, from
344
+ * `cwd` and from the folder above the project.
345
+ * @param {string} cwd
346
+ */
347
+ async function expectPrintedCommandToRun(cwd) {
348
+ const res = await upgrade({from: CLI_VERSION}, {cwd});
349
+ const command = /** @type {string} */ (res.data.cliBehindCore?.command);
350
+
351
+ for (const pastedIn of [cwd, base]) {
352
+ const ran = spawnSync(
353
+ '/bin/sh',
354
+ ['-c', `${RUNS_THIS_CLI}; ${command}`, process.execPath],
355
+ {cwd: pastedIn, encoding: 'utf8'},
356
+ );
357
+
358
+ expect(ran.status, ran.stderr).toBe(0);
359
+ expect(ran.stdout).toContain(`Installed target: ${NEWER_CORE}`);
360
+ expect(ran.stdout).toContain(`Scanning ${inProject('src')} `);
361
+ }
362
+ expect(command).toBe(
363
+ pinned('--from', CLI_VERSION, '--path', inProject('src')),
364
+ );
365
+ }
366
+
367
+ it('prints a command that runs, from a folder reached through a link above the project', async () => {
368
+ fs.symlinkSync(path.join(base, 'real'), path.join(base, 'link'), 'dir');
369
+
370
+ await expectPrintedCommandToRun(path.join(base, 'link', 'my app'));
371
+ });
372
+
373
+ it('prints a command that runs, through a project root that is a link', async () => {
374
+ fs.symlinkSync(dir, path.join(base, 'app'), 'dir');
375
+
376
+ await expectPrintedCommandToRun(path.join(base, 'app'));
377
+ });
378
+ },
379
+ );
380
+
381
+ describe('a CLI that covers the range', {timeout: SLOW}, () => {
382
+ it('adds nothing when the installed Core is this CLI release', async () => {
383
+ installCore(CLI_VERSION);
384
+
385
+ const res = await upgrade({from: '0.6.8'}, {cwd: dir});
386
+
387
+ expect(res.type).toBe('upgrade.run');
388
+ expect('cliBehindCore' in res.data).toBe(false);
389
+ });
390
+
391
+ it('adds nothing when --from is already the installed Core', async () => {
392
+ installCore(NEWER_CORE);
393
+
394
+ const res = await upgrade({from: NEWER_CORE}, {cwd: dir});
395
+
396
+ expect(res.data.status).toBe('up_to_date');
397
+ expect('cliBehindCore' in res.data).toBe(false);
398
+
399
+ const forced = await upgrade({from: NEWER_CORE, force: true}, {cwd: dir});
400
+
401
+ expect(forced.data.status).toBe('no_codemods');
402
+ expect('cliBehindCore' in forced.data).toBe(false);
403
+ });
404
+
405
+ it('adds nothing for legacy @xds/core, which shares no version with a CLI release', async () => {
406
+ installCore(NEWER_CORE, '@xds/core');
407
+
408
+ const res = await upgrade({from: CLI_VERSION}, {cwd: dir});
409
+
410
+ expect(res.data.status).toBe('no_codemods');
411
+ expect('cliBehindCore' in res.data).toBe(false);
412
+ });
413
+ });
414
+
415
+ describe('logCliBehindCore: the warning names the shell its command is written for', () => {
416
+ it.each([
417
+ ['win32', 'Run it in PowerShell with the CLI that matches your Core:'],
418
+ ['linux', 'Run it with the CLI that matches your Core:'],
419
+ ['darwin', 'Run it with the CLI that matches your Core:'],
420
+ ])('on %s', (platform, ending) => {
421
+ const warn = vi.spyOn(logger, 'warn').mockImplementation(() => {});
422
+ try {
423
+ logCliBehindCore(
424
+ {cliVersion: '0.7.0', command: 'cd x; npx y'},
425
+ '0.8.0',
426
+ platform,
427
+ );
428
+ expect(warn.mock.calls.map(([line]) => String(line))).toEqual([
429
+ expect.stringMatching(
430
+ new RegExp(`${ending.replace(/[.*+?^${}()|[\]\\]/gu, '\\$&')}$`, 'u'),
431
+ ),
432
+ ' cd x; npx y',
433
+ ]);
434
+ } finally {
435
+ warn.mockRestore();
436
+ }
437
+ });
438
+ });