@astryxdesign/cli 0.6.3-canary.ebaebc4 → 0.6.3-canary.f04b501

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 (390) hide show
  1. package/README.md +106 -73
  2. package/api/blog/blog.doc.mjs +1 -0
  3. package/api/build/build.doc.mjs +4 -3
  4. package/api/build/build.test.mjs +14 -3
  5. package/api/build/build.type.d.mts +35 -1
  6. package/api/build/build.type.mjs +24 -3
  7. package/api/build/help/help.d.mts +8 -5
  8. package/api/build/help/help.mjs +56 -6
  9. package/api/component/component.doc.mjs +13 -3
  10. package/api/component/component.mjs +31 -2
  11. package/api/component/component.test.mjs +38 -0
  12. package/api/component/component.type.d.mts +16 -5
  13. package/api/component/component.type.mjs +13 -5
  14. package/api/component/detail/blocks/blocks.d.mts +2 -1
  15. package/api/component/detail/blocks/blocks.mjs +4 -3
  16. package/api/component/list/list.d.mts +0 -5
  17. package/api/component/list/list.mjs +37 -9
  18. package/api/discover/discover.doc.mjs +2 -1
  19. package/api/docs/_adapter.d.mts +3 -8
  20. package/api/docs/_adapter.mjs +13 -105
  21. package/api/docs/docs.doc.mjs +1 -0
  22. package/api/docs/list/list.mjs +3 -7
  23. package/api/doctor/doctor.d.mts +25 -3
  24. package/api/doctor/doctor.doc.mjs +1 -0
  25. package/api/doctor/doctor.mjs +185 -13
  26. package/api/doctor/doctor.test.mjs +326 -1
  27. package/api/gap-report/gap-report.doc.mjs +8 -4
  28. package/api/hook/_adapter.mjs +19 -5
  29. package/api/hook/hook.doc.mjs +1 -0
  30. package/api/hook/list/list.d.mts +1 -1
  31. package/api/hook/list/list.mjs +69 -17
  32. package/api/init/init.doc.mjs +5 -0
  33. package/api/init/init.test.mjs +41 -1
  34. package/api/init/remove/remove.mjs +1 -1
  35. package/api/init/run/run.mjs +20 -10
  36. package/api/integration/add-contribution.component-names.test.mjs +120 -0
  37. package/api/integration/add-contribution.mjs +18 -7
  38. package/api/integration/add-contribution.test.mjs +117 -3
  39. package/api/integration/add-theme.mjs +34 -64
  40. package/api/integration/add-theme.test.mjs +105 -21
  41. package/api/integration/authoring-checks.test.mjs +8 -4
  42. package/api/integration/integration-block-exports.test.mjs +10 -6
  43. package/api/integration/integrationAdd.doc.mjs +8 -4
  44. package/api/integration/integrationAddAgentDoc.doc.mjs +1 -0
  45. package/api/integration/integrationAddCodemod.doc.mjs +1 -0
  46. package/api/integration/integrationAddComponent.doc.mjs +2 -1
  47. package/api/integration/integrationAddDoc.doc.mjs +1 -0
  48. package/api/integration/integrationAddTemplate.doc.mjs +1 -0
  49. package/api/integration/integrationAddTheme.doc.mjs +6 -5
  50. package/api/integration/integrationComponentConflicts.doc.mjs +1 -0
  51. package/api/integration/integrationDocConflicts.doc.mjs +1 -0
  52. package/api/integration/integrationPackCheck.doc.mjs +1 -0
  53. package/api/integration/integrationTemplateConflicts.doc.mjs +1 -0
  54. package/api/integration/pack-check.test.mjs +17 -47
  55. package/api/integration/summarizeIssues.doc.mjs +1 -0
  56. package/api/integration/validate-integration-fixes.test.mjs +1389 -0
  57. package/api/integration/validate-integration.mjs +52 -102
  58. package/api/integration/validate-integration.test.mjs +176 -23
  59. package/api/integration/validate-unread-theme-folders.test.mjs +110 -0
  60. package/api/integration/validateIntegration.doc.mjs +2 -1
  61. package/api/json/assertResponse.doc.mjs +1 -0
  62. package/api/json/envelope-types.test.mjs +76 -0
  63. package/api/json/isError.doc.mjs +1 -0
  64. package/api/json/parseResponse.doc.mjs +3 -2
  65. package/api/layout/expand/expand.mjs +7 -5
  66. package/api/layout/expand/expand.path-safety.test.mjs +53 -0
  67. package/api/layout/grammar/grammar.mjs +2 -1
  68. package/api/layout/layoutCheck.doc.mjs +1 -0
  69. package/api/layout/layoutExpand.doc.mjs +2 -1
  70. package/api/layout/layoutGrammar.doc.mjs +1 -0
  71. package/api/search/search-return-type.test.mjs +54 -0
  72. package/api/search/search.d.mts +2 -9
  73. package/api/search/search.doc.mjs +6 -1
  74. package/api/search/search.mjs +26 -9
  75. package/api/search/search.test.mjs +34 -0
  76. package/api/swizzle/copy/copy.mjs +28 -11
  77. package/api/swizzle/swizzle.doc.mjs +2 -1
  78. package/api/template/copy/copy.mjs +17 -23
  79. package/api/template/copy/copy.test.mjs +17 -0
  80. package/api/template/template-suffix.test.mjs +41 -21
  81. package/api/template/template.doc.mjs +3 -1
  82. package/api/theme/_adapter.d.mts +2 -3
  83. package/api/theme/_adapter.mjs +4 -5
  84. package/api/theme/add/add.binary.test.mjs +84 -0
  85. package/api/theme/add/add.mjs +20 -3
  86. package/api/theme/add/add.staging.test.mjs +66 -0
  87. package/api/theme/add/add.test.mjs +14 -1
  88. package/api/theme/build/build.mjs +114 -37
  89. package/api/theme/build/build.public-component-vars.test.mjs +1 -1
  90. package/api/theme/build/build.receipt-doc.test.mjs +111 -0
  91. package/api/theme/build/font-warning.mjs +3 -3
  92. package/api/theme/build/font-warning.test.mjs +5 -2
  93. package/api/theme/generateTonalPalette.doc.mjs +1 -0
  94. package/api/theme/integration-themes.test.mjs +39 -28
  95. package/api/theme/list/list.test.mjs +19 -20
  96. package/api/theme/listThemes.doc.mjs +6 -5
  97. package/api/theme/palette/generate/generate.mjs +7 -2
  98. package/api/theme/palette/generate/generate.test.mjs +96 -0
  99. package/api/theme/palette/generate/generator.mjs +8 -1
  100. package/api/theme/palette/generate/generator.test.mjs +10 -0
  101. package/api/theme/template/template.mjs +11 -2
  102. package/api/theme/template/template.test.mjs +15 -0
  103. package/api/theme/themeAdd.doc.mjs +4 -3
  104. package/api/theme/themeBuild.doc.mjs +8 -4
  105. package/api/theme/themeList.doc.mjs +6 -3
  106. package/api/theme/themeListAvailable.doc.mjs +5 -3
  107. package/api/theme/themePaletteGenerate.doc.mjs +1 -0
  108. package/api/theme/themeTargets.doc.mjs +1 -0
  109. package/api/theme/themeTemplate.doc.mjs +5 -1
  110. package/api/upgrade/_adapter.d.mts +12 -3
  111. package/api/upgrade/_adapter.mjs +108 -70
  112. package/api/upgrade/list/list.mjs +2 -1
  113. package/api/upgrade/list/list.test.mjs +73 -0
  114. package/api/upgrade/provider-agreement.test.mjs +152 -0
  115. package/api/upgrade/run/run.mjs +1 -0
  116. package/api/upgrade/status/status.mjs +2 -2
  117. package/api/upgrade/upgrade.doc.mjs +3 -1
  118. package/api/upgrade/upgrade.type.d.mts +4 -0
  119. package/api/upgrade/upgrade.type.mjs +1 -0
  120. package/assets/codemods/integration-discovery.mjs +8 -2
  121. package/assets/codemods/integration-discovery.test.mjs +15 -0
  122. package/assets/codemods/runner.mjs +115 -5
  123. package/assets/codemods/term-log.mjs +32 -8
  124. package/assets/codemods/term-log.test.mjs +19 -1
  125. package/assets/codemods/transform-prop.mjs +109 -0
  126. package/assets/codemods/transform-prop.test.mjs +95 -0
  127. package/assets/codemods/transforms/next/__tests__/migrate-theme-catalog-to-descriptors.test.mjs +216 -0
  128. package/assets/codemods/transforms/next/index.mjs +11 -1
  129. package/assets/codemods/transforms/next/migrate-theme-catalog-to-descriptors.mjs +141 -0
  130. package/assets/codemods/transforms/v0.0.14/__tests__/rename-status-variants.test.mjs +86 -165
  131. package/assets/codemods/transforms/v0.0.14/rename-status-variants.mjs +72 -210
  132. package/assets/codemods/transforms/v0.1.8/__tests__/rename-avatar-size-scale.test.mjs +83 -115
  133. package/assets/codemods/transforms/v0.1.8/rename-avatar-size-scale.mjs +57 -186
  134. package/assets/codemods/transforms/v0.6.0/__tests__/next-codemods.test.mjs +47 -0
  135. package/assets/codemods/transforms/v0.6.0/rename-resizable-pixel-bounds.mjs +57 -10
  136. package/assets/docs/cli-integrations.doc.mjs +17 -30
  137. package/assets/docs/cli.doc.mjs +15 -0
  138. package/assets/docs/theme.doc.mjs +1 -1
  139. package/assets/templates/blocks/components/ScrollableArea/ScrollableAreaInlineRail.doc.mjs +14 -0
  140. package/assets/templates/blocks/components/ScrollableArea/ScrollableAreaInlineRail.tsx +61 -0
  141. package/assets/templates/blocks/components/ScrollableArea/ScrollableAreaOverscrollChaining.doc.mjs +14 -0
  142. package/assets/templates/blocks/components/ScrollableArea/ScrollableAreaOverscrollChaining.tsx +126 -0
  143. package/assets/templates/blocks/components/ScrollableArea/ScrollableAreaShowcase.doc.mjs +15 -0
  144. package/assets/templates/blocks/components/ScrollableArea/ScrollableAreaShowcase.tsx +86 -0
  145. package/assets/templates/blocks/components/ScrollableArea/ScrollableAreaStickyGroupHeaders.doc.mjs +14 -0
  146. package/assets/templates/blocks/components/ScrollableArea/ScrollableAreaStickyGroupHeaders.tsx +99 -0
  147. package/assets/templates/blocks/components/ScrollableArea/ScrollableAreaStickyPassthrough.doc.mjs +14 -0
  148. package/assets/templates/blocks/components/ScrollableArea/ScrollableAreaStickyPassthrough.tsx +122 -0
  149. package/assets/templates/blocks/components/ScrollableArea/ScrollableAreaTwoAxisBoard.doc.mjs +14 -0
  150. package/assets/templates/blocks/components/ScrollableArea/ScrollableAreaTwoAxisBoard.tsx +95 -0
  151. package/assets/templates/pages/table-tree/page.tsx +1704 -0
  152. package/assets/templates/pages/table-tree/template.doc.mjs +12 -0
  153. package/assets/templates/themes/butter/butterTheme.doc.mjs +11 -0
  154. package/assets/templates/themes/chocolate/chocolateTheme.doc.mjs +11 -0
  155. package/assets/templates/themes/gothic/gothicTheme.doc.mjs +11 -0
  156. package/assets/templates/themes/matcha/matchaTheme.doc.mjs +11 -0
  157. package/assets/templates/themes/neutral/neutralTheme.doc.mjs +11 -0
  158. package/assets/templates/themes/stone/stoneTheme.doc.mjs +11 -0
  159. package/assets/templates/themes/y2k/y2kTheme.doc.mjs +11 -0
  160. package/authoring/codemod/codemod.doc.mjs +1 -1
  161. package/authoring/config/config.doc.mjs +2 -2
  162. package/authoring/config/debug-composition.test.mjs +92 -0
  163. package/authoring/config/type.ts +15 -3
  164. package/authoring/debug/debug.doc.d.mts +11 -0
  165. package/authoring/debug/debug.doc.mjs +182 -0
  166. package/authoring/doctypes/base/graph-fields.doc.mjs +1 -1
  167. package/authoring/doctypes/command/command.doc.mjs +1 -1
  168. package/authoring/doctypes/command/type.ts +1 -1
  169. package/authoring/doctypes/component/type.ts +2 -2
  170. package/authoring/doctypes/doctypes-new.test.mjs +48 -6
  171. package/authoring/doctypes/enum/enum.doc.mjs +1 -1
  172. package/authoring/doctypes/enum/type.ts +1 -1
  173. package/authoring/doctypes/function/function.doc.mjs +1 -1
  174. package/authoring/doctypes/function/type.ts +1 -1
  175. package/authoring/doctypes/hook/type.ts +2 -2
  176. package/authoring/doctypes/load-contract.test.mjs +2 -1
  177. package/authoring/doctypes/parse.d.mts +4 -2
  178. package/authoring/doctypes/parse.mjs +7 -3
  179. package/authoring/doctypes/reference/reference.doc.mjs +1 -1
  180. package/authoring/doctypes/reference/type.ts +2 -2
  181. package/authoring/doctypes/schema/schema.doc.mjs +1 -1
  182. package/authoring/doctypes/schema/type.ts +1 -2
  183. package/authoring/doctypes/template/template.doc.mjs +3 -3
  184. package/authoring/doctypes/theme/parse.d.mts +35 -0
  185. package/authoring/doctypes/theme/parse.mjs +76 -0
  186. package/authoring/doctypes/theme/theme.doc.d.mts +9 -0
  187. package/authoring/doctypes/theme/theme.doc.mjs +79 -0
  188. package/authoring/doctypes/theme/type.ts +42 -0
  189. package/authoring/doctypes/types.ts +2 -1
  190. package/authoring/gap-report/gap-report.doc.d.mts +12 -0
  191. package/authoring/gap-report/gap-report.doc.mjs +183 -0
  192. package/authoring/index.d.mts +1 -0
  193. package/authoring/index.d.ts +6 -4
  194. package/authoring/index.mjs +2 -1
  195. package/authoring/integration/integration.doc.mjs +2 -2
  196. package/authoring/integration/type.ts +3 -9
  197. package/clients/cli/__tests__/cliManifest.test.ts +27 -29
  198. package/clients/cli/command-load-failure.test.mjs +83 -0
  199. package/clients/cli/commands/blog.doc.mjs +1 -1
  200. package/clients/cli/commands/blog.mjs +23 -8
  201. package/clients/cli/commands/blog.test.mjs +42 -1
  202. package/clients/cli/commands/build-theme.ascii-output.test.mjs +161 -0
  203. package/clients/cli/commands/build-theme.flag-docs.test.mjs +110 -0
  204. package/clients/cli/commands/build-theme.mjs +5 -5
  205. package/clients/cli/commands/build-theme.path-safety.test.mjs +86 -1
  206. package/clients/cli/commands/build-theme.variants.test.mjs +76 -0
  207. package/clients/cli/commands/build.doc.mjs +5 -2
  208. package/clients/cli/commands/build.exit-codes-doc.test.mjs +50 -0
  209. package/clients/cli/commands/build.mjs +32 -47
  210. package/clients/cli/commands/build.playbook.test.mjs +75 -0
  211. package/clients/cli/commands/build.text-fields.test.mjs +40 -0
  212. package/clients/cli/commands/component/index.mjs +2 -7
  213. package/clients/cli/commands/component-package.test.mjs +46 -0
  214. package/clients/cli/commands/component-resolution.test.mjs +21 -0
  215. package/clients/cli/commands/component.doc.mjs +1 -1
  216. package/clients/cli/commands/component.test.mjs +19 -0
  217. package/clients/cli/commands/detail-levels.test.mjs +2 -2
  218. package/clients/cli/commands/discover.broken-integration.test.mjs +48 -0
  219. package/clients/cli/commands/discover.components-flag.test.mjs +97 -0
  220. package/clients/cli/commands/discover.doc.mjs +5 -3
  221. package/clients/cli/commands/discover.mjs +3 -3
  222. package/clients/cli/commands/discover.text-projection.test.mjs +103 -0
  223. package/clients/cli/commands/docs.doc.mjs +1 -1
  224. package/clients/cli/commands/doctor-integration-components.doc.mjs +1 -1
  225. package/clients/cli/commands/doctor-integration-docs.doc.mjs +1 -1
  226. package/clients/cli/commands/doctor-integration-templates.doc.mjs +1 -1
  227. package/clients/cli/commands/doctor-integration-validate.doc.mjs +1 -1
  228. package/clients/cli/commands/doctor-integration.doc.mjs +1 -1
  229. package/clients/cli/commands/doctor-integration.package-json.test.mjs +53 -0
  230. package/clients/cli/commands/doctor-integration.test.mjs +15 -7
  231. package/clients/cli/commands/doctor.doc.mjs +1 -1
  232. package/clients/cli/commands/doctor.mjs +6 -16
  233. package/clients/cli/commands/doctor.test.mjs +42 -0
  234. package/clients/cli/commands/gap-report.doc.mjs +17 -6
  235. package/clients/cli/commands/gap-report.test.mjs +72 -0
  236. package/clients/cli/commands/hook/index.mjs +7 -17
  237. package/clients/cli/commands/hook.doc.mjs +1 -1
  238. package/clients/cli/commands/hook.text-projection.test.mjs +45 -0
  239. package/clients/cli/commands/init.doc.mjs +20 -9
  240. package/clients/cli/commands/init.flag-help.test.mjs +153 -0
  241. package/clients/cli/commands/integration-add.controls.test.mjs +132 -0
  242. package/clients/cli/commands/integration-add.doc.mjs +22 -6
  243. package/clients/cli/commands/integration-pack.doc.mjs +1 -1
  244. package/clients/cli/commands/integration-real-world.test.mjs +3 -9
  245. package/clients/cli/commands/integration.doc.mjs +1 -1
  246. package/clients/cli/commands/interactive-guard.test.mjs +101 -24
  247. package/clients/cli/commands/json-contract.test.mjs +33 -0
  248. package/clients/cli/commands/layout-check.doc.mjs +15 -4
  249. package/clients/cli/commands/layout-expand.doc.mjs +22 -5
  250. package/clients/cli/commands/layout-grammar.doc.mjs +1 -1
  251. package/clients/cli/commands/layout.doc.mjs +3 -3
  252. package/clients/cli/commands/layout.mjs +21 -9
  253. package/clients/cli/commands/layout.path-help.test.mjs +33 -0
  254. package/clients/cli/commands/layout.stdin-cap.test.mjs +47 -0
  255. package/clients/cli/commands/layout.text-fields.test.mjs +39 -0
  256. package/clients/cli/commands/manifest.doc.mjs +1 -1
  257. package/clients/cli/commands/no-prompt-wording.test.mjs +94 -0
  258. package/clients/cli/commands/search.doc.mjs +7 -4
  259. package/clients/cli/commands/search.mjs +17 -7
  260. package/clients/cli/commands/search.test.mjs +75 -0
  261. package/clients/cli/commands/swizzle.doc.mjs +3 -2
  262. package/clients/cli/commands/swizzle.path-safety.test.mjs +42 -0
  263. package/clients/cli/commands/template.doc.mjs +29 -7
  264. package/clients/cli/commands/template.flag-help.test.mjs +117 -0
  265. package/clients/cli/commands/template.path-help.test.mjs +40 -0
  266. package/clients/cli/commands/theme-add.doc.mjs +4 -3
  267. package/clients/cli/commands/theme-build.doc.mjs +8 -7
  268. package/clients/cli/commands/theme-list.doc.mjs +2 -2
  269. package/clients/cli/commands/theme-palette-generate.doc.mjs +3 -3
  270. package/clients/cli/commands/theme-palette-generate.test.mjs +19 -0
  271. package/clients/cli/commands/theme-palette.doc.mjs +1 -1
  272. package/clients/cli/commands/theme-targets.doc.mjs +1 -1
  273. package/clients/cli/commands/theme-template.behavior.test.mjs +12 -0
  274. package/clients/cli/commands/theme-template.doc.mjs +2 -2
  275. package/clients/cli/commands/theme.doc.mjs +1 -1
  276. package/clients/cli/commands/upgrade.ascii-output.test.mjs +87 -0
  277. package/clients/cli/commands/upgrade.doc.mjs +18 -7
  278. package/clients/cli/commands/upgrade.flag-help.test.mjs +188 -0
  279. package/clients/cli/commands/upgrade.hook-output.test.mjs +88 -0
  280. package/clients/cli/index.mjs +13 -30
  281. package/clients/cli/latest-version-env.test.mjs +50 -0
  282. package/clients/cli/lib/cli-error.test.mjs +7 -0
  283. package/clients/cli/lib/component-format.mjs +9 -9
  284. package/clients/cli/lib/component-format.test.mjs +1 -1
  285. package/clients/cli/lib/define-command.mjs +32 -6
  286. package/clients/cli/lib/doc-text-ascii.test.mjs +82 -0
  287. package/clients/cli/lib/exit-codes.test.mjs +97 -0
  288. package/clients/cli/lib/hook-format.mjs +19 -10
  289. package/clients/cli/lib/json-shim.mjs +38 -2
  290. package/clients/cli/lib/json-shim.test.mjs +83 -0
  291. package/clients/cli/lib/manifest.d.ts +2 -0
  292. package/clients/cli/lib/manifest.mjs +22 -0
  293. package/clients/cli/lib/manifest.test.mjs +17 -0
  294. package/foundation/agent-docs/agent-docs.d.mts +4 -0
  295. package/foundation/agent-docs/agent-docs.mjs +69 -3
  296. package/foundation/agent-docs/agent-docs.path-safety.test.mjs +266 -4
  297. package/foundation/config/integration-debug.test.mjs +28 -3
  298. package/foundation/config/project-themes.test.mjs +11 -19
  299. package/foundation/config/project.d.mts +8 -0
  300. package/foundation/config/project.mjs +173 -30
  301. package/foundation/config/project.test.mjs +129 -16
  302. package/foundation/discovery/authoring-self-docs.d.mts +6 -0
  303. package/foundation/discovery/authoring-self-docs.mjs +17 -7
  304. package/foundation/discovery/authoring-surface.d.mts +74 -0
  305. package/foundation/discovery/authoring-surface.mjs +525 -0
  306. package/foundation/discovery/authoring-surface.test.mjs +392 -0
  307. package/foundation/discovery/cli-self-docs.d.mts +113 -0
  308. package/foundation/discovery/cli-self-docs.mjs +514 -0
  309. package/foundation/discovery/cli-self-docs.test.mjs +437 -0
  310. package/foundation/discovery/component-loader.d.mts +35 -38
  311. package/foundation/discovery/component-loader.mjs +53 -222
  312. package/foundation/discovery/docs-discovery.d.mts +3 -2
  313. package/foundation/discovery/docs-discovery.mjs +8 -12
  314. package/foundation/discovery/docs-discovery.test.mjs +8 -5
  315. package/foundation/discovery/template-adapter.d.mts +21 -5
  316. package/foundation/discovery/template-adapter.fixture-refs.test.mjs +248 -0
  317. package/foundation/discovery/template-adapter.integration-isolation.test.mjs +94 -0
  318. package/foundation/discovery/template-adapter.mjs +319 -65
  319. package/foundation/discovery/template-adapter.test.mjs +15 -0
  320. package/foundation/discovery/theme-discovery.d.mts +67 -7
  321. package/foundation/discovery/theme-discovery.mjs +916 -186
  322. package/foundation/discovery/theme-discovery.test.mjs +613 -219
  323. package/foundation/doc-compiler/bundle.d.mts +47 -0
  324. package/foundation/doc-compiler/bundle.mjs +218 -0
  325. package/foundation/doc-compiler/bundle.test.mjs +255 -0
  326. package/foundation/doc-compiler/compile.d.mts +200 -0
  327. package/foundation/doc-compiler/compile.mjs +254 -5
  328. package/foundation/doc-compiler/diagnostics.d.mts +126 -0
  329. package/foundation/doc-compiler/diagnostics.mjs +277 -0
  330. package/foundation/doc-compiler/doc-compiler.test.mjs +28 -1
  331. package/foundation/doc-compiler/doc-loads.test.mjs +1642 -0
  332. package/foundation/doc-compiler/import.d.mts +24 -0
  333. package/foundation/doc-compiler/import.mjs +59 -0
  334. package/foundation/doc-compiler/inputs.d.mts +102 -0
  335. package/foundation/doc-compiler/inputs.mjs +286 -0
  336. package/foundation/doc-compiler/inputs.test.mjs +299 -0
  337. package/foundation/doc-compiler/ir.d.mts +13 -0
  338. package/foundation/doc-compiler/ir.mjs +189 -12
  339. package/foundation/doc-compiler/lower-doc.test.mjs +492 -0
  340. package/foundation/doc-compiler/overlays.d.mts +37 -0
  341. package/foundation/doc-compiler/overlays.mjs +206 -0
  342. package/foundation/doc-compiler/parse-readable.d.mts +9 -0
  343. package/foundation/doc-compiler/parse-readable.mjs +29 -0
  344. package/foundation/doc-compiler/read.d.mts +126 -0
  345. package/foundation/doc-compiler/read.mjs +320 -0
  346. package/foundation/doc-compiler/read.test.mjs +313 -0
  347. package/foundation/doc-compiler/source.d.mts +33 -0
  348. package/foundation/doc-compiler/source.mjs +128 -0
  349. package/foundation/fs/module-loader.d.mts +1 -0
  350. package/foundation/fs/module-loader.mjs +50 -1
  351. package/foundation/fs/module-loader.stdout.test.mjs +332 -0
  352. package/foundation/fs/path-safety.d.mts +3 -2
  353. package/foundation/fs/path-safety.mjs +49 -19
  354. package/foundation/fs/path-safety.test.mjs +50 -0
  355. package/foundation/fs/publish-file-hardlink-unavailable.test.mjs +129 -94
  356. package/foundation/integrations/autolink.d.mts +58 -1
  357. package/foundation/integrations/autolink.mjs +143 -52
  358. package/foundation/integrations/contribution-fixes.d.mts +145 -0
  359. package/foundation/integrations/contribution-fixes.mjs +1284 -0
  360. package/foundation/integrations/contribution-inventory.d.mts +1 -1
  361. package/foundation/integrations/contribution-inventory.mjs +17 -20
  362. package/foundation/integrations/contribution-inventory.test.mjs +67 -27
  363. package/foundation/integrations/integrations.d.mts +3 -0
  364. package/foundation/integrations/integrations.mjs +11 -97
  365. package/foundation/integrations/provider-ledger.test.mjs +275 -0
  366. package/foundation/integrations/provider-resolution.d.mts +152 -0
  367. package/foundation/integrations/provider-resolution.mjs +576 -0
  368. package/foundation/integrations/provider-resolution.test.mjs +369 -0
  369. package/foundation/integrations/theme-descriptor.d.mts +8 -0
  370. package/foundation/integrations/theme-descriptor.mjs +44 -0
  371. package/foundation/integrations/validate-contributions.mjs +104 -10
  372. package/foundation/response/base.d.ts +8 -4
  373. package/foundation/response/error-codes.doc.mjs +3 -4
  374. package/foundation/response/error-codes.mjs +2 -2
  375. package/foundation/response/error-codes.test.mjs +83 -0
  376. package/foundation/response/json-contract.test.mjs +11 -0
  377. package/foundation/response/json.d.mts +4 -2
  378. package/foundation/response/json.mjs +8 -10
  379. package/foundation/response/response-types.doc.mjs +11 -11
  380. package/foundation/response/response-types.doc.test.mjs +158 -0
  381. package/foundation/response/response.doc.mjs +1 -1
  382. package/foundation/text/string-utils.mjs +22 -10
  383. package/foundation/xle/expand.d.mts +2 -0
  384. package/foundation/xle/expand.mjs +3 -2
  385. package/foundation/xle/expand.test.mjs +54 -0
  386. package/package.json +9 -9
  387. package/assets/templates/themes/manifest.json +0 -95
  388. package/clients/cli/lib/update-check.mjs +0 -83
  389. package/clients/cli/lib/update-check.test.mjs +0 -137
  390. package/clients/cli/update-hint-commands.test.mjs +0 -54
