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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (373) hide show
  1. package/README.md +104 -71
  2. package/api/build/_adapter.d.mts +58 -0
  3. package/api/build/_adapter.mjs +127 -3
  4. package/api/build/blocks/blocks.d.mts +92 -0
  5. package/api/build/blocks/blocks.mjs +528 -0
  6. package/api/build/blocks/blocks.test.mjs +238 -0
  7. package/api/build/blocks/blocks.weights.json +1 -0
  8. package/api/build/build.mjs +25 -6
  9. package/api/build/build.type.mjs +1 -1
  10. package/api/build/kit/answer.d.mts +18 -0
  11. package/api/build/kit/answer.mjs +195 -0
  12. package/api/build/kit/answer.test.mjs +108 -0
  13. package/api/build/kit/kind.d.mts +25 -0
  14. package/api/build/kit/kind.mjs +170 -0
  15. package/api/build/kit/kit.d.mts +2 -4
  16. package/api/build/kit/kit.mjs +8 -398
  17. package/api/build/kit/rank.d.mts +3 -32
  18. package/api/build/kit/rank.mjs +25 -342
  19. package/api/build/kit/rank.test.mjs +2 -1
  20. package/api/build/kit/signals.d.mts +79 -0
  21. package/api/build/kit/signals.mjs +295 -0
  22. package/api/build/kit/start.d.mts +32 -0
  23. package/api/build/kit/start.mjs +163 -0
  24. package/api/build/kit/words.d.mts +72 -0
  25. package/api/build/kit/words.mjs +202 -0
  26. package/api/build/tree.d.mts +20 -0
  27. package/api/build/tree.mjs +53 -0
  28. package/api/build/tree.test.mjs +185 -0
  29. package/api/build/tree.weights.json +1 -0
  30. package/api/component/_adapter.mjs +39 -5
  31. package/api/component/component-replacement.test.mjs +1 -0
  32. package/api/component/component.mjs +17 -3
  33. package/api/component/component.test.mjs +15 -0
  34. package/api/component/doc-links.test.mjs +2 -2
  35. package/api/discover/_adapter.d.mts +1 -2
  36. package/api/discover/_adapter.mjs +42 -17
  37. package/api/discover/_adapter.test.mjs +42 -0
  38. package/api/discover/discover.mjs +0 -1
  39. package/api/docs/_adapter.d.mts +2 -2
  40. package/api/docs/_adapter.mjs +82 -23
  41. package/api/docs/docs-homes.test.mjs +127 -0
  42. package/api/docs/docs.doc.mjs +3 -3
  43. package/api/docs/docs.test.mjs +35 -19
  44. package/api/docs/integration-tree.test.mjs +20 -20
  45. package/api/docs/integrationDocs.test.mjs +1 -1
  46. package/api/docs/namespace-section.test.mjs +6 -6
  47. package/api/docs/reference-blocks.test.mjs +2 -2
  48. package/api/doctor/_adapter.d.mts +33 -13
  49. package/api/doctor/_adapter.mjs +55 -25
  50. package/api/doctor/doctor.d.mts +23 -5
  51. package/api/doctor/doctor.mjs +183 -40
  52. package/api/doctor/doctor.test.mjs +95 -12
  53. package/api/hook/hook.mjs +1 -1
  54. package/api/init/init.doc.mjs +20 -6
  55. package/api/init/init.test.mjs +18 -2
  56. package/api/init/init.type.d.mts +3 -3
  57. package/api/init/init.type.mjs +3 -3
  58. package/api/init/remove/remove.mjs +17 -4
  59. package/api/init/remove/remove.test.mjs +114 -2
  60. package/api/init/run/run.mjs +69 -6
  61. package/api/integration/_adapter.d.mts +8 -0
  62. package/api/integration/_adapter.mjs +21 -1
  63. package/api/integration/authoring-checks.mjs +83 -9
  64. package/api/integration/authoring-checks.test.mjs +220 -0
  65. package/api/integration/pack-check.mjs +123 -4
  66. package/api/integration/pack-check.test.mjs +87 -0
  67. package/api/integration/validate-integration-case-disk.test.mjs +111 -0
  68. package/api/integration/validate-integration-fixes.test.mjs +6 -5
  69. package/api/integration/validate-integration-gaps.test.mjs +1975 -0
  70. package/api/integration/validate-integration.d.mts +15 -2
  71. package/api/integration/validate-integration.mjs +298 -39
  72. package/api/integration/validate-integration.type.d.mts +90 -1
  73. package/api/integration/validate-integration.type.mjs +67 -2
  74. package/api/integration/validateIntegration.doc.mjs +1 -1
  75. package/api/search/ORDERING.md +28 -0
  76. package/api/search/_adapter.d.mts +82 -2
  77. package/api/search/_adapter.mjs +157 -10
  78. package/api/search/_adapter.test.mjs +173 -0
  79. package/api/search/_candidate-score.d.mts +40 -0
  80. package/api/search/_candidate-score.mjs +238 -0
  81. package/api/search/_cards.json +6098 -0
  82. package/api/search/_compound.d.mts +50 -0
  83. package/api/search/_compound.mjs +170 -0
  84. package/api/search/_doc-searcher.d.mts +4 -0
  85. package/api/search/_doc-searcher.mjs +23 -0
  86. package/api/search/_doc-searcher.test.mjs +36 -0
  87. package/api/search/_doc-searcher.weights.json +1 -0
  88. package/api/search/_doc-text.d.mts +30 -0
  89. package/api/search/_doc-text.mjs +87 -0
  90. package/api/search/_entries.d.mts +63 -0
  91. package/api/search/_entries.mjs +232 -0
  92. package/api/search/_entries.test.mjs +48 -0
  93. package/api/search/_features.d.mts +35 -0
  94. package/api/search/_features.mjs +155 -0
  95. package/api/search/_gather-components.d.mts +26 -0
  96. package/api/search/_gather-components.mjs +284 -0
  97. package/api/search/_gather-docs.d.mts +20 -0
  98. package/api/search/_gather-docs.mjs +254 -0
  99. package/api/search/_gather-hooks.d.mts +12 -0
  100. package/api/search/_gather-hooks.mjs +52 -0
  101. package/api/search/_gather-templates.d.mts +25 -0
  102. package/api/search/_gather-templates.mjs +107 -0
  103. package/api/search/_lexical.d.mts +135 -0
  104. package/api/search/_lexical.mjs +351 -0
  105. package/api/search/_main-searcher.d.mts +19 -0
  106. package/api/search/_main-searcher.mjs +115 -0
  107. package/api/search/_main-searcher.test.mjs +106 -0
  108. package/api/search/_main-searcher.weights.json +1 -0
  109. package/api/search/_module-doc.d.mts +32 -0
  110. package/api/search/_module-doc.mjs +78 -0
  111. package/api/search/_order.d.mts +32 -0
  112. package/api/search/_order.mjs +162 -0
  113. package/api/search/_order.test.mjs +72 -0
  114. package/api/search/_priority.d.mts +50 -0
  115. package/api/search/_priority.mjs +139 -0
  116. package/api/search/_query-words.d.mts +25 -0
  117. package/api/search/_query-words.mjs +92 -0
  118. package/api/search/_query.d.mts +65 -0
  119. package/api/search/_query.mjs +176 -0
  120. package/api/search/_result.d.mts +97 -0
  121. package/api/search/_result.mjs +150 -0
  122. package/api/search/_score.d.mts +60 -0
  123. package/api/search/_score.mjs +283 -0
  124. package/api/search/_synonyms.d.mts +21 -0
  125. package/api/search/_synonyms.mjs +67 -0
  126. package/api/search/_template-searcher.d.mts +4 -0
  127. package/api/search/_template-searcher.mjs +23 -0
  128. package/api/search/_template-searcher.test.mjs +40 -0
  129. package/api/search/_template-searcher.weights.json +1 -0
  130. package/api/search/_titles.d.mts +33 -0
  131. package/api/search/_titles.mjs +65 -0
  132. package/api/search/_words.d.mts +64 -0
  133. package/api/search/_words.mjs +157 -0
  134. package/api/search/search.d.mts +22 -305
  135. package/api/search/search.doc.mjs +8 -6
  136. package/api/search/search.mjs +56 -2482
  137. package/api/search/search.test.mjs +14 -14
  138. package/api/search/search.type.d.mts +6 -1
  139. package/api/search/search.type.mjs +6 -1
  140. package/api/search/tree.d.mts +15 -0
  141. package/api/search/tree.mjs +40 -0
  142. package/api/search/tree.test.mjs +66 -0
  143. package/api/search/tree.weights.json +1 -0
  144. package/api/template/template.mjs +5 -1
  145. package/api/theme/build/build.packed-old-core.test.mjs +1 -0
  146. package/api/upgrade/_adapter.d.mts +57 -9
  147. package/api/upgrade/_adapter.mjs +198 -28
  148. package/api/upgrade/registry/registry.d.mts +10 -4
  149. package/api/upgrade/registry/registry.mjs +33 -16
  150. package/api/upgrade/registry/registry.test.mjs +85 -0
  151. package/api/upgrade/run/run.d.mts +4 -2
  152. package/api/upgrade/run/run.mjs +13 -2
  153. package/api/upgrade/run/run.test.mjs +417 -3
  154. package/api/upgrade/upgrade.doc.mjs +10 -5
  155. package/api/upgrade/upgrade.mjs +13 -2
  156. package/api/upgrade/upgrade.test.mjs +194 -0
  157. package/api/upgrade/upgrade.type.d.mts +5 -3
  158. package/api/upgrade/upgrade.type.mjs +8 -6
  159. package/assets/codemods/transforms/v0.7.0/__tests__/validate-data-token-ownership.test.mjs +11 -0
  160. package/assets/codemods/transforms/v0.7.0/validate-data-token-ownership.mjs +3 -3
  161. package/assets/docs/author-a-theme.doc.mjs +2 -2
  162. package/assets/docs/is-my-code-good.doc.mjs +287 -0
  163. package/assets/docs/tree/add-a-component.doc.mjs +1 -1
  164. package/assets/docs/tree/add-a-theme.doc.mjs +1 -1
  165. package/assets/docs/tree/add-a-topic.doc.mjs +2 -2
  166. package/assets/docs/tree/agent-guidance.doc.mjs +1 -1
  167. package/assets/docs/tree/api.doc.mjs +6 -0
  168. package/assets/docs/tree/{integrations.doc.mjs → authoring.doc.mjs} +15 -6
  169. package/assets/docs/tree/block-template.doc.mjs +1 -1
  170. package/assets/docs/tree/build-the-template.doc.mjs +1 -1
  171. package/assets/docs/tree/building-blocks.doc.mjs +2 -2
  172. package/assets/docs/tree/check-the-props.doc.mjs +1 -1
  173. package/assets/docs/tree/check-your-docs.doc.mjs +2 -2
  174. package/assets/docs/tree/checks.doc.mjs +3 -3
  175. package/assets/docs/tree/ci-and-containers.doc.mjs +13 -0
  176. package/assets/docs/tree/cli-command.doc.mjs +1 -1
  177. package/assets/docs/tree/cli.doc.mjs +1 -1
  178. package/assets/docs/tree/codemods.doc.mjs +1 -1
  179. package/assets/docs/tree/component-family.doc.mjs +1 -1
  180. package/assets/docs/tree/component-imports.doc.mjs +1 -1
  181. package/assets/docs/tree/components.doc.mjs +1 -1
  182. package/assets/docs/tree/configuration.doc.mjs +1 -1
  183. package/assets/docs/tree/debug-and-gap-reports.doc.mjs +1 -1
  184. package/assets/docs/tree/define-the-theme.doc.mjs +1 -1
  185. package/assets/docs/tree/describe-the-component.doc.mjs +1 -1
  186. package/assets/docs/tree/docs.doc.mjs +1 -1
  187. package/assets/docs/tree/document-the-template.doc.mjs +1 -1
  188. package/assets/docs/tree/document-the-theme.doc.mjs +1 -1
  189. package/assets/docs/tree/export-template-assets.doc.mjs +1 -1
  190. package/assets/docs/tree/extend-or-replace.doc.mjs +1 -1
  191. package/assets/docs/{authoring.doc.mjs → tree/field-reference.doc.mjs} +4 -3
  192. package/assets/docs/tree/fonts-and-assets.doc.mjs +1 -1
  193. package/assets/docs/tree/generate-a-palette.doc.mjs +1 -1
  194. package/assets/docs/tree/global-integrations.doc.mjs +2 -2
  195. package/assets/docs/tree/grade-template-with-agent.doc.mjs +4 -4
  196. package/assets/docs/tree/help.doc.mjs +2 -2
  197. package/assets/docs/tree/host-entry.doc.mjs +128 -0
  198. package/assets/docs/tree/links.doc.mjs +1 -1
  199. package/assets/docs/tree/package-and-test.doc.mjs +1 -1
  200. package/assets/docs/tree/page-template.doc.mjs +1 -1
  201. package/assets/docs/tree/publishing.doc.mjs +1 -1
  202. package/assets/docs/tree/quick-start.doc.mjs +1 -1
  203. package/assets/docs/tree/replace-a-core-component.doc.mjs +1 -1
  204. package/assets/docs/tree/replace-a-core-template.doc.mjs +1 -1
  205. package/assets/docs/tree/sections-and-placement.doc.mjs +1 -1
  206. package/assets/docs/tree/see-it-in-an-app.doc.mjs +1 -1
  207. package/assets/docs/tree/ship.doc.mjs +2 -2
  208. package/assets/docs/tree/short-and-findable.doc.mjs +1 -1
  209. package/assets/docs/tree/single-component.doc.mjs +1 -1
  210. package/assets/docs/tree/start-a-template.doc.mjs +1 -1
  211. package/assets/docs/tree/subcomponent.doc.mjs +1 -1
  212. package/assets/docs/tree/template-assets.doc.mjs +1 -1
  213. package/assets/docs/tree/template-doc-overview.doc.mjs +2 -2
  214. package/assets/docs/tree/template-fonts.doc.mjs +1 -1
  215. package/assets/docs/tree/template-grading-rubric.doc.mjs +17 -38
  216. package/assets/docs/tree/template-icons.doc.mjs +1 -1
  217. package/assets/docs/tree/template-images-media.doc.mjs +1 -1
  218. package/assets/docs/tree/template-styles.doc.mjs +1 -1
  219. package/assets/docs/tree/templates.doc.mjs +1 -1
  220. package/assets/docs/tree/test-in-an-app.doc.mjs +1 -1
  221. package/assets/docs/tree/test-template-in-app.doc.mjs +1 -1
  222. package/assets/docs/tree/themes.doc.mjs +1 -1
  223. package/assets/docs/tree/tokens-data.doc.mjs +2 -2
  224. package/assets/docs/tree/troubleshooting.doc.mjs +1 -1
  225. package/assets/docs/tree/upgrading.doc.mjs +1 -1
  226. package/assets/docs/tree/use-a-theme-in-an-app.doc.mjs +1 -1
  227. package/assets/docs/tree/verify-packed-template.doc.mjs +1 -1
  228. package/assets/docs/tree/versioning.doc.mjs +5 -1
  229. package/assets/docs/tree/write-good-templates.doc.mjs +1 -1
  230. package/assets/docs/tree/write-the-template-file.doc.mjs +1 -1
  231. package/assets/docs/use-integrations.doc.mjs +120 -0
  232. package/assets/docs/working-with-ai.doc.mjs +6 -2
  233. package/assets/templates/themes/butter/butterTheme.ts +4 -2
  234. package/assets/templates/themes/stone/stoneTheme.ts +4 -2
  235. package/assets/templates/themes/y2k/y2kTheme.ts +4 -2
  236. package/authoring/debug/parse.d.mts +1 -1
  237. package/authoring/doctypes/schema/schema.doc.mjs +1 -1
  238. package/authoring/integration/integration.doc.mjs +1 -1
  239. package/authoring/integration/type.ts +1 -1
  240. package/clients/cli/bin/astryx.mjs +7 -78
  241. package/clients/cli/command-result-coverage.test.mjs +6 -5
  242. package/clients/cli/commands/build-theme.color-scheme.test.mjs +1 -1
  243. package/clients/cli/commands/{component/index.mjs → component.mjs} +37 -37
  244. package/clients/cli/commands/component.test.mjs +6 -6
  245. package/clients/cli/commands/docs.doc.mjs +2 -2
  246. package/clients/cli/commands/docs.mjs +1 -1
  247. package/clients/cli/commands/docs.test.mjs +14 -14
  248. package/clients/cli/commands/doctor-integration.test.mjs +49 -1
  249. package/clients/cli/commands/doctor.doc.mjs +1 -1
  250. package/clients/cli/commands/doctor.mjs +40 -0
  251. package/clients/cli/commands/doctor.test.mjs +65 -0
  252. package/clients/cli/commands/{hook/index.mjs → hook.mjs} +22 -22
  253. package/clients/cli/commands/import-hint-correctness.test.mjs +1 -1
  254. package/clients/cli/commands/init.behavior.test.mjs +230 -1
  255. package/clients/cli/commands/init.doc.mjs +28 -9
  256. package/clients/cli/commands/init.flag-help.test.mjs +1 -0
  257. package/clients/cli/commands/init.mjs +2 -2
  258. package/clients/cli/commands/integration-check.test.mjs +48 -0
  259. package/clients/cli/commands/integration.doc.mjs +1 -1
  260. package/clients/cli/commands/manifest.doc.mjs +1 -1
  261. package/clients/cli/commands/manifest.mjs +67 -0
  262. package/clients/cli/commands/outside-project.test.mjs +101 -1
  263. package/clients/cli/commands/postinstall.doc.mjs +23 -0
  264. package/clients/cli/commands/postinstall.mjs +31 -0
  265. package/clients/cli/commands/setup-nudge.test.mjs +78 -0
  266. package/clients/cli/commands/{build-theme.mjs → theme.mjs} +1 -1
  267. package/clients/cli/commands/upgrade.doc.mjs +4 -2
  268. package/clients/cli/commands/upgrade.mjs +9 -2
  269. package/clients/cli/commands/upgrade.subfolder-path.test.mjs +97 -0
  270. package/clients/cli/index.mjs +54 -94
  271. package/clients/cli/lib/component-format.mjs +1 -1
  272. package/clients/cli/lib/define-command.mjs +12 -7
  273. package/clients/cli/lib/exit-codes.test.mjs +5 -0
  274. package/clients/cli/lib/manifest.test.mjs +12 -0
  275. package/clients/cli/lib/welcome-box.mjs +50 -0
  276. package/clients/cli/run.mjs +108 -0
  277. package/foundation/agent-docs/agent-doc-state.d.mts +32 -7
  278. package/foundation/agent-docs/agent-doc-state.mjs +58 -9
  279. package/foundation/agent-docs/agent-docs.d.mts +100 -12
  280. package/foundation/agent-docs/agent-docs.mjs +1017 -105
  281. package/foundation/agent-docs/agent-docs.path-safety.test.mjs +1239 -17
  282. package/foundation/agent-docs/agent-docs.test.mjs +683 -39
  283. package/foundation/agent-docs/computer-agent-text.d.mts +39 -2
  284. package/foundation/agent-docs/computer-agent-text.mjs +135 -8
  285. package/foundation/agent-docs/computer-agent-text.test.mjs +99 -2
  286. package/foundation/config/project.mjs +20 -22
  287. package/foundation/config/project.test.mjs +56 -0
  288. package/foundation/discovery/authoring-self-docs.d.mts +7 -0
  289. package/foundation/discovery/authoring-self-docs.mjs +16 -4
  290. package/foundation/discovery/authoring-self-docs.test.mjs +15 -3
  291. package/foundation/discovery/authoring-surface.d.mts +1 -1
  292. package/foundation/discovery/authoring-surface.mjs +2 -2
  293. package/foundation/discovery/authoring-surface.test.mjs +1 -1
  294. package/foundation/discovery/cli-self-docs.d.mts +2 -1
  295. package/foundation/discovery/cli-self-docs.mjs +4 -3
  296. package/foundation/discovery/cli-self-docs.test.mjs +2 -2
  297. package/foundation/discovery/component-discovery.d.mts +37 -0
  298. package/foundation/discovery/component-discovery.mjs +80 -5
  299. package/foundation/discovery/component-loader.d.mts +1 -1
  300. package/foundation/discovery/docs-discovery.mjs +14 -1
  301. package/foundation/discovery/module-exports.d.mts +41 -0
  302. package/foundation/discovery/module-exports.mjs +253 -0
  303. package/foundation/discovery/runner-allowlist.d.mts +2 -4
  304. package/foundation/discovery/runner-allowlist.mjs +2 -4
  305. package/foundation/discovery/runner-commands.d.mts +77 -0
  306. package/foundation/discovery/runner-commands.mjs +294 -0
  307. package/foundation/discovery/runner-commands.test.mjs +128 -0
  308. package/foundation/discovery/stray-pointers.test.mjs +1 -1
  309. package/foundation/discovery/template-adapter.mjs +18 -1
  310. package/foundation/discovery/template-needs.mjs +1 -44
  311. package/foundation/discovery/theme-discovery.mjs +147 -3
  312. package/foundation/doc-compiler/doc-loads.test.mjs +33 -9
  313. package/foundation/doc-compiler/read.mjs +59 -1
  314. package/foundation/doc-compiler/read.test.mjs +11 -2
  315. package/foundation/doc-compiler/split-topic-keywords.test.mjs +3 -2
  316. package/foundation/doc-compiler/tree.d.mts +32 -6
  317. package/foundation/doc-compiler/tree.mjs +77 -43
  318. package/foundation/doc-compiler/tree.test.mjs +7 -7
  319. package/foundation/fs/paths.d.mts +70 -8
  320. package/foundation/fs/paths.mjs +556 -38
  321. package/foundation/fs/paths.test.mjs +430 -54
  322. package/foundation/fs/read-record.d.mts +18 -0
  323. package/foundation/fs/read-record.mjs +131 -0
  324. package/foundation/fs/read-record.test.mjs +77 -0
  325. package/foundation/integrations/autolink.d.mts +2 -1
  326. package/foundation/integrations/autolink.mjs +4 -13
  327. package/foundation/integrations/cli-peer-version.d.mts +36 -0
  328. package/foundation/integrations/cli-peer-version.mjs +132 -0
  329. package/foundation/integrations/cli-peer-version.test.mjs +354 -0
  330. package/foundation/integrations/computer-integrations.d.mts +6 -3
  331. package/foundation/integrations/computer-integrations.mjs +38 -9
  332. package/foundation/integrations/contribution-inventory.d.mts +15 -20
  333. package/foundation/integrations/contribution-inventory.mjs +41 -5
  334. package/foundation/integrations/exports-subpaths.d.mts +57 -0
  335. package/foundation/integrations/exports-subpaths.mjs +113 -0
  336. package/foundation/integrations/integrations.d.mts +27 -1
  337. package/foundation/integrations/integrations.mjs +61 -2
  338. package/foundation/integrations/public-imports.d.mts +118 -0
  339. package/foundation/integrations/public-imports.mjs +1769 -0
  340. package/foundation/integrations/public-imports.test.mjs +1793 -0
  341. package/foundation/integrations/validate-contributions.d.mts +115 -0
  342. package/foundation/integrations/validate-contributions.mjs +1168 -13
  343. package/foundation/release/cache.test.mjs +24 -21
  344. package/foundation/release/global-state.d.mts +8 -4
  345. package/foundation/release/global-state.mjs +4 -2
  346. package/foundation/release/handoff.d.mts +23 -4
  347. package/foundation/release/handoff.mjs +175 -8
  348. package/foundation/release/notices.d.mts +62 -8
  349. package/foundation/release/notices.mjs +136 -25
  350. package/foundation/release/notices.test.mjs +21 -0
  351. package/foundation/release/run-context.mjs +14 -0
  352. package/foundation/response/response-types.doc.mjs +1 -1
  353. package/foundation/route/route.d.mts +285 -0
  354. package/foundation/route/route.mjs +324 -0
  355. package/foundation/route/route.test.mjs +153 -0
  356. package/foundation/runtime/hosted-runtime.test.mjs +403 -0
  357. package/foundation/runtime/locked-versions.d.mts +32 -0
  358. package/foundation/runtime/locked-versions.mjs +65 -0
  359. package/foundation/runtime/package-root.d.mts +91 -0
  360. package/foundation/runtime/package-root.mjs +174 -0
  361. package/foundation/runtime/package-set.d.mts +62 -0
  362. package/foundation/runtime/package-set.mjs +130 -0
  363. package/foundation/runtime/package-set.test.mjs +432 -0
  364. package/foundation/text/string-utils.d.mts +2 -1
  365. package/foundation/text/string-utils.mjs +16 -5
  366. package/foundation/text/suggest-order.d.mts +72 -0
  367. package/foundation/text/suggest-order.mjs +363 -0
  368. package/foundation/text/suggest-order.test.mjs +259 -0
  369. package/host/door-conformance.test.mjs +274 -0
  370. package/host/index.d.ts +116 -0
  371. package/host/index.mjs +295 -0
  372. package/host/index.test.mjs +302 -0
  373. package/package.json +14 -9
