@astryxdesign/cli 0.7.2-canary.fdd384f → 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 (450) hide show
  1. package/CHANGELOG.md +119 -0
  2. package/README.md +169 -149
  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/kit/kit.mjs +3 -11
  7. package/api/component/_adapter.d.mts +31 -0
  8. package/api/component/_adapter.mjs +119 -11
  9. package/api/component/component.doc.mjs +6 -1
  10. package/api/component/component.mjs +60 -10
  11. package/api/component/doc-links.test.mjs +386 -0
  12. package/api/discover/_adapter.d.mts +132 -12
  13. package/api/discover/_adapter.mjs +488 -27
  14. package/api/discover/_adapter.test.mjs +599 -17
  15. package/api/discover/_catalog-view.d.mts +16 -5
  16. package/api/discover/_catalog-view.mjs +32 -8
  17. package/api/discover/detail/doc/doc.mjs +9 -1
  18. package/api/discover/discover.d.mts +11 -0
  19. package/api/discover/discover.doc.mjs +9 -7
  20. package/api/discover/discover.mjs +121 -19
  21. package/api/discover/discover.type.d.mts +20 -7
  22. package/api/discover/discover.type.mjs +17 -7
  23. package/api/discover/list/list.d.mts +4 -3
  24. package/api/discover/list/list.mjs +17 -10
  25. package/api/docs/_adapter.d.mts +63 -0
  26. package/api/docs/_adapter.mjs +312 -18
  27. package/api/docs/docs.doc.mjs +2 -1
  28. package/api/docs/docs.mjs +2 -1
  29. package/api/docs/docs.test.mjs +5 -2
  30. package/api/docs/integration-tree.test.mjs +22 -0
  31. package/api/docs/list/list.mjs +12 -2
  32. package/api/docs/reference-blocks.test.mjs +1 -3
  33. package/api/doctor/_adapter.d.mts +108 -0
  34. package/api/doctor/_adapter.mjs +223 -4
  35. package/api/doctor/doctor.d.mts +199 -0
  36. package/api/doctor/doctor.doc.mjs +3 -2
  37. package/api/doctor/doctor.mjs +530 -34
  38. package/api/doctor/doctor.test.mjs +180 -14
  39. package/api/doctor/release-checks.test.mjs +156 -0
  40. package/api/doctor/theme-checks.mjs +16 -3
  41. package/api/doctor/theme-checks.test.mjs +223 -7
  42. package/api/error.d.mts +10 -0
  43. package/api/error.mjs +25 -0
  44. package/api/gap-report/gap-report.d.mts +3 -0
  45. package/api/gap-report/gap-report.doc.mjs +11 -3
  46. package/api/gap-report/gap-report.mjs +152 -48
  47. package/api/gap-report/gap-report.test.mjs +233 -0
  48. package/api/gap-report/gap-report.type.d.mts +41 -0
  49. package/api/gap-report/gap-report.type.mjs +18 -0
  50. package/api/hook/_adapter.mjs +3 -11
  51. package/api/hook/hook.doc.mjs +4 -0
  52. package/api/hook/list/list.d.mts +1 -1
  53. package/api/init/_adapter.d.mts +22 -0
  54. package/api/init/_adapter.mjs +59 -0
  55. package/api/init/init.d.mts +7 -5
  56. package/api/init/init.doc.mjs +28 -7
  57. package/api/init/init.mjs +28 -4
  58. package/api/init/init.type.d.mts +48 -3
  59. package/api/init/init.type.mjs +37 -3
  60. package/api/init/run/run.d.mts +1 -1
  61. package/api/init/run/run.mjs +15 -4
  62. package/api/init/user/user.d.mts +14 -0
  63. package/api/init/user/user.mjs +87 -0
  64. package/api/init/user/user.test.mjs +232 -0
  65. package/api/integration/_adapter.d.mts +33 -0
  66. package/api/integration/_adapter.mjs +58 -0
  67. package/api/integration/authoring-checks.mjs +100 -5
  68. package/api/integration/authoring-checks.test.mjs +769 -0
  69. package/api/integration/authoring-checks.type.d.mts +134 -0
  70. package/api/integration/authoring-checks.type.mjs +60 -1
  71. package/api/integration/integrationCheck.doc.mjs +1 -1
  72. package/api/integration/integrationComponentConflicts.doc.mjs +11 -3
  73. package/api/integration/integrationDocConflicts.doc.mjs +22 -5
  74. package/api/integration/pack-check.mjs +303 -13
  75. package/api/integration/pack-check.test.mjs +371 -10
  76. package/api/integration/validate-integration-fixes.test.mjs +67 -0
  77. package/api/integration/validate-integration.d.mts +8 -0
  78. package/api/integration/validate-integration.mjs +55 -9
  79. package/api/integration/validate-integration.test.mjs +77 -8
  80. package/api/integration/validate-integration.type.d.mts +8 -0
  81. package/api/integration/validate-integration.type.mjs +3 -1
  82. package/api/integration/validate-unread-theme-folders.test.mjs +3 -2
  83. package/api/integration/validateIntegration.doc.mjs +4 -2
  84. package/api/search/_adapter.d.mts +11 -0
  85. package/api/search/_adapter.mjs +25 -0
  86. package/api/search/search.d.mts +73 -13
  87. package/api/search/search.doc.mjs +8 -3
  88. package/api/search/search.domain-priority.test.mjs +108 -18
  89. package/api/search/search.mjs +541 -52
  90. package/api/search/search.test.mjs +143 -1
  91. package/api/search/search.type.d.mts +14 -1
  92. package/api/search/search.type.mjs +6 -1
  93. package/api/swizzle/_adapter.mjs +2 -2
  94. package/api/swizzle/copy/copy.mjs +1 -1
  95. package/api/template/template-integration.test.mjs +1 -1
  96. package/api/theme/_adapter.d.mts +16 -0
  97. package/api/theme/_adapter.mjs +32 -5
  98. package/api/theme/build/build.d.mts +2 -1
  99. package/api/theme/build/build.mjs +70 -14
  100. package/api/theme/build/build.packed-old-core.test.mjs +20 -3
  101. package/api/theme/build/font-warning.mjs +5 -3
  102. package/api/theme/build/font-warning.test.mjs +2 -1
  103. package/api/theme/remove/remove.test.mjs +2 -2
  104. package/api/theme/targets/targets.mjs +3 -10
  105. package/api/theme/themeTargets.doc.mjs +4 -0
  106. package/api/theme/use/use.test.mjs +1 -1
  107. package/api/upgrade/_adapter.d.mts +40 -2
  108. package/api/upgrade/_adapter.mjs +118 -5
  109. package/api/upgrade/run/cli-behind-core.test.mjs +438 -0
  110. package/api/upgrade/run/run.mjs +35 -2
  111. package/api/upgrade/status/status.d.mts +21 -5
  112. package/api/upgrade/status/status.mjs +45 -8
  113. package/api/upgrade/upgrade.doc.mjs +6 -3
  114. package/api/upgrade/upgrade.mjs +5 -1
  115. package/api/upgrade/upgrade.type.d.mts +23 -0
  116. package/api/upgrade/upgrade.type.mjs +16 -1
  117. package/assets/codemods/__tests__/registry.test.mjs +1 -0
  118. package/assets/codemods/registry.mjs +1 -0
  119. package/assets/codemods/runner.mjs +7 -1
  120. package/assets/codemods/transforms/next/index.mjs +1 -11
  121. package/assets/codemods/transforms/v0.7.2/index.mjs +20 -0
  122. package/assets/discover/official-packages.json +367 -0
  123. package/assets/docs/author-a-theme.doc.mjs +2 -2
  124. package/assets/docs/browser-support.doc.mjs +1 -1
  125. package/assets/docs/getting-started.doc.mjs +23 -3
  126. package/assets/docs/styling-overview.doc.mjs +1 -1
  127. package/assets/docs/theme.doc.mjs +1 -1
  128. package/assets/docs/tree/add-a-component.doc.mjs +12 -2
  129. package/assets/docs/tree/add-a-theme.doc.mjs +13 -3
  130. package/assets/docs/tree/add-a-topic.doc.mjs +54 -9
  131. package/assets/docs/tree/agent-guidance.doc.mjs +19 -5
  132. package/assets/docs/tree/block-template.doc.mjs +6 -18
  133. package/assets/docs/tree/build-the-template.doc.mjs +1 -1
  134. package/assets/docs/tree/building-blocks.doc.mjs +4 -0
  135. package/assets/docs/tree/check-the-props.doc.mjs +8 -3
  136. package/assets/docs/tree/check-your-docs.doc.mjs +30 -13
  137. package/assets/docs/tree/checks.doc.mjs +17 -17
  138. package/assets/docs/tree/ci-and-containers.doc.mjs +88 -0
  139. package/assets/docs/tree/cli-command.doc.mjs +142 -0
  140. package/assets/docs/tree/cli.doc.mjs +6 -0
  141. package/assets/docs/tree/codemods.doc.mjs +17 -7
  142. package/assets/docs/tree/commands.doc.mjs +7 -1
  143. package/assets/docs/tree/component-family.doc.mjs +3 -5
  144. package/assets/docs/tree/component-imports.doc.mjs +13 -3
  145. package/assets/docs/tree/components.doc.mjs +7 -1
  146. package/assets/docs/tree/computer-agent-guide.doc.mjs +160 -0
  147. package/assets/docs/tree/computer-integrations.doc.mjs +93 -0
  148. package/assets/docs/tree/configuration.doc.mjs +7 -3
  149. package/assets/docs/tree/debug-and-gap-reports.doc.mjs +41 -22
  150. package/assets/docs/tree/define-the-theme.doc.mjs +6 -5
  151. package/assets/docs/tree/describe-the-component.doc.mjs +2 -2
  152. package/assets/docs/tree/docs.doc.mjs +9 -3
  153. package/assets/docs/tree/document-the-template.doc.mjs +1 -1
  154. package/assets/docs/tree/document-the-theme.doc.mjs +3 -3
  155. package/assets/docs/tree/export-template-assets.doc.mjs +1 -1
  156. package/assets/docs/tree/extend-or-replace.doc.mjs +19 -9
  157. package/assets/docs/tree/fonts-and-assets.doc.mjs +2 -2
  158. package/assets/docs/tree/generate-a-palette.doc.mjs +7 -5
  159. package/assets/docs/tree/global-integrations.doc.mjs +124 -0
  160. package/assets/docs/tree/global.doc.mjs +131 -0
  161. package/assets/docs/tree/grade-template-with-agent.doc.mjs +5 -5
  162. package/assets/docs/tree/install-once.doc.mjs +105 -0
  163. package/assets/docs/tree/integrations.doc.mjs +13 -1
  164. package/assets/docs/tree/layout-spacing.doc.mjs +2 -3
  165. package/assets/docs/tree/layout.doc.mjs +6 -6
  166. package/assets/docs/tree/links.doc.mjs +6 -5
  167. package/assets/docs/tree/migration-setup.doc.mjs +3 -2
  168. package/assets/docs/tree/migration-verification.doc.mjs +0 -1
  169. package/assets/docs/tree/migration.doc.mjs +0 -1
  170. package/assets/docs/tree/monorepos.doc.mjs +84 -0
  171. package/assets/docs/tree/package-and-test.doc.mjs +1 -1
  172. package/assets/docs/tree/page-template.doc.mjs +3 -7
  173. package/assets/docs/tree/problems.doc.mjs +205 -0
  174. package/assets/docs/tree/project-global-or-both.doc.mjs +131 -0
  175. package/assets/docs/tree/publishing.doc.mjs +6 -6
  176. package/assets/docs/tree/quick-start.doc.mjs +52 -26
  177. package/assets/docs/tree/reference.doc.mjs +192 -0
  178. package/assets/docs/tree/replace-a-core-component.doc.mjs +15 -7
  179. package/assets/docs/tree/replace-a-core-template.doc.mjs +9 -11
  180. package/assets/docs/tree/scaffold.doc.mjs +1 -1
  181. package/assets/docs/tree/sections-and-placement.doc.mjs +9 -11
  182. package/assets/docs/tree/see-it-in-an-app.doc.mjs +17 -5
  183. package/assets/docs/tree/short-and-findable.doc.mjs +9 -8
  184. package/assets/docs/tree/single-component.doc.mjs +14 -6
  185. package/assets/docs/tree/start-a-template.doc.mjs +18 -8
  186. package/assets/docs/tree/subcomponent.doc.mjs +6 -6
  187. package/assets/docs/tree/template-assets.doc.mjs +2 -2
  188. package/assets/docs/tree/template-doc-overview.doc.mjs +6 -17
  189. package/assets/docs/tree/template-fonts.doc.mjs +1 -1
  190. package/assets/docs/tree/template-icons.doc.mjs +1 -1
  191. package/assets/docs/tree/template-images-media.doc.mjs +1 -1
  192. package/assets/docs/tree/template-styles.doc.mjs +2 -2
  193. package/assets/docs/tree/templates.doc.mjs +7 -1
  194. package/assets/docs/tree/test-in-an-app.doc.mjs +7 -7
  195. package/assets/docs/tree/test-template-in-app.doc.mjs +5 -5
  196. package/assets/docs/tree/themes.doc.mjs +4 -0
  197. package/assets/docs/tree/tokens-and-setup.doc.mjs +19 -15
  198. package/assets/docs/tree/troubleshooting.doc.mjs +22 -14
  199. package/assets/docs/tree/uninstall.doc.mjs +109 -0
  200. package/assets/docs/tree/updates.doc.mjs +92 -0
  201. package/assets/docs/tree/upgrading.doc.mjs +6 -6
  202. package/assets/docs/tree/use-a-theme-in-an-app.doc.mjs +14 -2
  203. package/assets/docs/tree/verify-packed-template.doc.mjs +3 -3
  204. package/assets/docs/tree/versioning.doc.mjs +9 -9
  205. package/assets/docs/tree/who-answers.doc.mjs +119 -0
  206. package/assets/docs/tree/write-the-template-file.doc.mjs +5 -5
  207. package/assets/docs/working-with-ai.doc.mjs +23 -9
  208. package/authoring/codemod/codemod.doc.mjs +7 -3
  209. package/authoring/config/config.doc.mjs +9 -6
  210. package/authoring/config/type.ts +4 -2
  211. package/authoring/debug/parse.d.mts +5 -5
  212. package/authoring/discover/discover.doc.mjs +7 -1
  213. package/authoring/doctypes/_schema.d.mts +8 -3
  214. package/authoring/doctypes/_schema.mjs +1 -0
  215. package/authoring/doctypes/base/graph-fields.doc.mjs +6 -4
  216. package/authoring/doctypes/command/command.doc.mjs +20 -12
  217. package/authoring/doctypes/command/type.ts +14 -0
  218. package/authoring/doctypes/component/component.doc.mjs +29 -26
  219. package/authoring/doctypes/component/type.ts +15 -5
  220. package/authoring/doctypes/enum/enum.doc.mjs +7 -4
  221. package/authoring/doctypes/function/function.doc.mjs +9 -8
  222. package/authoring/doctypes/hook/hook.doc.mjs +5 -4
  223. package/authoring/doctypes/hook/type.ts +4 -0
  224. package/authoring/doctypes/namespace/namespace.doc.mjs +7 -6
  225. package/authoring/doctypes/reference/reference.doc.mjs +12 -11
  226. package/authoring/doctypes/schema/schema.doc.mjs +9 -9
  227. package/authoring/doctypes/template/template.doc.mjs +2 -2
  228. package/authoring/doctypes/theme/theme.doc.mjs +2 -2
  229. package/authoring/gap-report/gap-report.doc.mjs +2 -2
  230. package/authoring/identity/identity.doc.mjs +3 -3
  231. package/authoring/index.d.ts +1 -0
  232. package/authoring/integration/integration.doc.mjs +22 -5
  233. package/authoring/integration/parse.test.mjs +11 -0
  234. package/authoring/integration/schema.d.mts +14 -3
  235. package/authoring/integration/schema.mjs +33 -2
  236. package/authoring/integration/type.ts +8 -0
  237. package/clients/cli/bin/astryx.mjs +20 -3
  238. package/clients/cli/cli-command.test.mjs +166 -0
  239. package/clients/cli/commands/blog.doc.mjs +1 -0
  240. package/clients/cli/commands/build-theme.batch-report.test.mjs +14 -2
  241. package/clients/cli/commands/build-theme.font-warning.test.mjs +3 -3
  242. package/clients/cli/commands/build-theme.mjs +25 -12
  243. package/clients/cli/commands/build.doc.mjs +1 -0
  244. package/clients/cli/commands/build.exit-codes-doc.test.mjs +4 -0
  245. package/clients/cli/commands/cli-postinstall.test.mjs +49 -1
  246. package/clients/cli/commands/component.doc.mjs +1 -0
  247. package/clients/cli/commands/discover.broken-integration.test.mjs +11 -7
  248. package/clients/cli/commands/discover.doc.mjs +9 -1
  249. package/clients/cli/commands/discover.mjs +144 -93
  250. package/clients/cli/commands/discover.no-source.test.mjs +448 -33
  251. package/clients/cli/commands/discover.sources.test.mjs +20 -1
  252. package/clients/cli/commands/docs.doc.mjs +1 -0
  253. package/clients/cli/commands/docs.mjs +49 -19
  254. package/clients/cli/commands/docs.test.mjs +2 -2
  255. package/clients/cli/commands/doctor-integration-components.doc.mjs +7 -3
  256. package/clients/cli/commands/doctor-integration-docs.doc.mjs +20 -5
  257. package/clients/cli/commands/doctor-integration-templates.doc.mjs +1 -0
  258. package/clients/cli/commands/doctor-integration-validate.doc.mjs +13 -2
  259. package/clients/cli/commands/doctor-integration.test.mjs +52 -4
  260. package/clients/cli/commands/doctor.doc.mjs +12 -2
  261. package/clients/cli/commands/doctor.mjs +102 -7
  262. package/clients/cli/commands/doctor.test.mjs +10 -4
  263. package/clients/cli/commands/gap-report.doc.mjs +15 -2
  264. package/clients/cli/commands/gap-report.mjs +24 -2
  265. package/clients/cli/commands/gap-report.test.mjs +79 -0
  266. package/clients/cli/commands/hook.doc.mjs +1 -0
  267. package/clients/cli/commands/init.behavior.test.mjs +5 -0
  268. package/clients/cli/commands/init.doc.mjs +29 -3
  269. package/clients/cli/commands/integration-add.doc.mjs +7 -0
  270. package/clients/cli/commands/integration-authoring.test.mjs +49 -3
  271. package/clients/cli/commands/integration-check.doc.mjs +10 -2
  272. package/clients/cli/commands/integration-check.test.mjs +175 -1
  273. package/clients/cli/commands/integration-pack.doc.mjs +8 -1
  274. package/clients/cli/commands/integration-verify.doc.mjs +8 -1
  275. package/clients/cli/commands/integration.mjs +15 -9
  276. package/clients/cli/commands/json-contract.test.mjs +11 -2
  277. package/clients/cli/commands/manifest.doc.mjs +1 -0
  278. package/clients/cli/commands/outside-project.test.mjs +774 -0
  279. package/clients/cli/commands/search.doc.mjs +1 -0
  280. package/clients/cli/commands/search.mjs +16 -11
  281. package/clients/cli/commands/search.test.mjs +429 -296
  282. package/clients/cli/commands/setup-nudge.test.mjs +102 -2
  283. package/clients/cli/commands/swizzle.doc.mjs +7 -0
  284. package/clients/cli/commands/swizzle.mjs +5 -3
  285. package/clients/cli/commands/swizzle.routing.test.mjs +19 -2
  286. package/clients/cli/commands/template.doc.mjs +1 -0
  287. package/clients/cli/commands/theme-add-lifecycle.test.mjs +3 -3
  288. package/clients/cli/commands/theme-add.doc.mjs +8 -1
  289. package/clients/cli/commands/theme-build.doc.mjs +1 -0
  290. package/clients/cli/commands/theme-eject.doc.mjs +1 -0
  291. package/clients/cli/commands/theme-list.behavior.test.mjs +6 -0
  292. package/clients/cli/commands/theme-list.doc.mjs +1 -0
  293. package/clients/cli/commands/theme-palette-generate.doc.mjs +1 -0
  294. package/clients/cli/commands/theme-remove.doc.mjs +7 -0
  295. package/clients/cli/commands/theme-targets.doc.mjs +1 -0
  296. package/clients/cli/commands/theme-template.doc.mjs +1 -0
  297. package/clients/cli/commands/theme-use.doc.mjs +7 -0
  298. package/clients/cli/commands/upgrade.config-ordering.test.mjs +2 -1
  299. package/clients/cli/commands/upgrade.doc.mjs +22 -1
  300. package/clients/cli/commands/upgrade.file-protection.test.mjs +3 -2
  301. package/clients/cli/commands/upgrade.hook-output.test.mjs +2 -1
  302. package/clients/cli/commands/upgrade.older-cli.test.mjs +160 -0
  303. package/clients/cli/index.mjs +111 -16
  304. package/clients/cli/lib/cli-error.mjs +4 -1
  305. package/clients/cli/lib/component-format.test.mjs +1 -1
  306. package/clients/cli/lib/define-command.mjs +2 -2
  307. package/clients/cli/lib/json-shim.mjs +3 -1
  308. package/clients/cli/lib/manifest.d.ts +9 -0
  309. package/clients/cli/lib/manifest.mjs +17 -1
  310. package/clients/cli/lib/manifest.test.mjs +25 -0
  311. package/foundation/agent-docs/agent-doc-state.d.mts +35 -6
  312. package/foundation/agent-docs/agent-doc-state.mjs +66 -6
  313. package/foundation/agent-docs/agent-docs.d.mts +4 -3
  314. package/foundation/agent-docs/agent-docs.mjs +11 -9
  315. package/foundation/agent-docs/computer-agent-text.d.mts +133 -0
  316. package/foundation/agent-docs/computer-agent-text.mjs +623 -0
  317. package/foundation/agent-docs/computer-agent-text.test.mjs +602 -0
  318. package/foundation/config/project.d.mts +35 -3
  319. package/foundation/config/project.mjs +145 -8
  320. package/foundation/config/project.test.mjs +66 -0
  321. package/foundation/config/theme-state.mjs +5 -4
  322. package/foundation/discovery/authoring-self-docs.mjs +1 -1
  323. package/foundation/discovery/cli-self-docs.mjs +20 -3
  324. package/foundation/discovery/cli-self-docs.test.mjs +3 -2
  325. package/foundation/discovery/component-replacement.mjs +2 -1
  326. package/foundation/discovery/copied-passages.d.mts +216 -0
  327. package/foundation/discovery/copied-passages.mjs +720 -0
  328. package/foundation/discovery/copied-passages.test.mjs +124 -0
  329. package/foundation/discovery/doc-consistency.d.mts +34 -0
  330. package/foundation/discovery/doc-consistency.mjs +513 -0
  331. package/foundation/discovery/doc-consistency.test.mjs +347 -0
  332. package/foundation/discovery/doc-fluff.d.mts +112 -0
  333. package/foundation/discovery/doc-fluff.mjs +328 -0
  334. package/foundation/discovery/doc-fluff.test.mjs +142 -0
  335. package/foundation/discovery/doc-types.d.mts +1 -1
  336. package/foundation/discovery/doc-types.mjs +15 -8
  337. package/foundation/discovery/doc-types.test.mjs +94 -2
  338. package/foundation/discovery/docs-discovery.d.mts +7 -0
  339. package/foundation/discovery/docs-discovery.mjs +26 -2
  340. package/foundation/discovery/project-typescript.d.mts +11 -2
  341. package/foundation/discovery/project-typescript.mjs +32 -4
  342. package/foundation/discovery/prop-drift.d.mts +26 -7
  343. package/foundation/discovery/prop-drift.mjs +173 -8
  344. package/foundation/discovery/prop-drift.test.mjs +120 -2
  345. package/foundation/discovery/runner-allowlist.d.mts +40 -0
  346. package/foundation/discovery/runner-allowlist.mjs +53 -0
  347. package/foundation/discovery/stray-pointers.d.mts +52 -0
  348. package/foundation/discovery/stray-pointers.mjs +293 -0
  349. package/foundation/discovery/stray-pointers.test.mjs +86 -0
  350. package/foundation/discovery/template-needs.mjs +5 -2
  351. package/foundation/discovery/theme-imports.mjs +8 -4
  352. package/foundation/discovery/theme-imports.test.mjs +1 -1
  353. package/foundation/discovery/type-text.mjs +1 -1
  354. package/foundation/discovery/type-text.test.mjs +1 -1
  355. package/foundation/doc-compiler/doc-loads.test.mjs +42 -2
  356. package/foundation/doc-compiler/inputs.mjs +4 -1
  357. package/foundation/doc-compiler/links.d.mts +32 -0
  358. package/foundation/doc-compiler/links.mjs +163 -26
  359. package/foundation/doc-compiler/links.test.mjs +97 -4
  360. package/foundation/doc-compiler/read.d.mts +1 -1
  361. package/foundation/doc-compiler/tree.mjs +30 -1
  362. package/foundation/doc-compiler/tree.test.mjs +8 -1
  363. package/foundation/env/cli-command.d.mts +83 -0
  364. package/foundation/env/cli-command.mjs +152 -0
  365. package/foundation/env/cli-command.test.mjs +129 -0
  366. package/foundation/env/node-version.test.mjs +15 -3
  367. package/foundation/env/package-manager.d.mts +93 -9
  368. package/foundation/env/package-manager.global.test.mjs +66 -0
  369. package/foundation/env/package-manager.mjs +184 -13
  370. package/foundation/env/package-manager.test.mjs +341 -15
  371. package/foundation/env/project-scope.d.mts +9 -0
  372. package/foundation/env/project-scope.mjs +33 -0
  373. package/foundation/env/search-path.d.mts +42 -0
  374. package/foundation/env/search-path.mjs +205 -0
  375. package/foundation/env/search-path.test.mjs +122 -0
  376. package/foundation/env/user-dirs.d.mts +39 -0
  377. package/foundation/env/user-dirs.mjs +93 -0
  378. package/foundation/env/user-dirs.test.mjs +72 -0
  379. package/foundation/env/user-settings.d.mts +46 -0
  380. package/foundation/env/user-settings.mjs +106 -0
  381. package/foundation/env/user-settings.test.mjs +64 -0
  382. package/foundation/fs/file-protection.mjs +15 -1
  383. package/foundation/fs/module-loader.d.mts +2 -1
  384. package/foundation/fs/module-loader.mjs +60 -4
  385. package/foundation/fs/module-loader.stdout.test.mjs +3 -2
  386. package/foundation/fs/module-loader.test.mjs +194 -0
  387. package/foundation/fs/path-compare.d.mts +33 -0
  388. package/foundation/fs/path-compare.mjs +73 -0
  389. package/foundation/fs/paths.d.mts +57 -1
  390. package/foundation/fs/paths.mjs +131 -3
  391. package/foundation/fs/paths.test.mjs +10 -1
  392. package/foundation/integrations/computer-integrations.d.mts +78 -0
  393. package/foundation/integrations/computer-integrations.mjs +316 -0
  394. package/foundation/integrations/computer-integrations.test.mjs +292 -0
  395. package/foundation/integrations/contribution-inventory.d.mts +11 -0
  396. package/foundation/integrations/contribution-inventory.mjs +50 -2
  397. package/foundation/integrations/integration-warnings.mjs +3 -2
  398. package/foundation/integrations/integration-warnings.test.mjs +8 -9
  399. package/foundation/integrations/integrations.d.mts +25 -1
  400. package/foundation/integrations/integrations.mjs +46 -3
  401. package/foundation/integrations/issue.ts +6 -0
  402. package/foundation/integrations/validate-contributions.d.mts +2 -0
  403. package/foundation/integrations/validate-contributions.mjs +41 -1
  404. package/foundation/release/agent-text-worker.d.mts +4 -0
  405. package/foundation/release/agent-text-worker.mjs +52 -0
  406. package/foundation/release/cache.d.mts +310 -0
  407. package/foundation/release/cache.mjs +1216 -0
  408. package/foundation/release/cache.test.mjs +920 -0
  409. package/foundation/release/choose.d.mts +207 -0
  410. package/foundation/release/choose.mjs +677 -0
  411. package/foundation/release/choose.test.mjs +427 -0
  412. package/foundation/release/core-worker.d.mts +4 -0
  413. package/foundation/release/core-worker.mjs +38 -0
  414. package/foundation/release/global-state.d.mts +259 -0
  415. package/foundation/release/global-state.mjs +472 -0
  416. package/foundation/release/global-state.test.mjs +381 -0
  417. package/foundation/release/handoff.d.mts +60 -0
  418. package/foundation/release/handoff.e2e.test.mjs +278 -0
  419. package/foundation/release/handoff.mjs +357 -0
  420. package/foundation/release/lock-lease.d.mts +23 -0
  421. package/foundation/release/lock-lease.mjs +80 -0
  422. package/foundation/release/notices.d.mts +253 -0
  423. package/foundation/release/notices.mjs +596 -0
  424. package/foundation/release/notices.test.mjs +565 -0
  425. package/foundation/release/npm-guard.d.mts +4 -0
  426. package/foundation/release/npm-guard.mjs +126 -0
  427. package/foundation/release/project.d.mts +141 -0
  428. package/foundation/release/project.mjs +466 -0
  429. package/foundation/release/project.test.mjs +288 -0
  430. package/foundation/release/refresh-worker.d.mts +4 -0
  431. package/foundation/release/refresh-worker.mjs +27 -0
  432. package/foundation/release/run-context.d.mts +47 -0
  433. package/foundation/release/run-context.mjs +149 -0
  434. package/foundation/release/update-notice-env.d.mts +28 -0
  435. package/foundation/release/update-notice-env.mjs +39 -0
  436. package/foundation/response/base.d.ts +26 -3
  437. package/foundation/response/error-codes.d.mts +3 -1
  438. package/foundation/response/error-codes.doc.mjs +12 -0
  439. package/foundation/response/error-codes.mjs +17 -0
  440. package/foundation/response/error-codes.test.mjs +2 -0
  441. package/foundation/response/json-contract.test.mjs +7 -2
  442. package/foundation/response/json.mjs +4 -1
  443. package/foundation/response/response-types.doc.mjs +17 -11
  444. package/foundation/response/response-types.doc.test.mjs +46 -0
  445. package/foundation/response/response.doc.mjs +3 -3
  446. package/foundation/xle/browser-imports.test.mjs +74 -0
  447. package/package.json +11 -11
  448. package/scripts/postinstall.mjs +24 -5
  449. /package/assets/codemods/transforms/{next → v0.7.2}/__tests__/migrate-list-edge-compensation-to-edge.test.mjs +0 -0
  450. /package/assets/codemods/transforms/{next → v0.7.2}/migrate-list-edge-compensation-to-edge.mjs +0 -0
