handoff-app 2.0.0-8 → 2.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 (322) hide show
  1. package/{Changelog.md → CHANGELOG.md} +85 -1
  2. package/{License.md → LICENSE.md} +1 -1
  3. package/README.md +816 -393
  4. package/RECIPES.md +975 -0
  5. package/UPGRADE.md +57 -9
  6. package/components.json +20 -20
  7. package/config/docs/foundations/colors/guidelines.md +0 -1
  8. package/config/docs/foundations/colors.md +0 -1
  9. package/config/docs/foundations/effects/guidelines.md +0 -1
  10. package/config/docs/foundations/effects/resources.md +0 -1
  11. package/config/docs/foundations/effects.md +0 -1
  12. package/config/docs/foundations/fonts.md +0 -1
  13. package/config/docs/foundations/grid.md +0 -1
  14. package/config/docs/foundations/icon.md +0 -1
  15. package/config/docs/foundations/icons/guidelines.md +0 -1
  16. package/config/docs/foundations/icons.md +0 -1
  17. package/config/docs/foundations/images/guidelines.md +0 -1
  18. package/config/docs/foundations/images/resources.md +0 -1
  19. package/config/docs/foundations/images.md +0 -1
  20. package/config/docs/foundations/logo/resources.md +0 -1
  21. package/config/docs/foundations/logo.md +0 -1
  22. package/config/docs/foundations/typography/guidelines.md +0 -1
  23. package/config/docs/foundations/typography/resources.md +0 -1
  24. package/config/docs/foundations/typography.md +0 -1
  25. package/config/docs/foundations.md +0 -1
  26. package/config/docs/guidelines.md +0 -1
  27. package/config/docs/index.md +0 -1
  28. package/config/docs/system/tokens/foundations/colors.md +0 -4
  29. package/config/docs/system/tokens/foundations/effects.md +0 -4
  30. package/config/docs/system/tokens/foundations/typography.md +0 -2
  31. package/config/docs/system.md +0 -2
  32. package/config/templates/page.md +2 -1
  33. package/config/templates/page.mdx +2 -1
  34. package/dist/ai/connections.d.ts +66 -0
  35. package/dist/ai/connections.js +149 -0
  36. package/dist/ai/define.d.ts +10 -0
  37. package/dist/ai/define.js +13 -0
  38. package/dist/ai/types.d.ts +26 -0
  39. package/dist/ai/types.js +10 -0
  40. package/dist/app-builder/build.d.ts +14 -7
  41. package/dist/app-builder/build.js +195 -66
  42. package/dist/app-builder/client-config.js +3 -2
  43. package/dist/app-builder/paths.d.ts +0 -12
  44. package/dist/app-builder/paths.js +3 -20
  45. package/dist/app-builder/static-export.js +1 -1
  46. package/dist/app-builder/vercel-output.d.ts +1 -1
  47. package/dist/app-builder/vercel-output.js +5 -4
  48. package/dist/cache/build-cache.d.ts +27 -4
  49. package/dist/cache/build-cache.js +114 -47
  50. package/dist/cache/build-inputs.d.ts +13 -0
  51. package/dist/cache/build-inputs.js +19 -0
  52. package/dist/cache/index.d.ts +2 -1
  53. package/dist/cache/index.js +5 -1
  54. package/dist/catalog/normalize.js +3 -0
  55. package/dist/catalog/previews.js +5 -4
  56. package/dist/catalog/types.d.ts +8 -0
  57. package/dist/cli/create.js +1 -2
  58. package/dist/commands/build/index.d.ts +1 -1
  59. package/dist/commands/build/index.js +1 -1
  60. package/dist/commands/utils.d.ts +3 -3
  61. package/dist/config/defaults.js +7 -4
  62. package/dist/config/from-env.d.ts +4 -0
  63. package/dist/config/from-env.js +38 -1
  64. package/dist/config/helpers.js +2 -2
  65. package/dist/config/index.d.ts +0 -1
  66. package/dist/config/index.js +1 -4
  67. package/dist/config/loader.js +28 -2
  68. package/dist/config/resolve-env.d.ts +2 -1
  69. package/dist/config/resolve-env.js +28 -12
  70. package/dist/config/runtime.js +9 -11
  71. package/dist/handlebars.d.ts +10 -2
  72. package/dist/index.d.ts +12 -7
  73. package/dist/index.js +18 -22
  74. package/dist/nav/index.d.ts +2 -2
  75. package/dist/nav/registry.d.ts +3 -3
  76. package/dist/nav/registry.js +1 -1
  77. package/dist/nav/workspace.d.ts +3 -3
  78. package/dist/nav/workspace.js +1 -1
  79. package/dist/pipeline/figma.d.ts +13 -2
  80. package/dist/pipeline/figma.js +86 -46
  81. package/dist/pipeline/styles.js +1 -1
  82. package/dist/pipeline/validation.d.ts +1 -1
  83. package/dist/pipeline/validation.js +3 -3
  84. package/dist/providers.d.ts +10 -0
  85. package/dist/providers.js +13 -0
  86. package/dist/registry/asset-storage/define.d.ts +5 -5
  87. package/dist/registry/asset-storage/define.js +4 -4
  88. package/dist/registry/asset-storage/resolve.d.ts +9 -19
  89. package/dist/registry/asset-storage/resolve.js +33 -21
  90. package/dist/registry/asset-storage/types.d.ts +37 -17
  91. package/dist/registry/asset-storage/types.js +5 -5
  92. package/dist/registry/auth/ai-keys.d.ts +32 -0
  93. package/dist/registry/auth/ai-keys.js +69 -0
  94. package/dist/registry/auth/crypto.d.ts +17 -0
  95. package/dist/registry/auth/crypto.js +61 -1
  96. package/dist/registry/auth/index.d.ts +2 -1
  97. package/dist/registry/auth/index.js +11 -2
  98. package/dist/registry/checkout/index.d.ts +2 -1
  99. package/dist/registry/checkout/index.js +57 -127
  100. package/dist/registry/client.js +53 -17
  101. package/dist/registry/db/driver.js +3 -3
  102. package/dist/registry/db/schema.d.ts +261 -3
  103. package/dist/registry/db/schema.js +29 -4
  104. package/dist/registry/email/define.d.ts +10 -0
  105. package/dist/registry/email/define.js +13 -0
  106. package/dist/registry/email/providers/smtp.d.ts +10 -0
  107. package/dist/registry/email/providers/smtp.js +73 -0
  108. package/dist/registry/email/resolve.d.ts +28 -0
  109. package/dist/registry/email/resolve.js +74 -0
  110. package/dist/registry/email/types.d.ts +24 -0
  111. package/dist/registry/email/types.js +6 -0
  112. package/dist/registry/path.d.ts +2 -0
  113. package/dist/registry/path.js +11 -1
  114. package/dist/registry/publish/package.js +8 -0
  115. package/dist/registry/transfer.d.ts +4 -0
  116. package/dist/store/registry.d.ts +3 -3
  117. package/dist/store/registry.js +4 -4
  118. package/dist/transformers/plugins/ssr-render.js +40 -15
  119. package/dist/transformers/preview/component/builder.d.ts +1 -9
  120. package/dist/transformers/preview/component/builder.js +17 -17
  121. package/dist/transformers/preview/component/css.d.ts +3 -1
  122. package/dist/transformers/preview/component/css.js +92 -51
  123. package/dist/transformers/preview/component/html.d.ts +3 -1
  124. package/dist/transformers/preview/component/html.js +6 -2
  125. package/dist/transformers/preview/component/javascript.d.ts +2 -1
  126. package/dist/transformers/preview/component/javascript.js +10 -4
  127. package/dist/transformers/preview/pattern/html.js +3 -1
  128. package/dist/transformers/preview/types.d.ts +13 -0
  129. package/dist/transformers/utils/build.d.ts +24 -0
  130. package/dist/transformers/utils/build.js +100 -1
  131. package/dist/transformers/utils/module.js +2 -1
  132. package/dist/types/config.d.ts +135 -18
  133. package/dist/utils/path.d.ts +1 -5
  134. package/dist/utils/path.js +6 -23
  135. package/docs/api.md +6 -4
  136. package/docs/cli.md +275 -54
  137. package/drizzle/0007_add_ai_keys.sql +11 -0
  138. package/drizzle/0008_add_source_dir.sql +2 -0
  139. package/drizzle/meta/0007_snapshot.json +2013 -0
  140. package/drizzle/meta/0008_snapshot.json +2025 -0
  141. package/drizzle/meta/_journal.json +14 -0
  142. package/package.json +19 -8
  143. package/src/ai/README.md +42 -0
  144. package/src/ai/connections.ts +189 -0
  145. package/src/ai/define.ts +15 -0
  146. package/src/ai/types.ts +30 -0
  147. package/src/app/components/AiAssistant/AiAssistantPanel.tsx +144 -0
  148. package/src/app/components/AiAssistant/AiAssistantProvider.tsx +215 -0
  149. package/src/app/components/AiAssistant/AiConversation.tsx +737 -0
  150. package/src/app/components/AiAssistant/AiToolResult.tsx +99 -0
  151. package/src/app/components/AiAssistant/dockState.ts +86 -0
  152. package/src/app/components/AiAssistant/toolLabel.ts +24 -0
  153. package/src/app/components/AiAssistant/useAiChat.ts +147 -0
  154. package/src/app/components/AiAssistant/useAiConnections.ts +86 -0
  155. package/src/app/components/AiAssistant/usePageContext.ts +63 -0
  156. package/src/app/components/Auth/AccountLayout.tsx +58 -19
  157. package/src/app/components/Auth/AuthShell.tsx +27 -33
  158. package/src/app/components/Auth/RegistryInstallGate.tsx +4 -8
  159. package/src/app/components/Auth/installStatus.ts +29 -0
  160. package/src/app/components/Component/PageSliceResolver.tsx +1 -0
  161. package/src/app/components/Component/Preview.tsx +383 -189
  162. package/src/app/components/Component/slices/ComponentDisplaySlice.tsx +4 -0
  163. package/src/app/components/Foundations/ColorGrid.tsx +13 -14
  164. package/src/app/components/Layout/Header.tsx +21 -15
  165. package/src/app/components/Layout/Main.tsx +2 -2
  166. package/src/app/components/Markdown/MarkdownComponents.tsx +30 -32
  167. package/src/app/components/McpIntegration/McpConfigDialog.tsx +12 -51
  168. package/src/app/components/Navigation/MobileNav.tsx +2 -2
  169. package/src/app/components/Navigation/SideNav.tsx +1 -15
  170. package/src/app/components/context/NavProvider.tsx +7 -3
  171. package/src/app/components/ui/attachment.tsx +50 -0
  172. package/src/app/components/ui/bubble.tsx +57 -0
  173. package/src/app/components/ui/button.tsx +3 -1
  174. package/src/app/components/ui/confirm-dialog.tsx +87 -0
  175. package/src/app/components/ui/dialog.tsx +7 -2
  176. package/src/app/components/ui/marker.tsx +56 -0
  177. package/src/app/components/ui/sidebar.tsx +3 -1
  178. package/src/app/components/util/index.ts +3 -12
  179. package/src/app/css/index.css +64 -0
  180. package/src/app/lib/ai/auth.ts +33 -0
  181. package/src/app/lib/ai/context.ts +99 -0
  182. package/src/app/lib/ai/model.ts +79 -0
  183. package/src/app/lib/ai/prompt.ts +36 -0
  184. package/src/app/lib/ai/resolve.ts +97 -0
  185. package/src/app/lib/ai/tools.ts +120 -0
  186. package/src/app/lib/api/methods.ts +13 -0
  187. package/src/app/lib/asset-storage.ts +35 -36
  188. package/src/app/lib/auth/api.ts +4 -12
  189. package/src/app/lib/auth/email.ts +96 -23
  190. package/src/app/lib/docs-api/page-rendering.ts +1 -1
  191. package/src/app/lib/docs-api/registry.ts +2 -3
  192. package/src/app/lib/docs-api/runtime-config.ts +47 -21
  193. package/src/app/lib/mcp/components.ts +523 -0
  194. package/src/app/lib/mcp/page-url.ts +24 -0
  195. package/src/app/lib/mcp/pages.ts +84 -0
  196. package/src/app/lib/mcp/result.ts +33 -0
  197. package/src/app/lib/mcp/server.ts +39 -318
  198. package/src/app/lib/mcp/tokens.ts +334 -0
  199. package/src/app/lib/mcp/tool.ts +19 -0
  200. package/src/app/lib/registry-api/asset-transfer.ts +119 -11
  201. package/src/app/lib/registry-api/errors.ts +2 -0
  202. package/src/app/lib/registry-api/handler.ts +7 -1
  203. package/src/app/lib/registry-api/transfer.ts +28 -6
  204. package/src/app/lib/server-module.ts +34 -0
  205. package/src/app/lib/utils.ts +20 -0
  206. package/src/app/next.config.mjs +26 -24
  207. package/src/app/pages/[...slug]/index.tsx +2 -1
  208. package/src/app/pages/_app.tsx +6 -2
  209. package/src/app/pages/_document.tsx +6 -0
  210. package/src/app/pages/account/ai.tsx +183 -0
  211. package/src/app/pages/account/index.tsx +68 -68
  212. package/src/app/pages/account/tokens.tsx +37 -7
  213. package/src/app/pages/account/users.tsx +75 -15
  214. package/src/app/pages/api/account/ai/keys/[connectionId].ts +60 -0
  215. package/src/app/pages/api/account/ai/keys/index.ts +20 -0
  216. package/src/app/pages/api/admin/users/[id]/[action].ts +80 -11
  217. package/src/app/pages/api/admin/users/index.ts +2 -2
  218. package/src/app/pages/api/ai/chat.ts +115 -0
  219. package/src/app/pages/api/ai/connections.ts +23 -0
  220. package/src/app/pages/api/auth/request-reset.ts +14 -16
  221. package/src/app/pages/api/docs/nav.json.ts +1 -1
  222. package/src/app/pages/api/install.ts +3 -3
  223. package/src/app/pages/api/mcp.ts +6 -0
  224. package/src/app/pages/api/registry/transfer/assets/blobs/[hash]/complete.ts +11 -0
  225. package/src/app/pages/api/registry/transfer/assets/blobs/[hash]/upload.ts +12 -0
  226. package/src/app/pages/api/registry/transfer/assets/blobs/[hash].ts +1 -1
  227. package/src/app/pages/cli/device.tsx +10 -5
  228. package/src/app/pages/foundations/colors.tsx +9 -8
  229. package/src/app/pages/foundations/index.tsx +3 -3
  230. package/src/app/pages/index.tsx +2 -2
  231. package/src/app/pages/install.tsx +7 -2
  232. package/src/app/pages/login.tsx +32 -27
  233. package/src/app/pages/reset-password.tsx +21 -20
  234. package/src/app/pages/system/component/[component]/index.tsx +2 -5
  235. package/src/app/pages/system/index.tsx +11 -11
  236. package/src/app/pages/system/pattern/[pattern]/index.tsx +2 -2
  237. package/src/app/pages/system/tokens/components/[component]/index.tsx +0 -4
  238. package/src/app-builder/README.md +2 -1
  239. package/src/app-builder/build.ts +218 -73
  240. package/src/app-builder/client-config.ts +3 -2
  241. package/src/app-builder/paths.ts +3 -22
  242. package/src/app-builder/static-export.ts +1 -1
  243. package/src/app-builder/vercel-output.ts +5 -4
  244. package/src/cache/README.md +4 -1
  245. package/src/cache/build-cache.ts +128 -43
  246. package/src/cache/build-inputs.ts +28 -0
  247. package/src/cache/index.ts +4 -0
  248. package/src/catalog/README.md +3 -0
  249. package/src/catalog/normalize.ts +2 -0
  250. package/src/catalog/previews.ts +6 -3
  251. package/src/catalog/types.ts +8 -0
  252. package/src/cli/create.ts +1 -2
  253. package/src/commands/build/index.ts +1 -1
  254. package/src/config/README.md +10 -9
  255. package/src/config/defaults.ts +7 -4
  256. package/src/config/from-env.ts +33 -0
  257. package/src/config/helpers.ts +1 -3
  258. package/src/config/index.ts +0 -3
  259. package/src/config/loader.ts +27 -3
  260. package/src/config/resolve-env.ts +28 -15
  261. package/src/config/runtime.ts +10 -5
  262. package/src/handlebars.ts +8 -2
  263. package/src/index.ts +33 -29
  264. package/src/nav/index.ts +2 -2
  265. package/src/nav/registry.ts +3 -3
  266. package/src/nav/workspace.ts +3 -3
  267. package/src/pipeline/README.md +1 -1
  268. package/src/pipeline/figma.ts +98 -49
  269. package/src/pipeline/styles.ts +1 -1
  270. package/src/pipeline/validation.ts +3 -3
  271. package/src/providers.ts +18 -0
  272. package/src/registry/asset-storage/define.ts +6 -6
  273. package/src/registry/asset-storage/resolve.ts +38 -37
  274. package/src/registry/asset-storage/types.ts +39 -17
  275. package/src/registry/auth/ai-keys.ts +62 -0
  276. package/src/registry/auth/crypto.ts +63 -1
  277. package/src/registry/auth/index.ts +11 -1
  278. package/src/registry/checkout/index.ts +60 -138
  279. package/src/registry/client.ts +60 -20
  280. package/src/registry/db/driver.ts +2 -2
  281. package/src/registry/db/schema.ts +49 -22
  282. package/src/registry/email/define.ts +15 -0
  283. package/src/registry/email/providers/smtp.ts +39 -0
  284. package/src/registry/email/resolve.ts +94 -0
  285. package/src/registry/email/types.ts +28 -0
  286. package/src/registry/path.ts +11 -0
  287. package/src/registry/publish/package.ts +9 -0
  288. package/src/registry/transfer.ts +4 -0
  289. package/src/store/registry.ts +7 -7
  290. package/src/transformers/plugins/ssr-render.ts +53 -16
  291. package/src/transformers/preview/component/builder.ts +19 -25
  292. package/src/transformers/preview/component/css.ts +101 -53
  293. package/src/transformers/preview/component/html.ts +6 -2
  294. package/src/transformers/preview/component/javascript.ts +14 -4
  295. package/src/transformers/preview/pattern/html.ts +3 -1
  296. package/src/transformers/preview/types.ts +14 -0
  297. package/src/transformers/utils/build.ts +93 -0
  298. package/src/transformers/utils/module.ts +4 -2
  299. package/src/types/config.ts +148 -20
  300. package/src/utils/README.md +1 -1
  301. package/src/utils/path.ts +6 -29
  302. package/templates/default/handoff.config.js.tpl +10 -2
  303. package/templates/default/handoff.config.ts.tpl +10 -2
  304. package/templates/default/handoff.config.with-examples.js.tpl +10 -2
  305. package/templates/default/handoff.config.with-examples.ts.tpl +10 -2
  306. package/templates/default/package.json.tpl +2 -2
  307. package/.editorconfig +0 -19
  308. package/.eslintrc.json +0 -4
  309. package/.prettierignore +0 -0
  310. package/.prettierrc.json +0 -8
  311. package/.vscode/settings.json +0 -17
  312. package/dist/config/validator.d.ts +0 -9
  313. package/dist/config/validator.js +0 -24
  314. package/dist/registry/asset-storage/adapters/vercel-blob.d.ts +0 -17
  315. package/dist/registry/asset-storage/adapters/vercel-blob.js +0 -87
  316. package/src/app/components/old/Header.tsx +0 -89
  317. package/src/app/lib/mcp/shape.ts +0 -596
  318. package/src/app/pages/assets/fonts.tsx +0 -129
  319. package/src/app/pages/assets/index.tsx +0 -91
  320. package/src/app/pages/assets/logos.tsx +0 -75
  321. package/src/config/validator.ts +0 -22
  322. package/src/registry/asset-storage/adapters/vercel-blob.ts +0 -51
