@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
@@ -24,6 +24,7 @@ export const doc = {
24
24
  {label: 'List posts', cli: 'astryx blog'},
25
25
  {label: 'Read one post', cli: 'astryx blog introducing-astryx --json'},
26
26
  ],
27
+ outsideProject: 'same',
27
28
  exitCodes: [
28
29
  {code: 0, when: 'success'},
29
30
  {code: 1, when: 'unknown slug, or the feed or a post cannot be fetched'},
@@ -91,12 +91,24 @@ function count(text, pattern) {
91
91
 
92
92
  /** @param {string} stdout */
93
93
  function lines(stdout) {
94
- return stdout.split('\n').filter(Boolean);
94
+ return bareRecipe(stdout).split('\n').filter(Boolean);
95
+ }
96
+
97
+ /**
98
+ * The font recipe names `docs typography` with the project's CLI invocation,
99
+ * which depends on where the test runs; compare it as bare `astryx`.
100
+ * @param {string} stdout
101
+ */
102
+ function bareRecipe(stdout) {
103
+ return stdout.replace(
104
+ /(recipe: |guidance: )\S.*? docs typography/g,
105
+ '$1astryx docs typography',
106
+ );
95
107
  }
96
108
 
97
109
  /** @param {string} stdout The sizes follow Core's CSS, so goldens keep the shape. */
98
110
  function normalized(stdout) {
99
- return stdout.replace(/\d+(\.\d+)? KB/g, '<size> KB');
111
+ return bareRecipe(stdout).replace(/\d+(\.\d+)? KB/g, '<size> KB');
100
112
  }
101
113
 
102
114
  /** Bytes of the full report printed by one invocation per theme. */
@@ -168,7 +180,9 @@ describe('theme build report', () => {
168
180
  }
169
181
  expect(count(out, 'Install in your app')).toBe(1);
170
182
  expect(count(out, 'Or with a <link> tag:')).toBe(1);
171
- expect(count(out, "import { Theme } from '@astryxdesign/core';")).toBe(2);
183
+ expect(
184
+ count(out, "import { Theme } from '@astryxdesign/core/theme';"),
185
+ ).toBe(2);
172
186
  expect(count(out, '[note]')).toBe(1);
173
187
  expect(count(out, 'note: Font')).toBe(0);
174
188
  expect(count(out, 'fonts.googleapis.com/css2')).toBe(1);
@@ -75,7 +75,7 @@ describe('theme build font-loading warning', () => {
75
75
  expect(result.stdout).toContain('display=swap');
76
76
  expect(result.stdout).toContain('@font-face');
77
77
  expect(result.stdout).toContain('font-display: swap');
78
- expect(result.stdout).toContain('astryx docs typography');
78
+ expect(result.stdout).toContain(' docs typography');
79
79
  // The one-line summaries follow the CLI's stream contract. These are
80
80
  // NOTICES about a correct theme, not warnings, so they go to stdout with
81
81
  // the rest of the build's progress — stderr stays for the defects an
@@ -105,8 +105,8 @@ describe('theme build font-loading warning', () => {
105
105
  );
106
106
 
107
107
  expect(result.code).toBe(0);
108
- expect(result.stdout).toContain(
109
- '[note] Fonts named but not loaded: "Space Grotesk", "JetBrains Mono". Load them in your app (recipe: astryx docs typography).\n',
108
+ expect(result.stdout).toMatch(
109
+ /\[note\] Fonts named but not loaded: "Space Grotesk", "JetBrains Mono"\. Load them in your app \(recipe: .+ docs typography\)\.\n/,
110
110
  );
111
111
  expect(result.stdout).not.toContain('fonts.googleapis.com');
112
112
  expect(result.stdout).not.toContain('@font-face');
@@ -34,6 +34,7 @@ import * as path from 'node:path';
34
34
  import {fileURLToPath} from 'node:url';
35
35
  import {spawn} from 'node:child_process';
36
36
  import {getCliInvocation} from '../../../foundation/env/package-manager.mjs';
37
+ import {isOutsideProject} from '../../../foundation/env/project-scope.mjs';
37
38
  import {jsonOut} from '../../../foundation/response/json.mjs';
38
39
  import {
39
40
  emit,
@@ -224,10 +225,13 @@ async function runThemeBuildWatch(entries, options) {
224
225
  emit(text(watchingLine));
225
226
 
226
227
  await new Promise((/** @type {(value?: void) => void} */ resolve) => {
228
+ let stopping = false;
227
229
  /** @param {NodeJS.Signals} signal */
228
230
  const stop = async signal => {
229
- process.removeListener('SIGINT', stop);
230
- process.removeListener('SIGTERM', stop);
231
+ // A second interrupt while stopping (a terminal's Ctrl-C and the same
232
+ // signal passed on by the copy that started the run) changes nothing.
233
+ if (stopping) return;
234
+ stopping = true;
231
235
  stopped = true;
232
236
  queued.clear();
233
237
  for (const d of debounces.values()) clearTimeout(d);
@@ -237,10 +241,12 @@ async function runThemeBuildWatch(entries, options) {
237
241
  rebuildChild?.kill(signal);
238
242
  await rebuildDone;
239
243
  emit(text('\nStopped watching.'));
244
+ process.removeListener('SIGINT', stop);
245
+ process.removeListener('SIGTERM', stop);
240
246
  resolve();
241
247
  };
242
- process.once('SIGINT', stop);
243
- process.once('SIGTERM', stop);
248
+ process.on('SIGINT', stop);
249
+ process.on('SIGTERM', stop);
244
250
  });
245
251
  }
246
252
 
@@ -283,14 +289,24 @@ function printThemeAddList(themes) {
283
289
  return theme.description ? [head, theme.description] : head;
284
290
  }),
285
291
  ),
286
- text(
287
- `Import one: ${run} theme add <slug> [--package <package>]\n` +
288
- `Fork source: ${run} theme eject <slug> [target-path]`,
289
- ),
292
+ text(themeNextSteps(run)),
290
293
  more,
291
294
  );
292
295
  }
293
296
 
297
+ /**
298
+ * What to do with a listed theme. Outside a project there is no app to add one
299
+ * to, so only the fork step is named (spec AST-081 FR3).
300
+ * @param {string} run the CLI invocation
301
+ * @returns {string}
302
+ */
303
+ function themeNextSteps(run) {
304
+ const fork = `Fork source: ${run} theme eject <slug> [target-path]`;
305
+ return isOutsideProject()
306
+ ? fork
307
+ : `Import one: ${run} theme add <slug> [--package <package>]\n${fork}`;
308
+ }
309
+
294
310
  /**
295
311
  * Print every field in the `theme.list` entries. Duplicate slugs stay separate
296
312
  * because each record names its owner.
@@ -325,10 +341,7 @@ function printThemeList(themes, unmigratedCopies = []) {
325
341
  default: (/** @type {boolean} */ value) => (value ? 'yes' : 'no'),
326
342
  },
327
343
  }),
328
- text(
329
- `Import one: ${run} theme add <slug> [--package <package>]\n` +
330
- `Fork source: ${run} theme eject <slug> [target-path]`,
331
- ),
344
+ text(themeNextSteps(run)),
332
345
  more,
333
346
  );
