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
@@ -0,0 +1,155 @@
1
+ /**
2
+ * First-class "related source files for an entity" accessor for the workspace store.
3
+ *
4
+ * Gathers an entity's declaration plus every `entries`-referenced source file for checkout and
5
+ * publish. This centralizes that logic so the store, and later transfer, share one definition of
6
+ * "what travels with an entity".
7
+ */
8
+
9
+ import fs from 'fs-extra';
10
+ import path from 'path';
11
+ import { resolveComponentDeclaration } from '../config/runtime';
12
+ import type { ComponentListObject, PatternListObject } from '../transformers/preview/types';
13
+ import { normalizePathForCompare } from '../utils/path';
14
+ import type { RegistryTextFileResource, TextFileKind, TextFileResource } from './types';
15
+
16
+ /** A normalized record carrying a source directory `path` and resolved absolute `entries`. */
17
+ type SourcedRecord = (ComponentListObject | PatternListObject) & {
18
+ entries?: Record<string, string | undefined>;
19
+ };
20
+
21
+ /** Maps a component/pattern `entries` key to the source-file kind it contributes. */
22
+ const ENTRY_KIND_BY_KEY: Record<string, TextFileKind> = {
23
+ js: 'script',
24
+ scss: 'style',
25
+ component: 'component',
26
+ story: 'story',
27
+ schema: 'schema',
28
+ template: 'template',
29
+ templates: 'template',
30
+ };
31
+
32
+ /** Source-text content types keyed by file extension (lowercase, no dot). */
33
+ const SOURCE_CONTENT_TYPE_BY_EXT: Record<string, string> = {
34
+ ts: 'text/typescript; charset=utf-8',
35
+ tsx: 'text/typescript; charset=utf-8',
36
+ js: 'application/javascript; charset=utf-8',
37
+ jsx: 'application/javascript; charset=utf-8',
38
+ cjs: 'application/javascript; charset=utf-8',
39
+ mjs: 'application/javascript; charset=utf-8',
40
+ json: 'application/json; charset=utf-8',
41
+ css: 'text/css; charset=utf-8',
42
+ scss: 'text/x-scss; charset=utf-8',
43
+ sass: 'text/x-sass; charset=utf-8',
44
+ hbs: 'text/x-handlebars-template; charset=utf-8',
45
+ md: 'text/markdown; charset=utf-8',
46
+ html: 'text/html; charset=utf-8',
47
+ };
48
+
49
+ export const sourceContentTypeForPath = (filePath: string): string => {
50
+ const ext = path.extname(filePath).slice(1).toLowerCase();
51
+ return SOURCE_CONTENT_TYPE_BY_EXT[ext] ?? 'text/plain; charset=utf-8';
52
+ };
53
+
54
+ /**
55
+ * Read a single text source file into a {@link TextFileResource}, or `null` when it is missing or
56
+ * unreadable. `relativeTo` is the entity source directory used to derive the registry-safe path.
57
+ */
58
+ const readTextFile = (
59
+ absolutePath: string,
60
+ kind: TextFileKind,
61
+ relativeTo: string
62
+ ): TextFileResource | null => {
63
+ try {
64
+ if (!fs.existsSync(absolutePath) || !fs.statSync(absolutePath).isFile()) {
65
+ return null;
66
+ }
67
+ const content = fs.readFileSync(absolutePath, 'utf8');
68
+ const relative = path.relative(relativeTo, absolutePath).split(path.sep).join('/');
69
+ return {
70
+ path: relative || path.basename(absolutePath),
71
+ absolutePath,
72
+ kind,
73
+ content,
74
+ contentType: sourceContentTypeForPath(absolutePath),
75
+ };
76
+ } catch {
77
+ return null;
78
+ }
79
+ };
80
+
81
+ /** Expand a `templates` directory entry into its contained template files. */
82
+ const expandTemplatesDir = (dirPath: string): string[] => {
83
+ try {
84
+ if (!fs.existsSync(dirPath) || !fs.statSync(dirPath).isDirectory()) {
85
+ return [];
86
+ }
87
+ return fs
88
+ .readdirSync(dirPath, { withFileTypes: true })
89
+ .filter((entry) => entry.isFile())
90
+ .map((entry) => path.join(dirPath, entry.name));
91
+ } catch {
92
+ return [];
93
+ }
94
+ };
95
+
96
+ /**
97
+ * Locate the entity's declaration file within its source directory and read it as a
98
+ * `kind: 'declaration'` resource. Returns `null` when no declaration can be found/read.
99
+ */
100
+ const readDeclarationFile = (sourceDir: string): TextFileResource | null => {
101
+ const declaration = resolveComponentDeclaration(sourceDir, path.basename(sourceDir));
102
+ if (!declaration) return null;
103
+ const absolutePath = path.resolve(sourceDir, declaration.fileName);
104
+ return readTextFile(absolutePath, 'declaration', sourceDir);
105
+ };
106
+
107
+ /**
108
+ * Gather an entity's declaration plus every `entries`-referenced source file. Files are deduped by
109
+ * normalized absolute path (entries duplicate `template`/`component`/`story` after normalization),
110
+ * and a `templates` directory entry is expanded into its files. Declaration always sorts first.
111
+ */
112
+ export const getRelatedSourceFilesForRecord = (record: SourcedRecord): TextFileResource[] => {
113
+ const sourceDir = record.path;
114
+ if (!sourceDir) {
115
+ return [];
116
+ }
117
+
118
+ const collected: TextFileResource[] = [];
119
+ const seen = new Set<string>();
120
+
121
+ const add = (absolutePath: string, kind: TextFileKind) => {
122
+ const key = normalizePathForCompare(absolutePath);
123
+ if (seen.has(key)) return;
124
+ const resource = readTextFile(absolutePath, kind, sourceDir);
125
+ if (!resource) return;
126
+ seen.add(key);
127
+ collected.push(resource);
128
+ };
129
+
130
+ const declaration = readDeclarationFile(sourceDir);
131
+ if (declaration) {
132
+ seen.add(normalizePathForCompare(declaration.absolutePath));
133
+ collected.push(declaration);
134
+ }
135
+
136
+ const entries = record.entries ?? {};
137
+ for (const [key, value] of Object.entries(entries)) {
138
+ if (!value) continue;
139
+ const kind = ENTRY_KIND_BY_KEY[key] ?? 'other';
140
+ if (key === 'templates') {
141
+ for (const templateFile of expandTemplatesDir(value)) {
142
+ add(templateFile, 'template');
143
+ }
144
+ continue;
145
+ }
146
+ add(value, kind);
147
+ }
148
+
149
+ return collected;
150
+ };
151
+
152
+ /** Whether a file resource should be excluded from registry source records (declarations only). */
153
+ export const isWorkspaceOnlyFile = (file: TextFileResource): boolean => file.kind === 'declaration';
154
+
155
+ export const isRegistrySourceFile = (file: TextFileResource): file is RegistryTextFileResource => !isWorkspaceOnlyFile(file);
@@ -0,0 +1,210 @@
1
+ /**
2
+ * Normalized store abstraction.
3
+ *
4
+ * Both the workspace (filesystem) store and the future registry (database) store implement one
5
+ * read contract, so the docs read API and other consumers are storage-agnostic.
6
+ * Read methods are typed `Awaitable` so the filesystem implementation can stay synchronous
7
+ * while a database implementation is async behind the same interface.
8
+ *
9
+ * This module is types-only and provider-agnostic.
10
+ */
11
+
12
+ import type { TokenSetKind } from '../registry/tokens/sets';
13
+ import type { ComponentListObject, PageListObject, PatternListObject } from '../transformers/preview/types';
14
+
15
+ /** A value that may be returned directly or as a promise. */
16
+ export type Awaitable<T> = T | Promise<T>;
17
+
18
+ /**
19
+ * Kind of a text source file tracked by the store. Mirrors the registry text-file model.
20
+ * `declaration` is **workspace-only** — registry stores never return it
21
+ * and declarations are synthesized on checkout.
22
+ */
23
+ export type TextFileKind =
24
+ | 'declaration'
25
+ | 'component'
26
+ | 'template'
27
+ | 'style'
28
+ | 'script'
29
+ | 'story'
30
+ | 'docs'
31
+ | 'markdown'
32
+ | 'schema'
33
+ | 'other';
34
+
35
+ export type RegistryTextFileKind = Exclude<TextFileKind, 'declaration'>;
36
+
37
+ /**
38
+ * A normalized text source file belonging to a component or pattern. Used for checkout,
39
+ * inspection, and publish — never to rebuild previews during docs serving.
40
+ */
41
+ export interface TextFileResource {
42
+ /** Registry-safe relative path of the file within the entity (e.g. `Badge.tsx`). */
43
+ path: string;
44
+ /** Absolute filesystem path the content was read from (workspace store only). */
45
+ absolutePath: string;
46
+ /** Classified file kind. */
47
+ kind: TextFileKind;
48
+ /** File content. */
49
+ content: string;
50
+ /** Content type to serve/transfer the file with. */
51
+ contentType: string;
52
+ }
53
+
54
+ export type RegistryTextFileResource = TextFileResource & { kind: RegistryTextFileKind };
55
+
56
+ /**
57
+ * A reference to a source file resolvable by a store. For the filesystem store this is an
58
+ * absolute path; the contract keeps it opaque so a database store can use its own reference form.
59
+ */
60
+ export interface SourceReference {
61
+ /** Absolute filesystem path (workspace store). */
62
+ absolutePath: string;
63
+ }
64
+
65
+ /**
66
+ * Read contract implemented by every component store backing. Consumers depend only on this so
67
+ * they never need to know whether the filesystem or a database is active.
68
+ */
69
+ export interface ComponentStore {
70
+ /** All normalized component records, identical in shape to today's runtime records. */
71
+ list(): Awaitable<ComponentListObject[]>;
72
+ /** A single normalized component record by stable id, or `null` when absent. */
73
+ get(id: string): Awaitable<ComponentListObject | null>;
74
+ /** Read a source file by reference, or `null` when it cannot be resolved. */
75
+ getSource(ref: SourceReference): Awaitable<TextFileResource | null>;
76
+ /**
77
+ * The entity's declaration plus every `entries`-referenced source file, for checkout/publish.
78
+ * Returns an empty array when the entity is unknown.
79
+ */
80
+ getRelatedSourceFiles(id: string): Awaitable<TextFileResource[]>;
81
+ }
82
+
83
+ /**
84
+ * Read contract implemented by every pattern store backing. Same shape as {@link ComponentStore}
85
+ * over pattern records.
86
+ */
87
+ export interface PatternStore {
88
+ /** All normalized pattern records, identical in shape to today's runtime records. */
89
+ list(): Awaitable<PatternListObject[]>;
90
+ /** A single normalized pattern record by stable id, or `null` when absent. */
91
+ get(id: string): Awaitable<PatternListObject | null>;
92
+ /** Read a source file by reference, or `null` when it cannot be resolved. */
93
+ getSource(ref: SourceReference): Awaitable<TextFileResource | null>;
94
+ /**
95
+ * The pattern's declaration plus every `entries`-referenced source file, for checkout/publish.
96
+ * Returns an empty array when the pattern is unknown.
97
+ */
98
+ getRelatedSourceFiles(id: string): Awaitable<TextFileResource[]>;
99
+ }
100
+
101
+ /**
102
+ * Read contract implemented by every page store backing. Same shape as {@link PatternStore} over
103
+ * page records. A page's single related source file is its verbatim `.md` (kind `markdown`).
104
+ */
105
+ export interface PageStore {
106
+ /** All normalized page records. */
107
+ list(): Awaitable<PageListObject[]>;
108
+ /** A single normalized page record by stable id, or `null` when absent. */
109
+ get(id: string): Awaitable<PageListObject | null>;
110
+ /** Read a source file by reference, or `null` when it cannot be resolved. */
111
+ getSource(ref: SourceReference): Awaitable<TextFileResource | null>;
112
+ /** The page's verbatim `.md` source file, for checkout/publish. Empty when the page is unknown. */
113
+ getRelatedSourceFiles(id: string): Awaitable<TextFileResource[]>;
114
+ }
115
+
116
+ /**
117
+ * A logical token set as the store exposes it: its stable id, kind, and the exact extracted token
118
+ * slice (a `record` of `IColorObject[]`/…/`IFileComponentObject`). Consumers read the whole set and
119
+ * group/sort/look up in memory, so there is no individual-token accessor.
120
+ */
121
+ export interface TokenSetRecord {
122
+ id: string;
123
+ kind: TokenSetKind;
124
+ record: unknown;
125
+ }
126
+
127
+ /** A generated token artifact (CSS/SCSS/Style Dictionary/types/custom output) as the store returns it. */
128
+ export interface TokenArtifactResource {
129
+ /** Registry-safe relative output path under the tokens dir (e.g. `css/colors.css`). */
130
+ path: string;
131
+ /** Logical format label (`css`|`scss`|`types`|`styleDictionary`|custom). */
132
+ format: string;
133
+ content: string;
134
+ contentType: string;
135
+ }
136
+
137
+ /**
138
+ * Read contract implemented by every token store backing. The filesystem backing derives sets from
139
+ * the local `tokens.json` + generated files; the registry backing reads the `token_sets` /
140
+ * `token_artifacts` tables. Consumers depend only on this so token behavior is consistent across
141
+ * modes even though the storage differs.
142
+ */
143
+ export interface TokenStore {
144
+ /** All logical token sets (three foundation sets + one per component). */
145
+ listSets(): Awaitable<TokenSetRecord[]>;
146
+ /** A single set by stable id, or `null` when absent. */
147
+ getSet(id: string): Awaitable<TokenSetRecord | null>;
148
+ /** Every generated artifact owned by a set. Empty when the set is unknown or has no output. */
149
+ getArtifacts(id: string): Awaitable<TokenArtifactResource[]>;
150
+ /** One generated artifact for a set by logical format, or `null` when absent. */
151
+ getArtifact(id: string, format: string): Awaitable<TokenArtifactResource | null>;
152
+ }
153
+
154
+ /**
155
+ * Lightweight metadata for one published asset. Collection/list reads return only this shape - never
156
+ * the binary body - so large collections do not produce oversized responses.
157
+ */
158
+ export interface AssetMetadata {
159
+ /** Collection the asset belongs to (`icons`|`logos`|`fonts`). */
160
+ collection: string;
161
+ /** Registry-safe logical path within the collection (e.g. `assets/icons/add.svg`, `icons.zip`). */
162
+ path: string;
163
+ /** Human-facing asset name. */
164
+ name: string;
165
+ /** Content type to serve the asset with. */
166
+ contentType: string;
167
+ /** Byte length. */
168
+ size: number;
169
+ /** SHA-256 of the bytes (hex) - content identity + ETag. */
170
+ contentHash: string;
171
+ /** Free-form asset metadata carried from extraction (icon index, description, …). */
172
+ metadata?: Record<string, unknown> | null;
173
+ }
174
+
175
+ /**
176
+ * An asset's resolved content: inline bytes (filesystem store, or a DB-backed blob), or a redirect
177
+ * URL when an object-storage provider serves the content directly.
178
+ */
179
+ export interface AssetContentResource extends AssetMetadata {
180
+ /** Inline bytes, when the content is served from the filesystem or an inline DB blob. */
181
+ body?: Buffer;
182
+ /** Signed/public provider URL to redirect to, when an object-storage provider serves the content. */
183
+ redirectUrl?: string;
184
+ }
185
+
186
+ /**
187
+ * Read contract implemented by every asset store backing. The filesystem backing derives collections
188
+ * from the generated `public/api` tree + archives; the registry backing reads the `assets` /
189
+ * `asset_collections` tables and resolves content through the configured storage provider. List
190
+ * methods return metadata only; content is fetched one asset at a time.
191
+ */
192
+ export interface AssetStore {
193
+ /** All collections that have any published assets. */
194
+ listCollections(): Awaitable<string[]>;
195
+ /** Lightweight metadata for every asset in a collection (no bodies). Empty when unknown. */
196
+ listAssets(collection: string): Awaitable<AssetMetadata[]>;
197
+ /** Metadata for one asset by collection + logical path, or `null` when absent. */
198
+ getAsset(collection: string, path: string): Awaitable<AssetMetadata | null>;
199
+ /** One asset's resolved content (bytes or redirect), or `null` when absent. */
200
+ getAssetContent(collection: string, path: string): Awaitable<AssetContentResource | null>;
201
+ }
202
+
203
+ /** Convenience pairing of the stores backing one runtime. */
204
+ export interface HandoffStore {
205
+ components: ComponentStore;
206
+ patterns: PatternStore;
207
+ pages: PageStore;
208
+ tokens: TokenStore;
209
+ assets: AssetStore;
210
+ }
@@ -10,7 +10,15 @@ import Handoff from '../..';
10
10
  import { Logger } from '../../utils/logger';
