@astryxdesign/cli 0.6.3-canary.98a2e4e → 0.6.3-canary.9c4d44d

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 (612) hide show
  1. package/README.md +117 -79
  2. package/api/blog/blog.doc.mjs +1 -0
  3. package/api/build/_adapter.d.mts +50 -0
  4. package/api/build/_adapter.mjs +60 -0
  5. package/api/build/build.doc.mjs +16 -9
  6. package/api/build/build.test.mjs +197 -8
  7. package/api/build/build.type.d.mts +91 -2
  8. package/api/build/build.type.mjs +52 -8
  9. package/api/build/help/help.d.mts +12 -5
  10. package/api/build/help/help.mjs +69 -6
  11. package/api/build/kit/kit.d.mts +4 -1
  12. package/api/build/kit/kit.mjs +165 -49
  13. package/api/build/kit/rank.d.mts +44 -0
  14. package/api/build/kit/rank.mjs +432 -0
  15. package/api/build/kit/rank.test.mjs +196 -0
  16. package/api/component/_adapter.d.mts +6 -12
  17. package/api/component/_adapter.mjs +20 -10
  18. package/api/component/component.doc.mjs +13 -3
  19. package/api/component/component.mjs +91 -14
  20. package/api/component/component.test.mjs +38 -0
  21. package/api/component/component.type.d.mts +22 -11
  22. package/api/component/component.type.mjs +32 -24
  23. package/api/component/detail/blocks/blocks.d.mts +2 -1
  24. package/api/component/detail/blocks/blocks.mjs +4 -3
  25. package/api/component/list/list.d.mts +0 -5
  26. package/api/component/list/list.mjs +40 -11
  27. package/api/discover/_adapter.d.mts +114 -6
  28. package/api/discover/_adapter.mjs +372 -17
  29. package/api/discover/_adapter.test.mjs +215 -0
  30. package/api/discover/_catalog-view.d.mts +115 -0
  31. package/api/discover/_catalog-view.mjs +203 -0
  32. package/api/discover/_catalog-view.test.mjs +128 -0
  33. package/api/discover/detail/detail.d.mts +18 -6
  34. package/api/discover/detail/detail.mjs +67 -13
  35. package/api/discover/detail/detail.test.mjs +85 -0
  36. package/api/discover/detail/item/item.d.mts +26 -0
  37. package/api/discover/detail/item/item.mjs +78 -0
  38. package/api/discover/detail/item/item.test.mjs +73 -0
  39. package/api/discover/discover.d.mts +3 -9
  40. package/api/discover/discover.doc.mjs +62 -18
  41. package/api/discover/discover.mjs +220 -36
  42. package/api/discover/discover.test.mjs +11 -2
  43. package/api/discover/discover.type.d.mts +150 -11
  44. package/api/discover/discover.type.mjs +107 -17
  45. package/api/discover/list/list.d.mts +20 -6
  46. package/api/discover/list/list.mjs +45 -12
  47. package/api/discover/list/list.test.mjs +46 -0
  48. package/api/discover/search/search.d.mts +18 -16
  49. package/api/discover/search/search.mjs +102 -56
  50. package/api/discover/search/search.test.mjs +144 -10
  51. package/api/docs/_adapter.d.mts +268 -47
  52. package/api/docs/_adapter.mjs +970 -149
  53. package/api/docs/compiled-topics.test.mjs +78 -0
  54. package/api/docs/detail/detail.d.mts +0 -15
  55. package/api/docs/detail/detail.mjs +22 -78
  56. package/api/docs/detail/section/section.mjs +39 -24
  57. package/api/docs/detail/section/section.test.mjs +19 -9
  58. package/api/docs/docs.d.mts +5 -3
  59. package/api/docs/docs.doc.mjs +39 -17
  60. package/api/docs/docs.mjs +44 -8
  61. package/api/docs/docs.test.mjs +158 -4
  62. package/api/docs/docs.type.d.mts +185 -6
  63. package/api/docs/docs.type.mjs +127 -13
  64. package/api/docs/index/index.mjs +15 -6
  65. package/api/docs/index/index.test.mjs +1 -1
  66. package/api/docs/integration-tree.test.mjs +555 -0
  67. package/api/docs/integrationDocs.test.mjs +14 -14
  68. package/api/docs/list/list.mjs +28 -12
  69. package/api/docs/node/node.d.mts +43 -0
  70. package/api/docs/node/node.mjs +192 -0
  71. package/api/docs/reference-blocks.test.mjs +406 -0
  72. package/api/doctor/doctor.d.mts +54 -4
  73. package/api/doctor/doctor.doc.mjs +1 -0
  74. package/api/doctor/doctor.mjs +332 -21
  75. package/api/doctor/doctor.test.mjs +420 -7
  76. package/api/gap-report/gap-report.doc.mjs +8 -4
  77. package/api/hook/_adapter.mjs +19 -5
  78. package/api/hook/hook.doc.mjs +1 -0
  79. package/api/hook/hook.type.d.mts +3 -3
  80. package/api/hook/hook.type.mjs +11 -11
  81. package/api/hook/list/list.d.mts +1 -1
  82. package/api/hook/list/list.mjs +69 -17
  83. package/api/index.d.mts +1 -1
  84. package/api/index.mjs +1 -0
  85. package/api/init/init.doc.mjs +6 -1
  86. package/api/init/init.test.mjs +41 -1
  87. package/api/init/remove/remove.mjs +1 -1
  88. package/api/init/run/run.mjs +20 -10
  89. package/api/integration/add-contribution.component-names.test.mjs +120 -0
  90. package/api/integration/add-contribution.d.mts +2 -1
  91. package/api/integration/add-contribution.mjs +130 -15
  92. package/api/integration/add-contribution.test.mjs +258 -7
  93. package/api/integration/add-helpers.d.mts +5 -2
  94. package/api/integration/add-helpers.mjs +36 -9
  95. package/api/integration/add-theme.mjs +34 -64
  96. package/api/integration/add-theme.test.mjs +105 -21
  97. package/api/integration/authoring-checks.mjs +138 -28
  98. package/api/integration/authoring-checks.test.mjs +179 -7
  99. package/api/integration/authoring-checks.type.mjs +6 -1
  100. package/api/integration/integration-authoring.type.d.mts +2 -0
  101. package/api/integration/integration-authoring.type.mjs +2 -0
  102. package/api/integration/integration-block-exports.test.mjs +10 -6
  103. package/api/integration/integrationAdd.doc.mjs +14 -4
  104. package/api/integration/integrationAddAgentDoc.doc.mjs +1 -0
  105. package/api/integration/integrationAddCodemod.doc.mjs +1 -0
  106. package/api/integration/integrationAddComponent.doc.mjs +2 -1
  107. package/api/integration/integrationAddDoc.doc.mjs +8 -1
  108. package/api/integration/integrationAddTemplate.doc.mjs +1 -0
  109. package/api/integration/integrationAddTheme.doc.mjs +6 -5
  110. package/api/integration/integrationComponentConflicts.doc.mjs +1 -0
  111. package/api/integration/integrationDocConflicts.doc.mjs +2 -1
  112. package/api/integration/integrationPackCheck.doc.mjs +2 -1
  113. package/api/integration/integrationTemplateConflicts.doc.d.mts +3 -0
  114. package/api/integration/integrationTemplateConflicts.doc.mjs +4 -0
  115. package/api/integration/pack-check.mjs +83 -7
  116. package/api/integration/pack-check.test.mjs +387 -47
  117. package/api/integration/pack-check.type.d.mts +26 -2
  118. package/api/integration/pack-check.type.mjs +14 -1
  119. package/api/integration/summarizeIssues.doc.mjs +1 -0
  120. package/api/integration/template-conflict-compatibility.test.mjs +73 -0
  121. package/api/integration/validate-integration-fixes.test.mjs +1389 -0
  122. package/api/integration/validate-integration.mjs +52 -102
  123. package/api/integration/validate-integration.test.mjs +179 -26
  124. package/api/integration/validate-unread-theme-folders.test.mjs +110 -0
  125. package/api/integration/validateIntegration.doc.mjs +3 -2
  126. package/api/json/assertResponse.doc.mjs +1 -0
  127. package/api/json/envelope-types.test.mjs +76 -0
  128. package/api/json/index.ts +2 -0
  129. package/api/json/isError.doc.mjs +1 -0
  130. package/api/json/parseResponse.doc.mjs +3 -2
  131. package/api/layout/_adapter.mjs +20 -5
  132. package/api/layout/expand/expand.mjs +7 -5
  133. package/api/layout/expand/expand.path-safety.test.mjs +53 -0
  134. package/api/layout/grammar/grammar.mjs +2 -1
  135. package/api/layout/layoutCheck.doc.mjs +1 -0
  136. package/api/layout/layoutExpand.doc.mjs +2 -1
  137. package/api/layout/layoutGrammar.doc.mjs +1 -0
  138. package/api/search/search-return-type.test.mjs +54 -0
  139. package/api/search/search.d.mts +61 -10
  140. package/api/search/search.doc.mjs +8 -2
  141. package/api/search/search.mjs +471 -83
  142. package/api/search/search.test.mjs +124 -1
  143. package/api/search/search.type.d.mts +14 -2
  144. package/api/search/search.type.mjs +5 -2
  145. package/api/swizzle/copy/copy.mjs +28 -11
  146. package/api/swizzle/swizzle.doc.mjs +2 -1
  147. package/api/swizzle/swizzle.type.d.mts +2 -2
  148. package/api/swizzle/swizzle.type.mjs +2 -2
  149. package/api/template/copy/copy.mjs +17 -23
  150. package/api/template/copy/copy.test.mjs +17 -0
  151. package/api/template/list/list.mjs +1 -0
  152. package/api/template/table-floating-bulk-actions.test.mjs +66 -0
  153. package/api/template/template-integration.test.mjs +1072 -3
  154. package/api/template/template-suffix.test.mjs +41 -21
  155. package/api/template/template.doc.mjs +30 -8
  156. package/api/template/template.mjs +45 -8
  157. package/api/template/template.type.d.mts +11 -13
  158. package/api/template/template.type.mjs +15 -14
  159. package/api/theme/_adapter.d.mts +2 -3
  160. package/api/theme/_adapter.mjs +4 -5
  161. package/api/theme/add/add.binary.test.mjs +84 -0
  162. package/api/theme/add/add.mjs +31 -22
  163. package/api/theme/add/add.rollback.test.mjs +158 -0
  164. package/api/theme/add/add.staging.test.mjs +83 -0
  165. package/api/theme/add/add.test.mjs +14 -1
  166. package/api/theme/build/build.family.test.mjs +7 -12
  167. package/api/theme/build/build.mjs +140 -59
  168. package/api/theme/build/build.public-component-vars.test.mjs +1 -1
  169. package/api/theme/build/build.receipt-doc.test.mjs +111 -0
  170. package/api/theme/build/build.rollback.test.mjs +148 -0
  171. package/api/theme/build/build.test.mjs +127 -0
  172. package/api/theme/build/font-warning.mjs +3 -3
  173. package/api/theme/build/font-warning.test.mjs +5 -2
  174. package/api/theme/generateTonalPalette.doc.mjs +1 -0
  175. package/api/theme/integration-themes.test.mjs +39 -28
  176. package/api/theme/list/list.test.mjs +19 -20
  177. package/api/theme/listThemes.doc.mjs +6 -5
  178. package/api/theme/palette/generate/generate.mjs +8 -3
  179. package/api/theme/palette/generate/generate.test.mjs +96 -0
  180. package/api/theme/palette/generate/generator.d.mts +10 -13
  181. package/api/theme/palette/generate/generator.mjs +15 -4
  182. package/api/theme/palette/generate/generator.test.mjs +10 -0
  183. package/api/theme/template/template.mjs +11 -2
  184. package/api/theme/template/template.test.mjs +20 -0
  185. package/api/theme/theme.type.d.mts +170 -11
  186. package/api/theme/theme.type.mjs +94 -27
  187. package/api/theme/themeAdd.doc.mjs +4 -3
  188. package/api/theme/themeBuild.doc.mjs +8 -4
  189. package/api/theme/themeList.doc.mjs +6 -3
  190. package/api/theme/themeListAvailable.doc.mjs +5 -3
  191. package/api/theme/themePaletteGenerate.doc.mjs +1 -0
  192. package/api/theme/themeTargets.doc.mjs +1 -0
  193. package/api/theme/themeTemplate.doc.mjs +6 -2
  194. package/api/upgrade/_adapter.d.mts +32 -5
  195. package/api/upgrade/_adapter.mjs +124 -73
  196. package/api/upgrade/list/list.mjs +2 -1
  197. package/api/upgrade/list/list.test.mjs +73 -0
  198. package/api/upgrade/provider-agreement.test.mjs +152 -0
  199. package/api/upgrade/run/run.mjs +356 -59
  200. package/api/upgrade/status/status.mjs +2 -2
  201. package/api/upgrade/upgrade.doc.mjs +8 -2
  202. package/api/upgrade/upgrade.type.d.mts +42 -4
  203. package/api/upgrade/upgrade.type.mjs +26 -10
  204. package/assets/codemods/__tests__/runner.test.mjs +330 -8
  205. package/assets/codemods/integration-discovery.mjs +48 -4
  206. package/assets/codemods/integration-discovery.test.mjs +73 -0
  207. package/assets/codemods/integration-runner.mjs +56 -4
  208. package/assets/codemods/integration-runner.protection.test.mjs +153 -0
  209. package/assets/codemods/run-codemod.mjs +177 -34
  210. package/assets/codemods/runner.mjs +350 -102
  211. package/assets/codemods/term-log.mjs +32 -8
  212. package/assets/codemods/term-log.test.mjs +19 -1
  213. package/assets/codemods/transform-prop.mjs +109 -0
  214. package/assets/codemods/transform-prop.test.mjs +95 -0
  215. package/assets/codemods/transforms/next/__tests__/migrate-native-picker-to-presentation.test.mjs +63 -0
  216. package/assets/codemods/transforms/next/__tests__/migrate-theme-catalog-to-descriptors.test.mjs +220 -0
  217. package/assets/codemods/transforms/next/index.mjs +19 -1
  218. package/assets/codemods/transforms/next/migrate-native-picker-to-presentation.mjs +148 -0
  219. package/assets/codemods/transforms/next/migrate-theme-catalog-to-descriptors.mjs +141 -0
  220. package/assets/codemods/transforms/v0.0.14/__tests__/rename-status-variants.test.mjs +86 -165
  221. package/assets/codemods/transforms/v0.0.14/rename-status-variants.mjs +72 -210
  222. package/assets/codemods/transforms/v0.1.8/__tests__/rename-avatar-size-scale.test.mjs +83 -115
  223. package/assets/codemods/transforms/v0.1.8/rename-avatar-size-scale.mjs +57 -186
  224. package/assets/codemods/transforms/v0.3.0/__tests__/unwrap-authoring-factories.test.mjs +27 -5
  225. package/assets/codemods/transforms/v0.3.0/unwrap-authoring-factories.mjs +20 -5
  226. package/assets/codemods/transforms/v0.6.0/__tests__/next-codemods.test.mjs +47 -0
  227. package/assets/codemods/transforms/v0.6.0/rename-resizable-pixel-bounds.mjs +57 -10
  228. package/assets/docs/getting-started.doc.mjs +2 -2
  229. package/assets/docs/internationalization.doc.mjs +7 -5
  230. package/assets/docs/layout.doc.dense.mjs +2 -2
  231. package/assets/docs/layout.doc.mjs +1 -1
  232. package/assets/docs/principles.doc.mjs +6 -6
  233. package/assets/docs/styling-libraries.doc.mjs +4 -4
  234. package/assets/docs/styling.doc.mjs +4 -4
  235. package/assets/docs/theme.doc.mjs +5 -5
  236. package/assets/docs/tokens.doc.mjs +1 -1
  237. package/assets/docs/tree/api.doc.mjs +30 -0
  238. package/assets/docs/tree/cli.doc.mjs +23 -0
  239. package/assets/docs/tree/commands.doc.mjs +25 -0
  240. package/assets/docs/{cli-integrations.doc.mjs → tree/integrations.doc.mjs} +95 -39
  241. package/assets/docs/tree/integrations.test.mjs +62 -0
  242. package/assets/docs/tree/writing-docs.doc.mjs +286 -0
  243. package/assets/docs/working-with-ai.doc.mjs +4 -4
  244. package/assets/templates/blocks/components/CheckboxList/CheckboxListSelectAllPattern.doc.mjs +1 -1
  245. package/assets/templates/blocks/components/CheckboxList/CheckboxListSelectAllPattern.tsx +1 -3
  246. package/assets/templates/blocks/components/DateInput/DateInputDateRange.tsx +1 -1
  247. package/assets/templates/blocks/components/InternationalizationProvider/InternationalizationProvider01ShippedLocale.tsx +1 -1
  248. package/assets/templates/blocks/components/Item/ItemDocumentTabs.doc.mjs +14 -0
  249. package/assets/templates/blocks/components/Item/ItemDocumentTabs.tsx +100 -0
  250. package/assets/templates/blocks/components/ScrollableArea/ScrollableAreaInlineRail.doc.mjs +14 -0
  251. package/assets/templates/blocks/components/ScrollableArea/ScrollableAreaInlineRail.tsx +61 -0
  252. package/assets/templates/blocks/components/ScrollableArea/ScrollableAreaOverscrollChaining.doc.mjs +14 -0
  253. package/assets/templates/blocks/components/ScrollableArea/ScrollableAreaOverscrollChaining.tsx +126 -0
  254. package/assets/templates/blocks/components/ScrollableArea/ScrollableAreaShowcase.doc.mjs +15 -0
  255. package/assets/templates/blocks/components/ScrollableArea/ScrollableAreaShowcase.tsx +86 -0
  256. package/assets/templates/blocks/components/ScrollableArea/ScrollableAreaStickyGroupHeaders.doc.mjs +14 -0
  257. package/assets/templates/blocks/components/ScrollableArea/ScrollableAreaStickyGroupHeaders.tsx +99 -0
  258. package/assets/templates/blocks/components/ScrollableArea/ScrollableAreaStickyPassthrough.doc.mjs +14 -0
  259. package/assets/templates/blocks/components/ScrollableArea/ScrollableAreaStickyPassthrough.tsx +122 -0
  260. package/assets/templates/blocks/components/ScrollableArea/ScrollableAreaTwoAxisBoard.doc.mjs +14 -0
  261. package/assets/templates/blocks/components/ScrollableArea/ScrollableAreaTwoAxisBoard.tsx +95 -0
  262. package/assets/templates/blocks/components/Table/TableBulkActionsTable.doc.mjs +14 -0
  263. package/assets/templates/blocks/components/Table/TableBulkActionsTable.tsx +71 -0
  264. package/assets/templates/blocks/components/Table/TableFloatingBulkActionsTable.doc.mjs +19 -0
  265. package/assets/templates/blocks/components/Table/TableFloatingBulkActionsTable.tsx +139 -0
  266. package/assets/templates/blocks/components/TimeInput/TimeInputConstrained.tsx +1 -0
  267. package/assets/templates/pages/table-tree/page.tsx +1704 -0
  268. package/assets/templates/pages/table-tree/template.doc.mjs +12 -0
  269. package/assets/templates/themes/butter/butterTheme.doc.mjs +11 -0
  270. package/assets/templates/themes/chocolate/chocolateTheme.doc.mjs +11 -0
  271. package/assets/templates/themes/gothic/gothicTheme.doc.mjs +11 -0
  272. package/assets/templates/themes/matcha/matchaTheme.doc.mjs +11 -0
  273. package/assets/templates/themes/neutral/neutralTheme.doc.mjs +11 -0
  274. package/assets/templates/themes/stone/stoneTheme.doc.mjs +11 -0
  275. package/assets/templates/themes/y2k/y2kTheme.doc.mjs +11 -0
  276. package/authoring/_shared/contract.ts +22 -0
  277. package/authoring/codemod/codemod.doc.mjs +7 -2
  278. package/authoring/codemod/parse.d.mts +8 -8
  279. package/authoring/codemod/parse.mjs +8 -6
  280. package/authoring/codemod/type.ts +12 -0
  281. package/authoring/config/config.doc.mjs +10 -2
  282. package/authoring/config/debug-composition.test.mjs +92 -0
  283. package/authoring/config/parse.d.mts +15 -13
  284. package/authoring/config/parse.mjs +27 -8
  285. package/authoring/config/parse.test.mjs +8 -0
  286. package/authoring/config/type.ts +29 -6
  287. package/authoring/debug/debug.doc.d.mts +11 -0
  288. package/authoring/debug/debug.doc.mjs +182 -0
  289. package/authoring/debug/parse.d.mts +7 -7
  290. package/authoring/debug/parse.mjs +3 -3
  291. package/authoring/discover/discover.doc.d.mts +13 -0
  292. package/authoring/discover/discover.doc.mjs +138 -0
  293. package/authoring/discover/parse.d.mts +24 -0
  294. package/authoring/discover/parse.mjs +128 -0
  295. package/authoring/discover/parse.test.mjs +124 -0
  296. package/authoring/discover/type.ts +87 -0
  297. package/authoring/doctypes/_schema.d.mts +240 -128
  298. package/authoring/doctypes/_schema.mjs +198 -16
  299. package/authoring/doctypes/base/graph-fields.doc.mjs +15 -6
  300. package/authoring/doctypes/base/type.ts +9 -4
  301. package/authoring/doctypes/command/command.doc.mjs +4 -3
  302. package/authoring/doctypes/command/parse.d.mts +2 -2
  303. package/authoring/doctypes/command/parse.mjs +1 -1
  304. package/authoring/doctypes/command/type.ts +4 -4
  305. package/authoring/doctypes/component/component.doc.mjs +5 -2
  306. package/authoring/doctypes/component/parse.d.mts +2 -2
  307. package/authoring/doctypes/component/parse.mjs +1 -1
  308. package/authoring/doctypes/component/type.ts +3 -3
  309. package/authoring/doctypes/doctypes-new.test.mjs +48 -6
  310. package/authoring/doctypes/enum/enum.doc.mjs +1 -1
  311. package/authoring/doctypes/enum/parse.d.mts +2 -2
  312. package/authoring/doctypes/enum/parse.mjs +1 -1
  313. package/authoring/doctypes/enum/type.ts +2 -2
  314. package/authoring/doctypes/function/function.doc.mjs +7 -2
  315. package/authoring/doctypes/function/parse.d.mts +2 -2
  316. package/authoring/doctypes/function/parse.mjs +1 -1
  317. package/authoring/doctypes/function/type.ts +4 -3
  318. package/authoring/doctypes/hook/hook.doc.mjs +4 -0
  319. package/authoring/doctypes/hook/parse.d.mts +2 -2
  320. package/authoring/doctypes/hook/parse.mjs +1 -1
  321. package/authoring/doctypes/hook/type.ts +3 -3
  322. package/authoring/doctypes/legacy.d.mts +6 -6
  323. package/authoring/doctypes/legacy.mjs +3 -3
  324. package/authoring/doctypes/load-contract.test.mjs +233 -0
  325. package/authoring/doctypes/namespace/namespace.doc.mjs +9 -9
  326. package/authoring/doctypes/namespace/parse.d.mts +2 -2
  327. package/authoring/doctypes/namespace/parse.mjs +1 -1
  328. package/authoring/doctypes/namespace/parse.test.mjs +23 -25
  329. package/authoring/doctypes/namespace/type.ts +6 -3
  330. package/authoring/doctypes/parse.d.mts +22 -20
  331. package/authoring/doctypes/parse.mjs +19 -14
  332. package/authoring/doctypes/reference/parse.d.mts +2 -2
  333. package/authoring/doctypes/reference/parse.mjs +1 -1
  334. package/authoring/doctypes/reference/reference.doc.mjs +39 -6
  335. package/authoring/doctypes/reference/type.ts +31 -14
  336. package/authoring/doctypes/schema/parse.d.mts +2 -2
  337. package/authoring/doctypes/schema/parse.mjs +1 -1
  338. package/authoring/doctypes/schema/schema.doc.mjs +1 -1
  339. package/authoring/doctypes/schema/type.ts +3 -4
  340. package/authoring/doctypes/template/parse.d.mts +94 -1
  341. package/authoring/doctypes/template/parse.mjs +37 -1
  342. package/authoring/doctypes/template/parse.test.mjs +18 -0
  343. package/authoring/doctypes/template/template.doc.mjs +13 -3
  344. package/authoring/doctypes/template/type.ts +12 -1
  345. package/authoring/doctypes/theme/parse.d.mts +35 -0
  346. package/authoring/doctypes/theme/parse.mjs +76 -0
  347. package/authoring/doctypes/theme/theme.doc.d.mts +9 -0
  348. package/authoring/doctypes/theme/theme.doc.mjs +79 -0
  349. package/authoring/doctypes/theme/type.ts +42 -0
  350. package/authoring/doctypes/types.ts +12 -11
  351. package/authoring/gap-report/gap-report.doc.d.mts +12 -0
  352. package/authoring/gap-report/gap-report.doc.mjs +183 -0
  353. package/authoring/gap-report/parse.d.mts +9 -9
  354. package/authoring/gap-report/parse.mjs +6 -6
  355. package/authoring/gap-report/type.ts +1 -1
  356. package/authoring/identity/identity.doc.mjs +3 -2
  357. package/authoring/identity/type.ts +10 -10
  358. package/authoring/index.d.mts +2 -0
  359. package/authoring/index.d.ts +32 -19
  360. package/authoring/index.mjs +3 -1
  361. package/authoring/integration/integration.doc.mjs +2 -2
  362. package/authoring/integration/parse.d.mts +2 -2
  363. package/authoring/integration/parse.mjs +1 -1
  364. package/authoring/integration/schema.d.mts +4 -4
  365. package/authoring/integration/schema.mjs +3 -3
  366. package/authoring/integration/type.ts +5 -11
  367. package/clients/cli/__tests__/cliManifest.test.ts +27 -29
  368. package/clients/cli/command-load-failure.test.mjs +83 -0
  369. package/clients/cli/commands/blog.doc.mjs +1 -1
  370. package/clients/cli/commands/blog.mjs +23 -8
  371. package/clients/cli/commands/blog.test.mjs +42 -1
  372. package/clients/cli/commands/build-theme.adaptations.test.mjs +100 -1
  373. package/clients/cli/commands/build-theme.ascii-output.test.mjs +161 -0
  374. package/clients/cli/commands/build-theme.flag-docs.test.mjs +110 -0
  375. package/clients/cli/commands/build-theme.mjs +16 -50
  376. package/clients/cli/commands/build-theme.path-safety.test.mjs +86 -1
  377. package/clients/cli/commands/build-theme.variants.test.mjs +76 -0
  378. package/clients/cli/commands/build.doc.mjs +16 -8
  379. package/clients/cli/commands/build.exit-codes-doc.test.mjs +50 -0
  380. package/clients/cli/commands/build.mjs +137 -114
  381. package/clients/cli/commands/build.playbook.test.mjs +75 -0
  382. package/clients/cli/commands/build.text-fields.test.mjs +81 -0
  383. package/clients/cli/commands/component/index.mjs +3 -8
  384. package/clients/cli/commands/component-ownership.test.mjs +3 -3
  385. package/clients/cli/commands/component-package.test.mjs +46 -0
  386. package/clients/cli/commands/component-resolution.test.mjs +21 -0
  387. package/clients/cli/commands/component.doc.mjs +1 -1
  388. package/clients/cli/commands/component.test.mjs +19 -0
  389. package/clients/cli/commands/detail-levels.test.mjs +2 -2
  390. package/clients/cli/commands/discover.broken-integration.test.mjs +52 -5
  391. package/clients/cli/commands/discover.components-flag.test.mjs +97 -0
  392. package/clients/cli/commands/discover.doc.mjs +55 -9
  393. package/clients/cli/commands/discover.mjs +393 -118
  394. package/clients/cli/commands/discover.sources.test.mjs +267 -0
  395. package/clients/cli/commands/discover.text-projection.test.mjs +103 -0
  396. package/clients/cli/commands/docs.doc.mjs +21 -9
  397. package/clients/cli/commands/docs.mjs +184 -70
  398. package/clients/cli/commands/docs.test.mjs +120 -16
  399. package/clients/cli/commands/doctor-integration-components.doc.mjs +1 -1
  400. package/clients/cli/commands/doctor-integration-docs.doc.mjs +3 -3
  401. package/clients/cli/commands/doctor-integration-templates.doc.mjs +15 -8
  402. package/clients/cli/commands/doctor-integration-validate.doc.mjs +1 -1
  403. package/clients/cli/commands/doctor-integration.doc.mjs +1 -1
  404. package/clients/cli/commands/doctor-integration.package-json.test.mjs +53 -0
  405. package/clients/cli/commands/doctor-integration.test.mjs +90 -8
  406. package/clients/cli/commands/doctor.doc.mjs +1 -1
  407. package/clients/cli/commands/doctor.mjs +59 -32
  408. package/clients/cli/commands/doctor.test.mjs +42 -0
  409. package/clients/cli/commands/gap-report.doc.mjs +17 -6
  410. package/clients/cli/commands/gap-report.test.mjs +72 -0
  411. package/clients/cli/commands/hook/index.mjs +7 -17
  412. package/clients/cli/commands/hook.doc.mjs +1 -1
  413. package/clients/cli/commands/hook.text-projection.test.mjs +45 -0
  414. package/clients/cli/commands/init.doc.mjs +20 -9
  415. package/clients/cli/commands/init.flag-help.test.mjs +153 -0
  416. package/clients/cli/commands/integration-add.controls.test.mjs +132 -0
  417. package/clients/cli/commands/integration-add.doc.mjs +32 -6
  418. package/clients/cli/commands/integration-authoring.test.mjs +13 -9
  419. package/clients/cli/commands/integration-pack.doc.mjs +1 -1
  420. package/clients/cli/commands/integration-real-world.test.mjs +3 -9
  421. package/clients/cli/commands/integration.doc.mjs +1 -1
  422. package/clients/cli/commands/integration.mjs +1 -0
  423. package/clients/cli/commands/interactive-guard.test.mjs +101 -24
  424. package/clients/cli/commands/json-contract.test.mjs +33 -0
  425. package/clients/cli/commands/layout-check.doc.mjs +15 -4
  426. package/clients/cli/commands/layout-expand.doc.mjs +22 -5
  427. package/clients/cli/commands/layout-grammar.doc.mjs +1 -1
  428. package/clients/cli/commands/layout.doc.mjs +3 -3
  429. package/clients/cli/commands/layout.mjs +21 -9
  430. package/clients/cli/commands/layout.path-help.test.mjs +33 -0
  431. package/clients/cli/commands/layout.stdin-cap.test.mjs +47 -0
  432. package/clients/cli/commands/layout.text-fields.test.mjs +39 -0
  433. package/clients/cli/commands/manifest.doc.mjs +1 -1
  434. package/clients/cli/commands/no-prompt-wording.test.mjs +94 -0
  435. package/clients/cli/commands/search.doc.mjs +7 -4
  436. package/clients/cli/commands/search.mjs +28 -9
  437. package/clients/cli/commands/search.test.mjs +75 -0
  438. package/clients/cli/commands/setup-nudge.test.mjs +6 -0
  439. package/clients/cli/commands/swizzle.doc.mjs +3 -2
  440. package/clients/cli/commands/swizzle.path-safety.test.mjs +42 -0
  441. package/clients/cli/commands/template.doc.mjs +52 -13
  442. package/clients/cli/commands/template.flag-help.test.mjs +117 -0
  443. package/clients/cli/commands/template.mjs +4 -91
  444. package/clients/cli/commands/template.path-help.test.mjs +40 -0
  445. package/clients/cli/commands/text-json-parity.test.mjs +719 -0
  446. package/clients/cli/commands/theme-add.doc.mjs +4 -3
  447. package/clients/cli/commands/theme-build.doc.mjs +8 -7
  448. package/clients/cli/commands/theme-list.doc.mjs +2 -2
  449. package/clients/cli/commands/theme-palette-generate.doc.mjs +3 -3
  450. package/clients/cli/commands/theme-palette-generate.test.mjs +19 -0
  451. package/clients/cli/commands/theme-palette.doc.mjs +1 -1
  452. package/clients/cli/commands/theme-targets.behavior.test.mjs +4 -3
  453. package/clients/cli/commands/theme-targets.doc.mjs +1 -1
  454. package/clients/cli/commands/theme-template.behavior.test.mjs +12 -0
  455. package/clients/cli/commands/theme-template.doc.mjs +2 -2
  456. package/clients/cli/commands/theme.doc.mjs +1 -1
  457. package/clients/cli/commands/upgrade.ascii-output.test.mjs +87 -0
  458. package/clients/cli/commands/upgrade.doc.mjs +20 -8
  459. package/clients/cli/commands/upgrade.file-protection.test.mjs +228 -0
  460. package/clients/cli/commands/upgrade.flag-help.test.mjs +188 -0
  461. package/clients/cli/commands/upgrade.hook-output.test.mjs +88 -0
  462. package/clients/cli/commands/upgrade.mjs +29 -7
  463. package/clients/cli/formatters/index.mjs +2 -0
  464. package/clients/cli/formatters/index.test.mjs +6 -0
  465. package/clients/cli/index.mjs +21 -30
  466. package/clients/cli/latest-version-env.test.mjs +50 -0
  467. package/clients/cli/lib/cli-error.test.mjs +7 -0
  468. package/clients/cli/lib/component-format.mjs +9 -9
  469. package/clients/cli/lib/component-format.test.mjs +1 -1
  470. package/clients/cli/lib/define-command.mjs +32 -6
  471. package/clients/cli/lib/doc-text-ascii.test.mjs +82 -0
  472. package/clients/cli/lib/exit-codes.test.mjs +97 -0
  473. package/clients/cli/lib/hook-format.mjs +19 -10
  474. package/clients/cli/lib/json-shim.mjs +38 -2
  475. package/clients/cli/lib/json-shim.test.mjs +83 -0
  476. package/clients/cli/lib/manifest.d.ts +2 -0
  477. package/clients/cli/lib/manifest.mjs +32 -2
  478. package/clients/cli/lib/manifest.test.mjs +17 -0
  479. package/foundation/agent-docs/agent-docs.d.mts +7 -2
  480. package/foundation/agent-docs/agent-docs.mjs +82 -12
  481. package/foundation/agent-docs/agent-docs.path-safety.test.mjs +266 -4
  482. package/foundation/agent-docs/agent-docs.test.mjs +19 -1
  483. package/foundation/config/integration-debug.test.mjs +28 -3
  484. package/foundation/config/project-themes.test.mjs +11 -19
  485. package/foundation/config/project.d.mts +20 -11
  486. package/foundation/config/project.mjs +246 -89
  487. package/foundation/config/project.test.mjs +270 -21
  488. package/foundation/discovery/authoring-self-docs.d.mts +18 -0
  489. package/foundation/discovery/authoring-self-docs.mjs +38 -15
  490. package/foundation/discovery/authoring-self-docs.test.mjs +87 -12
  491. package/foundation/discovery/authoring-surface.d.mts +74 -0
  492. package/foundation/discovery/authoring-surface.mjs +525 -0
  493. package/foundation/discovery/authoring-surface.test.mjs +392 -0
  494. package/foundation/discovery/cli-self-docs.d.mts +119 -0
  495. package/foundation/discovery/cli-self-docs.mjs +490 -0
  496. package/foundation/discovery/cli-self-docs.test.mjs +375 -0
  497. package/foundation/discovery/component-discovery.d.mts +38 -0
  498. package/foundation/discovery/component-discovery.mjs +48 -0
  499. package/foundation/discovery/component-loader.d.mts +35 -38
  500. package/foundation/discovery/component-loader.mjs +53 -222
  501. package/foundation/discovery/docs-discovery.d.mts +114 -9
  502. package/foundation/discovery/docs-discovery.mjs +239 -30
  503. package/foundation/discovery/docs-discovery.test.mjs +150 -35
  504. package/foundation/discovery/docs-output-budget.d.mts +2 -2
  505. package/foundation/discovery/docs-output-budget.mjs +1 -1
  506. package/foundation/discovery/docs-section-key.d.mts +28 -10
  507. package/foundation/discovery/docs-section-key.mjs +134 -33
  508. package/foundation/discovery/docs-section-key.test.mjs +41 -19
  509. package/foundation/discovery/template-adapter.d.mts +113 -11
  510. package/foundation/discovery/template-adapter.fixture-refs.test.mjs +248 -0
  511. package/foundation/discovery/template-adapter.integration-isolation.test.mjs +94 -0
  512. package/foundation/discovery/template-adapter.mjs +772 -82
  513. package/foundation/discovery/template-adapter.test.mjs +57 -0
  514. package/foundation/discovery/template-conflict-release.d.mts +13 -0
  515. package/foundation/discovery/template-conflict-release.mjs +40 -0
  516. package/foundation/discovery/template-conflict-release.test.mjs +40 -0
  517. package/foundation/discovery/theme-discovery.d.mts +67 -7
  518. package/foundation/discovery/theme-discovery.mjs +916 -186
  519. package/foundation/discovery/theme-discovery.test.mjs +613 -219
  520. package/foundation/discovery/theming-targets.test.mjs +4 -0
  521. package/foundation/doc-compiler/bundle.d.mts +47 -0
  522. package/foundation/doc-compiler/bundle.mjs +278 -0
  523. package/foundation/doc-compiler/bundle.test.mjs +266 -0
  524. package/foundation/doc-compiler/compile.d.mts +343 -0
  525. package/foundation/doc-compiler/compile.mjs +558 -0
  526. package/foundation/doc-compiler/diagnostics.d.mts +126 -0
  527. package/foundation/doc-compiler/diagnostics.mjs +305 -0
  528. package/foundation/doc-compiler/doc-compiler.test.mjs +714 -0
  529. package/foundation/doc-compiler/doc-loads.test.mjs +1642 -0
  530. package/foundation/doc-compiler/import.d.mts +24 -0
  531. package/foundation/doc-compiler/import.mjs +59 -0
  532. package/foundation/doc-compiler/inputs.d.mts +102 -0
  533. package/foundation/doc-compiler/inputs.mjs +291 -0
  534. package/foundation/doc-compiler/inputs.test.mjs +299 -0
  535. package/foundation/doc-compiler/ir.d.mts +22 -0
  536. package/foundation/doc-compiler/ir.mjs +471 -0
  537. package/foundation/doc-compiler/lenses.d.mts +36 -0
  538. package/foundation/doc-compiler/lenses.mjs +173 -0
  539. package/foundation/doc-compiler/links.d.mts +162 -0
  540. package/foundation/doc-compiler/links.mjs +294 -0
  541. package/foundation/doc-compiler/links.test.mjs +192 -0
  542. package/foundation/doc-compiler/lower-doc.test.mjs +495 -0
  543. package/foundation/doc-compiler/overlays.d.mts +37 -0
  544. package/foundation/doc-compiler/overlays.mjs +206 -0
  545. package/foundation/doc-compiler/parse-readable.d.mts +9 -0
  546. package/foundation/doc-compiler/parse-readable.mjs +29 -0
  547. package/foundation/doc-compiler/read.d.mts +127 -0
  548. package/foundation/doc-compiler/read.mjs +325 -0
  549. package/foundation/doc-compiler/read.test.mjs +313 -0
  550. package/foundation/doc-compiler/source.d.mts +33 -0
  551. package/foundation/doc-compiler/source.mjs +128 -0
  552. package/foundation/doc-compiler/tree.d.mts +288 -0
  553. package/foundation/doc-compiler/tree.mjs +876 -0
  554. package/foundation/doc-compiler/tree.test.mjs +598 -0
  555. package/foundation/fs/file-protection.d.mts +33 -0
  556. package/foundation/fs/file-protection.mjs +825 -0
  557. package/foundation/fs/file-protection.test.mjs +250 -0
  558. package/foundation/fs/module-loader.d.mts +1 -0
  559. package/foundation/fs/module-loader.mjs +50 -1
  560. package/foundation/fs/module-loader.stdout.test.mjs +332 -0
  561. package/foundation/fs/path-safety.d.mts +3 -2
  562. package/foundation/fs/path-safety.mjs +49 -19
  563. package/foundation/fs/path-safety.test.mjs +50 -0
  564. package/foundation/fs/publish-file-hardlink-unavailable.test.mjs +129 -94
  565. package/foundation/integrations/autolink.d.mts +58 -1
  566. package/foundation/integrations/autolink.mjs +143 -52
  567. package/foundation/integrations/autolink.test.mjs +1 -1
  568. package/foundation/integrations/cli-requirement.d.mts +45 -0
  569. package/foundation/integrations/cli-requirement.mjs +154 -0
  570. package/foundation/integrations/cli-requirement.test.mjs +84 -0
  571. package/foundation/integrations/contribution-fixes.d.mts +145 -0
  572. package/foundation/integrations/contribution-fixes.mjs +1284 -0
  573. package/foundation/integrations/contribution-inventory.d.mts +3 -2
  574. package/foundation/integrations/contribution-inventory.mjs +27 -24
  575. package/foundation/integrations/contribution-inventory.test.mjs +86 -27
  576. package/foundation/integrations/integration-warnings.d.mts +9 -2
  577. package/foundation/integrations/integration-warnings.mjs +51 -26
  578. package/foundation/integrations/integration-warnings.test.mjs +74 -1
  579. package/foundation/integrations/integrations.d.mts +17 -1
  580. package/foundation/integrations/integrations.mjs +52 -98
  581. package/foundation/integrations/integrations.test.mjs +31 -0
  582. package/foundation/integrations/provider-ledger.test.mjs +275 -0
  583. package/foundation/integrations/provider-resolution.d.mts +152 -0
  584. package/foundation/integrations/provider-resolution.mjs +576 -0
  585. package/foundation/integrations/provider-resolution.test.mjs +369 -0
  586. package/foundation/integrations/theme-descriptor.d.mts +8 -0
  587. package/foundation/integrations/theme-descriptor.mjs +44 -0
  588. package/foundation/integrations/validate-contributions.mjs +121 -29
  589. package/foundation/response/base.d.ts +8 -4
  590. package/foundation/response/error-codes.d.mts +3 -1
  591. package/foundation/response/error-codes.d.ts +2 -0
  592. package/foundation/response/error-codes.doc.mjs +13 -4
  593. package/foundation/response/error-codes.mjs +8 -2
  594. package/foundation/response/error-codes.test.mjs +137 -10
  595. package/foundation/response/json-contract.test.mjs +11 -0
  596. package/foundation/response/json.d.mts +4 -2
  597. package/foundation/response/json.mjs +8 -10
  598. package/foundation/response/response-types.doc.d.mts +5 -1
  599. package/foundation/response/response-types.doc.mjs +37 -22
  600. package/foundation/response/response-types.doc.test.mjs +158 -0
  601. package/foundation/response/response.doc.mjs +1 -1
  602. package/foundation/text/string-utils.d.mts +8 -0
  603. package/foundation/text/string-utils.mjs +40 -10
  604. package/foundation/xle/expand.d.mts +2 -0
  605. package/foundation/xle/expand.mjs +4 -3
  606. package/foundation/xle/expand.test.mjs +54 -0
  607. package/foundation/xle/xle.test.mjs +13 -0
  608. package/package.json +10 -11
  609. package/assets/templates/themes/manifest.json +0 -95
  610. package/clients/cli/lib/update-check.mjs +0 -83
  611. package/clients/cli/lib/update-check.test.mjs +0 -137
  612. package/clients/cli/update-hint-commands.test.mjs +0 -54
