handoff-app 1.2.2-7 → 2.0.0-0

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 (349) hide show
  1. package/Changelog.md +37 -15
  2. package/README.md +372 -118
  3. package/dist/app-builder/build.d.ts +41 -2
  4. package/dist/app-builder/build.js +455 -18
  5. package/dist/app-builder/client-config.d.ts +15 -2
  6. package/dist/app-builder/client-config.js +43 -4
  7. package/dist/app-builder/config-diff/snapshot.d.ts +1 -6
  8. package/dist/app-builder/config-diff/snapshot.js +2 -9
  9. package/dist/app-builder/index.d.ts +1 -1
  10. package/dist/app-builder/index.js +3 -1
  11. package/dist/app-builder/nav-shell.d.ts +23 -0
  12. package/dist/app-builder/nav-shell.js +80 -0
  13. package/dist/app-builder/static-export.d.ts +17 -0
  14. package/dist/app-builder/static-export.js +181 -0
  15. package/dist/app-builder/vercel-output.d.ts +39 -0
  16. package/dist/app-builder/vercel-output.js +259 -0
  17. package/dist/app-builder/watchers/utils.d.ts +2 -0
  18. package/dist/app-builder/watchers.d.ts +14 -4
  19. package/dist/app-builder/watchers.js +123 -28
  20. package/dist/app-builder/websocket.js +1 -1
  21. package/dist/artifacts/index.d.ts +2 -0
  22. package/dist/artifacts/index.js +13 -0
  23. package/dist/artifacts/types.d.ts +136 -0
  24. package/dist/artifacts/types.js +24 -0
  25. package/dist/artifacts/url.d.ts +49 -0
  26. package/dist/artifacts/url.js +86 -0
  27. package/dist/cache/build-cache.js +48 -4
  28. package/dist/cli.js +0 -0
  29. package/dist/commands/build/app.js +1 -2
  30. package/dist/commands/build/index.d.ts +17 -0
  31. package/dist/commands/build/index.js +66 -0
  32. package/dist/commands/checkout/index.d.ts +24 -0
  33. package/dist/commands/checkout/index.js +71 -0
  34. package/dist/commands/db/migrate.d.ts +11 -0
  35. package/dist/commands/db/migrate.js +33 -0
  36. package/dist/commands/index.js +8 -0
  37. package/dist/commands/publish/index.d.ts +21 -0
  38. package/dist/commands/publish/index.js +68 -0
  39. package/dist/config/defaults.js +15 -1
  40. package/dist/config/loader.js +6 -1
  41. package/dist/config/normalizers/declaration.js +4 -1
  42. package/dist/config/normalizers/page.d.ts +18 -0
  43. package/dist/config/normalizers/page.js +34 -0
  44. package/dist/config/normalizers/pattern.js +7 -1
  45. package/dist/config/runtime.d.ts +4 -0
  46. package/dist/config/runtime.js +53 -5
  47. package/dist/index.d.ts +65 -1
  48. package/dist/index.js +123 -3
  49. package/dist/nav/index.d.ts +83 -0
  50. package/dist/nav/index.js +9 -0
  51. package/dist/nav/registry.d.ts +15 -0
  52. package/dist/nav/registry.js +42 -0
  53. package/dist/nav/resolver.d.ts +8 -0
  54. package/dist/nav/resolver.js +192 -0
  55. package/dist/nav/workspace.d.ts +18 -0
  56. package/dist/nav/workspace.js +103 -0
  57. package/dist/pipeline/styles.js +7 -35
  58. package/dist/pipeline/token-transformers.d.ts +45 -0
  59. package/dist/pipeline/token-transformers.js +71 -0
  60. package/dist/registry/asset-storage/adapters/vercel-blob.d.ts +17 -0
  61. package/dist/registry/asset-storage/adapters/vercel-blob.js +87 -0
  62. package/dist/registry/asset-storage/define.d.ts +10 -0
  63. package/dist/registry/asset-storage/define.js +13 -0
  64. package/dist/registry/asset-storage/resolve.d.ts +45 -0
  65. package/dist/registry/asset-storage/resolve.js +36 -0
  66. package/dist/registry/asset-storage/types.d.ts +51 -0
  67. package/dist/registry/asset-storage/types.js +9 -0
  68. package/dist/registry/assets/layout.d.ts +22 -0
  69. package/dist/registry/assets/layout.js +30 -0
  70. package/dist/registry/assets/sets.d.ts +18 -0
  71. package/dist/registry/assets/sets.js +44 -0
  72. package/dist/registry/assets/transfer.d.ts +45 -0
  73. package/dist/registry/assets/transfer.js +11 -0
  74. package/dist/registry/checkout/assets.d.ts +16 -0
  75. package/dist/registry/checkout/assets.js +150 -0
  76. package/dist/registry/checkout/index.d.ts +33 -0
  77. package/dist/registry/checkout/index.js +610 -0
  78. package/dist/registry/checkout/tokens.d.ts +15 -0
  79. package/dist/registry/checkout/tokens.js +171 -0
  80. package/dist/registry/client.d.ts +61 -0
  81. package/dist/registry/client.js +211 -0
  82. package/dist/registry/connection.d.ts +30 -0
  83. package/dist/registry/connection.js +36 -0
  84. package/dist/registry/db/adapter.d.ts +43 -0
  85. package/dist/registry/db/adapter.js +59 -0
  86. package/dist/registry/db/client.d.ts +35 -0
  87. package/dist/registry/db/client.js +94 -0
  88. package/dist/registry/db/index.d.ts +5 -0
  89. package/dist/registry/db/index.js +24 -0
  90. package/dist/registry/db/migrate.d.ts +23 -0
  91. package/dist/registry/db/migrate.js +64 -0
  92. package/dist/registry/db/schema.d.ts +4869 -0
  93. package/dist/registry/db/schema.js +347 -0
  94. package/dist/registry/path.d.ts +4 -0
  95. package/dist/registry/path.js +31 -0
  96. package/dist/registry/publish/artifacts.d.ts +16 -0
  97. package/dist/registry/publish/artifacts.js +132 -0
  98. package/dist/registry/publish/assets.d.ts +16 -0
  99. package/dist/registry/publish/assets.js +129 -0
  100. package/dist/registry/publish/errors.d.ts +5 -0
  101. package/dist/registry/publish/errors.js +26 -0
  102. package/dist/registry/publish/index.d.ts +27 -0
  103. package/dist/registry/publish/index.js +126 -0
  104. package/dist/registry/publish/package.d.ts +27 -0
  105. package/dist/registry/publish/package.js +257 -0
  106. package/dist/registry/publish/publish-build.d.ts +12 -0
  107. package/dist/registry/publish/publish-build.js +43 -0
  108. package/dist/registry/publish/tokens.d.ts +18 -0
  109. package/dist/registry/publish/tokens.js +112 -0
  110. package/dist/registry/tokens/sets.d.ts +59 -0
  111. package/dist/registry/tokens/sets.js +109 -0
  112. package/dist/registry/tokens/transfer.d.ts +48 -0
  113. package/dist/registry/tokens/transfer.js +10 -0
  114. package/dist/registry/transfer.d.ts +75 -0
  115. package/dist/registry/transfer.js +11 -0
  116. package/dist/store/filesystem-assets.d.ts +17 -0
  117. package/dist/store/filesystem-assets.js +147 -0
  118. package/dist/store/filesystem.d.ts +76 -0
  119. package/dist/store/filesystem.js +208 -0
  120. package/dist/store/index.d.ts +20 -0
  121. package/dist/store/index.js +38 -0
  122. package/dist/store/registry.d.ts +77 -0
  123. package/dist/store/registry.js +350 -0
  124. package/dist/store/source-files.d.ts +24 -0
  125. package/dist/store/source-files.js +147 -0
  126. package/dist/store/types.d.ts +185 -0
  127. package/dist/store/types.js +12 -0
  128. package/dist/transformers/plugins/csf-render.js +17 -8
  129. package/dist/transformers/plugins/handlebars-previews.js +10 -3
  130. package/dist/transformers/plugins/ssr-render.js +116 -49
  131. package/dist/transformers/preview/component/api.js +3 -1
  132. package/dist/transformers/preview/component/artifacts.js +8 -1
  133. package/dist/transformers/preview/component/builder.js +13 -5
  134. package/dist/transformers/preview/component/css.js +9 -1
  135. package/dist/transformers/preview/component/javascript.d.ts +9 -3
  136. package/dist/transformers/preview/component/javascript.js +45 -17
  137. package/dist/transformers/preview/component/shared-artifacts.d.ts +83 -0
  138. package/dist/transformers/preview/component/shared-artifacts.js +92 -0
  139. package/dist/transformers/preview/component.js +5 -2
  140. package/dist/transformers/preview/pattern/builder.js +28 -9
  141. package/dist/transformers/preview/pattern/html.d.ts +13 -10
  142. package/dist/transformers/preview/pattern/html.js +62 -27
  143. package/dist/transformers/preview/types.d.ts +38 -0
  144. package/dist/transformers/utils/handlebars.d.ts +3 -0
  145. package/dist/transformers/utils/handlebars.js +19 -5
  146. package/dist/types/config.d.ts +118 -25
  147. package/dist/utils/menu-shell.d.ts +60 -0
  148. package/dist/utils/menu-shell.js +221 -0
  149. package/dist/utils/stable-stringify.d.ts +2 -0
  150. package/dist/utils/stable-stringify.js +8 -0
  151. package/docs/cli.md +4 -2
  152. package/drizzle/0000_init_registry_storage.sql +95 -0
  153. package/drizzle/0001_add_review_metadata.sql +2 -0
  154. package/drizzle/0002_add_pages.sql +28 -0
  155. package/drizzle/0003_add_tokens.sql +29 -0
  156. package/drizzle/0004_add_assets.sql +37 -0
  157. package/drizzle/meta/0000_snapshot.json +642 -0
  158. package/drizzle/meta/0001_snapshot.json +654 -0
  159. package/drizzle/meta/0002_snapshot.json +835 -0
  160. package/drizzle/meta/0003_snapshot.json +1023 -0
  161. package/drizzle/meta/0004_snapshot.json +1252 -0
  162. package/drizzle/meta/_journal.json +41 -0
  163. package/drizzle.config.ts +16 -0
  164. package/package.json +10 -1
  165. package/src/app/components/Component/Preview.tsx +3 -2
  166. package/src/app/components/Component/RegistryActions.tsx +150 -0
  167. package/src/app/components/Foundations/DisplayLogo.tsx +5 -3
  168. package/src/app/components/Layout/Header.tsx +21 -3
  169. package/src/app/components/Layout/Main.tsx +11 -3
  170. package/src/app/components/Navigation/MainNav.tsx +4 -2
  171. package/src/app/components/Navigation/MobileNav.tsx +4 -2
  172. package/src/app/components/Navigation/SideNav.tsx +46 -11
  173. package/src/app/components/NotFound.tsx +24 -0
  174. package/src/app/components/context/NavProvider.tsx +71 -0
  175. package/src/app/components/old/Header.tsx +3 -3
  176. package/src/app/components/util/index.ts +203 -380
  177. package/src/app/components/util/useCollectionAssets.ts +125 -0
  178. package/src/app/components/util/useFoundationTokens.ts +67 -0
  179. package/src/app/generated/default-pages.json +1 -0
  180. package/src/app/generated/nav-shell.json +1 -0
  181. package/src/app/lib/api/query.ts +6 -0
  182. package/src/app/lib/api/redact.ts +46 -0
  183. package/src/app/lib/asset-storage.ts +81 -0
  184. package/src/app/lib/docs-api/artifacts.ts +116 -0
  185. package/src/app/lib/docs-api/backend.ts +151 -0
  186. package/src/app/lib/docs-api/errors.ts +52 -0
  187. package/src/app/lib/docs-api/handler.ts +34 -0
  188. package/src/app/lib/docs-api/index.ts +7 -0
  189. package/src/app/lib/docs-api/records.ts +189 -0
  190. package/src/app/lib/docs-api/registry.ts +130 -0
  191. package/src/app/lib/docs-api/runtime-config.ts +173 -0
  192. package/src/app/lib/docs-api/serve.ts +37 -0
  193. package/src/app/lib/docs-api/token-detail.ts +19 -0
  194. package/src/app/lib/registry-api/allowlist.ts +163 -0
  195. package/src/app/lib/registry-api/asset-transfer.ts +363 -0
  196. package/src/app/lib/registry-api/auth.ts +68 -0
  197. package/src/app/lib/registry-api/entity-routes.ts +197 -0
  198. package/src/app/lib/registry-api/errors.ts +51 -0
  199. package/src/app/lib/registry-api/files.ts +135 -0
  200. package/src/app/lib/registry-api/handler.ts +96 -0
  201. package/src/app/lib/registry-api/index.ts +4 -0
  202. package/src/app/lib/registry-api/meta.ts +87 -0
  203. package/src/app/lib/registry-api/revalidate.ts +36 -0
  204. package/src/app/lib/registry-api/store.ts +348 -0
  205. package/src/app/lib/registry-api/token-transfer.ts +231 -0
  206. package/src/app/lib/registry-api/transfer.ts +513 -0
  207. package/src/app/lib/registry-api/validation.ts +74 -0
  208. package/src/app/lib/registry-connection.ts +58 -0
  209. package/src/app/lib/utils.ts +14 -0
  210. package/src/app/next.config.mjs +72 -13
  211. package/src/app/pages/[...slug]/index.tsx +74 -20
  212. package/src/app/pages/_app.tsx +5 -2
  213. package/src/app/pages/api/docs/artifacts/[...artifactPath].ts +16 -0
  214. package/src/app/pages/api/docs/assets/[collection]/[...path].ts +50 -0
  215. package/src/app/pages/api/docs/assets/[collection]/index.ts +22 -0
  216. package/src/app/pages/api/docs/components/[id]/assets/[asset].ts +33 -0
  217. package/src/app/pages/api/docs/components/[id]/inspect/[previewId].ts +20 -0
  218. package/src/app/pages/api/docs/components/[id]/previews/[previewId].ts +21 -0
  219. package/src/app/pages/api/docs/components/[id].ts +24 -0
  220. package/src/app/pages/api/docs/components.json.ts +12 -0
  221. package/src/app/pages/api/docs/nav.json.ts +48 -0
  222. package/src/app/pages/api/docs/patterns/[id]/preview.ts +19 -0
  223. package/src/app/pages/api/docs/patterns/[id].ts +24 -0
  224. package/src/app/pages/api/docs/patterns.json.ts +12 -0
  225. package/src/app/pages/api/docs/tokens/components/[id].ts +30 -0
  226. package/src/app/pages/api/docs/tokens/foundations/[type].ts +32 -0
  227. package/src/app/pages/api/registry/components/[id]/files/[...filePath].ts +19 -0
  228. package/src/app/pages/api/registry/components/[id]/files/index.ts +19 -0
  229. package/src/app/pages/api/registry/components/[id].ts +20 -0
  230. package/src/app/pages/api/registry/components/index.ts +19 -0
  231. package/src/app/pages/api/registry/health.ts +20 -0
  232. package/src/app/pages/api/registry/patterns/[id]/files/[...filePath].ts +19 -0
  233. package/src/app/pages/api/registry/patterns/[id]/files/index.ts +19 -0
  234. package/src/app/pages/api/registry/patterns/[id].ts +20 -0
  235. package/src/app/pages/api/registry/patterns/index.ts +19 -0
  236. package/src/app/pages/api/registry/transfer/[kind]/[id].ts +39 -0
  237. package/src/app/pages/api/registry/transfer/assets/[collection].ts +24 -0
  238. package/src/app/pages/api/registry/transfer/assets/blobs/[hash].ts +23 -0
  239. package/src/app/pages/api/registry/transfer/assets/blobs/have.ts +11 -0
  240. package/src/app/pages/api/registry/transfer/assets/index.ts +10 -0
  241. package/src/app/pages/api/registry/transfer/tokens/[...setId].ts +26 -0
  242. package/src/app/pages/api/registry/transfer/tokens/index.ts +11 -0
  243. package/src/app/pages/assets/fonts.tsx +41 -17
  244. package/src/app/pages/assets/index.tsx +2 -2
  245. package/src/app/pages/assets/logos.tsx +2 -2
  246. package/src/app/pages/foundations/colors.tsx +18 -8
  247. package/src/app/pages/foundations/effects.tsx +18 -8
  248. package/src/app/pages/foundations/icons/[name]/index.tsx +26 -14
  249. package/src/app/pages/foundations/icons/index.tsx +13 -9
  250. package/src/app/pages/foundations/index.tsx +1 -1
  251. package/src/app/pages/foundations/logo.tsx +7 -4
  252. package/src/app/pages/foundations/typography.tsx +19 -9
  253. package/src/app/pages/index.tsx +1 -1
  254. package/src/app/pages/system/component/[component]/index.tsx +87 -17
  255. package/src/app/pages/system/index.tsx +13 -4
  256. package/src/app/pages/system/pattern/[pattern]/index.tsx +70 -14
  257. package/src/app/pages/system/tokens/components/[component]/index.tsx +42 -14
  258. package/src/app/pages/system/tokens/foundations/colors.tsx +7 -5
  259. package/src/app/pages/system/tokens/foundations/effects.tsx +7 -5
  260. package/src/app/pages/system/tokens/foundations/typography.tsx +8 -4
  261. package/src/app/tsconfig.json +2 -3
  262. package/src/app-builder/build.ts +555 -11
  263. package/src/app-builder/client-config.ts +61 -4
  264. package/src/app-builder/config-diff/snapshot.ts +1 -11
  265. package/src/app-builder/index.ts +1 -1
  266. package/src/app-builder/nav-shell.ts +74 -0
  267. package/src/app-builder/static-export.ts +215 -0
  268. package/src/app-builder/vercel-output.ts +297 -0
  269. package/src/app-builder/watchers/utils.ts +2 -0
  270. package/src/app-builder/watchers.ts +149 -25
  271. package/src/app-builder/websocket.ts +2 -2
  272. package/src/artifacts/README.md +14 -0
  273. package/src/artifacts/index.ts +17 -0
  274. package/src/artifacts/types.ts +146 -0
  275. package/src/artifacts/url.ts +90 -0
  276. package/src/cache/build-cache.ts +47 -2
  277. package/src/commands/build/app.ts +1 -2
  278. package/src/commands/build/index.ts +61 -0
  279. package/src/commands/checkout/index.ts +66 -0
  280. package/src/commands/db/migrate.ts +25 -0
  281. package/src/commands/index.ts +8 -0
  282. package/src/commands/publish/index.ts +63 -0
  283. package/src/config/defaults.ts +15 -0
  284. package/src/config/loader.ts +10 -1
  285. package/src/config/normalizers/declaration.ts +3 -0
  286. package/src/config/normalizers/page.ts +45 -0
  287. package/src/config/normalizers/pattern.ts +4 -1
  288. package/src/config/runtime.ts +48 -2
  289. package/src/index.ts +126 -3
  290. package/src/nav/index.ts +94 -0
  291. package/src/nav/registry.ts +54 -0
  292. package/src/nav/resolver.ts +217 -0
  293. package/src/nav/workspace.ts +87 -0
  294. package/src/pipeline/styles.ts +4 -35
  295. package/src/pipeline/token-transformers.ts +97 -0
  296. package/src/registry/asset-storage/adapters/vercel-blob.ts +51 -0
  297. package/src/registry/asset-storage/define.ts +15 -0
  298. package/src/registry/asset-storage/resolve.ts +72 -0
  299. package/src/registry/asset-storage/types.ts +46 -0
  300. package/src/registry/assets/layout.ts +38 -0
  301. package/src/registry/assets/sets.ts +48 -0
  302. package/src/registry/assets/transfer.ts +50 -0
  303. package/src/registry/checkout/assets.ts +126 -0
  304. package/src/registry/checkout/index.ts +596 -0
  305. package/src/registry/checkout/tokens.ts +152 -0
  306. package/src/registry/client.ts +234 -0
  307. package/src/registry/connection.ts +50 -0
  308. package/src/registry/db/adapter.ts +74 -0
  309. package/src/registry/db/client.ts +88 -0
  310. package/src/registry/db/index.ts +35 -0
  311. package/src/registry/db/migrate.ts +56 -0
  312. package/src/registry/db/schema.ts +444 -0
  313. package/src/registry/path.ts +26 -0
  314. package/src/registry/publish/artifacts.ts +147 -0
  315. package/src/registry/publish/assets.ts +129 -0
  316. package/src/registry/publish/errors.ts +34 -0
  317. package/src/registry/publish/index.ts +128 -0
  318. package/src/registry/publish/package.ts +272 -0
  319. package/src/registry/publish/publish-build.ts +51 -0
  320. package/src/registry/publish/tokens.ts +119 -0
  321. package/src/registry/tokens/sets.ts +132 -0
  322. package/src/registry/tokens/transfer.ts +53 -0
  323. package/src/registry/transfer.ts +82 -0
  324. package/src/store/filesystem-assets.ts +152 -0
  325. package/src/store/filesystem.ts +237 -0
  326. package/src/store/index.ts +62 -0
  327. package/src/store/registry.ts +334 -0
  328. package/src/store/source-files.ts +155 -0
  329. package/src/store/types.ts +210 -0
  330. package/src/transformers/plugins/csf-render.ts +31 -7
  331. package/src/transformers/plugins/handlebars-previews.ts +22 -3
  332. package/src/transformers/plugins/ssr-render.ts +145 -57
  333. package/src/transformers/preview/component/api.ts +2 -1
  334. package/src/transformers/preview/component/artifacts.ts +10 -1
  335. package/src/transformers/preview/component/builder.ts +9 -2
  336. package/src/transformers/preview/component/css.ts +10 -1
  337. package/src/transformers/preview/component/javascript.ts +55 -20
  338. package/src/transformers/preview/component/shared-artifacts.ts +146 -0
  339. package/src/transformers/preview/component.ts +5 -2
  340. package/src/transformers/preview/pattern/builder.ts +33 -7
  341. package/src/transformers/preview/pattern/html.ts +71 -27
  342. package/src/transformers/preview/types.ts +42 -0
  343. package/src/transformers/utils/handlebars.ts +31 -5
  344. package/src/types/config.ts +123 -25
  345. package/src/utils/menu-shell.ts +241 -0
  346. package/src/utils/stable-stringify.ts +7 -0
  347. package/templates/default/package.json.tpl +1 -1
  348. package/handoff.config.js +0 -90
  349. package/src/app/components/ComponentSearch.tsx +0 -93
