@astryxdesign/cli 0.7.2 → 0.7.3-canary.02f5fe4

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 (373) hide show
  1. package/README.md +104 -71
  2. package/api/build/_adapter.d.mts +58 -0
  3. package/api/build/_adapter.mjs +127 -3
  4. package/api/build/blocks/blocks.d.mts +92 -0
  5. package/api/build/blocks/blocks.mjs +528 -0
  6. package/api/build/blocks/blocks.test.mjs +238 -0
  7. package/api/build/blocks/blocks.weights.json +1 -0
  8. package/api/build/build.mjs +25 -6
  9. package/api/build/build.type.mjs +1 -1
  10. package/api/build/kit/answer.d.mts +18 -0
  11. package/api/build/kit/answer.mjs +195 -0
  12. package/api/build/kit/answer.test.mjs +108 -0
  13. package/api/build/kit/kind.d.mts +25 -0
  14. package/api/build/kit/kind.mjs +170 -0
  15. package/api/build/kit/kit.d.mts +2 -4
  16. package/api/build/kit/kit.mjs +8 -398
  17. package/api/build/kit/rank.d.mts +3 -32
  18. package/api/build/kit/rank.mjs +25 -342
  19. package/api/build/kit/rank.test.mjs +2 -1
  20. package/api/build/kit/signals.d.mts +79 -0
  21. package/api/build/kit/signals.mjs +295 -0
  22. package/api/build/kit/start.d.mts +32 -0
  23. package/api/build/kit/start.mjs +163 -0
  24. package/api/build/kit/words.d.mts +72 -0
  25. package/api/build/kit/words.mjs +202 -0
  26. package/api/build/tree.d.mts +20 -0
  27. package/api/build/tree.mjs +53 -0
  28. package/api/build/tree.test.mjs +185 -0
  29. package/api/build/tree.weights.json +1 -0
  30. package/api/component/_adapter.mjs +39 -5
  31. package/api/component/component-replacement.test.mjs +1 -0
  32. package/api/component/component.mjs +17 -3
  33. package/api/component/component.test.mjs +15 -0
  34. package/api/component/doc-links.test.mjs +2 -2
  35. package/api/discover/_adapter.d.mts +1 -2
  36. package/api/discover/_adapter.mjs +42 -17
  37. package/api/discover/_adapter.test.mjs +42 -0
  38. package/api/discover/discover.mjs +0 -1
  39. package/api/docs/_adapter.d.mts +2 -2
  40. package/api/docs/_adapter.mjs +82 -23
  41. package/api/docs/docs-homes.test.mjs +127 -0
  42. package/api/docs/docs.doc.mjs +3 -3
  43. package/api/docs/docs.test.mjs +35 -19
  44. package/api/docs/integration-tree.test.mjs +20 -20
  45. package/api/docs/integrationDocs.test.mjs +1 -1
  46. package/api/docs/namespace-section.test.mjs +6 -6
  47. package/api/docs/reference-blocks.test.mjs +2 -2
  48. package/api/doctor/_adapter.d.mts +33 -13
  49. package/api/doctor/_adapter.mjs +55 -25
  50. package/api/doctor/doctor.d.mts +23 -5
  51. package/api/doctor/doctor.mjs +183 -40
  52. package/api/doctor/doctor.test.mjs +95 -12
  53. package/api/hook/hook.mjs +1 -1
  54. package/api/init/init.doc.mjs +20 -6
  55. package/api/init/init.test.mjs +18 -2
  56. package/api/init/init.type.d.mts +3 -3
  57. package/api/init/init.type.mjs +3 -3
  58. package/api/init/remove/remove.mjs +17 -4
  59. package/api/init/remove/remove.test.mjs +114 -2
  60. package/api/init/run/run.mjs +69 -6
  61. package/api/integration/_adapter.d.mts +8 -0
  62. package/api/integration/_adapter.mjs +21 -1
  63. package/api/integration/authoring-checks.mjs +83 -9
  64. package/api/integration/authoring-checks.test.mjs +220 -0
  65. package/api/integration/pack-check.mjs +123 -4
  66. package/api/integration/pack-check.test.mjs +87 -0
  67. package/api/integration/validate-integration-case-disk.test.mjs +111 -0
  68. package/api/integration/validate-integration-fixes.test.mjs +6 -5
  69. package/api/integration/validate-integration-gaps.test.mjs +1975 -0
  70. package/api/integration/validate-integration.d.mts +15 -2
  71. package/api/integration/validate-integration.mjs +298 -39
  72. package/api/integration/validate-integration.type.d.mts +90 -1
  73. package/api/integration/validate-integration.type.mjs +67 -2
  74. package/api/integration/validateIntegration.doc.mjs +1 -1
  75. package/api/search/ORDERING.md +28 -0
  76. package/api/search/_adapter.d.mts +82 -2
  77. package/api/search/_adapter.mjs +157 -10
  78. package/api/search/_adapter.test.mjs +173 -0
  79. package/api/search/_candidate-score.d.mts +40 -0
  80. package/api/search/_candidate-score.mjs +238 -0
  81. package/api/search/_cards.json +6098 -0
  82. package/api/search/_compound.d.mts +50 -0
  83. package/api/search/_compound.mjs +170 -0
  84. package/api/search/_doc-searcher.d.mts +4 -0
  85. package/api/search/_doc-searcher.mjs +23 -0
  86. package/api/search/_doc-searcher.test.mjs +36 -0
  87. package/api/search/_doc-searcher.weights.json +1 -0
  88. package/api/search/_doc-text.d.mts +30 -0
  89. package/api/search/_doc-text.mjs +87 -0
  90. package/api/search/_entries.d.mts +63 -0
  91. package/api/search/_entries.mjs +232 -0
  92. package/api/search/_entries.test.mjs +48 -0
  93. package/api/search/_features.d.mts +35 -0
  94. package/api/search/_features.mjs +155 -0
  95. package/api/search/_gather-components.d.mts +26 -0
  96. package/api/search/_gather-components.mjs +284 -0
  97. package/api/search/_gather-docs.d.mts +20 -0
  98. package/api/search/_gather-docs.mjs +254 -0
  99. package/api/search/_gather-hooks.d.mts +12 -0
  100. package/api/search/_gather-hooks.mjs +52 -0
  101. package/api/search/_gather-templates.d.mts +25 -0
  102. package/api/search/_gather-templates.mjs +107 -0
  103. package/api/search/_lexical.d.mts +135 -0
  104. package/api/search/_lexical.mjs +351 -0
  105. package/api/search/_main-searcher.d.mts +19 -0
  106. package/api/search/_main-searcher.mjs +115 -0
  107. package/api/search/_main-searcher.test.mjs +106 -0
  108. package/api/search/_main-searcher.weights.json +1 -0
  109. package/api/search/_module-doc.d.mts +32 -0
  110. package/api/search/_module-doc.mjs +78 -0
  111. package/api/search/_order.d.mts +32 -0
  112. package/api/search/_order.mjs +162 -0
  113. package/api/search/_order.test.mjs +72 -0
  114. package/api/search/_priority.d.mts +50 -0
  115. package/api/search/_priority.mjs +139 -0
  116. package/api/search/_query-words.d.mts +25 -0
  117. package/api/search/_query-words.mjs +92 -0
  118. package/api/search/_query.d.mts +65 -0
  119. package/api/search/_query.mjs +176 -0
  120. package/api/search/_result.d.mts +97 -0
  121. package/api/search/_result.mjs +150 -0
  122. package/api/search/_score.d.mts +60 -0
  123. package/api/search/_score.mjs +283 -0
  124. package/api/search/_synonyms.d.mts +21 -0
  125. package/api/search/_synonyms.mjs +67 -0
  126. package/api/search/_template-searcher.d.mts +4 -0
  127. package/api/search/_template-searcher.mjs +23 -0
  128. package/api/search/_template-searcher.test.mjs +40 -0
  129. package/api/search/_template-searcher.weights.json +1 -0
  130. package/api/search/_titles.d.mts +33 -0
  131. package/api/search/_titles.mjs +65 -0
  132. package/api/search/_words.d.mts +64 -0
  133. package/api/search/_words.mjs +157 -0
  134. package/api/search/search.d.mts +22 -305
  135. package/api/search/search.doc.mjs +8 -6
  136. package/api/search/search.mjs +56 -2482
  137. package/api/search/search.test.mjs +14 -14
  138. package/api/search/search.type.d.mts +6 -1
  139. package/api/search/search.type.mjs +6 -1
  140. package/api/search/tree.d.mts +15 -0
  141. package/api/search/tree.mjs +40 -0
  142. package/api/search/tree.test.mjs +66 -0
  143. package/api/search/tree.weights.json +1 -0
  144. package/api/template/template.mjs +5 -1
  145. package/api/theme/build/build.packed-old-core.test.mjs +1 -0
  146. package/api/upgrade/_adapter.d.mts +57 -9
  147. package/api/upgrade/_adapter.mjs +198 -28
  148. package/api/upgrade/registry/registry.d.mts +10 -4
  149. package/api/upgrade/registry/registry.mjs +33 -16
  150. package/api/upgrade/registry/registry.test.mjs +85 -0
  151. package/api/upgrade/run/run.d.mts +4 -2
  152. package/api/upgrade/run/run.mjs +13 -2
  153. package/api/upgrade/run/run.test.mjs +417 -3
  154. package/api/upgrade/upgrade.doc.mjs +10 -5
  155. package/api/upgrade/upgrade.mjs +13 -2
  156. package/api/upgrade/upgrade.test.mjs +194 -0
  157. package/api/upgrade/upgrade.type.d.mts +5 -3
  158. package/api/upgrade/upgrade.type.mjs +8 -6
  159. package/assets/codemods/transforms/v0.7.0/__tests__/validate-data-token-ownership.test.mjs +11 -0
  160. package/assets/codemods/transforms/v0.7.0/validate-data-token-ownership.mjs +3 -3
  161. package/assets/docs/author-a-theme.doc.mjs +2 -2
  162. package/assets/docs/is-my-code-good.doc.mjs +287 -0
  163. package/assets/docs/tree/add-a-component.doc.mjs +1 -1
  164. package/assets/docs/tree/add-a-theme.doc.mjs +1 -1
  165. package/assets/docs/tree/add-a-topic.doc.mjs +2 -2
  166. package/assets/docs/tree/agent-guidance.doc.mjs +1 -1
  167. package/assets/docs/tree/api.doc.mjs +6 -0
  168. package/assets/docs/tree/{integrations.doc.mjs → authoring.doc.mjs} +15 -6
  169. package/assets/docs/tree/block-template.doc.mjs +1 -1
  170. package/assets/docs/tree/build-the-template.doc.mjs +1 -1
  171. package/assets/docs/tree/building-blocks.doc.mjs +2 -2
  172. package/assets/docs/tree/check-the-props.doc.mjs +1 -1
  173. package/assets/docs/tree/check-your-docs.doc.mjs +2 -2
  174. package/assets/docs/tree/checks.doc.mjs +3 -3
  175. package/assets/docs/tree/ci-and-containers.doc.mjs +13 -0
  176. package/assets/docs/tree/cli-command.doc.mjs +1 -1
  177. package/assets/docs/tree/cli.doc.mjs +1 -1
  178. package/assets/docs/tree/codemods.doc.mjs +1 -1
  179. package/assets/docs/tree/component-family.doc.mjs +1 -1
  180. package/assets/docs/tree/component-imports.doc.mjs +1 -1
  181. package/assets/docs/tree/components.doc.mjs +1 -1
  182. package/assets/docs/tree/configuration.doc.mjs +1 -1
  183. package/assets/docs/tree/debug-and-gap-reports.doc.mjs +1 -1
  184. package/assets/docs/tree/define-the-theme.doc.mjs +1 -1
  185. package/assets/docs/tree/describe-the-component.doc.mjs +1 -1
  186. package/assets/docs/tree/docs.doc.mjs +1 -1
  187. package/assets/docs/tree/document-the-template.doc.mjs +1 -1
  188. package/assets/docs/tree/document-the-theme.doc.mjs +1 -1
  189. package/assets/docs/tree/export-template-assets.doc.mjs +1 -1
  190. package/assets/docs/tree/extend-or-replace.doc.mjs +1 -1
  191. package/assets/docs/{authoring.doc.mjs → tree/field-reference.doc.mjs} +4 -3
  192. package/assets/docs/tree/fonts-and-assets.doc.mjs +1 -1
  193. package/assets/docs/tree/generate-a-palette.doc.mjs +1 -1
  194. package/assets/docs/tree/global-integrations.doc.mjs +2 -2
  195. package/assets/docs/tree/grade-template-with-agent.doc.mjs +4 -4
  196. package/assets/docs/tree/help.doc.mjs +2 -2
  197. package/assets/docs/tree/host-entry.doc.mjs +128 -0
  198. package/assets/docs/tree/links.doc.mjs +1 -1
  199. package/assets/docs/tree/package-and-test.doc.mjs +1 -1
  200. package/assets/docs/tree/page-template.doc.mjs +1 -1
  201. package/assets/docs/tree/publishing.doc.mjs +1 -1
  202. package/assets/docs/tree/quick-start.doc.mjs +1 -1
  203. package/assets/docs/tree/replace-a-core-component.doc.mjs +1 -1
  204. package/assets/docs/tree/replace-a-core-template.doc.mjs +1 -1
  205. package/assets/docs/tree/sections-and-placement.doc.mjs +1 -1
  206. package/assets/docs/tree/see-it-in-an-app.doc.mjs +1 -1
  207. package/assets/docs/tree/ship.doc.mjs +2 -2
  208. package/assets/docs/tree/short-and-findable.doc.mjs +1 -1
  209. package/assets/docs/tree/single-component.doc.mjs +1 -1
  210. package/assets/docs/tree/start-a-template.doc.mjs +1 -1
  211. package/assets/docs/tree/subcomponent.doc.mjs +1 -1
  212. package/assets/docs/tree/template-assets.doc.mjs +1 -1
  213. package/assets/docs/tree/template-doc-overview.doc.mjs +2 -2
  214. package/assets/docs/tree/template-fonts.doc.mjs +1 -1
  215. package/assets/docs/tree/template-grading-rubric.doc.mjs +17 -38
  216. package/assets/docs/tree/template-icons.doc.mjs +1 -1
  217. package/assets/docs/tree/template-images-media.doc.mjs +1 -1
  218. package/assets/docs/tree/template-styles.doc.mjs +1 -1
  219. package/assets/docs/tree/templates.doc.mjs +1 -1
  220. package/assets/docs/tree/test-in-an-app.doc.mjs +1 -1
  221. package/assets/docs/tree/test-template-in-app.doc.mjs +1 -1
  222. package/assets/docs/tree/themes.doc.mjs +1 -1
  223. package/assets/docs/tree/tokens-data.doc.mjs +2 -2
  224. package/assets/docs/tree/troubleshooting.doc.mjs +1 -1
  225. package/assets/docs/tree/upgrading.doc.mjs +1 -1
  226. package/assets/docs/tree/use-a-theme-in-an-app.doc.mjs +1 -1
  227. package/assets/docs/tree/verify-packed-template.doc.mjs +1 -1
  228. package/assets/docs/tree/versioning.doc.mjs +5 -1
  229. package/assets/docs/tree/write-good-templates.doc.mjs +1 -1
  230. package/assets/docs/tree/write-the-template-file.doc.mjs +1 -1
  231. package/assets/docs/use-integrations.doc.mjs +120 -0
  232. package/assets/docs/working-with-ai.doc.mjs +6 -2
  233. package/assets/templates/themes/butter/butterTheme.ts +4 -2
  234. package/assets/templates/themes/stone/stoneTheme.ts +4 -2
  235. package/assets/templates/themes/y2k/y2kTheme.ts +4 -2
  236. package/authoring/debug/parse.d.mts +1 -1
  237. package/authoring/doctypes/schema/schema.doc.mjs +1 -1
  238. package/authoring/integration/integration.doc.mjs +1 -1
  239. package/authoring/integration/type.ts +1 -1
  240. package/clients/cli/bin/astryx.mjs +7 -78
  241. package/clients/cli/command-result-coverage.test.mjs +6 -5
  242. package/clients/cli/commands/build-theme.color-scheme.test.mjs +1 -1
  243. package/clients/cli/commands/{component/index.mjs → component.mjs} +37 -37
  244. package/clients/cli/commands/component.test.mjs +6 -6
  245. package/clients/cli/commands/docs.doc.mjs +2 -2
  246. package/clients/cli/commands/docs.mjs +1 -1
  247. package/clients/cli/commands/docs.test.mjs +14 -14
  248. package/clients/cli/commands/doctor-integration.test.mjs +49 -1
  249. package/clients/cli/commands/doctor.doc.mjs +1 -1
  250. package/clients/cli/commands/doctor.mjs +40 -0
  251. package/clients/cli/commands/doctor.test.mjs +65 -0
  252. package/clients/cli/commands/{hook/index.mjs → hook.mjs} +22 -22
  253. package/clients/cli/commands/import-hint-correctness.test.mjs +1 -1
  254. package/clients/cli/commands/init.behavior.test.mjs +230 -1
  255. package/clients/cli/commands/init.doc.mjs +28 -9
  256. package/clients/cli/commands/init.flag-help.test.mjs +1 -0
  257. package/clients/cli/commands/init.mjs +2 -2
  258. package/clients/cli/commands/integration-check.test.mjs +48 -0
  259. package/clients/cli/commands/integration.doc.mjs +1 -1
  260. package/clients/cli/commands/manifest.doc.mjs +1 -1
  261. package/clients/cli/commands/manifest.mjs +67 -0
  262. package/clients/cli/commands/outside-project.test.mjs +101 -1
  263. package/clients/cli/commands/postinstall.doc.mjs +23 -0
  264. package/clients/cli/commands/postinstall.mjs +31 -0
  265. package/clients/cli/commands/setup-nudge.test.mjs +78 -0
  266. package/clients/cli/commands/{build-theme.mjs → theme.mjs} +1 -1
  267. package/clients/cli/commands/upgrade.doc.mjs +4 -2
  268. package/clients/cli/commands/upgrade.mjs +9 -2
  269. package/clients/cli/commands/upgrade.subfolder-path.test.mjs +97 -0
  270. package/clients/cli/index.mjs +54 -94
  271. package/clients/cli/lib/component-format.mjs +1 -1
  272. package/clients/cli/lib/define-command.mjs +12 -7
  273. package/clients/cli/lib/exit-codes.test.mjs +5 -0
  274. package/clients/cli/lib/manifest.test.mjs +12 -0
  275. package/clients/cli/lib/welcome-box.mjs +50 -0
  276. package/clients/cli/run.mjs +108 -0
  277. package/foundation/agent-docs/agent-doc-state.d.mts +32 -7
  278. package/foundation/agent-docs/agent-doc-state.mjs +58 -9
  279. package/foundation/agent-docs/agent-docs.d.mts +100 -12
  280. package/foundation/agent-docs/agent-docs.mjs +1017 -105
  281. package/foundation/agent-docs/agent-docs.path-safety.test.mjs +1239 -17
  282. package/foundation/agent-docs/agent-docs.test.mjs +683 -39
  283. package/foundation/agent-docs/computer-agent-text.d.mts +39 -2
  284. package/foundation/agent-docs/computer-agent-text.mjs +135 -8
  285. package/foundation/agent-docs/computer-agent-text.test.mjs +99 -2
  286. package/foundation/config/project.mjs +20 -22
  287. package/foundation/config/project.test.mjs +56 -0
  288. package/foundation/discovery/authoring-self-docs.d.mts +7 -0
  289. package/foundation/discovery/authoring-self-docs.mjs +16 -4
  290. package/foundation/discovery/authoring-self-docs.test.mjs +15 -3
  291. package/foundation/discovery/authoring-surface.d.mts +1 -1
  292. package/foundation/discovery/authoring-surface.mjs +2 -2
  293. package/foundation/discovery/authoring-surface.test.mjs +1 -1
  294. package/foundation/discovery/cli-self-docs.d.mts +2 -1
  295. package/foundation/discovery/cli-self-docs.mjs +4 -3
  296. package/foundation/discovery/cli-self-docs.test.mjs +2 -2
  297. package/foundation/discovery/component-discovery.d.mts +37 -0
  298. package/foundation/discovery/component-discovery.mjs +80 -5
  299. package/foundation/discovery/component-loader.d.mts +1 -1
  300. package/foundation/discovery/docs-discovery.mjs +14 -1
  301. package/foundation/discovery/module-exports.d.mts +41 -0
  302. package/foundation/discovery/module-exports.mjs +253 -0
  303. package/foundation/discovery/runner-allowlist.d.mts +2 -4
  304. package/foundation/discovery/runner-allowlist.mjs +2 -4
  305. package/foundation/discovery/runner-commands.d.mts +77 -0
  306. package/foundation/discovery/runner-commands.mjs +294 -0
  307. package/foundation/discovery/runner-commands.test.mjs +128 -0
  308. package/foundation/discovery/stray-pointers.test.mjs +1 -1
  309. package/foundation/discovery/template-adapter.mjs +18 -1
  310. package/foundation/discovery/template-needs.mjs +1 -44
  311. package/foundation/discovery/theme-discovery.mjs +147 -3
  312. package/foundation/doc-compiler/doc-loads.test.mjs +33 -9
  313. package/foundation/doc-compiler/read.mjs +59 -1
  314. package/foundation/doc-compiler/read.test.mjs +11 -2
  315. package/foundation/doc-compiler/split-topic-keywords.test.mjs +3 -2
  316. package/foundation/doc-compiler/tree.d.mts +32 -6
  317. package/foundation/doc-compiler/tree.mjs +77 -43
  318. package/foundation/doc-compiler/tree.test.mjs +7 -7
  319. package/foundation/fs/paths.d.mts +70 -8
  320. package/foundation/fs/paths.mjs +556 -38
  321. package/foundation/fs/paths.test.mjs +430 -54
  322. package/foundation/fs/read-record.d.mts +18 -0
  323. package/foundation/fs/read-record.mjs +131 -0
  324. package/foundation/fs/read-record.test.mjs +77 -0
  325. package/foundation/integrations/autolink.d.mts +2 -1
  326. package/foundation/integrations/autolink.mjs +4 -13
  327. package/foundation/integrations/cli-peer-version.d.mts +36 -0
  328. package/foundation/integrations/cli-peer-version.mjs +132 -0
  329. package/foundation/integrations/cli-peer-version.test.mjs +354 -0
  330. package/foundation/integrations/computer-integrations.d.mts +6 -3
  331. package/foundation/integrations/computer-integrations.mjs +38 -9
  332. package/foundation/integrations/contribution-inventory.d.mts +15 -20
  333. package/foundation/integrations/contribution-inventory.mjs +41 -5
  334. package/foundation/integrations/exports-subpaths.d.mts +57 -0
  335. package/foundation/integrations/exports-subpaths.mjs +113 -0
  336. package/foundation/integrations/integrations.d.mts +27 -1
  337. package/foundation/integrations/integrations.mjs +61 -2
  338. package/foundation/integrations/public-imports.d.mts +118 -0
  339. package/foundation/integrations/public-imports.mjs +1769 -0
  340. package/foundation/integrations/public-imports.test.mjs +1793 -0
  341. package/foundation/integrations/validate-contributions.d.mts +115 -0
  342. package/foundation/integrations/validate-contributions.mjs +1168 -13
  343. package/foundation/release/cache.test.mjs +24 -21
  344. package/foundation/release/global-state.d.mts +8 -4
  345. package/foundation/release/global-state.mjs +4 -2
  346. package/foundation/release/handoff.d.mts +23 -4
  347. package/foundation/release/handoff.mjs +175 -8
  348. package/foundation/release/notices.d.mts +62 -8
  349. package/foundation/release/notices.mjs +136 -25
  350. package/foundation/release/notices.test.mjs +21 -0
  351. package/foundation/release/run-context.mjs +14 -0
  352. package/foundation/response/response-types.doc.mjs +1 -1
  353. package/foundation/route/route.d.mts +285 -0
  354. package/foundation/route/route.mjs +324 -0
  355. package/foundation/route/route.test.mjs +153 -0
  356. package/foundation/runtime/hosted-runtime.test.mjs +403 -0
  357. package/foundation/runtime/locked-versions.d.mts +32 -0
  358. package/foundation/runtime/locked-versions.mjs +65 -0
  359. package/foundation/runtime/package-root.d.mts +91 -0
  360. package/foundation/runtime/package-root.mjs +174 -0
  361. package/foundation/runtime/package-set.d.mts +62 -0
  362. package/foundation/runtime/package-set.mjs +130 -0
  363. package/foundation/runtime/package-set.test.mjs +432 -0
  364. package/foundation/text/string-utils.d.mts +2 -1
  365. package/foundation/text/string-utils.mjs +16 -5
  366. package/foundation/text/suggest-order.d.mts +72 -0
  367. package/foundation/text/suggest-order.mjs +363 -0
  368. package/foundation/text/suggest-order.test.mjs +259 -0
  369. package/host/door-conformance.test.mjs +274 -0
  370. package/host/index.d.ts +116 -0
  371. package/host/index.mjs +295 -0
  372. package/host/index.test.mjs +302 -0
  373. package/package.json +14 -9
