@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
@@ -8,11 +8,17 @@
8
8
  * the runner that launched us. Returns the correct command prefix for running
9
9
  * package binaries (e.g. 'npx astryx', 'yarn astryx', 'pnpm exec astryx'), and
10
10
  * the scoped one-off form ('npx @astryxdesign/cli') when the `astryx` bin does
11
- * not resolve from the project.
11
+ * not resolve from the project. An integration's declared `cliCommand`, then
12
+ * a host's door name, come first (see cli-command.mjs).
12
13
  */
13
14
 
14
15
  import * as fs from 'node:fs';
15
16
  import * as path from 'node:path';
17
+ import {cliDoor, declaredCliCommand} from './cli-command.mjs';
18
+ import {CLI_ROOT} from '../fs/paths.mjs';
19
+ import {cliVersion, runChoice} from '../release/run-context.mjs';
20
+ import {isGlobalCopy} from '../release/choose.mjs';
21
+ import {isInReleaseCache} from '../release/cache.mjs';
16
22
 
17
23
  /**
18
24
  * A package manager we can install with and run binaries through.
@@ -473,25 +479,73 @@ export function cliBinResolves(targetDir = process.cwd()) {
473
479
  }
474
480
 
475
481
  /**
476
- * The safe, install-aware CLI invocation stem to suggest to users.
482
+ * The CLI invocation stem every printed command starts with. The one picker:
483
+ * every hint, error, Doctor fix and doc line that names a command goes through
484
+ * it (directly, or through {@link formatCliCommand} and
485
+ * {@link formatCliCommandsIn}). JSON fields that carry a command stay bare.
477
486
  *
478
- * - The `astryx` bin resolves from `targetDir`: `<run-prefix> astryx`
479
- * (e.g. `pnpm exec astryx`).
480
- * - Launched one-off (npx/dlx cache), or the bin does not resolve from
481
- * `targetDir` (a global install, a fresh package in a workspace):
482
- * `<dlx-prefix> @astryxdesign/cli`. The bare `astryx` name would make npm
483
- * fetch an unrelated registry package; the scoped package always resolves
484
- * to this CLI.
487
+ * In order:
488
+ *
489
+ * - The `cliCommand` an integration loaded in this project declares, e.g.
490
+ * `acme astryx`. When two integrations declare different commands,
491
+ * neither is used, the guess below is (even when a host set a door), and
492
+ * `astryx doctor` warns.
493
+ * - The door a host ran this call through (see cli-command.mjs). Empty in a
494
+ * plain run.
495
+ * - Otherwise the guess below.
496
+ *
497
+ * The guess names a command that, run from `targetDir`, reaches the release
498
+ * that answered (spec AST-081 FR5):
499
+ *
500
+ * - The project's own CLI answered and its `astryx` bin resolves from
501
+ * `targetDir`: `<run-prefix> astryx` (e.g. `pnpm exec astryx`).
502
+ * - Outside a project, the CLI installed for the computer (or the release it
503
+ * keeps) answered: `astryx`.
504
+ * - Otherwise: `<dlx-prefix> @astryxdesign/cli@<answering version>`, pinned so
505
+ * the package runner starts the same release. The bare `astryx` name would
506
+ * make npm fetch an unrelated registry package, and the unpinned scoped
507
+ * package the newest release.
508
+ *
509
+ * With `installed`, the line follows a step that installs the project's own
510
+ * CLI (an update notice, the hint that matches a project's CLI to its Core),
511
+ * so the guess is that CLI: `<run-prefix> astryx`. A declared `cliCommand`
512
+ * and a host's door still come first.
485
513
  *
486
514
  * @param {string} [targetDir] the project the printed command runs in;
487
515
  * defaults to the working directory
516
+ * @param {{installed?: boolean}} [options]
517
+ * @returns {string}
518
+ */
519
+ export function getCliInvocation(targetDir, {installed = false} = {}) {
520
+ const declared = declaredCliCommand(targetDir);
521
+ if (declared.command) return declared.command;
522
+ const guess = () =>
523
+ installed
524
+ ? `${getRunPrefix(targetDir)} ${CLI_BIN}`
525
+ : guessCliInvocation(targetDir);
526
+ if (declared.conflict) return guess();
527
+ return cliDoor() ?? guess();
528
+ }
529
+
530
+ /**
531
+ * The package-manager guess alone, without any integration's `cliCommand` or
532
+ * a host's door. The picker falls back to it when declared commands conflict,
533
+ * and Doctor names it then.
534
+ *
535
+ * @param {string} [targetDir]
488
536
  * @returns {string}
489
537
  */