@@ -15,8 +15,13 @@ import * as fs from 'node:fs';
15
15
  import * as os from 'node:os';
16
16
  import * as path from 'node:path';
17
17
  import {fileURLToPath} from 'node:url';
18
- import {search, scoreCandidate, SEARCH_DOMAINS} from '../../../api/search/search.mjs';
18
+ import {
19
+ search,
20
+ scoreCandidate,
21
+ SEARCH_DOMAINS,
22
+ } from '../../../api/search/search.mjs';
19
23
  import {runCli} from '../../../test-utils/run-cli.mjs';
24
+ import {linkThisCliAsProjectCli} from '../../../test-utils/release-fixtures.mjs';
20
25
 
21
26
  const __dirname = path.dirname(fileURLToPath(import.meta.url));
22
27
  // Run against the monorepo root so @astryxdesign/core is discoverable.
@@ -58,123 +63,147 @@ function printsField(lines, key, value) {
58
63
  // `astryx` bin is installed), so compare the part after `astryx`.
59
64
  const text = asText(value).split('\n')[0].trimEnd();
60
65
  const shown = text.replace(/^astryx /, '');
61
- return lines.some(line => line.startsWith(`${key}:`) && line.trimEnd().endsWith(shown));
66
+ return lines.some(
67
+ line => line.startsWith(`${key}:`) && line.trimEnd().endsWith(shown),
68
+ );
62
69
  }