@@ -0,0 +1,77 @@
1
+ // Copyright (c) Meta Platforms, Inc. and affiliates.
2
+
3
+ /**
4
+ * @file Tests for read-record: which paths an operation read, and whether
5
+ * any changed since, including a change made while the operation ran.
6
+ */
7
+
8
+ import {describe, it, expect} from 'vitest';
9
+ import * as fs from 'node:fs';
10
+ import * as os from 'node:os';
11
+ import * as path from 'node:path';
12
+ import {recordReads, unchangedSince} from './read-record.mjs';
13
+
14
+ function scratch() {
15
+ const dir = fs.mkdtempSync(path.join(os.tmpdir(), 'astryx-read-record-'));
16
+ const file = path.join(dir, 'doc.txt');
17
+ fs.writeFileSync(file, 'one');
18
+ return {dir, file};
19
+ }
20
+
21
+ describe('recordReads', () => {
22
+ it('records a read file and a listed folder, and sees them unchanged', () => {
23
+ const {dir, file} = scratch();
24
+ /** @type {Map<string, string>} */
25
+ const seen = new Map();
26
+ recordReads(seen, () => {
27
+ fs.readFileSync(file, 'utf8');
28
+ fs.readdirSync(dir);
29
+ });
30
+ expect([...seen.keys()]).toEqual(expect.arrayContaining([file, dir]));
31
+ expect(unchangedSince(seen)).toBe(true);
32
+ });
33
+
34
+ it('sees an edit made after the operation', () => {
35
+ const {file} = scratch();
36
+ /** @type {Map<string, string>} */
37
+ const seen = new Map();
38
+ recordReads(seen, () => fs.readFileSync(file, 'utf8'));
39
+ const later = new Date(Date.now() + 5_000);
40
+ fs.writeFileSync(file, 'two, longer');
41
+ fs.utimesSync(file, later, later);
42
+ expect(unchangedSince(seen)).toBe(false);
43
+ });
44
+
45
+ it('sees an edit made while the operation ran, after the file was read', () => {
46
+ const {file} = scratch();
47
+ /** @type {Map<string, string>} */
48
+ const seen = new Map();
49
+ recordReads(seen, () => {
50
+ fs.readFileSync(file, 'utf8');
51
+ const later = new Date(Date.now() + 5_000);
52
+ fs.writeFileSync(file, 'edited mid-run');
53
+ fs.utimesSync(file, later, later);
54
+ });
55
+ expect(unchangedSince(seen)).toBe(false);
56
+ });
57
+
58
+ it('sees a file added to a folder it probed', () => {
59
+ const {dir} = scratch();
60
+ /** @type {Map<string, string>} */
61
+ const seen = new Map();
62
+ recordReads(seen, () => fs.existsSync(path.join(dir, 'missing.txt')));
63
+ const later = new Date(Date.now() + 5_000);
64
+ fs.writeFileSync(path.join(dir, 'missing.txt'), 'now here');
65
+ fs.utimesSync(dir, later, later);
66
+ expect(unchangedSince(seen)).toBe(false);
67
+ });
68
+
69
+ it('records nothing outside an operation', () => {
70
+ const {file} = scratch();
71
+ /** @type {Map<string, string>} */
72
+ const seen = new Map();
73
+ recordReads(seen, () => {});
74
+ fs.readFileSync(file, 'utf8');
75
+ expect(seen.size).toBe(0);
76
+ });
77
+ });
@@ -21,7 +21,8 @@ export function readDeclaredDependencies(projectDir: string): Array<{
21
21
  *
22
22
  * Walking up matters because hoisting is normal — yarn and npm workspaces lift
23
23
  * a workspace member's dependency to the repo root, so the app's own
24
- * node_modules can be empty of a package it genuinely declares.
24
+ * node_modules can be empty of a package it genuinely declares. Integrations
25
+ * named in astryx.config resolve through the same walk.
25
26
  *
26
27
  * Presence is decided by the package's own package.json, not the directory, so
27
28
  * a stale empty directory is not mistaken for an install.
@@ -39,6 +39,7 @@
39
39
 
40
40
  import * as fs from 'node:fs';
41
41
  import * as path from 'node:path';
42
+ import {findNodeModulesPackage} from '../fs/paths.mjs';
42
43
  import {findManifestPaths, loadIntegrations} from './integrations.mjs';
43
44
  import {resolveProviders} from './provider-resolution.mjs';
44
45
 
@@ -123,7 +124,8 @@ export function readDeclaredDependencies(projectDir) {
123
124
  *
124
125
  * Walking up matters because hoisting is normal — yarn and npm workspaces lift
125
126
  * a workspace member's dependency to the repo root, so the app's own
126
- * node_modules can be empty of a package it genuinely declares.
127
+ * node_modules can be empty of a package it genuinely declares. Integrations
128
+ * named in astryx.config resolve through the same walk.
127
129
  *
128
130
  * Presence is decided by the package's own package.json, not the directory, so
129
131
  * a stale empty directory is not mistaken for an install.
@@ -135,18 +137,7 @@ export function readDeclaredDependencies(projectDir) {
135
137
  */
136
138
  export function resolveInstalledPackageDir(name, fromDir) {
137
139
  if (!isBarePackageName(name)) return null;
138
- const segments = name.split('/');
139
- let dir = path.resolve(fromDir);
140
- for (let i = 0; i < 50; i++) {
141
- const packageDir = path.join(dir, 'node_modules', ...segments);
142
- if (fs.existsSync(path.join(packageDir, 'package.json'))) {
143
- return {packageDir, hostDir: dir};
144
- }
145
- const parent = path.dirname(dir);
146
- if (parent === dir) break;
147
- dir = parent;
148
- }
149
- return null;
140
+ return findNodeModulesPackage(fromDir, name);
150
141
  }
151
142
 
152
143
  /**
@@ -0,0 +1,36 @@
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
+ /**
5
+ * The running CLI version.
6
+ * @returns {string}
7
+ */
8
+ export function cliVersion(): string;
9
+ /**
10
+ * Why the running CLI is outside an integration's declared
11
+ * `@astryxdesign/cli` peer range, or null when it is inside it (or when no
12
+ * peer is declared or the package.json cannot be read).
13
+ *
14
+ * A CLI below the range is told to upgrade Core and the CLI together, with
15
+ * the project's own install command, and then to run the upgrade codemods.
16
+ * A CLI above it (the range has an upper bound, such as `^0.6.0`) is
17
+ * told to look for a release of the package that supports it, since
18
+ * upgrading the CLI cannot help there; the author of the package in the
19
+ * current folder is told to test with this CLI and widen the range.
20
+ *
21
+ * @param {import('./integrations.mjs').LoadedIntegration} integration
22
+ * @param {string} [version] the running CLI version; defaults to this CLI's
23
+ * own package.json version
24
+ * @param {string} [cwd] where the install command would run; decides the
25
+ * package manager it names
26
+ * @param {{own?: boolean, from?: string}} [options] `own`: the package is
27
+ * the one being authored in the current folder, so above the range its
28
+ * author is told to test with this CLI and widen the range instead; `from`:
29
+ * the version the project's upgrade starts from when it is not the running
30
+ * CLI's, such as the Core of a project with no CLI of its own
31
+ * @returns {string | null}
32
+ */
33
+ export function cliPeerVersionProblem(integration: import("./integrations.mjs").LoadedIntegration, version?: string, cwd?: string, { own, from }?: {
34
+ own?: boolean;
35
+ from?: string;
36
+ }): string | null;
@@ -0,0 +1,132 @@
1
+ // Copyright (c) Meta Platforms, Inc. and affiliates.
2
+
3
+ /**
4
+ * @file Warn when the running CLI is outside an integration's declared peer
5
+ * range.
6
+ *
7
+ * An integration published against a newer CLI declares
8
+ * `"@astryxdesign/cli": ">=X.Y.Z"` in peerDependencies. When the running CLI
9
+ * does not satisfy that range, the integration's newer contributions may not
10
+ * load correctly. This module detects that mismatch and says why, naming the
11
+ * package, the range, the running version, and what to do.
12
+ *
13
+ * @input A loaded integration (with __packageDir) and the running CLI version.
14
+ * @output The message, or null when the CLI is inside the range.
15
+ * @position foundation/integrations — read by api/integration/validate-integration,
16
+ * which reports it as a `cli_peer_version` note in `doctor integration
17
+ * validate` and `integration check`, never a warning, so it fails nothing
18
+ * (architecture:cli-surface INV35).
19
+ */
20
+
21
+ import * as fs from 'node:fs';
22
+ import * as path from 'node:path';
23
+ import {satisfiesRange, semverCompare} from '../env/semver.mjs';
24
+ import {
25
+ detectPackageManager,
26
+ getCliInvocation,
27
+ } from '../env/package-manager.mjs';
28
+ import {CLI_PACKAGE, lowestAdmitted} from './cli-requirement.mjs';
29
+
30
+ /** The command that adds a package, by package manager. */
31
+ const ADD_VERB = /** @type {Record<string, string>} */ ({
32
+ pnpm: 'pnpm add',
33
+ yarn: 'yarn add',
34
+ bun: 'bun add',
35
+ npm: 'npm install',
36
+ });
37
+
38
+ /**
39
+ * Read the CLI's own version from its package.json.
40
+ * @returns {string}
41
+ */
42
+ function readCliVersion() {
43
+ const cliPkg = path.resolve(import.meta.dirname, '..', '..', 'package.json');
44
+ return JSON.parse(fs.readFileSync(cliPkg, 'utf8')).version;
45
+ }
46
+
47
+ /** Cached CLI version, read once. @type {string | undefined} */
48
+ let _cliVersion;
49
+
50
+ /**
51
+ * The running CLI version.
52
+ * @returns {string}
53
+ */
54
+ export function cliVersion() {
55
+ if (_cliVersion == null) _cliVersion = readCliVersion();
56
+ return _cliVersion;
57
+ }
58
+
59
+ /**
60
+ * Why the running CLI is outside an integration's declared
61
+ * `@astryxdesign/cli` peer range, or null when it is inside it (or when no
62
+ * peer is declared or the package.json cannot be read).
63
+ *
64
+ * A CLI below the range is told to upgrade Core and the CLI together, with
65
+ * the project's own install command, and then to run the upgrade codemods.
66
+ * A CLI above it (the range has an upper bound, such as `^0.6.0`) is
67
+ * told to look for a release of the package that supports it, since
68
+ * upgrading the CLI cannot help there; the author of the package in the
69
+ * current folder is told to test with this CLI and widen the range.
70
+ *
71
+ * @param {import('./integrations.mjs').LoadedIntegration} integration
72
+ * @param {string} [version] the running CLI version; defaults to this CLI's
73
+ * own package.json version
74
+ * @param {string} [cwd] where the install command would run; decides the
75
+ * package manager it names
76
+ * @param {{own?: boolean, from?: string}} [options] `own`: the package is
77
+ * the one being authored in the current folder, so above the range its
78
+ * author is told to test with this CLI and widen the range instead; `from`:
79
+ * the version the project's upgrade starts from when it is not the running
80
+ * CLI's, such as the Core of a project with no CLI of its own
81
+ * @returns {string | null}
82
+ */
83
+ export function cliPeerVersionProblem(
84
+ integration,
85
+ version,
86
+ cwd = process.cwd(),
87
+ {own = false, from} = {},
88
+ ) {
89
+ const ver = version ?? cliVersion();
90
+ if (!integration.__packageDir) return null;
91
+
92
+ let pkg;
93
+ try {
94
+ pkg = JSON.parse(
95
+ fs.readFileSync(path.join(integration.__packageDir, 'package.json'), 'utf8'),
96
+ );
97
+ } catch {
98
+ return null;
99
+ }
100
+
101
+ const range = pkg?.peerDependencies?.[CLI_PACKAGE];
102
+ if (typeof range !== 'string') return null;
103
+ if (satisfiesRange(ver, range)) return null;
104
+
105
+ const name = integration.name ?? pkg.name ?? '(integration)';
106
+ const lowest = lowestAdmitted(range);
107
+ if (lowest != null && semverCompare(ver, lowest) < 0) {
108
+ // Core and the CLI move together, then the upgrade codemods run, as the
109
+ // project-upgrade notice does: moving only the CLI would leave it ahead of
110
+ // Core. A package's own folder holds them as dev dependencies.
111
+ const verb = ADD_VERB[detectPackageManager(cwd)] ?? ADD_VERB.npm;
112
+ const install = `${verb}${own ? ' -D' : ''} @astryxdesign/core@latest ${CLI_PACKAGE}@latest`;
113
+ const upgrade = `${getCliInvocation(cwd, {installed: true})} upgrade --from ${from ?? ver} --apply`;
114
+ return (
115
+ `${name} needs ${CLI_PACKAGE} ${range}, but this CLI is ${ver}, so ` +
116
+ `some of what it ships may not load. Upgrade Core and the CLI ` +
117
+ `together: \`${install}\`, then \`${upgrade}\`.`
118
+ );
119
+ }
120
+ if (own) {
121
+ return (
122
+ `${name} declares ${CLI_PACKAGE} ${range}, but this CLI is ${ver}, ` +
123
+ `outside that range. Test the package with this CLI, and if it works, ` +
124
+ `widen the ${CLI_PACKAGE} range in its peerDependencies.`
125
+ );
126
+ }
127
+ return (
128
+ `${name} supports ${CLI_PACKAGE} ${range}, but this CLI is ${ver}, ` +
129
+ `outside that range, so some of what it ships may not work as its ` +
130
+ `author tested. Look for a release of ${name} that supports ${ver}.`
131
+ );
132
+ }
@@ -0,0 +1,354 @@
1
+ // Copyright (c) Meta Platforms, Inc. and affiliates.
2
+
3
+ /**
4
+ * @file Tests for the CLI peer version warning.
5
+ */
6
+
7
+ import {describe, it, expect, beforeAll, afterAll} from 'vitest';
8
+ import * as fs from 'node:fs';
9
+ import * as os from 'node:os';
10
+ import * as path from 'node:path';
11
+ import {cliPeerVersionProblem, cliVersion} from './cli-peer-version.mjs';
12
+ import {runCli} from '../../test-utils/run-cli.mjs';
13
+
14
+ /** @type {string} */
15
+ let tmpDir;
16
+
17
+ beforeAll(() => {
18
+ tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'astryx-cli-peer-'));
19
+ });
20
+
21
+ afterAll(() => {
22
+ fs.rmSync(tmpDir, {recursive: true, force: true});
23
+ });
24
+
25
+ /**
26
+ * @param {string} name
27
+ * @param {Record<string, unknown>} pkgJson
28
+ */
29
+ function makeIntegration(name, pkgJson) {
30
+ const dir = path.join(tmpDir, name.replace(/[/@]/g, '_'));
31
+ fs.mkdirSync(dir, {recursive: true});
32
+ fs.writeFileSync(path.join(dir, 'package.json'), JSON.stringify(pkgJson));
33
+ return {name, version: String(pkgJson.version ?? '1.0.0'), __packageDir: dir};
34
+ }
35
+
36
+ describe('cliPeerVersionProblem', () => {
37
+ it('returns null when no @astryxdesign/cli peer is declared', () => {
38
+ const integration = makeIntegration('@test/no-peer', {
39
+ name: '@test/no-peer',
40
+ version: '1.0.0',
41
+ });
42
+ expect(cliPeerVersionProblem(integration, '0.6.5', tmpDir)).toBeNull();
43
+ });
44
+
45
+ it('returns null when the CLI satisfies the peer range', () => {
46
+ const integration = makeIntegration('@test/satisfied', {
47
+ name: '@test/satisfied',
48
+ version: '1.0.0',
49
+ peerDependencies: {'@astryxdesign/cli': '>=0.6.0'},
50
+ });
51
+ expect(cliPeerVersionProblem(integration, '0.6.5', tmpDir)).toBeNull();
52
+ });
53
+
54
+ it('warns when the CLI is below the peer range', () => {
55
+ const integration = makeIntegration('@test/needs-newer', {
56
+ name: '@test/needs-newer',
57
+ version: '2.0.0',
58
+ peerDependencies: {'@astryxdesign/cli': '>=0.8.0'},
59
+ });
60
+ const project = path.join(tmpDir, 'npm-project');
61
+ fs.mkdirSync(project, {recursive: true});
62
+ fs.writeFileSync(path.join(project, 'package-lock.json'), '{}\n');
63
+ expect(cliPeerVersionProblem(integration, '0.6.5', project)).toBe(
64
+ '@test/needs-newer needs @astryxdesign/cli >=0.8.0, but this CLI is 0.6.5, so some of what it ships may not load. Upgrade Core and the CLI together: `npm install @astryxdesign/core@latest @astryxdesign/cli@latest`, then `npx astryx upgrade --from 0.6.5 --apply`.',
65
+ );
66
+ });
67
+
68
+ it("names the project's own package manager in the upgrade command", () => {
69
+ const project = path.join(tmpDir, 'pnpm-project');
70
+ fs.mkdirSync(project, {recursive: true});
71
+ fs.writeFileSync(path.join(project, 'pnpm-lock.yaml'), "lockfileVersion: '9.0'\n");
72
+ const integration = makeIntegration('@test/pnpm', {
73
+ name: '@test/pnpm',
74
+ version: '1.0.0',
75
+ peerDependencies: {'@astryxdesign/cli': '>=0.8.0'},
76
+ });
77
+ expect(cliPeerVersionProblem(integration, '0.6.5', project)).toContain(
78
+ '`pnpm add @astryxdesign/core@latest @astryxdesign/cli@latest`, then `pnpm exec astryx upgrade --from 0.6.5 --apply`',
79
+ );
80
+ });
81
+
82
+ it('starts the upgrade from the version it is given, such as a Core-only project\'s Core', () => {
83
+ const project = path.join(tmpDir, 'from-npm-project');
84
+ fs.mkdirSync(project, {recursive: true});
85
+ fs.writeFileSync(path.join(project, 'package-lock.json'), '{}\n');
86
+ const integration = makeIntegration('@test/from', {
87
+ name: '@test/from',
88
+ version: '1.0.0',
89
+ peerDependencies: {'@astryxdesign/cli': '>=0.8.0'},
90
+ });
91
+ expect(
92
+ cliPeerVersionProblem(integration, '0.7.3', project, {from: '0.6.0'}),
93
+ ).toContain('upgrade --from 0.6.0 --apply');
94
+ });
95
+
96
+ it('installs them as dev dependencies in the package being authored', () => {
97
+ const project = path.join(tmpDir, 'own-npm-project');
98
+ fs.mkdirSync(project, {recursive: true});
99
+ fs.writeFileSync(path.join(project, 'package-lock.json'), '{}\n');
100
+ const integration = makeIntegration('@test/own-below', {
101
+ name: '@test/own-below',
102
+ version: '1.0.0',
103
+ peerDependencies: {'@astryxdesign/cli': '>=0.8.0'},
104
+ });
105
+ expect(
106
+ cliPeerVersionProblem(integration, '0.6.5', project, {own: true}),
107
+ ).toContain(
108
+ '`npm install -D @astryxdesign/core@latest @astryxdesign/cli@latest`, then `npx astryx upgrade --from 0.6.5 --apply`',
109
+ );
110
+ });
111
+
112
+ it('does not ask for an upgrade when the CLI is newer than the range', () => {
113
+ const integration = makeIntegration('@test/older', {
114
+ name: '@test/older',
115
+ version: '1.0.0',
116
+ peerDependencies: {'@astryxdesign/cli': '^0.6.0'},
117
+ });
118
+ expect(cliPeerVersionProblem(integration, '0.7.3', tmpDir)).toBe(
119
+ '@test/older supports @astryxdesign/cli ^0.6.0, but this CLI is 0.7.3, outside that range, so some of what it ships may not work as its author tested. Look for a release of @test/older that supports 0.7.3.',
120
+ );
121
+ });
122
+
123
+ it('handles a caret range that excludes the running CLI', () => {
124
+ const integration = makeIntegration('@test/caret', {
125
+ name: '@test/caret',
126
+ version: '1.0.0',
127
+ peerDependencies: {'@astryxdesign/cli': '^1.0.0'},
128
+ });
129
+ expect(cliPeerVersionProblem(integration, '0.6.5', tmpDir)).toContain(
130
+ '^1.0.0',
131
+ );
132
+ });
133
+
134
+ it('returns null for a caret range the CLI satisfies', () => {
135
+ const integration = makeIntegration('@test/caret-ok', {
136
+ name: '@test/caret-ok',
137
+ version: '1.0.0',
138
+ peerDependencies: {'@astryxdesign/cli': '^0.6.0'},
139
+ });
140
+ expect(cliPeerVersionProblem(integration, '0.6.5', tmpDir)).toBeNull();
141
+ });
142
+
143
+ it('tells the author to test and widen the range when the package is their own', () => {
144
+ const integration = makeIntegration('@test/own', {
145
+ name: '@test/own',
146
+ version: '1.0.0',
147
+ peerDependencies: {'@astryxdesign/cli': '^0.6.0'},
148
+ });
149
+ expect(cliPeerVersionProblem(integration, '0.7.3', tmpDir, {own: true})).toBe(
150
+ '@test/own declares @astryxdesign/cli ^0.6.0, but this CLI is 0.7.3, outside that range. Test the package with this CLI, and if it works, widen the @astryxdesign/cli range in its peerDependencies.',
151
+ );
152
+ });
153
+
154
+ it('warns for an optional peer the CLI does not satisfy', () => {
155
+ // Optional peers are still peers: the integration needs a newer CLI to
156
+ // work fully. The warning is about the CLI version, not about whether
157
+ // the peer is required for install.
158
+ const integration = makeIntegration('@test/optional', {
159
+ name: '@test/optional',
160
+ version: '1.0.0',
161
+ peerDependencies: {'@astryxdesign/cli': '>=0.9.0'},
162
+ peerDependenciesMeta: {'@astryxdesign/cli': {optional: true}},
163
+ });
164
+ expect(cliPeerVersionProblem(integration, '0.6.5', tmpDir)).not.toBeNull();
165
+ });
166
+
167
+ it('handles a prerelease CLI version gracefully', () => {
168
+ const integration = makeIntegration('@test/prerelease', {
169
+ name: '@test/prerelease',
170
+ version: '1.0.0',
171
+ peerDependencies: {'@astryxdesign/cli': '>=0.7.0'},
172
+ });
173
+ // A canary like 0.7.0-canary.abc123 should satisfy >=0.7.0
174
+ expect(cliPeerVersionProblem(integration, '0.7.0-canary.abc123', tmpDir)).toBeNull();
175
+ // A canary below the floor should warn
176
+ expect(
177
+ cliPeerVersionProblem(integration, '0.6.5-canary.xyz', tmpDir),
178
+ ).not.toBeNull();
179
+ });
180
+
181
+ it('returns null when __packageDir is missing', () => {
182
+ const integration = {name: '@test/no-dir', version: '1.0.0'};
183
+ expect(cliPeerVersionProblem(integration, '0.6.5', tmpDir)).toBeNull();
184
+ });
185
+
186
+ it('returns null when package.json cannot be read', () => {
187
+ const integration = {
188
+ name: '@test/bad-dir',
189
+ version: '1.0.0',
190
+ __packageDir: path.join(tmpDir, 'nonexistent'),
191
+ };
192
+ expect(cliPeerVersionProblem(integration, '0.6.5', tmpDir)).toBeNull();
193
+ });
194
+ });
195
+
196
+ describe('cliVersion', () => {
197
+ it('returns a valid semver string', () => {
198
+ const ver = cliVersion();
199
+ expect(ver).toMatch(/^\d+\.\d+\.\d+/);
200
+ });
201
+ });
202
+
203
+ describe('as a note that fails nothing', () => {
204
+ /**
205
+ * An integration package at `dir` whose CLI peer range is `range`.
206
+ * @param {string} dir
207
+ * @param {string} range
208
+ */
209
+ function writeIntegration(dir, range) {
210
+ fs.mkdirSync(dir, {recursive: true});
211
+ fs.writeFileSync(
212
+ path.join(dir, 'package.json'),
213
+ JSON.stringify({
214
+ name: '@acme/widgets',
215
+ version: '1.0.0',
216
+ peerDependencies: {'@astryxdesign/cli': range},
217
+ }),
218
+ );
219
+ fs.writeFileSync(path.join(dir, 'astryx.integration.mjs'), 'export default {};\n');
220
+ }
221
+
222
+ /**
223
+ * A project with a lockfile that installs @acme/widgets.
224
+ * @param {string} range
225
+ */
226
+ function project(range) {
227
+ const root = fs.mkdtempSync(path.join(tmpDir, 'project-'));
228
+ fs.writeFileSync(
229
+ path.join(root, 'package.json'),
230
+ JSON.stringify({
231
+ name: 'app',
232
+ version: '1.0.0',
233
+ dependencies: {'@acme/widgets': '^1.0.0'},
234
+ }),
235
+ );
236
+ fs.writeFileSync(path.join(root, 'package-lock.json'), '{}\n');
237
+ writeIntegration(path.join(root, 'node_modules', '@acme', 'widgets'), range);
238
+ return root;
239
+ }
240
+
241
+ /** @param {string} [dev] */
242
+ const below = (dev = '') =>
243
+ `@acme/widgets needs @astryxdesign/cli >=99.0.0, but this CLI is ${cliVersion()}, so some of what it ships may not load. Upgrade Core and the CLI together: \`npm install${dev} @astryxdesign/core@latest @astryxdesign/cli@latest\`, then \`npx astryx upgrade --from ${cliVersion()} --apply\`.`;
244
+
245
+ it('doctor integration validate lists it in data.notes, never in data.issues, and exits 0', async () => {
246
+ const cwd = project('>=99.0.0');
247
+ const {status, stdout} = await runCli(
248
+ ['doctor', 'integration', 'validate', '@acme/widgets', '--json'],
249
+ {cwd},
250
+ );
251
+ const {data} = JSON.parse(stdout);
252
+ expect(status).toBe(0);
253
+ expect(data.notes).toContainEqual({code: 'cli_peer_version', message: below()});
254
+ expect(data.issues.map((/** @type {{code: string}} */ i) => i.code)).not.toContain(
255
+ 'cli_peer_version',
256
+ );
257
+ });
258
+
259
+ it('integration check --strict carries the note and still exits 0', async () => {
260
+ const pkg = fs.mkdtempSync(path.join(tmpDir, 'package-'));
261
+ writeIntegration(pkg, '>=99.0.0');
262
+ fs.writeFileSync(path.join(pkg, 'package-lock.json'), '{}\n');
263
+ const {status, stdout} = await runCli(
264
+ ['--json', 'integration', 'check', '--strict'],
265
+ {cwd: pkg},
266
+ );
267
+ const envelope = JSON.parse(stdout);
268
+ const validate = envelope.data.checks[0].response.data;
269
+ expect(validate.notes).toContainEqual({
270
+ code: 'cli_peer_version',
271
+ message: below(' -D'),
272
+ });
273
+ expect(validate.issues.map((/** @type {{code: string}} */ i) => i.code)).not.toContain(
274
+ 'cli_peer_version',
275
+ );
276
+ expect(envelope.data.failed).toBe(false);
277
+ expect(status).toBe(0);
278
+ });
279
+
280
+ it('starts the upgrade from the Core of a project with no CLI of its own', async () => {
281
+ const cwd = project('>=99.0.0');
282
+ const pkgJson = path.join(cwd, 'package.json');
283
+ const pkg = JSON.parse(fs.readFileSync(pkgJson, 'utf8'));
284
+ pkg.dependencies['@astryxdesign/core'] = '0.6.0';
285
+ fs.writeFileSync(pkgJson, JSON.stringify(pkg));
286
+ const core = path.join(cwd, 'node_modules', '@astryxdesign', 'core');
287
+ fs.mkdirSync(core, {recursive: true});
288
+ fs.writeFileSync(
289
+ path.join(core, 'package.json'),
290
+ JSON.stringify({name: '@astryxdesign/core', version: '0.6.0'}),
291
+ );
292
+ const {status, stdout} = await runCli(
293
+ ['doctor', 'integration', 'validate', '@acme/widgets', '--json'],
294
+ {cwd},
295
+ );
296
+ expect(status).toBe(0);
297
+ const note = JSON.parse(stdout).data.notes.find(
298
+ (/** @type {{code: string}} */ n) => n.code === 'cli_peer_version',
299
+ );
300
+ expect(note.message).toContain('upgrade --from 0.6.0 --apply');
301
+ expect(note.message).not.toContain(`--from ${cliVersion()}`);
302
+ });
303
+
304
+ it('asks for a release of an installed package that supports a CLI above its range', async () => {
305
+ const cwd = project('^0.6.0');
306
+ const {stdout} = await runCli(
307
+ ['doctor', 'integration', 'validate', '@acme/widgets', '--json'],
308
+ {cwd},
309
+ );
310
+ expect(JSON.parse(stdout).data.notes).toContainEqual({
311
+ code: 'cli_peer_version',
312
+ message: `@acme/widgets supports @astryxdesign/cli ^0.6.0, but this CLI is ${cliVersion()}, outside that range, so some of what it ships may not work as its author tested. Look for a release of @acme/widgets that supports ${cliVersion()}.`,
313
+ });
314
+ });
315
+
316
+ it('tells the author of the package in the current folder to test and widen a range below this CLI', async () => {
317
+ const pkg = fs.mkdtempSync(path.join(tmpDir, 'package-'));
318
+ writeIntegration(pkg, '^0.6.0');
319
+ const {status, stdout} = await runCli(
320
+ ['--json', 'integration', 'check', '--strict'],
321
+ {cwd: pkg},
322
+ );
323
+ const envelope = JSON.parse(stdout);
324
+ expect(envelope.data.checks[0].response.data.notes).toContainEqual({
325
+ code: 'cli_peer_version',
326
+ message: `@acme/widgets declares @astryxdesign/cli ^0.6.0, but this CLI is ${cliVersion()}, outside that range. Test the package with this CLI, and if it works, widen the @astryxdesign/cli range in its peerDependencies.`,
327
+ });
328
+ expect(envelope.data.failed).toBe(false);
329
+ expect(status).toBe(0);
330
+ });
331
+
332
+ it('leaves integration verify as it was: no warning, no issue', async () => {
333
+ const pkg = fs.mkdtempSync(path.join(tmpDir, 'package-'));
334
+ writeIntegration(pkg, '>=99.0.0');
335
+ const {stdout} = await runCli(['--json', 'integration', 'verify'], {
336
+ cwd: pkg,
337
+ });
338
+ expect(stdout).not.toContain('cli_peer_version');
339
+ const text = await runCli(['integration', 'verify'], {cwd: pkg});
340
+ expect(text.stdout + text.stderr).not.toContain('needs @astryxdesign/cli');
341
+ }, 120_000);
342
+
343
+ it('says nothing when the CLI is in the range', async () => {
344
+ const cwd = project(`>=${cliVersion()}`);
345
+ const {stdout} = await runCli(
346
+ ['doctor', 'integration', 'validate', '@acme/widgets', '--json'],
347
+ {cwd},
348
+ );
349
+ const {data} = JSON.parse(stdout);
350
+ expect(
351
+ (data.notes ?? []).map((/** @type {{code: string}} */ n) => n.code),
352
+ ).not.toContain('cli_peer_version');
353
+ });
354
+ });
@@ -34,15 +34,18 @@ export function computerInstallRoots(cliRoot: string, options?: {
34
34
  export function peerProblem(pkg: Record<string, any>, release: string): string | null;
35
35
  /**
36
36
  * The computer-level integrations installed beside the CLI at `cliRoot`, in
37
- * name order (declaration order when the root declares them). Reads
37
+ * name order (declaration order when the root declares them), then each
38
+ * integration a host approves (`approved`, architecture:cli-surface INV30),
39
+ * resolved by name from the package set that holds that CLI alone. Reads
38
40
  * package.json files only.
39
- * @param {{cliRoot: string, release: string, cacheDir?: string}} options
41
+ * @param {{cliRoot: string, release: string, cacheDir?: string, approved?: readonly string[]}} options
40
42
  * @returns {ComputerIntegrationCandidate[]}
41
43
  */
42
- export function scanComputerIntegrations({ cliRoot, release, cacheDir }: {
44
+ export function scanComputerIntegrations({ cliRoot, release, cacheDir, approved, }: {
43
45
  cliRoot: string;
44
46
  release: string;
45
47
  cacheDir?: string;
48
+ approved?: readonly string[];
46
49
  }): ComputerIntegrationCandidate[];
47
50
  /**
48
51
  * Load the compatible computer-level integrations as provider candidates,