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
@@ -115,6 +115,108 @@ export type RuntimeMode = 'workspace' | 'registry';
115
115
  /** Format used when synthesizing local workspace declarations. */
116
116
  export type DeclarationFormat = 'ts' | 'js' | 'cjs';
117
117
 
118
+ /** How a connection's credential is supplied. Declared, never inferred from a missing key. */
119
+ export type AiCredentialKind = 'service' | 'user' | 'none';
120
+
121
+ /**
122
+ * An email option: a literal, baked into the build, or an environment reference, read by the deployed
123
+ * app at request time.
124
+ */
125
+ export type HandoffEmailOption<T> = T | RuntimeEnvReference<T>;
126
+
127
+ interface HandoffEmailBase {
128
+ /** Sender address, such as `Handoff <no-reply@example.com>`. Use a profile for per-environment values. */
129
+ from?: string;
130
+ }
131
+
132
+ /** Send through an SMTP server, such as Amazon SES, SendGrid, Postmark, Mailgun or Microsoft 365. */
133
+ export interface HandoffSmtpEmail extends HandoffEmailBase {
134
+ provider: 'smtp';
135
+ options: {
136
+ host: HandoffEmailOption<string>;
137
+ /** @default 465, or 587 when `secure` is false */
138
+ port?: HandoffEmailOption<number>;
139
+ /**
140
+ * Use TLS from the start of the connection. When false, the connection upgrades with STARTTLS if
141
+ * the server offers it. @default true, or false when `port` is set to a port other than 465
142
+ */
143
+ secure?: HandoffEmailOption<boolean>;
144
+ user?: HandoffEmailOption<string>;
145
+ /** Environment reference to the SMTP password. */
146
+ password?: RuntimeEnvReference<string>;
147
+ };
148
+ }
149
+
150
+ /** Send through a server-only module that default-exports a `defineEmailProvider()` result. */
151
+ export interface HandoffCustomEmail extends HandoffEmailBase {
152
+ provider: 'custom';
153
+ /** Server-only module path. It must be its own file so the build can trace it into the deployed registry. */
154
+ module: string;
155
+ /**
156
+ * Options passed to the provider factory. Literals are baked. Environment references are resolved
157
+ * at request time. `apiKey` and `password` must be environment references.
158
+ */
159
+ options?: Record<string, unknown>;
160
+ }
161
+
162
+ /** Registry email settings. `provider` selects the shape of `options`. */
163
+ export type HandoffEmailConfig = HandoffSmtpEmail | HandoffCustomEmail;
164
+
165
+ /**
166
+ * One AI connection the deployment declares. Connections are the whole surface: a reader can add a
167
+ * key for a declared connection and nothing else, so the server never calls a URL a reader chose.
168
+ *
169
+ * Every connection speaks the OpenAI-compatible `/chat/completions` API. A provider that fits
170
+ * nothing else names a server-only `module` instead of a `baseUrl`.
171
+ */
172
+ export interface HandoffAiConnection {
173
+ /** Stable id. A model is addressed as `<id>/<model>`. */
174
+ id: string;
175
+ /** Name shown to a reader in the model picker and on the account page. */
176
+ label: string;
177
+ /** OpenAI-compatible base URL, such as `https://api.openai.com/v1`. Omit only when `module` is set. */
178
+ baseUrl?: string;
179
+ /**
180
+ * Server-only module path default-exporting a `defineAiProvider()` result, for a provider that is
181
+ * not OpenAI-compatible. It must be its own file so the build can trace it into the deployed
182
+ * registry.
183
+ */
184
+ module?: string;
185
+ /**
186
+ * Environment reference to the deployment's own key. Its presence makes the connection a service
187
+ * key: the deployment pays and every reader can use it. The name is baked; the value is read at
188
+ * request time and never leaves the server.
189
+ */
190
+ apiKey?: RuntimeEnvReference<string>;
191
+ /**
192
+ * Set to `'user'` when each reader supplies their own key. Without it, a connection that needs a
193
+ * reader's key looks like one that needs none, and the reader gets a raw 401 instead of an
194
+ * instruction.
195
+ */
196
+ credential?: 'user';
197
+ /** The models this connection offers. A reader cannot use a model that is not listed. */
198
+ models?: string[];
199
+ /**
200
+ * Options passed to a `module` provider factory. Literals are baked. Environment references are
201
+ * resolved at request time, so secrets must be environment references.
202
+ */
203
+ options?: Record<string, unknown>;
204
+ }
205
+
206
+ /**
207
+ * The AI assistant block. Presence of the block opts the build in, and `enabled: false` turns it
208
+ * off. Like `mcp`, `enabled` is config-only and baked at build time, so a build without it leaves
209
+ * out both the chat route and the header control rather than showing a control that fails.
210
+ */
211
+ export interface HandoffAiConfig {
212
+ /** @default true when the `ai` block is present */
213
+ enabled?: boolean;
214
+ /** Declared connections. Merged by `id` with any supplied through `HANDOFF_AI_CONNECTIONS`. */
215
+ connections?: HandoffAiConnection[];
216
+ /** The `<connectionId>/<model>` used when a reader has not picked one. */
217
+ defaultModel?: string;
218
+ }
219
+
118
220
  /**
119
221
  * User-facing `runtime` configuration block. A single optional block that selects the runtime
120
222
  * mode and carries mode-specific host/connection settings.
@@ -135,6 +237,8 @@ export interface HandoffRuntimeConfig {
135
237
  * @default true
136
238
  */