63
70
 
64
- describe('search() API — ranking', () => {
65
- it('ranks an exact component name match first', async () => {
66
- const {data} = await search('button', OPTS);
67
- expect(data.results.length).toBeGreaterThan(0);
68
- const top = data.results[0];
69
- expect(top.domain).toBe('component');
70
- expect(top.name).toBe('Button');
71
- expect(top.score).toBe(100);
72
- expect(top.command).toBe('astryx component Button');
73
- });
74
-
75
- it('returns components/hooks tagged with a matching keyword', async () => {
76
- const {data} = await search('button', OPTS);
77
- // IconButton carries the "button" keyword; should be present and scored
78
- // on a keyword (not name) signal.
79
- const iconButton = data.results.find(r => r.name === 'IconButton');
80
- expect(iconButton).toBeTruthy();
81
- expect(iconButton.domain).toBe('component');
82
- expect(iconButton.score).toBeGreaterThanOrEqual(70);
83
- expect(iconButton.reason.toLowerCase()).toContain('keyword');
84
- });
85
- }, SCAN_TIMEOUT);
86
-
87
- describe('search() API — cross-domain', () => {
88
- it('returns results from more than one domain for a broad query', async () => {
89
- const {data} = await search('color', OPTS);
90
- const domains = new Set(data.results.map(r => r.domain));
91
- // "color" is a doc topic AND appears across components.
92
- expect(domains.has('doc')).toBe(true);
93
- expect(domains.size).toBeGreaterThan(1);
94
- // The doc topic should carry a follow-up `astryx docs` command.
95
- const docHit = data.results.find(r => r.domain === 'doc');
96
- expect(docHit.command.startsWith('astryx docs ')).toBe(true);
97
- });
98
-
99
- it('every result is tagged with a known domain and a command', async () => {
100
- const {data} = await search('button', OPTS);
101
- for (const r of data.results) {
102
- expect(SEARCH_DOMAINS).toContain(r.domain);
103
- expect(typeof r.command).toBe('string');
104
- expect(r.command.length).toBeGreaterThan(0);
105
- }
106
- });
107
- }, SCAN_TIMEOUT);
108
-
109
- describe('search() API — filters', () => {
110
- it('--type component returns only components', async () => {
111
- const {data} = await search('modal', {...OPTS, type: 'component'});
112
- expect(data.results.length).toBeGreaterThan(0);
113
- for (const r of data.results) {
114
- expect(r.domain).toBe('component');
115
- }
116
- });
117
-
118
- it('--type doc returns only docs', async () => {
119
- const {data} = await search('color', {...OPTS, type: 'doc'});
120
- for (const r of data.results) {
121
- expect(r.domain).toBe('doc');
122
- }
123
- });
124
-
125
- it('--type theme returns only themes, each with the command that adds it', async () => {
126
- const {data} = await search('warm', {...OPTS, type: 'theme'});
127
- expect(data.results.map(r => r.name)).toContain('neutral');
128
- for (const r of data.results) {
129
- expect(r.domain).toBe('theme');
130
- expect(r.command).toBe(`astryx theme add ${r.name}`);
131
- }
132
- });
133
-
134
- it('respects --limit', async () => {
135
- const {data} = await search('button', {...OPTS, limit: 3});
136
- expect(data.results.length).toBeLessThanOrEqual(3);
137
- });
138
-
139
- it('rejects an unknown --type', async () => {
140
- await expect(search('x', {...OPTS, type: 'bogus'})).rejects.toThrow(/Unknown --type/);
141
- });
142
- }, SCAN_TIMEOUT);
143
-
144
- describe('search() API — fuzzy / typo tolerance', () => {
145
- it('finds Button for the typo "buton"', async () => {
146
- const {data} = await search('buton', OPTS);
147
- const button = data.results.find(r => r.name === 'Button');
148
- expect(button).toBeTruthy();
149
- expect(button.domain).toBe('component');
150
- });
151
- }, SCAN_TIMEOUT);
152
-
153
- describe('search() API — empty + envelope', () => {
154
- it('returns an empty result set (not an error) for a no-match query', async () => {
155
- const {type, data} = await search('zzqqxx_definitely_no_match', OPTS);
156
- expect(type).toBe('search');
157
- expect(data.results).toEqual([]);
158
- expect(data.query).toBe('zzqqxx_definitely_no_match');
159
- });
160
-
161
- it('throws when the query is empty', async () => {
162
- await expect(search('', OPTS)).rejects.toThrow(/query is required/);
163
- });
71
+ describe(
72
+ 'search() API — ranking',
73
+ () => {
74
+ it('ranks an exact component name match first', async () => {
75
+ const {data} = await search('button', OPTS);
76
+ expect(data.results.length).toBeGreaterThan(0);
77
+ const top = data.results[0];
78
+ expect(top.domain).toBe('component');
79
+ expect(top.name).toBe('Button');
80
+ expect(top.score).toBe(100);
81
+ expect(top.command).toBe('astryx component Button');
82
+ });
83
+
84
+ it('returns components/hooks tagged with a matching keyword', async () => {
85
+ const {data} = await search('button', OPTS);
86
+ // IconButton carries the "button" keyword; should be present and scored
87
+ // on a keyword (not name) signal.
88
+ const iconButton = data.results.find(r => r.name === 'IconButton');
89
+ expect(iconButton).toBeTruthy();
90
+ expect(iconButton.domain).toBe('component');
91
+ expect(iconButton.score).toBeGreaterThanOrEqual(70);
92
+ expect(iconButton.reason.toLowerCase()).toContain('keyword');
93
+ });
94
+ },
95
+ SCAN_TIMEOUT,
96
+ );
97
+
98
+ describe(
99
+ 'search() API — cross-domain',
100
+ () => {
101
+ it('returns results from more than one domain for a broad query', async () => {
102
+ const {data} = await search('color', OPTS);
103
+ const domains = new Set(data.results.map(r => r.domain));
104
+ // "color" is a doc topic AND appears across components.
105
+ expect(domains.has('doc')).toBe(true);
106
+ expect(domains.size).toBeGreaterThan(1);
107
+ // The doc topic should carry a follow-up `astryx docs` command.
108
+ const docHit = data.results.find(r => r.domain === 'doc');
109
+ expect(docHit.command.startsWith('astryx docs ')).toBe(true);
110
+ });
111
+
112
+ it('every result is tagged with a known domain and a command', async () => {
113
+ const {data} = await search('button', OPTS);
114
+ for (const r of data.results) {
115
+ expect(SEARCH_DOMAINS).toContain(r.domain);
116
+ expect(typeof r.command).toBe('string');
117
+ expect(r.command.length).toBeGreaterThan(0);
118
+ }
119
+ });
120
+ },
121
+ SCAN_TIMEOUT,
122
+ );
123
+
124
+ describe(
125
+ 'search() API — filters',
126
+ () => {
127
+ it('--type component returns only components', async () => {
128
+ const {data} = await search('modal', {...OPTS, type: 'component'});
129
+ expect(data.results.length).toBeGreaterThan(0);
130
+ for (const r of data.results) {
131
+ expect(r.domain).toBe('component');
132
+ }
133
+ });
164
134
 
165
- it('returns the correct envelope shape', async () => {
166
- const result = await search('button', OPTS);
167
- expect(result.type).toBe('search');
168
- expect(result.data).toHaveProperty('query');
169
- expect(Array.isArray(result.data.results)).toBe(true);
170
- const top = result.data.results[0];
171
- expect(top).toHaveProperty('domain');
172
- expect(top).toHaveProperty('name');
173
- expect(top).toHaveProperty('score');
174
- expect(top).toHaveProperty('description');
175
- expect(top).toHaveProperty('command');
176
- });
177
- }, SCAN_TIMEOUT);
135
+ it('--type doc returns only docs', async () => {
136
+ const {data} = await search('color', {...OPTS, type: 'doc'});
137
+ for (const r of data.results) {
138
+ expect(r.domain).toBe('doc');
139
+ }
140
+ });
141
+
142
+ it('--type theme returns only themes, each with the command that adds it', async () => {
143
+ const {data} = await search('warm', {...OPTS, type: 'theme'});
144
+ expect(data.results.map(r => r.name)).toContain('neutral');
145
+ for (const r of data.results) {
146
+ expect(r.domain).toBe('theme');
147
+ expect(r.command).toBe(`astryx theme add ${r.name}`);
148
+ }
149
+ });
150
+
151
+ it('respects --limit', async () => {
152
+ const {data} = await search('button', {...OPTS, limit: 3});
153
+ expect(data.results.length).toBeLessThanOrEqual(3);
154
+ });
155
+
156
+ it('rejects an unknown --type', async () => {
157
+ await expect(search('x', {...OPTS, type: 'bogus'})).rejects.toThrow(
158
+ /Unknown --type/,
159
+ );
160
+ });
161
+ },
162
+ SCAN_TIMEOUT,
163
+ );
164
+
165
+ describe(
166
+ 'search() API — fuzzy / typo tolerance',
167
+ () => {
168
+ it('finds Button for the typo "buton"', async () => {
169
+ const {data} = await search('buton', OPTS);
170
+ const button = data.results.find(r => r.name === 'Button');
171
+ expect(button).toBeTruthy();
172
+ expect(button.domain).toBe('component');
173
+ });
174
+ },
175
+ SCAN_TIMEOUT,
176
+ );
177
+
178
+ describe(
179
+ 'search() API — empty + envelope',
180
+ () => {
181
+ it('returns an empty result set (not an error) for a no-match query', async () => {
182
+ const {type, data} = await search('zzqqxx_definitely_no_match', OPTS);
183
+ expect(type).toBe('search');
184
+ expect(data.results).toEqual([]);
185
+ expect(data.query).toBe('zzqqxx_definitely_no_match');
186
+ });
187
+
188
+ it('throws when the query is empty', async () => {
189
+ await expect(search('', OPTS)).rejects.toThrow(/query is required/);
190
+ });
191
+
192
+ it('returns the correct envelope shape', async () => {
193
+ const result = await search('button', OPTS);
194
+ expect(result.type).toBe('search');
195
+ expect(result.data).toHaveProperty('query');
196
+ expect(Array.isArray(result.data.results)).toBe(true);
197
+ const top = result.data.results[0];
198
+ expect(top).toHaveProperty('domain');
199
+ expect(top).toHaveProperty('name');
200
+ expect(top).toHaveProperty('score');
201
+ expect(top).toHaveProperty('description');
202
+ expect(top).toHaveProperty('command');
203
+ });
204
+ },
205
+ SCAN_TIMEOUT,
206
+ );
178
207
 