@@ -23,13 +23,15 @@
23
23
 
24
24
  import * as fs from 'node:fs';
25
25
  import * as path from 'node:path';
26
- import {createJiti} from 'jiti';
27
- import {loadModuleWithParser} from '../fs/module-loader.mjs';
28
- import {parseTemplate} from '../../authoring/doctypes/template/parse.mjs';
26
+ import {createRequire} from 'node:module';
27
+ import {readDocView} from '../doc-compiler/read.mjs';
28
+ import {importDocModule} from '../doc-compiler/import.mjs';
29
29
  import {CLI_ROOT, discoverExternalPackages} from '../fs/paths.mjs';
30
30
  import {CORE_PROVIDER_ID} from '../identity/providers.mjs';
31
31
  import {Project} from '../config/project.mjs';
32
32
 
33
+ const require = createRequire(import.meta.url);
34
+
33
35
  /** Identity used for core (built-in) templates in package-scoped listings. */
34
36
  const CORE_PACKAGE = CORE_PROVIDER_ID;
35
37
 
@@ -43,6 +45,53 @@ export function pkgOf(t) {
43
45
  return t.package ?? CORE_PACKAGE;
44
46
  }
45
47
 
48
+ /**
49
+ * Every id that should resolve to a discovered template. Replacements keep
50
+ * their own integration id and also own the Core id they replace.
51
+ * @param {{dirName: string, replaces?: string}} template
52
+ * @returns {string[]}
53
+ */
54
+ export function templateLookupIds(template) {
55
+ if (template.replaces && template.replaces !== template.dirName) {
56
+ return [template.dirName, template.replaces];
57
+ }
58
+ return [template.dirName];
59
+ }
60
+
61
+ /**
62
+ * Remove entries shadowed in the default discovery view while retaining them in
63
+ * package-scoped views. An active replacement owns its target id. A rejected
64
+ * same-id declaration falls back to Core when a Core template of the same kind
65
+ * owns that id; otherwise the integration template remains available by kind.
66
+ * @param {DiscoveredTemplate[]} templates
67
+ * @returns {DiscoveredTemplate[]}
68
+ */
69
+ export function effectiveTemplateDiscovery(templates) {
70
+ const activeTargets = new Set(
71
+ templates
72
+ .filter(template => template.replaces != null)
73
+ .map(template => `${template.type}:${template.replaces}`),
74
+ );
75
+ const coreIds = new Set(
76
+ templates
77
+ .filter(template => pkgOf(template) === CORE_PACKAGE)
78
+ .map(template => `${template.type}:${template.dirName}`),
79
+ );
80
+ return templates.filter(template => {
81
+ if (
82
+ template.replacementRejected &&
83
+ template.replacementTarget === template.dirName &&
84
+ coreIds.has(`${template.type}:${template.dirName}`)
85
+ ) {
86
+ return false;
87
+ }
88
+ return (
89
+ !activeTargets.has(`${template.type}:${template.dirName}`) ||
90
+ template.replaces != null
91
+ );
92
+ });
93
+ }
94
+
46
95
  /**
47
96
  * A discovered template (page or block), normalized across core, external, and
48
97
  * integration sources. Not every source populates every field, so
@@ -65,6 +114,11 @@ export function pkgOf(t) {
65
114
  * @property {string} filePath
66
115
  * @property {string} docPath
67
116
  * @property {string} [package]
117
+ * @property {boolean} [autolinked] whether the owning integration was discovered
118
+ * from package.json rather than named in astryx.config
119
+ * @property {string} [replaces] active Core template id this integration template replaces
120
+ * @property {boolean} [replacementRejected] whether an invalid declaration was disabled
121
+ * @property {string} [replacementTarget] disabled declaration target
68
122
  */
