handoff-app 2.0.0-9 → 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 (303) hide show
  1. package/{Changelog.md → CHANGELOG.md} +85 -1
  2. package/{License.md → LICENSE.md} +1 -1
  3. package/README.md +813 -406
  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/cli/create.js +1 -2
  55. package/dist/commands/build/index.d.ts +1 -1
  56. package/dist/commands/build/index.js +1 -1
  57. package/dist/commands/utils.d.ts +3 -3
  58. package/dist/config/defaults.js +7 -4
  59. package/dist/config/from-env.d.ts +4 -0
  60. package/dist/config/from-env.js +38 -1
  61. package/dist/config/helpers.js +2 -2
  62. package/dist/config/index.d.ts +0 -1
  63. package/dist/config/index.js +1 -4
  64. package/dist/config/loader.js +26 -2
  65. package/dist/config/resolve-env.d.ts +2 -1
  66. package/dist/config/resolve-env.js +28 -12
  67. package/dist/config/runtime.js +1 -1
  68. package/dist/index.d.ts +12 -7
  69. package/dist/index.js +18 -22
  70. package/dist/nav/index.d.ts +2 -2
  71. package/dist/nav/registry.d.ts +3 -3
  72. package/dist/nav/registry.js +1 -1
  73. package/dist/nav/workspace.d.ts +3 -3
  74. package/dist/nav/workspace.js +1 -1
  75. package/dist/pipeline/figma.d.ts +13 -2
  76. package/dist/pipeline/figma.js +86 -46
  77. package/dist/pipeline/styles.js +1 -1
  78. package/dist/pipeline/validation.d.ts +1 -1
  79. package/dist/pipeline/validation.js +3 -3
  80. package/dist/providers.d.ts +10 -0
  81. package/dist/providers.js +13 -0
  82. package/dist/registry/asset-storage/define.d.ts +5 -5
  83. package/dist/registry/asset-storage/define.js +4 -4
  84. package/dist/registry/asset-storage/resolve.d.ts +9 -19
  85. package/dist/registry/asset-storage/resolve.js +33 -21
  86. package/dist/registry/asset-storage/types.d.ts +37 -17
  87. package/dist/registry/asset-storage/types.js +5 -5
  88. package/dist/registry/auth/ai-keys.d.ts +32 -0
  89. package/dist/registry/auth/ai-keys.js +69 -0
  90. package/dist/registry/auth/crypto.d.ts +17 -0
  91. package/dist/registry/auth/crypto.js +61 -1
  92. package/dist/registry/auth/index.d.ts +2 -1
  93. package/dist/registry/auth/index.js +11 -2
  94. package/dist/registry/checkout/index.d.ts +2 -1
  95. package/dist/registry/checkout/index.js +57 -127
  96. package/dist/registry/client.js +53 -17
  97. package/dist/registry/db/driver.js +3 -3
  98. package/dist/registry/db/schema.d.ts +261 -3
  99. package/dist/registry/db/schema.js +29 -4
  100. package/dist/registry/email/define.d.ts +10 -0
  101. package/dist/registry/email/define.js +13 -0
  102. package/dist/registry/email/providers/smtp.d.ts +10 -0
  103. package/dist/registry/email/providers/smtp.js +73 -0
  104. package/dist/registry/email/resolve.d.ts +28 -0
  105. package/dist/registry/email/resolve.js +74 -0
  106. package/dist/registry/email/types.d.ts +24 -0
  107. package/dist/registry/email/types.js +6 -0
  108. package/dist/registry/path.d.ts +2 -0
  109. package/dist/registry/path.js +11 -1
  110. package/dist/registry/publish/package.js +8 -0
  111. package/dist/registry/transfer.d.ts +4 -0
  112. package/dist/store/registry.d.ts +3 -3
  113. package/dist/store/registry.js +4 -4
  114. package/dist/transformers/preview/component/builder.d.ts +1 -9
  115. package/dist/transformers/preview/component/builder.js +17 -17
  116. package/dist/transformers/preview/component/css.d.ts +2 -1
  117. package/dist/transformers/preview/component/css.js +8 -5
  118. package/dist/transformers/preview/component/html.d.ts +3 -1
  119. package/dist/transformers/preview/component/html.js +6 -2
  120. package/dist/transformers/preview/component/javascript.d.ts +2 -1
  121. package/dist/transformers/preview/component/javascript.js +10 -4
  122. package/dist/transformers/utils/build.d.ts +5 -0
  123. package/dist/transformers/utils/build.js +21 -4
  124. package/dist/types/config.d.ts +129 -18
  125. package/dist/utils/path.d.ts +1 -5
  126. package/dist/utils/path.js +6 -23
  127. package/docs/api.md +6 -4
  128. package/docs/cli.md +275 -54
  129. package/drizzle/0007_add_ai_keys.sql +11 -0
  130. package/drizzle/0008_add_source_dir.sql +2 -0
  131. package/drizzle/meta/0007_snapshot.json +2013 -0
  132. package/drizzle/meta/0008_snapshot.json +2025 -0
  133. package/drizzle/meta/_journal.json +14 -0
  134. package/package.json +19 -8
  135. package/src/ai/README.md +42 -0
  136. package/src/ai/connections.ts +189 -0
  137. package/src/ai/define.ts +15 -0
  138. package/src/ai/types.ts +30 -0
  139. package/src/app/components/AiAssistant/AiAssistantPanel.tsx +144 -0
  140. package/src/app/components/AiAssistant/AiAssistantProvider.tsx +215 -0
  141. package/src/app/components/AiAssistant/AiConversation.tsx +737 -0
  142. package/src/app/components/AiAssistant/AiToolResult.tsx +99 -0
  143. package/src/app/components/AiAssistant/dockState.ts +86 -0
  144. package/src/app/components/AiAssistant/toolLabel.ts +24 -0
  145. package/src/app/components/AiAssistant/useAiChat.ts +147 -0
  146. package/src/app/components/AiAssistant/useAiConnections.ts +86 -0
  147. package/src/app/components/AiAssistant/usePageContext.ts +63 -0
  148. package/src/app/components/Auth/AccountLayout.tsx +58 -19
  149. package/src/app/components/Auth/AuthShell.tsx +27 -33
  150. package/src/app/components/Auth/RegistryInstallGate.tsx +4 -8
  151. package/src/app/components/Auth/installStatus.ts +29 -0
  152. package/src/app/components/Component/Preview.tsx +5 -2
  153. package/src/app/components/Foundations/ColorGrid.tsx +13 -14
  154. package/src/app/components/Layout/Header.tsx +21 -15
  155. package/src/app/components/Layout/Main.tsx +2 -2
  156. package/src/app/components/Markdown/MarkdownComponents.tsx +30 -32
  157. package/src/app/components/McpIntegration/McpConfigDialog.tsx +12 -51
  158. package/src/app/components/Navigation/MobileNav.tsx +2 -2
  159. package/src/app/components/Navigation/SideNav.tsx +1 -15
  160. package/src/app/components/context/NavProvider.tsx +7 -3
  161. package/src/app/components/ui/attachment.tsx +50 -0
  162. package/src/app/components/ui/bubble.tsx +57 -0
  163. package/src/app/components/ui/button.tsx +3 -1
  164. package/src/app/components/ui/confirm-dialog.tsx +87 -0
  165. package/src/app/components/ui/dialog.tsx +7 -2
  166. package/src/app/components/ui/marker.tsx +56 -0
  167. package/src/app/components/ui/sidebar.tsx +3 -1
  168. package/src/app/components/util/index.ts +3 -12
  169. package/src/app/css/index.css +64 -0
  170. package/src/app/lib/ai/auth.ts +33 -0
  171. package/src/app/lib/ai/context.ts +99 -0
  172. package/src/app/lib/ai/model.ts +79 -0
  173. package/src/app/lib/ai/prompt.ts +36 -0
  174. package/src/app/lib/ai/resolve.ts +97 -0
  175. package/src/app/lib/ai/tools.ts +120 -0
  176. package/src/app/lib/api/methods.ts +13 -0
  177. package/src/app/lib/asset-storage.ts +35 -36
  178. package/src/app/lib/auth/api.ts +4 -12
  179. package/src/app/lib/auth/email.ts +96 -23
  180. package/src/app/lib/docs-api/page-rendering.ts +1 -1
  181. package/src/app/lib/docs-api/registry.ts +2 -3
  182. package/src/app/lib/docs-api/runtime-config.ts +47 -21
  183. package/src/app/lib/mcp/components.ts +523 -0
  184. package/src/app/lib/mcp/page-url.ts +24 -0
  185. package/src/app/lib/mcp/pages.ts +84 -0
  186. package/src/app/lib/mcp/result.ts +33 -0
  187. package/src/app/lib/mcp/server.ts +39 -318
  188. package/src/app/lib/mcp/tokens.ts +334 -0
  189. package/src/app/lib/mcp/tool.ts +19 -0
  190. package/src/app/lib/registry-api/asset-transfer.ts +119 -11
  191. package/src/app/lib/registry-api/errors.ts +2 -0
  192. package/src/app/lib/registry-api/handler.ts +7 -1
  193. package/src/app/lib/registry-api/transfer.ts +28 -6
  194. package/src/app/lib/server-module.ts +34 -0
  195. package/src/app/lib/utils.ts +20 -0
  196. package/src/app/next.config.mjs +26 -24
  197. package/src/app/pages/[...slug]/index.tsx +2 -1
  198. package/src/app/pages/_app.tsx +6 -2
  199. package/src/app/pages/_document.tsx +6 -0
  200. package/src/app/pages/account/ai.tsx +183 -0
  201. package/src/app/pages/account/index.tsx +68 -68
  202. package/src/app/pages/account/tokens.tsx +37 -7
  203. package/src/app/pages/account/users.tsx +75 -15
  204. package/src/app/pages/api/account/ai/keys/[connectionId].ts +60 -0
  205. package/src/app/pages/api/account/ai/keys/index.ts +20 -0
  206. package/src/app/pages/api/admin/users/[id]/[action].ts +80 -11
  207. package/src/app/pages/api/admin/users/index.ts +2 -2
  208. package/src/app/pages/api/ai/chat.ts +115 -0
  209. package/src/app/pages/api/ai/connections.ts +23 -0
  210. package/src/app/pages/api/auth/request-reset.ts +14 -16
  211. package/src/app/pages/api/docs/nav.json.ts +1 -1
  212. package/src/app/pages/api/install.ts +3 -3
  213. package/src/app/pages/api/mcp.ts +6 -0
  214. package/src/app/pages/api/registry/transfer/assets/blobs/[hash]/complete.ts +11 -0
  215. package/src/app/pages/api/registry/transfer/assets/blobs/[hash]/upload.ts +12 -0
  216. package/src/app/pages/api/registry/transfer/assets/blobs/[hash].ts +1 -1
  217. package/src/app/pages/cli/device.tsx +10 -5
  218. package/src/app/pages/foundations/colors.tsx +9 -8
  219. package/src/app/pages/foundations/index.tsx +3 -3
  220. package/src/app/pages/index.tsx +2 -2
  221. package/src/app/pages/install.tsx +7 -2
  222. package/src/app/pages/login.tsx +32 -27
  223. package/src/app/pages/reset-password.tsx +21 -20
  224. package/src/app/pages/system/component/[component]/index.tsx +2 -5
  225. package/src/app/pages/system/index.tsx +11 -11
  226. package/src/app/pages/system/pattern/[pattern]/index.tsx +2 -2
  227. package/src/app/pages/system/tokens/components/[component]/index.tsx +0 -4
  228. package/src/app-builder/README.md +2 -1
  229. package/src/app-builder/build.ts +218 -73
  230. package/src/app-builder/client-config.ts +3 -2
  231. package/src/app-builder/paths.ts +3 -22
  232. package/src/app-builder/static-export.ts +1 -1
  233. package/src/app-builder/vercel-output.ts +5 -4
  234. package/src/cache/README.md +4 -1
  235. package/src/cache/build-cache.ts +128 -43
  236. package/src/cache/build-inputs.ts +28 -0
  237. package/src/cache/index.ts +4 -0
  238. package/src/cli/create.ts +1 -2
  239. package/src/commands/build/index.ts +1 -1
  240. package/src/config/README.md +10 -9
  241. package/src/config/defaults.ts +7 -4
  242. package/src/config/from-env.ts +33 -0
  243. package/src/config/helpers.ts +1 -3
  244. package/src/config/index.ts +0 -3
  245. package/src/config/loader.ts +25 -3
  246. package/src/config/resolve-env.ts +28 -15
  247. package/src/config/runtime.ts +1 -1
  248. package/src/index.ts +33 -29
  249. package/src/nav/index.ts +2 -2
  250. package/src/nav/registry.ts +3 -3
  251. package/src/nav/workspace.ts +3 -3
  252. package/src/pipeline/README.md +1 -1
  253. package/src/pipeline/figma.ts +98 -49
  254. package/src/pipeline/styles.ts +1 -1
  255. package/src/pipeline/validation.ts +3 -3
  256. package/src/providers.ts +18 -0
  257. package/src/registry/asset-storage/define.ts +6 -6
  258. package/src/registry/asset-storage/resolve.ts +38 -37
  259. package/src/registry/asset-storage/types.ts +39 -17
  260. package/src/registry/auth/ai-keys.ts +62 -0
  261. package/src/registry/auth/crypto.ts +63 -1
  262. package/src/registry/auth/index.ts +11 -1
  263. package/src/registry/checkout/index.ts +60 -138
  264. package/src/registry/client.ts +60 -20
  265. package/src/registry/db/driver.ts +2 -2
  266. package/src/registry/db/schema.ts +49 -22
  267. package/src/registry/email/define.ts +15 -0
  268. package/src/registry/email/providers/smtp.ts +39 -0
  269. package/src/registry/email/resolve.ts +94 -0
  270. package/src/registry/email/types.ts +28 -0
  271. package/src/registry/path.ts +11 -0
  272. package/src/registry/publish/package.ts +9 -0
  273. package/src/registry/transfer.ts +4 -0
  274. package/src/store/registry.ts +7 -7
  275. package/src/transformers/preview/component/builder.ts +19 -25
  276. package/src/transformers/preview/component/css.ts +9 -4
  277. package/src/transformers/preview/component/html.ts +6 -2
  278. package/src/transformers/preview/component/javascript.ts +14 -4
  279. package/src/transformers/utils/build.ts +16 -3
  280. package/src/types/config.ts +142 -20
  281. package/src/utils/README.md +1 -1
  282. package/src/utils/path.ts +6 -29
  283. package/templates/default/handoff.config.js.tpl +10 -2
  284. package/templates/default/handoff.config.ts.tpl +10 -2
  285. package/templates/default/handoff.config.with-examples.js.tpl +10 -2
  286. package/templates/default/handoff.config.with-examples.ts.tpl +10 -2
  287. package/templates/default/package.json.tpl +2 -2
  288. package/.editorconfig +0 -19
  289. package/.eslintrc.json +0 -4
  290. package/.prettierignore +0 -0
  291. package/.prettierrc.json +0 -8
  292. package/.vscode/settings.json +0 -17
  293. package/dist/config/validator.d.ts +0 -9
  294. package/dist/config/validator.js +0 -24
  295. package/dist/registry/asset-storage/adapters/vercel-blob.d.ts +0 -17
  296. package/dist/registry/asset-storage/adapters/vercel-blob.js +0 -87
  297. package/src/app/components/old/Header.tsx +0 -89
  298. package/src/app/lib/mcp/shape.ts +0 -596
  299. package/src/app/pages/assets/fonts.tsx +0 -129
  300. package/src/app/pages/assets/index.tsx +0 -91
  301. package/src/app/pages/assets/logos.tsx +0 -75
  302. package/src/config/validator.ts +0 -22
  303. package/src/registry/asset-storage/adapters/vercel-blob.ts +0 -51