11
11
  import { generateDocsArtifact, getPropertiesForComponentFromDocs } from '../docgen';
12
12
  import { SlotMetadata, SlotType } from '../preview/component';
13
- import { MAIN_COMPONENT_CSS_FILE } from '../preview/component/css';
13
+ import {
14
+ renderComponentStyleLink,
15
+ renderGlobalScriptTag,
16
+ renderSharedStyleLinks,
17
+ resolveComponentArtifactPresence,
18
+ resolveSharedArtifactPresence,
19
+ type ComponentArtifactPresence,
20
+ type SharedArtifactPresence,
21
+ } from '../preview/component/shared-artifacts';
14
22
  import { TransformComponentTokensResult } from '../preview/types';
15
23
  import { formatHtml, trimPreview } from '../utils/html';
16
24
  import { buildAndEvaluateModule } from '../utils/module';
@@ -266,14 +274,25 @@ function buildUsageSnippet(
266
274
  });
267
275
  }
268
276
 
269
- function createHtmlDocument(componentId: string, previewTitle: string, renderedHtml: string): string {
277
+ function createHtmlDocument(
278
+ componentId: string,
279
+ previewTitle: string,
280
+ renderedHtml: string,
281
+ sharedArtifacts: SharedArtifactPresence,
282
+ componentArtifacts: ComponentArtifactPresence
283
+ ): string {
284
+ const basePath = process.env.HANDOFF_APP_BASE_PATH ?? '';
285
+ // Shared/global and component-owned artifacts are referenced only when present, so a CSF preview
286
+ // never requests an absent optional artifact. CSF previews stay static; the global script runs
287
+ // only for its top-level side effects when a global JS entry exists.
288
+ const sharedStyleLinks = renderSharedStyleLinks(sharedArtifacts, basePath);
289
+ const componentStyleLink = renderComponentStyleLink(componentArtifacts, componentId, basePath);
290
+ const globalScriptTag = renderGlobalScriptTag(sharedArtifacts, basePath);
270
291
  return `<!DOCTYPE html>
271
292
  <html lang="en">
272
293
  <head>
273
- <meta charset="UTF-8" />
274
- <link rel="stylesheet" href="${process.env.HANDOFF_APP_BASE_PATH ?? ''}/api/component/${MAIN_COMPONENT_CSS_FILE}" />
275
- <link rel="stylesheet" href="${process.env.HANDOFF_APP_BASE_PATH ?? ''}/api/component/${componentId}.css" />
276
- <link rel="stylesheet" href="${process.env.HANDOFF_APP_BASE_PATH ?? ''}/assets/css/preview.css" />
294
+ <meta charset="UTF-8" />${sharedStyleLinks ? `\n ${sharedStyleLinks}` : ''}${componentStyleLink ? `\n ${componentStyleLink}` : ''}
295
+ <link rel="stylesheet" href="${basePath}/assets/css/preview.css" />${globalScriptTag ? `\n ${globalScriptTag}` : ''}
277
296
  <title>${previewTitle}</title>
278
297
  </head>
279
298
  <body>
@@ -425,6 +444,11 @@ export function csfRenderPlugin(
425
444
 
426
445
  componentData.properties = ensureIds(generatedProperties);
427
446
 
447
+ // Resolve which shared/global and component-owned artifacts exist so generated HTML references
448
+ // them only when present. These are built before component HTML, so this reflects final state.
449
+ const sharedArtifacts = resolveSharedArtifactPresence(handoff);
450
+ const componentArtifacts = resolveComponentArtifactPresence(handoff, componentId);
451
+
428
452
  let lastHtml = '';
429
453
  for (const [previewKey, preview] of Object.entries(componentData.previews)) {
430
454
  const storyKey = preview.sourcePreview && storyMap[preview.sourcePreview]
@@ -433,7 +457,7 @@ export function csfRenderPlugin(
433
457
  const storyValue = storyKey ? storyMap[storyKey] : undefined;
434
458
  const storyArgs = preview.values || {};
435
459
  const rendered = safeRenderToHtml(meta, storyValue, storyArgs);
436
- const html = await formatHtml(createHtmlDocument(componentId, preview.title, rendered));
460
+ const html = await formatHtml(createHtmlDocument(componentId, preview.title, rendered, sharedArtifacts, componentArtifacts));
437
461
  const fileName = `${componentId}-${previewKey}.html`;
438
462
 
439
463
  this.emitFile({
@@ -6,6 +6,12 @@ import { Plugin } from 'vite';
6
6
  import Handoff from '../..';
7
7
  import { Logger } from '../../utils/logger';
8
8
  import { TransformComponentTokensResult } from '../preview/types';
9
+ import {
10
+ resolveComponentArtifactPresence,
11
+ resolveSharedArtifactPresence,
12
+ type ComponentArtifactPresence,
13
+ type SharedArtifactPresence,
14
+ } from '../preview/component/shared-artifacts';
9
15
  import { createHandlebarsContext, registerHandlebarsHelpers } from '../utils/handlebars';
10
16
  import { formatHtmlWithWrapper, trimPreview } from '../utils/html';
11
17
  import { slugify } from '../utils/string';
@@ -70,7 +76,9 @@ async function renderHandlebarsTemplate(
70
76
  componentData: TransformComponentTokensResult,
71
77
  previewData: PreviewRenderData,
72
78
  injectFieldWrappers: boolean,
73
- handoff: Handoff
79
+ handoff: Handoff,
80
+ sharedArtifacts: SharedArtifactPresence,
81
+ componentArtifacts: ComponentArtifactPresence
74
82
  ): Promise<string> {
75
83
  // Register Handlebars helpers with current injection state
76
84
  registerHandlebarsHelpers(
@@ -85,6 +93,8 @@ async function renderHandlebarsTemplate(
85
93
  title: componentData.title
86
94
  }, previewData, {
87
95
  includeSharedStyles: Boolean(componentData.sharedStyles),
96
+ sharedArtifacts,
97
+ componentArtifacts,
88
98
  });
89
99
 
90
100
  const compiled = Handlebars.compile(template)(context);
@@ -173,6 +183,11 @@ export function handlebarsPreviewsPlugin(
173
183
 
174
184
  const generatedPreviews: { [key: string]: string } = {};
175
185
 
186
+ // Resolve which shared/global and component-owned artifacts exist so generated HTML references
187
+ // them only when present. These are built before component HTML, so this reflects final state.
188
+ const sharedArtifacts = resolveSharedArtifactPresence(handoff);
189
+ const componentArtifacts = resolveComponentArtifactPresence(handoff, componentId);
190
+
176
191
  // Generate previews for each variation
177
192
  for (const previewKey in componentData.previews) {
178
193
  try {
@@ -184,7 +199,9 @@ export function handlebarsPreviewsPlugin(
184
199
  componentData,
185
200
  previewData,
186
201
  false,
187
- handoff
202
+ handoff,
203
+ sharedArtifacts,
204
+ componentArtifacts
188
205
  );
189
206
 
190
207
  const inspectModeHtml = await renderHandlebarsTemplate(
@@ -192,7 +209,9 @@ export function handlebarsPreviewsPlugin(
192
209
  componentData,
193
210
  previewData,
194
211
  true,
195
- handoff
212
+ handoff,
213
+ sharedArtifacts,
214
+ componentArtifacts
196
215
  );
197
216
 
198
217
  // Emit preview files