490
- export function getCliInvocation(targetDir) {
491
- if (isCliOneOff() || !cliBinResolves(targetDir)) {
492
- return `${getDlxPrefix(targetDir)} ${CLI_PACKAGE}`;
538
+ export function guessCliInvocation(targetDir) {
539
+ // Who answered this run decides the stem, so every command one run prints
540
+ // reaches the same release, whichever folder a line names.
541
+ const choice = runChoice(process.cwd());
542
+ if (choice.copy === 'project' && !isCliOneOff() && cliBinResolves(targetDir)) {
543
+ return `${getRunPrefix(targetDir)} ${CLI_BIN}`;
493
544
  }
494
- return `${getRunPrefix(targetDir)} ${CLI_BIN}`;
545
+ if (!choice.inProject && (isGlobalCopy(CLI_ROOT) || isInReleaseCache(CLI_ROOT))) {
546
+ return CLI_BIN;
547
+ }
548
+ return `${getDlxPrefix(targetDir)} ${CLI_PACKAGE}@${cliVersion()}`;
495
549
  }
496
550
 
497
551
  /**
@@ -512,3 +566,120 @@ export function formatCliCommand(command, targetDir) {
512
566
  const stem = getCliInvocation(targetDir);
513
567
  return sub ? `${stem} ${sub}` : stem;
514
568
  }
569
+
570
+ /** Code-fence languages whose lines are shell commands. */
571
+ const SHELL_FENCES = new Set(['', 'bash', 'sh', 'shell', 'zsh', 'console']);
572
+
573
+ /** Code-fence languages that hold prose, such as a prompt to paste. */
574
+ const PROSE_FENCES = new Set(['text', 'txt', 'markdown', 'md']);
575
+
576
+ /** `astryx` where a shell line starts a command: line start, `$ `, `&&`, `||`, `;`, `|`. */
577
+ const SHELL_COMMAND = /(^\s*(?:\$\s+)?|(?:&&|\|\||;|\|)\s*)astryx(?=\s|$)/g;
578
+
579
+ /** `astryx` opening an inline code span: `` `astryx docs` `` or `` `astryx` ``. */
580
+ const INLINE_COMMAND = /`astryx(?=[\s`])/g;
581
+
582
+ /**
583
+ * Prefix the CLI commands in one line.
584
+ * @param {string} line
585
+ * @param {string} stem
586
+ * @param {string | null} lang the fence the line sits in; null for prose
587
+ * @returns {string}
588
+ */
589
+ function prefixLine(line, stem, lang) {
590
+ if (lang == null) return line.replace(INLINE_COMMAND, `\`${stem}`);
591
+ if (SHELL_FENCES.has(lang)) return line.replace(SHELL_COMMAND, `$1${stem}`);
592
+ if (PROSE_FENCES.has(lang)) {
593
+ return line
594
+ .replace(INLINE_COMMAND, `\`${stem}`)
595
+ .replace(SHELL_COMMAND, `$1${stem}`);
596
+ }
597
+ return line;
598
+ }
599
+
600
+ /**
601
+ * Prefix every CLI command in doc text with `stem`, the invocation from
602
+ * {@link getCliInvocation}.
603
+ *
604
+ * Docs, including an integration's, are written with bare `astryx ...`
605
+ * commands; this rewrites them when they are shown. In prose, an inline code
606
+ * span that starts with `astryx`. In a shell code fence (no language, `bash`,
607
+ * `sh`, `shell`, `zsh`, `console`), each `astryx` that starts a command. In a
608
+ * `text` or `markdown` fence, both. Other fences, and plain words, are left
609
+ * alone. JSON output keeps the authored text.
610
+ *
611
+ * @param {string} text Markdown-style doc text, or the body of one code block.
612
+ * @param {string} stem e.g. `pnpm exec astryx`
613
+ * @param {string | null} [lang] the code block's language when `text` is one
614
+ * block's body; omit for Markdown, whose fences are followed
615
+ * @returns {string}
616
+ */
617
+ export function prefixCliCommands(text, stem, lang = null) {
618
+ if (stem === CLI_BIN) return String(text);
619
+ const blockLang = lang == null ? null : lang.toLowerCase();
620
+ /** @type {{marker: string, lang: string} | null} */
621
+ let fence = null;
622
+ return String(text)
623
+ .split('\n')
624
+ .map(line => {
625
+ if (blockLang != null) return prefixLine(line, stem, blockLang);
626
+ const marker = /^\s*(`{3,}|~{3,})\s*([\w-]*)/.exec(line);
627
+ if (marker) {
628
+ if (fence == null) {
629
+ fence = {marker: marker[1], lang: marker[2].toLowerCase()};
630
+ } else if (marker[1].startsWith(fence.marker)) {
631
+ fence = null;
632
+ }
633
+ return line;
634
+ }
635
+ return prefixLine(line, stem, fence?.lang ?? null);
636
+ })
637
+ .join('\n');
638
+ }
639
+
640
+ /**
641
+ * Quote one argument of a printed command so the shell it is pasted into
642
+ * passes it on unchanged. Printed commands target a POSIX shell (sh, bash,
643
+ * zsh), and PowerShell on Windows; cmd.exe is not a target. A value made only
644
+ * of letters, digits and `@ / . _ -` (and, on Windows, a drive path's `:` and
645
+ * `\`) stays bare. Any other value gets single quotes, inside which both
646
+ * shells read `$`, backticks, `%`, `!`, spaces and double quotes literally. An
647
+ * embedded single quote is written `'\''` for a POSIX shell and doubled for
648
+ * PowerShell, which also ends a single-quoted string at the typographic
649
+ * quotes `‘ ’ ‚ ‛`, so those are doubled too (`Macy’s` → `'Macy’’s'`).
650
+ *
651
+ * @param {string} value
652
+ * @param {string} [platform] the `process.platform` the command is printed for
653
+ * @returns {string}
654
+ */
655
+ export function quoteCommandArgument(value, platform = process.platform) {
656
+ const text = String(value);
657
+ if (platform === 'win32') {
658
+ if (/^[A-Za-z0-9@/._:\\-]+$/u.test(text)) return text;
659
+ return `'${text.replace(/['\u2018\u2019\u201A\u201B]/gu, quote => quote + quote)}'`;
660
+ }
661
+ if (/^[A-Za-z0-9@/._-]+$/u.test(text)) return text;
662
+ return `'${text.replace(/'/gu, `'\\''`)}'`;
663
+ }
664
+
665
+ /**
666
+ * A printed command that changes to `dir`, then runs `command` there, so it
667
+ * does the same work pasted from any folder. It runs `command` only when the
668
+ * change worked. For a POSIX shell: `cd <dir> && <command>`. For PowerShell
669
+ * on Windows: `cd -LiteralPath <dir> -ErrorAction Stop; <command>`, because
670
+ * `-LiteralPath` reads `[` and `]` in a folder name literally, and a failed
671
+ * change stops the line in Windows PowerShell 5.1, which has no `&&`, as in
672
+ * PowerShell 7.
673
+ *
674
+ * @param {string} dir an absolute folder
675
+ * @param {string} command the command to run there, its arguments already
676
+ * quoted with {@link quoteCommandArgument}
677
+ * @param {string} [platform] the `process.platform` the command is printed for
678
+ * @returns {string}
679
+ */
680
+ export function commandInFolder(dir, command, platform = process.platform) {
681
+ const folder = quoteCommandArgument(dir, platform);
682
+ return platform === 'win32'
683
+ ? `cd -LiteralPath ${folder} -ErrorAction Stop; ${command}`
684
+ : `cd ${folder} && ${command}`;
685
+ }
@@ -1,6 +1,7 @@
1
1
  // Copyright (c) Meta Platforms, Inc. and affiliates.
2
2
 
3
3
  import {describe, it, expect, afterEach, vi} from 'vitest';
4
+ import {spawnSync} from 'node:child_process';
4
5
  import * as fs from 'node:fs';
5
6
  import * as path from 'node:path';
6
7
  import * as os from 'node:os';
@@ -11,12 +12,37 @@ import {
11
12
  isCliOneOff,
12
13
  cliBinResolves,
13
14
  getCliInvocation,
15
+ guessCliInvocation,
14
16
  formatCliCommand,
17
+ prefixCliCommands,
18
+ quoteCommandArgument,
19
+ commandInFolder,
15
20
  } from './package-manager.mjs';
21
+ import {recordDeclaredCliCommands, runWithCliDoor} from './cli-command.mjs';
22
+ import {CLI_VERSION} from '../../test-utils/release-fixtures.mjs';
16
23
 
17
24
  let tmpDir;
18
25
  const ORIGINAL_ARGV1 = process.argv[1];
19
26
 
27
+ /** Prints, as JSON, the folder a command ran in and the arguments it got. */
28
+ const PRINT_RUN =
29
+ 'process.stdout.write(JSON.stringify({cwd: process.cwd(), argv: process.argv.slice(1)}))';
30
+
31
+ /** PowerShell, when it is on the PATH: printed commands target it on Windows. */
32
+ const PWSH = (process.env.PATH ?? '')
33
+ .split(path.delimiter)
34
+ .flatMap(dir => ['pwsh', 'pwsh.exe'].map(name => path.join(dir, name)))
35
+ .find(file => isFile(file));
36
+
37
+ /** @param {string} file */
38
+ function isFile(file) {
39
+ try {
40
+ return fs.statSync(file).isFile();
41
+ } catch {
42
+ return false;
43
+ }
44
+ }
45
+
20
46
  afterEach(() => {
21
47
  if (tmpDir) {
22
48
  fs.rmSync(tmpDir, {recursive: true, force: true});
@@ -37,8 +63,16 @@ function linkCliBin(dir, name = 'astryx') {
37
63
  const binDir = path.join(dir, 'node_modules', '.bin');
38
64
  fs.mkdirSync(binDir, {recursive: true});
39
65
  fs.writeFileSync(path.join(binDir, name), '');
66
+ // The installed package the bin belongs to: this copy, so the project's own
67
+ // CLI is the one answering (spec AST-081 FR5's run-prefix case).
68
+ const pkg = path.join(dir, 'node_modules', '@astryxdesign', 'cli');
69
+ fs.mkdirSync(path.dirname(pkg), {recursive: true});
70
+ if (!fs.existsSync(pkg)) fs.symlinkSync(THIS_CLI, pkg, 'dir');
40
71
  }
41
72
 
73
+ /** This CLI package's folder. */
74
+ const THIS_CLI = path.resolve(import.meta.dirname, '..', '..');
75
+
42
76
  /** The header a classic Yarn (1.x) lockfile starts with. */
43
77
  const YARN_V1_LOCK =
44
78
  '# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.\n# yarn lockfile v1\n\n';
@@ -48,7 +82,8 @@ const YARN_BERRY_LOCK =
48
82
  '# This file is generated by running "yarn install" inside your project.\n# Manual changes might be lost - proceed with caution!\n\n__metadata:\n version: 8\n';
49
83
 
50
84
  /** An installed CLI's entry, outside any runner cache (a global or workspace install). */
51
- const INSTALLED_ENTRY = '/usr/local/lib/node_modules/@astryxdesign/cli/clients/cli/bin/astryx.mjs';
85
+ const INSTALLED_ENTRY =
86
+ '/usr/local/lib/node_modules/@astryxdesign/cli/clients/cli/bin/astryx.mjs';
52
87
 
53
88
  describe('detectPackageManager', () => {
54
89
  it('detects yarn from yarn.lock', () => {
@@ -132,7 +167,10 @@ describe('detectPackageManager', () => {
132
167
  const dir = makeTmpDir();
133
168
  fs.writeFileSync(path.join(dir, 'pnpm-lock.yaml'), '');
134
169
  fs.writeFileSync(path.join(dir, 'yarn.lock'), '');
135
- fs.writeFileSync(path.join(dir, '.yarnrc.yml'), 'nodeLinker: node-modules\n');
170
+ fs.writeFileSync(
171
+ path.join(dir, '.yarnrc.yml'),
172
+ 'nodeLinker: node-modules\n',
173
+ );
136
174
  expect(detectPackageManager(dir)).toBe('yarn');
137
175
  });
138
176
 
@@ -425,12 +463,14 @@ describe('isCliOneOff', () => {
425
463
  });
426
464
 
427
465
  it('detects a pnpm dlx cache entry', () => {
428
- process.argv[1] = '/home/u/.cache/pnpm/dlx/9f/node_modules/@astryxdesign/cli/bin/astryx.mjs';
466
+ process.argv[1] =
467
+ '/home/u/.cache/pnpm/dlx/9f/node_modules/@astryxdesign/cli/bin/astryx.mjs';
429
468
  expect(isCliOneOff()).toBe(true);
430
469
  });
431
470
 
432
471
  it('detects a bunx cache entry', () => {
433
- process.argv[1] = '/home/u/.bun/install/cache/@astryxdesign/cli/bin/astryx.mjs';
472
+ process.argv[1] =
473
+ '/home/u/.bun/install/cache/@astryxdesign/cli/bin/astryx.mjs';
434
474
  expect(isCliOneOff()).toBe(true);
435
475
  });
436
476
 
@@ -506,11 +546,11 @@ describe('getCliInvocation', () => {
506
546
  // fresh package), the CLI used to print `npx astryx` for a project where that
507
547
  // bin does not resolve, so following the hint fetched someone else's package.
508
548
  it.each([
509
- ['package-lock.json', '{}', 'npx @astryxdesign/cli'],
510
- ['pnpm-lock.yaml', '', 'pnpm dlx @astryxdesign/cli'],
511
- ['yarn.lock', YARN_BERRY_LOCK, 'yarn dlx @astryxdesign/cli'],
512
- ['yarn.lock', YARN_V1_LOCK, 'npx @astryxdesign/cli'],
513
- ['bun.lockb', '', 'bunx @astryxdesign/cli'],
549
+ ['package-lock.json', '{}', `npx @astryxdesign/cli@${CLI_VERSION}`],
550
+ ['pnpm-lock.yaml', '', `pnpm dlx @astryxdesign/cli@${CLI_VERSION}`],
551
+ ['yarn.lock', YARN_BERRY_LOCK, `yarn dlx @astryxdesign/cli@${CLI_VERSION}`],
552
+ ['yarn.lock', YARN_V1_LOCK, `npx @astryxdesign/cli@${CLI_VERSION}`],
553
+ ['bun.lockb', '', `bunx @astryxdesign/cli@${CLI_VERSION}`],
514
554
  ])(
515
555
  'prints the scoped package for a project with no local CLI (%s), launched from an installed path',
516
556
  (lockfile, contents, expected) => {
@@ -527,21 +567,24 @@ describe('getCliInvocation', () => {
527
567
  const dir = makeTmpDir();
528
568
  delete process.env.npm_config_user_agent;
529
569
  fs.writeFileSync(path.join(dir, 'package.json'), '{"name":"fresh"}');
530
- expect(getCliInvocation(dir)).toBe('npx @astryxdesign/cli');
570
+ expect(getCliInvocation(dir)).toBe(`npx @astryxdesign/cli@${CLI_VERSION}`);
531
571
  });
532
572
 
533
573
  it('uses the dlx runner + scoped package when run one-off', () => {
534
574
  process.argv[1] = '/home/u/.npm/_npx/a1b2/node_modules/.bin/astryx';
535
575
  const dir = makeTmpDir();
536
576
  delete process.env.npm_config_user_agent;
537
- expect(getCliInvocation(dir)).toBe('npx @astryxdesign/cli');
577
+ expect(getCliInvocation(dir)).toBe(`npx @astryxdesign/cli@${CLI_VERSION}`);
538
578
  });
539
579
 
540
580
  it('pairs the dlx runner with the scoped package for pnpm one-off', () => {
541
- process.argv[1] = '/home/u/.cache/pnpm/dlx/9f/node_modules/@astryxdesign/cli/bin/astryx.mjs';
581
+ process.argv[1] =
582
+ '/home/u/.cache/pnpm/dlx/9f/node_modules/@astryxdesign/cli/bin/astryx.mjs';
542
583
  const dir = makeTmpDir();
543
584
  fs.writeFileSync(path.join(dir, 'pnpm-lock.yaml'), '');
544
- expect(getCliInvocation(dir)).toBe('pnpm dlx @astryxdesign/cli');
585
+ expect(getCliInvocation(dir)).toBe(
586
+ `pnpm dlx @astryxdesign/cli@${CLI_VERSION}`,
587
+ );
545
588
  });
546
589
  });
547
590
 
@@ -551,7 +594,9 @@ describe('formatCliCommand', () => {
551
594
  const dir = makeTmpDir();
552
595
  fs.writeFileSync(path.join(dir, 'pnpm-lock.yaml'), '');
553
596
  linkCliBin(dir);
554
- expect(formatCliCommand('astryx component Button', dir)).toBe('pnpm exec astryx component Button');
597
+ expect(formatCliCommand('astryx component Button', dir)).toBe(
598
+ 'pnpm exec astryx component Button',
599
+ );
555
600
  });
556
601
 
557
602
  it('accepts a bare subcommand (no leading astryx)', () => {
@@ -566,6 +611,287 @@ describe('formatCliCommand', () => {
566
611
  process.argv[1] = '/home/u/.npm/_npx/a1b2/node_modules/.bin/astryx';
567
612
  const dir = makeTmpDir();
568
613
  fs.writeFileSync(path.join(dir, 'package-lock.json'), '{}');
569
- expect(formatCliCommand('astryx component Button', dir)).toBe('npx @astryxdesign/cli component Button');
614
+ expect(formatCliCommand('astryx component Button', dir)).toBe(
615
+ `npx @astryxdesign/cli@${CLI_VERSION} component Button`,
616
+ );
617
+ });
618
+ });
619
+
620
+ describe('the picker: declared cliCommand, then the door, then the guess', () => {
621
+ /** A pnpm project with the CLI installed: the guess is `pnpm exec astryx`. */
622
+ function pnpmProject() {
623
+ process.argv[1] = INSTALLED_ENTRY;
624
+ const dir = makeTmpDir();
625
+ fs.writeFileSync(path.join(dir, 'package.json'), '{"name":"app"}');
626
+ fs.writeFileSync(path.join(dir, 'pnpm-lock.yaml'), '');
627
+ linkCliBin(dir);
628
+ return dir;
629
+ }
630
+
631
+ it('guesses when nothing is declared', () => {
632
+ const dir = pnpmProject();
633
+ recordDeclaredCliCommands(dir, [{name: '@acme/a'}]);
634
+ expect(getCliInvocation(dir)).toBe('pnpm exec astryx');
635
+ });
636
+
637
+ it("uses an integration's cliCommand over the guess", () => {
638
+ const dir = pnpmProject();
639
+ recordDeclaredCliCommands(dir, [
640
+ {name: '@acme/a', cliCommand: 'acme astryx'},
641
+ {name: '@acme/b', cliCommand: 'acme astryx'},
642
+ ]);
643
+ expect(getCliInvocation(dir)).toBe('acme astryx');
644
+ expect(formatCliCommand('astryx docs tokens', dir)).toBe(
645
+ 'acme astryx docs tokens',
646
+ );
647
+ expect(guessCliInvocation(dir)).toBe('pnpm exec astryx');
648
+ });
649
+
650
+ it('falls back to the guess when integrations declare different commands', () => {
651
+ const dir = pnpmProject();
652
+ recordDeclaredCliCommands(dir, [
653
+ {name: '@acme/a', cliCommand: 'acme astryx'},
654
+ {name: '@acme/b', cliCommand: 'other astryx'},
655
+ ]);
656
+ expect(getCliInvocation(dir)).toBe('pnpm exec astryx');
657
+ });
658
+
659
+ it("puts a declared command ahead of a host's door", () => {
660
+ const dir = pnpmProject();
661
+ recordDeclaredCliCommands(dir, [
662
+ {name: '@acme/a', cliCommand: 'acme astryx'},
663
+ ]);
664
+ expect(runWithCliDoor('astryx', () => getCliInvocation(dir))).toBe(
665
+ 'acme astryx',
666
+ );
667
+ });
668
+
669
+ it("uses a host's door when nothing is declared", () => {
670
+ const dir = pnpmProject();
671
+ recordDeclaredCliCommands(dir, []);
672
+ expect(runWithCliDoor('astryx', () => getCliInvocation(dir))).toBe(
673
+ 'astryx',
674
+ );
675
+ });
676
+
677
+ it('guesses on a conflict even when a host set a door', () => {
678
+ const dir = pnpmProject();
679
+ recordDeclaredCliCommands(dir, [
680
+ {name: '@acme/a', cliCommand: 'acme astryx'},
681
+ {name: '@acme/b', cliCommand: 'other astryx'},
682
+ ]);
683
+ expect(runWithCliDoor('astryx', () => getCliInvocation(dir))).toBe(
684
+ 'pnpm exec astryx',
685
+ );
686
+ });
687
+ });
688
+
689
+ describe('prefixCliCommands', () => {
690
+ /** @param {string} text @param {string | null} [lang] */
691
+ const shown = (text, lang) => prefixCliCommands(text, 'acme astryx', lang);
692
+
693
+ it('prefixes inline code spans that run the CLI', () => {
694
+ expect(
695
+ shown('Run `astryx docs theme`, then `astryx`. Not `astryx.config.ts`.'),
696
+ ).toBe(
697
+ 'Run `acme astryx docs theme`, then `acme astryx`. Not `astryx.config.ts`.',
698
+ );
699
+ });
700
+
701
+ it('prefixes commands in shell fences and leaves code fences alone', () => {
702
+ const text = [
703
+ '```bash',
704
+ '# astryx in a comment stays',
705
+ 'astryx doctor && astryx integration verify',
706
+ '$ astryx docs',
707
+ 'npm install @astryxdesign/cli',
708
+ '```',
709
+ '```tsx',
710
+ 'astryx docs',
711
+ '```',
712
+ ].join('\n');
713
+ expect(shown(text).split('\n')).toEqual([
714
+ '```bash',
715
+ '# astryx in a comment stays',
716
+ 'acme astryx doctor && acme astryx integration verify',
717
+ '$ acme astryx docs',
718
+ 'npm install @astryxdesign/cli',
719
+ '```',
720
+ '```tsx',
721
+ 'astryx docs',
722
+ '```',
723
+ ]);
724
+ });
725
+
726
+ it("follows one code block's language", () => {
727
+ expect(shown('astryx docs', 'bash')).toBe('acme astryx docs');
728
+ expect(shown('1. Read `astryx docs cli`.', 'text')).toBe(
729
+ '1. Read `acme astryx docs cli`.',
730
+ );
731
+ expect(shown("const run = 'astryx docs';", 'ts')).toBe(
732
+ "const run = 'astryx docs';",
733
+ );
734
+ });
735
+
736
+ it('leaves plain prose alone', () => {
737
+ expect(shown('Astryx and astryx themes')).toBe('Astryx and astryx themes');
738
+ });
739
+
740
+ it('changes nothing when the stem is the bare bin', () => {
741
+ expect(prefixCliCommands('`astryx docs`', 'astryx')).toBe('`astryx docs`');
742
+ });
743
+ });
744
+
745
+ describe('quoteCommandArgument', () => {
746
+ it('leaves a plain value bare', () => {
747
+ for (const platform of ['linux', 'darwin', 'win32']) {
748
+ expect(quoteCommandArgument('0.7.1', platform)).toBe('0.7.1');
749
+ expect(quoteCommandArgument('@acme/widgets', platform)).toBe(
750
+ '@acme/widgets',
751
+ );
752
+ expect(quoteCommandArgument('/home/me/app/src', platform)).toBe(
753
+ '/home/me/app/src',
754
+ );
755
+ }
756
+ });
757
+
758
+ it('single-quotes for a POSIX shell, so $, backticks and double quotes stay literal', () => {
759
+ expect(quoteCommandArgument('/tmp/my app/src', 'linux')).toBe(
760
+ "'/tmp/my app/src'",
761
+ );
762
+ expect(quoteCommandArgument('/tmp/cost $5/src', 'darwin')).toBe(
763
+ "'/tmp/cost $5/src'",
764
+ );
765
+ expect(quoteCommandArgument('a`id`"b"!', 'linux')).toBe('\'a`id`"b"!\'');
766
+ expect(quoteCommandArgument("it's", 'linux')).toBe("'it'\\''s'");
767
+ expect(quoteCommandArgument('C:\\app', 'linux')).toBe("'C:\\app'");
768
+ expect(quoteCommandArgument('', 'linux')).toBe("''");
769
+ });
770
+
771
+ it('keeps a Windows drive path bare', () => {
772
+ expect(quoteCommandArgument('C:\\Users\\me\\app\\src', 'win32')).toBe(
773
+ 'C:\\Users\\me\\app\\src',
774
+ );
775
+ });
776
+
777
+ // Windows output targets PowerShell, not cmd.exe: PowerShell reads a
778
+ // single-quoted value literally, `$`, backticks, `%` and `!` included.
779
+ it('single-quotes for PowerShell on Windows, doubling an embedded quote', () => {
780
+ expect(quoteCommandArgument('C:\\Users\\Jo Smith\\app\\src', 'win32')).toBe(
781
+ "'C:\\Users\\Jo Smith\\app\\src'",
782
+ );
783
+ expect(quoteCommandArgument('C:\\cost $5 %x% !y\\src', 'win32')).toBe(
784
+ "'C:\\cost $5 %x% !y\\src'",
785
+ );
786
+ expect(quoteCommandArgument('C:\\it\'s `x` "q"', 'win32')).toBe(
787
+ "'C:\\it''s `x` \"q\"'",
788
+ );
789
+ // PowerShell also ends a single-quoted string at ‘ ’ ‚ and ‛.
790
+ expect(
791
+ quoteCommandArgument('C:\\Users\\jo\\OneDrive - Macy’s\\app', 'win32'),
792
+ ).toBe("'C:\\Users\\jo\\OneDrive - Macy’’s\\app'");
793
+ expect(quoteCommandArgument('‘a’ ‚b‛', 'win32')).toBe("'‘‘a’’ ‚‚b‛‛'");
794
+ // A POSIX shell reads them literally inside single quotes.
795
+ expect(quoteCommandArgument('/home/jo/Macy’s app', 'linux')).toBe(
796
+ "'/home/jo/Macy’s app'",
797
+ );
798
+ });
799
+ });
800
+
801
+ describe('commandInFolder', () => {
802
+ it('enters the folder, then runs the command only if that worked, in a POSIX shell', () => {
803
+ expect(commandInFolder('/tmp/my app', 'npx x', 'linux')).toBe(
804
+ "cd '/tmp/my app' && npx x",
805
+ );
806
+ expect(commandInFolder('/srv/app', 'npx x', 'darwin')).toBe(
807
+ 'cd /srv/app && npx x',
808
+ );
809
+ });
810
+
811
+ it('enters the folder literally and stops if it cannot, in PowerShell', () => {
812
+ expect(commandInFolder('C:\\a[1] b', 'npx x', 'win32')).toBe(
813
+ "cd -LiteralPath 'C:\\a[1] b' -ErrorAction Stop; npx x",
814
+ );
815
+ });
816
+
817
+ it.skipIf(process.platform === 'win32')(
818
+ 'runs in the folder with every argument unchanged, pasted into /bin/sh elsewhere',
819
+ () => {
820
+ const root = makeTmpDir();
821
+ const project = path.join(root, 'it\'s Macy’s ‘q’ $5 %x% !y `z` "q" [1]');
822
+ fs.mkdirSync(project);
823
+ const args = [path.join(project, 'src'), 'a$b c', "it's", '@scope/pkg@1'];
824
+ const command = commandInFolder(
825
+ project,
826
+ ['npx', ...args.map(arg => quoteCommandArgument(arg, 'linux'))].join(
827
+ ' ',
828
+ ),
829
+ 'linux',
830
+ );
831
+ const ran = spawnSync(
832
+ '/bin/sh',
833
+ [
834
+ '-c',
835
+ `npx() { "$0" -e '${PRINT_RUN}' -- "$@"; }; ${command}`,
836
+ process.execPath,
837
+ ],
838
+ {cwd: root, encoding: 'utf8'},
839
+ );
840
+
841
+ expect(ran.status, ran.stderr).toBe(0);
842
+ expect(JSON.parse(ran.stdout)).toEqual({
843
+ cwd: fs.realpathSync(project),
844
+ argv: args,
845
+ });
846
+ },
847
+ );
848
+
849
+ describe.runIf(PWSH)('in PowerShell, where it is installed', () => {
850
+ it('runs in the folder with every argument unchanged', () => {
851
+ const root = makeTmpDir();
852
+ const project = path.join(root, 'it\'s Macy’s ‘q’ $5 %x% !y `z` "q" [1]');
853
+ fs.mkdirSync(project);
854
+ const args = [path.join(project, 'src'), 'a$b c', "it's", '@scope/pkg@1'];
855
+ const command = commandInFolder(
856
+ project,
857
+ ['npx', ...args.map(arg => quoteCommandArgument(arg, 'win32'))].join(
858
+ ' ',
859
+ ),
860
+ 'win32',
861
+ );
862
+ const npx = `function npx { & ${quoteCommandArgument(process.execPath, 'win32')} -e '${PRINT_RUN}' -- @args }`;
863
+ const ran = spawnSync(
864
+ /** @type {string} */ (PWSH),
865
+ ['-NoProfile', '-NonInteractive', '-Command', `${npx}; ${command}`],
866
+ {cwd: root, encoding: 'utf8'},
867
+ );
868
+
869
+ expect(ran.status, ran.stderr).toBe(0);
870
+ expect(JSON.parse(ran.stdout)).toEqual({
871
+ cwd: fs.realpathSync(project),
872
+ argv: args,
873
+ });
874
+ });
875
+
876
+ it('stops when the folder cannot be entered', () => {
877
+ const root = makeTmpDir();
878
+ const command = commandInFolder(
879
+ path.join(root, 'missing'),
880
+ 'npx',
881
+ 'win32',
882
+ );
883
+ const ran = spawnSync(
884
+ /** @type {string} */ (PWSH),
885
+ [
886
+ '-NoProfile',
887
+ '-NonInteractive',
888
+ '-Command',
889
+ `function npx { 'RAN' }; ${command}`,
890
+ ],
891
+ {cwd: root, encoding: 'utf8'},
892
+ );
893
+
894
+ expect(ran.stdout).not.toContain('RAN');
895
+ });
570
896
  });
571
897
  });
@@ -0,0 +1,9 @@
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
+ * True when `cwd` is outside any project.
6
+ * @param {string} [cwd]
7
+ * @returns {boolean}
8
+ */
9
+ export function isOutsideProject(cwd?: string): boolean;