@@ -50,7 +50,7 @@ const resolveBasePath = (rawBasePath) => {
50
50
  * no functions, no database.
51
51
  *
52
52
  * @param exportDir Absolute path of the materialized static export (the same content the static
53
- * target writes to `out/<projectId>`).
53
+ * target writes to `out/static`).
54
54
  */
55
55
  const writeStaticVercelOutput = (handoff, exportDir) => __awaiter(void 0, void 0, void 0, function* () {
56
56
  var _a, _b;
@@ -77,10 +77,11 @@ exports.writeStaticVercelOutput = writeStaticVercelOutput;
77
77
  /**
78
78
  * Node.js runtime identifier baked into the registry function's `.vc-config.json`. The `pg` driver
79
79
  * (and the Neon serverless driver in its Node fallback) need the Node.js runtime, not the edge
80
- * runtime — this is the required baseline. Vercel resolves the patch
81
- * version within this major; pinning the major keeps the artifact stable.
80
+ * runtime. The major matches the package `engines` minimum, and the Vercel project's Node.js setting
81
+ * does not override it. Vercel resolves the patch version within this major; pinning the major keeps
82
+ * the artifact stable.
82
83
  */
83
- const VERCEL_NODE_RUNTIME = 'nodejs20.x';
84
+ const VERCEL_NODE_RUNTIME = 'nodejs22.x';
84
85
  /** Name (sans `.func`) of the single registry function. Served at `/` and the catch-all route. */
85
86
  const REGISTRY_FUNCTION_NAME = 'index';
86
87
  /** Handler file the Node launcher executes; wraps the Next standalone server as a request listener. */
@@ -1,10 +1,11 @@
1
1
  import Handoff from '../index';
2
+ import { BuildInputs } from './build-inputs';
2
3
  import { FileState } from './file-state';
3
4
  /**
4
5
  * Cache entry for a single component
5
6
  */
6
7
  export interface ComponentCacheEntry {
7
- /** File states for all source files of this component */
8
+ /** File states for the declared source files of this component and every file its last build read */
8
9
  files: Record<string, FileState>;
9
10
  /** States for template directory files (if templates is a directory) */
10
11
  templateDirFiles?: Record<string, FileState>;
@@ -21,6 +22,18 @@ export interface GlobalDepsState {
21
22
  globalScss?: FileState;
22
23
  /** Global JS entry file state */
23
24
  globalJs?: FileState;
25
+ /** Main config file state. The build hooks are declared there */
26
+ mainConfig?: FileState;
27
+ /** Profile config file state */
28
+ profileConfig?: FileState;
29
+ /** Selected profile. All profiles write to the same output, so a switch invalidates all of it */
30
+ profile?: string;
31
+ /** Lockfile state. It stands in for the files under `node_modules`, which the cache does not track */
32
+ lockfile?: FileState;
33
+ /** handoff-app version that built the output */
34
+ version?: string;
35
+ /** Base path written into the generated HTML */
36
+ basePath?: string;
24
37
  }
25
38
  /**
26
39
  * Complete build cache structure
@@ -70,12 +83,22 @@ export declare function computeComponentFileStates(handoff: Handoff, componentId
70
83
  templateDirFiles?: Record<string, FileState>;
71
84
  }>;
72
85
  /**
73
- * Checks if a component needs to be rebuilt based on file states
86
+ * Checks if a component needs to be rebuilt based on file states. This also compares the files that
87
+ * the last build read with their current state on disk.
74
88
  */
75
89
  export declare function hasComponentChanged(cached: ComponentCacheEntry | null | undefined, current: {
76
90
  files: Record<string, FileState>;
77
91
  templateDirFiles?: Record<string, FileState>;
78
- }): boolean;
92
+ }): Promise<boolean>;
93
+ /**
94
+ * File states for every file that the builds of a component read, or `null` when that list is
95
+ * incomplete and the component must not be cached.
96
+ *
97
+ * Previews compile their sources with esbuild inside a Vite plugin, so Rollup does not see those
98
+ * imports. The esbuild graph of each script entry supplies them. The lockfile covers the files under
99
+ * `node_modules`, so they are skipped, together with the generated files in the output directory.
100
+ */
101
+ export declare function computeBuildInputStates(handoff: Handoff, componentId: string, inputs: BuildInputs): Promise<Record<string, FileState> | null>;
79
102
  /**
80
103
  * Checks if the component output files exist
81
104
  */
@@ -90,7 +113,7 @@ export declare function createEmptyCache(): BuildCache;
90
113
  export declare function updateComponentCacheEntry(cache: BuildCache, componentId: string, fileStates: {
91
114
  files: Record<string, FileState>;
92
115
  templateDirFiles?: Record<string, FileState>;
93
- }): void;
116
+ }, inputStates: Record<string, FileState>): void;
94
117
  /**
95
118
  * Removes components from cache that are no longer in runtime config
96
119
  */
