@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,565 @@
1
+ // Copyright (c) Meta Platforms, Inc. and affiliates.
2
+
3
+ /**
4
+ * @file Update notices (spec AST-081 FR10): when each one applies, its exact
5
+ * two lines, the upgrade command for each package manager, and that it prints
6
+ * at most once a day and never when the environment turns notices off.
7
+ */
8
+
9
+ import {afterEach, describe, expect, it} from 'vitest';
10
+ import * as fs from 'node:fs';
11
+ import * as os from 'node:os';
12
+ import * as path from 'node:path';
13
+ import {spawn} from 'node:child_process';
14
+ import {
15
+ recordDeclaredCliCommands,
16
+ runWithCliDoor,
17
+ } from '../env/cli-command.mjs';
18
+ import {
19
+ NOTICE_INTERVAL_MS,
20
+ availableLine,
21
+ globalBehind,
22
+ globalUpgradeCommand,
23
+ noticeLines,
24
+ oldGlobalCommand,
25
+ oldGlobalLines,
26
+ projectLag,
27
+ alignCliAndCore,
28
+ lastNoticeSkip,
29
+ recordNoticeSkip,
30
+ takeNotice,
31
+ upgradeLine,
32
+ } from './notices.mjs';
33
+ import {
34
+ readGlobalState,
35
+ stateLockHolder,
36
+ tryStateLock,
37
+ updateGlobalState,
38
+ } from './global-state.mjs';
39
+ import {
40
+ cleanupFixtures,
41
+ installCore,
42
+ installStandInCli,
43
+ project,
44
+ tempDir,
45
+ writeJson,
46
+ } from '../../test-utils/release-fixtures.mjs';
47
+
48
+ afterEach(cleanupFixtures);
49
+
50
+ const DAY = 24 * 60 * 60_000;
51
+
52
+ /**
53
+ * An Astryx CLI package on a search path folder, with or without the hand-off.
54
+ * @param {string} version
55
+ * @param {{handsOff?: boolean, prefix?: string[]}} [options]
56
+ */
57
+ function cliOnPath(version, {handsOff = false, prefix = ['lib']} = {}) {
58
+ const root = tempDir('prefix');
59
+ const pkg = path.join(
60
+ root,
61
+ ...prefix,
62
+ 'node_modules',
63
+ '@astryxdesign',
64
+ 'cli',
65
+ );
66
+ fs.mkdirSync(pkg, {recursive: true});
67
+ writeJson(path.join(pkg, 'package.json'), {
68
+ name: '@astryxdesign/cli',
69
+ version,
70
+ });
71
+ fs.writeFileSync(path.join(pkg, 'bin.mjs'), '#!/usr/bin/env node\n');
72
+ fs.chmodSync(path.join(pkg, 'bin.mjs'), 0o755);
73
+ if (handsOff) {
74
+ fs.mkdirSync(path.join(pkg, 'foundation', 'release'), {recursive: true});
75
+ fs.writeFileSync(path.join(pkg, 'foundation', 'release', 'choose.mjs'), '');
76
+ }
77
+ fs.mkdirSync(path.join(root, 'bin'));
78
+ fs.symlinkSync(path.join(pkg, 'bin.mjs'), path.join(root, 'bin', 'astryx'));
79
+ return {bin: path.join(root, 'bin'), pkg, root};
80
+ }
81
+
82
+ /** A state folder whose last check found `version`, needing Node `node`. */
83
+ function checked(version = '0.7.3', node = null) {
84
+ const stateDir = tempDir('state');
85
+ updateGlobalState(
86
+ {latest: version, latestNode: node, checkOkAt: 1},
87
+ stateDir,
88
+ );
89
+ return stateDir;
90
+ }
91
+
92
+ describe('takeNotice', () => {
93
+ it('lets a notice print once in any 24 hours, per key', () => {
94
+ const stateDir = tempDir('state');
95
+ expect(takeNotice('a', {stateDir, now: 1000})).toBe(true);
96
+ expect(takeNotice('a', {stateDir, now: 1000 + DAY - 1})).toBe(false);
97
+ expect(takeNotice('b', {stateDir, now: 2000})).toBe(true);
98
+ expect(takeNotice('a', {stateDir, now: 1000 + NOTICE_INTERVAL_MS})).toBe(
99
+ true,
100
+ );
101
+ expect(readGlobalState(stateDir).notifiedAt).toEqual({
102
+ a: 1000 + NOTICE_INTERVAL_MS,
103
+ b: 2000,
104
+ });
105
+ });
106
+
107
+ it('lets exactly one of many runs finishing at once take a notice (b18)', async () => {
108
+ const stateDir = tempDir('state');
109
+ const notices = new URL('./notices.mjs', import.meta.url).href;
110
+ const source = `
111
+ const {takeNotice} = await import(${JSON.stringify(notices)});
112
+ const at = Number(process.argv[1]);
113
+ while (Date.now() < at) {}
114
+ process.stdout.write(takeNotice('global', {stateDir: ${JSON.stringify(stateDir)}}) ? 'took' : 'skip');
115
+ `;
116
+ const at = String(Date.now() + 1500);
117
+ const runs = await Promise.all(
118
+ Array.from(
119
+ {length: 10},
120
+ () =>
121
+ new Promise(resolve => {
122
+ const child = spawn(
123
+ process.execPath,
124
+ ['--input-type=module', '-e', source, at],
125
+ {stdio: ['ignore', 'pipe', 'inherit']},
126
+ );
127
+ let out = '';
128
+ child.stdout.on('data', d => (out += d));
129
+ child.on('close', () => resolve(out));
130
+ }),
131
+ ),
132
+ );
133
+ expect(runs.filter(r => r === 'took')).toHaveLength(1);
134
+ expect(runs.filter(r => r === 'skip')).toHaveLength(9);
135
+ expect(fs.existsSync(path.join(stateDir, '.lock-state'))).toBe(false);
136
+ });
137
+
138
+ it('prints nothing when the time cannot be recorded', () => {
139
+ const file = path.join(tempDir('state'), 'file');
140
+ fs.writeFileSync(file, '');
141
+ expect(takeNotice('a', {stateDir: file, now: 1})).toBe(false);
142
+ });
143
+ });
144
+
145
+ describe('why a notice did not print (diagnostic state)', () => {
146
+ it('records an already-shown skip, at most once an hour for the same reason', () => {
147
+ const stateDir = tempDir('state');
148
+ expect(takeNotice('global', {stateDir, now: 1000})).toBe(true);
149
+ expect(takeNotice('global', {stateDir, now: 2000})).toBe(false);
150
+ expect(readGlobalState(stateDir).noticeSkips).toEqual({
151
+ global: {reason: 'already-shown', at: 2000},
152
+ });
153
+ // Within the hour: not rewritten.
154
+ expect(takeNotice('global', {stateDir, now: 3000})).toBe(false);
155
+ expect(readGlobalState(stateDir).noticeSkips?.global.at).toBe(2000);
156
+ // After it: recorded again.
157
+ expect(takeNotice('global', {stateDir, now: 2000 + 60 * 60_000})).toBe(
158
+ false,
159
+ );
160
+ expect(readGlobalState(stateDir).noticeSkips?.global.at).toBe(
161
+ 2000 + 60 * 60_000,
162
+ );
163
+ expect(lastNoticeSkip({stateDir})).toEqual({
164
+ key: 'global',
165
+ reason: 'already-shown',
166
+ at: 2000 + 60 * 60_000,
167
+ });
168
+ });
169
+
170
+ it('says a held lock timed out, and who holds it', () => {
171
+ const stateDir = tempDir('state');
172
+ const lockDir = path.join(stateDir, '.lock-state');
173
+ fs.mkdirSync(lockDir, {recursive: true});
174
+ fs.writeFileSync(
175
+ path.join(lockDir, 'owner.json'),
176
+ JSON.stringify({pid: process.pid, host: os.hostname(), at: Date.now()}),
177
+ );
178
+ expect(tryStateLock(stateDir, () => 'ran')).toEqual({
179
+ ran: false,
180
+ reason: 'lock-timeout',
181
+ });
182
+ expect(stateLockHolder(stateDir)).toMatchObject({pid: process.pid});
183
+ fs.rmSync(lockDir, {recursive: true});
184
+ expect(stateLockHolder(stateDir)).toBeNull();
185
+ // Nothing is recorded where no state file exists yet.
186
+ recordNoticeSkip('global', 'no-newer', {stateDir, now: 4});
187
+ expect(fs.existsSync(path.join(stateDir, 'state.json'))).toBe(false);
188
+ }, 10_000);
189
+
190
+ it('records nothing for a notice the lock kept from printing, and writes no other file', () => {
191
+ const stateDir = tempDir('state');
192
+ updateGlobalState({latest: '0.7.1'}, stateDir);
193
+ const lockDir = path.join(stateDir, '.lock-state');
194
+ fs.mkdirSync(lockDir, {recursive: true});
195
+ expect(takeNotice('global', {stateDir, now: 6})).toBe(false);
196
+ fs.rmSync(lockDir, {recursive: true});
197
+ expect(lastNoticeSkip({stateDir})).toBeNull();
198
+ expect(fs.readdirSync(stateDir).sort()).toEqual(['state.json']);
199
+ }, 10_000);
200
+ });
201
+
202
+ describe('the lines', () => {
203
+ it('match the gh form, naming the Node a release needs', () => {
204
+ expect(availableLine('0.7.2', '0.7.3')).toBe(
205
+ 'A new release of Astryx is available: 0.7.2 → 0.7.3',
206
+ );
207
+ expect(
208
+ upgradeLine('npm i -g @astryxdesign/cli@latest', '>=20', '22.0.0'),
209
+ ).toBe('To upgrade, run: npm i -g @astryxdesign/cli@latest');
210
+ expect(
211
+ upgradeLine('npm i -g @astryxdesign/cli@latest', '>=24', '22.13.0'),
212
+ ).toBe(
213
+ 'To upgrade, install Node >=24 (this computer runs Node 22.13.0), then run: npm i -g @astryxdesign/cli@latest',
214
+ );
215
+ });
216
+
217
+ it('upgrade a CLI that npm installed under its own prefix into that prefix', () => {
218
+ const linux = {
219
+ execPath: '/usr/bin/node',
220
+ home: '/home/a',
221
+ platform: /** @type {const} */ ('linux'),
222
+ env: {},
223
+ };
224
+ expect(
225
+ globalUpgradeCommand(
226
+ '/home/a/.local/lib/node_modules/@astryxdesign/cli',
227
+ linux,
228
+ ),
229
+ ).toBe('npm i -g --prefix ~/.local @astryxdesign/cli@latest');
230
+ expect(
231
+ globalUpgradeCommand(
232
+ '/home/a/.npm-global/lib/node_modules/@astryxdesign/cli',
233
+ linux,
234
+ ),
235
+ ).toBe('npm i -g --prefix ~/.npm-global @astryxdesign/cli@latest');
236
+ expect(
237
+ globalUpgradeCommand(
238
+ '/opt/tools/lib/node_modules/@astryxdesign/cli',
239
+ linux,
240
+ ),
241
+ ).toBe('npm i -g --prefix /opt/tools @astryxdesign/cli@latest');
242
+ // npm's default prefixes: Node's own, /usr/local, /usr.
243
+ for (const cli of [
244
+ '/usr/local/lib/node_modules/@astryxdesign/cli',
245
+ '/usr/lib/node_modules/@astryxdesign/cli',
246
+ ]) {
247
+ expect(globalUpgradeCommand(cli, linux)).toBe(
248
+ 'npm i -g @astryxdesign/cli@latest',
249
+ );
250
+ }
251
+ expect(
252
+ globalUpgradeCommand(
253
+ '/home/a/.nvm/versions/node/v22.1.0/lib/node_modules/@astryxdesign/cli',
254
+ {
255
+ ...linux,
256
+ execPath: '/home/a/.nvm/versions/node/v22.1.0/bin/node',
257
+ },
258
+ ),
259
+ ).toBe('npm i -g @astryxdesign/cli@latest');
260
+ const win = {
261
+ execPath: 'C:\\Program Files\\nodejs\\node.exe',
262
+ home: 'C:\\Users\\a',
263
+ platform: /** @type {const} */ ('win32'),
264
+ env: {APPDATA: 'C:\\Users\\a\\AppData\\Roaming'},
265
+ };
266
+ expect(
267
+ globalUpgradeCommand(
268
+ 'C:\\Users\\a\\AppData\\Roaming\\npm\\node_modules\\@astryxdesign\\cli',
269
+ win,
270
+ ),
271
+ ).toBe('npm i -g @astryxdesign/cli@latest');
272
+ expect(
273
+ globalUpgradeCommand(
274
+ 'D:\\tools\\npm\\node_modules\\@astryxdesign\\cli',
275
+ win,
276
+ ),
277
+ ).toBe('npm i -g --prefix D:\\tools\\npm @astryxdesign/cli@latest');
278
+ });
279
+
280
+ it('name the package manager that installed the global copy', () => {
281
+ const at = (/** @type {string} */ p) =>
282
+ globalUpgradeCommand(path.join(path.sep, ...p.split('/')), {
283
+ home: '/home/a',
284
+ platform: 'linux',
285
+ env: {PNPM_HOME: '/home/a/pn'},
286
+ });
287
+ expect(at('usr/local/lib/node_modules/@astryxdesign/cli')).toBe(
288
+ 'npm i -g @astryxdesign/cli@latest',
289
+ );
290
+ expect(at('home/a/pn/global/5/node_modules/@astryxdesign/cli')).toBe(
291
+ 'pnpm add -g @astryxdesign/cli@latest',
292
+ );
293
+ expect(at('home/a/pn/global/v11/3f2a/node_modules/@astryxdesign/cli')).toBe(
294
+ 'pnpm add -g @astryxdesign/cli@latest',
295
+ );
296
+ expect(
297
+ at('home/a/.config/yarn/global/node_modules/@astryxdesign/cli'),
298
+ ).toBe('yarn global add @astryxdesign/cli@latest');
299
+ expect(
300
+ at('home/a/.bun/install/global/node_modules/@astryxdesign/cli'),
301
+ ).toBe('bun add -g @astryxdesign/cli@latest');
302
+ expect(
303
+ at(
304
+ 'home/a/.volta/tools/image/packages/@astryxdesign/cli/lib/node_modules/@astryxdesign/cli',
305
+ ),
306
+ ).toBe('volta install @astryxdesign/cli@latest');
307
+ });
308
+ });
309
+
310
+ describe('globalBehind and projectLag', () => {
311
+ it('need a completed check, and never report a current, newer, or prerelease version', () => {
312
+ const self = {
313
+ version: '0.7.2',
314
+ root: '/usr/lib/node_modules/@astryxdesign/cli',
315
+ };
316
+ expect(globalBehind({self, stateDir: tempDir('state')})).toBeNull();
317
+ const stateDir = checked('0.7.3', '>=24');
318
+ expect(globalBehind({self, stateDir})).toEqual({
319
+ current: '0.7.2',
320
+ newest: '0.7.3',
321
+ node: '>=24',
322
+ command: 'npm i -g @astryxdesign/cli@latest',
323
+ });
324
+ for (const version of ['0.7.3', '0.8.0', '0.7.2-canary.1']) {
325
+ expect(globalBehind({self: {...self, version}, stateDir})).toBeNull();
326
+ }
327
+ });
328
+
329
+ it("name a project's install and upgrade commands for its package manager", () => {
330
+ const stateDir = checked('0.7.3');
331
+ const coreOnly = project(tempDir(), {'@astryxdesign/core': '0.7.1'});
332
+ writeJson(path.join(coreOnly, 'package-lock.json'), {lockfileVersion: 3});
333
+ installCore(coreOnly, '0.7.1');
334
+ expect(
335
+ projectLag({cwd: coreOnly, current: '0.7.1', stateDir}),
336
+ ).toMatchObject({
337
+ root: coreOnly,
338
+ install: 'npm install @astryxdesign/core@0.7.3 @astryxdesign/cli@0.7.3',
339
+ upgrade: 'npx astryx upgrade --from 0.7.1 --apply',
340
+ });
341
+ const pnpm = project(tempDir(), {
342
+ '@astryxdesign/core': '0.7.1',
343
+ '@astryxdesign/cli': '0.7.1',
344
+ });
345
+ fs.writeFileSync(
346
+ path.join(pnpm, 'pnpm-lock.yaml'),
347
+ "lockfileVersion: '9.0'\n",
348
+ );
349
+ installCore(pnpm, '0.7.1');
350
+ installStandInCli(pnpm, '0.7.1');
351
+ expect(projectLag({cwd: pnpm, current: '0.7.1', stateDir})).toMatchObject({
352
+ install: 'pnpm add @astryxdesign/core@0.7.3 @astryxdesign/cli@0.7.3',
353
+ upgrade: 'pnpm exec astryx upgrade --from 0.7.1 --apply',
354
+ });
355
+ });
356
+ });
357
+
358
+ describe('the commands notices print go through the command-name picker (spec AST-081 FR5)', () => {
359
+ /** A Core-only npm project behind the newest release. */
360
+ const coreOnlyProject = () => {
361
+ const dir = project(tempDir(), {'@astryxdesign/core': '0.7.1'});
362
+ writeJson(path.join(dir, 'package-lock.json'), {lockfileVersion: 3});
363
+ installCore(dir, '0.7.1');
364
+ return dir;
365
+ };
366
+
367
+ it("use an integration's declared cliCommand for the upgrade step", () => {
368
+ const stateDir = checked('0.7.3');
369
+ const dir = coreOnlyProject();
370
+ recordDeclaredCliCommands(dir, [
371
+ {name: '@acme/a', cliCommand: 'acme astryx'},
372
+ ]);
373
+ try {
374
+ expect(projectLag({cwd: dir, current: '0.7.1', stateDir})?.upgrade).toBe(
375
+ 'acme astryx upgrade --from 0.7.1 --apply',
376
+ );
377
+ expect(alignCliAndCore({cwd: dir, cli: '0.7.3', core: '0.7.1'})).toBe(
378
+ 'npm install @astryxdesign/core@0.7.3, then acme astryx upgrade --from 0.7.1 --apply',
379
+ );
380
+ } finally {
381
+ recordDeclaredCliCommands(dir, []);
382
+ }
383
+ });
384
+
385
+ it("use a host's door when nothing is declared", () => {
386
+ const stateDir = checked('0.7.3');
387
+ const dir = coreOnlyProject();
388
+ runWithCliDoor('host astryx', () => {
389
+ expect(projectLag({cwd: dir, current: '0.7.1', stateDir})?.upgrade).toBe(
390
+ 'host astryx upgrade --from 0.7.1 --apply',
391
+ );
392
+ expect(alignCliAndCore({cwd: dir, cli: '0.7.3', core: '0.7.1'})).toBe(
393
+ 'npm install @astryxdesign/core@0.7.3, then host astryx upgrade --from 0.7.1 --apply',
394
+ );
395
+ });
396
+ });
397
+
398
+ it("fall back to the project's own installed CLI", () => {
399
+ const dir = coreOnlyProject();
400
+ expect(alignCliAndCore({cwd: dir, cli: '0.7.3', core: '0.7.1'})).toBe(
401
+ 'npm install @astryxdesign/core@0.7.3, then npx astryx upgrade --from 0.7.1 --apply',
402
+ );
403
+ });
404
+ });
405
+
406
+ describe.skipIf(process.platform === 'win32')('oldGlobalCommand', () => {
407
+ it('names a first astryx that predates the hand-off with its own package manager, and not one that hands off', () => {
408
+ const old = cliOnPath('0.7.1', {prefix: ['global', '5']});
409
+ expect(oldGlobalCommand({searchPath: old.bin, platform: 'linux'})).toEqual({
410
+ file: path.join(old.bin, 'astryx'),
411
+ version: '0.7.1',
412
+ command: 'pnpm add -g @astryxdesign/cli@latest',
413
+ });
414
+ expect(
415
+ oldGlobalLines({
416
+ version: '0.7.1',
417
+ command: 'npm i -g @astryxdesign/cli@latest',
418
+ }),
419
+ ).toEqual([
420
+ 'The astryx command on this computer runs CLI 0.7.1, which answers every folder itself.',
421
+ 'To upgrade, run: npm i -g @astryxdesign/cli@latest',
422
+ ]);
423
+ const current = cliOnPath('0.8.0', {handsOff: true});
424
+ expect(
425
+ oldGlobalCommand({
426
+ searchPath: [current.bin, old.bin].join(path.delimiter),
427
+ platform: 'linux',
428
+ }),
429
+ ).toBeNull();
430
+ expect(
431
+ oldGlobalCommand({
432
+ selfRoot: old.pkg,
433
+ searchPath: old.bin,
434
+ platform: 'linux',
435
+ }),
436
+ ).toBeNull();
437
+ });
438
+ });
439
+
440
+ describe.skipIf(process.platform === 'win32')('noticeLines', () => {
441
+ /** A global copy, outside a project. */
442
+ const outside = () => {
443
+ const global = cliOnPath('0.7.2', {handsOff: true});
444
+ return {
445
+ global,
446
+ cwd: tempDir('empty'),
447
+ self: {version: '0.7.2', root: global.pkg},
448
+ choice: {inProject: false, version: '0.7.2'},
449
+ cacheDir: tempDir('cache'),
450
+ searchPath: global.bin,
451
+ env: {},
452
+ };
453
+ };
454
+
455
+ it('tells the global copy about a newer release, once a day', () => {
456
+ const {global, ...input} = {...outside(), stateDir: checked('0.7.3')};
457
+ // The fixture's global folder is a prefix of its own, so the upgrade
458
+ // names it.
459
+ const prefix = fs.realpathSync(global.root);
460
+ expect(noticeLines({...input, now: 10})).toEqual([
461
+ 'A new release of Astryx is available: 0.7.2 → 0.7.3',
462
+ `To upgrade, run: npm i -g --prefix ${prefix} @astryxdesign/cli@latest`,
463
+ ]);
464
+ expect(noticeLines({...input, now: 20})).toEqual([]);
465
+ expect(lastNoticeSkip({stateDir: input.stateDir})).toEqual({
466
+ key: 'global',
467
+ reason: 'already-shown',
468
+ at: 20,
469
+ });
470
+ expect(noticeLines({...input, now: 10 + DAY})).toHaveLength(2);
471
+ });
472
+
473
+ it('records that nothing newer was known when the global copy is current', () => {
474
+ const {global: _global, ...input} = {
475
+ ...outside(),
476
+ stateDir: checked('0.7.2'),
477
+ };
478
+ expect(noticeLines({...input, now: 30})).toEqual([]);
479
+ expect(lastNoticeSkip({stateDir: input.stateDir})).toEqual({
480
+ key: 'global',
481
+ reason: 'no-newer',
482
+ at: 30,
483
+ });
484
+ });
485
+
486
+ it('prints nothing with --json, in CI, or with NO_UPDATE_NOTIFIER, and keeps the notice for later', () => {
487
+ const {global: _global, ...input} = {
488
+ ...outside(),
489
+ stateDir: checked('0.7.3'),
490
+ };
491
+ expect(noticeLines({...input, json: true, now: 10})).toEqual([]);
492
+ expect(noticeLines({...input, env: {CI: 'true'}, now: 10})).toEqual([]);
493
+ expect(
494
+ noticeLines({...input, env: {NO_UPDATE_NOTIFIER: '1'}, now: 10}),
495
+ ).toEqual([]);
496
+ expect(noticeLines({...input, now: 10})).toHaveLength(2);
497
+ });
498
+
499
+ it('tells a project about its release, and an old astryx first on the search path, even with no check', () => {
500
+ const stateDir = checked('0.7.3');
501
+ const dir = project(tempDir(), {'@astryxdesign/core': '0.7.1'});
502
+ writeJson(path.join(dir, 'package-lock.json'), {lockfileVersion: 3});
503
+ installCore(dir, '0.7.1');
504
+ const old = cliOnPath('0.7.1');
505
+ const oldUpgrade = `To upgrade, run: npm i -g --prefix ${fs.realpathSync(old.root)} @astryxdesign/cli@latest`;
506
+ const input = {
507
+ cwd: dir,
508
+ self: {version: '0.7.1', root: tempDir('self')},
509
+ choice: {inProject: true, version: '0.7.1'},
510
+ searchPath: old.bin,
511
+ env: {},
512
+ };
513
+ expect(noticeLines({...input, stateDir, now: 10})).toEqual([
514
+ 'A new release of Astryx is available: 0.7.1 → 0.7.3',
515
+ 'To upgrade, run: npm install @astryxdesign/core@0.7.3 @astryxdesign/cli@0.7.3, then npx astryx upgrade --from 0.7.1 --apply',
516
+ 'The astryx command on this computer runs CLI 0.7.1, which answers every folder itself.',
517
+ oldUpgrade,
518
+ ]);
519
+ expect(
520
+ noticeLines({...input, stateDir: tempDir('fresh'), now: 10}),
521
+ ).toEqual([
522
+ 'The astryx command on this computer runs CLI 0.7.1, which answers every folder itself.',
523
+ oldUpgrade,
524
+ ]);
525
+ });
526
+
527
+ it('says nothing in a run inside the release cache, the CLI checking a release it installed', () => {
528
+ const cacheDir = tempDir('cache');
529
+ const tmp = path.join(cacheDir, 'releases', '.tmp-0.7.2-1-abc');
530
+ fs.mkdirSync(tmp, {recursive: true});
531
+ const stateDir = checked('0.7.3');
532
+ const old = cliOnPath('0.7.1');
533
+ const input = {
534
+ cwd: tmp,
535
+ self: {version: '0.7.2', root: tempDir('self')},
536
+ choice: {inProject: true, version: '0.7.2'},
537
+ cacheDir,
538
+ stateDir,
539
+ searchPath: old.bin,
540
+ env: {},
541
+ now: 10,
542
+ };
543
+ expect(noticeLines(input)).toEqual([]);
544
+ expect(readGlobalState(stateDir).notifiedAt).toBeUndefined();
545
+ });
546
+
547
+ it('says nothing about a copy that is not the global copy outside a project', () => {
548
+ const runner = {
549
+ ...outside(),
550
+ self: {
551
+ version: '0.7.2',
552
+ root: path.join(
553
+ tempDir('npx'),
554
+ '_npx',
555
+ 'x',
556
+ 'node_modules',
557
+ '@astryxdesign',
558
+ 'cli',
559
+ ),
560
+ },
561
+ stateDir: checked('0.7.3'),
562
+ };
563
+ expect(noticeLines({...runner, now: 10})).toEqual([]);
564
+ });
565
+ });
@@ -0,0 +1,4 @@
1
+ // @generated by scripts/sync-api-types.mjs from the JSDoc in foundation/**/*.mjs.
2
+ // DO NOT EDIT — run `pnpm sync:api-types` to regenerate.
3
+
4
+ export {};
@@ -0,0 +1,126 @@
1
+ // Copyright (c) Meta Platforms, Inc. and affiliates.
2
+
3
+ /**
4
+ * @file Runs one package-manager command so that it can never outlive the
5
+ * run that started it.
6
+ *
7
+ * `runNpm` (cache.mjs) starts this file instead of npm itself. It starts npm
8
+ * in a process group of its own, records npm's process id where the release
9
+ * lock can find it, and ends npm's whole group when any of these happens:
10
+ *
11
+ * - the time limit passes (exit status 124);
12
+ * - the run that started it goes away, even when it was killed outright and
13
+ * had no chance to clean up (its parent process changes);
14
+ * - it receives Ctrl-C, a closed terminal, or a termination request: it ends
15
+ * npm's group outright (npm defers these signals and would keep this run's
16
+ * output pipes open), then ends the same way.
17
+ *
18
+ * While npm works it renews the lock's owner record every 10 seconds
19
+ * (lock-lease.mjs), so a run on another computer that shares the cache knows
20
+ * the install is alive.
21
+ *
22
+ * Not a command and takes no options of its own; its arguments come only
23
+ * from `runNpm`.
24
+ *
25
+ * @input argv: time limit (ms), process-id file ('' for none), command, args
26
+ * @output npm's exit status; npm's output on the inherited streams
27
+ * @position foundation/release — started only by cache.mjs.
28
+ */
29
+
30
+ import {spawn} from 'node:child_process';
31
+ import * as fs from 'node:fs';
32
+ import * as path from 'node:path';
33
+ import {LEASE_RENEW_MS, renewLock} from './lock-lease.mjs';
34
+
35
+ const [timeoutArg, pidFile, command, ...args] = process.argv.slice(2);
36
+ const timeoutMs = Number(timeoutArg);
37
+ const parent = process.ppid;
38
+ const posix = process.platform !== 'win32';
39
+
40
+ const child = spawn(command, args, {
41
+ stdio: 'inherit',
42
+ detached: posix,
43
+ windowsHide: true,
44
+ });
45
+
46
+ /** End npm and everything it started. @param {NodeJS.Signals} signal */
47
+ function endGroup(signal) {
48
+ try {
49
+ if (posix && child.pid) process.kill(-child.pid, signal);
50
+ else child.kill(signal);
51
+ } catch {
52
+ // Already gone.
53
+ }
54
+ }
55
+
56
+ if (pidFile && child.pid) {
57
+ try {
58
+ fs.writeFileSync(pidFile, String(child.pid));
59
+ } catch {
60
+ // Without the record a takeover cannot end npm; the watch below still does.
61
+ }
62
+ }
63
+
64
+ /**
65
+ * Remove the process-id record: once npm is ended, its id may be reused by
66
+ * an unrelated process, which a later takeover must never end.
67
+ */
68
+ function forgetPid() {
69
+ if (pidFile) fs.rmSync(pidFile, {force: true});
70
+ }
71
+
72
+ const limit = setTimeout(() => {
73
+ endGroup('SIGKILL');
74
+ forgetPid();
75
+ process.exit(124);
76
+ }, timeoutMs);
77
+
78
+ const watch = setInterval(() => {
79
+ if (process.ppid !== parent) {
80
+ endGroup('SIGKILL');
81
+ forgetPid();
82
+ process.exit(1);
83
+ }
84
+ }, 500);
85
+
86
+ // The lock beside the pid file is renewed while npm works, so a run on
87
+ // another computer sharing the cache knows its holder is alive.
88
+ const lease = pidFile
89
+ ? setInterval(() => renewLock(path.dirname(pidFile)), LEASE_RENEW_MS)
90
+ : null;
91
+ lease?.unref();
92
+
93
+ for (const signal of /** @type {NodeJS.Signals[]} */ ([
94
+ 'SIGINT',
95
+ 'SIGTERM',
96
+ 'SIGHUP',
97
+ ])) {
98
+ process.on(signal, () => {
99
+ // npm defers these signals until its work finishes, and it holds this
100
+ // run's output pipes open, so it is ended outright.
101
+ endGroup('SIGKILL');
102
+ forgetPid();
103
+ clearTimeout(limit);
104
+ clearInterval(watch);
105
+ process.removeAllListeners(signal);
106
+ process.kill(process.pid, signal);
107
+ });
108
+ }
109
+
110
+ child.on('error', () => {
111
+ clearTimeout(limit);
112
+ clearInterval(watch);
113
+ forgetPid();
114
+ process.exit(127);
115
+ });
116
+
117
+ child.on('exit', (code, signal) => {
118
+ clearTimeout(limit);
119
+ clearInterval(watch);
120
+ forgetPid();
121
+ if (signal) {
122
+ process.kill(process.pid, signal);
123
+ return;
124
+ }
125
+ process.exit(code ?? 1);
126
+ });