@@ -11,6 +11,7 @@ export const doc = {
11
11
  type: 'function',
12
12
  kind: 'api',
13
13
  name: 'hook',
14
+ namespace: 'cli/api',
14
15
  displayName: 'hook()',
15
16
  summary:
16
17
  'Resolve a hook by name, or list the catalog, with an optional params-only slice.',
@@ -10,7 +10,7 @@
10
10
  * @param {string|null} [options.lang]
11
11
  * @returns {Promise<import('../hook.type.mjs').HookListResponse>}
12
12
  */
13
- export function list({ cwd, category, detail, zh, lang }?: {
13
+ export function list({ cwd, category, detail, zh, lang, }?: {
14
14
  cwd?: string | undefined;
15
15
  category?: string | undefined;
16
16
  detail?: "compact" | "full" | "names" | "brief" | undefined;
@@ -13,7 +13,10 @@
13
13
  * @position api/hook/list/list.mjs — dispatched from ../hook.mjs
14
14
  */
15
15
 
16
- import {discoverHooks, findHookDoc} from '../../../foundation/discovery/hook-discovery.mjs';
16
+ import {
17
+ discoverHooks,
18
+ findHookDoc,
19
+ } from '../../../foundation/discovery/hook-discovery.mjs';
17
20
  import {loadDocs} from '../../../foundation/discovery/component-loader.mjs';
18
21
  import {AstryxError} from '../../error.mjs';
19
22
  import {ERROR_CODES} from '../../../foundation/response/error-codes.mjs';
@@ -28,7 +31,13 @@ import {resolveCoreDir} from '../_adapter.mjs';
28
31
  * @param {string|null} [options.lang]
29
32
  * @returns {Promise<import('../hook.type.mjs').HookListResponse>}
30
33
  */
31
- export async function list({cwd = process.cwd(), category, detail = 'names', zh = false, lang = null} = {}) {
34
+ export async function list({
35
+ cwd = process.cwd(),
36
+ category,
37
+ detail = 'names',
38
+ zh = false,
39
+ lang = null,
40
+ } = {}) {
32
41
  const coreDir = resolveCoreDir(cwd);
33
42
  const hooks = discoverHooks(coreDir);
34
43
 
@@ -51,20 +60,31 @@ export async function list({cwd = process.cwd(), category, detail = 'names', zh
51
60
  const docPath = findHookDoc(coreDir, hookName);
52
61
  if (docPath) {
53
62
  try {
54
- const docs = await loadDocs(docPath, /** @type {{zh?: boolean, dense?: boolean, lang?: string}} */ ({zh, lang}));
63
+ const docs = await loadDocs(docPath, {zh, lang, root: 'hooks'});
55
64
  entries.push({
56
65
  name: hookName,
57
66
  description: docs.usage?.description || '',
58
67
  import: docs.importPath || '@astryxdesign/core/hooks',
59
68
  });
60
69
  } catch {
61
- entries.push({name: hookName, description: '', import: '@astryxdesign/core/hooks'});
70
+ entries.push({
71
+ name: hookName,
72
+ description: '',
73
+ import: '@astryxdesign/core/hooks',
74
+ });
62
75
  }
63
76
  } else {
64
- entries.push({name: hookName, description: '', import: '@astryxdesign/core/hooks'});
77
+ entries.push({
78
+ name: hookName,
79
+ description: '',
80
+ import: '@astryxdesign/core/hooks',
81
+ });
65
82
  }
66
83
  }
67
- return {type: 'hook.list', data: {detail: 'compact', components: {[match[0]]: entries}}};
84
+ return {
85
+ type: 'hook.list',
86
+ data: {detail: 'compact', components: {[match[0]]: entries}},
87
+ };
68
88
  }
69
89
 
70
90
  if (detail === 'full') {
@@ -74,19 +94,33 @@ export async function list({cwd = process.cwd(), category, detail = 'names', zh
74
94
  const docPath = findHookDoc(coreDir, hookName);
75
95
  if (docPath) {
76
96
  try {
77
- entries.push(await loadDocs(docPath, /** @type {{zh?: boolean, dense?: boolean, lang?: string}} */ ({zh, lang})));
97
+ entries.push(await loadDocs(docPath, {zh, lang, root: 'hooks'}));
78
98
  } catch {
79
- entries.push(/** @type {import('../hook.type.mjs').HookDoc} */ ({name: hookName}));
99
+ entries.push(
100
+ /** @type {import('../hook.type.mjs').HookDoc} */ ({
101
+ name: hookName,
102
+ }),
103
+ );
80
104
  }
81
105
  } else {
82
- entries.push(/** @type {import('../hook.type.mjs').HookDoc} */ ({name: hookName}));
106
+ entries.push(
107
+ /** @type {import('../hook.type.mjs').HookDoc} */ ({
108
+ name: hookName,
109
+ }),
110
+ );
83
111
  }
84
112
  }
85
- return {type: 'hook.list', data: {detail: 'full', components: {[match[0]]: entries}}};
113
+ return {
114
+ type: 'hook.list',
115
+ data: {detail: 'full', components: {[match[0]]: entries}},
116
+ };
86
117
  }
87
118
 
88
119
  // Default: names only
89
- return {type: 'hook.list', data: {detail: 'names', components: {[match[0]]: match[1]}}};
120
+ return {
121
+ type: 'hook.list',
122
+ data: {detail: 'names', components: {[match[0]]: match[1]}},
123
+ };
90
124
  }
91
125
 
92
126
  // All hooks
@@ -99,17 +133,25 @@ export async function list({cwd = process.cwd(), category, detail = 'names', zh
99
133
  const docPath = findHookDoc(coreDir, hookName);
100
134
  if (docPath) {
101
135
  try {
102
- const docs = await loadDocs(docPath, /** @type {{zh?: boolean, dense?: boolean, lang?: string}} */ ({zh, lang}));
136
+ const docs = await loadDocs(docPath, {zh, lang, root: 'hooks'});
103
137
  result[cat].push({
104
138
  name: hookName,
105
139
  description: docs.usage?.description || '',
106
140
  import: docs.importPath || '@astryxdesign/core/hooks',
107
141
  });
108
142
  } catch {
109
- result[cat].push({name: hookName, description: '', import: '@astryxdesign/core/hooks'});
143
+ result[cat].push({
144
+ name: hookName,
145
+ description: '',
146
+ import: '@astryxdesign/core/hooks',
147
+ });
110
148
  }
111
149
  } else {
112
- result[cat].push({name: hookName, description: '', import: '@astryxdesign/core/hooks'});
150
+ result[cat].push({
151
+ name: hookName,
152
+ description: '',
153
+ import: '@astryxdesign/core/hooks',
154
+ });
113
155
  }
114
156
  }
115
157
  }
@@ -125,12 +167,22 @@ export async function list({cwd = process.cwd(), category, detail = 'names', zh
125
167
  const docPath = findHookDoc(coreDir, hookName);
126
168
  if (docPath) {
127
169
  try {
128
- result[cat].push(await loadDocs(docPath, /** @type {{zh?: boolean, dense?: boolean, lang?: string}} */ ({zh, lang})));
170
+ result[cat].push(
171
+ await loadDocs(docPath, {zh, lang, root: 'hooks'}),
172
+ );
129
173
  } catch {
130
- result[cat].push(/** @type {import('../hook.type.mjs').HookDoc} */ ({name: hookName}));
174
+ result[cat].push(
175
+ /** @type {import('../hook.type.mjs').HookDoc} */ ({
176
+ name: hookName,
177
+ }),
178
+ );
131
179
  }
132
180
  } else {
133
- result[cat].push(/** @type {import('../hook.type.mjs').HookDoc} */ ({name: hookName}));
181
+ result[cat].push(
182
+ /** @type {import('../hook.type.mjs').HookDoc} */ ({
183
+ name: hookName,
184
+ }),
185
+ );
134
186
  }
135
187
  }
136
188
  }
@@ -11,6 +11,7 @@ export const doc = {
11
11
  type: 'function',
12
12
  kind: 'api',
13
13
  name: 'init',
14
+ namespace: 'cli/api',
14
15
  displayName: 'init()',
15
16
  summary:
16
17
  'Non-interactive project setup: install agent docs and point at the theme + build workflows.',
@@ -90,6 +91,10 @@ export const doc = {
90
91
  code: 'ERR_FILE_EXISTS',
91
92
  when: 'scaffolding a template would overwrite an existing page.tsx',
92
93
  },
94
+ {
95
+ code: 'ERR_PATH_TRAVERSAL',
96
+ when: 'the template output path resolves outside cwd, for example through a symlinked src directory',
97
+ },
93
98
  ],
94
99
  examples: [
95
100
  {label: 'Default setup', code: 'const r = await init();'},
@@ -230,13 +230,40 @@ describe('init() — logger', () => {
230
230
  errSpy.mockRestore();
231
231
  }
232
232
  const text = lines.join('\n');
233
- expect(text).toContain('✓ AI agent docs installed → AGENTS.md');
233
+ expect(text).toContain('[ok] AI agent docs installed -> AGENTS.md');
234
234
  expect(text).toContain(' Next steps:');
235
235
  // The exact next-steps block the CLI prints comes from getNextSteps().
236
236
  expect(text).toContain(getNextSteps('npx astryx')[2].slice(0, 20));
237
237
  });
238
238
  });
239
239
 
240
+ describe('init() — ASCII output', () => {
241
+ it('prints only ASCII on the default, all-features, re-run, template, and remove paths', async () => {
242
+ /** @type {string[]} */
243
+ const lines = [];
244
+ const logSpy = vi.spyOn(console, 'log').mockImplementation((...a) => lines.push(a.join(' ')));
245
+ const errSpy = vi.spyOn(console, 'error').mockImplementation((...a) => lines.push(a.join(' ')));
246
+ const empty = fs.mkdtempSync(path.join(process.cwd(), '.astryx-init-remove-'));
247
+ logger.setSilent(false);
248
+ try {
249
+ await init({}, {cwd: tmpDir});
250
+ await init({all: true}, {cwd: tmpDir});
251
+ await init({all: true}, {cwd: tmpDir});
252
+ await init({features: 'template', templateName: 'blank'}, {cwd: tmpDir});
253
+ await init({removeAgents: true}, {cwd: tmpDir});
254
+ await init({removeAgents: true}, {cwd: empty});
255
+ } finally {
256
+ logger.setSilent(true);
257
+ logSpy.mockRestore();
258
+ errSpy.mockRestore();
259
+ fs.rmSync(empty, {recursive: true, force: true});
260
+ }
261
+ expect(lines.length).toBeGreaterThan(0);
262
+ expect(lines.some(line => line.startsWith('[ok] Removed'))).toBe(true);
263
+ expect(lines.filter(line => /[\u0080-\uFFFF]/.test(line))).toEqual([]);
264
+ });
265
+ });
266
+
240
267
  describe('init() — write-path safety', () => {
241
268
  it('template scaffold refuses to clobber an existing page.tsx', async () => {
242
269
  const dest = path.join(tmpDir, 'src', 'pages', 'blank');
@@ -260,4 +287,17 @@ describe('init() — write-path safety', () => {
260
287
  init({features: 'template', templateName: '../../etc/evil'}, {cwd: tmpDir}),
261
288
  ).rejects.toMatchObject({code: ERROR_CODES.ERR_UNKNOWN_TEMPLATE});
262
289
  });
290
+
291
+ it('refuses a template write that a symlinked src would carry outside cwd (ERR_PATH_TRAVERSAL)', async () => {
292
+ const outside = fs.mkdtempSync(path.join(process.cwd(), '.astryx-init-outside-'));
293
+ try {
294
+ fs.symlinkSync(outside, path.join(tmpDir, 'src'), 'dir');
295
+ await expect(
296
+ init({features: 'template', templateName: 'blank'}, {cwd: tmpDir}),
297
+ ).rejects.toMatchObject({code: ERROR_CODES.ERR_PATH_TRAVERSAL});
298
+ expect(fs.readdirSync(outside)).toEqual([]);
299
+ } finally {
300
+ fs.rmSync(outside, {recursive: true, force: true});
301
+ }
302
+ });
263
303
  });
@@ -24,6 +24,6 @@ import {logger} from '../../logger.mjs';
24
24
  */
25
25
  export async function remove({cwd = process.cwd()} = {}) {
26
26
  removeAgentDocs(cwd);
27
- logger.log('✓ AI agent docs removed.');
27
+ logger.log('[ok] AI agent docs removed.');
28
28
  return {type: 'init.remove', data: {removed: true}};
29
29
  }
@@ -17,7 +17,7 @@
17
17
  import * as path from 'node:path';
18
18
  import * as fs from 'node:fs';
19
19
  import {CLI_ROOT} from '../../../foundation/fs/paths.mjs';
20
- import {PathSafetyError} from '../../../foundation/fs/path-safety.mjs';
20
+ import {PathSafetyError, assertWithin} from '../../../foundation/fs/path-safety.mjs';
21
21
  import {getCliInvocation} from '../../../foundation/env/package-manager.mjs';
22
22
  import {
23
23
  installAgentDocs,
@@ -98,7 +98,7 @@ async function applyAgents(cwd, options, invocation, data) {
98
98
  renderedBlock,
99
99
  });
100
100
  data.docsWritten = written;
101
- logger.log(`✓ AI agent docs installed → ${written.join(', ')}`);
101
+ logger.log(`[ok] AI agent docs installed -> ${written.join(', ')}`);
102
102
  } catch (err) {
103
103
  // PathSafetyError carries a precise, user-actionable message — surface it
104
104
  // (and flag exit 1) instead of the generic "could not install" warning so
@@ -132,8 +132,8 @@ function applyTheme(cwd, invocation, data) {
132
132
  data.themeTemplatePath = didWrite ? written : null;
133
133
  logger.log(
134
134
  didWrite
135
- ? `✓ Theme template written → ${written}`
136
- : `• ${written} already exists — left as is.`,
135
+ ? `[ok] Theme template written -> ${written}`
136
+ : `- ${written} already exists - left as is.`,
137
137
  );
138
138
  } catch {
139
139
  // Soft failure, like agent docs: the guidance below is still useful.
@@ -166,16 +166,16 @@ function applyTemplate(cwd, {templateName}, invocation, data) {
166
166
  // Point agents at the build workflow rather than dumping page-template
167
167
  // names — `build` surfaces pages AND blocks AND components for an idea,
168
168
  // and `build` with no args is the full how-to-build playbook.
169
- logger.log('✓ To build UI, use these commands:');
169
+ logger.log('[ok] To build UI, use these commands:');
170
170
  logger.log('');
171
171
  logger.log(
172
- ` ${invocation} build "<what you're building>" build a page — kit: closest template + blocks + components`,
172
+ ` ${invocation} build "<what you're building>" build a page - kit: closest template + blocks + components`,
173
173
  );
174
174
  logger.log(
175
175
  ` ${invocation} build the how-to-build workflow (read this first)`,
176
176
  );
177
177
  logger.log(
178
- ` ${invocation} search <query> find anything — components, docs, templates, blocks`,
178
+ ` ${invocation} search <query> find anything - components, docs, templates, blocks`,
179
179
  );
180
180
  logger.log('');
181
181
  data.template = 'workflow';
@@ -190,9 +190,19 @@ function applyTemplate(cwd, {templateName}, invocation, data) {
190
190
  );
191
191
  }
192
192
 
193
- const outputDir = path.resolve(cwd, `./src/pages/${templateName}`);
193
+ let destFile;
194
+ try {
195
+ destFile = assertWithin(path.join('src', 'pages', templateName, 'page.tsx'), cwd, {
196
+ label: 'template output path',
197
+ });
198
+ } catch (err) {
199
+ if (err instanceof PathSafetyError) {
200
+ throw new AstryxError(err.message, undefined, ERROR_CODES.ERR_PATH_TRAVERSAL);
201
+ }
202
+ throw err;
203
+ }
204
+ const outputDir = path.dirname(destFile);
194
205
  const srcPath = path.join(CLI_ROOT, 'assets', 'templates', 'pages', templateName, 'page.tsx');
195
- const destFile = path.join(outputDir, 'page.tsx');
196
206
  // Don't clobber a user's existing page — same guard the peer template/copy and
197
207
  // theme/add write-leaves apply (init is a public API surface too).
198
208
  if (fs.existsSync(destFile)) {
@@ -206,7 +216,7 @@ function applyTemplate(cwd, {templateName}, invocation, data) {
206
216
  fs.mkdirSync(outputDir, {recursive: true});
207
217
  fs.copyFileSync(srcPath, destFile);
208
218
  const rel = path.relative(cwd, outputDir);
209
- logger.log(`✓ Template created at ${rel}/page.tsx`);
219
+ logger.log(`[ok] Template created at ${rel}/page.tsx`);
210
220
  data.template = 'created';
211
221
  data.templatePath = rel;
212
222
  }
@@ -0,0 +1,120 @@
1
+ // Copyright (c) Meta Platforms, Inc. and affiliates.
2
+
3
+ import {afterEach, beforeEach, describe, expect, it} from 'vitest';
4
+ import * as fs from 'node:fs';
5
+ import * as path from 'node:path';
6
+ import {integrationAdd, integrationAddComponent} from './add-contribution.mjs';
7
+ import {discoverIntegrationComponents} from '../../foundation/discovery/component-discovery.mjs';
8
+
9
+ let tmpDir;
10
+
11
+ beforeEach(() => {
12
+ tmpDir = fs.mkdtempSync(path.join(process.cwd(), '.astryx-add-component-'));
13
+ fs.writeFileSync(
14
+ path.join(tmpDir, 'package.json'),
15
+ `${JSON.stringify({name: '@acme/widgets', version: '1.0.0', files: ['dist']}, null, 2)}\n`,
16
+ );
17
+ fs.writeFileSync(
18
+ path.join(tmpDir, 'astryx.integration.mjs'),
19
+ "export default {\n components: './components',\n};\n",
20
+ );
21
+ });
22
+
23
+ afterEach(() => {
24
+ fs.rmSync(tmpDir, {recursive: true, force: true});
25
+ });
26
+
27
+ /** @param {string} relativeDoc */
28
+ function writeAuthoredComponent(relativeDoc) {
29
+ const doc = path.join(tmpDir, relativeDoc);
30
+ fs.mkdirSync(path.dirname(doc), {recursive: true});
31
+ fs.writeFileSync(
32
+ doc,
33
+ "export default {\n type: 'component',\n name: 'AcmeWidget',\n description: 'The authored AcmeWidget.',\n props: [],\n};\n",
34
+ );
35
+ fs.writeFileSync(
36
+ doc.replace(/\.doc\.(?:ts|mjs|js)$/u, '.tsx'),
37
+ 'export function AcmeWidget() {\n return <div>Authored</div>;\n}\n',
38
+ );
39
+ }
40
+
41
+ /** Every file under the package, with its bytes. */
42
+ function snapshot() {
43
+ /** @type {Record<string, string>} */
44
+ const files = {};
45
+ /** @param {string} dir */
46
+ const walk = dir => {
47
+ for (const entry of fs.readdirSync(dir, {withFileTypes: true})) {
48
+ const full = path.join(dir, entry.name);
49
+ if (entry.isDirectory()) walk(full);
50
+ else files[path.relative(tmpDir, full)] = fs.readFileSync(full, 'utf-8');
51
+ }
52
+ };
53
+ walk(tmpDir);
54
+ return files;
55
+ }
56
+
57
+ describe('integrationAddComponent name collisions', () => {
58
+ it('refuses a name a nested component doc already defines', async () => {
59
+ writeAuthoredComponent('components/AcmeWidget/AcmeWidget.doc.mjs');
60
+ const before = snapshot();
61
+
62
+ await expect(
63
+ integrationAddComponent('AcmeWidget', {cwd: tmpDir}),
64
+ ).rejects.toMatchObject({
65
+ code: 'ERR_FILE_EXISTS',
66
+ message: expect.stringContaining(
67
+ 'components/AcmeWidget/AcmeWidget.doc.mjs',
68
+ ),
69
+ });
70
+ expect(snapshot()).toEqual(before);
71
+ expect(
72
+ discoverIntegrationComponents({
73
+ name: '@acme/widgets',
74
+ components: path.join(tmpDir, 'components'),
75
+ }).map(component => path.relative(tmpDir, component.docPath)),
76
+ ).toEqual([path.join('components', 'AcmeWidget', 'AcmeWidget.doc.mjs')]);
77
+ });
78
+
79
+ it('refuses the same collision under --dry-run', async () => {
80
+ writeAuthoredComponent('components/widgets/AcmeWidget.doc.mjs');
81
+
82
+ await expect(
83
+ integrationAdd('component', 'AcmeWidget', {cwd: tmpDir, dryRun: true}),
84
+ ).rejects.toMatchObject({code: 'ERR_FILE_EXISTS'});
85
+ });
86
+
87
+ it('refuses a same-stem doc with another extension in the root', async () => {
88
+ fs.mkdirSync(path.join(tmpDir, 'components'));
89
+ fs.writeFileSync(
90
+ path.join(tmpDir, 'components', 'AcmeWidget.doc.ts'),
91
+ "export default {type: 'component', name: 'AcmeWidget', props: []};\n",
92
+ );
93
+ const before = snapshot();
94
+
95
+ await expect(
96
+ integrationAddComponent('AcmeWidget', {cwd: tmpDir}),
97
+ ).rejects.toMatchObject({code: 'ERR_FILE_EXISTS'});
98
+ expect(snapshot()).toEqual(before);
99
+ });
100
+
101
+ it('still adds a component whose name is free', async () => {
102
+ writeAuthoredComponent('components/AcmeWidget/AcmeWidget.doc.mjs');
103
+
104
+ const result = await integrationAddComponent('AcmeBadge', {cwd: tmpDir});
105
+
106
+ expect(result.data.files).toEqual([
107
+ 'components/AcmeBadge.doc.mjs',
108
+ 'components/AcmeBadge.tsx',
109
+ 'package.json',
110
+ ]);
111
+ expect(
112
+ discoverIntegrationComponents({
113
+ name: '@acme/widgets',
114
+ components: path.join(tmpDir, 'components'),
115
+ })
116
+ .map(component => component.name)
117
+ .sort(),
118
+ ).toEqual(['AcmeBadge', 'AcmeWidget']);
119
+ });
120
+ });
@@ -5,8 +5,7 @@
5
5
  * contribution into an integration package.
6
6
  *
7
7
  * Dispatches component, doc, template, codemod, and agent-doc. Theme
8
- * delegates to the existing `integrationAddTheme` (not modified here).
9
- * Every root-based kind shares: first add creates the manifest; one shared
8
+ * delegates to the dedicated same-stem descriptor writer. Every root-based kind shares: first add creates the manifest; one shared
10
9
  * writer; never creates `files`/`exports`; atomic staged writes; no clobber;
11
10
  * dry-run receipt predicts real run; post-write verify through the real
12
11
  * discovery/parser seam.
@@ -35,6 +34,7 @@ import {
35
34
  } from '../../foundation/integrations/integrations.mjs';
36
35
  import {isValidSemver} from '../../foundation/env/semver.mjs';
37
36
  import {assertContributionVisible} from '../../foundation/integrations/contribution-inventory.mjs';
37
+ import {findIntegrationComponentDoc} from '../../foundation/discovery/component-discovery.mjs';
38
38
  import {parseAgentDocsField} from '../../authoring/integration/schema.mjs';
39
39
  import {integrationAddTheme} from './add-theme.mjs';
40
40
  import {
@@ -248,11 +248,21 @@ async function addComponent(name, options) {
248
248
  ERROR_CODES.ERR_FILE_EXISTS,
249
249
  );
250
250
  }
251
+ // Discovery keys components by doc stem anywhere under the root, so a second
252
+ // doc with this name would shadow one of the two.
253
+ const existingDoc = findIntegrationComponentDoc({components: root}, name);
254
+ if (existingDoc != null) {
255
+ throw new AstryxError(
256
+ `Refusing to add component "${name}": ${projectPath(path.relative(packageDir, existingDoc))} already defines it.`,
257
+ undefined,
258
+ ERROR_CODES.ERR_FILE_EXISTS,
259
+ );
260
+ }
251
261
 
252
262
  const sourcePath = projectPath(path.relative(packageDir, sourceFile));
253
263
  const extensionlessPath = sourcePath.replace(/\.tsx?$/u, '');
254
264
  const importSpecifier = `${owner}/${extensionlessPath}`;
255
- const docContents = `export default {\n type: 'component',\n name: '${name}',\n import: ${JSON.stringify(importSpecifier)},\n description: '${name} component.',\n props: [],\n};\n`;
265
+ const docContents = `/** @type {import('@astryxdesign/cli/authoring').ComponentDoc} */\nexport default {\n type: 'component',\n name: '${name}',\n displayName: '${name}',\n import: ${JSON.stringify(importSpecifier)},\n usage: {description: '${name} component.'},\n props: [],\n};\n`;
256
266
  const sourceContents = `export function ${name}() {\n return <div>${name}</div>;\n}\n`;
257
267
 
258
268
  /** @type {import('./add-helpers.mjs').WritePlan[]} */
@@ -427,7 +437,7 @@ async function addDoc(name, options) {
427
437
  : options.extends
428
438
  ? `\n extends: '${options.extends}',`
429
439
  : '';
430
- const docContents = `export default {\n type: 'generic',\n name: '${name}',\n title: '${title}',\n description: '${title} documentation.',${relationship}\n sections: [\n {\n title: 'Overview',\n content: [\n { type: 'prose', text: '${title} documentation.' },\n ],\n },\n ],\n};\n`;
440
+ const docContents = `/** @type {import('@astryxdesign/cli/authoring').ReferenceDoc} */\nexport default {\n type: 'generic',\n name: '${name}',\n title: '${title}',\n description: '${title} documentation.',${relationship}\n sections: [\n {\n title: 'Overview',\n content: [\n { type: 'prose', text: '${title} documentation.' },\n ],\n },\n ],\n};\n`;
431
441
 
432
442
  /** @type {import('./add-helpers.mjs').WritePlan[]} */
433
443
  const plans = [{path: docFile, contents: docContents, createOnly: true}];
@@ -554,8 +564,8 @@ async function addTemplate(name, options) {
554
564
  throw error;
555
565
  }
556
566
 
557
- const specFile = assertWithin(`${name}.template.mjs`, root, {
558
- label: 'template spec',
567
+ const specFile = assertWithin(`${name}.doc.mjs`, root, {
568
+ label: 'template descriptor',
559
569
  });
560
570
  const sourceFile = assertWithin(`${name}.tsx`, root, {
561
571
  label: 'template source',
@@ -578,7 +588,8 @@ async function addTemplate(name, options) {
578
588
  const pascalName = kebabToPascal(name);
579
589
  const sourcePath = projectPath(path.relative(packageDir, sourceFile));
580
590
  const extensionlessPath = sourcePath.replace(/\.tsx?$/u, '');
581
- const specContents = `export default {\n type: '${templateType}',\n name: '${name}',\n description: '${kebabToTitle(name)} template.',\n};\n`;
591
+ const blockFields = templateType === 'block' ? '\n aspectRatio: 1,' : '';
592
+ const specContents = `/** @type {import('@astryxdesign/cli/authoring').TemplateDoc} */\nexport default {\n type: '${templateType}',\n name: '${name}',\n displayName: '${kebabToTitle(name)}',\n description: '${kebabToTitle(name)} template.',${blockFields}\n};\n`;
582
593
  const sourceContents = `export default function ${pascalName}() {\n return <div>${kebabToTitle(name)}</div>;\n}\n`;
583
594
 
584
595
  /** @type {import('./add-helpers.mjs').WritePlan[]} */