@@ -20,26 +20,29 @@ exports.haveGlobalDepsChanged = haveGlobalDepsChanged;
20
20
  exports.getComponentFilePaths = getComponentFilePaths;
21
21
  exports.computeComponentFileStates = computeComponentFileStates;
22
22
  exports.hasComponentChanged = hasComponentChanged;
23
+ exports.computeBuildInputStates = computeBuildInputStates;
23
24
  exports.checkOutputExists = checkOutputExists;
24
25
  exports.createEmptyCache = createEmptyCache;
25
26
  exports.updateComponentCacheEntry = updateComponentCacheEntry;
26
27
  exports.pruneRemovedComponents = pruneRemovedComponents;
27
28
  const fs_extra_1 = __importDefault(require("fs-extra"));
28
29
  const path_1 = __importDefault(require("path"));
30
+ const build_1 = require("../transformers/utils/build");
29
31
  const logger_1 = require("../utils/logger");
30
32
  const path_2 = require("../utils/path");
31
33
  const file_state_1 = require("./file-state");
32
34
  /** Current cache format version - bump when structure changes */
33
- const CACHE_VERSION = '1.0.0';
35
+ const CACHE_VERSION = '2.0.0';
36
+ const GLOBAL_FILE_DEPS = ['tokens', 'globalScss', 'globalJs', 'mainConfig', 'profileConfig', 'lockfile'];
37
+ const GLOBAL_VALUE_DEPS = ['profile', 'version', 'basePath'];
38
+ const LOCKFILE_NAMES = ['package-lock.json', 'pnpm-lock.yaml', 'yarn.lock', 'bun.lock', 'bun.lockb'];
34
39
  /**
35
40
  * Gets the path to the build cache file
36
41
  */