179
208
  describe('scoreCandidate()', () => {
180
209
  it('scores an exact name match at 100', () => {
@@ -183,199 +212,303 @@ describe('scoreCandidate()', () => {
183
212
 
184
213
  it('scores an exact keyword above a description mention', () => {
185
214
  const kw = scoreCandidate('toggle', {name: 'Switch', keywords: ['toggle']});
186
- const desc = scoreCandidate('toggle', {name: 'Switch', description: 'A toggle control'});
215
+ const desc = scoreCandidate('toggle', {
216
+ name: 'Switch',
217
+ description: 'A toggle control',
218
+ });
187
219
  expect(kw.score).toBeGreaterThan(desc.score);
188
220
  });
189
221
 
190
222
  it('returns null when nothing matches', () => {
191
- expect(scoreCandidate('zzzz', {name: 'Button', keywords: ['cta']})).toBeNull();
223
+ expect(
224
+ scoreCandidate('zzzz', {name: 'Button', keywords: ['cta']}),
225
+ ).toBeNull();
192
226
  });
193
227
  });
194
228
 
195
- describe('search CLI — exit codes + JSON contract', () => {
196
- it('exits 0 for a successful search', async () => {
197
- const r = await runCli(['search', 'button'], REPO_ROOT);
198
- expect(r.status).toBe(0);
199
- });
200
-
201
- it('exits 0 for a no-match query (valid empty result, not failure)', async () => {
202
- const r = await runCli(['search', 'zzqqxx_no_match'], REPO_ROOT);
203
- expect(r.status).toBe(0);
204
- expect(r.stdout).toContain('No results');
205
- });
206
-
207
- it('takes every word after `search` as one query', async () => {
208
- // Commander took only the first word, so `search dark mode` searched for
209
- // "dark" and dropped "mode" without a word.
210
- const json = await runCli(['--json', 'search', 'dark', 'mode', '--type', 'doc'], REPO_ROOT);
211
- expect(json.status).toBe(0);
212
- const env = JSON.parse(json.stdout);
213
- expect(env.data.query).toBe('dark mode');
214
- // theme topic-level and section tie; either is correct
215
- const top = env.data.results[0];
216
- expect(top.name === 'theme' || top.name === 'use-a-theme' || top.section === 'light-dark-mode').toBe(true);
217
- const text = await runCli(['search', 'dark', 'mode', '--type', 'doc'], REPO_ROOT);
218
- expect(text.stdout).toContain('Results for "dark mode"');
219
- }, SCAN_TIMEOUT);
220
-
221
- it('exits 1 for an invalid --type', async () => {
222
- const r = await runCli(['search', 'x', '--type', 'bogus'], REPO_ROOT);
223
- expect(r.status).toBe(1);
224
- });
225
-
226
- it('exits 1 for an invalid --limit', async () => {
227
- const r = await runCli(['search', 'x', '--limit', '0'], REPO_ROOT);
228
- expect(r.status).toBe(1);
229
- });
230
-
231
- it.each(['1.5', '5abc'])(
232
- 'refuses --limit %s like search({limit}) does, in both modes',
233
- async value => {
234
- await expect(
235
- search('x', {...OPTS, limit: Number(value)}),
236
- ).rejects.toMatchObject({code: 'ERR_INVALID_ARGUMENT'});
237
- const json = await runCli(['--json', 'search', 'x', '--limit', value], REPO_ROOT);
238
- expect(json.status).toBe(1);
239
- expect(JSON.parse(json.stdout)).toMatchObject({code: 'ERR_INVALID_ARGUMENT'});
240
- const text = await runCli(['search', 'x', '--limit', value], REPO_ROOT);
241
- expect(text.status).toBe(1);
242
- },
243
- );
244
-
245
- it('emits a valid --json envelope', async () => {
246
- const r = await runCli(['--json', 'search', 'button'], REPO_ROOT);
247
- expect(r.status).toBe(0);
248
- const parsed = JSON.parse(r.stdout);
249
- expect(parsed.apiVersion).toBe(1);
250
- expect(parsed.type).toBe('search');
251
- expect(parsed.data.query).toBe('button');
252
- expect(Array.isArray(parsed.data.results)).toBe(true);
253
- expect(parsed.data.matchCount).toBeGreaterThanOrEqual(
254
- parsed.data.results.length,
255
- );
256
- expect(parsed.data.results[0].name).toBe('Button');
257
- });
258
-
259
- it('says how many matched when --limit cut the list short', async () => {
260
- // The text view is a projection of the JSON, and the JSON now carries the
261
- // match total. A bare "(2)" heading over a capped list reads as "that is
262
- // all Astryx has", which is the conclusion `search` exists to prevent.
263
- const capped = await runCli(
264
- ['search', 'button', '--type', 'component', '--limit', '2'],
265
- REPO_ROOT,
229
+ describe(
230
+ 'search CLI — exit codes + JSON contract',
231
+ () => {
232
+ it('exits 0 for a successful search', async () => {
233
+ const r = await runCli(['search', 'button'], REPO_ROOT);
234
+ expect(r.status).toBe(0);
235
+ });
236
+
237
+ it('exits 0 for a no-match query (valid empty result, not failure)', async () => {
238
+ const r = await runCli(['search', 'zzqqxx_no_match'], REPO_ROOT);
239
+ expect(r.status).toBe(0);
240
+ expect(r.stdout).toContain('No results');
241
+ });
242
+
243
+ it(
244
+ 'takes every word after `search` as one query',
245
+ async () => {
246
+ // Commander took only the first word, so `search dark mode` searched for
247
+ // "dark" and dropped "mode" without a word.
248
+ const json = await runCli(
249
+ ['--json', 'search', 'dark', 'mode', '--type', 'doc'],
250
+ REPO_ROOT,
251
+ );
252
+ expect(json.status).toBe(0);
253
+ const env = JSON.parse(json.stdout);
254
+ expect(env.data.query).toBe('dark mode');
255
+ // theme topic-level and section tie; either is correct
256
+ const top = env.data.results[0];
257
+ expect(
258
+ top.name === 'theme' ||
259
+ top.name === 'use-a-theme' ||
260
+ top.section === 'light-dark-mode',
261
+ ).toBe(true);
262
+ const text = await runCli(
263
+ ['search', 'dark', 'mode', '--type', 'doc'],
264
+ REPO_ROOT,
265
+ );
266
+ expect(text.stdout).toContain('Results for "dark mode"');
267
+ },
268
+ SCAN_TIMEOUT,
266
269
  );
267
- expect(capped.status).toBe(0);
268
- expect(capped.stdout).toMatch(/^Results for "button" \(2 of \d+\)$/m);
269
270
 
270
- const uncapped = await runCli(
271
- ['search', 'button', '--type', 'component', '--limit', '500'],
272
- REPO_ROOT,
271
+ it('exits 1 for an invalid --type', async () => {
272
+ const r = await runCli(['search', 'x', '--type', 'bogus'], REPO_ROOT);
273
+ expect(r.status).toBe(1);
274
+ });
275
+
276
+ it('exits 1 for an invalid --limit', async () => {
277
+ const r = await runCli(['search', 'x', '--limit', '0'], REPO_ROOT);
278
+ expect(r.status).toBe(1);
279
+ });
280
+
281
+ it.each(['1.5', '5abc'])(
282
+ 'refuses --limit %s like search({limit}) does, in both modes',
283
+ async value => {
284
+ await expect(
285
+ search('x', {...OPTS, limit: Number(value)}),
286
+ ).rejects.toMatchObject({code: 'ERR_INVALID_ARGUMENT'});
287
+ const json = await runCli(
288
+ ['--json', 'search', 'x', '--limit', value],
289
+ REPO_ROOT,
290
+ );
291
+ expect(json.status).toBe(1);
292
+ expect(JSON.parse(json.stdout)).toMatchObject({
293
+ code: 'ERR_INVALID_ARGUMENT',
294
+ });
295
+ const text = await runCli(['search', 'x', '--limit', value], REPO_ROOT);
296
+ expect(text.status).toBe(1);
297
+ },
273
298
  );
274
- expect(uncapped.stdout).toMatch(/^Results for "button" \(\d+\)$/m);
275
- expect(uncapped.stdout).not.toMatch(/Results for "button" \(\d+ of/);
276
- });
277
299
 
278
- it('emits a valid --json envelope with empty results for no match', async () => {
279
- const r = await runCli(['--json', 'search', 'zzqqxx_no_match'], REPO_ROOT);
280
- expect(r.status).toBe(0);
281
- const parsed = JSON.parse(r.stdout);
282
- expect(parsed.type).toBe('search');
283
- expect(parsed.data.results).toEqual([]);
284
- expect(parsed.data.matchCount).toBe(0);
285
- });
286
-
287
- it('renders each result as a greppable key: value record', async () => {
288
- const r = await runCli(['search', 'button'], REPO_ROOT);
289
- expect(r.stdout).toMatch(/^command:\s+\S.*(?:astryx|@astryxdesign\/cli) component Button$/m);
290
- // Fields mirror the JSON object and are line-greppable.
291
- expect(r.stdout).toMatch(/^name:\s+Button$/m);
292
- expect(r.stdout).toMatch(/^domain:\s+component$/m);
293
- expect(r.stdout).toContain('description:');
294
- });
295
-
296
- it('prints every result field under its JSON key (score and reason with --verbose)', async () => {
297
- // Between them these reach every domain, so every per-domain field shows.
298
- // A theme matches `theme` only through its description, a prose mention
299
- // ranked below every name and keyword hit, so the theme domain comes from
300
- // a theme's own name.
301
- const domains = new Set();
302
- for (const args of [
303
- ['search', 'theme', '--limit', '60'],
304
- ['search', 'neutral', '--type', 'theme'],
305
- ]) {
306
- const env = JSON.parse((await runCli(['--json', ...args], REPO_ROOT)).stdout);
307
- const plain = (await runCli(args, REPO_ROOT)).stdout.split('\n');
308
- const verbose = (await runCli([...args, '--verbose'], REPO_ROOT)).stdout.split('\n');
309
- for (const result of env.data.results) {
310
- domains.add(result.domain);
311
- for (const [key, value] of Object.entries(result)) {
312
- if (value == null || value === '') continue;
313
- const label = `${result.domain} ${result.name}: ${key}`;
314
- expect(printsField(verbose, key, value), label).toBe(true);
315
- if (key !== 'score' && key !== 'reason') {
316
- expect(printsField(plain, key, value), label).toBe(true);
300
+ it('emits a valid --json envelope', async () => {
301
+ const r = await runCli(['--json', 'search', 'button'], REPO_ROOT);
302
+ expect(r.status).toBe(0);
303
+ const parsed = JSON.parse(r.stdout);
304
+ expect(parsed.apiVersion).toBe(1);
305
+ expect(parsed.type).toBe('search');
306
+ expect(parsed.data.query).toBe('button');
307
+ expect(Array.isArray(parsed.data.results)).toBe(true);
308
+ expect(parsed.data.matchCount).toBeGreaterThanOrEqual(
309
+ parsed.data.results.length,
310
+ );
311
+ expect(parsed.data.results[0].name).toBe('Button');
312
+ });
313
+
314
+ it('says how many matched when --limit cut the list short', async () => {
315
+ // The text view is a projection of the JSON, and the JSON now carries the
316
+ // match total. A bare "(2)" heading over a capped list reads as "that is
317
+ // all Astryx has", which is the conclusion `search` exists to prevent.
318
+ const capped = await runCli(
319
+ ['search', 'button', '--type', 'component', '--limit', '2'],
320
+ REPO_ROOT,
321
+ );
322
+ expect(capped.status).toBe(0);
323
+ expect(capped.stdout).toMatch(/^Results for "button" \(2 of \d+\)$/m);
324
+
325
+ const uncapped = await runCli(
326
+ ['search', 'button', '--type', 'component', '--limit', '500'],
327
+ REPO_ROOT,
328
+ );
329
+ expect(uncapped.stdout).toMatch(/^Results for "button" \(\d+\)$/m);
330
+ expect(uncapped.stdout).not.toMatch(/Results for "button" \(\d+ of/);
331
+ });
332
+
333
+ it('emits a valid --json envelope with empty results for no match', async () => {
334
+ const r = await runCli(
335
+ ['--json', 'search', 'zzqqxx_no_match'],
336
+ REPO_ROOT,
337
+ );
338
+ expect(r.status).toBe(0);
339
+ const parsed = JSON.parse(r.stdout);
340
+ expect(parsed.type).toBe('search');
341
+ expect(parsed.data.results).toEqual([]);
342
+ expect(parsed.data.matchCount).toBe(0);
343
+ });
344
+
345
+ it('renders each result as a greppable key: value record', async () => {
346
+ const r = await runCli(['search', 'button'], REPO_ROOT);
347
+ expect(r.stdout).toMatch(
348
+ /^command:\s+\S.*(?:astryx|@astryxdesign\/cli(?:@\S+)?) component Button$/m,
349
+ );
350
+ // Fields mirror the JSON object and are line-greppable.
351
+ expect(r.stdout).toMatch(/^name:\s+Button$/m);
352
+ expect(r.stdout).toMatch(/^domain:\s+component$/m);
353
+ expect(r.stdout).toContain('description:');
354
+ });
355
+
356
+ it('prints every result field under its JSON key (score and reason with --verbose)', async () => {
357
+ // Between them these reach every domain, so every per-domain field shows.
358
+ // A theme matches `theme` only through its description, a prose mention
359
+ // ranked below every name and keyword hit, so the theme domain comes from
360
+ // a theme's own name.
361
+ const domains = new Set();
362
+ for (const args of [
363
+ ['search', 'theme', '--limit', '60'],
364
+ ['search', 'neutral', '--type', 'theme'],
365
+ ]) {
366
+ const env = JSON.parse(
367
+ (await runCli(['--json', ...args], REPO_ROOT)).stdout,
368
+ );
369
+ const plain = (await runCli(args, REPO_ROOT)).stdout.split('\n');
370
+ const verbose = (
371
+ await runCli([...args, '--verbose'], REPO_ROOT)
372
+ ).stdout.split('\n');
373
+ for (const result of env.data.results) {
374
+ domains.add(result.domain);
375
+ for (const [key, value] of Object.entries(result)) {
376
+ if (value == null || value === '') continue;
377
+ const label = `${result.domain} ${result.name}: ${key}`;
378
+ expect(printsField(verbose, key, value), label).toBe(true);
379
+ if (key !== 'score' && key !== 'reason') {
380
+ expect(printsField(plain, key, value), label).toBe(true);
381
+ }
317
382
  }
318
383
  }
319
384
  }
320
- }
321
- expect(domains).toEqual(new Set(SEARCH_DOMAINS));
322
- }, 90_000);
323
-
324
- it('--verbose exits 0 and prints import/match detail', async () => {
325
- // Regression: the boolean verbose flag was named --detail, which collided
326
- // with the value-taking global `--detail <level>` — a bare `search
327
- // --detail` errored "argument missing" and the verbose output was
328
- // unreachable. It is now `--verbose`.
329
- const r = await runCli(['search', 'button', '--verbose'], REPO_ROOT);
330
- expect(r.status).toBe(0);
331
- expect(r.stdout).toContain('import:');
332
- // Ranking detail: pre-formatter this was a single `match: <reason> (score N)`
333
- // line; it's now separate `score:` / `reason:` record fields mirroring --json.
334
- expect(r.stdout).toContain('score:');
335
- expect(r.stdout).toContain('reason:');
336
- });
337
-
338
- it('points at discover for packages that could add more, except for hooks', async () => {
339
- const open = await runCli(['search', 'data', 'table'], REPO_ROOT);
340
- expect(open.status).toBe(0);
341
- expect(open.stdout).toMatch(/^More in packages you could add: .*discover 'data table'$/m);
342
- const none = await runCli(['search', 'zzqqxxnomatch'], REPO_ROOT);
343
- expect(none.stdout).toMatch(/^More in packages you could add: .*discover zzqqxxnomatch$/m);
344
- const hooks = await runCli(['search', 'click', '--type', 'hook'], REPO_ROOT);
345
- expect(hooks.status).toBe(0);
346
- expect(hooks.stdout).not.toContain('More in packages you could add');
347
- const json = await runCli(['--json', 'search', 'button'], REPO_ROOT);
348
- expect(json.stdout).not.toContain('More in packages you could add');
349
- });
350
-
351
- it('searches the docs and themes when no @astryxdesign/core is reachable, and exits 1 for --type component', async () => {
352
- const empty = fs.mkdtempSync(path.join(os.tmpdir(), 'astryx-search-cli-no-core-'));
353
- try {
354
- const open = await runCli(['--json', 'search', 'make', 'an', 'integration'], empty);
385
+ expect(domains).toEqual(new Set(SEARCH_DOMAINS));
386
+ }, 90_000);
387
+
388
+ it('--verbose exits 0 and prints import/match detail', async () => {
389
+ // Regression: the boolean verbose flag was named --detail, which collided
390
+ // with the value-taking global `--detail <level>` — a bare `search
391
+ // --detail` errored "argument missing" and the verbose output was
392
+ // unreachable. It is now `--verbose`.
393
+ const r = await runCli(['search', 'button', '--verbose'], REPO_ROOT);
394
+ expect(r.status).toBe(0);
395
+ expect(r.stdout).toContain('import:');
396
+ // Ranking detail: pre-formatter this was a single `match: <reason> (score N)`
397
+ // line; it's now separate `score:` / `reason:` record fields mirroring --json.
398
+ expect(r.stdout).toContain('score:');
399
+ expect(r.stdout).toContain('reason:');
400
+ });
401
+
402
+ it('points at discover for packages that could add more, except for hooks', async () => {
403
+ const open = await runCli(['search', 'data', 'table'], REPO_ROOT);
355
404
  expect(open.status).toBe(0);
356
- expect(JSON.parse(open.stdout).data.results[0]).toMatchObject({domain: 'doc'});
357
- // Bundled themes need no project, so an open search finds them too.
358
- const theme = await runCli(['--json', 'search', 'neutral'], empty);
359
- expect(theme.status).toBe(0);
360
- expect(JSON.parse(theme.stdout).data.results[0]).toMatchObject({
361
- domain: 'theme',
362
- name: 'neutral',
363
- });
364
- // The text says the search covered the docs and themes alone.
365
- const text = await runCli(['search', 'button'], empty);
366
- expect(text.status).toBe(0);
367
- expect(text.stdout).toContain('only the docs and themes were searched');
368
- // A themes-only search needs no core, like a docs-only one.
369
- const themes = await runCli(['--json', 'search', 'warm', '--type', 'theme'], empty);
370
- expect(themes.status).toBe(0);
371
- const found = JSON.parse(themes.stdout).data.results;
372
- expect(found.map(r => r.name)).toContain('neutral');
373
- expect(found.every(r => r.domain === 'theme')).toBe(true);
374
- const json = await runCli(['--json', 'search', 'button', '--type', 'component'], empty);
375
- expect(json.status).toBe(1);
376
- expect(JSON.parse(json.stdout)).toMatchObject({code: 'ERR_CORE_NOT_FOUND'});
377
- } finally {
378
- fs.rmSync(empty, {recursive: true, force: true});
379
- }
380
- });
381
- }, SCAN_TIMEOUT);
405
+ expect(open.stdout).toMatch(
406
+ /^More in packages you could add: .*discover 'data table'$/m,
407
+ );
408
+ const none = await runCli(['search', 'zzqqxxnomatch'], REPO_ROOT);
409
+ expect(none.stdout).toMatch(
410
+ /^More in packages you could add: .*discover zzqqxxnomatch$/m,
411
+ );
412
+ const hooks = await runCli(
413
+ ['search', 'click', '--type', 'hook'],
414
+ REPO_ROOT,
415
+ );
416
+ expect(hooks.status).toBe(0);
417
+ expect(hooks.stdout).not.toContain('More in packages you could add');
418
+ const json = await runCli(['--json', 'search', 'button'], REPO_ROOT);
419
+ expect(json.stdout).not.toContain('More in packages you could add');
420
+ });
421
+
422
+ it("finds components in an open search outside a project and in a fresh clone, reading the release's own Core", async () => {
423
+ const empty = fs.mkdtempSync(
424
+ path.join(os.tmpdir(), 'astryx-search-cli-carried-'),
425
+ );
426
+ const clone = fs.mkdtempSync(
427
+ path.join(os.tmpdir(), 'astryx-search-cli-clone-'),
428
+ );
429
+ // A fresh clone: it declares Core, with nothing installed.
430
+ fs.writeFileSync(
431
+ path.join(clone, 'package.json'),
432
+ JSON.stringify({
433
+ name: 'clone',
434
+ dependencies: {'@astryxdesign/core': '*'},
435
+ }),
436
+ );
437
+ try {
438
+ for (const dir of [empty, clone]) {
439
+ const run = await runCli(['--json', 'search', 'button'], dir);
440
+ expect(run.status).toBe(0);
441
+ const envelope = JSON.parse(run.stdout);
442
+ expect(envelope.meta?.partial).toBeUndefined();
443
+ expect(
444
+ envelope.data.results.some(
445
+ (/** @type {any} */ r) =>
446
+ r.domain === 'component' && r.name === 'Button',
447
+ ),
448
+ ).toBe(true);
449
+ }
450
+ } finally {
451
+ fs.rmSync(empty, {recursive: true, force: true});
452
+ fs.rmSync(clone, {recursive: true, force: true});
453
+ }
454
+ });
455
+
456
+ it('searches the docs and themes when no @astryxdesign/core is reachable, and exits 1 for --type component', async () => {
457
+ const empty = fs.mkdtempSync(
458
+ path.join(os.tmpdir(), 'astryx-search-cli-no-core-'),
459
+ );
460
+ // The project's own CLI answers here and no Core is installed; outside a
461
+ // project the carried Core would answer instead (AST-081 FR3).
462
+ linkThisCliAsProjectCli(empty);
463
+ try {
464
+ const open = await runCli(
465
+ ['--json', 'search', 'make', 'an', 'integration'],
466
+ empty,
467
+ );
468
+ expect(open.status).toBe(0);
469
+ expect(JSON.parse(open.stdout).data.results[0]).toMatchObject({
470
+ domain: 'doc',
471
+ });
472
+ // Never silently: JSON names what it skipped and why.
473
+ expect(JSON.parse(open.stdout).meta.partial).toMatchObject({
474
+ skipped: ['component', 'hook', 'template'],
475
+ reason: expect.stringContaining('@astryxdesign/core'),
476
+ });
477
+ // Bundled themes need no project, so an open search finds them too.
478
+ const theme = await runCli(['--json', 'search', 'neutral'], empty);
479
+ expect(theme.status).toBe(0);
480
+ expect(JSON.parse(theme.stdout).data.results[0]).toMatchObject({
481
+ domain: 'theme',
482
+ name: 'neutral',
483
+ });
484
+ // The text says the search covered the docs and themes alone.
485
+ const text = await runCli(['search', 'button'], empty);
486
+ expect(text.status).toBe(0);
487
+ expect(text.stdout).toContain('only the docs and themes were searched');
488
+ expect(text.stderr).toMatch(
489
+ /^astryx: components, hooks, and templates were not searched: /mu,
490
+ );
491
+ // A themes-only search needs no core, like a docs-only one.
492
+ const themes = await runCli(
493
+ ['--json', 'search', 'warm', '--type', 'theme'],
494
+ empty,
495
+ );
496
+ expect(themes.status).toBe(0);
497
+ const found = JSON.parse(themes.stdout).data.results;
498
+ expect(found.map(r => r.name)).toContain('neutral');
499
+ expect(found.every(r => r.domain === 'theme')).toBe(true);
500
+ const json = await runCli(
501
+ ['--json', 'search', 'button', '--type', 'component'],
502
+ empty,
503
+ );
504
+ expect(json.status).toBe(1);
505
+ expect(JSON.parse(json.stdout)).toMatchObject({
506
+ code: 'ERR_CORE_NOT_FOUND',
507
+ });
508
+ } finally {
509
+ fs.rmSync(empty, {recursive: true, force: true});
510
+ }
511
+ });
512
+ },
513
+ SCAN_TIMEOUT,
514
+ );