@@ -225,12 +225,19 @@ export function peerProblem(pkg, release) {
225
225
 
226
226
  /**
227
227
  * The computer-level integrations installed beside the CLI at `cliRoot`, in
228
- * name order (declaration order when the root declares them). Reads
228
+ * name order (declaration order when the root declares them), then each
229
+ * integration a host approves (`approved`, architecture:cli-surface INV30),
230
+ * resolved by name from the package set that holds that CLI alone. Reads
229
231
  * package.json files only.
230
- * @param {{cliRoot: string, release: string, cacheDir?: string}} options
232
+ * @param {{cliRoot: string, release: string, cacheDir?: string, approved?: readonly string[]}} options
231
233
  * @returns {ComputerIntegrationCandidate[]}
232
234
  */
233
- export function scanComputerIntegrations({cliRoot, release, cacheDir}) {
235
+ export function scanComputerIntegrations({
236
+ cliRoot,
237
+ release,
238
+ cacheDir,
239
+ approved = [],
240
+ }) {
234
241
  const self = real(cliRoot);
235
242
  /** @type {ComputerIntegrationCandidate[]} */
236
243
  const found = [];
@@ -238,21 +245,43 @@ export function scanComputerIntegrations({cliRoot, release, cacheDir}) {
238
245
  for (const root of computerInstallRoots(cliRoot, {cacheDir})) {
239
246
  scanRoot(root, {self, release, found, seen});
240
247
  }
248
+ const owner = approved.length > 0 ? installPlaceOf(self).owner : null;
249
+ if (owner) scanRoot(owner, {self, release, found, seen}, approved);
241
250
  return found;
242
251
  }
243
252
 
244
253
  /**
245
- * Add the integrations installed in one install root.
254
+ * `name` as installed in `root`'s own `node_modules`, never in a folder above.
255
+ * @param {string} name
256
+ * @param {string} root
257
+ * @returns {{packageDir: string, hostDir: string} | null}
258
+ */
259
+ function ownPackageDir(name, root) {
260
+ const packageDir = path.join(root, 'node_modules', ...name.split('/'));
261
+ return fs.existsSync(path.join(packageDir, 'package.json'))
262
+ ? {packageDir, hostDir: root}
263
+ : null;
264
+ }
265
+
266
+ /**
267
+ * Add the integrations installed in one install root: those it declares or
268
+ * installs, or, given `approved`, those names installed in its own
269
+ * `node_modules`.
246
270
  * @param {string} root
247
271
  * @param {{self: string, release: string, found: ComputerIntegrationCandidate[], seen: Set<string>}} into
272
+ * @param {readonly string[] | null} [approved]
248
273
  */
249
- function scanRoot(root, {self, release, found, seen}) {
250
- const declared = readPackage(root)
251
- ? readDeclaredDependencies(root).map(d => d.name)
252
- : installedNames(root);
274
+ function scanRoot(root, {self, release, found, seen}, approved = null) {
275
+ const declared =
276
+ approved ??
277
+ (readPackage(root)
278
+ ? readDeclaredDependencies(root).map(d => d.name)
279
+ : installedNames(root));
253
280
  for (const name of declared) {
254
281
  if (NOT_INTEGRATIONS.has(name)) continue;
255
- const resolved = resolveInstalledPackageDir(name, root);
282
+ const resolved = approved
283
+ ? ownPackageDir(name, root)
284
+ : resolveInstalledPackageDir(name, root);
256
285
  if (!resolved) continue;
257
286
  const identity = real(resolved.packageDir);
258
287
  if (identity === self || seen.has(identity)) continue;
@@ -1,6 +1,21 @@
1
1
  // @generated by scripts/sync-api-types.mjs from the JSDoc in foundation/**/*.mjs.
2
2
  // DO NOT EDIT — run `pnpm sync:api-types` to regenerate.
3
3
 
4
+ /**
5
+ * The contribution files readers read under each declared root that is a
6
+ * readable folder, package-relative, without importing anything. A folder
7
+ * inside a root that cannot be listed is skipped.
8
+ * @param {{components?: string, templates?: string, codemods?: string, docs?: string, themes?: string}} loaded
9
+ * @param {string} pkgDir
10
+ * @returns {string[]}
11
+ */
12
+ export function contributionFiles(loaded: {
13
+ components?: string;
14
+ templates?: string;
15
+ codemods?: string;
16
+ docs?: string;
17
+ themes?: string;
18
+ }, pkgDir: string): string[];
4
19
  /**
5
20
  * @typedef {object} FileInventoryRoot
6
21
  * @property {'themes'|'components'|'templates'|'codemods'|'docs'} kind
@@ -22,26 +37,6 @@
22
37
  * @returns {FileInventory}
23
38
  */
24
39
  export function computeRequiredFiles(loaded: import("./integrations.mjs").LoadedIntegration): FileInventory;
25
- /**
26
- * @typedef {object} ContributionIdentities
27
- * @property {{slug: string, exportName: string}[]} themes
28
- * @property {string[]} components
29
- * @property {{id: string, type: string, name: string, replaces?: string}[]} templates
30
- * @property {{version: string, id: string}[]} codemods
31
- * @property {string[]} docs
32
- * @property {string[]} agentDocsAppend
33
- */
34
- /**
35
- * The resolved fields needed to discover contribution identities.
36
- * @typedef {object} IdentitySource
37
- * @property {string} name
38
- * @property {string} [themes]
39
- * @property {string} [components]
40
- * @property {string} [templates]
41
- * @property {string} [codemods]
42
- * @property {string} [docs]
43
- * @property {{append?: readonly string[]}} [agentDocs]
44
- */
45
40
  /**
46
41
  * Run the same discovery functions a consumer uses to collect every
47
42
  * contribution identity from a loaded integration.
@@ -137,6 +137,29 @@ const ROOT_KINDS = /** @type {const} */ ([
137
137
  'docs',
138
138
  ]);
139
139
 
140
+ /**
141
+ * The contribution files readers read under each declared root that is a
142
+ * readable folder, package-relative, without importing anything. A folder
143
+ * inside a root that cannot be listed is skipped.
144
+ * @param {{components?: string, templates?: string, codemods?: string, docs?: string, themes?: string}} loaded
145
+ * @param {string} pkgDir
146
+ * @returns {string[]}
147
+ */
148
+ export function contributionFiles(loaded, pkgDir) {
149
+ /** @type {Set<string>} */
150
+ const files = new Set();
151
+ for (const kind of ROOT_KINDS) {
152
+ const root = loaded[kind];
153
+ if (!root || !isFolder(root)) continue;
154
+ try {
155
+ for (const file of ENUMERATORS[kind](root, pkgDir)) files.add(file);
156
+ } catch {
157
+ // An unreadable folder inside the root.
158
+ }
159
+ }
160
+ return [...files].sort();
161
+ }
162
+
140
163
  // ── Public: static file inventory ────────────────────────────────
141
164
 
142
165
  /**
@@ -207,6 +230,19 @@ export function computeRequiredFiles(loaded) {
207
230
  * @property {{append?: readonly string[]}} [agentDocs]
208
231
  */
209
232
 
233
+ /**
234
+ * Whether `dir` is a folder to list. A declared root that is a file is
235
+ * reported by the root check; discovery finds nothing in it.
236
+ * @param {string} dir
237
+ */
238
+ function isFolder(dir) {
239
+ try {
240
+ return fs.statSync(dir).isDirectory();
241
+ } catch {
242
+ return false;
243
+ }
244
+ }
245
+
210
246
  /**
211
247
  * Run the same discovery functions a consumer uses to collect every
212
248
  * contribution identity from a loaded integration.
@@ -229,7 +265,7 @@ export async function collectIdentities(loaded) {
229
265
  /** @type {Array<{kind: string, message: string}>} */
230
266
  const errors = [];
231
267
 
232
- if (loaded.themes && fs.existsSync(loaded.themes)) {
268
+ if (loaded.themes && isFolder(loaded.themes)) {
233
269
  try {
234
270
  const themes = await discoverIntegrationThemes(
235
271
  /** @type {import('./integrations.mjs').LoadedIntegration} */ (loaded),
@@ -245,7 +281,7 @@ export async function collectIdentities(loaded) {
245
281
  }
246
282
  }
247
283
 
248
- if (loaded.components && fs.existsSync(loaded.components)) {
284
+ if (loaded.components && isFolder(loaded.components)) {
249
285
  try {
250
286
  const records = discoverIntegrationComponents(loaded) ?? [];
251
287
  // A family doc backs several records: load it, and report it, once.
@@ -275,7 +311,7 @@ export async function collectIdentities(loaded) {
275
311
  }
276
312
  }
277
313
 
278
- if (loaded.templates && fs.existsSync(loaded.templates)) {
314
+ if (loaded.templates && isFolder(loaded.templates)) {
279
315
  try {
280
316
  const {templates, errors: templateErrors} =
281
317
  await discoverIntegrationTemplatesForOne(loaded);
@@ -298,7 +334,7 @@ export async function collectIdentities(loaded) {
298
334
  }
299
335
  }
300
336
 
301
- if (loaded.codemods && fs.existsSync(loaded.codemods)) {
337
+ if (loaded.codemods && isFolder(loaded.codemods)) {
302
338
  try {
303
339
  const byVersion = await discoverIntegrationCodemods([loaded]);
304
340
  /** @type {{version: string, id: string}[]} */
@@ -317,7 +353,7 @@ export async function collectIdentities(loaded) {
317
353
  }
318
354
  }
319
355
 
320
- if (loaded.docs && fs.existsSync(loaded.docs)) {
356
+ if (loaded.docs && isFolder(loaded.docs)) {
321
357
  try {
322
358
  const {
323
359
  records,
@@ -0,0 +1,57 @@
1
+ // @generated by scripts/sync-api-types.mjs from the JSDoc in foundation/**/*.mjs.
2
+ // DO NOT EDIT — run `pnpm sync:api-types` to regenerate.
3
+
4
+ /**
5
+ * @file How a package.json `exports` field declares a subpath, read without
6
+ * touching the targets (which may be build output that does not exist yet).
7
+ *
8
+ * @input A package.json `exports` value and a `.`/`./…` subpath.
9
+ * @output Whether the subpath is declared for an ES module importer, and why
10
+ * not when it is not.
11
+ * @position foundation/integrations; read by the public-import checks.
12
+ */
13
+ /** @param {unknown} value @returns {value is Record<string, unknown>} */
14
+ export function isRecord(value: unknown): value is Record<string, unknown>;
15
+ /**
16
+ * Whether an export value gives an ES module importer something: a string,
17
+ * or a condition object / fallback array with a target under a condition
18
+ * other than `require` and `types`.
19
+ * @param {unknown} value
20
+ * @returns {boolean}
21
+ */
22
+ export function importable(value: unknown): boolean;
23
+ /**
24
+ * Every string target an export value names, any condition.
25
+ * @param {unknown} value
26
+ * @param {string[]} [out]
27
+ * @returns {string[]}
28
+ */
29
+ export function allTargets(value: unknown, out?: string[]): string[];
30
+ /**
31
+ * @typedef {object} SubpathDeclaration
32
+ * @property {'declared'|'no_exports'|'root_only'|'missing'|'blocked'|'not_importable'} state
33
+ * @property {unknown} [value] the matched export value
34
+ * @property {string} [wildcard] what a `*` key matched
35
+ */
36
+ /**
37
+ * How a package's `exports` field declares one subpath, without touching the
38
+ * targets: a missing key, a key with only a `require` condition, an `exports`
39
+ * that is a bare string or a condition object (so only `.`), a wildcard that
40
+ * does not match, or a `null` that blocks it. An `exports` of `null` is no
41
+ * `exports` field, as Node reads it.
42
+ * @param {unknown} exportsField
43
+ * @param {string} subpath `.` or `./…`
44
+ * @returns {SubpathDeclaration}
45
+ */
46
+ export function subpathDeclaration(exportsField: unknown, subpath: string): SubpathDeclaration;
47
+ export type SubpathDeclaration = {
48
+ state: "declared" | "no_exports" | "root_only" | "missing" | "blocked" | "not_importable";
49
+ /**
50
+ * the matched export value
51
+ */
52
+ value?: unknown;
53
+ /**
54
+ * what a `*` key matched
55
+ */
56
+ wildcard?: string | undefined;
57
+ };
@@ -0,0 +1,113 @@
1
+ // Copyright (c) Meta Platforms, Inc. and affiliates.
2
+
3
+ /**
4
+ * @file How a package.json `exports` field declares a subpath, read without
5
+ * touching the targets (which may be build output that does not exist yet).
6
+ *
7
+ * @input A package.json `exports` value and a `.`/`./…` subpath.
8
+ * @output Whether the subpath is declared for an ES module importer, and why
9
+ * not when it is not.
10
+ * @position foundation/integrations; read by the public-import checks.
11
+ */
12
+
13
+ /** @param {unknown} value @returns {value is Record<string, unknown>} */
14
+ export function isRecord(value) {
15
+ return value !== null && typeof value === 'object' && !Array.isArray(value);
16
+ }
17
+
18
+ /**
19
+ * Whether an export value gives an ES module importer something: a string,
20
+ * or a condition object / fallback array with a target under a condition
21
+ * other than `require` and `types`.
22
+ * @param {unknown} value
23
+ * @returns {boolean}
24
+ */
25
+ export function importable(value) {
26
+ if (typeof value === 'string') return true;
27
+ if (Array.isArray(value)) return value.some(importable);
28
+ if (!isRecord(value)) return false;
29
+ return Object.entries(value).some(
30
+ ([condition, target]) =>
31
+ condition !== 'require' && condition !== 'types' && importable(target),
32
+ );
33
+ }
34
+
35
+ /**
36
+ * Every string target an export value names, any condition.
37
+ * @param {unknown} value
38
+ * @param {string[]} [out]
39
+ * @returns {string[]}
40
+ */
41
+ export function allTargets(value, out = []) {
42
+ if (typeof value === 'string') out.push(value);
43
+ else if (Array.isArray(value)) for (const v of value) allTargets(v, out);
44
+ else if (isRecord(value))
45
+ for (const v of Object.values(value)) allTargets(v, out);
46
+ return out;
47
+ }
48
+
49
+ /**
50
+ * @typedef {object} SubpathDeclaration
51
+ * @property {'declared'|'no_exports'|'root_only'|'missing'|'blocked'|'not_importable'} state
52
+ * @property {unknown} [value] the matched export value
53
+ * @property {string} [wildcard] what a `*` key matched
54
+ */
55
+
56
+ /**
57
+ * How a package's `exports` field declares one subpath, without touching the
58
+ * targets: a missing key, a key with only a `require` condition, an `exports`
59
+ * that is a bare string or a condition object (so only `.`), a wildcard that
60
+ * does not match, or a `null` that blocks it. An `exports` of `null` is no
61
+ * `exports` field, as Node reads it.
62
+ * @param {unknown} exportsField
63
+ * @param {string} subpath `.` or `./…`
64
+ * @returns {SubpathDeclaration}
65
+ */
66
+ export function subpathDeclaration(exportsField, subpath) {
67
+ if (exportsField == null) return {state: 'no_exports'};
68
+ const keys = isRecord(exportsField) ? Object.keys(exportsField) : [];
69
+ // An empty object is a subpath map with no keys: it exports nothing, so
70
+ // every subpath (the root too) is missing from it.
71
+ const isMap =
72
+ isRecord(exportsField) && keys.every(key => key.startsWith('.'));
73
+ if (!isMap) {
74
+ if (subpath !== '.') return {state: 'root_only'};
75
+ return importable(exportsField)
76
+ ? {state: 'declared', value: exportsField}
77
+ : {state: 'not_importable', value: exportsField};
78
+ }
79
+ const map = /** @type {Record<string, unknown>} */ (exportsField);
80
+ /** @type {{value: unknown, wildcard: string} | null} */
81
+ let hit = null;
82
+ if (Object.hasOwn(map, subpath)) {
83
+ hit = {value: map[subpath], wildcard: ''};
84
+ } else {
85
+ const patterns = keys
86
+ .filter(key => key.split('*').length === 2)
87
+ .sort((a, b) => b.indexOf('*') - a.indexOf('*') || b.length - a.length);
88
+ for (const pattern of patterns) {
89
+ const star = pattern.indexOf('*');
90
+ const prefix = pattern.slice(0, star);
91
+ const suffix = pattern.slice(star + 1);
92
+ if (
93
+ subpath.length >= prefix.length + suffix.length &&
94
+ subpath.startsWith(prefix) &&
95
+ subpath.endsWith(suffix)
96
+ ) {
97
+ hit = {
98
+ value: map[pattern],
99
+ wildcard: subpath.slice(
100
+ prefix.length,
101
+ subpath.length - suffix.length,
102
+ ),
103
+ };
104
+ break;
105
+ }
106
+ }
107
+ }
108
+ if (!hit) return {state: 'missing'};
109
+ if (hit.value == null) return {state: 'blocked'};
110
+ if (!importable(hit.value))
111
+ return {state: 'not_importable', value: hit.value, wildcard: hit.wildcard};
112
+ return {state: 'declared', value: hit.value, wildcard: hit.wildcard};
113
+ }
@@ -45,13 +45,14 @@ export function findManifestPaths(dir: string): string[];
45
45
  * @param {string} file absolute manifest path
46
46
  * @param {string} [label] used in error messages
47
47
  * @param {{fresh?: boolean}} [options]
48
- * @returns {Promise<{manifest: import('../../authoring/integration/type').AstryxIntegration, unknownKeys: string[], debug?: import('../../authoring/debug/type').DebugEventHandler, gapReport?: import('../../authoring/gap-report/type').GapReportHandler, gapReportError?: string, discover?: import('../../authoring/discover/type').DiscoverSource, discoverError?: string, agentDocsError?: string, cliCommandError?: string}>}
48
+ * @returns {Promise<{manifest: import('../../authoring/integration/type').AstryxIntegration, unknownKeys: string[], declaredKeys: string[], debug?: import('../../authoring/debug/type').DebugEventHandler, gapReport?: import('../../authoring/gap-report/type').GapReportHandler, gapReportError?: string, discover?: import('../../authoring/discover/type').DiscoverSource, discoverError?: string, agentDocsError?: string, cliCommandError?: string}>}
49
49
  */
50
50
  export function loadManifest(file: string, label?: string, { fresh }?: {
51
51
  fresh?: boolean;
52
52
  }): Promise<{
53
53
  manifest: import("../../authoring/integration/type").AstryxIntegration;
54
54
  unknownKeys: string[];
55
+ declaredKeys: string[];
55
56
  debug?: import("../../authoring/debug/type").DebugEventHandler;
56
57
  gapReport?: import("../../authoring/gap-report/type").GapReportHandler;
57
58
  gapReportError?: string;
@@ -79,6 +80,17 @@ export function loadManifestObject(file: string, label?: string, options?: {
79
80
  * @param {string} [cwd]
80
81
  */
81
82
  export function resolvePackageDir(packageName: string, cwd?: string): string;
83
+ /**
84
+ * Where an integration named in astryx.config is installed: the nearest
85
+ * `node_modules/<name>` at or above `cwd`, found the way an autolinked
86
+ * dependency is, so a package a workspace hoists to its root loads in every
87
+ * member. When it is installed nowhere, `cwd`'s own `node_modules/<name>`,
88
+ * which the caller reports as not installed.
89
+ * @param {string} packageName
90
+ * @param {string} [cwd]
91
+ * @returns {string}
92
+ */
93
+ export function resolveInstalledIntegrationDir(packageName: string, cwd?: string): string;
82
94
  /**
83
95
  * Load the integration package rooted at `packageDir` directly, without first
84
96
  * installing or publishing it. This is the self-resolution seam: an author in
@@ -134,6 +146,8 @@ export function loadIntegrations(specs?: string[], { cwd, fresh, resolveProvider
134
146
  * @property {string} [__agentDocsError] contribution-specific validation
135
147
  * failure; other manifest contributions remain available
136
148
  * @property {string} __spec
149
+ * @property {string} [__declaredProviderId] the manifest's `providerId` as
150
+ * written, kept by validation so it can tell whether the package has one
137
151
  * @property {string} __packageDir
138
152
  * @property {Record<string, unknown>|null} [__packageExports] the owning
139
153
  * package's `exports` map, kept from the package.json this loader already
@@ -145,6 +159,8 @@ export function loadIntegrations(specs?: string[], { cwd, fresh, resolveProvider
145
159
  * set on a package whose provider ID an earlier-loaded package already
146
160
  * claims; it contributes nothing and is reported as a `duplicate_provider`
147
161
  * warning
162
+ * @property {string[]} [__declaredKeys] the keys the manifest object itself
163
+ * declares, before any is filled in (providerId from the package name)
148
164
  * @property {string[]} [__unknownKeys] manifest keys this CLI does not know —
149
165
  * surfaced as a warning; the rest of the manifest still contributes
150
166
  * @property {boolean} [__autolinked] loaded because the project declares the
@@ -210,6 +226,11 @@ export type LoadedIntegration = {
210
226
  */
211
227
  __agentDocsError?: string | undefined;
212
228
  __spec: string;
229
+ /**
230
+ * the manifest's `providerId` as
231
+ * written, kept by validation so it can tell whether the package has one
232
+ */
233
+ __declaredProviderId?: string | undefined;
213
234
  __packageDir: string;
214
235
  /**
215
236
  * the owning
@@ -233,6 +254,11 @@ export type LoadedIntegration = {
233
254
  claimedBy: string;
234
255
  message: string;
235
256
  } | undefined;
257
+ /**
258
+ * the keys the manifest object itself
259
+ * declares, before any is filled in (providerId from the package name)
260
+ */
261
+ __declaredKeys?: string[] | undefined;
236
262
  /**
237
263
  * manifest keys this CLI does not know —
238
264
  * surfaced as a warning; the rest of the manifest still contributes
@@ -19,6 +19,7 @@
19
19
  import * as fs from 'node:fs';
20
20
  import * as path from 'node:path';
21
21
  import {assertWithin} from '../fs/path-safety.mjs';
22
+ import {findNodeModulesPackage} from '../fs/paths.mjs';
22
23
  import {normalizeProviderId} from '../identity/provider-identity.mjs';
23
24
  // The key census and contribution parsers are internal to the schema module on
24
25
  // purpose: the public parser still validates the complete authored type, while
@@ -60,6 +61,8 @@ import {resolveProviders} from './provider-resolution.mjs';
60
61
  * @property {string} [__agentDocsError] contribution-specific validation
61
62
  * failure; other manifest contributions remain available
62
63
  * @property {string} __spec
64
+ * @property {string} [__declaredProviderId] the manifest's `providerId` as
65
+ * written, kept by validation so it can tell whether the package has one
63
66
  * @property {string} __packageDir
64
67
  * @property {Record<string, unknown>|null} [__packageExports] the owning
65
68
  * package's `exports` map, kept from the package.json this loader already
@@ -71,6 +74,8 @@ import {resolveProviders} from './provider-resolution.mjs';
71
74
  * set on a package whose provider ID an earlier-loaded package already
72
75
  * claims; it contributes nothing and is reported as a `duplicate_provider`
73
76
  * warning
77
+ * @property {string[]} [__declaredKeys] the keys the manifest object itself
78
+ * declares, before any is filled in (providerId from the package name)
74
79
  * @property {string[]} [__unknownKeys] manifest keys this CLI does not know —
75
80
  * surfaced as a warning; the rest of the manifest still contributes
76
81
  * @property {boolean} [__autolinked] loaded because the project declares the
@@ -213,7 +218,7 @@ function parseDiscoverSourceExport(value, label) {
213
218
  * @param {string} file absolute manifest path
214
219
  * @param {string} [label] used in error messages
215
220
  * @param {{fresh?: boolean}} [options]
216
- * @returns {Promise<{manifest: import('../../authoring/integration/type').AstryxIntegration, unknownKeys: string[], debug?: import('../../authoring/debug/type').DebugEventHandler, gapReport?: import('../../authoring/gap-report/type').GapReportHandler, gapReportError?: string, discover?: import('../../authoring/discover/type').DiscoverSource, discoverError?: string, agentDocsError?: string, cliCommandError?: string}>}
221
+ * @returns {Promise<{manifest: import('../../authoring/integration/type').AstryxIntegration, unknownKeys: string[], declaredKeys: string[], debug?: import('../../authoring/debug/type').DebugEventHandler, gapReport?: import('../../authoring/gap-report/type').GapReportHandler, gapReportError?: string, discover?: import('../../authoring/discover/type').DiscoverSource, discoverError?: string, agentDocsError?: string, cliCommandError?: string}>}
217
222
  */
218
223
  export async function loadManifest(
219
224
  file,
@@ -222,6 +227,7 @@ export async function loadManifest(
222
227
  ) {
223
228
  const mod = await importUserModule(file, {fresh});
224
229
  const raw = mod?.default;
230
+ if (raw === undefined) throw new Error(noDefaultExportMessage(file, label, mod));
225
231
  const baseManifest = parseIntegrationBase(raw, label);
226
232
  const gapReport = parseGapReportHandlerExport(mod?.gapReport, label);
227
233
  const discover = parseDiscoverSourceExport(mod?.discover, label);
@@ -274,6 +280,8 @@ export async function loadManifest(
274
280
  ...(cliCommand == null ? {} : {cliCommand}),
275
281
  },
276
282
  unknownKeys: unknownIntegrationKeys(raw),
283
+ declaredKeys:
284
+ raw != null && typeof raw === 'object' ? Object.keys(raw) : [],
277
285
  debug:
278
286
  typeof mod?.debug === 'function'
279
287
  ? /** @type {import('../../authoring/debug/type').DebugEventHandler} */ (
@@ -289,6 +297,30 @@ export async function loadManifest(
289
297
  };
290
298
  }
291
299
 
300
+ /**
301
+ * Why a manifest has no default export, said plainly instead of as a schema
302
+ * failure on `undefined`: the file is empty, or it exports other names.
303
+ * @param {string} file
304
+ * @param {string} label
305
+ * @param {Record<string, unknown> | undefined} mod
306
+ */
307
+ function noDefaultExportMessage(file, label, mod) {
308
+ const fix = `Fix: export the manifest object as the default export: \`export default {components: './components'}\`, naming the roots the package has.`;
309
+ let text = '';
310
+ try {
311
+ text = fs.readFileSync(file, 'utf8');
312
+ } catch {
313
+ // Unreadable now: say only what is missing.
314
+ }
315
+ const names = Object.keys(mod ?? {}).filter(name => name !== 'default');
316
+ if (text.trim() === '') return `${label} is empty. ${fix}`;
317
+ const found =
318
+ names.length > 0
319
+ ? ` (it exports ${names.map(name => `\`${name}\``).join(', ')})`
320
+ : '';
321
+ return `${label} has no default export${found}. ${fix}`;
322
+ }
323
+
292
324
  /**
293
325
  * Load and validate a manifest module's default export against the integration
294
326
  * schema. Throws if the default export is missing or invalid. Exposed for
@@ -337,6 +369,25 @@ export function resolvePackageDir(packageName, cwd = process.cwd()) {
337
369
  return dir;
338
370
  }
339
371
 
372
+ /**
373
+ * Where an integration named in astryx.config is installed: the nearest
374
+ * `node_modules/<name>` at or above `cwd`, found the way an autolinked
375
+ * dependency is, so a package a workspace hoists to its root loads in every
376
+ * member. When it is installed nowhere, `cwd`'s own `node_modules/<name>`,
377
+ * which the caller reports as not installed.
378
+ * @param {string} packageName
379
+ * @param {string} [cwd]
380
+ * @returns {string}
381
+ */
382
+ export function resolveInstalledIntegrationDir(
383
+ packageName,
384
+ cwd = process.cwd(),
385
+ ) {
386
+ // Validates the name first: it throws on a spec with path segments.
387
+ const ownDir = resolvePackageDir(packageName, cwd);
388
+ return findNodeModulesPackage(cwd, packageName)?.packageDir ?? ownDir;
389
+ }
390
+
340
391
  /**
341
392
  * Find the single conventional root manifest sibling of the package's
342
393
  * package.json. Multiple present is a hard error; missing is a hard error.
@@ -396,6 +447,8 @@ export async function loadLocalIntegration(packageDir, {fresh = false} = {}) {
396
447
  let manifest;
397
448
  /** @type {string[]} */
398
449
  let unknownKeys;
450
+ /** @type {string[]} */
451
+ let declaredKeys;
399
452
  /** @type {import('../../authoring/debug/type').DebugEventHandler | undefined} */
400
453
  let debugHandler;
401
454
  /** @type {import('../../authoring/gap-report/type').GapReportHandler | undefined} */
@@ -414,6 +467,7 @@ export async function loadLocalIntegration(packageDir, {fresh = false} = {}) {
414
467
  ({
415
468
  manifest,
416
469
  unknownKeys,
470
+ declaredKeys,
417
471
  debug: debugHandler,
418
472
  gapReport: gapReportHandler,
419
473
  gapReportError,
@@ -462,6 +516,7 @@ export async function loadLocalIntegration(packageDir, {fresh = false} = {}) {
462
516
  __cliCommandError: cliCommandError,
463
517
  __agentDocsError: agentDocsError,
464
518
  __unknownKeys: unknownKeys,
519
+ __declaredKeys: declaredKeys,
465
520
  __debug: debugHandler,
466
521
  __gapReport: gapReportHandler,
467
522
  __gapReportError: gapReportError,
@@ -496,7 +551,7 @@ export async function loadIntegrations(
496
551
  if (!spec || seenSpecs.has(spec)) continue;
497
552
  seenSpecs.add(spec);
498
553
 
499
- const packageDir = resolvePackageDir(spec, cwd);
554
+ const packageDir = resolveInstalledIntegrationDir(spec, cwd);
500
555
  const pkgPath = path.join(packageDir, 'package.json');
501
556
  let pkg;
502
557
  try {
@@ -512,6 +567,8 @@ export async function loadIntegrations(
512
567
  let manifest;
513
568
  /** @type {string[]} */
514
569
  let unknownKeys;
570
+ /** @type {string[]} */
571
+ let declaredKeys;
515
572
  /** @type {import('../../authoring/debug/type').DebugEventHandler | undefined} */
516
573
  let debugHandler;
517
574
  /** @type {import('../../authoring/gap-report/type').GapReportHandler | undefined} */
@@ -530,6 +587,7 @@ export async function loadIntegrations(
530
587
  ({
531
588
  manifest,
532
589
  unknownKeys,
590
+ declaredKeys,
533
591
  debug: debugHandler,
534
592
  gapReport: gapReportHandler,
535
593
  gapReportError,
@@ -585,6 +643,7 @@ export async function loadIntegrations(
585
643
  __cliCommandError: cliCommandError,
586
644
  __agentDocsError: agentDocsError,
587
645
  __unknownKeys: unknownKeys,
646
+ __declaredKeys: declaredKeys,
588
647
  __debug: debugHandler,
589
648
  __gapReport: gapReportHandler,
590
649
  __gapReportError: gapReportError,