@@ -165,6 +165,7 @@ const buildComponentCss = (data, handoff) => __awaiter(void 0, void 0, void 0, f
165
165
  const buildMainCss = (handoff) => __awaiter(void 0, void 0, void 0, function* () {
166
166
  var _a, _b, _c;
167
167
  const outputPath = (0, component_1.getComponentOutputPath)(handoff);
168
+ const mainCssPath = path_1.default.resolve(outputPath, exports.MAIN_COMPONENT_CSS_FILE);
168
169
  const runtimeConfig = (0, config_1.initRuntimeConfig)(handoff)[0];
169
170
  if (((_a = runtimeConfig === null || runtimeConfig === void 0 ? void 0 : runtimeConfig.entries) === null || _a === void 0 ? void 0 : _a.scss) && fs_extra_1.default.existsSync(runtimeConfig.entries.scss)) {
170
171
  const stat = yield fs_extra_1.default.stat(runtimeConfig.entries.scss);
@@ -172,6 +173,9 @@ const buildMainCss = (handoff) => __awaiter(void 0, void 0, void 0, function* ()
172
173
  if (entryPath === runtimeConfig.entries.scss || fs_extra_1.default.existsSync(entryPath)) {
173
174
  logger_1.Logger.info(`Building styles for global entry (${exports.MAIN_COMPONENT_CSS_FILE})…`);
174
175
  const startedAt = Date.now();
176
+ // Drop the previous artifact so a failed/empty rebuild cannot preserve stale global CSS that
177
+ // would still be referenced as current.
178
+ yield fs_extra_1.default.remove(mainCssPath);
175
179
  try {
176
180
  const loadPaths = [
177
181
  path_1.default.resolve(handoff.workingPath),
@@ -191,10 +195,14 @@ const buildMainCss = (handoff) => __awaiter(void 0, void 0, void 0, function* ()
191
195
  logger_1.Logger.info(`Finished building styles for global entry (${exports.MAIN_COMPONENT_CSS_FILE}) in ${(0, duration_1.formatDurationMs)(Date.now() - startedAt)}`);
192
196
  }
193
197
  catch (_d) {
194
- // buildCssBundle already logs failure for the entry path
198
+ // buildCssBundle already logs failure for the entry path; ensure no stale artifact remains.
199
+ yield fs_extra_1.default.remove(mainCssPath);
195
200
  }
196
201
  }
202
+ return;
197
203
  }
204
+ // No usable global SCSS entry: drop any stale artifact so it is not referenced as current.
205
+ yield fs_extra_1.default.remove(mainCssPath);
198
206
  });
199
207
  exports.buildMainCss = buildMainCss;
200
208
  exports.default = buildComponentCss;
@@ -12,10 +12,16 @@ export declare const MAIN_COMPONENT_JS_FILE = "main.js";
12
12
  */
13
13
  export declare const buildComponentJs: (data: TransformComponentTokensResult, handoff: Handoff) => Promise<TransformComponentTokensResult>;
14
14
  /**
15
- * Builds the main JavaScript bundle for the component preview.
15
+ * Builds the global preview script artifact (`component/main.js`) from the configured workspace JS
16
+ * entry.
16
17
  *
17
- * This function checks if there's a main JavaScript bundle defined in the runtime config,
18
- * and if the file exists, it builds the bundle and outputs it to the component's output path.
18
+ * The artifact is optional: when no global JS entry is configured, or the configured entry no longer
19
+ * exists, or the build fails/produces no output, any stale `main.js` is removed so a failed rebuild
20
+ * never leaves global JS that appears current. Generated HTML references `main.js` only when this
21
+ * function leaves a fresh artifact on disk, so an omitted artifact yields no dangling reference.
22
+ *
23
+ * Output uses the browser-compatible IIFE format so the artifact can load as a classic script ahead
24
+ * of dependent component scripts while preserving intentional top-level side effects.
19
25
  *
20
26
  * @param handoff - The Handoff configuration object containing build settings
21
27
  * @returns A Promise that resolves when the build process is complete
@@ -30,7 +30,7 @@ exports.MAIN_COMPONENT_JS_FILE = 'main.js';
30
30
  * @param options.outputPath - The directory where the bundle will be output
31
31
  * @param options.outputFilename - The name of the output file
32
32
  */
33
- const buildJsBundle = (_a, handoff_1) => __awaiter(void 0, [_a, handoff_1], void 0, function* ({ entry, outputPath, outputFilename }, handoff) {
33
+ const buildJsBundle = (_a, handoff_1) => __awaiter(void 0, [_a, handoff_1], void 0, function* ({ entry, outputPath, outputFilename, format = 'cjs', }, handoff) {
34
34
  var _b, _c, _d;
35
35
  const absEntryPath = path_1.default.resolve(entry);
36
36
  // Store the current NODE_ENV value before vite build
@@ -42,10 +42,12 @@ const buildJsBundle = (_a, handoff_1) => __awaiter(void 0, [_a, handoff_1], void
42
42
  let viteConfig = Object.assign(Object.assign({}, vite_config_1.default), { build: Object.assign(Object.assign({}, vite_config_1.default.build), { lib: {
43
43
  entry: absEntryPath,
44
44
  name: path_1.default.basename(outputFilename, '.js'),
45
- formats: ['cjs'],
45
+ formats: [format],
46
46
  fileName: () => outputFilename,
47
47
  }, rollupOptions: Object.assign(Object.assign({}, (_b = vite_config_1.default.build) === null || _b === void 0 ? void 0 : _b.rollupOptions), { output: {
48
- exports: 'named',
48
+ // The global IIFE bundle is frequently side-effect-only; `auto` lets a bundle with no
49
+ // exports emit cleanly while `named` preserves the existing component-JS (cjs) behavior.
50
+ exports: format === 'iife' ? 'auto' : 'named',
49
51
  } }), outDir: outputPath }) });
50
52
  if ((_d = (_c = handoff === null || handoff === void 0 ? void 0 : handoff.config) === null || _c === void 0 ? void 0 : _c.hooks) === null || _d === void 0 ? void 0 : _d.jsBuildConfig) {
51
53
  viteConfig = handoff.config.hooks.jsBuildConfig(viteConfig);
@@ -118,32 +120,58 @@ const buildComponentJs = (data, handoff) => __awaiter(void 0, void 0, void 0, fu
118
120
  });
119
121
  exports.buildComponentJs = buildComponentJs;
120
122
  /**
121
- * Builds the main JavaScript bundle for the component preview.
123
+ * Builds the global preview script artifact (`component/main.js`) from the configured workspace JS
124
+ * entry.
122
125
  *
123
- * This function checks if there's a main JavaScript bundle defined in the runtime config,
124
- * and if the file exists, it builds the bundle and outputs it to the component's output path.
126
+ * The artifact is optional: when no global JS entry is configured, or the configured entry no longer
127
+ * exists, or the build fails/produces no output, any stale `main.js` is removed so a failed rebuild
128
+ * never leaves global JS that appears current. Generated HTML references `main.js` only when this
129
+ * function leaves a fresh artifact on disk, so an omitted artifact yields no dangling reference.
130
+ *
131
+ * Output uses the browser-compatible IIFE format so the artifact can load as a classic script ahead
132
+ * of dependent component scripts while preserving intentional top-level side effects.
125
133
  *
126
134
  * @param handoff - The Handoff configuration object containing build settings
127
135
  * @returns A Promise that resolves when the build process is complete
128
136
  */
129
137
  const buildMainJS = (handoff) => __awaiter(void 0, void 0, void 0, function* () {
138
+ var _a;
130
139
  const outputPath = (0, component_1.getComponentOutputPath)(handoff);
140
+ const mainJsPath = path_1.default.resolve(outputPath, exports.MAIN_COMPONENT_JS_FILE);
131
141
  const runtimeConfig = (0, config_1.initRuntimeConfig)(handoff)[0];
132
- if (runtimeConfig && runtimeConfig.entries.js && fs_extra_1.default.existsSync(path_1.default.resolve(runtimeConfig.entries.js))) {
133
- logger_1.Logger.info(`Building script for global entry (${exports.MAIN_COMPONENT_JS_FILE})…`);
134
- const startedAt = Date.now();
135
- try {
136
- yield buildJsBundle({
137
- entry: runtimeConfig.entries.js,
138
- outputPath,
139
- outputFilename: exports.MAIN_COMPONENT_JS_FILE,
140
- }, handoff);
142
+ const entry = (_a = runtimeConfig === null || runtimeConfig === void 0 ? void 0 : runtimeConfig.entries) === null || _a === void 0 ? void 0 : _a.js;
143
+ // No usable global JS entry: drop any stale artifact and emit nothing to reference.
144
+ if (!entry || !fs_extra_1.default.existsSync(path_1.default.resolve(entry))) {
145
+ if (entry) {
146
+ logger_1.Logger.warn(`Global JS entry "${entry}" was not found; removing stale ${exports.MAIN_COMPONENT_JS_FILE}.`);
147
+ }
148
+ yield fs_extra_1.default.remove(mainJsPath);
149
+ return;
150
+ }
151
+ logger_1.Logger.info(`Building script for global entry (${exports.MAIN_COMPONENT_JS_FILE})…`);
152
+ const startedAt = Date.now();
153
+ // Remove the previous artifact before rebuilding so a failed or empty build cannot silently
154
+ // preserve stale global JS that would still be referenced as current.
155
+ yield fs_extra_1.default.remove(mainJsPath);
156
+ try {
157
+ yield buildJsBundle({
158
+ entry,
159
+ outputPath,
160
+ outputFilename: exports.MAIN_COMPONENT_JS_FILE,
161
+ format: 'iife',
162
+ }, handoff);
163
+ if (yield fs_extra_1.default.pathExists(mainJsPath)) {
141
164
  logger_1.Logger.info(`Finished building script for global entry (${exports.MAIN_COMPONENT_JS_FILE}) in ${(0, duration_1.formatDurationMs)(Date.now() - startedAt)}`);
142
165
  }
143
- catch (e) {
144
- logger_1.Logger.error(`Failed to build global script (${exports.MAIN_COMPONENT_JS_FILE}):`, e);
166
+ else {
167
+ logger_1.Logger.error(`Global script build produced no ${exports.MAIN_COMPONENT_JS_FILE} from "${entry}".`);
145
168
  }
146
169
  }
170
+ catch (e) {
171
+ logger_1.Logger.error(`Failed to build global script (${exports.MAIN_COMPONENT_JS_FILE}) from "${entry}":`, e);
172
+ // Ensure a partial/failed build leaves no stale or broken artifact behind.
173
+ yield fs_extra_1.default.remove(mainJsPath);
174
+ }
147
175
  });
148
176
  exports.buildMainJS = buildMainJS;
149
177
  exports.default = exports.buildComponentJs;
@@ -0,0 +1,83 @@
1
+ import { ArtifactReference } from '../../../artifacts';
2
+ import Handoff from '../../../index';
3
+ /**
4
+ * Centralized shared/global artifact model for generated preview HTML.
5
+ *
6
+ * The shared artifacts (`component/main.css`, `component/main.js`, `component/shared.css`) are
7
+ * optional: each is emitted only when a corresponding source entry is configured and builds
8
+ * successfully. Generated HTML must reference a shared artifact only when it actually exists, so a
9
+ * failed or absent global build never leaves a dangling reference the browser would request.
10
+ *
11
+ * This module is the single place that decides which shared artifacts are present and turns that
12
+ * decision into structured references and the `<link>`/`<script>` tags every renderer emits — so
13
+ * dependency resolution is driven by the artifact model, never by parsing HTML.
14
+ */
15
+ /** Which shared/global artifacts currently exist in the component output directory. */
16
+ export interface SharedArtifactPresence {
17
+ /** `component/main.js` — the global preview script built from the configured JS entry. */
18
+ mainJs: boolean;
19
+ /** `component/main.css` — the global stylesheet built from the configured SCSS entry. */
20
+ mainCss: boolean;
21
+ /** `component/shared.css` — shared component styles extracted by the SCSS split marker. */
22
+ sharedCss: boolean;
23
+ }
24
+ /**
25
+ * Resolve which shared/global artifacts are present on disk. Global artifacts are built before
26
+ * component/pattern HTML is generated, so an existence check here reflects whether the artifact can
27
+ * be referenced without producing a missing-file browser request.
28
+ */
29
+ export declare const resolveSharedArtifactPresence: (handoff: Handoff) => SharedArtifactPresence;
30
+ /** Options controlling which shared style references a renderer participates in. */
31
+ export interface SharedStyleOptions {
32
+ /**
33
+ * Whether the rendered component opts into the shared-styles split (`component/shared.css`).
34
+ * Only renderers that consume shared styles (Handlebars) set this; it is still gated on the
35
+ * artifact actually existing.
36
+ */
37
+ includeSharedStyles?: boolean;
38
+ }
39
+ /**
40
+ * Build the structured shared-artifact references a preview/inspect document depends on, filtered to
41
+ * the artifacts that actually exist. Shared/global artifacts are optional references (`required:
42
+ * false`) owned by no entity (`ownerKind: 'asset'`, `ownerId: null`). Stylesheet references precede
43
+ * the script reference so consumers preserve the documented load order.
44
+ */
45
+ export declare const getSharedArtifactReferences: (presence: SharedArtifactPresence, options?: SharedStyleOptions) => ArtifactReference[];
46
+ /**
47
+ * Render the `<link rel="stylesheet">` tags for the present shared stylesheet artifacts
48
+ * (`component/shared.css` then `component/main.css`), in load order. Returns the tags joined by a
49
+ * newline, or an empty string when no shared stylesheet exists.
50
+ */
51
+ export declare const renderSharedStyleLinks: (presence: SharedArtifactPresence, basePath: string, options?: SharedStyleOptions) => string;
52
+ /**
53
+ * Render the global preview script tag for `component/main.js` when present, or an empty string when
54
+ * absent. The global script is a classic (non-deferred) tag so it executes before dependent
55
+ * component-specific scripts and preserves intentional top-level side effects.
56
+ */
57
+ export declare const renderGlobalScriptTag: (presence: SharedArtifactPresence, basePath: string) => string;
58
+ /** Which of a component's own optional artifacts exist in the component output directory. */
59
+ export interface ComponentArtifactPresence {
60
+ /** `component/<id>.css` — built only when the component declares an SCSS/CSS entry. */
61
+ css: boolean;
62
+ /** `component/<id>.js` — built only when the component declares a JS entry. */
63
+ js: boolean;
64
+ }
65
+ /**
66
+ * Resolve which of a component's own css/js artifacts exist on disk. A component's styles and
67
+ * scripts are built (or removed when their entry is absent) before its preview HTML is generated, so
68
+ * an existence check here reflects whether the artifact can be referenced without producing a
69
+ * missing-file browser request. Like the shared artifacts, these references are optional.
70
+ */
71
+ export declare const resolveComponentArtifactPresence: (handoff: Handoff, componentId: string) => ComponentArtifactPresence;
72
+ /**
73
+ * Render the `<link rel="stylesheet">` tag for a component's own stylesheet (`component/<id>.css`)
74
+ * when it exists, or an empty string when the component declares no styles — so generated HTML never
75
+ * references an absent optional artifact.
76
+ */
77
+ export declare const renderComponentStyleLink: (presence: ComponentArtifactPresence, componentId: string, basePath: string) => string;
78
+ /**
79
+ * Render the `<script>` tag for a component's own script (`component/<id>.js`) when it exists, or an
80
+ * empty string when the component declares no script. The component script is a classic tag loaded
81
+ * after the global script so global side effects run first.
82
+ */
83
+ export declare const renderComponentScriptTag: (presence: ComponentArtifactPresence, componentId: string, basePath: string) => string;
@@ -0,0 +1,92 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.renderComponentScriptTag = exports.renderComponentStyleLink = exports.resolveComponentArtifactPresence = exports.renderGlobalScriptTag = exports.renderSharedStyleLinks = exports.getSharedArtifactReferences = exports.resolveSharedArtifactPresence = void 0;
7
+ const fs_extra_1 = __importDefault(require("fs-extra"));
8
+ const path_1 = __importDefault(require("path"));
9
+ const artifacts_1 = require("../../../artifacts");
10
+ const url_1 = require("../../../artifacts/url");
11
+ const component_1 = require("../component");
12
+ const css_1 = require("./css");
13
+ const javascript_1 = require("./javascript");
14
+ /**
15
+ * Resolve which shared/global artifacts are present on disk. Global artifacts are built before
16
+ * component/pattern HTML is generated, so an existence check here reflects whether the artifact can
17
+ * be referenced without producing a missing-file browser request.
18
+ */
19
+ const resolveSharedArtifactPresence = (handoff) => {
20
+ const outputPath = (0, component_1.getComponentOutputPath)(handoff);
21
+ return {
22
+ mainJs: fs_extra_1.default.existsSync(path_1.default.join(outputPath, javascript_1.MAIN_COMPONENT_JS_FILE)),
23
+ mainCss: fs_extra_1.default.existsSync(path_1.default.join(outputPath, css_1.MAIN_COMPONENT_CSS_FILE)),
24
+ sharedCss: fs_extra_1.default.existsSync(path_1.default.join(outputPath, css_1.SHARED_COMPONENT_CSS_FILE)),
25
+ };
26
+ };
27
+ exports.resolveSharedArtifactPresence = resolveSharedArtifactPresence;
28
+ /**
29
+ * Build the structured shared-artifact references a preview/inspect document depends on, filtered to
30
+ * the artifacts that actually exist. Shared/global artifacts are optional references (`required:
31
+ * false`) owned by no entity (`ownerKind: 'asset'`, `ownerId: null`). Stylesheet references precede
32
+ * the script reference so consumers preserve the documented load order.
33
+ */
34
+ const getSharedArtifactReferences = (presence, options) => {
35
+ const references = [];
36
+ if ((options === null || options === void 0 ? void 0 : options.includeSharedStyles) && presence.sharedCss) {
37
+ references.push({ path: artifacts_1.SHARED_STYLES_CSS_ARTIFACT_PATH, kind: 'shared', required: false, ownerKind: 'asset', ownerId: null });
38
+ }
39
+ if (presence.mainCss) {
40
+ references.push({ path: artifacts_1.SHARED_MAIN_CSS_ARTIFACT_PATH, kind: 'shared', required: false, ownerKind: 'asset', ownerId: null });
41
+ }
42
+ if (presence.mainJs) {
43
+ references.push({ path: artifacts_1.SHARED_MAIN_JS_ARTIFACT_PATH, kind: 'shared', required: false, ownerKind: 'asset', ownerId: null });
44
+ }
45
+ return references;
46
+ };
47
+ exports.getSharedArtifactReferences = getSharedArtifactReferences;
48
+ /**
49
+ * Render the `<link rel="stylesheet">` tags for the present shared stylesheet artifacts
50
+ * (`component/shared.css` then `component/main.css`), in load order. Returns the tags joined by a
51
+ * newline, or an empty string when no shared stylesheet exists.
52
+ */
53
+ const renderSharedStyleLinks = (presence, basePath, options) => (0, exports.getSharedArtifactReferences)(presence, options)
54
+ .filter((ref) => ref.path.endsWith('.css'))
55
+ .map((ref) => `<link rel="stylesheet" href="${(0, url_1.buildArtifactUrl)(ref.path, basePath)}">`)
56
+ .join('\n');
57
+ exports.renderSharedStyleLinks = renderSharedStyleLinks;
58
+ /**
59
+ * Render the global preview script tag for `component/main.js` when present, or an empty string when
60
+ * absent. The global script is a classic (non-deferred) tag so it executes before dependent
61
+ * component-specific scripts and preserves intentional top-level side effects.
62
+ */
63
+ const renderGlobalScriptTag = (presence, basePath) => presence.mainJs ? `<script src="${(0, url_1.buildArtifactUrl)(artifacts_1.SHARED_MAIN_JS_ARTIFACT_PATH, basePath)}"></script>` : '';
64
+ exports.renderGlobalScriptTag = renderGlobalScriptTag;
65
+ /**
66
+ * Resolve which of a component's own css/js artifacts exist on disk. A component's styles and
67
+ * scripts are built (or removed when their entry is absent) before its preview HTML is generated, so
68
+ * an existence check here reflects whether the artifact can be referenced without producing a
69
+ * missing-file browser request. Like the shared artifacts, these references are optional.
70
+ */
71
+ const resolveComponentArtifactPresence = (handoff, componentId) => {
72
+ const outputPath = (0, component_1.getComponentOutputPath)(handoff);
73
+ return {
74
+ css: fs_extra_1.default.existsSync(path_1.default.join(outputPath, `${componentId}.css`)),
75
+ js: fs_extra_1.default.existsSync(path_1.default.join(outputPath, `${componentId}.js`)),
76
+ };
77
+ };
78
+ exports.resolveComponentArtifactPresence = resolveComponentArtifactPresence;
79
+ /**
80
+ * Render the `<link rel="stylesheet">` tag for a component's own stylesheet (`component/<id>.css`)
81
+ * when it exists, or an empty string when the component declares no styles — so generated HTML never
82
+ * references an absent optional artifact.
83
+ */
84
+ const renderComponentStyleLink = (presence, componentId, basePath) => presence.css ? `<link rel="stylesheet" href="${(0, url_1.buildArtifactUrl)(`component/${componentId}.css`, basePath)}">` : '';
85
+ exports.renderComponentStyleLink = renderComponentStyleLink;
86
+ /**
87
+ * Render the `<script>` tag for a component's own script (`component/<id>.js`) when it exists, or an
88
+ * empty string when the component declares no script. The component script is a classic tag loaded
89
+ * after the global script so global side effects run first.
90
+ */
91
+ const renderComponentScriptTag = (presence, componentId, basePath) => presence.js ? `<script src="${(0, url_1.buildArtifactUrl)(`component/${componentId}.js`, basePath)}"></script>` : '';
92
+ exports.renderComponentScriptTag = renderComponentScriptTag;
@@ -43,9 +43,12 @@ exports.getComponentOutputPath = getComponentOutputPath;
43
43
  */
44
44
  function componentTransformer(handoff) {
45
45
  return __awaiter(this, void 0, void 0, function* () {
46
- const componentData = yield (0, builder_1.default)(handoff);
47
- yield (0, summary_1.default)(handoff, componentData);
46
+ // Build the shared/global artifacts first so component/pattern preview HTML can reference them
47
+ // only when they actually exist. A failed/absent global build therefore never produces a dangling
48
+ // reference in generated HTML.
48
49
  yield (0, javascript_1.buildMainJS)(handoff);
49
50
  yield (0, css_1.buildMainCss)(handoff);
51
+ const componentData = yield (0, builder_1.default)(handoff);
52
+ yield (0, summary_1.default)(handoff, componentData);
50
53
  });
51
54
  }
@@ -16,7 +16,9 @@ exports.getPatternIdsReferencingComponents = void 0;
16
16
  exports.processPatterns = processPatterns;
17
17
  const fs_extra_1 = __importDefault(require("fs-extra"));
18
18
  const path_1 = __importDefault(require("path"));
19
+ const url_1 = require("../../../artifacts/url");
19
20
  const logger_1 = require("../../../utils/logger");
21
+ const shared_artifacts_1 = require("../component/shared-artifacts");
20
22
  const api_1 = require("./api");
21
23
  const html_1 = require("./html");
22
24
  /**
@@ -39,7 +41,7 @@ const getPatternIdsReferencingComponents = (handoff, componentIds) => {
39
41
  return result;
40
42
  };
41
43
  exports.getPatternIdsReferencingComponents = getPatternIdsReferencingComponents;
42
- function buildPattern(handoff, patternId, pattern, componentOutputDir, basePath) {
44
+ function buildPattern(handoff, patternId, pattern, componentOutputDir, basePath, sharedArtifacts) {
43
45
  return __awaiter(this, void 0, void 0, function* () {
44
46
  const fragments = [];
45
47
  let hasErrors = false;
@@ -71,6 +73,14 @@ function buildPattern(handoff, patternId, pattern, componentOutputDir, basePath)
71
73
  const html = yield fs_extra_1.default.readFile(htmlFilePath, 'utf8');
72
74
  fragments.push({ componentId: ref.id, html });
73
75
  }
76
+ // Resolve component-owned artifact presence for each composed component so the pattern references
77
+ // only the stylesheets that actually exist (no dangling references for style-less components).
78
+ const componentArtifacts = new Map();
79
+ for (const fragment of fragments) {
80
+ if (!componentArtifacts.has(fragment.componentId)) {
81
+ componentArtifacts.set(fragment.componentId, (0, shared_artifacts_1.resolveComponentArtifactPresence)(handoff, fragment.componentId));
82
+ }
83
+ }
74
84
  if (fragments.length === 0) {
75
85
  logger_1.Logger.warn(`Pattern "${patternId}" produced no fragments. Skipping.`);
76
86
  return null;
@@ -78,9 +88,9 @@ function buildPattern(handoff, patternId, pattern, componentOutputDir, basePath)
78
88
  if (hasErrors) {
79
89
  logger_1.Logger.warn(`Pattern "${patternId}" has missing components but will be composed from available fragments.`);
80
90
  }
81
- const composedHtml = (0, html_1.composePatternHtml)(patternId, pattern.title, fragments, basePath);
91
+ const composedHtml = (0, html_1.composePatternHtml)(patternId, pattern.title, fragments, basePath, sharedArtifacts, componentArtifacts);
82
92
  const patternUrl = `${patternId}.html`;
83
- const patternData = Object.assign(Object.assign({}, pattern), { id: patternId, url: patternUrl, path: `${basePath}/api/pattern/${patternId}.json` });
93
+ const patternData = Object.assign(Object.assign({}, pattern), { id: patternId, url: patternUrl, path: (0, url_1.buildPatternDetailUrl)(patternId, basePath) });
84
94
  yield (0, api_1.writePatternHtml)(patternId, composedHtml, handoff);
85
95
  yield (0, api_1.writePatternApi)(patternId, patternData, handoff);
86
96
  logger_1.Logger.debug(`Built pattern: ${patternId} (${fragments.length} component(s))`);
@@ -94,8 +104,14 @@ function buildPattern(handoff, patternId, pattern, componentOutputDir, basePath)
94
104
  */
95
105
  function processPatterns(handoff, options) {
96
106
  return __awaiter(this, void 0, void 0, function* () {
97
- var _a, _b, _c, _d;
98
- const runtimePatterns = (_c = (_b = (_a = handoff.runtimeConfig) === null || _a === void 0 ? void 0 : _a.entries) === null || _b === void 0 ? void 0 : _b.patterns) !== null && _c !== void 0 ? _c : {};
107
+ var _a;
108
+ // Resolve the pattern set through the storage-agnostic store (v2). The filesystem store is a read
109
+ // view over `runtimeConfig.entries.patterns`, so these are the same records the build has always
110
+ // used — rebuilt into the id-keyed map the build logic below expects.
111
+ const runtimePatterns = {};
112
+ for (const pattern of yield handoff.store.patterns.list()) {
113
+ runtimePatterns[pattern.id] = pattern;
114
+ }
99
115
  const patternIds = Object.keys(runtimePatterns);
100
116
  const onlyPatternIds = options === null || options === void 0 ? void 0 : options.onlyPatternIds;
101
117
  const isPartial = onlyPatternIds !== undefined;
@@ -105,13 +121,16 @@ function processPatterns(handoff, options) {
105
121
  return [];
106
122
  }
107
123
  const componentOutputDir = path_1.default.resolve(handoff.workingPath, 'public/api/component');
108
- const basePath = (_d = process.env.HANDOFF_APP_BASE_PATH) !== null && _d !== void 0 ? _d : '';
124
+ const basePath = (_a = process.env.HANDOFF_APP_BASE_PATH) !== null && _a !== void 0 ? _a : '';
125
+ // Resolve the shared/global artifacts once so composed patterns reference `component/main.css` and
126
+ // `component/main.js` only when present (deduplicated to a single global script per page).
127
+ const sharedArtifacts = (0, shared_artifacts_1.resolveSharedArtifactPresence)(handoff);
109
128
  if (!isPartial) {
110
129
  logger_1.Logger.info(`Building ${patternIds.length} pattern(s)...`);
111
130
  const result = [];
112
131
  for (const patternId of patternIds) {
113
132
  const pattern = runtimePatterns[patternId];
114
- const built = yield buildPattern(handoff, patternId, pattern, componentOutputDir, basePath);
133
+ const built = yield buildPattern(handoff, patternId, pattern, componentOutputDir, basePath, sharedArtifacts);
115
134
  if (built) {
116
135
  result.push(built);
117
136
  }
@@ -135,7 +154,7 @@ function processPatterns(handoff, options) {
135
154
  for (const patternId of patternIds) {
136
155
  if (onlyPatternIds.has(patternId)) {
137
156
  const pattern = runtimePatterns[patternId];
138
- const built = yield buildPattern(handoff, patternId, pattern, componentOutputDir, basePath);
157
+ const built = yield buildPattern(handoff, patternId, pattern, componentOutputDir, basePath, sharedArtifacts);
139
158
  if (built) {
140
159
  existingById.set(patternId, built);
141
160
  }
@@ -145,7 +164,7 @@ function processPatterns(handoff, options) {
145
164
  }
146
165
  else if (!existingById.has(patternId)) {
147
166
  const pattern = runtimePatterns[patternId];
148
- const built = yield buildPattern(handoff, patternId, pattern, componentOutputDir, basePath);
167
+ const built = yield buildPattern(handoff, patternId, pattern, componentOutputDir, basePath, sharedArtifacts);
149
168
  if (built) {
150
169
  existingById.set(patternId, built);
151
170
  }
@@ -1,19 +1,22 @@
1
+ import type { ComponentArtifactPresence, SharedArtifactPresence } from '../component/shared-artifacts';
1
2
  /**
2
3
  * Composes multiple component preview HTML documents into a single-page HTML.
3
4
  *
4
- * Each component preview may be a React SSR document (with hydration scripts
5
- * and a `#root` / `#__APP_PROPS__` pair) or a static Handlebars document.
5
+ * Each component preview may be a React SSR document (server-rendered markup plus a `#root` /
6
+ * `#__APP_PROPS__` pair) or a static Handlebars document.
6
7
  *
7
- * This function namespaces the element ids per-fragment so multiple React
8
- * components can coexist and hydrate independently on the same page.
8
+ * React client/hydration bundles are component-owned artifacts (`component/<id>.client.js`). This
9
+ * composer copies no per-fragment bundle: it namespaces each fragment's root/props so multiple
10
+ * instances coexist, marks each root with the `data-handoff-component`/`data-handoff-props`
11
+ * attributes the component bundle hydrates by, and references each unique component's client artifact
12
+ * exactly once.
9
13
  *
10
- * Each hydration bundle gets its own `<script type="module">` tag for full
11
- * module-level isolation. This is critical because esbuild bundles React
12
- * into each script using module-scoped `var` declarations, and `hydrateRoot`
13
- * is asynchronous — sharing a single module scope would cause later bundles
14
- * to overwrite earlier React instances before hydration completes.
14
+ * Shared/global artifacts follow the shared-artifact model: `component/main.css` and
15
+ * `component/main.js` are referenced only when present, and the global script is emitted exactly once
16
+ * (deduplicated across fragments) so its top-level side effects run a single time, ahead of the
17
+ * per-component client bundles.
15
18
  */
16
19
  export declare const composePatternHtml: (_patternId: string, patternTitle: string, fragments: {
17
20
  componentId: string;
18
21
  html: string;
19
- }[], basePath: string) => string;
22
+ }[], basePath: string, sharedArtifacts: SharedArtifactPresence, componentArtifacts: Map<string, ComponentArtifactPresence>) => string;
@@ -6,33 +6,47 @@ Object.defineProperty(exports, "__esModule", { value: true });
6
6
  exports.composePatternHtml = void 0;
7
7
  const escape_1 = __importDefault(require("lodash/escape"));
8
8
  const node_html_parser_1 = require("node-html-parser");
9
+ const artifacts_1 = require("../../../artifacts");
10
+ const url_1 = require("../../../artifacts/url");
9
11
  /**
10
12
  * Composes multiple component preview HTML documents into a single-page HTML.
11
13
  *
12
- * Each component preview may be a React SSR document (with hydration scripts
13
- * and a `#root` / `#__APP_PROPS__` pair) or a static Handlebars document.
14
+ * Each component preview may be a React SSR document (server-rendered markup plus a `#root` /
15
+ * `#__APP_PROPS__` pair) or a static Handlebars document.
14
16
  *
15
- * This function namespaces the element ids per-fragment so multiple React
16
- * components can coexist and hydrate independently on the same page.
17
+ * React client/hydration bundles are component-owned artifacts (`component/<id>.client.js`). This
18
+ * composer copies no per-fragment bundle: it namespaces each fragment's root/props so multiple
19
+ * instances coexist, marks each root with the `data-handoff-component`/`data-handoff-props`
20
+ * attributes the component bundle hydrates by, and references each unique component's client artifact
21
+ * exactly once.
17
22
  *
18
- * Each hydration bundle gets its own `<script type="module">` tag for full
19
- * module-level isolation. This is critical because esbuild bundles React
20
- * into each script using module-scoped `var` declarations, and `hydrateRoot`
21
- * is asynchronous — sharing a single module scope would cause later bundles
22
- * to overwrite earlier React instances before hydration completes.
23
+ * Shared/global artifacts follow the shared-artifact model: `component/main.css` and
24
+ * `component/main.js` are referenced only when present, and the global script is emitted exactly once
25
+ * (deduplicated across fragments) so its top-level side effects run a single time, ahead of the
26
+ * per-component client bundles.
23
27
  */
24
- const composePatternHtml = (_patternId, patternTitle, fragments, basePath) => {
28
+ const composePatternHtml = (_patternId, patternTitle, fragments, basePath, sharedArtifacts, componentArtifacts) => {
29
+ var _a;
25
30
  const cssHrefs = new Set();
26
31
  const bodyParts = [];
27
- const scriptTags = [];
28
- cssHrefs.add(`${basePath}/api/component/main.css`);
32
+ const clientArtifactHrefs = new Set();
33
+ if (sharedArtifacts.mainCss) {
34
+ cssHrefs.add((0, url_1.buildArtifactUrl)(artifacts_1.SHARED_MAIN_CSS_ARTIFACT_PATH, basePath));
35
+ }
29
36
  cssHrefs.add(`${basePath}/assets/css/preview.css`);
37
+ // Canonical URL of the shared global script; used to strip any per-fragment reference so the
38
+ // global script is emitted (and executed) exactly once for the whole composed page.
39
+ const globalScriptUrl = (0, url_1.buildArtifactUrl)(artifacts_1.SHARED_MAIN_JS_ARTIFACT_PATH, basePath);
30
40
  const seenComponents = new Set();
31
41
  for (let i = 0; i < fragments.length; i++) {
32
42
  const { componentId, html } = fragments[i];
33
43
  if (!seenComponents.has(componentId)) {
34
44
  seenComponents.add(componentId);
35
- cssHrefs.add(`${basePath}/api/component/${componentId}.css`);
45
+ // Reference a component's stylesheet only when it actually exists, so a pattern never requests
46
+ // an absent optional artifact for a component that declares no styles.
47
+ if ((_a = componentArtifacts.get(componentId)) === null || _a === void 0 ? void 0 : _a.css) {
48
+ cssHrefs.add((0, url_1.buildArtifactUrl)(`component/${componentId}.css`, basePath));
49
+ }
36
50
  }
37
51
  const doc = (0, node_html_parser_1.parse)(html);
38
52
  const suffix = `_p${i}`;
@@ -40,28 +54,37 @@ const composePatternHtml = (_patternId, patternTitle, fragments, basePath) => {
40
54
  const namespacedPropsId = `__APP_PROPS__${suffix}`;
41
55
  // -- Extract props JSON (React SSR) ------------------------------------
42
56
  const propsScript = doc.querySelector('#__APP_PROPS__');
43
- let propsContent = '{}';
57
+ let propsContent = null;
44
58
  if (propsScript) {
45
59
  propsContent = propsScript.textContent || '{}';
46
60
  propsScript.remove();
47
61
  }
48
- // -- Extract and rewrite hydration scripts (React SSR) -----------------
49
- // Each script becomes its own <script type="module"> for full isolation.
50
- const moduleScripts = doc.querySelectorAll('script[type="module"]');
51
- for (const script of moduleScripts) {
52
- let code = script.textContent || '';
53
- if (code.trim()) {
54
- code = code
55
- .replace(/__APP_PROPS__/g, namespacedPropsId)
56
- .replace(/getElementById\(\s*["']root["']\s*\)/g, `getElementById("${namespacedRootId}")`);
57
- scriptTags.push(` <script type="module">\n${code}\n </script>`);
58
- }
62
+ // -- Drop the fragment's own client-artifact reference -----------------
63
+ // The bundle is referenced once per component for the whole page (below); a fragment must not
64
+ // carry its standalone-preview script tag into the composed document.
65
+ for (const script of doc.querySelectorAll('script[type="module"]')) {
59
66
  script.remove();
60
67
  }
68
+ // -- Drop the fragment's own global-script reference -------------------
69
+ // Static (Handlebars) fragments render the global `component/main.js` tag inside their body; left
70
+ // in place it would be inlined per fragment and run the global side effects multiple times. The
71
+ // page references the global script exactly once (below).
72
+ for (const script of doc.querySelectorAll('script')) {
73
+ if (script.getAttribute('src') === globalScriptUrl) {
74
+ script.remove();
75
+ }
76
+ }
61
77
  // -- Namespace the root element (React SSR) ----------------------------
78
+ // A React fragment exposes its mount point to the component bundle via stable data attributes.
62
79
  const rootEl = doc.querySelector('#root');
80
+ const isReactFragment = !!rootEl && propsContent !== null;
63
81
  if (rootEl) {
64
82
  rootEl.setAttribute('id', namespacedRootId);
83
+ if (isReactFragment) {
84
+ rootEl.setAttribute('data-handoff-component', componentId);
85
+ rootEl.setAttribute('data-handoff-props', namespacedPropsId);
86
+ clientArtifactHrefs.add((0, url_1.buildArtifactUrl)(`component/${componentId}.client.js`, basePath));
87
+ }
65
88
  }
66
89
  // -- Extract body content ----------------------------------------------
67
90
  const bodyEl = doc.querySelector('body');
@@ -70,14 +93,26 @@ const composePatternHtml = (_patternId, patternTitle, fragments, basePath) => {
70
93
  : rootEl
71
94
  ? rootEl.outerHTML
72
95
  : doc.innerHTML || '';
96
+ const propsScriptTag = propsContent !== null
97
+ ? ` <script id="${namespacedPropsId}" type="application/json">${propsContent}</script>\n`
98
+ : '';
73
99
  bodyParts.push(` <div class="handoff-pattern-block" data-component="${componentId}" data-fragment="${i}">\n` +
74
- ` <script id="${namespacedPropsId}" type="application/json">${propsContent}</script>\n` +
100
+ propsScriptTag +
75
101
  ` ${bodyContent}\n` +
76
102
  ` </div>`);
77
103
  }
78
104
  const linkTags = Array.from(cssHrefs)
79
105
  .map((href) => ` <link rel="stylesheet" href="${href}" />`)
80
106
  .join('\n');
107
+ // The global script is referenced once for the whole page (deduplicated across fragments) and
108
+ // placed before the per-component client bundles so its side effects run first and exactly once.
109
+ const globalScriptTag = sharedArtifacts.mainJs
110
+ ? ` <script src="${(0, url_1.buildArtifactUrl)(artifacts_1.SHARED_MAIN_JS_ARTIFACT_PATH, basePath)}"></script>`
111
+ : '';
112
+ const clientScriptTags = Array.from(clientArtifactHrefs)
113
+ .map((href) => ` <script type="module" src="${href}"></script>`)
114
+ .join('\n');
115
+ const scriptTags = [globalScriptTag, clientScriptTags].filter(Boolean).join('\n');
81
116
  return `<!DOCTYPE html>
82
117
  <html lang="en">
83
118
  <head>
@@ -87,7 +122,7 @@ ${linkTags}
87
122
  </head>
88
123
  <body>
89
124
  ${bodyParts.join('\n')}
90
- ${scriptTags.join('\n')}
125
+ ${scriptTags}
91
126
  </body>
92
127
  </html>`;
93
128
  };