@@ -1,6 +1,7 @@
1
1
  import react from '@vitejs/plugin-react';
2
2
  import { Types as CoreTypes } from 'handoff-core';
3
3
  import { InlineConfig, PluginOption, build as viteBuild } from 'vite';
4
+ import { BuildInputs, withBuildInputs } from '../../../cache/build-inputs';
4
5
  import type { RendererKind, SourceFormat } from '../../../catalog/renderers';
5
6
  import Handoff from '../../../index';
6
7
  import { Logger } from '../../../utils/logger';
@@ -35,6 +36,7 @@ const RENDERER_PLUGINS: Record<RendererKind, PreviewPlugins> = {
35
36
  * @param data - The result of transforming component tokens.
36
37
  * @param handoff - The Handoff configuration object.
37
38
  * @param components - Optional file components object.
39
+ * @param inputs - Collects the files that the build reads, for the build cache.
38
40
  * @returns A promise that resolves to the transformed component tokens result.
39
41
  * @throws Will throw an error if the Vite build process fails.
40
42
  *
@@ -46,7 +48,8 @@ const RENDERER_PLUGINS: Record<RendererKind, PreviewPlugins> = {
46
48
  export const buildPreviews = async (
47
49
  data: TransformComponentTokensResult,
48
50
  handoff: Handoff,
49
- components?: CoreTypes.IDocumentationObject['components']
51
+ components?: CoreTypes.IDocumentationObject['components'],
52
+ inputs?: BuildInputs
50
53
  ): Promise<TransformComponentTokensResult> => {
51
54
  if (!data.entries?.template) return data;
52
55
 
@@ -87,8 +90,9 @@ export const buildPreviews = async (
87
90
  viteConfig = handoff.config.hooks.htmlBuildConfig(viteConfig);
88
91
  }
89
92
 
90
- await viteBuild(viteConfig);
93
+ await viteBuild(withBuildInputs(viteConfig, inputs));
91
94
  } catch (error) {
95
+ if (inputs) inputs.failed = true;
92
96
  Logger.error(`Error building component previews: ${data.entries.template}`, error);
93
97
  } finally {
94
98
  // Restore the original NODE_ENV value after vite build completes
@@ -1,6 +1,7 @@
1
1
  import fs from 'fs-extra';
2
2
  import path from 'path';
3
3
  import { InlineConfig, build as viteBuild } from 'vite';
4
+ import { BuildInputs, withBuildInputs } from '../../../cache/build-inputs';
4
5
  import Handoff from '../../../index';
5
6
  import { formatDurationMs } from '../../../utils/duration';
6
7
  import { Logger } from '../../../utils/logger';
@@ -17,6 +18,7 @@ export const MAIN_COMPONENT_JS_FILE = 'main.js';
17
18
  * @param options.entry - The entry file path for the bundle
18
19
  * @param options.outputPath - The directory where the bundle will be output
19
20
  * @param options.outputFilename - The name of the output file
21
+ * @param inputs - Collects the files that the build reads, for the build cache
20
22
  */
21
23
  const buildJsBundle = async (
22
24
  {
@@ -25,7 +27,8 @@ const buildJsBundle = async (
25
27
  outputFilename,
26
28
  format = 'cjs',
27
29
  }: { entry: string; outputPath: string; outputFilename: string; format?: 'cjs' | 'iife' },
28
- handoff: Handoff
30
+ handoff: Handoff,
31
+ inputs?: BuildInputs
29
32
  ) => {
30
33
  const absEntryPath = path.resolve(entry);
31
34
 
@@ -62,7 +65,7 @@ const buildJsBundle = async (
62
65
  viteConfig = handoff.config.hooks.jsBuildConfig(viteConfig);
63
66
  }
64
67
 
65
- await viteBuild(viteConfig);
68
+ await viteBuild(withBuildInputs(viteConfig, inputs));
66
69
  } finally {
67
70
  // Restore the original NODE_ENV value after vite build completes
68
71
  // This prevents interference with Next.js app building/running processes
@@ -84,7 +87,11 @@ const buildJsBundle = async (
84
87
  * @param handoff - The Handoff configuration object
85
88
  * @returns The updated component transformation result with JavaScript data
86
89
  */
87
- export const buildComponentJs = async (data: TransformComponentTokensResult, handoff: Handoff): Promise<TransformComponentTokensResult> => {
90
+ export const buildComponentJs = async (
91
+ data: TransformComponentTokensResult,
92
+ handoff: Handoff,
93
+ inputs?: BuildInputs
94
+ ): Promise<TransformComponentTokensResult> => {
88
95
  const id = data.id;
89
96
  const outputPath = getComponentOutputPath(handoff);
90
97
  const builtJsPath = path.resolve(outputPath, `${id}.js`);
@@ -111,9 +118,11 @@ export const buildComponentJs = async (data: TransformComponentTokensResult, han
111
118
  outputPath,
112
119
  outputFilename: `${id}.js`,
113
120
  },
114
- handoff
121
+ handoff,
122
+ inputs
115
123
  );
116
124
  } catch (e) {
125
+ if (inputs) inputs.failed = true;
117
126
  Logger.error(`Failed to bundle JS for component "${id}" (${id}.js):`, e);
118
127
  return data;
119
128
  }
@@ -126,6 +135,7 @@ export const buildComponentJs = async (data: TransformComponentTokensResult, han
126
135
  delete data['jsCompiled'];
127
136
  }
128
137
  } catch (e) {
138
+ if (inputs) inputs.failed = true;
129
139
  Logger.error(`JS build failed for component "${id}":`, e);
130
140
  }
131
141
 
@@ -88,6 +88,20 @@ function orderStyleImports(metafile: esbuild.Metafile, entryPath: string): strin
88
88
  * declared entry.
89
89
  */
90
90
  export async function collectStyleImports(entryPath: string, handoff: any): Promise<string[]> {
91
+ const metafile = await buildModuleGraph(entryPath, handoff);
92
+ return metafile ? orderStyleImports(metafile, entryPath) : [];
93
+ }
94
+
95
+ /**
96
+ * Every file in the module graph of an entry, stylesheets included, or `null` when the graph does
97
+ * not build.
98
+ */
99
+ export async function collectModuleInputs(entryPath: string, handoff: any): Promise<string[] | null> {
100
+ const metafile = await buildModuleGraph(entryPath, handoff);
101
+ return metafile ? Object.keys(metafile.inputs).map((input) => path.resolve(input)) : null;
102
+ }
103
+
104
+ async function buildModuleGraph(entryPath: string, handoff: any): Promise<esbuild.Metafile | null> {
91
105
  const baseConfig: esbuild.BuildOptions = {
92
106
  ...DEFAULT_SSR_BUILD_CONFIG,
93
107
  entryPoints: [entryPath],
@@ -99,10 +113,9 @@ export async function collectStyleImports(entryPath: string, handoff: any): Prom
99
113
  // The `empty` loader keeps stylesheets out of the JavaScript, and the metafile still records
100
114
  // them as inputs, so the graph carries the order.
101
115
  const result = await esbuild.build({ ...withStyleLoaders(hookedConfig), metafile: true });
102
-
103
- return result.metafile ? orderStyleImports(result.metafile, entryPath) : [];
116
+ return result.metafile ?? null;
104
117
  } catch {
105
- return [];
118
+ return null;
106
119
  }
107
120
  }
108
121
 
@@ -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>;
@@ -402,13 +520,14 @@ export interface ClientRuntimeConfig {
402
520
  /** Values available after config loading. Deferred references retain their names. */
403
521
  export type ResolvedConfig = Omit<
404
522
  Config,
405
- 'dev_access_token' | 'devAccessToken' | 'figma_project_id' | 'figmaProjectId' |
406
- 'exportsOutputDirectory' | 'sitesOutputDirectory' | 'useVariables' | 'app' | 'runtime'
523
+ | 'integrations'
524
+ | 'exportsOutputDirectory'
525
+ | 'sitesOutputDirectory'
526
+ | 'useVariables'
527
+ | 'app'
528
+ | 'runtime'
407
529
  > & {
408
- dev_access_token?: string | null;
409
- devAccessToken?: string | null;
410
- figma_project_id?: string | null;
411
- figmaProjectId?: string | null;
530
+ integrations?: { figma?: { projectId?: string | null; accessToken?: string | null } };
412
531
  exportsOutputDirectory?: string;
413
532
  sitesOutputDirectory?: string;
414
533
  useVariables?: boolean;
@@ -418,7 +537,10 @@ export type ResolvedConfig = Omit<
418
537
  };
419
538
  };
420
539
 
421
- 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
+ > & {
422
544
  runtime: ClientRuntimeConfig;
423
545
  };
424
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;