@astryxdesign/cli 0.7.2-canary.fe2c655 → 0.7.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (509) hide show
  1. package/CHANGELOG.md +119 -0
  2. package/README.md +175 -154
  3. package/api/build/_adapter.d.mts +8 -0
  4. package/api/build/_adapter.mjs +12 -1
  5. package/api/build/build.doc.mjs +4 -0
  6. package/api/build/help/help.mjs +1 -1
  7. package/api/build/kit/kit.mjs +3 -11
  8. package/api/component/_adapter.d.mts +72 -1
  9. package/api/component/_adapter.mjs +212 -18
  10. package/api/component/component.doc.mjs +6 -1
  11. package/api/component/component.mjs +82 -15
  12. package/api/component/component.test.mjs +80 -0
  13. package/api/component/doc-links.test.mjs +386 -0
  14. package/api/component/integration-export-name.test.mjs +644 -0
  15. package/api/component/list/list.mjs +42 -4
  16. package/api/discover/_adapter.d.mts +132 -12
  17. package/api/discover/_adapter.mjs +488 -27
  18. package/api/discover/_adapter.test.mjs +599 -17
  19. package/api/discover/_catalog-view.d.mts +16 -5
  20. package/api/discover/_catalog-view.mjs +32 -8
  21. package/api/discover/detail/doc/doc.mjs +9 -1
  22. package/api/discover/discover.d.mts +11 -0
  23. package/api/discover/discover.doc.mjs +9 -7
  24. package/api/discover/discover.mjs +121 -19
  25. package/api/discover/discover.type.d.mts +20 -7
  26. package/api/discover/discover.type.mjs +17 -7
  27. package/api/discover/list/list.d.mts +4 -3
  28. package/api/discover/list/list.mjs +17 -10
  29. package/api/docs/_adapter.d.mts +74 -0
  30. package/api/docs/_adapter.mjs +553 -16
  31. package/api/docs/docs.doc.mjs +2 -1
  32. package/api/docs/docs.mjs +2 -1
  33. package/api/docs/docs.test.mjs +5 -2
  34. package/api/docs/integration-tree.test.mjs +22 -0
  35. package/api/docs/integrationDocs.test.mjs +124 -0
  36. package/api/docs/list/list.mjs +12 -2
  37. package/api/docs/reference-blocks.test.mjs +1 -3
  38. package/api/doctor/_adapter.d.mts +108 -0
  39. package/api/doctor/_adapter.mjs +223 -4
  40. package/api/doctor/doctor.d.mts +199 -0
  41. package/api/doctor/doctor.doc.mjs +3 -2
  42. package/api/doctor/doctor.mjs +530 -34
  43. package/api/doctor/doctor.test.mjs +180 -14
  44. package/api/doctor/release-checks.test.mjs +156 -0
  45. package/api/doctor/theme-checks.mjs +17 -4
  46. package/api/doctor/theme-checks.test.mjs +223 -7
  47. package/api/error.d.mts +10 -0
  48. package/api/error.mjs +25 -0
  49. package/api/gap-report/gap-report.d.mts +3 -0
  50. package/api/gap-report/gap-report.doc.mjs +11 -3
  51. package/api/gap-report/gap-report.mjs +219 -52
  52. package/api/gap-report/gap-report.test.mjs +233 -0
  53. package/api/gap-report/gap-report.type.d.mts +41 -0
  54. package/api/gap-report/gap-report.type.mjs +18 -0
  55. package/api/hook/_adapter.mjs +3 -11
  56. package/api/hook/hook.doc.mjs +4 -0
  57. package/api/hook/list/list.d.mts +1 -1
  58. package/api/index.d.mts +2 -0
  59. package/api/index.mjs +2 -0
  60. package/api/init/_adapter.d.mts +22 -0
  61. package/api/init/_adapter.mjs +59 -0
  62. package/api/init/init.d.mts +7 -5
  63. package/api/init/init.doc.mjs +28 -7
  64. package/api/init/init.mjs +28 -4
  65. package/api/init/init.type.d.mts +48 -3
  66. package/api/init/init.type.mjs +37 -3
  67. package/api/init/run/run.d.mts +1 -1
  68. package/api/init/run/run.mjs +17 -6
  69. package/api/init/user/user.d.mts +14 -0
  70. package/api/init/user/user.mjs +87 -0
  71. package/api/init/user/user.test.mjs +232 -0
  72. package/api/integration/_adapter.d.mts +45 -0
  73. package/api/integration/_adapter.mjs +112 -0
  74. package/api/integration/add-contribution.component-names.test.mjs +66 -0
  75. package/api/integration/add-contribution.mjs +8 -4
  76. package/api/integration/add-contribution.test.mjs +4 -2
  77. package/api/integration/authoring-checks.mjs +110 -6
  78. package/api/integration/authoring-checks.test.mjs +808 -0
  79. package/api/integration/authoring-checks.type.d.mts +143 -0
  80. package/api/integration/authoring-checks.type.mjs +74 -2
  81. package/api/integration/check/check.d.mts +96 -0
  82. package/api/integration/check/check.mjs +357 -0
  83. package/api/integration/check/check.test.mjs +489 -0
  84. package/api/integration/integration-check.type.d.mts +118 -0
  85. package/api/integration/integration-check.type.mjs +84 -0
  86. package/api/integration/integrationCheck.doc.d.mts +10 -0
  87. package/api/integration/integrationCheck.doc.mjs +73 -0
  88. package/api/integration/integrationComponentConflicts.doc.mjs +45 -6
  89. package/api/integration/integrationDocConflicts.doc.mjs +22 -5
  90. package/api/integration/pack-check.mjs +364 -120
  91. package/api/integration/pack-check.test.mjs +730 -10
  92. package/api/integration/validate-integration-fixes.test.mjs +67 -0
  93. package/api/integration/validate-integration.d.mts +8 -0
  94. package/api/integration/validate-integration.mjs +63 -9
  95. package/api/integration/validate-integration.test.mjs +88 -8
  96. package/api/integration/validate-integration.type.d.mts +16 -0
  97. package/api/integration/validate-integration.type.mjs +14 -1
  98. package/api/integration/validate-unread-theme-folders.test.mjs +3 -2
  99. package/api/integration/validateIntegration.doc.mjs +19 -3
  100. package/api/search/_adapter.d.mts +11 -0
  101. package/api/search/_adapter.mjs +25 -0
  102. package/api/search/search.d.mts +82 -13
  103. package/api/search/search.doc.mjs +8 -3
  104. package/api/search/search.domain-priority.test.mjs +108 -18
  105. package/api/search/search.integration-check.test.mjs +24 -0
  106. package/api/search/search.mjs +645 -64
  107. package/api/search/search.test.mjs +179 -1
  108. package/api/search/search.type.d.mts +14 -1
  109. package/api/search/search.type.mjs +6 -1
  110. package/api/swizzle/_adapter.mjs +2 -2
  111. package/api/swizzle/copy/copy.mjs +29 -15
  112. package/api/template/template-integration.test.mjs +1 -1
  113. package/api/template/template.test.mjs +120 -3
  114. package/api/theme/_adapter.d.mts +16 -0
  115. package/api/theme/_adapter.mjs +32 -5
  116. package/api/theme/build/build.d.mts +2 -1
  117. package/api/theme/build/build.family.test.mjs +64 -0
  118. package/api/theme/build/build.mjs +250 -38
  119. package/api/theme/build/build.packed-old-core.test.mjs +20 -3
  120. package/api/theme/build/font-warning.mjs +5 -3
  121. package/api/theme/build/font-warning.test.mjs +2 -1
  122. package/api/theme/remove/remove.test.mjs +2 -2
  123. package/api/theme/targets/targets.mjs +3 -10
  124. package/api/theme/themeBuild.doc.mjs +4 -4
  125. package/api/theme/themeTargets.doc.mjs +4 -0
  126. package/api/theme/use/use.test.mjs +1 -1
  127. package/api/upgrade/_adapter.d.mts +40 -2
  128. package/api/upgrade/_adapter.mjs +118 -5
  129. package/api/upgrade/run/cli-behind-core.test.mjs +438 -0
  130. package/api/upgrade/run/run.mjs +35 -2
  131. package/api/upgrade/status/status.d.mts +21 -5
  132. package/api/upgrade/status/status.mjs +45 -8
  133. package/api/upgrade/upgrade.doc.mjs +6 -3
  134. package/api/upgrade/upgrade.mjs +5 -1
  135. package/api/upgrade/upgrade.type.d.mts +23 -0
  136. package/api/upgrade/upgrade.type.mjs +16 -1
  137. package/assets/codemods/__tests__/registry.test.mjs +1 -0
  138. package/assets/codemods/registry.mjs +1 -0
  139. package/assets/codemods/runner.mjs +7 -1
  140. package/assets/codemods/transforms/v0.7.2/__tests__/migrate-list-edge-compensation-to-edge.test.mjs +120 -0
  141. package/assets/codemods/transforms/v0.7.2/index.mjs +20 -0
  142. package/assets/codemods/transforms/v0.7.2/migrate-list-edge-compensation-to-edge.mjs +198 -0
  143. package/assets/discover/official-packages.json +367 -0
  144. package/assets/docs/README.md +10 -0
  145. package/assets/docs/author-a-theme.doc.mjs +3 -3
  146. package/assets/docs/browser-support.doc.mjs +1 -1
  147. package/assets/docs/getting-started.doc.mjs +26 -6
  148. package/assets/docs/styling-overview.doc.mjs +1 -1
  149. package/assets/docs/theme.doc.dense.mjs +1 -1
  150. package/assets/docs/theme.doc.mjs +2 -2
  151. package/assets/docs/theme.doc.zh.mjs +1 -1
  152. package/assets/docs/tree/add-a-component.doc.mjs +22 -2
  153. package/assets/docs/tree/add-a-theme.doc.mjs +13 -3
  154. package/assets/docs/tree/add-a-topic.doc.mjs +54 -9
  155. package/assets/docs/tree/agent-guidance.doc.mjs +19 -5
  156. package/assets/docs/tree/block-template.doc.mjs +8 -20
  157. package/assets/docs/tree/build-the-template.doc.mjs +1 -1
  158. package/assets/docs/tree/building-blocks.doc.mjs +4 -0
  159. package/assets/docs/tree/check-the-props.doc.mjs +201 -0
  160. package/assets/docs/tree/check-your-docs.doc.mjs +69 -11
  161. package/assets/docs/tree/checks.doc.mjs +32 -24
  162. package/assets/docs/tree/ci-and-containers.doc.mjs +88 -0
  163. package/assets/docs/tree/cli-command.doc.mjs +142 -0
  164. package/assets/docs/tree/cli.doc.mjs +6 -0
  165. package/assets/docs/tree/codemods.doc.mjs +17 -7
  166. package/assets/docs/tree/commands.doc.mjs +7 -1
  167. package/assets/docs/tree/component-family.doc.mjs +8 -6
  168. package/assets/docs/tree/component-imports.doc.mjs +21 -3
  169. package/assets/docs/tree/components.doc.mjs +7 -1
  170. package/assets/docs/tree/computer-agent-guide.doc.mjs +160 -0
  171. package/assets/docs/tree/computer-integrations.doc.mjs +93 -0
  172. package/assets/docs/tree/configuration.doc.mjs +7 -3
  173. package/assets/docs/tree/debug-and-gap-reports.doc.mjs +41 -22
  174. package/assets/docs/tree/define-the-theme.doc.mjs +6 -5
  175. package/assets/docs/tree/describe-the-component.doc.mjs +3 -3
  176. package/assets/docs/tree/docs.doc.mjs +9 -3
  177. package/assets/docs/tree/document-the-template.doc.mjs +1 -1
  178. package/assets/docs/tree/document-the-theme.doc.mjs +3 -3
  179. package/assets/docs/tree/export-template-assets.doc.mjs +1 -1
  180. package/assets/docs/tree/extend-or-replace.doc.mjs +19 -9
  181. package/assets/docs/tree/fonts-and-assets.doc.mjs +2 -2
  182. package/assets/docs/tree/generate-a-palette.doc.mjs +7 -5
  183. package/assets/docs/tree/global-integrations.doc.mjs +124 -0
  184. package/assets/docs/tree/global.doc.mjs +131 -0
  185. package/assets/docs/tree/grade-template-with-agent.doc.mjs +5 -5
  186. package/assets/docs/tree/install-once.doc.mjs +105 -0
  187. package/assets/docs/tree/integrations.doc.mjs +13 -1
  188. package/assets/docs/tree/layout-spacing.doc.mjs +2 -3
  189. package/assets/docs/tree/layout.doc.mjs +6 -6
  190. package/assets/docs/tree/links.doc.mjs +7 -6
  191. package/assets/docs/tree/migration-setup.doc.mjs +3 -2
  192. package/assets/docs/tree/migration-verification.doc.mjs +0 -1
  193. package/assets/docs/tree/migration.doc.mjs +0 -1
  194. package/assets/docs/tree/monorepos.doc.mjs +84 -0
  195. package/assets/docs/tree/package-and-test.doc.mjs +1 -1
  196. package/assets/docs/tree/page-template.doc.mjs +4 -8
  197. package/assets/docs/tree/problems.doc.mjs +205 -0
  198. package/assets/docs/tree/project-global-or-both.doc.mjs +131 -0
  199. package/assets/docs/tree/publishing.doc.mjs +6 -6
  200. package/assets/docs/tree/quick-start.doc.mjs +53 -27
  201. package/assets/docs/tree/reference.doc.mjs +192 -0
  202. package/assets/docs/tree/replace-a-core-component.doc.mjs +15 -7
  203. package/assets/docs/tree/replace-a-core-template.doc.mjs +10 -12
  204. package/assets/docs/tree/scaffold.doc.mjs +1 -1
  205. package/assets/docs/tree/sections-and-placement.doc.mjs +9 -11
  206. package/assets/docs/tree/see-it-in-an-app.doc.mjs +17 -5
  207. package/assets/docs/tree/short-and-findable.doc.mjs +9 -8
  208. package/assets/docs/tree/single-component.doc.mjs +15 -6
  209. package/assets/docs/tree/start-a-template.doc.mjs +18 -8
  210. package/assets/docs/tree/subcomponent.doc.mjs +6 -6
  211. package/assets/docs/tree/template-assets.doc.mjs +2 -2
  212. package/assets/docs/tree/template-doc-overview.doc.mjs +6 -17
  213. package/assets/docs/tree/template-fonts.doc.mjs +1 -1
  214. package/assets/docs/tree/template-icons.doc.mjs +1 -1
  215. package/assets/docs/tree/template-images-media.doc.mjs +1 -1
  216. package/assets/docs/tree/template-styles.doc.mjs +2 -2
  217. package/assets/docs/tree/templates.doc.mjs +7 -1
  218. package/assets/docs/tree/test-in-an-app.doc.mjs +7 -7
  219. package/assets/docs/tree/test-template-in-app.doc.mjs +5 -5
  220. package/assets/docs/tree/themes.doc.mjs +4 -0
  221. package/assets/docs/tree/tokens-and-setup.doc.mjs +19 -15
  222. package/assets/docs/tree/troubleshooting.doc.mjs +29 -13
  223. package/assets/docs/tree/type-scale.doc.mjs +3 -3
  224. package/assets/docs/tree/uninstall.doc.mjs +109 -0
  225. package/assets/docs/tree/updates.doc.mjs +92 -0
  226. package/assets/docs/tree/upgrading.doc.mjs +6 -6
  227. package/assets/docs/tree/use-a-theme-in-an-app.doc.mjs +14 -2
  228. package/assets/docs/tree/verify-packed-template.doc.mjs +3 -3
  229. package/assets/docs/tree/versioning.doc.mjs +10 -9
  230. package/assets/docs/tree/who-answers.doc.mjs +119 -0
  231. package/assets/docs/tree/write-the-template-file.doc.mjs +7 -6
  232. package/assets/docs/use-a-theme.doc.mjs +2 -2
  233. package/assets/docs/working-with-ai.doc.mjs +66 -13
  234. package/assets/theme.template.ts +1 -1
  235. package/authoring/codemod/codemod.doc.mjs +7 -3
  236. package/authoring/config/config.doc.mjs +9 -6
  237. package/authoring/config/type.ts +4 -2
  238. package/authoring/debug/parse.d.mts +5 -5
  239. package/authoring/discover/discover.doc.mjs +7 -1
  240. package/authoring/doctypes/_schema.d.mts +8 -3
  241. package/authoring/doctypes/_schema.mjs +1 -0
  242. package/authoring/doctypes/base/graph-fields.doc.mjs +6 -4
  243. package/authoring/doctypes/command/command.doc.mjs +20 -12
  244. package/authoring/doctypes/command/type.ts +14 -0
  245. package/authoring/doctypes/component/component.doc.mjs +30 -27
  246. package/authoring/doctypes/component/type.ts +21 -8
  247. package/authoring/doctypes/enum/enum.doc.mjs +7 -4
  248. package/authoring/doctypes/function/function.doc.mjs +9 -8
  249. package/authoring/doctypes/hook/hook.doc.mjs +5 -4
  250. package/authoring/doctypes/hook/type.ts +4 -0
  251. package/authoring/doctypes/namespace/namespace.doc.mjs +7 -6
  252. package/authoring/doctypes/reference/reference.doc.mjs +12 -11
  253. package/authoring/doctypes/schema/schema.doc.mjs +9 -9
  254. package/authoring/doctypes/template/integration-template.doc.d.mts +11 -0
  255. package/authoring/doctypes/template/integration-template.doc.mjs +49 -0
  256. package/authoring/doctypes/template/parse.d.mts +6 -2
  257. package/authoring/doctypes/template/parse.mjs +3 -1
  258. package/authoring/doctypes/template/template.doc.mjs +3 -3
  259. package/authoring/doctypes/template/type.ts +22 -0
  260. package/authoring/doctypes/theme/theme.doc.mjs +2 -2
  261. package/authoring/gap-report/gap-report.doc.mjs +2 -2
  262. package/authoring/identity/identity.doc.mjs +3 -3
  263. package/authoring/index.d.ts +2 -0
  264. package/authoring/integration/integration.doc.mjs +30 -6
  265. package/authoring/integration/parse.test.mjs +21 -0
  266. package/authoring/integration/schema.d.mts +16 -3
  267. package/authoring/integration/schema.mjs +34 -2
  268. package/authoring/integration/type.ts +12 -0
  269. package/clients/cli/bin/astryx.mjs +20 -3
  270. package/clients/cli/cli-command.test.mjs +166 -0
  271. package/clients/cli/commands/blog.doc.mjs +1 -0
  272. package/clients/cli/commands/build-theme.batch-report.test.mjs +17 -3
  273. package/clients/cli/commands/build-theme.font-warning.test.mjs +3 -3
  274. package/clients/cli/commands/build-theme.mjs +26 -13
  275. package/clients/cli/commands/build-theme.path-safety.test.mjs +224 -6
  276. package/clients/cli/commands/build-theme.registry.test.mjs +5 -2
  277. package/clients/cli/commands/build.doc.mjs +1 -0
  278. package/clients/cli/commands/build.exit-codes-doc.test.mjs +4 -0
  279. package/clients/cli/commands/cli-postinstall.test.mjs +49 -1
  280. package/clients/cli/commands/component.doc.mjs +1 -0
  281. package/clients/cli/commands/discover.broken-integration.test.mjs +11 -7
  282. package/clients/cli/commands/discover.doc.mjs +9 -1
  283. package/clients/cli/commands/discover.mjs +144 -93
  284. package/clients/cli/commands/discover.no-source.test.mjs +448 -33
  285. package/clients/cli/commands/discover.sources.test.mjs +20 -1
  286. package/clients/cli/commands/docs.doc.mjs +1 -0
  287. package/clients/cli/commands/docs.mjs +49 -19
  288. package/clients/cli/commands/docs.test.mjs +2 -2
  289. package/clients/cli/commands/doctor-integration-components.doc.mjs +19 -4
  290. package/clients/cli/commands/doctor-integration-docs.doc.mjs +20 -5
  291. package/clients/cli/commands/doctor-integration-templates.doc.mjs +1 -0
  292. package/clients/cli/commands/doctor-integration-validate.doc.mjs +22 -2
  293. package/clients/cli/commands/doctor-integration.test.mjs +888 -3
  294. package/clients/cli/commands/doctor.doc.mjs +12 -2
  295. package/clients/cli/commands/doctor.mjs +292 -7
  296. package/clients/cli/commands/doctor.test.mjs +10 -4
  297. package/clients/cli/commands/gap-report.doc.mjs +15 -2
  298. package/clients/cli/commands/gap-report.mjs +24 -2
  299. package/clients/cli/commands/gap-report.test.mjs +79 -0
  300. package/clients/cli/commands/hook.doc.mjs +1 -0
  301. package/clients/cli/commands/init.behavior.test.mjs +5 -0
  302. package/clients/cli/commands/init.doc.mjs +29 -3
  303. package/clients/cli/commands/integration-add.doc.mjs +7 -0
  304. package/clients/cli/commands/integration-authoring.test.mjs +49 -3
  305. package/clients/cli/commands/integration-check.doc.mjs +93 -0
  306. package/clients/cli/commands/integration-check.test.mjs +441 -0
  307. package/clients/cli/commands/integration-pack.doc.mjs +8 -1
  308. package/clients/cli/commands/integration-verify.doc.mjs +8 -1
  309. package/clients/cli/commands/integration.doc.mjs +6 -2
  310. package/clients/cli/commands/integration.mjs +119 -7
  311. package/clients/cli/commands/json-contract.test.mjs +11 -2
  312. package/clients/cli/commands/manifest.doc.mjs +1 -0
  313. package/clients/cli/commands/outside-project.test.mjs +774 -0
  314. package/clients/cli/commands/search.doc.mjs +1 -0
  315. package/clients/cli/commands/search.mjs +16 -11
  316. package/clients/cli/commands/search.test.mjs +429 -296
  317. package/clients/cli/commands/setup-nudge.test.mjs +102 -2
  318. package/clients/cli/commands/swizzle.doc.mjs +7 -0
  319. package/clients/cli/commands/swizzle.mjs +5 -3
  320. package/clients/cli/commands/swizzle.routing.test.mjs +19 -2
  321. package/clients/cli/commands/template.doc.mjs +1 -0
  322. package/clients/cli/commands/text-json-parity.test.mjs +5 -0
  323. package/clients/cli/commands/theme-add-lifecycle.test.mjs +3 -3
  324. package/clients/cli/commands/theme-add.doc.mjs +8 -1
  325. package/clients/cli/commands/theme-build.doc.mjs +3 -2
  326. package/clients/cli/commands/theme-eject.doc.mjs +1 -0
  327. package/clients/cli/commands/theme-list.behavior.test.mjs +6 -0
  328. package/clients/cli/commands/theme-list.doc.mjs +1 -0
  329. package/clients/cli/commands/theme-palette-generate.doc.mjs +1 -0
  330. package/clients/cli/commands/theme-remove.doc.mjs +7 -0
  331. package/clients/cli/commands/theme-targets.doc.mjs +1 -0
  332. package/clients/cli/commands/theme-template.doc.mjs +1 -0
  333. package/clients/cli/commands/theme-use.doc.mjs +7 -0
  334. package/clients/cli/commands/upgrade.config-ordering.test.mjs +2 -1
  335. package/clients/cli/commands/upgrade.doc.mjs +22 -1
  336. package/clients/cli/commands/upgrade.file-protection.test.mjs +3 -2
  337. package/clients/cli/commands/upgrade.hook-output.test.mjs +2 -1
  338. package/clients/cli/commands/upgrade.older-cli.test.mjs +160 -0
  339. package/clients/cli/index.mjs +112 -16
  340. package/clients/cli/lib/cli-error.mjs +4 -1
  341. package/clients/cli/lib/component-format.test.mjs +1 -1
  342. package/clients/cli/lib/define-command.mjs +2 -2
  343. package/clients/cli/lib/json-shim.mjs +3 -1
  344. package/clients/cli/lib/manifest.d.ts +9 -0
  345. package/clients/cli/lib/manifest.mjs +17 -1
  346. package/clients/cli/lib/manifest.test.mjs +25 -0
  347. package/foundation/agent-docs/agent-doc-state.d.mts +35 -6
  348. package/foundation/agent-docs/agent-doc-state.mjs +66 -6
  349. package/foundation/agent-docs/agent-docs.d.mts +4 -3
  350. package/foundation/agent-docs/agent-docs.mjs +11 -9
  351. package/foundation/agent-docs/computer-agent-text.d.mts +133 -0
  352. package/foundation/agent-docs/computer-agent-text.mjs +623 -0
  353. package/foundation/agent-docs/computer-agent-text.test.mjs +602 -0
  354. package/foundation/config/project.d.mts +35 -3
  355. package/foundation/config/project.mjs +226 -7
  356. package/foundation/config/project.test.mjs +66 -0
  357. package/foundation/config/theme-state.mjs +5 -4
  358. package/foundation/discovery/authoring-self-docs.mjs +2 -1
  359. package/foundation/discovery/cli-self-docs.mjs +20 -3
  360. package/foundation/discovery/cli-self-docs.test.mjs +3 -2
  361. package/foundation/discovery/component-discovery.d.mts +190 -10
  362. package/foundation/discovery/component-discovery.export-names.test.mjs +760 -0
  363. package/foundation/discovery/component-discovery.mjs +864 -56
  364. package/foundation/discovery/component-discovery.test.mjs +96 -0
  365. package/foundation/discovery/component-replacement.mjs +20 -1
  366. package/foundation/discovery/component-replacement.test.mjs +40 -0
  367. package/foundation/discovery/copied-passages.d.mts +216 -0
  368. package/foundation/discovery/copied-passages.mjs +720 -0
  369. package/foundation/discovery/copied-passages.test.mjs +124 -0
  370. package/foundation/discovery/doc-consistency.d.mts +34 -0
  371. package/foundation/discovery/doc-consistency.mjs +513 -0
  372. package/foundation/discovery/doc-consistency.test.mjs +347 -0
  373. package/foundation/discovery/doc-fluff.d.mts +112 -0
  374. package/foundation/discovery/doc-fluff.mjs +328 -0
  375. package/foundation/discovery/doc-fluff.test.mjs +142 -0
  376. package/foundation/discovery/doc-types.d.mts +157 -0
  377. package/foundation/discovery/doc-types.mjs +1231 -0
  378. package/foundation/discovery/doc-types.test.mjs +744 -0
  379. package/foundation/discovery/docs-discovery.d.mts +88 -0
  380. package/foundation/discovery/docs-discovery.mjs +83 -2
  381. package/foundation/discovery/integration-doc-files.d.mts +22 -0
  382. package/foundation/discovery/integration-doc-files.mjs +231 -0
  383. package/foundation/discovery/integration-doc-files.test.mjs +246 -0
  384. package/foundation/discovery/module-exports.d.mts +27 -0
  385. package/foundation/discovery/module-exports.mjs +356 -31
  386. package/foundation/discovery/module-exports.test.mjs +172 -0
  387. package/foundation/discovery/package-tsconfig.d.mts +14 -0
  388. package/foundation/discovery/package-tsconfig.mjs +228 -0
  389. package/foundation/discovery/package-tsconfig.test.mjs +210 -0
  390. package/foundation/discovery/project-typescript.d.mts +32 -0
  391. package/foundation/discovery/project-typescript.mjs +89 -0
  392. package/foundation/discovery/prop-drift.d.mts +297 -0
  393. package/foundation/discovery/prop-drift.mjs +2428 -0
  394. package/foundation/discovery/prop-drift.test.mjs +2337 -0
  395. package/foundation/discovery/runner-allowlist.d.mts +40 -0
  396. package/foundation/discovery/runner-allowlist.mjs +53 -0
  397. package/foundation/discovery/stray-pointers.d.mts +52 -0
  398. package/foundation/discovery/stray-pointers.mjs +293 -0
  399. package/foundation/discovery/stray-pointers.test.mjs +86 -0
  400. package/foundation/discovery/template-adapter.mjs +74 -5
  401. package/foundation/discovery/template-needs.mjs +5 -2
  402. package/foundation/discovery/theme-imports.mjs +8 -4
  403. package/foundation/discovery/theme-imports.test.mjs +1 -1
  404. package/foundation/discovery/type-match.d.mts +53 -0
  405. package/foundation/discovery/type-match.mjs +660 -0
  406. package/foundation/discovery/type-match.test.mjs +169 -0
  407. package/foundation/discovery/type-text.d.mts +68 -0
  408. package/foundation/discovery/type-text.mjs +658 -0
  409. package/foundation/discovery/type-text.test.mjs +196 -0
  410. package/foundation/discovery/unresolved-references.d.mts +62 -0
  411. package/foundation/discovery/unresolved-references.mjs +790 -0
  412. package/foundation/discovery/unresolved-references.test.mjs +567 -0
  413. package/foundation/doc-compiler/doc-loads.test.mjs +51 -1
  414. package/foundation/doc-compiler/inputs.mjs +27 -1
  415. package/foundation/doc-compiler/inputs.test.mjs +0 -1
  416. package/foundation/doc-compiler/links.d.mts +41 -5
  417. package/foundation/doc-compiler/links.mjs +167 -28
  418. package/foundation/doc-compiler/links.test.mjs +97 -4
  419. package/foundation/doc-compiler/read.d.mts +1 -1
  420. package/foundation/doc-compiler/tree.mjs +30 -1
  421. package/foundation/doc-compiler/tree.test.mjs +8 -1
  422. package/foundation/env/cli-command.d.mts +83 -0
  423. package/foundation/env/cli-command.mjs +152 -0
  424. package/foundation/env/cli-command.test.mjs +129 -0
  425. package/foundation/env/node-version.test.mjs +15 -3
  426. package/foundation/env/package-manager.d.mts +93 -9
  427. package/foundation/env/package-manager.global.test.mjs +66 -0
  428. package/foundation/env/package-manager.mjs +184 -13
  429. package/foundation/env/package-manager.test.mjs +341 -15
  430. package/foundation/env/project-scope.d.mts +9 -0
  431. package/foundation/env/project-scope.mjs +33 -0
  432. package/foundation/env/search-path.d.mts +42 -0
  433. package/foundation/env/search-path.mjs +205 -0
  434. package/foundation/env/search-path.test.mjs +122 -0
  435. package/foundation/env/user-dirs.d.mts +39 -0
  436. package/foundation/env/user-dirs.mjs +93 -0
  437. package/foundation/env/user-dirs.test.mjs +72 -0
  438. package/foundation/env/user-settings.d.mts +46 -0
  439. package/foundation/env/user-settings.mjs +106 -0
  440. package/foundation/env/user-settings.test.mjs +64 -0
  441. package/foundation/fs/file-protection.mjs +15 -1
  442. package/foundation/fs/module-loader.d.mts +2 -1
  443. package/foundation/fs/module-loader.mjs +60 -4
  444. package/foundation/fs/module-loader.stdout.test.mjs +3 -2
  445. package/foundation/fs/module-loader.test.mjs +194 -0
  446. package/foundation/fs/path-compare.d.mts +33 -0
  447. package/foundation/fs/path-compare.mjs +73 -0
  448. package/foundation/fs/paths.d.mts +57 -1
  449. package/foundation/fs/paths.mjs +131 -3
  450. package/foundation/fs/paths.test.mjs +10 -1
  451. package/foundation/integrations/computer-integrations.d.mts +78 -0
  452. package/foundation/integrations/computer-integrations.mjs +316 -0
  453. package/foundation/integrations/computer-integrations.test.mjs +292 -0
  454. package/foundation/integrations/contribution-inventory.d.mts +11 -0
  455. package/foundation/integrations/contribution-inventory.mjs +56 -3
  456. package/foundation/integrations/integration-warnings.mjs +3 -2
  457. package/foundation/integrations/integration-warnings.test.mjs +8 -9
  458. package/foundation/integrations/integrations.d.mts +25 -1
  459. package/foundation/integrations/integrations.mjs +46 -3
  460. package/foundation/integrations/issue.ts +6 -0
  461. package/foundation/integrations/validate-contributions.d.mts +2 -0
  462. package/foundation/integrations/validate-contributions.mjs +41 -1
  463. package/foundation/release/agent-text-worker.d.mts +4 -0
  464. package/foundation/release/agent-text-worker.mjs +52 -0
  465. package/foundation/release/cache.d.mts +310 -0
  466. package/foundation/release/cache.mjs +1216 -0
  467. package/foundation/release/cache.test.mjs +920 -0
  468. package/foundation/release/choose.d.mts +207 -0
  469. package/foundation/release/choose.mjs +677 -0
  470. package/foundation/release/choose.test.mjs +427 -0
  471. package/foundation/release/core-worker.d.mts +4 -0
  472. package/foundation/release/core-worker.mjs +38 -0
  473. package/foundation/release/global-state.d.mts +259 -0
  474. package/foundation/release/global-state.mjs +472 -0
  475. package/foundation/release/global-state.test.mjs +381 -0
  476. package/foundation/release/handoff.d.mts +60 -0
  477. package/foundation/release/handoff.e2e.test.mjs +278 -0
  478. package/foundation/release/handoff.mjs +357 -0
  479. package/foundation/release/lock-lease.d.mts +23 -0
  480. package/foundation/release/lock-lease.mjs +80 -0
  481. package/foundation/release/notices.d.mts +253 -0
  482. package/foundation/release/notices.mjs +596 -0
  483. package/foundation/release/notices.test.mjs +565 -0
  484. package/foundation/release/npm-guard.d.mts +4 -0
  485. package/foundation/release/npm-guard.mjs +126 -0
  486. package/foundation/release/project.d.mts +141 -0
  487. package/foundation/release/project.mjs +466 -0
  488. package/foundation/release/project.test.mjs +288 -0
  489. package/foundation/release/refresh-worker.d.mts +4 -0
  490. package/foundation/release/refresh-worker.mjs +27 -0
  491. package/foundation/release/run-context.d.mts +47 -0
  492. package/foundation/release/run-context.mjs +149 -0
  493. package/foundation/release/update-notice-env.d.mts +28 -0
  494. package/foundation/release/update-notice-env.mjs +39 -0
  495. package/foundation/response/base.d.ts +26 -3
  496. package/foundation/response/error-codes.d.mts +3 -1
  497. package/foundation/response/error-codes.doc.mjs +12 -0
  498. package/foundation/response/error-codes.mjs +17 -0
  499. package/foundation/response/error-codes.test.mjs +2 -0
  500. package/foundation/response/json-contract.test.mjs +7 -2
  501. package/foundation/response/json.mjs +4 -1
  502. package/foundation/response/response-types.doc.mjs +22 -11
  503. package/foundation/response/response-types.doc.test.mjs +46 -0
  504. package/foundation/response/response.doc.mjs +3 -3
  505. package/foundation/xle/browser-imports.test.mjs +74 -0
  506. package/foundation/xle/registry.mjs +4 -0
  507. package/foundation/xle/registry.test.mjs +28 -0
  508. package/package.json +11 -11
  509. package/scripts/postinstall.mjs +24 -5