69
123
 
70
124
  /**
@@ -73,6 +127,15 @@ export function pkgOf(t) {
73
127
  * @property {string} package
74
128
  * @property {string} [template]
75
129
  * @property {string} message
130
+ * @property {string} [code]
131
+ * @property {'warning' | 'error'} [severity]
132
+ * @property {string} [replacementTarget] Core target whose replacement set is invalid
133
+ * @property {boolean} [autolinked] whether the declaration came from an autolinked integration
134
+ */
135
+
136
+ /**
137
+ * A semantic error in an integration template replacement declaration.
138
+ * @typedef {TemplateDiscoveryError & {code: 'missing_template_replacement_target' | 'ambiguous_template_replacement' | 'invalid_template_replacement', severity: 'warning' | 'error'}} TemplateReplacementError
76
139
  */
77
140
 
78
141
  /**
@@ -81,31 +144,28 @@ export function pkgOf(t) {
81
144
  */
82
145
 
83
146
  /**
84
- * Canonical basename suffixes for template-spec files, in precedence order.
85
- * A template spec is a scaffoldable TEMPLATE (a plain object stamped with a
86
- * `type` of `'page'` or `'block'`), so `.template.*` is the descriptive family
87
- * name.
147
+ * Released compatibility suffixes, in precedence order. Stable 0.6.0
148
+ * documented `.template.*`, so discovery keeps reading those files while all
149
+ * new authoring uses `.doc.mjs`.
88
150
  */