37
42
  function getCachePath(handoff) {
38
- // Two profiles share one project id and can build different output, so the profile has to name
39
- // the cache file. One shared cache would serve the other profile's components.
40
- const profile = handoff.getProfile();
41
- const fileName = profile ? `build-cache.${profile}.json` : 'build-cache.json';
42
- return path_1.default.resolve(handoff.modulePath, '.handoff', handoff.getProjectId(), '.cache', fileName);
43
+ // The cache describes the output in the working directory, so it lives there too. A build deletes
44
+ // the staged app under the package, so a cache there would not survive.
45
+ return path_1.default.resolve(handoff.workingPath, '.handoff', '.cache', 'build-cache.json');
43
46
  }
44
47
  /**
45
48
  * Loads the build cache from disk
@@ -99,7 +102,7 @@ function saveBuildCache(handoff, cache) {
99
102
  */
100
103
  function computeGlobalDepsState(handoff) {
101
104
  return __awaiter(this, void 0, void 0, function* () {
102
- var _a, _b, _c, _d, _e, _f, _g;
105
+ var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l;
103
106
  const result = {};
104
107
  // tokens.json
105
108
  const tokensPath = handoff.getTokensFilePath();
@@ -112,27 +115,61 @@ function computeGlobalDepsState(handoff) {
112
115
  if ((_f = (_e = handoff.runtimeConfig) === null || _e === void 0 ? void 0 : _e.entries) === null || _f === void 0 ? void 0 : _f.js) {
113
116
  result.globalJs = (_g = (yield (0, file_state_1.computeFileState)(handoff.runtimeConfig.entries.js))) !== null && _g !== void 0 ? _g : undefined;
114
117
  }
118
+ const mainConfigPath = handoff.getMainConfigFilePath();
119
+ if (mainConfigPath) {
120
+ result.mainConfig = (_h = (yield (0, file_state_1.computeFileState)(mainConfigPath))) !== null && _h !== void 0 ? _h : undefined;
121
+ }
122
+ const profileConfigPath = handoff.getProfileConfigFilePath();
123
+ if (profileConfigPath) {
124
+ result.profileConfig = (_j = (yield (0, file_state_1.computeFileState)(profileConfigPath))) !== null && _j !== void 0 ? _j : undefined;
125
+ }
126
+ result.profile = handoff.getProfile();
127
+ const lockfilePath = findLockfile(handoff.workingPath);
128
+ if (lockfilePath) {
129
+ result.lockfile = (_k = (yield (0, file_state_1.computeFileState)(lockfilePath))) !== null && _k !== void 0 ? _k : undefined;
130
+ }
131
+ result.version = yield readPackageVersion(handoff);
132
+ result.basePath = (_l = process.env.HANDOFF_APP_BASE_PATH) !== null && _l !== void 0 ? _l : '';
115
133
  return result;
116
134
  });
117
135
  }
136
+ /** The nearest lockfile, searched up from the working directory so that a monorepo root lockfile is found. */
137
+ function findLockfile(fromDirectory) {
138
+ for (let directory = path_1.default.resolve(fromDirectory);; directory = path_1.default.dirname(directory)) {
139
+ const lockfile = LOCKFILE_NAMES.map((name) => path_1.default.join(directory, name)).find((file) => fs_extra_1.default.existsSync(file));
140
+ if (lockfile)
141
+ return lockfile;
142
+ if (path_1.default.dirname(directory) === directory)
143
+ return undefined;
144
+ }
145
+ }
146
+ function readPackageVersion(handoff) {
147
+ return __awaiter(this, void 0, void 0, function* () {
148
+ try {
149
+ return (yield fs_extra_1.default.readJson(path_1.default.resolve(handoff.modulePath, 'package.json'))).version;
150
+ }
151
+ catch (_a) {
152
+ return undefined;
153
+ }
154
+ });
155
+ }
118
156
  /**
119
157
  * Checks if global dependencies have changed
120
158
  */
121
159
  function haveGlobalDepsChanged(cached, current) {
122
160
  if (!cached)
123
161
  return true;
124
- // Check each global dependency
125
- if (!(0, file_state_1.statesMatch)(cached.tokens, current.tokens)) {
126
- logger_1.Logger.debug('Global dependency changed: tokens.json');
127
- return true;
128
- }
129
- if (!(0, file_state_1.statesMatch)(cached.globalScss, current.globalScss)) {
130
- logger_1.Logger.debug('Global dependency changed: global SCSS entry');
131
- return true;
162
+ for (const key of GLOBAL_FILE_DEPS) {
163
+ if (!(0, file_state_1.statesMatch)(cached[key], current[key])) {
164
+ logger_1.Logger.debug(`Global dependency changed: ${key}`);
165
+ return true;
166
+ }
132
167
  }
133
- if (!(0, file_state_1.statesMatch)(cached.globalJs, current.globalJs)) {
134
- logger_1.Logger.debug('Global dependency changed: global JS entry');
135
- return true;
168
+ for (const key of GLOBAL_VALUE_DEPS) {
169
+ if (cached[key] !== current[key]) {
170
+ logger_1.Logger.debug(`Global dependency changed: ${key}`);
171
+ return true;
172
+ }
136
173
  }
137
174
  return false;
138
175
  }
@@ -243,37 +280,67 @@ function computeComponentFileStates(handoff, componentId) {
243
280
  });
244
281
  }
245
282
  /**
246
- * Checks if a component needs to be rebuilt based on file states
283
+ * Checks if a component needs to be rebuilt based on file states. This also compares the files that
284
+ * the last build read with their current state on disk.
247
285
  */
248
286
  function hasComponentChanged(cached, current) {
249
- if (!cached) {
250
- return true; // No cache entry means new component
251
- }
252
- // Check regular files
253
- const cachedFiles = Object.keys(cached.files);
254
- const currentFiles = Object.keys(current.files);
255
- // Check if file count changed
256
- if (cachedFiles.length !== currentFiles.length) {
257
- return true;
258
- }
259
- // Check if any files were added or removed
260
- const cachedSet = new Set(cachedFiles);
261
- for (const file of currentFiles) {
262
- if (!cachedSet.has(file)) {
263
- return true;
287
+ return __awaiter(this, void 0, void 0, function* () {
288
+ if (!cached) {
289
+ return true; // No cache entry means new component
264
290
  }
265
- }
266
- // Check if any file states changed
267
- for (const file of cachedFiles) {
268
- if (!(0, file_state_1.statesMatch)(cached.files[file], current.files[file])) {
269
- return true;
291
+ for (const [file, state] of Object.entries(current.files)) {
292
+ if (!(0, file_state_1.statesMatch)(cached.files[file], state)) {
293
+ return true;
294
+ }
270
295
  }
271
- }
272
- // Check template directory files if applicable
273
- if (!(0, file_state_1.directoryStatesMatch)(cached.templateDirFiles, current.templateDirFiles)) {
274
- return true;
275
- }
276
- return false;
296
+ for (const [file, state] of Object.entries(cached.files)) {
297
+ if (!(file in current.files) && !(0, file_state_1.statesMatch)(state, yield (0, file_state_1.computeFileState)(file))) {
298
+ return true;
299
+ }
300
+ }
301
+ return !(0, file_state_1.directoryStatesMatch)(cached.templateDirFiles, current.templateDirFiles);
302
+ });
303
+ }
304
+ const SCRIPT_EXTENSIONS = new Set(['.js', '.jsx', '.ts', '.tsx', '.mjs', '.cjs', '.mts', '.cts']);
305
+ /**
306
+ * File states for every file that the builds of a component read, or `null` when that list is
307
+ * incomplete and the component must not be cached.
308
+ *
309
+ * Previews compile their sources with esbuild inside a Vite plugin, so Rollup does not see those
310
+ * imports. The esbuild graph of each script entry supplies them. The lockfile covers the files under
311
+ * `node_modules`, so they are skipped, together with the generated files in the output directory.
312
+ */
313
+ function computeBuildInputStates(handoff, componentId, inputs) {
314
+ return __awaiter(this, void 0, void 0, function* () {
315
+ var _a, _b;
316
+ if (inputs.failed) {
317
+ return null;
318
+ }
319
+ const files = new Set(inputs.files);
320
+ const entries = (_b = (_a = handoff.runtimeConfig) === null || _a === void 0 ? void 0 : _a.entities.components[componentId]) === null || _b === void 0 ? void 0 : _b.entries;
321
+ const scriptEntries = new Set(Object.values(entries !== null && entries !== void 0 ? entries : {}).filter((entry) => !!entry && SCRIPT_EXTENSIONS.has(path_1.default.extname(entry))));
322
+ for (const entry of scriptEntries) {
323
+ const moduleInputs = yield (0, build_1.collectModuleInputs)(entry, handoff);
324
+ if (!moduleInputs) {
325
+ return null;
326
+ }
327
+ for (const file of moduleInputs)
328
+ files.add(file);
329
+ }
330
+ const outputDirectory = path_1.default.resolve(handoff.workingPath, 'public/api/component');
331
+ const states = {};
332
+ for (const id of files) {
333
+ const file = id.split('?')[0];
334
+ if (!path_1.default.isAbsolute(file) || file.split(path_1.default.sep).includes('node_modules') || file.startsWith(outputDirectory + path_1.default.sep)) {
335
+ continue;
336
+ }
337
+ const state = yield (0, file_state_1.computeFileState)(file);
338
+ if (state) {
339
+ states[path_1.default.resolve(file)] = state;
340
+ }
341
+ }
342
+ return states;
343
+ });
277
344
  }
278
345
  /**
279
346
  * Checks if the component output files exist
@@ -323,9 +390,9 @@ function createEmptyCache() {
323
390
  /**
324
391
  * Updates cache entry for a specific component
325
392
  */
326
- function updateComponentCacheEntry(cache, componentId, fileStates) {
393
+ function updateComponentCacheEntry(cache, componentId, fileStates, inputStates) {
327
394
  cache.components[componentId] = {
328
- files: fileStates.files,
395
+ files: Object.assign(Object.assign({}, inputStates), fileStates.files),
329
396
  templateDirFiles: fileStates.templateDirFiles,
330
397
  buildTimestamp: Date.now(),
331
398
  };
@@ -0,0 +1,13 @@
1
+ import type { InlineConfig } from 'vite';
2
+ /**
3
+ * The files that the builds of one component read, so the build cache can tell when an imported
4
+ * file changes. A failed build can stop before it reads the file that broke it, so its list is
5
+ * incomplete and must not be cached.
6
+ */
7
+ export interface BuildInputs {
8
+ files: Set<string>;
9
+ failed: boolean;
10
+ }
11
+ export declare const createBuildInputs: () => BuildInputs;
12
+ /** Call after the consumer hooks, so that a hook that replaces `plugins` cannot drop the collector. */
13
+ export declare const withBuildInputs: (config: InlineConfig, inputs?: BuildInputs) => InlineConfig;
@@ -0,0 +1,19 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.withBuildInputs = exports.createBuildInputs = void 0;
4
+ const createBuildInputs = () => ({ files: new Set(), failed: false });
5
+ exports.createBuildInputs = createBuildInputs;
6
+ /**
7
+ * Records every file that a Vite build loaded, Sass partials included. The Vite CSS plugin registers
8
+ * partials with `addWatchFile`, and Rollup keeps this list in every build, not only in watch mode.
9
+ */
10
+ const collectBuildInputs = (inputs) => ({
11
+ name: 'handoff:collect-build-inputs',
12
+ buildEnd() {
13
+ for (const file of this.getWatchFiles())
14
+ inputs.files.add(file);
15
+ },
16
+ });
17
+ /** Call after the consumer hooks, so that a hook that replaces `plugins` cannot drop the collector. */
18
+ const withBuildInputs = (config, inputs) => { var _a; return inputs ? Object.assign(Object.assign({}, config), { plugins: [...((_a = config.plugins) !== null && _a !== void 0 ? _a : []), collectBuildInputs(inputs)] }) : config; };
19
+ exports.withBuildInputs = withBuildInputs;
@@ -1,2 +1,3 @@
1
1
  export { computeDirectoryState, computeFileState, directoryStatesMatch, statesMatch, type FileState } from './file-state';
2
- export { checkOutputExists, computeComponentFileStates, computeGlobalDepsState, createEmptyCache, getCachePath, hasComponentChanged, haveGlobalDepsChanged, loadBuildCache, pruneRemovedComponents, saveBuildCache, updateComponentCacheEntry, type BuildCache, type ComponentCacheEntry, type GlobalDepsState, } from './build-cache';
2
+ export { checkOutputExists, computeBuildInputStates, computeComponentFileStates, computeGlobalDepsState, createEmptyCache, getCachePath, hasComponentChanged, haveGlobalDepsChanged, loadBuildCache, pruneRemovedComponents, saveBuildCache, updateComponentCacheEntry, type BuildCache, type ComponentCacheEntry, type GlobalDepsState, } from './build-cache';
3
+ export { createBuildInputs, type BuildInputs } from './build-inputs';
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.updateComponentCacheEntry = exports.saveBuildCache = exports.pruneRemovedComponents = exports.loadBuildCache = exports.haveGlobalDepsChanged = exports.hasComponentChanged = exports.getCachePath = exports.createEmptyCache = exports.computeGlobalDepsState = exports.computeComponentFileStates = exports.checkOutputExists = exports.statesMatch = exports.directoryStatesMatch = exports.computeFileState = exports.computeDirectoryState = void 0;
3
+ exports.createBuildInputs = exports.updateComponentCacheEntry = exports.saveBuildCache = exports.pruneRemovedComponents = exports.loadBuildCache = exports.haveGlobalDepsChanged = exports.hasComponentChanged = exports.getCachePath = exports.createEmptyCache = exports.computeGlobalDepsState = exports.computeComponentFileStates = exports.computeBuildInputStates = exports.checkOutputExists = exports.statesMatch = exports.directoryStatesMatch = exports.computeFileState = exports.computeDirectoryState = void 0;
4
4
  // File state utilities
5
5
  var file_state_1 = require("./file-state");
6
6
  Object.defineProperty(exports, "computeDirectoryState", { enumerable: true, get: function () { return file_state_1.computeDirectoryState; } });
@@ -10,6 +10,7 @@ Object.defineProperty(exports, "statesMatch", { enumerable: true, get: function
10
10
  // Build cache utilities
11
11
  var build_cache_1 = require("./build-cache");
12
12
  Object.defineProperty(exports, "checkOutputExists", { enumerable: true, get: function () { return build_cache_1.checkOutputExists; } });
13
+ Object.defineProperty(exports, "computeBuildInputStates", { enumerable: true, get: function () { return build_cache_1.computeBuildInputStates; } });
13
14
  Object.defineProperty(exports, "computeComponentFileStates", { enumerable: true, get: function () { return build_cache_1.computeComponentFileStates; } });
14
15
  Object.defineProperty(exports, "computeGlobalDepsState", { enumerable: true, get: function () { return build_cache_1.computeGlobalDepsState; } });
15
16
  Object.defineProperty(exports, "createEmptyCache", { enumerable: true, get: function () { return build_cache_1.createEmptyCache; } });
@@ -20,3 +21,6 @@ Object.defineProperty(exports, "loadBuildCache", { enumerable: true, get: functi
20
21
  Object.defineProperty(exports, "pruneRemovedComponents", { enumerable: true, get: function () { return build_cache_1.pruneRemovedComponents; } });
21
22
  Object.defineProperty(exports, "saveBuildCache", { enumerable: true, get: function () { return build_cache_1.saveBuildCache; } });
22
23
  Object.defineProperty(exports, "updateComponentCacheEntry", { enumerable: true, get: function () { return build_cache_1.updateComponentCacheEntry; } });
24
+ // Build input collection
25
+ var build_inputs_1 = require("./build-inputs");
26
+ Object.defineProperty(exports, "createBuildInputs", { enumerable: true, get: function () { return build_inputs_1.createBuildInputs; } });
@@ -88,6 +88,9 @@ const normalizeCatalogItem = (moduleExports, options) => {
88
88
  entries[entryKey] = path_1.default.resolve(directory, source.file);
89
89
  // Existing builders consume template as the primary build input for every renderer.
90
90
  entries.template = entries[entryKey];
91
+ // The renderer and the hydration bundle both read a preview `render` from the declaration.
92
+ if (options.declarationPath)
93
+ entries.declaration = options.declarationPath;
91
94
  // A wrong renderer builds a broken preview without failing on its own, so a mismatch is fatal.
92
95
  // `config/runtime.ts` turns the throw into a skipped item that retries on the next save.
93
96
  const claim = (0, renderers_1.sourceForFile)(source.file);
@@ -8,16 +8,17 @@ const fs_extra_1 = __importDefault(require("fs-extra"));
8
8
  const lodash_1 = require("lodash");
9
9
  /**
10
10
  * A declaration module exports more than previews: the item itself, the implementation it
11
- * re-exports, helper values. Accept only a plain object that owns `args` or `name`, so an exported
12
- * component or helper is never mistaken for a preview.
11
+ * re-exports, helper values. Accept only a plain object that owns one of the preview fields, so an
12
+ * exported component or helper is never mistaken for a preview.
13
13
  *
14
14
  * `getCsfStoryEntries` does not fit here: on a CSF file every named export is a story, and on a
15
15
  * declaration file it is not.
16
16
  */
17
+ const PREVIEW_KEYS = ['args', 'name', 'render'];
17
18
  const isPreviewDeclaration = (value) => {
18
19
  if (!value || typeof value !== 'object' || Array.isArray(value))
19
20
  return false;
20
- return Object.prototype.hasOwnProperty.call(value, 'args') || Object.prototype.hasOwnProperty.call(value, 'name');
21
+ return PREVIEW_KEYS.some((key) => Object.prototype.hasOwnProperty.call(value, key));
21
22
  };
22
23
  /** Matches an exported binding, an export list, or a CommonJS export assignment. */
23
24
  const EXPORT_PATTERNS = [
@@ -99,7 +100,7 @@ const createCatalogPreviews = (moduleExports, declarationPath) => {
99
100
  throw new Error(`Preview "${exportName}" uses removed fields. Use args and name. See UPGRADE.md#catalog-items.`);
100
101
  }
101
102
  const preview = value;
102
- previews[exportName] = Object.assign({ title: preview.name || (0, lodash_1.startCase)(exportName), values: Object.assign({}, ((_a = preview.args) !== null && _a !== void 0 ? _a : {})), url: (_b = preview.url) !== null && _b !== void 0 ? _b : '' }, (preview.usage ? { usage: preview.usage } : {}));
103
+ previews[exportName] = Object.assign(Object.assign({ title: preview.name || (0, lodash_1.startCase)(exportName), values: Object.assign({}, ((_a = preview.args) !== null && _a !== void 0 ? _a : {})), url: (_b = preview.url) !== null && _b !== void 0 ? _b : '' }, (preview.usage ? { usage: preview.usage } : {})), (typeof preview.render === 'function' ? { render: preview.render } : {}));
103
104
  }
104
105
  return declarationPath ? (0, exports.orderPreviews)(previews, (0, exports.readExportOrder)(declarationPath)) : previews;
105
106
  };
@@ -43,6 +43,14 @@ export type CatalogPreview<TArgs = Record<string, unknown>> = {
43
43
  args?: TArgs;
44
44
  usage?: string;
45
45
  url?: string;
46
+ /**
47
+ * Renders this preview in place of the implementation, for props it cannot carry as data: an icon
48
+ * component, a callback, JSX children, or state the preview owns.
49
+ *
50
+ * Rendered as a component with `args`, so it can use hooks. The item still documents the
51
+ * implementation, so a wrapper never becomes the published API.
52
+ */
53
+ render?: (args: TArgs) => unknown;
46
54
  };
47
55
  /**
48
56
  * `__args` exists only in the type system so `Preview<typeof item>` can infer the preview argument type.
@@ -130,8 +130,7 @@ const renderTemplate = (content, data) => {
130
130
  });
131
131
  };
132
132
  const getDependenciesToInstall = (options) => {
133
- // TODO: switch back to `handoff-app@latest` before the 2.0 stable release.
134
- const dependencies = ['handoff-app@next'];
133
+ const dependencies = ['handoff-app@latest'];
135
134
  const devDependencies = [];
136
135
  if (options.includeExamples) {
137
136
  dependencies.push('react', 'react-dom');
@@ -10,7 +10,7 @@ export interface BuildArgs extends SharedArgs {
10
10
  * `handoff-app build [--target static|registry] [--package standalone|vercel]` — builds the
11
11
  * documentation site for the resolved target and packages it for the resolved deliverable.
12
12
  * `--target` selects *what* is built; the optional, additive `--package` selects *how* it is
13
- * packaged. Bare `build` resolves to the static target with the `out/<projectId>` export;
13
+ * packaged. Bare `build` resolves to the static target with the `out/static` export;
14
14
  * `--package` never implies a `--target`.
15
15
  */
16
16
  declare const command: CommandModule<{}, BuildArgs>;
@@ -18,7 +18,7 @@ const BUILD_PACKAGES = ['standalone', 'vercel'];
18
18
  * `handoff-app build [--target static|registry] [--package standalone|vercel]` — builds the
19
19
  * documentation site for the resolved target and packages it for the resolved deliverable.
20
20
  * `--target` selects *what* is built; the optional, additive `--package` selects *how* it is
21
- * packaged. Bare `build` resolves to the static target with the `out/<projectId>` export;
21
+ * packaged. Bare `build` resolves to the static target with the `out/static` export;
22
22
  * `--package` never implies a `--target`.
23
23
  */
24
24
  const command = {
@@ -23,7 +23,7 @@ export declare const createHandoff: (args: SharedArgs, options?: Pick<HandoffOpt
23
23
  * trace.
24
24
  */
25
25
  export declare const runRegistryCommand: (args: SharedArgs, run: (handoff: Handoff) => Promise<unknown>) => Promise<void>;
26
- export declare const getSharedOptions: (yargs: Argv) => Argv<import("yargs").Omit<{}, "debug" | "config" | "profile" | "force"> & import("yargs").InferredOptionTypes<{
26
+ export declare const getSharedOptions: (yargs: Argv) => Argv<import("yargs").Omit<{}, "debug" | "profile" | "config" | "force"> & import("yargs").InferredOptionTypes<{
27
27
  config: {
28
28
  alias: string;
29
29
  type: "string";
@@ -45,7 +45,7 @@ export declare const getSharedOptions: (yargs: Argv) => Argv<import("yargs").Omi
45
45
  };
46
46
  }>>;
47
47
  /** Shared options plus the transfer options: publish and its compatibility aliases use these. */
48
- export declare const getPublishOptions: (yargs: Argv) => Argv<import("yargs").Omit<import("yargs").Omit<{}, "debug" | "config" | "profile" | "force"> & import("yargs").InferredOptionTypes<{
48
+ export declare const getPublishOptions: (yargs: Argv) => Argv<import("yargs").Omit<import("yargs").Omit<{}, "debug" | "profile" | "config" | "force"> & import("yargs").InferredOptionTypes<{
49
49
  config: {
50
50
  alias: string;
51
51
  type: "string";
@@ -77,7 +77,7 @@ export declare const getPublishOptions: (yargs: Argv) => Argv<import("yargs").Om
77
77
  };
78
78
  }>>;
79
79
  /** Shared options plus `--dry-run`: checkout and its compatibility aliases use these. */
80
- export declare const getCheckoutOptions: (yargs: Argv) => Argv<import("yargs").Omit<import("yargs").Omit<{}, "debug" | "config" | "profile" | "force"> & import("yargs").InferredOptionTypes<{
80
+ export declare const getCheckoutOptions: (yargs: Argv) => Argv<import("yargs").Omit<import("yargs").Omit<{}, "debug" | "profile" | "config" | "force"> & import("yargs").InferredOptionTypes<{
81
81
  config: {
82
82
  alias: string;
83
83
  type: "string";
@@ -7,8 +7,12 @@ const connection_1 = require("../registry/connection");
7
7
  const isMcpEnabled = (config) => { var _a; return ((_a = config === null || config === void 0 ? void 0 : config.runtime) === null || _a === void 0 ? void 0 : _a.mcp) !== false; };
8
8
  exports.isMcpEnabled = isMcpEnabled;
9
9
  const defaultConfig = () => ({
10
- dev_access_token: (0, from_env_1.fromEnv)('HANDOFF_DEV_ACCESS_TOKEN', { default: null }),
11
- figma_project_id: (0, from_env_1.fromEnv)('HANDOFF_FIGMA_PROJECT_ID', { default: null }),
10
+ integrations: {
11
+ figma: {
12
+ projectId: (0, from_env_1.fromEnv)('HANDOFF_FIGMA_PROJECT_ID', { default: null }),
13
+ accessToken: (0, from_env_1.fromEnv)('HANDOFF_DEV_ACCESS_TOKEN', { default: null }),
14
+ },
15
+ },
12
16
  runtime: {
13
17
  mode: 'workspace',
14
18
  registryConnection: {
@@ -16,8 +20,7 @@ const defaultConfig = () => ({
16
20
  accessToken: (0, from_env_1.fromEnv)('HANDOFF_REGISTRY_ACCESS_TOKEN', { default: '' }),
17
21
  },
18
22
  registry: {
19
- databaseUrl: (0, from_env_1.fromEnv)('DATABASE_URL'),
20
- assetStorage: { token: (0, from_env_1.fromEnv)('BLOB_READ_WRITE_TOKEN') },
23
+ database: { url: (0, from_env_1.fromEnv)('DATABASE_URL') },
21
24
  },
22
25
  },
23
26
  exportsOutputDirectory: (0, from_env_1.fromEnv)('HANDOFF_OUTPUT_DIR', { default: 'exported' }),
@@ -22,3 +22,7 @@ export declare const fromEnv: <T = string>(name: string, options?: {
22
22
  default: T;
23
23
  }) => EnvReference<T>;
24
24
  export declare const isEnvReference: (value: unknown) => value is EnvReference<unknown>;
25
+ /** Replace each reference nested in `options` with its value from `env`. An unset or empty variable becomes `undefined`. */
26
+ export declare const resolveEnvReferences: (options: Record<string, unknown>, env: NodeJS.ProcessEnv) => Record<string, unknown>;
27
+ /** The variable names that the references nested in `options` use, for deployment instructions. */
28
+ export declare const envReferenceNames: (options: Record<string, unknown>) => string[];
@@ -1,7 +1,44 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.isEnvReference = exports.fromEnv = void 0;
3
+ exports.envReferenceNames = exports.resolveEnvReferences = exports.isEnvReference = exports.fromEnv = void 0;
4
4
  const fromEnv = (name, options) => options === undefined ? { $env: name } : { $env: name, default: options.default };
5
5
  exports.fromEnv = fromEnv;
6
6
  const isEnvReference = (value) => value !== null && typeof value === 'object' && Object.prototype.hasOwnProperty.call(value, '$env');
7
7
  exports.isEnvReference = isEnvReference;
8
+ const isPlainRecord = (value) => value !== null && typeof value === 'object' && !Array.isArray(value);
9
+ /** Replace each reference nested in `options` with its value from `env`. An unset or empty variable becomes `undefined`. */
10
+ const resolveEnvReferences = (options, env) => {
11
+ const resolve = (value) => {
12
+ var _a;
13
+ if ((0, exports.isEnvReference)(value)) {
14
+ const resolved = typeof value.$env === 'string' ? (_a = env[value.$env]) === null || _a === void 0 ? void 0 : _a.trim() : undefined;
15
+ return resolved || undefined;
16
+ }
17
+ if (Array.isArray(value))
18
+ return value.map(resolve);
19
+ if (isPlainRecord(value))
20
+ return Object.fromEntries(Object.entries(value).map(([key, entry]) => [key, resolve(entry)]));
21
+ return value;
22
+ };
23
+ return resolve(options);
24
+ };
25
+ exports.resolveEnvReferences = resolveEnvReferences;
26
+ /** The variable names that the references nested in `options` use, for deployment instructions. */
27
+ const envReferenceNames = (options) => {
28
+ const names = new Set();
29
+ const collect = (value) => {
30
+ if ((0, exports.isEnvReference)(value)) {
31
+ if (typeof value.$env === 'string')
32
+ names.add(value.$env);
33
+ }
34
+ else if (Array.isArray(value)) {
35
+ value.forEach(collect);
36
+ }
37
+ else if (isPlainRecord(value)) {
38
+ Object.values(value).forEach(collect);
39
+ }
40
+ };
41
+ collect(options);
42
+ return [...names];
43
+ };
44
+ exports.envReferenceNames = envReferenceNames;
@@ -19,8 +19,8 @@ const normalizeAppConfig = (app) => {
19
19
  return Object.assign(Object.assign({}, rest), { google_tag_manager: googleTagManager !== null && googleTagManager !== void 0 ? googleTagManager : app.google_tag_manager, type_copy: typeCopy !== null && typeCopy !== void 0 ? typeCopy : app.type_copy, type_sort: typeSort !== null && typeSort !== void 0 ? typeSort : app.type_sort, color_sort: colorSort !== null && colorSort !== void 0 ? colorSort : app.color_sort, component_sort: componentSort !== null && componentSort !== void 0 ? componentSort : app.component_sort, base_path: basePath !== null && basePath !== void 0 ? basePath : app.base_path });
20
20
  };
21
21
  const normalizeConfig = (config) => {
22
- const { devAccessToken, figmaProjectId, assetsZipLinks, app } = config, rest = __rest(config, ["devAccessToken", "figmaProjectId", "assetsZipLinks", "app"]);
23
- return Object.assign(Object.assign({}, rest), { dev_access_token: devAccessToken !== undefined ? devAccessToken : config.dev_access_token, figma_project_id: figmaProjectId !== undefined ? figmaProjectId : config.figma_project_id, assets_zip_links: assetsZipLinks !== null && assetsZipLinks !== void 0 ? assetsZipLinks : config.assets_zip_links, app: normalizeAppConfig(app) });
22
+ const { assetsZipLinks, app } = config, rest = __rest(config, ["assetsZipLinks", "app"]);
23
+ return Object.assign(Object.assign({}, rest), { assets_zip_links: assetsZipLinks !== null && assetsZipLinks !== void 0 ? assetsZipLinks : config.assets_zip_links, app: normalizeAppConfig(app) });
24
24
  };
25
25
  exports.normalizeConfig = normalizeConfig;
26
26
  /**
@@ -5,6 +5,5 @@ export { CONFIG_FILE_PREFERENCE, HandoffConfigError, initConfig, initConfigWithM
5
5
  export type { ConfigLoadContext } from './loader';
6
6
  export { defineConfig } from './helpers';
7
7
  export { initRuntimeConfig } from './runtime';
8
- export { validateConfig } from './validator';
9
8
  export { addToCatalog, isIncluded } from './catalog-include';
10
9
  export type { AddToCatalogResult } from './catalog-include';
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.isIncluded = exports.addToCatalog = exports.validateConfig = exports.initRuntimeConfig = exports.defineConfig = exports.resolveProfileSelection = exports.initConfigWithMetadata = exports.initConfig = exports.HandoffConfigError = exports.CONFIG_FILE_PREFERENCE = exports.loadProfileEnv = exports.isMcpEnabled = exports.getClientConfig = exports.defaultConfig = void 0;
3
+ exports.isIncluded = exports.addToCatalog = exports.initRuntimeConfig = exports.defineConfig = exports.resolveProfileSelection = exports.initConfigWithMetadata = exports.initConfig = exports.HandoffConfigError = exports.CONFIG_FILE_PREFERENCE = exports.loadProfileEnv = exports.isMcpEnabled = exports.getClientConfig = exports.defaultConfig = void 0;
4
4
  // Config defaults and client config
5
5
  var defaults_1 = require("./defaults");
6
6
  Object.defineProperty(exports, "defaultConfig", { enumerable: true, get: function () { return defaults_1.defaultConfig; } });
@@ -22,9 +22,6 @@ Object.defineProperty(exports, "defineConfig", { enumerable: true, get: function
22
22
  // Runtime config resolution
23
23
  var runtime_1 = require("./runtime");
24
24
  Object.defineProperty(exports, "initRuntimeConfig", { enumerable: true, get: function () { return runtime_1.initRuntimeConfig; } });
25
- // Config validation
26
- var validator_1 = require("./validator");
27
- Object.defineProperty(exports, "validateConfig", { enumerable: true, get: function () { return validator_1.validateConfig; } });
28
25
  // Registering catalog item directories under `catalog.include`
29
26
  var catalog_include_1 = require("./catalog-include");
30
27
  Object.defineProperty(exports, "addToCatalog", { enumerable: true, get: function () { return catalog_include_1.addToCatalog; } });