@astryxdesign/cli 0.7.2-canary.fe2c655 → 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 (509) hide show
  1. package/CHANGELOG.md +119 -0
  2. package/README.md +175 -154
  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/help/help.mjs +1 -1
  7. package/api/build/kit/kit.mjs +3 -11
  8. package/api/component/_adapter.d.mts +72 -1
  9. package/api/component/_adapter.mjs +212 -18
  10. package/api/component/component.doc.mjs +6 -1
  11. package/api/component/component.mjs +82 -15
  12. package/api/component/component.test.mjs +80 -0
  13. package/api/component/doc-links.test.mjs +386 -0
  14. package/api/component/integration-export-name.test.mjs +644 -0
  15. package/api/component/list/list.mjs +42 -4
  16. package/api/discover/_adapter.d.mts +132 -12
  17. package/api/discover/_adapter.mjs +488 -27
  18. package/api/discover/_adapter.test.mjs +599 -17
  19. package/api/discover/_catalog-view.d.mts +16 -5
  20. package/api/discover/_catalog-view.mjs +32 -8
  21. package/api/discover/detail/doc/doc.mjs +9 -1
  22. package/api/discover/discover.d.mts +11 -0
  23. package/api/discover/discover.doc.mjs +9 -7
  24. package/api/discover/discover.mjs +121 -19
  25. package/api/discover/discover.type.d.mts +20 -7
  26. package/api/discover/discover.type.mjs +17 -7
  27. package/api/discover/list/list.d.mts +4 -3
  28. package/api/discover/list/list.mjs +17 -10
  29. package/api/docs/_adapter.d.mts +74 -0
  30. package/api/docs/_adapter.mjs +553 -16
  31. package/api/docs/docs.doc.mjs +2 -1
  32. package/api/docs/docs.mjs +2 -1
  33. package/api/docs/docs.test.mjs +5 -2
  34. package/api/docs/integration-tree.test.mjs +22 -0
  35. package/api/docs/integrationDocs.test.mjs +124 -0
  36. package/api/docs/list/list.mjs +12 -2
  37. package/api/docs/reference-blocks.test.mjs +1 -3
  38. package/api/doctor/_adapter.d.mts +108 -0
  39. package/api/doctor/_adapter.mjs +223 -4
  40. package/api/doctor/doctor.d.mts +199 -0
  41. package/api/doctor/doctor.doc.mjs +3 -2
  42. package/api/doctor/doctor.mjs +530 -34
  43. package/api/doctor/doctor.test.mjs +180 -14
  44. package/api/doctor/release-checks.test.mjs +156 -0
  45. package/api/doctor/theme-checks.mjs +17 -4
  46. package/api/doctor/theme-checks.test.mjs +223 -7
  47. package/api/error.d.mts +10 -0
  48. package/api/error.mjs +25 -0
  49. package/api/gap-report/gap-report.d.mts +3 -0
  50. package/api/gap-report/gap-report.doc.mjs +11 -3
  51. package/api/gap-report/gap-report.mjs +219 -52
  52. package/api/gap-report/gap-report.test.mjs +233 -0
  53. package/api/gap-report/gap-report.type.d.mts +41 -0
  54. package/api/gap-report/gap-report.type.mjs +18 -0
  55. package/api/hook/_adapter.mjs +3 -11
  56. package/api/hook/hook.doc.mjs +4 -0
  57. package/api/hook/list/list.d.mts +1 -1
  58. package/api/index.d.mts +2 -0
  59. package/api/index.mjs +2 -0
  60. package/api/init/_adapter.d.mts +22 -0
  61. package/api/init/_adapter.mjs +59 -0
  62. package/api/init/init.d.mts +7 -5
  63. package/api/init/init.doc.mjs +28 -7
  64. package/api/init/init.mjs +28 -4
  65. package/api/init/init.type.d.mts +48 -3
  66. package/api/init/init.type.mjs +37 -3
  67. package/api/init/run/run.d.mts +1 -1
  68. package/api/init/run/run.mjs +17 -6
  69. package/api/init/user/user.d.mts +14 -0
  70. package/api/init/user/user.mjs +87 -0
  71. package/api/init/user/user.test.mjs +232 -0
  72. package/api/integration/_adapter.d.mts +45 -0
  73. package/api/integration/_adapter.mjs +112 -0
  74. package/api/integration/add-contribution.component-names.test.mjs +66 -0
  75. package/api/integration/add-contribution.mjs +8 -4
  76. package/api/integration/add-contribution.test.mjs +4 -2
  77. package/api/integration/authoring-checks.mjs +110 -6
  78. package/api/integration/authoring-checks.test.mjs +808 -0
  79. package/api/integration/authoring-checks.type.d.mts +143 -0
  80. package/api/integration/authoring-checks.type.mjs +74 -2
  81. package/api/integration/check/check.d.mts +96 -0
  82. package/api/integration/check/check.mjs +357 -0
  83. package/api/integration/check/check.test.mjs +489 -0
  84. package/api/integration/integration-check.type.d.mts +118 -0
  85. package/api/integration/integration-check.type.mjs +84 -0
  86. package/api/integration/integrationCheck.doc.d.mts +10 -0
  87. package/api/integration/integrationCheck.doc.mjs +73 -0
  88. package/api/integration/integrationComponentConflicts.doc.mjs +45 -6
  89. package/api/integration/integrationDocConflicts.doc.mjs +22 -5
  90. package/api/integration/pack-check.mjs +364 -120
  91. package/api/integration/pack-check.test.mjs +730 -10
  92. package/api/integration/validate-integration-fixes.test.mjs +67 -0
  93. package/api/integration/validate-integration.d.mts +8 -0
  94. package/api/integration/validate-integration.mjs +63 -9
  95. package/api/integration/validate-integration.test.mjs +88 -8
  96. package/api/integration/validate-integration.type.d.mts +16 -0
  97. package/api/integration/validate-integration.type.mjs +14 -1
  98. package/api/integration/validate-unread-theme-folders.test.mjs +3 -2
  99. package/api/integration/validateIntegration.doc.mjs +19 -3
  100. package/api/search/_adapter.d.mts +11 -0
  101. package/api/search/_adapter.mjs +25 -0
  102. package/api/search/search.d.mts +82 -13
  103. package/api/search/search.doc.mjs +8 -3
  104. package/api/search/search.domain-priority.test.mjs +108 -18
  105. package/api/search/search.integration-check.test.mjs +24 -0
  106. package/api/search/search.mjs +645 -64
  107. package/api/search/search.test.mjs +179 -1
  108. package/api/search/search.type.d.mts +14 -1
  109. package/api/search/search.type.mjs +6 -1
  110. package/api/swizzle/_adapter.mjs +2 -2
  111. package/api/swizzle/copy/copy.mjs +29 -15
  112. package/api/template/template-integration.test.mjs +1 -1
  113. package/api/template/template.test.mjs +120 -3
  114. package/api/theme/_adapter.d.mts +16 -0
  115. package/api/theme/_adapter.mjs +32 -5
  116. package/api/theme/build/build.d.mts +2 -1
  117. package/api/theme/build/build.family.test.mjs +64 -0
  118. package/api/theme/build/build.mjs +250 -38
  119. package/api/theme/build/build.packed-old-core.test.mjs +20 -3
  120. package/api/theme/build/font-warning.mjs +5 -3
  121. package/api/theme/build/font-warning.test.mjs +2 -1
  122. package/api/theme/remove/remove.test.mjs +2 -2
  123. package/api/theme/targets/targets.mjs +3 -10
  124. package/api/theme/themeBuild.doc.mjs +4 -4
  125. package/api/theme/themeTargets.doc.mjs +4 -0
  126. package/api/theme/use/use.test.mjs +1 -1
  127. package/api/upgrade/_adapter.d.mts +40 -2
  128. package/api/upgrade/_adapter.mjs +118 -5
  129. package/api/upgrade/run/cli-behind-core.test.mjs +438 -0
  130. package/api/upgrade/run/run.mjs +35 -2
  131. package/api/upgrade/status/status.d.mts +21 -5
  132. package/api/upgrade/status/status.mjs +45 -8
  133. package/api/upgrade/upgrade.doc.mjs +6 -3
  134. package/api/upgrade/upgrade.mjs +5 -1
  135. package/api/upgrade/upgrade.type.d.mts +23 -0
  136. package/api/upgrade/upgrade.type.mjs +16 -1
  137. package/assets/codemods/__tests__/registry.test.mjs +1 -0
  138. package/assets/codemods/registry.mjs +1 -0
  139. package/assets/codemods/runner.mjs +7 -1
  140. package/assets/codemods/transforms/v0.7.2/__tests__/migrate-list-edge-compensation-to-edge.test.mjs +120 -0
  141. package/assets/codemods/transforms/v0.7.2/index.mjs +20 -0
  142. package/assets/codemods/transforms/v0.7.2/migrate-list-edge-compensation-to-edge.mjs +198 -0
  143. package/assets/discover/official-packages.json +367 -0
  144. package/assets/docs/README.md +10 -0
  145. package/assets/docs/author-a-theme.doc.mjs +3 -3
  146. package/assets/docs/browser-support.doc.mjs +1 -1
  147. package/assets/docs/getting-started.doc.mjs +26 -6
  148. package/assets/docs/styling-overview.doc.mjs +1 -1
  149. package/assets/docs/theme.doc.dense.mjs +1 -1
  150. package/assets/docs/theme.doc.mjs +2 -2
  151. package/assets/docs/theme.doc.zh.mjs +1 -1
  152. package/assets/docs/tree/add-a-component.doc.mjs +22 -2
  153. package/assets/docs/tree/add-a-theme.doc.mjs +13 -3
  154. package/assets/docs/tree/add-a-topic.doc.mjs +54 -9
  155. package/assets/docs/tree/agent-guidance.doc.mjs +19 -5
  156. package/assets/docs/tree/block-template.doc.mjs +8 -20
  157. package/assets/docs/tree/build-the-template.doc.mjs +1 -1
  158. package/assets/docs/tree/building-blocks.doc.mjs +4 -0
  159. package/assets/docs/tree/check-the-props.doc.mjs +201 -0
  160. package/assets/docs/tree/check-your-docs.doc.mjs +69 -11
  161. package/assets/docs/tree/checks.doc.mjs +32 -24
  162. package/assets/docs/tree/ci-and-containers.doc.mjs +88 -0
  163. package/assets/docs/tree/cli-command.doc.mjs +142 -0
  164. package/assets/docs/tree/cli.doc.mjs +6 -0
  165. package/assets/docs/tree/codemods.doc.mjs +17 -7
  166. package/assets/docs/tree/commands.doc.mjs +7 -1
  167. package/assets/docs/tree/component-family.doc.mjs +8 -6
  168. package/assets/docs/tree/component-imports.doc.mjs +21 -3
  169. package/assets/docs/tree/components.doc.mjs +7 -1
  170. package/assets/docs/tree/computer-agent-guide.doc.mjs +160 -0
  171. package/assets/docs/tree/computer-integrations.doc.mjs +93 -0
  172. package/assets/docs/tree/configuration.doc.mjs +7 -3
  173. package/assets/docs/tree/debug-and-gap-reports.doc.mjs +41 -22
  174. package/assets/docs/tree/define-the-theme.doc.mjs +6 -5
  175. package/assets/docs/tree/describe-the-component.doc.mjs +3 -3
  176. package/assets/docs/tree/docs.doc.mjs +9 -3
  177. package/assets/docs/tree/document-the-template.doc.mjs +1 -1
  178. package/assets/docs/tree/document-the-theme.doc.mjs +3 -3
  179. package/assets/docs/tree/export-template-assets.doc.mjs +1 -1
  180. package/assets/docs/tree/extend-or-replace.doc.mjs +19 -9
  181. package/assets/docs/tree/fonts-and-assets.doc.mjs +2 -2
  182. package/assets/docs/tree/generate-a-palette.doc.mjs +7 -5
  183. package/assets/docs/tree/global-integrations.doc.mjs +124 -0
  184. package/assets/docs/tree/global.doc.mjs +131 -0
  185. package/assets/docs/tree/grade-template-with-agent.doc.mjs +5 -5
  186. package/assets/docs/tree/install-once.doc.mjs +105 -0
  187. package/assets/docs/tree/integrations.doc.mjs +13 -1
  188. package/assets/docs/tree/layout-spacing.doc.mjs +2 -3
  189. package/assets/docs/tree/layout.doc.mjs +6 -6
  190. package/assets/docs/tree/links.doc.mjs +7 -6
  191. package/assets/docs/tree/migration-setup.doc.mjs +3 -2
  192. package/assets/docs/tree/migration-verification.doc.mjs +0 -1
  193. package/assets/docs/tree/migration.doc.mjs +0 -1
  194. package/assets/docs/tree/monorepos.doc.mjs +84 -0
  195. package/assets/docs/tree/package-and-test.doc.mjs +1 -1
  196. package/assets/docs/tree/page-template.doc.mjs +4 -8
  197. package/assets/docs/tree/problems.doc.mjs +205 -0
  198. package/assets/docs/tree/project-global-or-both.doc.mjs +131 -0
  199. package/assets/docs/tree/publishing.doc.mjs +6 -6
  200. package/assets/docs/tree/quick-start.doc.mjs +53 -27
  201. package/assets/docs/tree/reference.doc.mjs +192 -0
  202. package/assets/docs/tree/replace-a-core-component.doc.mjs +15 -7
  203. package/assets/docs/tree/replace-a-core-template.doc.mjs +10 -12
  204. package/assets/docs/tree/scaffold.doc.mjs +1 -1
  205. package/assets/docs/tree/sections-and-placement.doc.mjs +9 -11
  206. package/assets/docs/tree/see-it-in-an-app.doc.mjs +17 -5
  207. package/assets/docs/tree/short-and-findable.doc.mjs +9 -8
  208. package/assets/docs/tree/single-component.doc.mjs +15 -6
  209. package/assets/docs/tree/start-a-template.doc.mjs +18 -8
  210. package/assets/docs/tree/subcomponent.doc.mjs +6 -6
  211. package/assets/docs/tree/template-assets.doc.mjs +2 -2
  212. package/assets/docs/tree/template-doc-overview.doc.mjs +6 -17
  213. package/assets/docs/tree/template-fonts.doc.mjs +1 -1
  214. package/assets/docs/tree/template-icons.doc.mjs +1 -1
  215. package/assets/docs/tree/template-images-media.doc.mjs +1 -1
  216. package/assets/docs/tree/template-styles.doc.mjs +2 -2
  217. package/assets/docs/tree/templates.doc.mjs +7 -1
  218. package/assets/docs/tree/test-in-an-app.doc.mjs +7 -7
  219. package/assets/docs/tree/test-template-in-app.doc.mjs +5 -5
  220. package/assets/docs/tree/themes.doc.mjs +4 -0
  221. package/assets/docs/tree/tokens-and-setup.doc.mjs +19 -15
  222. package/assets/docs/tree/troubleshooting.doc.mjs +29 -13
  223. package/assets/docs/tree/type-scale.doc.mjs +3 -3
  224. package/assets/docs/tree/uninstall.doc.mjs +109 -0
  225. package/assets/docs/tree/updates.doc.mjs +92 -0
  226. package/assets/docs/tree/upgrading.doc.mjs +6 -6
  227. package/assets/docs/tree/use-a-theme-in-an-app.doc.mjs +14 -2
  228. package/assets/docs/tree/verify-packed-template.doc.mjs +3 -3
  229. package/assets/docs/tree/versioning.doc.mjs +10 -9
  230. package/assets/docs/tree/who-answers.doc.mjs +119 -0
  231. package/assets/docs/tree/write-the-template-file.doc.mjs +7 -6
  232. package/assets/docs/use-a-theme.doc.mjs +2 -2
  233. package/assets/docs/working-with-ai.doc.mjs +66 -13
  234. package/assets/theme.template.ts +1 -1
  235. package/authoring/codemod/codemod.doc.mjs +7 -3
  236. package/authoring/config/config.doc.mjs +9 -6
  237. package/authoring/config/type.ts +4 -2
  238. package/authoring/debug/parse.d.mts +5 -5
  239. package/authoring/discover/discover.doc.mjs +7 -1
  240. package/authoring/doctypes/_schema.d.mts +8 -3
  241. package/authoring/doctypes/_schema.mjs +1 -0
  242. package/authoring/doctypes/base/graph-fields.doc.mjs +6 -4
  243. package/authoring/doctypes/command/command.doc.mjs +20 -12
  244. package/authoring/doctypes/command/type.ts +14 -0
  245. package/authoring/doctypes/component/component.doc.mjs +30 -27
  246. package/authoring/doctypes/component/type.ts +21 -8
  247. package/authoring/doctypes/enum/enum.doc.mjs +7 -4
  248. package/authoring/doctypes/function/function.doc.mjs +9 -8
  249. package/authoring/doctypes/hook/hook.doc.mjs +5 -4
  250. package/authoring/doctypes/hook/type.ts +4 -0
  251. package/authoring/doctypes/namespace/namespace.doc.mjs +7 -6
  252. package/authoring/doctypes/reference/reference.doc.mjs +12 -11
  253. package/authoring/doctypes/schema/schema.doc.mjs +9 -9
  254. package/authoring/doctypes/template/integration-template.doc.d.mts +11 -0
  255. package/authoring/doctypes/template/integration-template.doc.mjs +49 -0
  256. package/authoring/doctypes/template/parse.d.mts +6 -2
  257. package/authoring/doctypes/template/parse.mjs +3 -1
  258. package/authoring/doctypes/template/template.doc.mjs +3 -3
  259. package/authoring/doctypes/template/type.ts +22 -0
  260. package/authoring/doctypes/theme/theme.doc.mjs +2 -2
  261. package/authoring/gap-report/gap-report.doc.mjs +2 -2
  262. package/authoring/identity/identity.doc.mjs +3 -3
  263. package/authoring/index.d.ts +2 -0
  264. package/authoring/integration/integration.doc.mjs +30 -6
  265. package/authoring/integration/parse.test.mjs +21 -0
  266. package/authoring/integration/schema.d.mts +16 -3
  267. package/authoring/integration/schema.mjs +34 -2
  268. package/authoring/integration/type.ts +12 -0
  269. package/clients/cli/bin/astryx.mjs +20 -3
  270. package/clients/cli/cli-command.test.mjs +166 -0
  271. package/clients/cli/commands/blog.doc.mjs +1 -0
  272. package/clients/cli/commands/build-theme.batch-report.test.mjs +17 -3
  273. package/clients/cli/commands/build-theme.font-warning.test.mjs +3 -3
  274. package/clients/cli/commands/build-theme.mjs +26 -13
  275. package/clients/cli/commands/build-theme.path-safety.test.mjs +224 -6
  276. package/clients/cli/commands/build-theme.registry.test.mjs +5 -2
  277. package/clients/cli/commands/build.doc.mjs +1 -0
  278. package/clients/cli/commands/build.exit-codes-doc.test.mjs +4 -0
  279. package/clients/cli/commands/cli-postinstall.test.mjs +49 -1
  280. package/clients/cli/commands/component.doc.mjs +1 -0
  281. package/clients/cli/commands/discover.broken-integration.test.mjs +11 -7
  282. package/clients/cli/commands/discover.doc.mjs +9 -1
  283. package/clients/cli/commands/discover.mjs +144 -93
  284. package/clients/cli/commands/discover.no-source.test.mjs +448 -33
  285. package/clients/cli/commands/discover.sources.test.mjs +20 -1
  286. package/clients/cli/commands/docs.doc.mjs +1 -0
  287. package/clients/cli/commands/docs.mjs +49 -19
  288. package/clients/cli/commands/docs.test.mjs +2 -2
  289. package/clients/cli/commands/doctor-integration-components.doc.mjs +19 -4
  290. package/clients/cli/commands/doctor-integration-docs.doc.mjs +20 -5
  291. package/clients/cli/commands/doctor-integration-templates.doc.mjs +1 -0
  292. package/clients/cli/commands/doctor-integration-validate.doc.mjs +22 -2
  293. package/clients/cli/commands/doctor-integration.test.mjs +888 -3
  294. package/clients/cli/commands/doctor.doc.mjs +12 -2
  295. package/clients/cli/commands/doctor.mjs +292 -7
  296. package/clients/cli/commands/doctor.test.mjs +10 -4
  297. package/clients/cli/commands/gap-report.doc.mjs +15 -2
  298. package/clients/cli/commands/gap-report.mjs +24 -2
  299. package/clients/cli/commands/gap-report.test.mjs +79 -0
  300. package/clients/cli/commands/hook.doc.mjs +1 -0
  301. package/clients/cli/commands/init.behavior.test.mjs +5 -0
  302. package/clients/cli/commands/init.doc.mjs +29 -3
  303. package/clients/cli/commands/integration-add.doc.mjs +7 -0
  304. package/clients/cli/commands/integration-authoring.test.mjs +49 -3
  305. package/clients/cli/commands/integration-check.doc.mjs +93 -0
  306. package/clients/cli/commands/integration-check.test.mjs +441 -0
  307. package/clients/cli/commands/integration-pack.doc.mjs +8 -1
  308. package/clients/cli/commands/integration-verify.doc.mjs +8 -1
  309. package/clients/cli/commands/integration.doc.mjs +6 -2
  310. package/clients/cli/commands/integration.mjs +119 -7
  311. package/clients/cli/commands/json-contract.test.mjs +11 -2
  312. package/clients/cli/commands/manifest.doc.mjs +1 -0
  313. package/clients/cli/commands/outside-project.test.mjs +774 -0
  314. package/clients/cli/commands/search.doc.mjs +1 -0
  315. package/clients/cli/commands/search.mjs +16 -11
  316. package/clients/cli/commands/search.test.mjs +429 -296
  317. package/clients/cli/commands/setup-nudge.test.mjs +102 -2
  318. package/clients/cli/commands/swizzle.doc.mjs +7 -0
  319. package/clients/cli/commands/swizzle.mjs +5 -3
  320. package/clients/cli/commands/swizzle.routing.test.mjs +19 -2
  321. package/clients/cli/commands/template.doc.mjs +1 -0
  322. package/clients/cli/commands/text-json-parity.test.mjs +5 -0
  323. package/clients/cli/commands/theme-add-lifecycle.test.mjs +3 -3
  324. package/clients/cli/commands/theme-add.doc.mjs +8 -1
  325. package/clients/cli/commands/theme-build.doc.mjs +3 -2
  326. package/clients/cli/commands/theme-eject.doc.mjs +1 -0
  327. package/clients/cli/commands/theme-list.behavior.test.mjs +6 -0
  328. package/clients/cli/commands/theme-list.doc.mjs +1 -0
  329. package/clients/cli/commands/theme-palette-generate.doc.mjs +1 -0
  330. package/clients/cli/commands/theme-remove.doc.mjs +7 -0
  331. package/clients/cli/commands/theme-targets.doc.mjs +1 -0
  332. package/clients/cli/commands/theme-template.doc.mjs +1 -0
  333. package/clients/cli/commands/theme-use.doc.mjs +7 -0
  334. package/clients/cli/commands/upgrade.config-ordering.test.mjs +2 -1
  335. package/clients/cli/commands/upgrade.doc.mjs +22 -1
  336. package/clients/cli/commands/upgrade.file-protection.test.mjs +3 -2
  337. package/clients/cli/commands/upgrade.hook-output.test.mjs +2 -1
  338. package/clients/cli/commands/upgrade.older-cli.test.mjs +160 -0
  339. package/clients/cli/index.mjs +112 -16
  340. package/clients/cli/lib/cli-error.mjs +4 -1
  341. package/clients/cli/lib/component-format.test.mjs +1 -1
  342. package/clients/cli/lib/define-command.mjs +2 -2
  343. package/clients/cli/lib/json-shim.mjs +3 -1
  344. package/clients/cli/lib/manifest.d.ts +9 -0
  345. package/clients/cli/lib/manifest.mjs +17 -1
  346. package/clients/cli/lib/manifest.test.mjs +25 -0
  347. package/foundation/agent-docs/agent-doc-state.d.mts +35 -6
  348. package/foundation/agent-docs/agent-doc-state.mjs +66 -6
  349. package/foundation/agent-docs/agent-docs.d.mts +4 -3
  350. package/foundation/agent-docs/agent-docs.mjs +11 -9
  351. package/foundation/agent-docs/computer-agent-text.d.mts +133 -0
  352. package/foundation/agent-docs/computer-agent-text.mjs +623 -0
  353. package/foundation/agent-docs/computer-agent-text.test.mjs +602 -0
  354. package/foundation/config/project.d.mts +35 -3
  355. package/foundation/config/project.mjs +226 -7
  356. package/foundation/config/project.test.mjs +66 -0
  357. package/foundation/config/theme-state.mjs +5 -4
  358. package/foundation/discovery/authoring-self-docs.mjs +2 -1
  359. package/foundation/discovery/cli-self-docs.mjs +20 -3
  360. package/foundation/discovery/cli-self-docs.test.mjs +3 -2
  361. package/foundation/discovery/component-discovery.d.mts +190 -10
  362. package/foundation/discovery/component-discovery.export-names.test.mjs +760 -0
  363. package/foundation/discovery/component-discovery.mjs +864 -56
  364. package/foundation/discovery/component-discovery.test.mjs +96 -0
  365. package/foundation/discovery/component-replacement.mjs +20 -1
  366. package/foundation/discovery/component-replacement.test.mjs +40 -0
  367. package/foundation/discovery/copied-passages.d.mts +216 -0
  368. package/foundation/discovery/copied-passages.mjs +720 -0
  369. package/foundation/discovery/copied-passages.test.mjs +124 -0
  370. package/foundation/discovery/doc-consistency.d.mts +34 -0
  371. package/foundation/discovery/doc-consistency.mjs +513 -0
  372. package/foundation/discovery/doc-consistency.test.mjs +347 -0
  373. package/foundation/discovery/doc-fluff.d.mts +112 -0
  374. package/foundation/discovery/doc-fluff.mjs +328 -0
  375. package/foundation/discovery/doc-fluff.test.mjs +142 -0
  376. package/foundation/discovery/doc-types.d.mts +157 -0
  377. package/foundation/discovery/doc-types.mjs +1231 -0
  378. package/foundation/discovery/doc-types.test.mjs +744 -0
  379. package/foundation/discovery/docs-discovery.d.mts +88 -0
  380. package/foundation/discovery/docs-discovery.mjs +83 -2
  381. package/foundation/discovery/integration-doc-files.d.mts +22 -0
  382. package/foundation/discovery/integration-doc-files.mjs +231 -0
  383. package/foundation/discovery/integration-doc-files.test.mjs +246 -0
  384. package/foundation/discovery/module-exports.d.mts +27 -0
  385. package/foundation/discovery/module-exports.mjs +356 -31
  386. package/foundation/discovery/module-exports.test.mjs +172 -0
  387. package/foundation/discovery/package-tsconfig.d.mts +14 -0
  388. package/foundation/discovery/package-tsconfig.mjs +228 -0
  389. package/foundation/discovery/package-tsconfig.test.mjs +210 -0
  390. package/foundation/discovery/project-typescript.d.mts +32 -0
  391. package/foundation/discovery/project-typescript.mjs +89 -0
  392. package/foundation/discovery/prop-drift.d.mts +297 -0
  393. package/foundation/discovery/prop-drift.mjs +2428 -0
  394. package/foundation/discovery/prop-drift.test.mjs +2337 -0
  395. package/foundation/discovery/runner-allowlist.d.mts +40 -0
  396. package/foundation/discovery/runner-allowlist.mjs +53 -0
  397. package/foundation/discovery/stray-pointers.d.mts +52 -0
  398. package/foundation/discovery/stray-pointers.mjs +293 -0
  399. package/foundation/discovery/stray-pointers.test.mjs +86 -0
  400. package/foundation/discovery/template-adapter.mjs +74 -5
  401. package/foundation/discovery/template-needs.mjs +5 -2
  402. package/foundation/discovery/theme-imports.mjs +8 -4
  403. package/foundation/discovery/theme-imports.test.mjs +1 -1
  404. package/foundation/discovery/type-match.d.mts +53 -0
  405. package/foundation/discovery/type-match.mjs +660 -0
  406. package/foundation/discovery/type-match.test.mjs +169 -0
  407. package/foundation/discovery/type-text.d.mts +68 -0
  408. package/foundation/discovery/type-text.mjs +658 -0
  409. package/foundation/discovery/type-text.test.mjs +196 -0
  410. package/foundation/discovery/unresolved-references.d.mts +62 -0
  411. package/foundation/discovery/unresolved-references.mjs +790 -0
  412. package/foundation/discovery/unresolved-references.test.mjs +567 -0
  413. package/foundation/doc-compiler/doc-loads.test.mjs +51 -1
  414. package/foundation/doc-compiler/inputs.mjs +27 -1
  415. package/foundation/doc-compiler/inputs.test.mjs +0 -1
  416. package/foundation/doc-compiler/links.d.mts +41 -5
  417. package/foundation/doc-compiler/links.mjs +167 -28
  418. package/foundation/doc-compiler/links.test.mjs +97 -4
  419. package/foundation/doc-compiler/read.d.mts +1 -1
  420. package/foundation/doc-compiler/tree.mjs +30 -1
  421. package/foundation/doc-compiler/tree.test.mjs +8 -1
  422. package/foundation/env/cli-command.d.mts +83 -0
  423. package/foundation/env/cli-command.mjs +152 -0
  424. package/foundation/env/cli-command.test.mjs +129 -0
  425. package/foundation/env/node-version.test.mjs +15 -3
  426. package/foundation/env/package-manager.d.mts +93 -9
  427. package/foundation/env/package-manager.global.test.mjs +66 -0
  428. package/foundation/env/package-manager.mjs +184 -13
  429. package/foundation/env/package-manager.test.mjs +341 -15
  430. package/foundation/env/project-scope.d.mts +9 -0
  431. package/foundation/env/project-scope.mjs +33 -0
  432. package/foundation/env/search-path.d.mts +42 -0
  433. package/foundation/env/search-path.mjs +205 -0
  434. package/foundation/env/search-path.test.mjs +122 -0
  435. package/foundation/env/user-dirs.d.mts +39 -0
  436. package/foundation/env/user-dirs.mjs +93 -0
  437. package/foundation/env/user-dirs.test.mjs +72 -0
  438. package/foundation/env/user-settings.d.mts +46 -0
  439. package/foundation/env/user-settings.mjs +106 -0
  440. package/foundation/env/user-settings.test.mjs +64 -0
  441. package/foundation/fs/file-protection.mjs +15 -1
  442. package/foundation/fs/module-loader.d.mts +2 -1
  443. package/foundation/fs/module-loader.mjs +60 -4
  444. package/foundation/fs/module-loader.stdout.test.mjs +3 -2
  445. package/foundation/fs/module-loader.test.mjs +194 -0
  446. package/foundation/fs/path-compare.d.mts +33 -0
  447. package/foundation/fs/path-compare.mjs +73 -0
  448. package/foundation/fs/paths.d.mts +57 -1
  449. package/foundation/fs/paths.mjs +131 -3
  450. package/foundation/fs/paths.test.mjs +10 -1
  451. package/foundation/integrations/computer-integrations.d.mts +78 -0
  452. package/foundation/integrations/computer-integrations.mjs +316 -0
  453. package/foundation/integrations/computer-integrations.test.mjs +292 -0
  454. package/foundation/integrations/contribution-inventory.d.mts +11 -0
  455. package/foundation/integrations/contribution-inventory.mjs +56 -3
  456. package/foundation/integrations/integration-warnings.mjs +3 -2
  457. package/foundation/integrations/integration-warnings.test.mjs +8 -9
  458. package/foundation/integrations/integrations.d.mts +25 -1
  459. package/foundation/integrations/integrations.mjs +46 -3
  460. package/foundation/integrations/issue.ts +6 -0
  461. package/foundation/integrations/validate-contributions.d.mts +2 -0
  462. package/foundation/integrations/validate-contributions.mjs +41 -1
  463. package/foundation/release/agent-text-worker.d.mts +4 -0
  464. package/foundation/release/agent-text-worker.mjs +52 -0
  465. package/foundation/release/cache.d.mts +310 -0
  466. package/foundation/release/cache.mjs +1216 -0
  467. package/foundation/release/cache.test.mjs +920 -0
  468. package/foundation/release/choose.d.mts +207 -0
  469. package/foundation/release/choose.mjs +677 -0
  470. package/foundation/release/choose.test.mjs +427 -0
  471. package/foundation/release/core-worker.d.mts +4 -0
  472. package/foundation/release/core-worker.mjs +38 -0
  473. package/foundation/release/global-state.d.mts +259 -0
  474. package/foundation/release/global-state.mjs +472 -0
  475. package/foundation/release/global-state.test.mjs +381 -0
  476. package/foundation/release/handoff.d.mts +60 -0
  477. package/foundation/release/handoff.e2e.test.mjs +278 -0
  478. package/foundation/release/handoff.mjs +357 -0
  479. package/foundation/release/lock-lease.d.mts +23 -0
  480. package/foundation/release/lock-lease.mjs +80 -0
  481. package/foundation/release/notices.d.mts +253 -0
  482. package/foundation/release/notices.mjs +596 -0
  483. package/foundation/release/notices.test.mjs +565 -0
  484. package/foundation/release/npm-guard.d.mts +4 -0
  485. package/foundation/release/npm-guard.mjs +126 -0
  486. package/foundation/release/project.d.mts +141 -0
  487. package/foundation/release/project.mjs +466 -0
  488. package/foundation/release/project.test.mjs +288 -0
  489. package/foundation/release/refresh-worker.d.mts +4 -0
  490. package/foundation/release/refresh-worker.mjs +27 -0
  491. package/foundation/release/run-context.d.mts +47 -0
  492. package/foundation/release/run-context.mjs +149 -0
  493. package/foundation/release/update-notice-env.d.mts +28 -0
  494. package/foundation/release/update-notice-env.mjs +39 -0
  495. package/foundation/response/base.d.ts +26 -3
  496. package/foundation/response/error-codes.d.mts +3 -1
  497. package/foundation/response/error-codes.doc.mjs +12 -0
  498. package/foundation/response/error-codes.mjs +17 -0
  499. package/foundation/response/error-codes.test.mjs +2 -0
  500. package/foundation/response/json-contract.test.mjs +7 -2
  501. package/foundation/response/json.mjs +4 -1
  502. package/foundation/response/response-types.doc.mjs +22 -11
  503. package/foundation/response/response-types.doc.test.mjs +46 -0
  504. package/foundation/response/response.doc.mjs +3 -3
  505. package/foundation/xle/browser-imports.test.mjs +74 -0
  506. package/foundation/xle/registry.mjs +4 -0
  507. package/foundation/xle/registry.test.mjs +28 -0
  508. package/package.json +11 -11
  509. package/scripts/postinstall.mjs +24 -5