@@ -17,6 +17,7 @@ import {
17
17
  integrationAddComponent,
18
18
  integrationAddTemplate,
19
19
  } from './add-contribution.mjs';
20
+ import {formatCliCommand} from '../../foundation/env/package-manager.mjs';
20
21
 
21
22
  let tmpDir;
22
23
 
@@ -213,6 +214,7 @@ function writePackage({
213
214
  themeExports = false,
214
215
  npmignore,
215
216
  components = false,
217
+ templates = false,
216
218
  scripts,
217
219
  } = {}) {
218
220
  const pkg = {name, version};
@@ -272,6 +274,14 @@ export default {type: 'theme', name: 'ocean', displayName: 'Ocean', description:
272
274
  'export default function Card() { return null; }\n',
273
275
  );
274
276
  }
277
+ if (templates) {
278
+ const root = path.join(tmpDir, 'templates');
279
+ fs.mkdirSync(root, {recursive: true});
280
+ fs.writeFileSync(
281
+ path.join(root, 'stray.tsx'),
282
+ 'export default function Stray() { return null; }\n',
283
+ );
284
+ }
275
285
  if (npmignore) {
276
286
  fs.writeFileSync(path.join(tmpDir, '.npmignore'), npmignore);
277
287
  }
@@ -303,6 +313,345 @@ function runCliVerify() {
303
313
  };