89
151
  const TEMPLATE_SUFFIXES = ['.template.ts', '.template.mjs', '.template.js'];
90
152
 
91
153
  /**
92
- * Legacy basename suffixes for template-spec files, in precedence order.
93
- * `.doc.*` was inherited from the component-doc convention before templates
94
- * had their own name; it is still accepted during the transition window.
154
+ * Descriptor suffixes for templates, in precedence order. New authoring always
155
+ * emits `.doc.mjs`; the TypeScript and JavaScript variants remain readable.
95
156
  */
96
157
  const DOC_SUFFIXES = ['.doc.ts', '.doc.mjs', '.doc.js'];
97
158
 
98
159
  /**
99
- * The union of canonical + legacy template-spec suffixes, canonical first so
100
- * `.template.*` wins over `.doc.*` when both stems exist. All template
101
- * discovery matches this union so a `Foo.template.ts` file is treated exactly
102
- * like a legacy `Foo.doc.mjs`.
160
+ * Every template-spec suffix, in the released precedence a core page directory
161
+ * uses to pick one file. Integration discovery never picks: every match is a
162
+ * template, so two specs for one stem list twice and read as ambiguous.
103
163
  */
104
164
  const ALL_TEMPLATE_SUFFIXES = [...TEMPLATE_SUFFIXES, ...DOC_SUFFIXES];