334
347
  }
@@ -397,7 +410,7 @@ function printThemeApp(result) {
397
410
  emit(
398
411
  text('Wire the generated module once in the app root:'),
399
412
  code(
400
- "import {Theme} from '@astryxdesign/core';\n" +
413
+ "import {Theme} from '@astryxdesign/core/theme';\n" +
401
414
  `import {themes, defaultThemeSlug} from './${stem}';\n\n` +
402
415
  '<Theme theme={themes[defaultThemeSlug]}>\n <App />\n</Theme>',
403
416
  ),
@@ -9,6 +9,12 @@
9
9
  * - Multi-file write is atomic: if one file fails, none are left behind.
10
10
  * - An `--out` escape, or an output directory that cannot be created, fails
11
11
  * with a registered error code.
12
+ * - Every output a build writes stays inside the working directory: an
13
+ * absolute `--out` path or a theme file outside it fails before anything
14
+ * is written, while absolute paths inside it build, also through a
15
+ * symlinked spelling. `--check` writes nothing, so it compares those
16
+ * outside outputs read-only, and a stale report for them ends with a
17
+ * remedy that works: the directory to build and check from.
12
18
  *
13
19
  * Runs the CLI in-process (shared runCli harness) against a tiny synthetic theme file.
14
20
  */
@@ -22,6 +28,7 @@ import {runCli} from '../../../test-utils/run-cli.mjs';
22
28
  import {themeBuild} from '../../../api/theme/build/build.mjs';
23
29
  import {AstryxError} from '../../../api/error.mjs';
24
30
  import {ERROR_CODES} from '../../../foundation/response/error-codes.mjs';
31
+ import {getCliInvocation} from '../../../foundation/env/package-manager.mjs';
25
32
 
26
33
  function writeTheme(dir, name) {
27
34
  fs.mkdirSync(dir, {recursive: true});
@@ -59,7 +66,9 @@ describe('theme build path safety', () => {
59
66
  );
60
67
 
61
68
  expect(result.code).not.toBe(0);
62
- expect(result.stderr + result.stdout).toMatch(/path separator|invalid theme name/i);
69
+ expect(result.stderr + result.stdout).toMatch(
70
+ /path separator|invalid theme name/i,
71
+ );
63
72
 
64
73
  // The traversal target must not exist.
65
74
  expect(fs.existsSync(path.join(tmpDir, 'escaped.js'))).toBe(false);
@@ -92,7 +101,10 @@ describe('theme build path safety', () => {
92
101
 
93
102
  const themeFile = writeTheme(themesDir, '../leak');
94
103
 
95
- await runCli(['theme', 'build', path.relative(project, themeFile)], project);
104
+ await runCli(
105
+ ['theme', 'build', path.relative(project, themeFile)],
106
+ project,
107
+ );
96
108
 
97
109
  // Neither the CSS nor any sibling JS should have been written.
98
110
  const entries = fs.readdirSync(themesDir).sort();
@@ -114,19 +126,200 @@ describe('theme build output-path failures carry registered codes', () => {
114
126
  return {result, envelope: JSON.parse(result.stdout)};
115
127
  }
116
128
 
117
- it('reports an --out path outside the project as ERR_PATH_TRAVERSAL', async () => {
129
+ it('reports a relative --out path outside the project as ERR_PATH_TRAVERSAL, with --check too', async () => {
118
130
  const project = path.join(tmpDir, 'project');
119
131
  const themeFile = writeTheme(path.join(project, 'themes'), 'ocean');
120
132
 
133
+ for (const extra of [[], ['--check']]) {
134
+ const {result, envelope} = await buildJson(
135
+ [
136
+ path.relative(project, themeFile),
137
+ '--out',
138
+ '../escaped.css',
139
+ ...extra,
140
+ ],
141
+ project,
142
+ );
143
+
144
+ expect(result.code).toBe(1);
145
+ expect(envelope.code).toBe(ERROR_CODES.ERR_PATH_TRAVERSAL);
146
+ expect(registered.has(envelope.code)).toBe(true);
147
+ }
148
+ expect(fs.existsSync(path.join(tmpDir, 'escaped.css'))).toBe(false);
149
+ });
150
+
151
+ it('reports an absolute --out path outside the project as ERR_PATH_TRAVERSAL', async () => {
152
+ const project = path.join(tmpDir, 'project');
153
+ const outside = path.join(tmpDir, 'outside');
154
+ fs.mkdirSync(outside, {recursive: true});
155
+ const themeFile = writeTheme(path.join(project, 'themes'), 'ocean');
156
+ const out = path.join(outside, 'ocean.css');
157
+
121
158
  const {result, envelope} = await buildJson(
122
- [path.relative(project, themeFile), '--out', '../escaped.css'],
159
+ [path.relative(project, themeFile), '--out', out],
123
160
  project,
124
161
  );
125
162
 
126
163
  expect(result.code).toBe(1);
127
164
  expect(envelope.code).toBe(ERROR_CODES.ERR_PATH_TRAVERSAL);
128
- expect(registered.has(envelope.code)).toBe(true);
129
- expect(fs.existsSync(path.join(tmpDir, 'escaped.css'))).toBe(false);
165
+ expect(fs.readdirSync(outside)).toEqual([]);
166
+ });
167
+
168
+ it('checks an absolute --out path outside the project read-only', async () => {
169
+ const project = path.join(tmpDir, 'project');
170
+ const outside = path.join(tmpDir, 'outside');
171
+ fs.mkdirSync(outside, {recursive: true});
172
+ const themeFile = path.relative(
173
+ project,
174
+ writeTheme(path.join(project, 'themes'), 'ocean'),
175
+ );
176
+ const out = path.join(outside, 'ocean.css');
177
+
178
+ const missing = await buildJson(
179
+ [themeFile, '--out', out, '--check'],
180
+ project,
181
+ );
182
+ expect(missing.result.code).toBe(1);
183
+ expect(missing.envelope.type).toBe('theme.build.check');
184
+ expect(missing.envelope.data.upToDate).toBe(false);
185
+ expect(fs.readdirSync(outside)).toEqual([]);
186
+
187
+ // Outputs built inside the project compare equal once copied outside:
188
+ // only the volatile Command line, which --check ignores, names --out.
189
+ const dist = path.join(project, 'dist');
190
+ const built = await buildJson(
191
+ [themeFile, '--out', path.join(dist, 'ocean.css')],
192
+ project,
193
+ );
194
+ expect(built.result.code).toBe(0);
195
+ for (const file of fs.readdirSync(dist)) {
196
+ fs.copyFileSync(path.join(dist, file), path.join(outside, file));
197
+ }
198
+ const copied = fs.readdirSync(outside).sort();
199
+
200
+ const current = await buildJson(
201
+ [themeFile, '--out', out, '--check'],
202
+ project,
203
+ );
204
+ expect(current.result.code).toBe(0);
205
+ expect(current.envelope.type).toBe('theme.build.check');
206
+ expect(current.envelope.data.upToDate).toBe(true);
207
+ expect(fs.readdirSync(outside).sort()).toEqual(copied);
208
+ });
209
+
210
+ it('reports a theme file outside the project as ERR_PATH_TRAVERSAL, because its outputs go beside it; --check only compares them', async () => {
211
+ const project = path.join(tmpDir, 'project');
212
+ fs.mkdirSync(project, {recursive: true});
213
+ const shared = path.join(tmpDir, 'shared');
214
+ const themeFile = writeTheme(shared, 'ocean');
215
+
216
+ for (const file of [themeFile, path.relative(project, themeFile)]) {
217
+ const {result, envelope} = await buildJson([file], project);
218
+
219
+ expect(result.code).toBe(1);
220
+ expect(envelope.code).toBe(ERROR_CODES.ERR_PATH_TRAVERSAL);
221
+
222
+ const checked = await buildJson([file, '--check'], project);
223
+ expect(checked.result.code).toBe(1);
224
+ expect(checked.envelope.type).toBe('theme.build.check');
225
+ expect(checked.envelope.data.upToDate).toBe(false);
226
+ }
227
+ expect(fs.readdirSync(shared)).toEqual(['theme.mjs']);
228
+ });
229
+
230
+ /**
231
+ * Read the remedy that ends a stale report for outputs outside cwd: the
232
+ * directory to run from, then the build and check arguments after `theme
233
+ * build`. Each command starts with the install-aware invocation for it.
234
+ * @param {string} stderr
235
+ */
236
+ function readRemedy(stderr) {
237
+ const lines = stderr.split('\n').map(line => line.trim());
238
+ const cd = lines.find(line => line.startsWith('cd '));
239
+ expect(cd).toBeDefined();
240
+ const dir = /** @type {string} */ (cd).slice('cd '.length);
241
+ const stem = `${getCliInvocation(dir)} theme build `;
242
+ const commands = lines.filter(line => line.startsWith(stem));
243
+ expect(commands).toHaveLength(2);
244
+ const [build, check] = commands.map(line =>
245
+ line.slice(stem.length).split(' '),
246
+ );
247
+ expect(check).toEqual([...build, '--check']);
248
+ return {dir, build, check};
249
+ }
250
+
251
+ it('ends a stale report for outputs outside cwd with a remedy that works, and keeps the report for outputs inside it', async () => {
252
+ const project = path.join(tmpDir, 'project');
253
+ const inside = path.relative(
254
+ project,
255
+ writeTheme(path.join(project, 'themes'), 'ocean'),
256
+ );
257
+ const shared = writeTheme(path.join(tmpDir, 'shared'), 'ocean');
258
+ const outside = path.join(tmpDir, 'outside');
259
+
260
+ for (const args of [
261
+ [path.relative(project, shared)],
262
+ [inside, '--out', path.join(outside, 'ocean.css')],
263
+ ]) {
264
+ const stale = await runCli(
265
+ ['theme', 'build', ...args, '--check'],
266
+ project,
267
+ );
268
+ expect(stale.code).toBe(1);
269
+ // The old remedy ran the build from cwd, which refuses these outputs.
270
+ expect(stale.stderr).not.toContain('Rebuild with:');
271
+ expect(stale.stderr).not.toContain(`theme build ${args.join(' ')}`);
272
+
273
+ // Following the remedy builds, and the check from there passes.
274
+ const remedy = readRemedy(stale.stderr);
275
+ const built = await runCli(
276
+ ['theme', 'build', ...remedy.build],
277
+ remedy.dir,
278
+ );
279
+ expect(built.code).toBe(0);
280
+ const checked = await runCli(
281
+ ['--json', 'theme', 'build', ...remedy.check],
282
+ remedy.dir,
283
+ );
284
+ expect(checked.code).toBe(0);
285
+ expect(JSON.parse(checked.stdout).data.upToDate).toBe(true);
286
+ }
287
+
288
+ // Outputs inside cwd keep the plain rebuild line.
289
+ const local = await runCli(['theme', 'build', inside, '--check'], project);
290
+ expect(local.code).toBe(1);
291
+ expect(local.stderr).toContain(
292
+ `Rebuild with: astryx theme build ${inside}`,
293
+ );
294
+ expect(local.stderr).not.toMatch(/^\s*cd /m);
295
+ });
296
+
297
+ it('builds absolute paths inside the project, also when they reach it through a symlink', async () => {
298
+ const project = path.join(tmpDir, 'project');
299
+ const themeFile = writeTheme(path.join(project, 'themes'), 'ocean');
300
+ const alias = path.join(tmpDir, 'alias');
301
+ fs.symlinkSync(project, alias, 'dir');
302
+
303
+ for (const [args, written] of [
304
+ [
305
+ [themeFile, '--out', path.join(project, 'dist', 'direct.css')],
306
+ path.join(project, 'dist', 'direct.css'),
307
+ ],
308
+ [
309
+ [themeFile, '--out', path.join(alias, 'dist', 'aliased.css')],
310
+ path.join(project, 'dist', 'aliased.css'),
311
+ ],
312
+ [
313
+ [path.join(alias, 'themes', 'theme.mjs')],
314
+ path.join(project, 'themes', 'ocean.css'),
315
+ ],
316
+ ]) {
317
+ const {result, envelope} = await buildJson(args, project);
318
+
319
+ expect(result.code).toBe(0);
320
+ expect(envelope.type).toBe('theme.build');
321
+ expect(fs.existsSync(written)).toBe(true);
322
+ }
130
323
  });
131
324
 
132
325
  it('reports an --out path that escapes through a symlink as ERR_PATH_TRAVERSAL', async () => {
@@ -177,4 +370,29 @@ describe('theme build output-path failures carry registered codes', () => {
177
370
  code: ERROR_CODES.ERR_PATH_TRAVERSAL,
178
371
  });
179
372
  });
373
+
374
+ it('themeBuild() rejects an output outside cwd, an absolute out path or the default beside a theme file outside cwd, and checks it read-only', async () => {
375
+ const project = path.join(tmpDir, 'project');
376
+ const inside = writeTheme(path.join(project, 'themes'), 'ocean');
377
+ const shared = path.join(tmpDir, 'shared');
378
+ const outsideTheme = writeTheme(shared, 'ocean');
379
+ const outDir = path.join(tmpDir, 'out');
380
+
381
+ for (const [file, options] of [
382
+ [path.relative(project, inside), {out: path.join(outDir, 'ocean.css')}],
383
+ [outsideTheme, {}],
384
+ ]) {
385
+ await expect(
386
+ themeBuild(file, options, {cwd: project}),
387
+ ).rejects.toMatchObject({code: ERROR_CODES.ERR_PATH_TRAVERSAL});
388
+ await expect(
389
+ themeBuild(file, {...options, check: true}, {cwd: project}),
390
+ ).resolves.toMatchObject({
391
+ type: 'theme.build.check',
392
+ data: {upToDate: false},
393
+ });
394
+ }
395
+ expect(fs.existsSync(outDir)).toBe(false);
396
+ expect(fs.readdirSync(shared)).toEqual(['theme.mjs']);
397
+ });
180
398
  });
@@ -126,7 +126,10 @@ describe('theme build emits a live TextInput selector (#4109)', () => {
126
126
  `};\n`,
127
127
  );
128
128
 
129
- const result = await runCli(['theme', 'build', themeFile, '-o', outFile]);
129
+ const result = await runCli(
130
+ ['theme', 'build', themeFile, '-o', outFile],
131
+ tmpDir,
132
+ );
130
133
  const css = fs.readFileSync(outFile, 'utf8');
131
134
 
132
135
  expect(result.status).toBe(0);
@@ -148,7 +151,7 @@ describe('theme build emits a live TextInput selector (#4109)', () => {
148
151
  `};\n`,
149
152
  );
150
153
 
151
- await runCli(['theme', 'build', themeFile, '-o', outFile]);
154
+ await runCli(['theme', 'build', themeFile, '-o', outFile], tmpDir);
152
155
  const css = fs.readFileSync(outFile, 'utf8');
153
156
 
154
157
  expect(css).toContain('.astryx-text-input');
@@ -47,6 +47,7 @@ export const doc = {
47
47
  cli: 'astryx build "analytics dashboard" --json',
48
48
  },
49
49
  ],
50
+ outsideProject: 'same',
50
51
  exitCodes: [
51
52
  {code: 0, when: 'success (including zero matches)'},
52
53
  {
@@ -11,6 +11,7 @@ import * as os from 'node:os';
11
11
  import * as path from 'node:path';
12
12
  import {runCli} from '../../../test-utils/run-cli.mjs';
13
13
  import {doc as buildDoc} from './build.doc.mjs';
14
+ import {linkThisCliAsProjectCli} from '../../../test-utils/release-fixtures.mjs';
14
15
 
15
16
  const SLOW = 30_000;
16
17
 
@@ -20,6 +21,9 @@ let tmpDir;
20
21
  beforeEach(() => {
21
22
  tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'astryx-build-no-core-'));
22
23
  fs.writeFileSync(path.join(tmpDir, 'package.json'), JSON.stringify({name: 'consumer'}));
24
+ // A project whose own CLI answers and that installs no Core: outside a
25
+ // project a copy answers from the Core it carries instead (AST-081 FR3).
26
+ linkThisCliAsProjectCli(tmpDir);
23
27
  });
24
28
 
25
29
  afterEach(() => {
@@ -8,9 +8,16 @@
8
8
  * transient fetch (path _npx or npm_command=exec), and once set up.
9
9
  */
10
10
 
11
- import {describe, it, expect} from 'vitest';
11
+ import {describe, it, expect, beforeEach, afterEach} from 'vitest';
12
+ import {spawnSync} from 'node:child_process';
13
+ import * as fs from 'node:fs';
14
+ import * as os from 'node:os';
15
+ import * as path from 'node:path';
16
+ import {fileURLToPath} from 'node:url';
12
17
  import {shouldNudge} from '../../../scripts/postinstall.mjs';
13
18
 
19
+ const CLI = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '../../..');
20
+
14
21
  const DEP = '/proj/node_modules/@astryxdesign/cli/scripts/postinstall.mjs'; // real dep install
15
22
  const NPX = '/Users/x/.npm/_npx/a1b2/node_modules/@astryxdesign/cli/scripts/postinstall.mjs';
16
23
  const REPO = '/repo/packages/cli/scripts/postinstall.mjs'; // monorepo/source
@@ -40,3 +47,44 @@ describe('cli postinstall — shouldNudge', () => {
40
47
  expect(shouldNudge({})).toBe(false);
41
48
  });
42
49
  });
50
+
51
+ // Install time, end to end: the real script and the leaf it loads, installed
52
+ // under a monorepo's node_modules, run the way npm runs them for a package.
53
+ describe('cli postinstall — inside a monorepo package', () => {
54
+ let repo;
55
+ beforeEach(() => {
56
+ repo = fs.mkdtempSync(path.join(os.tmpdir(), 'astryx-cli-postinstall-'));
57
+ fs.mkdirSync(path.join(repo, '.git'));
58
+ fs.mkdirSync(path.join(repo, 'packages/app'), {recursive: true});
59
+ fs.writeFileSync(path.join(repo, 'packages/app/package.json'), '{"name":"app"}');
60
+ const installed = path.join(repo, 'node_modules/@astryxdesign/cli');
61
+ for (const rel of ['scripts/postinstall.mjs', 'foundation/agent-docs/agent-doc-state.mjs']) {
62
+ fs.mkdirSync(path.dirname(path.join(installed, rel)), {recursive: true});
63
+ fs.copyFileSync(path.join(CLI, rel), path.join(installed, rel));
64
+ }
65
+ });
66
+ afterEach(() => fs.rmSync(repo, {recursive: true, force: true}));
67
+
68
+ const install = () =>
69
+ spawnSync(
70
+ process.execPath,
71
+ [path.join(repo, 'node_modules/@astryxdesign/cli/scripts/postinstall.mjs')],
72
+ {
73
+ env: {...process.env, INIT_CWD: path.join(repo, 'packages/app'), npm_command: 'install'},
74
+ encoding: 'utf8',
75
+ },
76
+ );
77
+
78
+ it('stays quiet when init ran at the repository root', () => {
79
+ fs.writeFileSync(path.join(repo, 'AGENTS.md'), '<!-- ASTRYX:START -->\n');
80
+ const r = install();
81
+ expect(r.status).toBe(0);
82
+ expect(r.stdout).toBe('');
83
+ });
84
+
85
+ it('still nudges when no folder up to the repository root ran init', () => {
86
+ const r = install();
87
+ expect(r.status).toBe(0);
88
+ expect(r.stdout).toMatch(/finish setup and install the Astryx agent prompt/);
89
+ });
90
+ });
@@ -90,6 +90,7 @@ export const doc = {
90
90
  cli: 'astryx component SideNav --package @astryxdesign/core',
91
91
  },
92
92
  ],
93
+ outsideProject: 'same',
93
94
  exitCodes: [
94
95
  {code: 0, when: 'success'},
95
96
  {
@@ -68,8 +68,8 @@ describe('astryx discover with a manifest that fails to load', () => {
68
68
  const {status, stdout, stderr} = await runCli(['discover'], {cwd: project});
69
69
 
70
70
  expect(status).toBe(0);
71
- expect(stderr).toContain(
72
- 'Warning: @test/broken has 1 integration issue(s). Run: astryx doctor',
71
+ expect(stderr).toMatch(
72
+ /Warning: @test\/broken has 1 integration issue\(s\)\. Run: .+ doctor/,
73
73
  );
74
74
  expect(stdout).not.toContain('No integrations configured.');
75
75
  expect(stdout).toContain(
@@ -83,7 +83,11 @@ describe('astryx discover with a manifest that fails to load', () => {
83
83
  });
84
84
 
85
85
  expect(status).toBe(0);
86
- expect(JSON.parse(stdout).meta).toEqual({configured: true});
86
+ const {meta} = JSON.parse(stdout);
87
+ expect(meta.configured).toBe(true);
88
+ // The official list still answers; nothing else is claimed.
89
+ expect(meta.sources.map(s => s.name)).toEqual(['Astryx']);
90
+ expect(meta.answeredBy).toEqual(expect.any(Object));
87
91
  expect(stderr).not.toContain('integration issue');
88
92
  });
89
93
  });
@@ -129,8 +133,8 @@ describe('astryx discover with a configured package that is not installed', () =
129
133
  const {status, stderr} = await runCli(['discover'], {cwd: project});
130
134
 
131
135
  expect(status).toBe(0);
132
- expect(stderr).toContain(
133
- 'Warning: @test/missing has 1 integration issue(s). Run: astryx doctor\n',
136
+ expect(stderr).toMatch(
137
+ /Warning: @test\/missing has 1 integration issue\(s\)\. Run: .+ doctor\n/,
134
138
  );
135
139
  });
136
140
  });
@@ -148,8 +152,8 @@ describe('astryx search with a manifest that fails to load', () => {
148
152
  const {status, stderr} = await runCli(['search', 'button'], {cwd: project});
149
153
 
150
154
  expect(status).toBe(0);
151
- expect(stderr).toContain(
152
- 'Warning: @test/broken has 1 integration issue(s). Run: astryx doctor',
155
+ expect(stderr).toMatch(
156
+ /Warning: @test\/broken has 1 integration issue\(s\)\. Run: .+ doctor/,
153
157
  );
154
158
 
155
159
  const asJson = await runCli(['search', 'button', '--json'], {cwd: project});
@@ -17,7 +17,8 @@ export const doc = {
17
17
  summary:
18
18
  'Browse and search integrations: the ones you have and the ones you could add',
19
19
  description:
20
- 'Lists the integrations this project loads and, when it has a discover source, the ones it could add, with what each one adds. ' +
20
+ 'Lists what this project has, the integrations it loads and the official packages it declares, then what it could add: the official Astryx packages first, then whatever its discover sources list, with what each one adds. ' +
21
+ 'Inside a project, integrations installed for the computer, beside the CLI installed for it, are listed as ones the project could add, with its install command; outside one they load. ' +
21
22
  'A package query shows one package: what it adds, every version a source knows, and the command that adds it. ' +
22
23
  "An @scope/name/Component path prints an installed component's doc; any other item path shows that item. " +
23
24
  'Anything else, even an exact component name, searches every item and package and lists the matches. Discover only reads: your package manager installs, updates, and removes packages.',
@@ -78,6 +79,7 @@ export const doc = {
78
79
  cli: 'astryx discover dashboard --type template --available',
79
80
  },
80
81
  ],
82
+ outsideProject: 'same',
81
83
  exitCodes: [
82
84
  {code: 0, when: 'success'},
83
85
  {
@@ -86,4 +88,10 @@ export const doc = {
86
88
  },
87
89
  ],
88
90
  related: ['component', 'search', 'template'],
91
+ notes: [
92
+ {
93
+ type: 'prose',
94
+ text: "In global mode: outside a project, integrations installed next to your global install are listed as installed and their items show up; inside a project, only the project's integrations are, and the global ones the project could add are listed under available, with the project's install command. In a project, the project's version of Astryx runs it, whichever astryx command you start. See {@link namespace:global} and {@link generic:computer-integrations}.",
95
+ },
96
+ ],
89
97
  };