@@ -0,0 +1,596 @@
1
+ // Copyright (c) Meta Platforms, Inc. and affiliates.
2
+
3
+ /**
4
+ * @file Update notices (spec AST-081 FR10), the way `gh` and the Vercel CLI
5
+ * tell their users about a newer release: after the command finishes, on
6
+ * standard error, at most once a day per notice, never in machine-readable
7
+ * output, and never when the environment turns them off (`CI`,
8
+ * `NO_UPDATE_NOTIFIER`; update-notice-env.mjs). Unlike `gh`, a notice prints
9
+ * when standard error is not a terminal too, because agents run the CLI
10
+ * without one.
11
+ *
12
+ * Each notice is two lines:
13
+ *
14
+ * A new release of Astryx is available: 0.7.2 → 0.7.3
15
+ * To upgrade, run: npm i -g @astryxdesign/cli@latest
16
+ *
17
+ * - Outside a project, about the global copy that answered, with the command
18
+ * of the package manager that installed it (npm, pnpm, Yarn, Bun, Volta),
19
+ * read from where it lives, as the Vercel CLI does.
20
+ * - Inside a project, about the project's release, with the project's install
21
+ * and `upgrade` commands.
22
+ * - In any folder, about an `astryx` command first on the search path that
23
+ * predates the hand-off and so answers every folder itself.
24
+ *
25
+ * A newer release needs a check that has reached the registry
26
+ * (global-state.mjs); the last kind needs none. When the newest release needs
27
+ * a newer Node than the computer runs, the second line says so. Prerelease and
28
+ * canary versions are never reported as behind. The times of the notices are
29
+ * kept in the per-user state file; a time that cannot be recorded keeps the
30
+ * notice from printing, so it never prints on every run.
31
+ *
32
+ * @input the per-user state, the release choice, the project's files, the
33
+ * search path, the clock, and the environment
34
+ * @output notice lines, and the facts doctor reports
35
+ * @position foundation/release — printed by handoff.mjs's startRun after the
36
+ * command, read by doctor.
37
+ */
38
+
39
+ import * as fs from 'node:fs';
40
+ import * as os from 'node:os';
41
+ import * as path from 'node:path';
42
+ import {userConfigDir} from '../env/user-dirs.mjs';
43
+ import {astryxCommandsOnSearchPath} from '../env/search-path.mjs';
44
+ import {
45
+ CLI_PACKAGE,
46
+ detectPackageManager,
47
+ getCliInvocation,
48
+ } from '../env/package-manager.mjs';
49
+ import {satisfiesRange, semverCompare} from '../env/semver.mjs';
50
+ import {isInReleaseCache, isReleaseVersion} from './cache.mjs';
51
+ import {
52
+ knownLatest,
53
+ readGlobalState,
54
+ stateFile,
55
+ tryStateLock,
56
+ writeGlobalState,
57
+ } from './global-state.mjs';
58
+ import {globalFolderOf, isGlobalCopy, projectRootOf} from './choose.mjs';
59
+ import {readReleaseFacts} from './project.mjs';
60
+ import {samePath} from '../fs/path-compare.mjs';
61
+ import {updateNoticesOff} from './update-notice-env.mjs';
62
+
63
+ /** A notice prints at most once in this long. */
64
+ export const NOTICE_INTERVAL_MS = 24 * 60 * 60_000;
65
+
66
+ /** Notice times older than this are dropped from the state file. */
67
+ const FORGET_AFTER_MS = 30 * 24 * 60 * 60_000;
68
+
69
+ /**
70
+ * The CLI package at its newest release, for every upgrade command. Spelled
71
+ * out, not built from package-manager.mjs's constant: that module reaches
72
+ * this one through the run context, so its bindings are not ready when this
73
+ * module loads.
74
+ */
75
+ const LATEST = '@astryxdesign/cli@latest';
76
+
77
+ /**
78
+ * Whether notice `key` may print now; when it may, its time is recorded
79
+ * first, and a time that cannot be recorded keeps it from printing.
80
+ * @param {string} key
81
+ * @param {{stateDir?: string, now?: number}} [options]
82
+ * @returns {boolean}
83
+ */
84
+ export function takeNotice(
85
+ key,
86
+ {stateDir = userConfigDir(), now = Date.now()} = {},
87
+ ) {
88
+ try {
89
+ fs.mkdirSync(stateDir, {recursive: true});
90
+ } catch {
91
+ return false;
92
+ }
93
+ // Read, decide, and record under one lock: of the runs that finish at the
94
+ // same moment, exactly one takes the notice.
95
+ const outcome = tryStateLock(stateDir, () => {
96
+ const state = readGlobalState(stateDir);
97
+ const shown = state.notifiedAt ?? {};
98
+ const last = shown[key];
99
+ if (typeof last === 'number' && now - last < NOTICE_INTERVAL_MS) {
100
+ const skips = withSkip(state.noticeSkips, key, 'already-shown', now);
101
+ if (skips) writeGlobalState({...state, noticeSkips: skips}, stateDir);
102
+ return /** @type {const} */ ('already-shown');
103
+ }
104
+ /** @type {Record<string, number>} */
105
+ const next = {};
106
+ for (const [k, t] of Object.entries(shown)) {
107
+ if (typeof t === 'number' && now - t < FORGET_AFTER_MS) next[k] = t;
108
+ }
109
+ next[key] = now;
110
+ return writeGlobalState({...state, notifiedAt: next}, stateDir)
111
+ ? /** @type {const} */ ('taken')
112
+ : /** @type {const} */ ('state-unwritable');
113
+ });
114
+ // A skip is recorded only inside the lock that made the decision. A lock
115
+ // held past the wait, or a state file that cannot be written, records
116
+ // nothing: doctor names a lock's holder from the lock itself.
117
+ return outcome.ran && outcome.value === 'taken';
118
+ }
119
+
120
+ /** A skip with the same reason is recorded again only after this long. */
121
+ const SKIP_RECORD_INTERVAL_MS = 60 * 60_000;
122
+
123
+ /**
124
+ * `skips` with `key`'s skip set to `reason` at `now`, dropping skips older
125
+ * than {@link FORGET_AFTER_MS}; null when the same reason was recorded within
126
+ * {@link SKIP_RECORD_INTERVAL_MS}, so a notice skipped on every run does not
127
+ * rewrite the state file on every run.
128
+ * @param {Record<string, import('./global-state.mjs').NoticeSkip> | undefined} skips
129
+ * @param {string} key
130
+ * @param {import('./global-state.mjs').NoticeSkip['reason']} reason
131
+ * @param {number} now
132
+ * @returns {Record<string, import('./global-state.mjs').NoticeSkip> | null}
133
+ */
134
+ function withSkip(skips, key, reason, now) {
135
+ const last = skips?.[key];
136
+ if (last?.reason === reason && now - last.at < SKIP_RECORD_INTERVAL_MS) {
137
+ return null;
138
+ }
139
+ /** @type {Record<string, import('./global-state.mjs').NoticeSkip>} */
140
+ const next = {};
141
+ for (const [k, skip] of Object.entries(skips ?? {})) {
142
+ if (
143
+ skip &&
144
+ typeof skip.at === 'number' &&
145
+ now - skip.at < FORGET_AFTER_MS
146
+ ) {
147
+ next[k] = skip;
148
+ }
149
+ }
150
+ next[key] = {reason, at: now};
151
+ return next;
152
+ }
153
+
154
+ /**
155
+ * Record why notice `key` did not print (state `noticeSkips`), for diagnosis
156
+ * only. It adds to a state file that already exists and never creates one, so
157
+ * a run that keeps no state (a project's own CLI before any check) writes
158
+ * nothing. It is written only while this run holds the state file's lock:
159
+ * when the lock stays taken or the file cannot be written, nothing is
160
+ * recorded, and a run never fails because of it.
161
+ * @param {string} key
162
+ * @param {import('./global-state.mjs').NoticeSkip['reason']} reason
163
+ * @param {{stateDir?: string, now?: number}} [options]
164
+ */
165
+ export function recordNoticeSkip(
166
+ key,
167
+ reason,
168
+ {stateDir = userConfigDir(), now = Date.now()} = {},
169
+ ) {
170
+ try {
171
+ if (!fs.existsSync(stateFile(stateDir))) return;
172
+ tryStateLock(stateDir, () => {
173
+ const state = readGlobalState(stateDir);
174
+ const skips = withSkip(state.noticeSkips, key, reason, now);
175
+ if (skips) writeGlobalState({...state, noticeSkips: skips}, stateDir);
176
+ });
177
+ } catch {
178
+ // Diagnosis only.
179
+ }
180
+ }
181
+
182
+ /**
183
+ * The newest notice skip the state file records, with the notice it was for.
184
+ * @param {{stateDir?: string}} [options]
185
+ * @returns {({key: string} & import('./global-state.mjs').NoticeSkip) | null}
186
+ */
187
+ export function lastNoticeSkip({stateDir = userConfigDir()} = {}) {
188
+ /** @type {({key: string} & import('./global-state.mjs').NoticeSkip) | null} */
189
+ let newest = null;
190
+ for (const [key, skip] of Object.entries(
191
+ readGlobalState(stateDir).noticeSkips ?? {},
192
+ )) {
193
+ if (
194
+ skip &&
195
+ typeof skip.at === 'number' &&
196
+ (!newest || skip.at > newest.at)
197
+ ) {
198
+ newest = {key, ...skip};
199
+ }
200
+ }
201
+ return newest;
202
+ }
203
+
204
+ /**
205
+ * Is `version` a plain release older than `newest`?
206
+ * @param {string | null | undefined} version
207
+ * @param {string | null | undefined} newest
208
+ * @returns {boolean}
209
+ */
210
+ function isBehind(version, newest) {
211
+ return (
212
+ isReleaseVersion(version) &&
213
+ isReleaseVersion(newest) &&
214
+ !version.includes('-') &&
215
+ !newest.includes('-') &&
216
+ semverCompare(newest, version) > 0
217
+ );
218
+ }
219
+
220
+ /**
221
+ * The command that upgrades a CLI installed for the computer, by the package
222
+ * manager whose global folder holds it (the Vercel CLI reads it the same
223
+ * way); npm when none is recognized. A CLI that npm installed under a prefix
224
+ * of its own (`npm i -g --prefix ~/.local`, `NPM_CONFIG_PREFIX`, or `prefix=`
225
+ * in an `.npmrc`) is upgraded into that same prefix, named with `--prefix`,
226
+ * so the upgrade lands where the running copy is whatever npm's settings are
227
+ * when it runs. pnpm, Yarn, and Bun read their global folder from their own
228
+ * settings, so their plain command reinstalls in place.
229
+ * @param {string} cliRoot the CLI package's folder
230
+ * @param {{execPath?: string, home?: string, platform?: NodeJS.Platform, env?: NodeJS.ProcessEnv}} [where]
231
+ * @returns {string}
232
+ */
233
+ export function globalUpgradeCommand(cliRoot, where = {}) {
234
+ let at = cliRoot;
235
+ try {
236
+ at = fs.realpathSync(cliRoot);
237
+ } catch {
238
+ // Read the path as given.
239
+ }
240
+ if (/[\\/]\.volta[\\/]/iu.test(at)) return `volta install ${LATEST}`;
241
+ const {
242
+ home = os.homedir(),
243
+ platform = process.platform,
244
+ env = process.env,
245
+ } = where;
246
+ const global = globalFolderOf(at, {home, platform, env});
247
+ // Each reads the same settings that named its global folder, so its plain
248
+ // command reinstalls into that folder.
249
+ if (global?.manager === 'bun') return `bun add -g ${LATEST}`;
250
+ if (global?.manager === 'yarn') return `yarn global add ${LATEST}`;
251
+ if (
252
+ global?.manager === 'pnpm' ||
253
+ /[\\/]pnpm[\\/]|[\\/]global[\\/]v?\d+[\\/]/iu.test(at)
254
+ ) {
255
+ return `pnpm add -g ${LATEST}`;
256
+ }
257
+ const prefix = npmOwnPrefix(at, where);
258
+ return prefix
259
+ ? `npm i -g --prefix ${prefix} ${LATEST}`
260
+ : `npm i -g ${LATEST}`;
261
+ }
262
+
263
+ /**
264
+ * The npm prefix that holds the CLI at `at`, as a person types it (`~` for
265
+ * the home folder outside Windows), when it is not one npm uses by default:
266
+ * Node's own prefix, `/usr/local` or `/usr`, or `%APPDATA%\\npm` on Windows.
267
+ * Null for any other layout.
268
+ * @param {string} at the CLI package's real path
269
+ * @param {{execPath?: string, home?: string, platform?: NodeJS.Platform, env?: NodeJS.ProcessEnv}} where
270
+ * @returns {string | null}
271
+ */
272
+ function npmOwnPrefix(at, where) {
273
+ const {
274
+ execPath = process.execPath,
275
+ home = os.homedir(),
276
+ platform = process.platform,
277
+ env = process.env,
278
+ } = where;
279
+ const win = platform === 'win32';
280
+ const paths = win ? path.win32 : path.posix;
281
+ const parts = at.split(/[\\/]/);
282
+ const n = parts.length;
283
+ if (parts[n - 1] !== 'cli' || parts[n - 2] !== '@astryxdesign') return null;
284
+ if (parts[n - 3] !== 'node_modules') return null;
285
+ // npm's global folder: `<prefix>/lib/node_modules` on macOS and Linux,
286
+ // `<prefix>\\node_modules` on Windows.
287
+ const depth = win ? n - 3 : n - 4;
288
+ if (!win && parts[n - 4] !== 'lib') return null;
289
+ const prefix = parts.slice(0, depth).join(win ? '\\' : '/') || paths.sep;
290
+ const defaults = win
291
+ ? [
292
+ paths.dirname(execPath),
293
+ ...(env.APPDATA ? [paths.join(env.APPDATA, 'npm')] : []),
294
+ ]
295
+ : [paths.dirname(paths.dirname(execPath)), '/usr/local', '/usr'];
296
+ if (defaults.some(d => samePath(d, prefix, platform))) return null;
297
+ if (!win) {
298
+ const rel = paths.relative(home, prefix);
299
+ if (rel === '') return '~';
300
+ if (!rel.startsWith('..') && !paths.isAbsolute(rel)) return `~/${rel}`;
301
+ }
302
+ return /\s/.test(prefix) ? JSON.stringify(prefix) : prefix;
303
+ }
304
+
305
+ /**
306
+ * The second line of a notice: the command, after the Node it needs when the
307
+ * computer runs an older one.
308
+ * @param {string} command
309
+ * @param {string | null} node the Node range the newest release declares
310
+ * @param {string} [nodeVersion]
311
+ * @returns {string}
312
+ */
313
+ export function upgradeLine(
314
+ command,
315
+ node,
316
+ nodeVersion = process.versions.node,
317
+ ) {
318
+ return node && !satisfiesRange(nodeVersion, node)
319
+ ? `To upgrade, install Node ${node} (this computer runs Node ${nodeVersion}), then run: ${command}`
320
+ : `To upgrade, run: ${command}`;
321
+ }
322
+
323
+ /**
324
+ * The first line of a newer-release notice.
325
+ * @param {string} current
326
+ * @param {string} newest
327
+ * @returns {string}
328
+ */
329
+ export function availableLine(current, newest) {
330
+ return `A new release of Astryx is available: ${current} → ${newest}`;
331
+ }
332
+
333
+ /**
334
+ * @typedef {object} GlobalBehind
335
+ * @property {string} current the global copy's version
336
+ * @property {string} newest the newest release known
337
+ * @property {string | null} node the Node range it declares
338
+ * @property {string} command the command that upgrades the global copy
339
+ */
340
+
341
+ /**
342
+ * Whether the global copy is older than the newest release known.
343
+ * @param {{self: {version: string, root: string}, stateDir?: string}} options
344
+ * @returns {GlobalBehind | null}
345
+ */
346
+ export function globalBehind({self, stateDir = userConfigDir()}) {
347
+ const latest = knownLatest(stateDir);
348
+ if (!latest || !isBehind(self.version, latest.version)) return null;
349
+ return {
350
+ current: self.version,
351
+ newest: latest.version,
352
+ node: latest.node,
353
+ command: globalUpgradeCommand(self.root),
354
+ };
355
+ }
356
+
357
+ /**
358
+ * @typedef {object} ProjectLag
359
+ * @property {string} root the project's folder
360
+ * @property {string} current the release that answers it
361
+ * @property {string} newest the newest release known
362
+ * @property {string | null} node the Node range the newest release declares
363
+ * @property {string} install the command that installs the newest release
364
+ * @property {string} upgrade the command that migrates the project
365
+ */
366
+
367
+ /**
368
+ * The install verb of each package manager.
369
+ * @type {Record<string, string>}
370
+ */
371
+ const ADD = {
372
+ npm: 'npm install',
373
+ pnpm: 'pnpm add',
374
+ yarn: 'yarn add',
375
+ bun: 'bun add',
376
+ };
377
+
378
+ /**
379
+ * The commands that bring a project's own CLI and Core to one release, with
380
+ * the project's package manager: when the CLI is newer, Core up to it and then
381
+ * the codemods from Core's release; otherwise the CLI to Core's release.
382
+ * @param {{cwd: string, cli: string, core: string}} options
383
+ * @returns {string}
384
+ */
385
+ export function alignCliAndCore({cwd, cli, core}) {
386
+ const facts = readReleaseFacts(cwd);
387
+ const root = projectRootOf(facts) ?? cwd;
388
+ const add = ADD[detectPackageManager(root)] ?? ADD.npm;
389
+ if (semverCompare(cli, core) > 0) {
390
+ return `${add} @astryxdesign/core@${cli}, then ${getCliInvocation(root, {installed: true})} upgrade --from ${core} --apply`;
391
+ }
392
+ return `${add} ${CLI_PACKAGE}@${core}`;
393
+ }
394
+
395
+ /**
396
+ * The hint for a project with no CLI of its own, answered by `answered` while
397
+ * its Core is `core`: the command that adds the CLI at Core's version, with
398
+ * the project's package manager.
399
+ * @param {{cwd: string, answered: string, core: string}} options
400
+ * @returns {{root: string, line: string}}
401
+ */
402
+ export function noCliHint({cwd, answered, core}) {
403
+ const root = projectRootOf(readReleaseFacts(cwd)) ?? cwd;
404
+ return {root, line: noCliHintLine(answered, core, addCliCommand(root, core))};
405
+ }
406
+
407
+ /**
408
+ * The hint's text.
409
+ * @param {string} answered
410
+ * @param {string} core
411
+ * @param {string} command the command that adds the CLI at Core's version
412
+ * @returns {string}
413
+ */
414
+ export function noCliHintLine(answered, core, command) {
415
+ return `astryx: this project has no Astryx CLI installed; answered by CLI ${answered} while its Core is ${core}. To match, run: ${command}`;
416
+ }
417
+
418
+ /**
419
+ * The command that installs the CLI at `version` in the project at `root`,
420
+ * with its package manager.
421
+ * @param {string} root
422
+ * @param {string} version
423
+ * @returns {string}
424
+ */
425
+ export function addCliCommand(root, version) {
426
+ return `${ADD[detectPackageManager(root)] ?? ADD.npm} ${CLI_PACKAGE}@${version}`;
427
+ }
428
+
429
+ /**
430
+ * Whether the project's release is older than the newest release known, and
431
+ * the commands that upgrade it.
432
+ * @param {{cwd: string, current: string, stateDir?: string}} options
433
+ * @returns {ProjectLag | null}
434
+ */
435
+ export function projectLag({cwd, current, stateDir = userConfigDir()}) {
436
+ const latest = knownLatest(stateDir);
437
+ if (!latest || !isBehind(current, latest.version)) return null;
438
+ const newest = latest.version;
439
+ const facts = readReleaseFacts(cwd);
440
+ if (!facts.inProject) return null;
441
+ const root = projectRootOf(facts) ?? cwd;
442
+ const pm = detectPackageManager(root);
443
+ // The CLI is always installed beside Core: a project that installed only
444
+ // Core gets it too, as recommended, so its version is pinned with Core's.
445
+ const install = `${ADD[pm] ?? ADD.npm} @astryxdesign/core@${newest} ${CLI_PACKAGE}@${newest}`;
446
+ // The command runs after the install above, so the picker's guess is the
447
+ // project's own CLI (spec AST-081 FR5).
448
+ const stem = getCliInvocation(root, {installed: true});
449
+ return {
450
+ root,
451
+ current,
452
+ newest,
453
+ node: latest.node,
454
+ install,
455
+ upgrade: `${stem} upgrade --from ${current} --apply`,
456
+ };
457
+ }
458
+
459
+ /**
460
+ * The first `astryx` on the search path, when it is an Astryx CLI release
461
+ * that predates the hand-off and is not the running copy.
462
+ * @param {{selfRoot?: string, searchPath?: string, platform?: NodeJS.Platform}} [options]
463
+ * @returns {{file: string, version: string | null, command: string} | null}
464
+ */
465
+ export function oldGlobalCommand({selfRoot, ...options} = {}) {
466
+ const first = astryxCommandsOnSearchPath(options)[0];
467
+ if (!first?.astryx || !first.cliRoot || first.handsOff !== false) return null;
468
+ if (selfRoot && samePath(real(selfRoot), real(first.cliRoot))) return null;
469
+ return {
470
+ file: first.file,
471
+ version: first.version,
472
+ command: globalUpgradeCommand(first.cliRoot),
473
+ };
474
+ }
475
+
476
+ /**
477
+ * @param {string} p
478
+ * @returns {string}
479
+ */
480
+ function real(p) {
481
+ try {
482
+ return fs.realpathSync(p);
483
+ } catch {
484
+ return path.resolve(p);
485
+ }
486
+ }
487
+
488
+ /**
489
+ * The two lines about a global copy that predates the hand-off.
490
+ * @param {{version: string | null, command: string}} old
491
+ * @returns {string[]}
492
+ */
493
+ export function oldGlobalLines(old) {
494
+ return [
495
+ `The astryx command on this computer runs CLI ${old.version ?? 'of an unknown version'}, which answers every folder itself.`,
496
+ `To upgrade, run: ${old.command}`,
497
+ ];
498
+ }
499
+
500
+ /**
501
+ * The notice lines to print after a command, each notice recorded as shown.
502
+ * Empty in machine-readable runs and when the environment turns notices off.
503
+ * Never throws.
504
+ * @param {{
505
+ * cwd: string,
506
+ * self: {version: string, root: string},
507
+ * choice: {inProject: boolean, version: string, copy?: string, coreVersion?: string | null},
508
+ * json?: boolean,
509
+ * stateDir?: string,
510
+ * cacheDir?: string,
511
+ * now?: number,
512
+ * env?: Record<string, string | undefined>,
513
+ * searchPath?: string,
514
+ * }} options
515
+ * @returns {string[]}
516
+ */
517
+ export function noticeLines({
518
+ cwd,
519
+ self,
520
+ choice,
521
+ json = false,
522
+ stateDir = userConfigDir(),
523
+ cacheDir,
524
+ now = Date.now(),
525
+ env = process.env,
526
+ searchPath,
527
+ }) {
528
+ // A run inside the release cache is the CLI checking a release it
529
+ // installed, never a person's run.
530
+ if (json || updateNoticesOff(env) || isInReleaseCache(cwd, cacheDir)) {
531
+ return [];
532
+ }
533
+ /** @type {string[]} */
534
+ const lines = [];
535
+ const take = (/** @type {string} */ key) => takeNotice(key, {stateDir, now});
536
+ try {
537
+ // A project with no CLI of its own, answered by a release other than its
538
+ // Core's: once a day per project, how to match them (FR2).
539
+ if (
540
+ choice.inProject &&
541
+ choice.copy === 'starting' &&
542
+ choice.coreVersion &&
543
+ choice.coreVersion !== choice.version
544
+ ) {
545
+ const hint = noCliHint({
546
+ cwd,
547
+ answered: choice.version,
548
+ core: choice.coreVersion,
549
+ });
550
+ if (take(`no-cli:${hint.root}`)) lines.push(hint.line);
551
+ }
552
+ } catch {
553
+ // A hint never fails a run.
554
+ }
555
+ try {
556
+ if (choice.inProject) {
557
+ // The project's version is its own CLI's, or, with none, its Core's.
558
+ const current =
559
+ choice.copy === 'starting' && choice.coreVersion
560
+ ? choice.coreVersion
561
+ : choice.version;
562
+ const lag = projectLag({cwd, current, stateDir});
563
+ if (!lag) recordNoticeSkip('project', 'no-newer', {stateDir, now});
564
+ if (lag && take(`project:${lag.root}`)) {
565
+ lines.push(
566
+ availableLine(lag.current, lag.newest),
567
+ upgradeLine(`${lag.install}, then ${lag.upgrade}`, lag.node),
568
+ );
569
+ }
570
+ } else if (isGlobalCopy(self.root, cacheDir)) {
571
+ const behind = globalBehind({self, stateDir});
572
+ if (!behind) recordNoticeSkip('global', 'no-newer', {stateDir, now});
573
+ if (behind && take('global')) {
574
+ lines.push(
575
+ availableLine(behind.current, behind.newest),
576
+ upgradeLine(behind.command, behind.node),
577
+ );
578
+ }
579
+ }
580
+ } catch {
581
+ // A notice never fails a run.
582
+ }
583
+ try {
584
+ const shown = readGlobalState(stateDir).notifiedAt?.['old-global'];
585
+ if (typeof shown !== 'number' || now - shown >= NOTICE_INTERVAL_MS) {
586
+ const old = oldGlobalCommand({
587
+ selfRoot: self.root,
588
+ ...(searchPath === undefined ? {} : {searchPath}),
589
+ });
590
+ if (old && take('old-global')) lines.push(...oldGlobalLines(old));
591
+ }
592
+ } catch {
593
+ // Nor does this one.
594
+ }
595
+ return lines;
596
+ }