105
165
 
106
166
  /**
107
167
  * The template-spec suffix present on `file`, or null if none matches.
108
- * Recognizes both the canonical `.template.*` and legacy `.doc.*` families.
168
+ * Recognizes both the canonical `.doc.*` and released `.template.*` families.
109
169
  * @param {string} file
110
170
  * @returns {string | null}
111
171
  */
@@ -119,16 +179,6 @@ function matchedTemplateSuffix(file) {
119
179
  */
120
180
  const TEMPLATE_SUFFIX_RE = /\.(template|doc)\.(ts|mjs|js)$/;
121
181
 
122
- /** @type {ReturnType<typeof createJiti> | undefined} */
123
- let jitiInstance;
124
- /** Lazily-created jiti for loading `.ts` template specs (JSX-capable). */
125
- function getJiti() {
126
- if (!jitiInstance) {
127
- jitiInstance = createJiti(import.meta.url, {jsx: true});
128
- }
129
- return jitiInstance;
130
- }
131
-
132
182
  /**
133
183
  * Load an integration template doc module and validate it against the template
134
184
  * envelope at the load boundary. Default export only — `.ts` via jiti,
@@ -141,7 +191,13 @@ function getJiti() {
141
191
  * @param {string} [label]
142
192
  */
143
193
  async function loadIntegrationDoc(file, label) {
144
- return loadModuleWithParser(file, parseTemplate, {label});
194
+ return readDocView(file, {
195
+ root: 'templates',
196
+ exports: ['default'],
197
+ label: label ?? file,
198
+ strict: true,
199
+ value: 'parsed',
200
+ });
145
201
  }
146
202
 
147
203
  const TEMPLATES_DIR = path.join(CLI_ROOT, 'assets', 'templates');
@@ -185,17 +241,53 @@ const IMAGE_EXTENSIONS = new Set([
185
241
  ]);
186
242
 
187
243
  /**
188
- * Demo-asset sources to strip from scaffolded projects. Template demo imagery
244
+ * First path segment of every Astryx template fixture. Template demo imagery
189
245
  * is self-hosted under the docsite's `/template-assets/*` dir (committed there,
190
- * mirrored into the sandbox preview by scripts/sync-templates.js). Those paths
191
- * only resolve inside the Astryx docsite/sandbox, so on scaffold they're
192
- * replaced — a scaffolded project has no `/template-assets/` dir and would
193
- * otherwise 404. Genuine third-party URLs (e.g. brand logos from
194
- * paypalobjects.com) are intentionally left untouched.
195
- *
196
- * @type {RegExp}
246
+ * mirrored into the sandbox preview by scripts/sync-templates.js), so those
247
+ * paths only resolve inside the Astryx docsite/sandbox and are replaced on
248
+ * scaffold. Only a root-relative URL whose first segment this is counts: the
249
+ * same text inside a third-party URL or a product path is left untouched.
197
250
  */
198
- const DEMO_ASSET_PATTERN = /\/template-assets\/[\w.-]+\.(\w+)/g;
251
+ const FIXTURE_SEGMENT = 'template-assets';
252
+
253
+ /** Characters that end a URL token in template source, besides whitespace. */
254
+ const TOKEN_DELIMITERS = new Set([
255
+ "'",
256
+ '"',
257
+ '`',
258
+ '(',
259
+ ')',
260
+ '<',
261
+ '>',
262
+ '{',
263
+ '}',
264
+ '[',
265
+ ']',
266
+ '\\',
267
+ '|',
268
+ '^',
269
+ ]);
270
+
271
+ /** The closing delimiter each opening delimiter must pair with. */
272
+ const CLOSING_DELIMITERS = new Map([
273
+ ["'", "'"],
274
+ ['"', '"'],
275
+ ['`', '`'],
276
+ ['(', ')'],
277
+ ]);
278
+ const CLOSERS = new Set(CLOSING_DELIMITERS.values());
279
+
280
+ /** A URL with a scheme: absolute, so never a fixture reference. */
281
+ const ABSOLUTE_URL = /^[a-z][a-z\d+.-]*:/iu;
282
+
283
+ /** What may lead up to the fixture segment inside a relative product path. */
284
+ const RELATIVE_PATH_PREFIX = /^[\w.~%@/-]+$/u;
285
+
286
+ /** Punctuation that ends a sentence or list item after a path in text. */
287
+ const PROSE_PUNCTUATION = new Set(['.', ',', ';', ':', '!', '?']);
288
+
289
+ /** `from`, `import` or `require` right before a quoted module specifier. */
290
+ const MODULE_SPECIFIER_LEAD = /(?:^|[^\w$])(?:from|import|require)\s*\(?$/u;
199
291
 
200
292
  /**
201
293
  * Normalize path into Unix path (using forward slashes) for consistent comparison
@@ -209,36 +301,255 @@ function toPosixPath(p) {
209
301
  }
210
302
 
211
303
  /**
212
- * Replace demo asset references with a placeholder so scaffolded pages
213
- * render with zero setup. Images get a self-contained data URI; videos
214
- * (which have no equivalent inline placeholder — see VIDEO_EXTENSIONS) are
215
- * stripped to an empty src instead of being mis-replaced with image data.
216
- * Builders drop in their own media either way.
304
+ * Replace each Astryx template fixture reference with a placeholder so
305
+ * scaffolded pages render with zero setup. Images get a self-contained data
306
+ * URI; videos (which have no equivalent inline placeholder — see
307
+ * VIDEO_EXTENSIONS) are stripped to an empty src instead of being
308
+ * mis-replaced with image data. Builders drop in their own media either way.
309
+ *
310
+ * A reference is a URL token that resolves to a root-relative path under
311
+ * FIXTURE_SEGMENT. It is replaced whole, query and fragment included, and
312
+ * classified by the suffix of its last path segment. A reference that cannot
313
+ * be replaced safely throws with its path rather than being guessed at or
314
+ * left behind. Prose is left as written: in text (JSX text, a word inside a
315
+ * longer string) trailing punctuation is not part of a path and a path with
316
+ * no suffix, such as the bare directory, is a mention; inside a comment
317
+ * nothing throws.
217
318
  *
218
319
  * @param {string} source - Template source code.
219
320
  * @returns {string} Source with demo asset references replaced.
220
321
  */
221
322
  export function stripTemplateAssetRefs(source) {
222
- return source.replace(DEMO_ASSET_PATTERN, (match, extension) => {
223
- const ext = extension.toLowerCase();
224
- if (VIDEO_EXTENSIONS.has(ext)) {
225
- return '';
323
+ const needle = `/${FIXTURE_SEGMENT}`;
324
+ let output = '';
325
+ let copied = 0;
326
+ /** @type {Array<[number, number]> | undefined} */
327
+ let comments;
328
+ let at = source.indexOf(needle);
329
+ while (at !== -1) {
330
+ let start = at;
331
+ while (start > 0 && !isTokenDelimiter(source[start - 1])) start--;
332
+ let end = at + needle.length;
333
+ while (end < source.length && !isTokenDelimiter(source[end])) end++;
334
+ /** @type {{text: string, end: number} | null} */
335
+ let edit;
336
+ try {
337
+ edit = fixtureEdit(source, start, at, end);
338
+ } catch (err) {
339
+ comments ??= commentRanges(source);
340
+ if (!comments.some(([from, to]) => from <= at && at < to)) throw err;
341
+ edit = null;
226
342
  }
227
- if (IMAGE_EXTENSIONS.has(ext)) {
228
- return PLACEHOLDER_IMAGE;
343
+ if (edit) {
344
+ output += source.slice(copied, start) + edit.text;
345
+ copied = edit.end;
229
346
  }
230
- throw new Error(`Unrecognized template asset format ${ext} for ${match}`);
231
- });
347
+ at = source.indexOf(needle, end);
348
+ }
349
+ return output + source.slice(copied);
350
+ }
351
+
352
+ /** @param {string | undefined} char */
353
+ function isTokenDelimiter(char) {
354
+ return char === undefined || /\s/u.test(char) || TOKEN_DELIMITERS.has(char);
355
+ }
356
+
357
+ /**
358
+ * Whitespace, the source edge, or a tag bracket: what bounds a word of text.
359
+ * @param {string | undefined} char
360
+ */
361
+ function isTextDelimiter(char) {
362
+ return char === undefined || /\s/u.test(char) || char === '<' || char === '>';
363
+ }
364
+
365
+ /**
366
+ * The path segments of a root-relative URL under FIXTURE_SEGMENT, or null.
367
+ * @param {string} reference
368
+ * @returns {string[] | null}
369
+ */
370
+ function fixtureSegments(reference) {
371
+ if (!reference.startsWith('/') || reference.startsWith('//')) return null;
372
+ const segments = new URL(reference, 'http://template.invalid').pathname.split(
373
+ '/',
374
+ );
375
+ return segments[1] === FIXTURE_SEGMENT ? segments : null;
376
+ }
377
+
378
+ /**
379
+ * The replacement for the fixture reference in the URL token [start, end),
380
+ * which contains FIXTURE_SEGMENT's path at `at`; null when the token is not a
381
+ * fixture reference or is a prose mention.
382
+ * @param {string} source
383
+ * @param {number} start
384
+ * @param {number} at
385
+ * @param {number} end
386
+ * @returns {{text: string, end: number} | null}
387
+ */
388
+ function fixtureEdit(source, start, at, end) {
389
+ const token = source.slice(start, end);
390
+ if (ABSOLUTE_URL.test(token)) return null;
391
+ if (!token.startsWith('/')) {
392
+ if (!fixtureSegments(source.slice(at, end))) return null;
393
+ if (
394
+ source[start - 1] !== '\\' &&
395
+ RELATIVE_PATH_PREFIX.test(source.slice(start, at))
396
+ ) {
397
+ return null;
398
+ }
399
+ throw unsafeFixtureReference(
400
+ source,
401
+ at,
402
+ 'it is joined to text the copy cannot parse',
403
+ );
404
+ }
405
+
406
+ const before = source[start - 1];
407
+ const after = source[end];
408
+ if (after === '{' && token.endsWith('$')) {
409
+ if (!fixtureSegments(token.slice(0, -1))) return null;
410
+ throw unsafeFixtureReference(
411
+ source,
412
+ at,
413
+ 'it is built by a template-literal interpolation',
414
+ );
415
+ }
416
+ const quoted =
417
+ before !== undefined && CLOSING_DELIMITERS.get(before) === after;
418
+ const escapedQuoted =
419
+ (before === "'" || before === '"') &&
420
+ source[start - 2] === '\\' &&
421
+ after === '\\' &&
422
+ source[end + 1] === before;
423
+ const whole = quoted || escapedQuoted;
424
+ let referenceEnd = end;
425
+ if (!whole) {
426
+ while (
427
+ referenceEnd > at &&
428
+ PROSE_PUNCTUATION.has(source[referenceEnd - 1])
429
+ ) {
430
+ referenceEnd--;
431
+ }
432
+ }
433
+ const segments = fixtureSegments(source.slice(start, referenceEnd));
434
+ if (!segments) return null;
435
+
436
+ const inText =
437
+ (isTextDelimiter(before) || CLOSING_DELIMITERS.has(before)) &&
438
+ (isTextDelimiter(after) || CLOSERS.has(after)) &&
439
+ (isTextDelimiter(before) || isTextDelimiter(after));
440
+ if (!whole && !inText) {
441
+ throw unsafeFixtureReference(
442
+ source,
443
+ at,
444
+ 'it is not a whole quoted, url(), or text value',
445
+ );
446
+ }
447
+ if (quoted && before !== '(') {
448
+ const use = expressionUse(source, start - 1, end);
449
+ if (use) throw unsafeFixtureReference(source, at, use);
450
+ }
451
+
452
+ const ext = path.posix
453
+ .extname(segments[segments.length - 1])
454
+ .slice(1)
455
+ .toLowerCase();
456
+ if (VIDEO_EXTENSIONS.has(ext)) return {text: '', end: referenceEnd};
457
+ if (IMAGE_EXTENSIONS.has(ext)) {
458
+ return {text: PLACEHOLDER_IMAGE, end: referenceEnd};
459
+ }
460
+ if (whole || ext) {
461
+ throw new Error(
462
+ `Unrecognized template asset format ${ext || '(none)'} for ${displayReference(source, at)}`,
463
+ );
464
+ }
465
+ return null;
466
+ }
467
+
468
+ /**
469
+ * Why the string quoted at `open` and `close` cannot be replaced on its own,
470
+ * or null when it is a plain value.
471
+ * @param {string} source
472
+ * @param {number} open
473
+ * @param {number} close
474
+ * @returns {string | null}
475
+ */
476
+ function expressionUse(source, open, close) {
477
+ let prev = open - 1;
478
+ while (prev >= 0 && /\s/u.test(source[prev])) prev--;
479
+ let next = close + 1;
480
+ while (next < source.length && /\s/u.test(source[next])) next++;
481
+ if (
482
+ source[prev] === '+' ||
483
+ (source[prev] === '=' && source[prev - 1] === '+') ||
484
+ source[next] === '+'
485
+ ) {
486
+ return 'the string is concatenated with another value';
487
+ }
488
+ if (source[next] === '.' && /[A-Za-z_$]/u.test(source[next + 1] ?? '')) {
489
+ return 'a method is called on the string';
490
+ }
491
+ if (
492
+ MODULE_SPECIFIER_LEAD.test(source.slice(Math.max(0, prev - 16), prev + 1))
493
+ ) {
494
+ return 'it is imported as a module';
495
+ }
496
+ return null;
497
+ }
498
+
499
+ /**
500
+ * The [start, end) range of every comment in `source`, or none when it does
501
+ * not parse as TSX.
502
+ * @param {string} source
503
+ * @returns {Array<[number, number]>}
504
+ */
505
+ function commentRanges(source) {
506
+ try {
507
+ const {parse} = require('@babel/parser');
508
+ const {comments} = parse(source, {
509
+ sourceType: 'module',
510
+ plugins: ['jsx', 'typescript'],
511
+ });
512
+ return (comments ?? []).map(
513
+ comment =>
514
+ /** @type {[number, number]} */ ([
515
+ comment.start ?? 0,
516
+ comment.end ?? 0,
517
+ ]),
518
+ );
519
+ } catch {
520
+ return [];
521
+ }
522
+ }
523
+
524
+ /**
525
+ * The fixture path at `at`, up to the next whitespace, quote, or tag bracket.
526
+ * @param {string} source
527
+ * @param {number} at
528
+ */
529
+ function displayReference(source, at) {
530
+ let end = at;
531
+ while (end < source.length && !/[\s'"`<>]/u.test(source[end])) end++;
532
+ return source.slice(at, end);
533
+ }
534
+
535
+ /**
536
+ * @param {string} source
537
+ * @param {number} at
538
+ * @param {string} reason
539
+ */
540
+ function unsafeFixtureReference(source, at, reason) {
541
+ return new Error(
542
+ `Template asset reference ${displayReference(source, at)} cannot be replaced safely: ${reason}. Use the complete path as one static string.`,
543
+ );
232
544
  }
233
545
  /**
234
546
  * Load a template-spec module and return its metadata object. Supports both
235
547
  * families of suffix:
236
- * - Legacy `.doc.*` core/external specs export `export const doc = {...}`.
237
- * - Canonical `.template.*` specs export the stamped object (`type: 'page' |
238
- * 'block'`) as the default export.
239
- * Prefers the default export, falling back to the named `doc` export, so a
240
- * `Foo.template.ts` (default export) is read identically to a legacy
241
- * `Foo.doc.mjs` (`doc` export). `.ts` is loaded via jiti; `.mjs`/`.js` via a
548
+ * - Canonical `.doc.*` specs may export the stamped object (`type: 'page' |
549
+ * 'block'`) as the default export or use the historical named `doc` export.
550
+ * - Released `.template.*` compatibility specs use the same object shape.
551
+ * Prefers the default export, falling back to the named `doc` export. `.ts` is
552
+ * loaded via jiti; `.mjs`/`.js` via a
242
553
  * native dynamic import. Returns null if the file does not exist.
243
554
  *
244
555
  * @param {string} docPath absolute path to the spec file
@@ -246,10 +557,7 @@ export function stripTemplateAssetRefs(source) {
246
557
  */
247
558
  async function loadDocModule(docPath) {
248
559
  if (!fs.existsSync(docPath)) return null;
249
- const docModule = docPath.endsWith('.ts')
250
- ? await getJiti().import(docPath)
251
- : await import(`file://${docPath}`);
252
- return docModule.default ?? docModule.doc;
560
+ return readDocView(docPath, {root: 'templates', loader: 'template'});
253
561
  }
254
562
 
255
563
  /**
@@ -294,11 +602,11 @@ function findDocFiles(dir, pattern) {
294
602
 
295
603
  /**
296
604
  * Resolve the template-spec file for a core page directory: the first existing
297
- * `template.<suffix>` in canonical-then-legacy precedence, or null.
605
+ * metadata file in {@link ALL_TEMPLATE_SUFFIXES} precedence, or null.
298
606
  * @param {string} dirPath
299
607
  * @returns {string | null}
300
608
  */
301
- function findPageDocFile(dirPath) {
609
+ export function findPageDocFile(dirPath) {
302
610
  for (const suffix of ALL_TEMPLATE_SUFFIXES) {
303
611
  const candidate = path.join(dirPath, `template${suffix}`);
304
612
  if (fs.existsSync(candidate)) return candidate;
@@ -437,44 +745,297 @@ export async function discoverCoreTemplates() {
437
745
  }
438
746
 
439
747
  /**
748
+ * Apply valid integration replacements to a raw template set.
749
+ *
750
+ * One declaration owns its Core target. When different configured packages
751
+ * replace the same target, the later package wins and discovery returns a
752
+ * warning, matching integration-doc replacement order. Multiple declarations
753
+ * inside one package are invalid and fail closed. Missing targets and kind
754
+ * mismatches also fail closed: Core stays selected and every integration
755
+ * template remains addressable by its own id.
756
+ *
757
+ * @param {DiscoveredTemplate[]} templates
758
+ * @param {TemplateDiscoveryError[]} [declarationErrors]
759
+ * @returns {{templates: DiscoveredTemplate[], errors: TemplateReplacementError[]}}
760
+ */
761
+ export function applyTemplateReplacements(templates, declarationErrors = []) {
762
+ /** @type {Map<string, DiscoveredTemplate[]>} */
763
+ const coreById = new Map();
764
+ /** @type {Map<string, DiscoveredTemplate[]>} */
765
+ const replacementsByTarget = new Map();
766
+
767
+ for (const template of templates) {
768
+ if (pkgOf(template) === CORE_PACKAGE) {
769
+ const matches = coreById.get(template.dirName) ?? [];
770
+ matches.push(template);
771
+ coreById.set(template.dirName, matches);
772
+ }
773
+ if (template.replaces != null) {
774
+ const replacements = replacementsByTarget.get(template.replaces) ?? [];
775
+ replacements.push(template);
776
+ replacementsByTarget.set(template.replaces, replacements);
777
+ }
778
+ }
779
+
780
+ const activeReplacements = new Set();
781
+ const replacedCore = new Set();
782
+ /** @type {TemplateReplacementError[]} */
783
+ const errors = declarationErrors.map(error => ({
784
+ ...error,
785
+ code: /** @type {TemplateReplacementError['code']} */ (
786
+ error.code ?? 'invalid_template_replacement'
787
+ ),
788
+ severity: error.severity ?? 'error',
789
+ }));
790
+ /** @param {TemplateReplacementError} error */
791
+ const pushError = error => {
792
+ if (
793
+ errors.some(
794
+ existing =>
795
+ existing.code === error.code &&
796
+ existing.package === error.package &&
797
+ existing.template === error.template &&
798
+ existing.message === error.message,
799
+ )
800
+ ) {
801
+ return;
802
+ }
803
+ errors.push(error);
804
+ };
805
+ for (const [target, replacements] of replacementsByTarget) {
806
+ const targetErrors = errors.filter(
807
+ error => error.replacementTarget === target,
808
+ );
809
+ const hasExplicitIntent =
810
+ replacements.some(replacement => !replacement.autolinked) ||
811
+ targetErrors.some(error => !error.autolinked);
812
+ const contenders = hasExplicitIntent
813
+ ? replacements.filter(replacement => !replacement.autolinked)
814
+ : replacements;
815
+ let targetInvalid = targetErrors.some(
816
+ error =>
817
+ error.severity === 'error' && (!hasExplicitIntent || !error.autolinked),
818
+ );
819
+
820
+ /** @type {Map<string, DiscoveredTemplate[]>} */
821
+ const byPackage = new Map();
822
+ for (const replacement of replacements) {
823
+ const pkg = pkgOf(replacement);
824
+ const fromPackage = byPackage.get(pkg) ?? [];
825
+ fromPackage.push(replacement);
826
+ byPackage.set(pkg, fromPackage);
827
+ }
828
+ for (const [pkg, declarations] of byPackage) {
829
+ if (declarations.length < 2) continue;
830
+ if (!hasExplicitIntent || !declarations[0].autolinked) {
831
+ targetInvalid = true;
832
+ }
833
+ const message =
834
+ `${pkg} declares ${declarations.length} templates as replacements for Core ` +
835
+ `template "${target}" (${declarations.map(template => template.dirName).join(', ')}). ` +
836
+ 'One package must declare at most one replacement for a Core target.';
837
+ for (const declaration of declarations) {
838
+ pushError({
839
+ code: 'ambiguous_template_replacement',
840
+ severity: 'error',
841
+ package: pkg,
842
+ template: declaration.dirName,
843
+ replacementTarget: target,
844
+ autolinked: declaration.autolinked,
845
+ message,
846
+ });
847
+ }
848
+ }
849
+
850
+ const coreMatches = coreById.get(target) ?? [];
851
+ /** @type {Map<DiscoveredTemplate, DiscoveredTemplate>} */
852
+ const coreMatchByReplacement = new Map();
853
+ for (const replacement of replacements) {
854
+ const invalidAffectsTarget =
855
+ !hasExplicitIntent || !replacement.autolinked;
856
+ if (coreMatches.length === 0) {
857
+ if (invalidAffectsTarget) targetInvalid = true;
858
+ pushError({
859
+ code: 'missing_template_replacement_target',
860
+ severity: 'error',
861
+ package: pkgOf(replacement),
862
+ template: replacement.dirName,
863
+ replacementTarget: target,
864
+ autolinked: replacement.autolinked,
865
+ message: `Template "${replacement.dirName}" replaces "${target}", which is not a Core template id.`,
866
+ });
867
+ continue;
868
+ }
869
+ const sameType = coreMatches.filter(
870
+ core => core.type === replacement.type,
871
+ );
872
+ if (sameType.length !== 1) {
873
+ if (invalidAffectsTarget) targetInvalid = true;
874
+ const kinds = coreMatches.map(core => core.type).join(', ');
875
+ pushError({
876
+ code: 'invalid_template_replacement',
877
+ severity: 'error',
878
+ package: pkgOf(replacement),
879
+ template: replacement.dirName,
880
+ replacementTarget: target,
881
+ autolinked: replacement.autolinked,
882
+ message:
883
+ `Template "${replacement.dirName}" is a ${replacement.type} template, but Core ` +
884
+ `template "${target}" is ${kinds || 'not available'}. A replacement must have the same type.`,
885
+ });
886
+ continue;
887
+ }
888
+ coreMatchByReplacement.set(replacement, sameType[0]);
889
+ }
890
+
891
+ const validContenders = contenders.filter(replacement =>
892
+ coreMatchByReplacement.has(replacement),
893
+ );
894
+ if (targetInvalid || validContenders.length === 0) continue;
895
+
896
+ const replacement = validContenders[validContenders.length - 1];
897
+ const validReplacements = replacements.filter(candidate =>
898
+ coreMatchByReplacement.has(candidate),
899
+ );
900
+ if (validReplacements.length > 1) {
901
+ const autolinkedLost =
902
+ hasExplicitIntent &&
903
+ validReplacements.some(candidate => candidate.autolinked);
904
+ const allAutolinked = validReplacements.every(
905
+ candidate => candidate.autolinked,
906
+ );
907
+ pushError({
908
+ code: 'ambiguous_template_replacement',
909
+ severity: 'warning',
910
+ package: pkgOf(replacement),
911
+ template: replacement.dirName,
912
+ replacementTarget: target,
913
+ autolinked: replacement.autolinked,
914
+ message: autolinkedLost
915
+ ? `Core template "${target}" is replaced by ${validReplacements.map(candidate => pkgOf(candidate)).join(', ')}. ${pkgOf(replacement)} is explicitly configured, so it wins over autolinked integrations.`
916
+ : allAutolinked
917
+ ? `Core template "${target}" is replaced by autolinked dependencies ${validReplacements.map(candidate => pkgOf(candidate)).join(', ')}. ${pkgOf(replacement)} is listed later in package.json dependencies, so it wins. Add the intended package to astryx.config integrations to make precedence explicit.`
918
+ : `Core template "${target}" is replaced by ${validReplacements.map(candidate => pkgOf(candidate)).join(', ')}. ${pkgOf(replacement)} is configured later, so it wins.`,
919
+ });
920
+ }
921
+
922
+ activeReplacements.add(replacement);
923
+ const coreMatch = coreMatchByReplacement.get(replacement);
924
+ if (coreMatch) replacedCore.add(coreMatch);
925
+ }
926
+
927
+ const effective = templates.flatMap(template => {
928
+ if (replacedCore.has(template)) return [];
929
+ if (template.replaces != null && !activeReplacements.has(template)) {
930
+ const fallback = {
931
+ ...template,
932
+ replacementRejected: true,
933
+ replacementTarget: template.replaces,
934
+ };
935
+ delete fallback.replaces;
936
+ return [fallback];
937
+ }
938
+ return [template];
939
+ });
940
+
941
+ return {
942
+ templates: effective.sort((a, b) => a.name.localeCompare(b.name)),
943
+ errors,
944
+ };
945
+ }
946
+
947
+ /**
948
+ * Discover the raw Core, external, and integration template set before
949
+ * replacement declarations are applied.
440
950
  * @param {string} [cwd]
441
- * @returns {Promise<DiscoveredTemplate[]>}
951
+ * @returns {Promise<{templates: DiscoveredTemplate[], errors: TemplateDiscoveryError[]}>}
442
952
  */
443
- export async function discoverAll(cwd = process.cwd()) {
953
+ async function discoverAllSources(cwd = process.cwd()) {
444
954
  const [core, external, integration] = await Promise.all([
445
955
  discoverCoreTemplates(),
446
956
  discoverExternalBlocks(cwd),
447
957
  discoverIntegrationTemplates(cwd),
448
958
  ]);
449
- return [...core, ...external, ...integration.templates].sort((a, b) =>
959
+ return {
960
+ templates: [...core, ...external, ...integration.templates],
961
+ errors: integration.errors,
962
+ };
963
+ }
964
+
965
+ /**
966
+ * Discover every template without hiding replaced Core originals. Internal
967
+ * package-qualified selection uses this view.
968
+ * @param {string} [cwd]
969
+ * @returns {Promise<DiscoveredTemplate[]>}
970
+ */
971
+ export async function discoverAllUnresolved(cwd = process.cwd()) {
972
+ return (await discoverAllSources(cwd)).templates.sort((a, b) =>
450
973
  a.name.localeCompare(b.name),
451
974
  );
452
975
  }
453
976
 
454
977
  /**
455
- * Like {@link discoverAll} but also returns integration-template discovery
456
- * errors (missing same-stem source, missing `type`, load failure). Use this
457
- * when the caller wants to warn about malformed integration templates.
978
+ * Resolve replacement declarations while retaining package-addressable entries
979
+ * that are shadowed or rejected in the default discovery view.
980
+ * @param {string} [cwd]
981
+ * @returns {Promise<{templates: DiscoveredTemplate[], errors: Array<TemplateDiscoveryError | TemplateReplacementError>}>}
982
+ */
983
+ async function resolveAllSources(cwd = process.cwd()) {
984
+ const discovered = await discoverAllSources(cwd);
985
+ const replacementErrors = discovered.errors.filter(
986
+ error => error.replacementTarget != null,
987
+ );
988
+ const resolved = applyTemplateReplacements(
989
+ discovered.templates,
990
+ replacementErrors,
991
+ );
992
+ return {
993
+ templates: resolved.templates,
994
+ errors: [
995
+ ...discovered.errors.filter(error => error.replacementTarget == null),
996
+ ...resolved.errors,
997
+ ],
998
+ };
999
+ }
1000
+
1001
+ /**
1002
+ * Discover the resolved catalog before default-view shadowing. Internal
1003
+ * package-qualified selection uses this view.
1004
+ * @param {string} [cwd]
1005
+ * @returns {Promise<DiscoveredTemplate[]>}
1006
+ */
1007
+ export async function discoverAllResolved(cwd = process.cwd()) {
1008
+ return (await resolveAllSources(cwd)).templates;
1009
+ }
1010
+
1011
+ /**
1012
+ * @param {string} [cwd]
1013
+ * @returns {Promise<DiscoveredTemplate[]>}
1014
+ */
1015
+ export async function discoverAll(cwd = process.cwd()) {
1016
+ return effectiveTemplateDiscovery(await discoverAllResolved(cwd));
1017
+ }
1018
+
1019
+ /**
1020
+ * Like {@link discoverAll} but also returns integration-template discovery and
1021
+ * replacement-declaration errors. Use this when the caller wants to warn about
1022
+ * malformed integration templates or inactive replacement declarations.
458
1023
  *
459
1024
  * @param {string} [cwd]
460
- * @returns {Promise<{templates: DiscoveredTemplate[], errors: TemplateDiscoveryError[]}>}
1025
+ * @returns {Promise<{templates: DiscoveredTemplate[], errors: Array<TemplateDiscoveryError | TemplateReplacementError>}>}
461
1026
  */
462
1027
  export async function discoverAllWithErrors(cwd = process.cwd()) {
463
- const [pages, blocks, integration] = await Promise.all([
464
- discoverPages(),
465
- discoverAllBlocks(cwd),
466
- discoverIntegrationTemplates(cwd),
467
- ]);
468
- const templates = [...pages, ...blocks, ...integration.templates].sort(
469
- (a, b) => a.name.localeCompare(b.name),
470
- );
471
- return {templates, errors: integration.errors};
1028
+ const resolved = await resolveAllSources(cwd);
1029
+ return {
1030
+ templates: effectiveTemplateDiscovery(resolved.templates),
1031
+ errors: resolved.errors,
1032
+ };
472
1033
  }
473
1034
 
474
1035
  /**
475
1036
  * Recursively collect integration template-spec files under `root`.
476
1037
  * Returns absolute paths to files ending in one of ALL_TEMPLATE_SUFFIXES
477
- * (canonical `.template.*` or legacy `.doc.*`).
1038
+ * (canonical `.doc.*` or released `.template.*` compatibility files).
478
1039
  *
479
1040
  * @param {string} root
480
1041
  * @returns {string[]}
@@ -504,7 +1065,7 @@ function findIntegrationDocFiles(root) {
504
1065
  * Discover templates contributed by configured integrations.
505
1066
  *
506
1067
  * For each integration with a resolved `templates` root, every
507
- * `<id>.template.{ts,mjs,js}` (or legacy `<id>.doc.{ts,mjs,js}`) file is a
1068
+ * canonical `<id>.doc.{mjs,ts,js}` (or released `<id>.template.{ts,mjs,js}`) file is a
508
1069
  * template whose id is its path relative to the templates root with the
509
1070
  * matched suffix stripped (kebab-case, may be nested). The doc's `type`
510
1071
  * (page|block) decides scaffolding — there is no `/pages` vs `/blocks`
@@ -545,21 +1106,67 @@ async function discoverIntegrationTemplates(cwd = process.cwd()) {
545
1106
  }
546
1107
 
547
1108
  for (const integration of loadedIntegrations) {
548
- const result = await discoverIntegrationTemplatesForOne(integration);
549
- templates.push(...result.templates);
550
- errors.push(...result.errors);
1109
+ // One integration's unreadable root must not cost core or the others theirs.
1110
+ try {
1111
+ const result = await discoverIntegrationTemplatesForOne(integration);
1112
+ templates.push(...result.templates);
1113
+ errors.push(...result.errors);
1114
+ } catch (err) {
1115
+ errors.push({
1116
+ package: integration?.name ?? integration?.__spec ?? 'integration',
1117
+ message: err instanceof Error ? err.message : String(err),
1118
+ });
1119
+ }
551
1120
  }
552
1121
 
553
1122
  return {templates, errors};
554
1123
  }
555
1124
 
1125
+ /**
1126
+ * Report replacement declarations that cannot hold inside one package: two of
1127
+ * its templates replacing the same Core target. Cross-package precedence and
1128
+ * target checks happen in {@link applyTemplateReplacements}.
1129
+ * @param {string} pkg
1130
+ * @param {boolean | undefined} autolinked
1131
+ * @param {DiscoveredTemplate[]} templates
1132
+ * @param {TemplateDiscoveryError[]} errors
1133
+ */
1134
+ function reportSamePackageReplacements(pkg, autolinked, templates, errors) {
1135
+ /** @type {Map<string, string[]>} */
1136
+ const idsByTarget = new Map();
1137
+ for (const template of templates) {
1138
+ if (template.replaces == null) continue;
1139
+ const ids = idsByTarget.get(template.replaces) ?? [];
1140
+ ids.push(template.dirName);
1141
+ idsByTarget.set(template.replaces, ids);
1142
+ }
1143
+ for (const [target, ids] of idsByTarget) {
1144
+ if (ids.length < 2) continue;
1145
+ const message =
1146
+ `${pkg} declares ${ids.length} templates as replacements for Core template ` +
1147
+ `"${target}" (${ids.join(', ')}). One package must declare at most one ` +
1148
+ 'replacement for a Core target.';
1149
+ for (const template of ids) {
1150
+ errors.push({
1151
+ code: 'ambiguous_template_replacement',
1152
+ severity: 'error',
1153
+ package: pkg,
1154
+ autolinked,
1155
+ template,
1156
+ replacementTarget: target,
1157
+ message,
1158
+ });
1159
+ }
1160
+ }
1161
+ }
1162
+
556
1163
  /**
557
1164
  * Discover the templates contributed by a SINGLE integration. Same per-template
558
1165
  * rules as {@link discoverIntegrationTemplates} (same-stem source required,
559
1166
  * page|block type required); broken templates are recorded in `errors` rather
560
1167
  * than thrown. Exposed for `doctor integration validate` and template authoring checks.
561
1168
  *
562
- * @param {{name?: string, __spec?: string, templates?: string}} integration
1169
+ * @param {{name?: string, __spec?: string, __autolinked?: boolean, templates?: string}} integration
563
1170
  * @returns {Promise<{templates: DiscoveredTemplate[], errors: TemplateDiscoveryError[]}>}
564
1171
  */
565
1172
  export async function discoverIntegrationTemplatesForOne(integration) {
@@ -588,6 +1195,20 @@ export async function discoverIntegrationTemplatesForOne(integration) {
588
1195
  template: id,
589
1196
  message: `Template "${id}" is missing its same-stem source file ${path.basename(sourcePath)}.`,
590
1197
  });
1198
+ // A replacement this unusable template declares still counts, so its
1199
+ // target fails closed instead of going to a sibling.
1200
+ const declared = await declaredReplacement(docPath, id);
1201
+ if (declared != null) {
1202
+ errors.push({
1203
+ code: 'invalid_template_replacement',
1204
+ severity: 'error',
1205
+ package: pkgLabel,
1206
+ autolinked: integration.__autolinked,
1207
+ template: id,
1208
+ replacementTarget: declared,
1209
+ message: `Template "${id}" replaces "${declared}", but it cannot be used: it is missing its same-stem source file ${path.basename(sourcePath)}.`,
1210
+ });
1211
+ }
591
1212
  continue;
592
1213
  }
593
1214
 
@@ -600,6 +1221,20 @@ export async function discoverIntegrationTemplatesForOne(integration) {
600
1221
  template: id,
601
1222
  message: `Template "${id}" failed to load: ${/** @type {any} */ (err).message}`,
602
1223
  });
1224
+ // A replacement this unusable template declares still counts, so its
1225
+ // target fails closed instead of going to a sibling (spec:AST-035 FR4).
1226
+ const declared = await declaredReplacement(docPath, id);
1227
+ if (declared != null) {
1228
+ errors.push({
1229
+ code: 'invalid_template_replacement',
1230
+ severity: 'error',
1231
+ package: pkgLabel,
1232
+ autolinked: integration.__autolinked,
1233
+ template: id,
1234
+ replacementTarget: declared,
1235
+ message: `Template "${id}" replaces "${declared}", but it cannot be used: its metadata does not load.`,
1236
+ });
1237
+ }
603
1238
  continue;
604
1239
  }
605
1240
 
@@ -613,6 +1248,18 @@ export async function discoverIntegrationTemplatesForOne(integration) {
613
1248
  template: id,
614
1249
  message: `Template "${id}" is missing a "type" of "page" or "block". Stamp the default export with type: 'page' or type: 'block'.`,
615
1250
  });
1251
+ const declared = doc?.replaces ?? (await declaredReplacement(docPath, id));
1252
+ if (declared != null) {
1253
+ errors.push({
1254
+ code: 'invalid_template_replacement',
1255
+ severity: 'error',
1256
+ package: pkgLabel,
1257
+ autolinked: integration.__autolinked,
1258
+ template: id,
1259
+ replacementTarget: declared,
1260
+ message: `Template "${id}" replaces "${declared}", but it cannot be used: it has no "type".`,
1261
+ });
1262
+ }
616
1263
  continue;
617
1264
  }
618
1265
 
@@ -640,12 +1287,55 @@ export async function discoverIntegrationTemplatesForOne(integration) {
640
1287
  filePath: sourcePath,
641
1288
  docPath,
642
1289
  package: pkgLabel,
1290
+ autolinked: integration.__autolinked,
1291
+ replaces: doc?.replaces,
643
1292
  });
644
1293
  }
645
1294
 
1295
+ reportSamePackageReplacements(
1296
+ pkgLabel,
1297
+ integration.__autolinked,
1298
+ templates,
1299
+ errors,
1300
+ );
646
1301
  return {templates, errors};
647
1302
  }
648
1303
 
1304
+ /**
1305
+ * The replacement a template doc declares, even when the doc does not validate
1306
+ * or load: an unusable template's declaration still counts, so its target
1307
+ * fails closed (spec:AST-035 FR4). Null when it declares none.
1308
+ * @param {string} docPath
1309
+ * @param {string} id
1310
+ * @returns {Promise<string | null>}
1311
+ */
1312
+ async function declaredReplacement(docPath, id) {
1313
+ try {
1314
+ const doc = await loadIntegrationDoc(docPath, `Template "${id}"`);
1315
+ return doc?.replaces ?? null;
1316
+ } catch {
1317
+ // The doc does not validate; read what it declares without validating.
1318
+ }
1319
+ try {
1320
+ // The guarded importer: anything the doc writes to stdout as it loads
1321
+ // goes to stderr, so `--json` output stays clean.
1322
+ const raw = (await importDocModule(docPath))?.default;
1323
+ if (raw != null && typeof raw === 'object') {
1324
+ return typeof raw.replaces === 'string' ? raw.replaces : null;
1325
+ }
1326
+ } catch {
1327
+ // The module does not load at all; its text may still name a target.
1328
+ }
1329
+ try {
1330
+ const match = /\breplaces\s*:\s*['"]([^'"\n]+)['"]/u.exec(
1331
+ fs.readFileSync(docPath, 'utf8'),
1332
+ );
1333
+ return match ? match[1] : null;
1334
+ } catch {
1335
+ return null;
1336
+ }
1337
+ }
1338
+
649
1339
  /**
650
1340
  * @param {string} componentName
651
1341
  * @param {string} [cwd]