304
314
  }
305
315
 
316
+ /**
317
+ * Give the built ocean theme a font stylesheet and an entry module that loads
318
+ * the theme's own styles, and export them the way such a package does.
319
+ * @param {string} entry
320
+ * @param {string} [file] the entry's file name in `entries/`
321
+ */
322
+ function writeOwnStylesEntry(entry, file = 'ocean.mjs') {
323
+ fs.writeFileSync(
324
+ path.join(tmpDir, 'themes', 'ocean', 'ocean.fonts.css'),
325
+ "@font-face { font-family: 'Ocean Sans'; src: local('Ocean Sans'); }\n",
326
+ );
327
+ fs.mkdirSync(path.join(tmpDir, 'entries'), {recursive: true});
328
+ fs.writeFileSync(path.join(tmpDir, 'entries', file), entry);
329
+ const packageFile = path.join(tmpDir, 'package.json');
330
+ const pkg = JSON.parse(fs.readFileSync(packageFile, 'utf-8'));
331
+ pkg.files = ['astryx.integration.mjs', 'themes', 'entries'];
332
+ pkg.exports = {
333
+ './themes/ocean': `./entries/${file}`,
334
+ './themes/ocean.css': './themes/ocean/ocean.css',
335
+ './themes/ocean.fonts.css': './themes/ocean/ocean.fonts.css',
336
+ };
337
+ fs.writeFileSync(packageFile, `${JSON.stringify(pkg, null, 2)}\n`);
338
+ }
339
+
340
+ /**
341
+ * Rename the ocean theme to deep-sea, keeping its `ocean` slug, and build it,
342
+ * so its outputs are named after the theme: `deep-sea.css` and `deep-sea.js`,
343
+ * which exports `deepSeaTheme`.
344
+ */
345
+ function writeRenamedOcean() {
346
+ writePackage({files: ['astryx.integration.mjs', 'themes', 'entries']});
347
+ fs.writeFileSync(
348
+ path.join(tmpDir, 'themes', 'ocean', 'oceanTheme.ts'),
349
+ "import {defineTheme} from '@astryxdesign/core/theme';\n\nexport const oceanTheme = defineTheme({name: 'deep-sea'});\n",
350
+ );
351
+ const built = spawnSync(
352
+ process.execPath,
353
+ [
354
+ path.join(process.cwd(), 'packages/cli/clients/cli/bin/astryx.mjs'),
355
+ 'theme',
356
+ 'build',
357
+ 'themes/ocean/oceanTheme.ts',
358
+ ],
359
+ {cwd: tmpDir, encoding: 'utf-8', timeout: 30_000},
360
+ );
361
+ if (built.status !== 0) {
362
+ throw new Error(`Could not build the theme fixture: ${built.stderr}`);
363
+ }
364
+ }
365
+
366
+ /**
367
+ * Give the ocean theme an icon registry and build it, so its built module
368
+ * also exports `oceanIcons`.
369
+ */
370
+ function writeOceanWithIcons() {
371
+ writePackage({files: ['astryx.integration.mjs', 'themes', 'entries']});
372
+ const themeDir = path.join(tmpDir, 'themes', 'ocean');
373
+ fs.writeFileSync(
374
+ path.join(themeDir, 'icons.js'),
375
+ 'export const oceanIcons = {};\n',
376
+ );
377
+ fs.writeFileSync(
378
+ path.join(themeDir, 'oceanTheme.ts'),
379
+ "import {defineTheme} from '@astryxdesign/core/theme';\nimport {oceanIcons} from './icons';\n\nexport const oceanTheme = defineTheme({name: 'ocean', icons: oceanIcons});\n",
380
+ );
381
+ const built = spawnSync(
382
+ process.execPath,
383
+ [
384
+ path.join(process.cwd(), 'packages/cli/clients/cli/bin/astryx.mjs'),
385
+ 'theme',
386
+ 'build',
387
+ 'themes/ocean/oceanTheme.ts',
388
+ ],
389
+ {cwd: tmpDir, encoding: 'utf-8', timeout: 30_000},
390
+ );
391
+ if (built.status !== 0) {
392
+ throw new Error(`Could not build the theme fixture: ${built.stderr}`);
393
+ }
394
+ }
395
+
396
+ // An entry like one whose import is the whole integration: it loads the
397
+ // theme's fonts and stylesheet, by relative path and through the package's
398
+ // own exports, and re-exports the built theme.
399
+ const OWN_STYLES_ENTRY = `// Copyright (c) Acme.
400
+
401
+ import '../themes/ocean/ocean.fonts.css';
402
+ import '@acme/widgets/themes/ocean.css';
403
+
404
+ export {oceanTheme} from '../themes/ocean/ocean.js';
405
+ `;
406
+
407
+ describe('a theme module that loads its own styles', () => {
408
+ it('passes an entry that imports the theme stylesheet and fonts and re-exports the built module', async () => {
409
+ writePackage({themeExports: true});
410
+ writeOwnStylesEntry(OWN_STYLES_ENTRY);
411
+
412
+ const result = await integrationPackCheck({cwd: tmpDir});
413
+
414
+ expect(result.data.issues).toEqual([]);
415
+ expect(result.data.packable).toBe(true);
416
+ const cli = runCliVerify();
417
+ expect(cli.status, cli.stderr).toBe(0);
418
+ expect(cli.receipt?.data.issues).toEqual([]);
419
+ });
420
+
421
+ it('passes an entry that imports its fonts through the package exports and re-exports everything', async () => {
422
+ writePackage({themeExports: true});
423
+ writeOwnStylesEntry(
424
+ "import '@acme/widgets/themes/ocean.fonts.css';\nimport '../themes/ocean/ocean.css';\nexport * from '../themes/ocean/ocean.js';\n",
425
+ );
426
+
427
+ const result = await integrationPackCheck({cwd: tmpDir});
428
+
429
+ expect(result.data.issues).toEqual([]);
430
+ expect(result.data.packable).toBe(true);
431
+ });
432
+
433
+ it.each([
434
+ [
435
+ 'also imports another stylesheet',
436
+ `${OWN_STYLES_ENTRY}import './other.css';\n`,
437
+ "./other.css is not the theme's own stylesheet or font stylesheet",
438
+ ],
439
+ [
440
+ 'also imports a script',
441
+ `import './setup.mjs';\n${OWN_STYLES_ENTRY}`,
442
+ "./setup.mjs is not the theme's own stylesheet or font stylesheet",
443
+ ],
444
+ [
445
+ 'runs code',
446
+ `${OWN_STYLES_ENTRY}console.log('ocean');\n`,
447
+ 'line 7 is neither a stylesheet import nor the re-export',
448
+ ],
449
+ [
450
+ 'exports another value',
451
+ `${OWN_STYLES_ENTRY}export const extra = 1;\n`,
452
+ 'line 7 is neither a stylesheet import nor the re-export',
453
+ ],
454
+ [
455
+ 'renames the theme it re-exports',
456
+ "import '../themes/ocean/ocean.css';\nexport {oceanTheme, oceanTheme as theme} from '../themes/ocean/ocean.js';\n",
457
+ 'it re-exports names other than oceanTheme',
458
+ ],
459
+ [
460
+ 'binds the stylesheet it imports',
461
+ "import styles from '../themes/ocean/ocean.css';\nexport {oceanTheme} from '../themes/ocean/ocean.js';\n",
462
+ 'its import of ../themes/ocean/ocean.css is not a side-effect import',
463
+ ],
464
+ [
465
+ 'imports the stylesheet as a CSS module script',
466
+ "import '../themes/ocean/ocean.css' with {type: 'css'};\nexport {oceanTheme} from '../themes/ocean/ocean.js';\n",
467
+ 'its import of ../themes/ocean/ocean.css is not a side-effect import',
468
+ ],
469
+ [
470
+ 're-exports another name',
471
+ "import '../themes/ocean/ocean.css';\nexport {oceanTheme, missing} from '../themes/ocean/ocean.js';\n",
472
+ 'it re-exports names other than oceanTheme',
473
+ ],
474
+ [
475
+ 're-exports a copy that is not a .js module',
476
+ "import '../themes/ocean/ocean.css';\nexport {oceanTheme} from './copy.cjs';\n",
477
+ 'its re-export from ./copy.cjs does not name a .js module',
478
+ ],
479
+ [
480
+ 'has a directive',
481
+ `'use client';\n${OWN_STYLES_ENTRY}`,
482
+ 'it has a directive',
483
+ ],
484
+ [
485
+ 're-exports twice',
486
+ `${OWN_STYLES_ENTRY}export * from '../themes/ocean/ocean.js';\n`,
487
+ 'it has more than one re-export',
488
+ ],
489
+ ])('fails an entry that %s', async (_label, entry, reason) => {
490
+ writePackage({themeExports: true});
491
+ writeOwnStylesEntry(entry);
492
+ fs.writeFileSync(path.join(tmpDir, 'entries', 'other.css'), '.x {}\n');
493
+ fs.writeFileSync(path.join(tmpDir, 'entries', 'setup.mjs'), 'export {};\n');
494
+ fs.copyFileSync(
495
+ path.join(tmpDir, 'themes', 'ocean', 'ocean.js'),
496
+ path.join(tmpDir, 'entries', 'copy.cjs'),
497
+ );
498
+
499
+ const result = await integrationPackCheck({cwd: tmpDir});
500
+
501
+ expect(result.data.packable).toBe(false);
502
+ expect(result.data.issues).toEqual([
503
+ {
504
+ code: 'theme_module_stale',
505
+ severity: 'error',
506
+ message: `Theme "ocean" module is not the module \`${formatCliCommand('theme build themes/ocean/oceanTheme.ts', tmpDir)}\` writes and does not only wrap it: ${reason}. A module that wraps the built module may only import the theme's own stylesheet and font stylesheet for their side effects and re-export from the built module either everything or only its theme, as oceanTheme.`,
507
+ },
508
+ ]);
509
+ });
510
+
511
+ it('fails an entry whose re-exported module drifts from source', async () => {
512
+ writePackage({themeExports: true});
513
+ writeOwnStylesEntry(OWN_STYLES_ENTRY);
514
+ fs.appendFileSync(
515
+ path.join(tmpDir, 'themes', 'ocean', 'ocean.js'),
516
+ '// stale module\n',
517
+ );
518
+
519
+ const result = await integrationPackCheck({cwd: tmpDir});
520
+
521
+ expect(result.data.packable).toBe(false);
522
+ expect(result.data.issues).toEqual([
523
+ {
524
+ code: 'theme_module_stale',
525
+ severity: 'error',
526
+ message: `Theme "ocean" module re-exports ../themes/ocean/ocean.js, which does not match themes/ocean/oceanTheme.ts. Run \`${formatCliCommand('theme build themes/ocean/oceanTheme.ts', tmpDir)}\` before packing.`,
527
+ },
528
+ ]);
529
+ });
530
+
531
+ it('does not accept a TypeScript entry', async () => {
532
+ writePackage({themeExports: true});
533
+ writeOwnStylesEntry(OWN_STYLES_ENTRY, 'ocean.ts');
534
+
535
+ const result = await integrationPackCheck({cwd: tmpDir});
536
+
537
+ expect(result.data.packable).toBe(false);
538
+ expect(result.data.issues).toEqual([
539
+ {
540
+ code: 'theme_module_stale',
541
+ severity: 'error',
542
+ message: `Theme "ocean" module does not match themes/ocean/oceanTheme.ts. Run \`${formatCliCommand('theme build themes/ocean/oceanTheme.ts', tmpDir)}\` before packing.`,
543
+ },
544
+ ]);
545
+ });
546
+
547
+ it('does not accept an entry that re-exports itself', async () => {
548
+ writePackage({themeExports: true});
549
+ writeOwnStylesEntry(
550
+ "import '../themes/ocean/ocean.css';\nexport * from './ocean.js';\n",
551
+ 'ocean.js',
552
+ );
553
+
554
+ const result = await integrationPackCheck({cwd: tmpDir});
555
+
556
+ expect(result.data.packable).toBe(false);
557
+ expect(result.data.issues).toContainEqual(
558
+ expect.objectContaining({
559
+ code: 'theme_import_unresolvable',
560
+ message: expect.stringContaining('does not export "oceanTheme"'),
561
+ }),
562
+ );
563
+ });
564
+
565
+ it('passes an entry for a theme whose own name differs from its slug', async () => {
566
+ writeRenamedOcean();
567
+ writeOwnStylesEntry(
568
+ "import '../themes/ocean/deep-sea.css';\nexport {deepSeaTheme as oceanTheme} from '../themes/ocean/deep-sea.js';\n",
569
+ );
570
+ setThemeExports({
571
+ './themes/ocean': './entries/ocean.mjs',
572
+ './themes/ocean.css': './themes/ocean/deep-sea.css',
573
+ });
574
+
575
+ const result = await integrationPackCheck({cwd: tmpDir});
576
+
577
+ expect(result.data.issues).toEqual([]);
578
+ expect(result.data.packable).toBe(true);
579
+ const cli = runCliVerify();
580
+ expect(cli.status, cli.stderr).toBe(0);
581
+ });
582
+
583
+ it('compares a partial export set with the module named after the theme', async () => {
584
+ writeRenamedOcean();
585
+ setThemeExports({'./themes/ocean': './themes/ocean/deep-sea.js'});
586
+
587
+ const result = await integrationPackCheck({cwd: tmpDir});
588
+
589
+ expect(result.data.packable).toBe(true);
590
+ expect(result.data.issues.map(issue => issue.code)).toEqual([
591
+ 'theme_exports_incomplete',
592
+ ]);
593
+ });
594
+
595
+ it('passes an entry that re-exports only the theme from a built module that also exports its icons', async () => {
596
+ writeOceanWithIcons();
597
+ writeOwnStylesEntry(OWN_STYLES_ENTRY);
598
+
599
+ const result = await integrationPackCheck({cwd: tmpDir});
600
+
601
+ expect(result.data.issues).toEqual([]);
602
+ expect(result.data.packable).toBe(true);
603
+ });
604
+
605
+ it('fails an entry that gives the built icons the theme export name', async () => {
606
+ writeOceanWithIcons();
607
+ writeOwnStylesEntry(
608
+ "import '../themes/ocean/ocean.css';\nexport {oceanIcons as oceanTheme} from '../themes/ocean/ocean.js';\n",
609
+ );
610
+
611
+ const result = await integrationPackCheck({cwd: tmpDir});
612
+
613
+ expect(result.data.packable).toBe(false);
614
+ expect(result.data.issues).toEqual([
615
+ expect.objectContaining({
616
+ code: 'theme_module_stale',
617
+ message: expect.stringContaining(
618
+ 'it re-exports oceanIcons, which is not the built theme',
619
+ ),
620
+ }),
621
+ ]);
622
+ });
623
+
624
+ it('fails when packing changes the module an entry re-exports', async () => {
625
+ const prepack = [
626
+ "const fs=require('fs')",
627
+ "fs.copyFileSync('themes/ocean/ocean.js','.ocean.js.orig')",
628
+ "fs.appendFileSync('themes/ocean/ocean.js','// packed\\n')",
629
+ ].join(';');
630
+ const postpack =
631
+ "require('fs').renameSync('.ocean.js.orig','themes/ocean/ocean.js')";
632
+ writePackage({
633
+ themeExports: true,
634
+ scripts: {
635
+ prepack: `node -e "${prepack}"`,
636
+ postpack: `node -e "${postpack}"`,
637
+ },
638
+ });
639
+ writeOwnStylesEntry(OWN_STYLES_ENTRY);
640
+
641
+ const result = await integrationPackCheck({cwd: tmpDir});
642
+
643
+ expect(result.data.packable).toBe(false);
644
+ expect(result.data.issues).toEqual([
645
+ {
646
+ code: 'packed_theme_module_changed',
647
+ severity: 'error',
648
+ message:
649
+ 'Theme "ocean" module changed during packing. Keep the built output unchanged in the tarball.',
650
+ },
651
+ ]);
652
+ });
653
+ });
654
+
306
655
  describe('integrationPackCheck', () => {
307
656
  it('returns packable:true for a valid integration with themes', async () => {
308
657
  writePackage({files: ['astryx.integration.mjs', 'themes']});
@@ -337,8 +686,7 @@ describe('integrationPackCheck', () => {
337
686
  {
338
687
  code: 'theme_exports_missing',
339
688
  severity: 'warning',
340
- message:
341
- 'Theme "ocean" has no declared built exports. The package remains source-compatible. To make it importable, run `astryx theme build themes/ocean/oceanTheme.ts` and export "./themes/ocean" plus "./themes/ocean.css" from package.json; `astryx integration add theme ocean` creates those exports for a new theme.',
689
+ message: `Theme "ocean" has no declared built exports. The package remains source-compatible. To make it importable, run \`${formatCliCommand('theme build themes/ocean/oceanTheme.ts', tmpDir)}\` and export "./themes/ocean" plus "./themes/ocean.css" from package.json; \`${formatCliCommand('integration add theme ocean', tmpDir)}\` creates those exports for a new theme.`,
342
690
  },
343
691
  ]);
344
692
  const cli = runCliVerify();
@@ -509,8 +857,7 @@ describe('integrationPackCheck', () => {
509
857
  {
510
858
  code: 'theme_stylesheet_stale',
511
859
  severity: 'warning',
512
- message:
513
- 'Theme "ocean" stylesheet does not match themes/ocean/oceanTheme.ts. This incomplete export remains packable, but run `astryx theme build themes/ocean/oceanTheme.ts` before adding its matching export.',
860
+ message: `Theme "ocean" stylesheet does not match themes/ocean/oceanTheme.ts. This incomplete export remains packable, but run \`${formatCliCommand('theme build themes/ocean/oceanTheme.ts', tmpDir)}\` before adding its matching export.`,
514
861
  },
515
862
  ]);
516
863
  const cli = runCliVerify();
@@ -543,8 +890,7 @@ describe('integrationPackCheck', () => {
543
890
  {
544
891
  code: 'theme_stylesheet_stale',
545
892
  severity: 'warning',
546
- message:
547
- 'Theme "ocean" stylesheet does not match themes/ocean/oceanTheme.ts. This incomplete export remains packable, but run `astryx theme build themes/ocean/oceanTheme.ts` before adding its matching export.',
893
+ message: `Theme "ocean" stylesheet does not match themes/ocean/oceanTheme.ts. This incomplete export remains packable, but run \`${formatCliCommand('theme build themes/ocean/oceanTheme.ts', tmpDir)}\` before adding its matching export.`,
548
894
  },
549
895
  ]);
550
896
  const cli = runCliVerify();
@@ -617,14 +963,12 @@ describe('integrationPackCheck', () => {
617
963
  {
618
964
  code: 'theme_module_stale',
619
965
  severity: 'error',
620
- message:
621
- 'Theme "ocean" module does not match themes/ocean/oceanTheme.ts. Run `astryx theme build themes/ocean/oceanTheme.ts` before packing.',
966
+ message: `Theme "ocean" module does not match themes/ocean/oceanTheme.ts. Run \`${formatCliCommand('theme build themes/ocean/oceanTheme.ts', tmpDir)}\` before packing.`,
622
967
  },
623
968
  {
624
969
  code: 'theme_stylesheet_stale',
625
970
  severity: 'error',
626
- message:
627
- 'Theme "ocean" stylesheet does not match themes/ocean/oceanTheme.ts. Run `astryx theme build themes/ocean/oceanTheme.ts` before packing.',
971
+ message: `Theme "ocean" stylesheet does not match themes/ocean/oceanTheme.ts. Run \`${formatCliCommand('theme build themes/ocean/oceanTheme.ts', tmpDir)}\` before packing.`,
628
972
  },
629
973
  ]);
630
974
  const cli = runCliVerify();
@@ -754,6 +1098,285 @@ describe('integrationPackCheck', () => {
754
1098
  );
755
1099
  });
756
1100
 
1101
+ it('checks the export a component doc names when its file is named otherwise', async () => {
1102
+ fs.writeFileSync(
1103
+ path.join(tmpDir, 'package.json'),
1104
+ `${JSON.stringify({
1105
+ name: '@acme/widgets',
1106
+ version: '1.0.0',
1107
+ files: ['astryx.integration.mjs', 'components'],
1108
+ exports: {'./components/Slides': './components/Slides.tsx'},
1109
+ })}\n`,
1110
+ );
1111
+ fs.writeFileSync(
1112
+ path.join(tmpDir, 'astryx.integration.mjs'),
1113
+ "export default {components: './components'};\n",
1114
+ );
1115
+ fs.mkdirSync(path.join(tmpDir, 'components'));
1116
+ // Slides.tsx exports AcmeCarousel and nothing named Slides.
1117
+ fs.writeFileSync(
1118
+ path.join(tmpDir, 'components', 'Slides.tsx'),
1119
+ 'export function AcmeCarousel() { return null; }\n',
1120
+ );
1121
+ fs.writeFileSync(
1122
+ path.join(tmpDir, 'components', 'Slides.doc.mjs'),
1123
+ "export default {type: 'component', name: 'AcmeCarousel', import: '@acme/widgets/components/Slides', description: 'x', props: []};\n",
1124
+ );
1125
+
1126
+ const result = await integrationPackCheck({cwd: tmpDir});
1127
+ expect(result.data.packable).toBe(true);
1128
+ expect(result.data.contributions.packed.components).toEqual([
1129
+ 'AcmeCarousel',
1130
+ ]);
1131
+ });
1132
+
1133
+ it('checks each family member against the module its family doc imports', async () => {
1134
+ fs.writeFileSync(
1135
+ path.join(tmpDir, 'package.json'),
1136
+ `${JSON.stringify({
1137
+ name: '@acme/widgets',
1138
+ version: '1.0.0',
1139
+ files: ['astryx.integration.mjs', 'components'],
1140
+ exports: {'./tabs': './components/tabs/index.ts'},
1141
+ })}\n`,
1142
+ );
1143
+ fs.writeFileSync(
1144
+ path.join(tmpDir, 'astryx.integration.mjs'),
1145
+ "export default {components: './components'};\n",
1146
+ );
1147
+ const dir = path.join(tmpDir, 'components', 'tabs');
1148
+ fs.mkdirSync(dir, {recursive: true});
1149
+ fs.writeFileSync(
1150
+ path.join(dir, 'AcmeTabs.tsx'),
1151
+ 'export function AcmeTabs() { return null; }\nexport function AcmeTabsLegacy() { return null; }\n',
1152
+ );
1153
+ fs.writeFileSync(
1154
+ path.join(dir, 'tab.tsx'),
1155
+ 'export function AcmeTab() { return null; }\n',
1156
+ );
1157
+ // The module apps import exports AcmeTabs and AcmeTab, not AcmeTabsLegacy.
1158
+ fs.writeFileSync(
1159
+ path.join(dir, 'index.ts'),
1160
+ "export {AcmeTabs} from './AcmeTabs';\nexport {AcmeTab} from './tab';\n",
1161
+ );
1162
+ fs.writeFileSync(
1163
+ path.join(dir, 'AcmeTabs.doc.mjs'),
1164
+ "export default {type: 'component', name: 'AcmeTabs', import: '@acme/widgets/tabs', description: 'x', components: [{name: 'AcmeTabs', description: 'x', props: []}, {name: 'AcmeTab', description: 'x', props: []}, {name: 'AcmeTabsLegacy', description: 'x', props: []}]};\n",
1165
+ );
1166
+
1167
+ const result = await integrationPackCheck({cwd: tmpDir});
1168
+
1169
+ expect(result.data.issues).toEqual([]);
1170
+ expect(result.data.packable).toBe(true);
1171
+ expect(result.data.contributions.packed.components).toEqual([
1172
+ 'AcmeTab',
1173
+ 'AcmeTabs',
1174
+ ]);
1175
+ });
1176
+
1177
+ it.each([
1178
+ 'a clean checkout',
1179
+ 'a stale build',
1180
+ 'a stale build that exports more',
1181
+ ])(
1182
+ 'reads members a family imports from output its pack lifecycle builds, from %s',
1183
+ async start => {
1184
+ fs.writeFileSync(
1185
+ path.join(tmpDir, 'package.json'),
1186
+ `${JSON.stringify({
1187
+ name: '@acme/widgets',
1188
+ version: '1.0.0',
1189
+ files: ['astryx.integration.mjs', 'components', 'dist'],
1190
+ exports: {'./tabs': './dist/tabs/index.mjs'},
1191
+ scripts: {prepack: 'node build.mjs'},
1192
+ })}\n`,
1193
+ );
1194
+ fs.writeFileSync(
1195
+ path.join(tmpDir, 'astryx.integration.mjs'),
1196
+ "export default {components: './components'};\n",
1197
+ );
1198
+ // The build writes the module the family imports; a clean checkout has none.
1199
+ fs.writeFileSync(
1200
+ path.join(tmpDir, 'build.mjs'),
1201
+ "import fs from 'node:fs';\nfs.mkdirSync('dist/tabs', {recursive: true});\nfs.writeFileSync('dist/tabs/index.mjs', \"export {AcmeTabs, AcmeTab} from '../../components/tabs/AcmeTabs.tsx';\\n\");\n",
1202
+ );
1203
+ const dir = path.join(tmpDir, 'components', 'tabs');
1204
+ fs.mkdirSync(dir, {recursive: true});
1205
+ fs.writeFileSync(
1206
+ path.join(dir, 'AcmeTabs.tsx'),
1207
+ 'export function AcmeTabs() { return null; }\nexport function AcmeTab() { return null; }\nexport function AcmeTabLegacy() { return null; }\n',
1208
+ );
1209
+ fs.writeFileSync(
1210
+ path.join(dir, 'AcmeTabs.doc.mjs'),
1211
+ "export default {type: 'component', name: 'AcmeTabs', import: '@acme/widgets/tabs', description: 'x', components: [{name: 'AcmeTabs', description: 'x', props: []}, {name: 'AcmeTab', description: 'x', props: []}, {name: 'AcmeTabLegacy', description: 'x', props: []}]};\n",
1212
+ );
1213
+
1214
+ if (start !== 'a clean checkout') {
1215
+ // An earlier build: before AcmeTab was exported, or while AcmeTabLegacy
1216
+ // still was.
1217
+ const names =
1218
+ start === 'a stale build'
1219
+ ? 'AcmeTabs'
1220
+ : 'AcmeTabs, AcmeTab, AcmeTabLegacy';
1221
+ fs.mkdirSync(path.join(tmpDir, 'dist', 'tabs'), {recursive: true});
1222
+ fs.writeFileSync(
1223
+ path.join(tmpDir, 'dist', 'tabs', 'index.mjs'),
1224
+ `export {${names}} from '../../components/tabs/AcmeTabs.tsx';\n`,
1225
+ );
1226
+ }
1227
+
1228
+ const result = await integrationPackCheck({cwd: tmpDir});
1229
+
1230
+ expect(result.data.issues).toEqual([]);
1231
+ expect(result.data.packable).toBe(true);
1232
+ expect(result.data.contributions.local.components).toEqual(
1233
+ result.data.contributions.packed.components,
1234
+ );
1235
+ },
1236
+ );
1237
+
1238
+ it.each(['a clean checkout', 'an incremental build'])(
1239
+ 'reads members built output re-exports from compiled files, from %s',
1240
+ async start => {
1241
+ fs.writeFileSync(
1242
+ path.join(tmpDir, 'package.json'),
1243
+ `${JSON.stringify({
1244
+ name: '@acme/widgets',
1245
+ version: '1.0.0',
1246
+ files: ['astryx.integration.mjs', 'components', 'dist'],
1247
+ exports: {'./tabs': './dist/tabs/index.mjs'},
1248
+ scripts: {prepack: 'node build.mjs'},
1249
+ })}\n`,
1250
+ );
1251
+ fs.writeFileSync(
1252
+ path.join(tmpDir, 'astryx.integration.mjs'),
1253
+ "export default {components: './components'};\n",
1254
+ );
1255
+ // The build compiles each source to its own file and, like an
1256
+ // incremental build, rewrites only the files whose output changed.
1257
+ const output = {
1258
+ 'index.mjs':
1259
+ "export {AcmeTabs} from './tabs.mjs';\nexport * from './tab.mjs';\n",
1260
+ 'tabs.mjs': 'export function AcmeTabs() { return null; }\n',
1261
+ 'tab.mjs': 'export function AcmeTab() { return null; }\n',
1262
+ };
1263
+ fs.writeFileSync(
1264
+ path.join(tmpDir, 'build.mjs'),
1265
+ `import fs from 'node:fs';\nconst output = ${JSON.stringify(output)};\nfs.mkdirSync('dist/tabs', {recursive: true});\nfor (const [file, text] of Object.entries(output)) {\n const target = 'dist/tabs/' + file;\n if (!fs.existsSync(target) || fs.readFileSync(target, 'utf-8') !== text) fs.writeFileSync(target, text);\n}\n`,
1266
+ );
1267
+ const dir = path.join(tmpDir, 'components', 'tabs');
1268
+ fs.mkdirSync(dir, {recursive: true});
1269
+ fs.writeFileSync(
1270
+ path.join(dir, 'AcmeTabs.tsx'),
1271
+ 'export function AcmeTabs() { return null; }\n',
1272
+ );
1273
+ fs.writeFileSync(
1274
+ path.join(dir, 'AcmeTab.tsx'),
1275
+ 'export function AcmeTab() { return null; }\n',
1276
+ );
1277
+ fs.writeFileSync(
1278
+ path.join(dir, 'AcmeTabs.doc.mjs'),
1279
+ "export default {type: 'component', name: 'AcmeTabs', import: '@acme/widgets/tabs', description: 'x', components: [{name: 'AcmeTabs', description: 'x', props: []}, {name: 'AcmeTab', description: 'x', props: []}]};\n",
1280
+ );
1281
+ if (start === 'an incremental build') {
1282
+ // An earlier build from before AcmeTab existed: the file the import
1283
+ // names is already current, so the build leaves it untouched.
1284
+ fs.mkdirSync(path.join(tmpDir, 'dist', 'tabs'), {recursive: true});
1285
+ for (const [file, text] of Object.entries({...output, 'tab.mjs': ''})) {
1286
+ fs.writeFileSync(path.join(tmpDir, 'dist', 'tabs', file), text);
1287
+ }
1288
+ }
1289
+
1290
+ const result = await integrationPackCheck({cwd: tmpDir});
1291
+
1292
+ expect(result.data.issues).toEqual([]);
1293
+ expect(result.data.packable).toBe(true);
1294
+ expect(result.data.contributions.packed.components).toEqual([
1295
+ 'AcmeTab',
1296
+ 'AcmeTabs',
1297
+ ]);
1298
+ expect(result.data.contributions.local.components).toEqual(
1299
+ result.data.contributions.packed.components,
1300
+ );
1301
+ },
1302
+ );
1303
+
1304
+ it('passes a member another folder declares, which has no source', async () => {
1305
+ fs.writeFileSync(
1306
+ path.join(tmpDir, 'package.json'),
1307
+ `${JSON.stringify({
1308
+ name: '@acme/widgets',
1309
+ version: '1.0.0',
1310
+ files: ['astryx.integration.mjs', 'components'],
1311
+ exports: {'./menu': './components/menu/index.ts'},
1312
+ })}\n`,
1313
+ );
1314
+ fs.writeFileSync(
1315
+ path.join(tmpDir, 'astryx.integration.mjs'),
1316
+ "export default {components: './components'};\n",
1317
+ );
1318
+ const menu = path.join(tmpDir, 'components', 'menu');
1319
+ const shared = path.join(tmpDir, 'components', 'shared');
1320
+ fs.mkdirSync(menu, {recursive: true});
1321
+ fs.mkdirSync(shared, {recursive: true});
1322
+ fs.writeFileSync(
1323
+ path.join(menu, 'AcmeMenu.doc.mjs'),
1324
+ "export default {type: 'component', name: 'AcmeMenu', import: '@acme/widgets/menu', description: 'x', components: [{name: 'AcmeMenuItem', description: 'x', props: []}]};\n",
1325
+ );
1326
+ fs.writeFileSync(
1327
+ path.join(menu, 'AcmeMenu.tsx'),
1328
+ 'export function AcmeMenu() { return null; }\n',
1329
+ );
1330
+ fs.writeFileSync(
1331
+ path.join(menu, 'index.ts'),
1332
+ "export {AcmeMenu} from './AcmeMenu';\nexport {AcmeMenuItem} from '../shared/AcmeMenuItem';\n",
1333
+ );
1334
+ fs.writeFileSync(
1335
+ path.join(shared, 'AcmeMenuItem.tsx'),
1336
+ 'export function AcmeMenuItem() { return null; }\n',
1337
+ );
1338
+
1339
+ const result = await integrationPackCheck({cwd: tmpDir});
1340
+
1341
+ expect(result.data.issues).toEqual([]);
1342
+ expect(result.data.packable).toBe(true);
1343
+ expect(result.data.contributions.packed.components).toEqual([
1344
+ 'AcmeMenu',
1345
+ 'AcmeMenuItem',
1346
+ ]);
1347
+ });
1348
+
1349
+ it('reports a family doc the loader rejects once, not once per member', async () => {
1350
+ fs.writeFileSync(
1351
+ path.join(tmpDir, 'package.json'),
1352
+ `${JSON.stringify({
1353
+ name: '@acme/widgets',
1354
+ version: '1.0.0',
1355
+ files: ['astryx.integration.mjs', 'components'],
1356
+ })}\n`,
1357
+ );
1358
+ fs.writeFileSync(
1359
+ path.join(tmpDir, 'astryx.integration.mjs'),
1360
+ "export default {components: './components'};\n",
1361
+ );
1362
+ fs.mkdirSync(path.join(tmpDir, 'components'));
1363
+ fs.writeFileSync(
1364
+ path.join(tmpDir, 'components', 'AcmeTabs.tsx'),
1365
+ 'export function AcmeTabs() { return null; }\nexport function AcmeTab() { return null; }\nexport function AcmeTabPanel() { return null; }\n',
1366
+ );
1367
+ // A displayName that is not a string makes the loader reject the doc.
1368
+ fs.writeFileSync(
1369
+ path.join(tmpDir, 'components', 'AcmeTabs.doc.mjs'),
1370
+ "export default {type: 'component', name: 'AcmeTabs', displayName: 1, description: 'x', components: [{name: 'AcmeTab', description: 'x', props: []}, {name: 'AcmeTabPanel', description: 'x', props: []}]};\n",
1371
+ );
1372
+
1373
+ const result = await integrationPackCheck({cwd: tmpDir});
1374
+ const codes = result.data.issues.map(issue => issue.code);
1375
+
1376
+ expect(codes).toContain('component_doc_unloadable');
1377
+ expect(codes.length).toBe(new Set(codes).size);
1378
+ });
1379
+
757
1380
  it('passes a generated component through its exact packed export', async () => {
758
1381
  fs.writeFileSync(
759
1382
  path.join(tmpDir, 'package.json'),
@@ -811,6 +1434,86 @@ describe('integrationPackCheck', () => {
811
1434
  );
812
1435
  });
813
1436
 
1437
+ it('does not count a type-only re-export as the component', async () => {
1438
+ fs.writeFileSync(
1439
+ path.join(tmpDir, 'package.json'),
1440
+ `${JSON.stringify({
1441
+ name: '@acme/widgets',
1442
+ version: '1.0.0',
1443
+ files: ['astryx.integration.mjs', 'src'],
1444
+ exports: {'.': './src/index.ts'},
1445
+ })}\n`,
1446
+ );
1447
+ fs.writeFileSync(
1448
+ path.join(tmpDir, 'astryx.integration.mjs'),
1449
+ "export default {components: './src'};\n",
1450
+ );
1451
+ fs.mkdirSync(path.join(tmpDir, 'src'));
1452
+ fs.writeFileSync(
1453
+ path.join(tmpDir, 'src', 'index.ts'),
1454
+ "export type {AcmeWidget} from './AcmeWidget';\n",
1455
+ );
1456
+ fs.writeFileSync(
1457
+ path.join(tmpDir, 'src', 'AcmeWidget.tsx'),
1458
+ 'export function AcmeWidget() { return null; }\n',
1459
+ );
1460
+ fs.writeFileSync(
1461
+ path.join(tmpDir, 'src', 'AcmeWidget.doc.mjs'),
1462
+ "export default {type: 'component', name: 'AcmeWidget', import: '@acme/widgets', description: 'Widget.', props: []};\n",
1463
+ );
1464
+
1465
+ const result = await integrationPackCheck({cwd: tmpDir});
1466
+
1467
+ expect(result.data.issues).toContainEqual(
1468
+ expect.objectContaining({code: 'component_export_missing'}),
1469
+ );
1470
+ });
1471
+
1472
+ it('follows a re-export of a folder to its index file', async () => {
1473
+ // `./primitives` names a folder: TypeScript and bundlers load its index file.
1474
+ fs.writeFileSync(
1475
+ path.join(tmpDir, 'package.json'),
1476
+ `${JSON.stringify({
1477
+ name: '@acme/widgets',
1478
+ version: '1.0.0',
1479
+ files: ['astryx.integration.mjs', 'src'],
1480
+ exports: {'.': './src/index.ts'},
1481
+ })}\n`,
1482
+ );
1483
+ fs.writeFileSync(
1484
+ path.join(tmpDir, 'astryx.integration.mjs'),
1485
+ "export default {components: './src'};\n",
1486
+ );
1487
+ const primitives = path.join(tmpDir, 'src', 'primitives');
1488
+ fs.mkdirSync(primitives, {recursive: true});
1489
+ fs.writeFileSync(
1490
+ path.join(tmpDir, 'src', 'index.ts'),
1491
+ "export * from './primitives';\n",
1492
+ );
1493
+ fs.writeFileSync(
1494
+ path.join(primitives, 'index.ts'),
1495
+ "export * from './AcmeWidget';\n",
1496
+ );
1497
+ fs.writeFileSync(
1498
+ path.join(primitives, 'AcmeWidget.tsx'),
1499
+ 'export function AcmeWidget() { return null; }\n',
1500
+ );
1501
+ fs.writeFileSync(
1502
+ path.join(primitives, 'AcmeWidget.doc.mjs'),
1503
+ "export default {type: 'component', name: 'AcmeWidget', import: '@acme/widgets', description: 'Widget.', props: []};\n",
1504
+ );
1505
+
1506
+ const result = await integrationPackCheck({cwd: tmpDir});
1507
+
1508
+ expect(result.data.issues).not.toContainEqual(
1509
+ expect.objectContaining({code: 'component_export_missing'}),
1510
+ );
1511
+ expect(
1512
+ result.data.packable,
1513
+ JSON.stringify(result.data.issues, null, 2),
1514
+ ).toBe(true);
1515
+ });
1516
+
814
1517
  it('fails when packed template source is hidden by package exports', async () => {
815
1518
  writePackage({
816
1519
  manifest: "export default {templates: './templates'};\n",
@@ -1199,6 +1902,23 @@ describe('integrationPackCheck', () => {
1199
1902
  expect(sourceWarnings[0].message).toContain('Card.tsx');
1200
1903
  });
1201
1904
 
1905
+ it('reports template sources without metadata as warnings', async () => {
1906
+ writePackage({
1907
+ manifest:
1908
+ "export default {themes: './themes', templates: './templates'};\n",
1909
+ files: ['astryx.integration.mjs', 'themes', 'templates'],
1910
+ templates: true,
1911
+ });
1912
+ const result = await integrationPackCheck({cwd: tmpDir});
1913
+
1914
+ const sourceWarnings = result.data.issues.filter(
1915
+ issue => issue.code === 'source_without_template_doc',
1916
+ );
1917
+ expect(sourceWarnings).toHaveLength(1);
1918
+ expect(sourceWarnings[0].severity).toBe('warning');
1919
+ expect(sourceWarnings[0].message).toContain('stray.tsx');
1920
+ });
1921
+
1202
1922
  it('cleans up its scratch consumer when npm pack fails', async () => {
1203
1923
  writePackage({
1204
1924
  files: ['astryx.integration.mjs', 'themes'],