137
239
  mcp?: boolean;
240
+ /** The docs AI assistant. Omit the block to leave it out of the build. See {@link HandoffAiConfig}. */
241
+ ai?: HandoffAiConfig;
138
242
  /** Workspace-mode settings. */
139
243
  workspace?: {
140
244
  entries?: ConfigEntries;
@@ -142,9 +246,9 @@ export interface HandoffRuntimeConfig {
142
246
  };
143
247
  /** Registry-mode host settings. Env-var values are stored as names, never as secrets. */
144
248
  registry?: {
145
- /** Environment reference to the database URL. @default fromEnv('DATABASE_URL') */
146
- databaseUrl?: RuntimeEnvReference<string>;
147
249
  database?: {
250
+ /** Environment reference to the database URL. @default fromEnv('DATABASE_URL') */
251
+ url?: RuntimeEnvReference<string>;
148
252
  /**
149
253
  * PostgreSQL is the supported database. `driver` selects the built-in connection driver — how
150
254
  * to connect, not which engine — over the same package-owned Postgres schema and migrations.
@@ -159,20 +263,26 @@ export interface HandoffRuntimeConfig {
159
263
  */
160
264
  assetStorage?: {
161
265
  /**
162
- * Active storage provider for new uploads. `database` keeps bytes inline in Postgres; use the
163
- * pre-packaged `vercel-blob` adapter or a `custom` module for object storage.
266
+ * Active storage provider for new uploads. `database` keeps bytes inline in Postgres; use a
267
+ * `custom` module for object storage.
164
268
  * @default "database"
165
269
  */
166
- adapter?: 'database' | 'vercel-blob' | 'custom';
167
- /** For `adapter: "custom"` - server-only module path default-exporting a `defineAssetStorage` adapter. */
270
+ provider?: 'database' | 'custom';
271
+ /** For `provider: "custom"` - server-only module path default-exporting a `defineStorageProvider()` result. */
168
272
  module?: string;
169
- /** For `adapter: "vercel-blob"` - environment reference to the Blob read/write token. @default fromEnv('BLOB_READ_WRITE_TOKEN') */
170
- token?: RuntimeEnvReference<string>;
171
273
  /** Max bytes kept inline in the database `bytea` column (larger uploads are rejected). @default 4194304 */
172
274
  maxInlineBytes?: number;
173
- /** Non-secret options, JSON-encoded into the bundle. Environment references are rejected. Pass variable names instead. */
275
+ /**
276
+ * Options passed to the provider factory. Literals are baked. Environment references are resolved
277
+ * at request time, so secrets must be environment references.
278
+ */
174
279
  options?: Record<string, unknown>;
175
280
  };
281
+ /**
282
+ * Invitation and password-reset email. Email stays off until `from` is set and the required
283
+ * options of the provider have values. Until then, invitation links are shown to an administrator.
284
+ */
285
+ email?: HandoffEmailConfig;
176
286
  };
177
287
  /**
178
288
  * Connected-workspace settings pointing at a remote registry. A connected workspace is
@@ -186,11 +296,19 @@ export interface HandoffRuntimeConfig {
186
296
  };
187
297
  }
188
298
 
299
+ /** Connections to external services. */
300
+ export interface ConfigIntegrations {
301
+ /** The Figma file that `fetch` reads foundations from. In a terminal, `fetch` asks for an empty value. */
302
+ figma?: {
303
+ /** The Figma file ID. @default fromEnv('HANDOFF_FIGMA_PROJECT_ID', { default: null }) */
304
+ projectId?: EnvValue<string | null>;
305
+ /** A Figma personal access token. Accepts only an environment reference. @default fromEnv('HANDOFF_DEV_ACCESS_TOKEN', { default: null }) */
306
+ accessToken?: EnvSecret<string | null>;
307
+ };
308
+ }
309
+
189
310
  export interface Config {
190
- dev_access_token?: EnvSecret<string | null>;
191
- devAccessToken?: EnvSecret<string | null>;
192
- figma_project_id?: EnvValue<string | null>;
193
- figmaProjectId?: EnvValue<string | null>;
311
+ integrations?: ConfigIntegrations;
194
312
  exportsOutputDirectory?: EnvValue<string>;
195
313
  sitesOutputDirectory?: EnvValue<string>;
196
314
  useVariables?: EnvValue<boolean>;
@@ -275,6 +393,9 @@ export interface Config {
275
393
 
276
394
  /**
277
395
  * Optional hook to override the SSR build configuration used in the ssrRenderPlugin
396
+ *
397
+ * Stylesheet loaders are re-applied after this hook, so a `.css`/`.scss`/`.sass` import stays out
398
+ * of the JavaScript graph whatever the hook returns.
278
399
  * @param config - The default esbuild configuration
279
400
  * @returns Modified esbuild configuration
280
401
  * @example
@@ -289,6 +410,9 @@ export interface Config {
289
410
 
290
411
  /**
291
412
  * Optional hook to override the client-side build configuration used in the ssrRenderPlugin
413
+ *
414
+ * Stylesheet loaders are re-applied after this hook, so a `.css`/`.scss`/`.sass` import stays out
415
+ * of the JavaScript graph whatever the hook returns.
292
416
  * @param config - The default esbuild configuration
293
417
  * @returns Modified esbuild configuration
294
418
  * @example
@@ -396,13 +520,14 @@ export interface ClientRuntimeConfig {
396
520
  /** Values available after config loading. Deferred references retain their names. */
397
521
  export type ResolvedConfig = Omit<
398
522
  Config,
399
- 'dev_access_token' | 'devAccessToken' | 'figma_project_id' | 'figmaProjectId' |
400
- 'exportsOutputDirectory' | 'sitesOutputDirectory' | 'useVariables' | 'app' | 'runtime'
523
+ | 'integrations'
524
+ | 'exportsOutputDirectory'
525
+ | 'sitesOutputDirectory'
526
+ | 'useVariables'
527
+ | 'app'
528
+ | 'runtime'
401
529
  > & {
402
- dev_access_token?: string | null;
403
- devAccessToken?: string | null;
404
- figma_project_id?: string | null;
405
- figmaProjectId?: string | null;
530
+ integrations?: { figma?: { projectId?: string | null; accessToken?: string | null } };
406
531
  exportsOutputDirectory?: string;
407
532
  sitesOutputDirectory?: string;
408
533
  useVariables?: boolean;
@@ -412,7 +537,10 @@ export type ResolvedConfig = Omit<
412
537
  };
413
538
  };
414
539
 
415
- export type ClientConfig = Pick<ResolvedConfig, 'app' | 'exportsOutputDirectory' | 'sitesOutputDirectory' | 'assets_zip_links' | 'useVariables'> & {
540
+ export type ClientConfig = Pick<
541
+ ResolvedConfig,
542
+ 'app' | 'exportsOutputDirectory' | 'sitesOutputDirectory' | 'assets_zip_links' | 'useVariables'
543
+ > & {
416
544
  runtime: ClientRuntimeConfig;
417
545
  };
418
546
 
@@ -8,7 +8,7 @@ Shared utility functions used across the codebase.
8
8
  |------|---------|
9
9
  | `logger.ts` | `Logger` class — timestamped, color-coded console logging |
10
10
  | `filter.ts` | `evaluateFilter()`, `filterAndSort()` — generic object filtering with logical operators |
11
- | `path.ts` | `generateFilesystemSafeId()` — creates safe directory names from paths |
11
+ | `path.ts` | `generateFilesystemSafeId()` — creates short, safe directory names (base name + path hash) from paths |
12
12
  | `fs.ts` | `findFilesByExtension()` — recursive file search by extension |
13
13
  | `markdown.ts` | `parseMarkdown()` — frontmatter parse without gray-matter's unbounded module cache |
14
14
  | `index.ts` | `filterOutNull()` type guard |
package/src/utils/path.ts CHANGED
@@ -1,35 +1,12 @@
1
+ import { createHash } from 'crypto';
1
2
  import path from 'path';
2
3
 
3
- /**
4
- * Generates a filesystem-safe directory name from an absolute path
5
- * @param workingPath - The absolute working path to transform
6
- * @returns A filesystem-safe string suitable for use as a directory name
7
- */
4
+ /** `<base name>-<path hash>`; the hash keeps directories with the same base name apart. */
8
5
  export function generateFilesystemSafeId(workingPath: string): string {
9
- // Normalize path separators to forward slashes for consistent processing
10
- let safeId = workingPath.replace(/\\/g, '/');
11
-
12
- // Replace forward slashes with dashes
13
- safeId = safeId.replace(/\//g, '-');
14
-
15
- // Remove or replace invalid filesystem characters
16
- safeId = safeId.replace(/[<>:"|?*\x00]/g, '-');
17
-
18
- // Remove leading/trailing dashes and spaces
19
- safeId = safeId.replace(/^[\s-]+|[\s-]+$/g, '');
20
-
21
- // Handle Windows reserved names (case-insensitive)
22
- const reservedNames = /^(CON|PRN|AUX|NUL|COM[1-9]|LPT[1-9])(\.|$)/i;
23
- if (reservedNames.test(safeId)) {
24
- safeId = `project-${safeId}`;
25
- }
26
-
27
- // Ensure we have at least one character
28
- if (!safeId) {
29
- safeId = 'default-project';
30
- }
31
-
32
- return safeId;
6
+ const baseName = path.basename(path.resolve(workingPath));
7
+ const name = baseName.replace(/[^A-Za-z0-9._-]/g, '-').replace(/^[.-]+|[.-]+$/g, '') || 'project';
8
+ const hash = createHash('sha256').update(normalizePathForCompare(workingPath)).digest('hex').slice(0, 8);
9
+ return `${name}-${hash}`;
33
10
  }
34
11
 
35
12
  /**
@@ -46,6 +46,15 @@ const config = {
46
46
  // subdirectories are each an item.
47
47
  // catalog: { include: ["components", "patterns"] },
48
48
 
49
+ // Figma: `fetch` reads the file ID and token from these variables. Defaults shown. If a value is
50
+ // empty, `fetch` asks for it. The token accepts only an environment reference.
51
+ // integrations: {
52
+ // figma: {
53
+ // projectId: fromEnv("HANDOFF_FIGMA_PROJECT_ID", { default: null }),
54
+ // accessToken: fromEnv("HANDOFF_DEV_ACCESS_TOKEN", { default: null }),
55
+ // },
56
+ // },
57
+
49
58
  // `mode` selects the runtime: "workspace" (no database) or "registry" (Postgres). The registry
50
59
  // block is read only by `build --target registry` and `db:migrate`, and secrets are referenced by
51
60
  // env var name. A named profile (`handoff.config.registry.*`, `--profile registry`) is a good home
@@ -54,8 +63,7 @@ const config = {
54
63
  mode: "workspace",
55
64
  // mcp: false,
56
65
  // registry: {
57
- // databaseUrl: fromEnv("DATABASE_URL"),
58
- // database: { driver: "pg" }, // 'pg' | 'neon'
66
+ // database: { url: fromEnv("DATABASE_URL"), driver: "pg" }, // driver: 'pg' | 'neon'
59
67
  // },
60
68
  },
61
69
 
@@ -44,6 +44,15 @@ export default defineConfig({
44
44
  // subdirectories are each an item.
45
45
  // catalog: { include: ["components", "patterns"] },
46
46
 
47
+ // Figma: `fetch` reads the file ID and token from these variables. Defaults shown. If a value is
48
+ // empty, `fetch` asks for it. The token accepts only an environment reference.
49
+ // integrations: {
50
+ // figma: {
51
+ // projectId: fromEnv("HANDOFF_FIGMA_PROJECT_ID", { default: null }),
52
+ // accessToken: fromEnv("HANDOFF_DEV_ACCESS_TOKEN", { default: null }),
53
+ // },
54
+ // },
55
+
47
56
  // `mode` selects the runtime: "workspace" (no database) or "registry" (Postgres). The registry
48
57
  // block is read only by `build --target registry` and `db:migrate`, and secrets are referenced by
49
58
  // env var name. A named profile (`handoff.config.registry.*`, `--profile registry`) is a good home
@@ -52,8 +61,7 @@ export default defineConfig({
52
61
  mode: "workspace",
53
62
  // mcp: false,
54
63
  // registry: {
55
- // databaseUrl: fromEnv("DATABASE_URL"),
56
- // database: { driver: "pg" }, // 'pg' | 'neon'
64
+ // database: { url: fromEnv("DATABASE_URL"), driver: "pg" }, // driver: 'pg' | 'neon'
57
65
  // },
58
66
  },
59
67
 
@@ -48,6 +48,15 @@ const config = {
48
48
  include: ["components/button"],
49
49
  },
50
50
 
51
+ // Figma: `fetch` reads the file ID and token from these variables. Defaults shown. If a value is
52
+ // empty, `fetch` asks for it. The token accepts only an environment reference.
53
+ // integrations: {
54
+ // figma: {
55
+ // projectId: fromEnv("HANDOFF_FIGMA_PROJECT_ID", { default: null }),
56
+ // accessToken: fromEnv("HANDOFF_DEV_ACCESS_TOKEN", { default: null }),
57
+ // },
58
+ // },
59
+
51
60
  // `mode` selects the runtime: "workspace" (no database) or "registry" (Postgres). The registry
52
61
  // block is read only by `build --target registry` and `db:migrate`, and secrets are referenced by
53
62
  // env var name. A named profile (`handoff.config.registry.*`, `--profile registry`) is a good home
@@ -56,8 +65,7 @@ const config = {
56
65
  mode: "workspace",
57
66
  // mcp: false,
58
67
  // registry: {
59
- // databaseUrl: fromEnv("DATABASE_URL"),
60
- // database: { driver: "pg" }, // 'pg' | 'neon'
68
+ // database: { url: fromEnv("DATABASE_URL"), driver: "pg" }, // driver: 'pg' | 'neon'
61
69
  // },
62
70
  },
63
71
 
@@ -46,6 +46,15 @@ export default defineConfig({
46
46
  include: ["components/button"],
47
47
  },
48
48
 
49
+ // Figma: `fetch` reads the file ID and token from these variables. Defaults shown. If a value is
50
+ // empty, `fetch` asks for it. The token accepts only an environment reference.
51
+ // integrations: {
52
+ // figma: {
53
+ // projectId: fromEnv("HANDOFF_FIGMA_PROJECT_ID", { default: null }),
54
+ // accessToken: fromEnv("HANDOFF_DEV_ACCESS_TOKEN", { default: null }),
55
+ // },
56
+ // },
57
+
49
58
  // `mode` selects the runtime: "workspace" (no database) or "registry" (Postgres). The registry
50
59
  // block is read only by `build --target registry` and `db:migrate`, and secrets are referenced by
51
60
  // env var name. A named profile (`handoff.config.registry.*`, `--profile registry`) is a good home
@@ -54,8 +63,7 @@ export default defineConfig({
54
63
  mode: "workspace",
55
64
  // mcp: false,
56
65
  // registry: {
57
- // databaseUrl: fromEnv("DATABASE_URL"),
58
- // database: { driver: "pg" }, // 'pg' | 'neon'
66
+ // database: { url: fromEnv("DATABASE_URL"), driver: "pg" }, // driver: 'pg' | 'neon'
59
67
  // },
60
68
  },
61
69
 
@@ -17,7 +17,7 @@
17
17
  "dependencies": {},
18
18
  "devDependencies": {},
19
19
  "engines": {
20
- "node": ">=16.0.0",
21
- "npm": ">=8.0.0"
20
+ "node": ">=22",
21
+ "npm": ">=10"
22
22
  }
23
23
  }
package/.editorconfig DELETED
@@ -1,19 +0,0 @@
1
- # EditorConfig is awesome: https://EditorConfig.org
2
-
3
- # top-most EditorConfig file
4
- root = true
5
-
6
- [*.{css,scss,sass}]
7
- charset = utf-8
8
- indent_style = tab
9
- indent_size = unset
10
- end_of_line = lf
11
- trim_trailing_whitespace = true
12
- insert_final_newline = true
13
-
14
- [*.{js,ts,jsx,tsx}]
15
- charset = utf-8
16
- indent_style = space
17
- end_of_line = lf
18
- trim_trailing_whitespace = true
19
- insert_final_newline = true
package/.eslintrc.json DELETED
@@ -1,4 +0,0 @@
1
- {
2
- "extends": ["next", "prettier"],
3
- "ignorePatterns": ["./src/app/out/**/*", "**/*.js"]
4
- }
package/.prettierignore DELETED
File without changes
package/.prettierrc.json DELETED
@@ -1,8 +0,0 @@
1
- {
2
- "trailingComma": "es5",
3
- "semi": true,
4
- "tabWidth": 2,
5
- "singleQuote": true,
6
- "jsxSingleQuote": true,
7
- "plugins": ["prettier-plugin-tailwindcss"]
8
- }
@@ -1,17 +0,0 @@
1
- {
2
- "editor.defaultFormatter": "esbenp.prettier-vscode",
3
- "[javascript]": { "editor.defaultFormatter": "esbenp.prettier-vscode" },
4
- "editor.formatOnSave": true,
5
- "editor.codeActionsOnSave": {
6
- "source.organizeImports": "explicit"
7
- },
8
- "[xml]": {
9
- "editor.defaultFormatter": "redhat.vscode-xml"
10
- },
11
- "[typescriptreact]": {
12
- "editor.defaultFormatter": "vscode.typescript-language-features"
13
- },
14
- "[markdown]": {
15
- "editor.defaultFormatter": "unifiedjs.vscode-remark"
16
- }
17
- }
@@ -1,9 +0,0 @@
1
- import { ResolvedConfig } from '../types/config';
2
- /**
3
- * Validates that the configuration has the required Figma credentials.
4
- *
5
- * @param config - The config to validate.
6
- * @returns The validated config (same reference).
7
- * @throws {Error} If required Figma credentials are missing.
8
- */
9
- export declare const validateConfig: (config: ResolvedConfig) => ResolvedConfig;
@@ -1,24 +0,0 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.validateConfig = void 0;
4
- const logger_1 = require("../utils/logger");
5
- /**
6
- * Validates that the configuration has the required Figma credentials.
7
- *
8
- * @param config - The config to validate.
9
- * @returns The validated config (same reference).
10
- * @throws {Error} If required Figma credentials are missing.
11
- */
12
- const validateConfig = (config) => {
13
- // TODO: Check to see if the exported folder exists before we run start
14
- if (!config.figma_project_id) {
15
- logger_1.Logger.error('Figma Project ID missing. Please set HANDOFF_FIGMA_PROJECT_ID or run "handoff-app fetch".');
16
- throw new Error('Cannot initialize configuration');
17
- }
18
- if (!config.dev_access_token) {
19
- logger_1.Logger.error('Figma Access Token missing. Please set HANDOFF_DEV_ACCESS_TOKEN or run "handoff-app fetch".');
20
- throw new Error('Cannot initialize configuration');
21
- }
22
- return config;
23
- };
24
- exports.validateConfig = validateConfig;
@@ -1,17 +0,0 @@
1
- /**
2
- * Pre-packaged Vercel Blob asset storage adapter.
3
- *
4
- * Ships with Handoff and is selected via `runtime.registry.assetStorage.adapter: "vercel-blob"`. The
5
- * read/write token is read from `process.env[tokenEnv]` at request time (never persisted in config).
6
- * Blobs are stored public and content-addressed by hash, so reads resolve to a direct provider URL
7
- * (a `redirect` result), keeping large payloads out of the serverless function body/response.
8
- *
9
- * The `@vercel/blob` SDK is loaded lazily so it is only pulled into memory (and traced into the
10
- * registry bundle) when this adapter is actually used.
11
- */
12
- import type { AssetStorage } from '../types';
13
- /** Build a Vercel Blob storage adapter bound to a token env-var name + non-secret options. */
14
- export declare const createVercelBlobStorage: (params: {
15
- tokenEnv: string;
16
- options: Record<string, unknown>;
17
- }) => AssetStorage;
@@ -1,87 +0,0 @@
1
- "use strict";
2
- /**
3
- * Pre-packaged Vercel Blob asset storage adapter.
4
- *
5
- * Ships with Handoff and is selected via `runtime.registry.assetStorage.adapter: "vercel-blob"`. The
6
- * read/write token is read from `process.env[tokenEnv]` at request time (never persisted in config).
7
- * Blobs are stored public and content-addressed by hash, so reads resolve to a direct provider URL
8
- * (a `redirect` result), keeping large payloads out of the serverless function body/response.
9
- *
10
- * The `@vercel/blob` SDK is loaded lazily so it is only pulled into memory (and traced into the
11
- * registry bundle) when this adapter is actually used.
12
- */
13
- var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
14
- if (k2 === undefined) k2 = k;
15
- var desc = Object.getOwnPropertyDescriptor(m, k);
16
- if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
17
- desc = { enumerable: true, get: function() { return m[k]; } };
18
- }
19
- Object.defineProperty(o, k2, desc);
20
- }) : (function(o, m, k, k2) {
21
- if (k2 === undefined) k2 = k;
22
- o[k2] = m[k];
23
- }));
24
- var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
25
- Object.defineProperty(o, "default", { enumerable: true, value: v });
26
- }) : function(o, v) {
27
- o["default"] = v;
28
- });
29
- var __importStar = (this && this.__importStar) || function (mod) {
30
- if (mod && mod.__esModule) return mod;
31
- var result = {};
32
- if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
33
- __setModuleDefault(result, mod);
34
- return result;
35
- };
36
- var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
37
- function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
38
- return new (P || (P = Promise))(function (resolve, reject) {
39
- function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
40
- function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
41
- function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
42
- step((generator = generator.apply(thisArg, _arguments || [])).next());
43
- });
44
- };
45
- Object.defineProperty(exports, "__esModule", { value: true });
46
- exports.createVercelBlobStorage = void 0;
47
- /** Build a Vercel Blob storage adapter bound to a token env-var name + non-secret options. */
48
- const createVercelBlobStorage = (params) => {
49
- const prefix = typeof params.options.prefix === 'string' && params.options.prefix.trim() ? params.options.prefix.trim() : 'handoff/assets';
50
- const resolveToken = () => {
51
- var _a;
52
- const token = (_a = process.env[params.tokenEnv]) === null || _a === void 0 ? void 0 : _a.trim();
53
- if (!token) {
54
- throw new Error(`Vercel Blob storage is configured but the token env var "${params.tokenEnv}" is not set. ` +
55
- 'Set it to a Blob read/write token, or change runtime.registry.assetStorage.token.');
56
- }
57
- return token;
58
- };
59
- return {
60
- put(_a) {
61
- return __awaiter(this, arguments, void 0, function* ({ hash, bytes, contentType }) {
62
- const { put } = yield Promise.resolve().then(() => __importStar(require('@vercel/blob')));
63
- // Content-addressed pathname + no random suffix so the same bytes map to a stable, dedupable key.
64
- const result = yield put(`${prefix}/${hash}`, bytes, {
65
- access: 'public',
66
- token: resolveToken(),
67
- contentType,
68
- addRandomSuffix: false,
69
- });
70
- return { storageRef: result.url };
71
- });
72
- },
73
- get(storageRef) {
74
- return __awaiter(this, void 0, void 0, function* () {
75
- // Public blob URLs are directly fetchable; redirect the client to avoid proxying bytes.
76
- return { kind: 'redirect', url: storageRef };
77
- });
78
- },
79
- delete(storageRef) {
80
- return __awaiter(this, void 0, void 0, function* () {
81
- const { del } = yield Promise.resolve().then(() => __importStar(require('@vercel/blob')));
82
- yield del(storageRef, { token: resolveToken() });
83
- });
84
- },
85
- };
86
- };
87
- exports.createVercelBlobStorage = createVercelBlobStorage;