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
@@ -15,6 +15,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
15
15
  exports.buildPreviews = void 0;
16
16
  const plugin_react_1 = __importDefault(require("@vitejs/plugin-react"));
17
17
  const vite_1 = require("vite");
18
+ const build_inputs_1 = require("../../../cache/build-inputs");
18
19
  const logger_1 = require("../../../utils/logger");
19
20
  const plugins_1 = require("../../plugins");
20
21
  const vite_config_1 = __importDefault(require("../../vite-config"));
@@ -37,6 +38,7 @@ const RENDERER_PLUGINS = {
37
38
  * @param data - The result of transforming component tokens.
38
39
  * @param handoff - The Handoff configuration object.
39
40
  * @param components - Optional file components object.
41
+ * @param inputs - Collects the files that the build reads, for the build cache.
40
42
  * @returns A promise that resolves to the transformed component tokens result.
41
43
  * @throws Will throw an error if the Vite build process fails.
42
44
  *
@@ -45,7 +47,7 @@ const RENDERER_PLUGINS = {
45
47
  * const result = await buildPreviews(transformedData, handoffConfig, fileComponents);
46
48
  * ```
47
49
  */
48
- const buildPreviews = (data, handoff, components) => __awaiter(void 0, void 0, void 0, function* () {
50
+ const buildPreviews = (data, handoff, components, inputs) => __awaiter(void 0, void 0, void 0, function* () {
49
51
  var _a, _b, _c;
50
52
  if (!((_a = data.entries) === null || _a === void 0 ? void 0 : _a.template))
51
53
  return data;
@@ -75,9 +77,11 @@ const buildPreviews = (data, handoff, components) => __awaiter(void 0, void 0, v
75
77
  if ((_c = (_b = handoff === null || handoff === void 0 ? void 0 : handoff.config) === null || _b === void 0 ? void 0 : _b.hooks) === null || _c === void 0 ? void 0 : _c.htmlBuildConfig) {
76
78
  viteConfig = handoff.config.hooks.htmlBuildConfig(viteConfig);
77
79
  }
78
- yield (0, vite_1.build)(viteConfig);
80
+ yield (0, vite_1.build)((0, build_inputs_1.withBuildInputs)(viteConfig, inputs));
79
81
  }
80
82
  catch (error) {
83
+ if (inputs)
84
+ inputs.failed = true;
81
85
  logger_1.Logger.error(`Error building component previews: ${data.entries.template}`, error);
82
86
  }
83
87
  finally {
@@ -1,3 +1,4 @@
1
+ import { BuildInputs } from '../../../cache/build-inputs';
1
2
  import Handoff from '../../../index';
2
3
  import { TransformComponentTokensResult } from '../types';
3
4
  export declare const MAIN_COMPONENT_JS_FILE = "main.js";
@@ -10,7 +11,7 @@ export declare const MAIN_COMPONENT_JS_FILE = "main.js";
10
11
  * @param handoff - The Handoff configuration object
11
12
  * @returns The updated component transformation result with JavaScript data
12
13
  */
13
- export declare const buildComponentJs: (data: TransformComponentTokensResult, handoff: Handoff) => Promise<TransformComponentTokensResult>;
14
+ export declare const buildComponentJs: (data: TransformComponentTokensResult, handoff: Handoff, inputs?: BuildInputs) => Promise<TransformComponentTokensResult>;
14
15
  /**
15
16
  * Builds the global preview script artifact (`component/main.js`) from the configured workspace JS
16
17
  * entry.
@@ -16,6 +16,7 @@ exports.buildMainJS = exports.buildComponentJs = exports.MAIN_COMPONENT_JS_FILE
16
16
  const fs_extra_1 = __importDefault(require("fs-extra"));
17
17
  const path_1 = __importDefault(require("path"));
18
18
  const vite_1 = require("vite");
19
+ const build_inputs_1 = require("../../../cache/build-inputs");
19
20
  const duration_1 = require("../../../utils/duration");
20
21
  const logger_1 = require("../../../utils/logger");
21
22
  const vite_config_1 = __importDefault(require("../../vite-config"));
@@ -28,8 +29,9 @@ exports.MAIN_COMPONENT_JS_FILE = 'main.js';
28
29
  * @param options.entry - The entry file path for the bundle
29
30
  * @param options.outputPath - The directory where the bundle will be output
30
31
  * @param options.outputFilename - The name of the output file
32
+ * @param inputs - Collects the files that the build reads, for the build cache
31
33
  */
32
- const buildJsBundle = (_a, handoff_1) => __awaiter(void 0, [_a, handoff_1], void 0, function* ({ entry, outputPath, outputFilename, format = 'cjs', }, handoff) {
34
+ const buildJsBundle = (_a, handoff_1, inputs_1) => __awaiter(void 0, [_a, handoff_1, inputs_1], void 0, function* ({ entry, outputPath, outputFilename, format = 'cjs', }, handoff, inputs) {
33
35
  var _b, _c, _d;
34
36
  const absEntryPath = path_1.default.resolve(entry);
35
37
  // Store the current NODE_ENV value before vite build
@@ -51,7 +53,7 @@ const buildJsBundle = (_a, handoff_1) => __awaiter(void 0, [_a, handoff_1], void
51
53
  if ((_d = (_c = handoff === null || handoff === void 0 ? void 0 : handoff.config) === null || _c === void 0 ? void 0 : _c.hooks) === null || _d === void 0 ? void 0 : _d.jsBuildConfig) {
52
54
  viteConfig = handoff.config.hooks.jsBuildConfig(viteConfig);
53
55
  }
54
- yield (0, vite_1.build)(viteConfig);
56
+ yield (0, vite_1.build)((0, build_inputs_1.withBuildInputs)(viteConfig, inputs));
55
57
  }
56
58
  finally {
57
59
  // Restore the original NODE_ENV value after vite build completes
@@ -74,7 +76,7 @@ const buildJsBundle = (_a, handoff_1) => __awaiter(void 0, [_a, handoff_1], void
74
76
  * @param handoff - The Handoff configuration object
75
77
  * @returns The updated component transformation result with JavaScript data
76
78
  */
77
- const buildComponentJs = (data, handoff) => __awaiter(void 0, void 0, void 0, function* () {
79
+ const buildComponentJs = (data, handoff, inputs) => __awaiter(void 0, void 0, void 0, function* () {
78
80
  var _a;
79
81
  const id = data.id;
80
82
  const outputPath = (0, component_1.getComponentOutputPath)(handoff);
@@ -97,9 +99,11 @@ const buildComponentJs = (data, handoff) => __awaiter(void 0, void 0, void 0, fu
97
99
  entry,
98
100
  outputPath,
99
101
  outputFilename: `${id}.js`,
100
- }, handoff);
102
+ }, handoff, inputs);
101
103
  }
102
104
  catch (e) {
105
+ if (inputs)
106
+ inputs.failed = true;
103
107
  logger_1.Logger.error(`Failed to bundle JS for component "${id}" (${id}.js):`, e);
104
108
  return data;
105
109
  }
@@ -113,6 +117,8 @@ const buildComponentJs = (data, handoff) => __awaiter(void 0, void 0, void 0, fu
113
117
  }
114
118
  }
115
119
  catch (e) {
120
+ if (inputs)
121
+ inputs.failed = true;
116
122
  logger_1.Logger.error(`JS build failed for component "${id}":`, e);
117
123
  }
118
124
  return data;
@@ -48,7 +48,9 @@ const composePatternHtml = (_patternId, patternTitle, fragments, basePath, share
48
48
  cssHrefs.add((0, url_1.buildArtifactUrl)(`component/${componentId}.css`, basePath));
49
49
  }
50
50
  }
51
- const doc = (0, node_html_parser_1.parse)(html);
51
+ // React SSR separates adjacent text children with `<!-- -->`. The parser drops comments by
52
+ // default, which would make every composed React fragment fail to hydrate.
53
+ const doc = (0, node_html_parser_1.parse)(html, { comment: true });
52
54
  const suffix = `_p${i}`;
53
55
  const namespacedRootId = `root${suffix}`;
54
56
  const namespacedPropsId = `__APP_PROPS__${suffix}`;
@@ -14,6 +14,8 @@ export type PageSliceType = 'BEST_PRACTICES' | 'COMPONENT_DISPLAY' | 'VALIDATION
14
14
  export interface BasePageSlice {
15
15
  type: PageSliceType;
16
16
  }
17
+ /** A fluid preview renders at the width the reader's stage has, so it is never scaled down. */
18
+ export type PreviewWidth = 'fluid' | number;
17
19
  export interface BestPracticesPageSlice extends BasePageSlice {
18
20
  type: 'BEST_PRACTICES';
19
21
  }
@@ -22,6 +24,8 @@ export interface ComponentDisplayPageSlice extends BasePageSlice {
22
24
  showPreview?: boolean;
23
25
  showCodeHighlight?: boolean;
24
26
  defaultHeight?: string;
27
+ /** Viewport width for the preview, in pixels. Defaults to 'fluid'. */
28
+ defaultWidth?: PreviewWidth;
25
29
  filterBy?: Filter;
26
30
  }
27
31
  export interface ValidationResultsPageSlice extends BasePageSlice {
@@ -100,6 +104,8 @@ export type ComponentObject = {
100
104
  story?: string;
101
105
  /** Optional path to schema file (if available) */
102
106
  schema?: string;
107
+ /** Declaration file path; a preview `render` is read from it at build and hydration time. */
108
+ declaration?: string;
103
109
  /** Optional path to templates directory */
104
110
  templates?: string;
105
111
  };
@@ -187,6 +193,8 @@ export type TransformComponentTokensResult = {
187
193
  story?: string;
188
194
  schema?: string;
189
195
  templates?: string;
196
+ /** See `ComponentObject.entries.declaration`. */
197
+ declaration?: string;
190
198
  };
191
199
  renderer?: RendererKind;
192
200
  /** See `ComponentObject.sourceFormat`. Distinct from `format`, the preview's code language. */
@@ -208,6 +216,11 @@ export type OptionalPreviewRender = {
208
216
  };
209
217
  url: string;
210
218
  usage?: string;
219
+ /**
220
+ * Build-time only: the server renders it and the client re-reads it from the declaration module.
221
+ * `JSON.stringify` drops it from published artifacts.
222
+ */
223
+ render?: (args: Record<string, any>) => unknown;
211
224
  /** Optional source preview/story key used to derive this preview */
212
225
  sourcePreview?: string;
213
226
  };
@@ -1,4 +1,14 @@
1
1
  import esbuild from 'esbuild';
2
+ /**
3
+ * Keeps co-located stylesheet imports out of the JavaScript graph; the CSS pipeline compiles them.
4
+ *
5
+ * esbuild has no `.scss` loader, and a `.css` import fails too because these builds run with
6
+ * `write: false` and no output path. `empty` rather than a plugin: `esbuild.buildSync` rejects
7
+ * plugins, and the declaration and config loaders are synchronous.
8
+ *
9
+ * CSS Modules are not covered — the empty module leaves the class map undefined.
10
+ */
11
+ export declare const STYLE_IMPORT_LOADERS: Record<string, esbuild.Loader>;
2
12
  /**
3
13
  * Default esbuild configuration for SSR builds
4
14
  */
@@ -7,6 +17,20 @@ export declare const DEFAULT_SSR_BUILD_CONFIG: esbuild.BuildOptions;
7
17
  * Default esbuild configuration for client builds
8
18
  */
9
19
  export declare const DEFAULT_CLIENT_BUILD_CONFIG: esbuild.BuildOptions;
20
+ /** Apply after a consumer hook: a hook that replaces `loader` would otherwise drop these. */
21
+ export declare function withStyleLoaders<T extends esbuild.BuildOptions>(config: T): T;
22
+ /**
23
+ * Stylesheet paths imported anywhere in a component's module graph, in cascade order.
24
+ *
25
+ * An unbuildable graph yields an empty list rather than throwing, leaving the CSS step with the
26
+ * declared entry.
27
+ */
28
+ export declare function collectStyleImports(entryPath: string, handoff: any): Promise<string[]>;
29
+ /**
30
+ * Every file in the module graph of an entry, stylesheets included, or `null` when the graph does
31
+ * not build.
32
+ */
33
+ export declare function collectModuleInputs(entryPath: string, handoff: any): Promise<string[] | null>;
10
34
  /**
11
35
  * Resolves a module from multiple search directories
12
36
  * @param id - Module ID to resolve
@@ -1,12 +1,36 @@
1
1
  "use strict";
2
+ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
3
+ function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
4
+ return new (P || (P = Promise))(function (resolve, reject) {
5
+ function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
6
+ function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
7
+ function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
8
+ step((generator = generator.apply(thisArg, _arguments || [])).next());
9
+ });
10
+ };
2
11
  var __importDefault = (this && this.__importDefault) || function (mod) {
3
12
  return (mod && mod.__esModule) ? mod : { "default": mod };
4
13
  };
5
14
  Object.defineProperty(exports, "__esModule", { value: true });
6
- exports.DEFAULT_CLIENT_BUILD_CONFIG = exports.DEFAULT_SSR_BUILD_CONFIG = void 0;
15
+ exports.DEFAULT_CLIENT_BUILD_CONFIG = exports.DEFAULT_SSR_BUILD_CONFIG = exports.STYLE_IMPORT_LOADERS = void 0;
16
+ exports.withStyleLoaders = withStyleLoaders;
17
+ exports.collectStyleImports = collectStyleImports;
18
+ exports.collectModuleInputs = collectModuleInputs;
7
19
  exports.resolveModule = resolveModule;
8
20
  exports.createReactResolvePlugin = createReactResolvePlugin;
21
+ const esbuild_1 = __importDefault(require("esbuild"));
9
22
  const path_1 = __importDefault(require("path"));
23
+ const STYLE_EXTENSIONS = ['.css', '.scss', '.sass'];
24
+ /**
25
+ * Keeps co-located stylesheet imports out of the JavaScript graph; the CSS pipeline compiles them.
26
+ *
27
+ * esbuild has no `.scss` loader, and a `.css` import fails too because these builds run with
28
+ * `write: false` and no output path. `empty` rather than a plugin: `esbuild.buildSync` rejects
29
+ * plugins, and the declaration and config loaders are synchronous.
30
+ *
31
+ * CSS Modules are not covered — the empty module leaves the class map undefined.
32
+ */
33
+ exports.STYLE_IMPORT_LOADERS = Object.fromEntries(STYLE_EXTENSIONS.map((extension) => [extension, 'empty']));
10
34
  /**
11
35
  * Default esbuild configuration for SSR builds
12
36
  */
@@ -17,6 +41,7 @@ exports.DEFAULT_SSR_BUILD_CONFIG = {
17
41
  platform: 'node',
18
42
  jsx: 'automatic',
19
43
  external: ['react', 'react-dom', '@opentelemetry/api'],
44
+ loader: exports.STYLE_IMPORT_LOADERS,
20
45
  };
21
46
  /**
22
47
  * Default esbuild configuration for client builds
@@ -29,7 +54,81 @@ exports.DEFAULT_CLIENT_BUILD_CONFIG = {
29
54
  jsx: 'automatic',
30
55
  sourcemap: false,
31
56
  minify: false,
57
+ loader: exports.STYLE_IMPORT_LOADERS,
32
58
  };
59
+ /** Apply after a consumer hook: a hook that replaces `loader` would otherwise drop these. */
60
+ function withStyleLoaders(config) {
61
+ return Object.assign(Object.assign({}, config), { loader: Object.assign(Object.assign({}, config.loader), exports.STYLE_IMPORT_LOADERS) });
62
+ }
63
+ /**
64
+ * Stylesheets of the module graph in cascade order: every import of a module before the module
65
+ * itself, siblings in source order.
66
+ *
67
+ * This is the order a bundler emits CSS in, and the cascade depends on it. A component that
68
+ * overrides a rule of a component it imports ties that rule on specificity, so the later of the
69
+ * two wins. esbuild's `onLoad` callbacks do not give this order: they fire roughly breadth-first,
70
+ * which puts a component's own stylesheet ahead of the ones it imports and reverses every such
71
+ * override.
72
+ */
73
+ function orderStyleImports(metafile, entryPath) {
74
+ const entryKey = Object.keys(metafile.inputs).find((key) => path_1.default.resolve(key) === entryPath);
75
+ if (!entryKey)
76
+ return [];
77
+ const ordered = [];
78
+ const visited = new Set();
79
+ const visit = (key) => {
80
+ var _a, _b;
81
+ if (visited.has(key))
82
+ return;
83
+ visited.add(key);
84
+ for (const imported of (_b = (_a = metafile.inputs[key]) === null || _a === void 0 ? void 0 : _a.imports) !== null && _b !== void 0 ? _b : []) {
85
+ if (!imported.external && metafile.inputs[imported.path])
86
+ visit(imported.path);
87
+ }
88
+ if (STYLE_EXTENSIONS.includes(path_1.default.extname(key)))
89
+ ordered.push(path_1.default.resolve(key));
90
+ };
91
+ visit(entryKey);
92
+ return ordered;
93
+ }
94
+ /**
95
+ * Stylesheet paths imported anywhere in a component's module graph, in cascade order.
96
+ *
97
+ * An unbuildable graph yields an empty list rather than throwing, leaving the CSS step with the
98
+ * declared entry.
99
+ */
100
+ function collectStyleImports(entryPath, handoff) {
101
+ return __awaiter(this, void 0, void 0, function* () {
102
+ const metafile = yield buildModuleGraph(entryPath, handoff);
103
+ return metafile ? orderStyleImports(metafile, entryPath) : [];
104
+ });
105
+ }
106
+ /**
107
+ * Every file in the module graph of an entry, stylesheets included, or `null` when the graph does
108
+ * not build.
109
+ */
110
+ function collectModuleInputs(entryPath, handoff) {
111
+ return __awaiter(this, void 0, void 0, function* () {
112
+ const metafile = yield buildModuleGraph(entryPath, handoff);
113
+ return metafile ? Object.keys(metafile.inputs).map((input) => path_1.default.resolve(input)) : null;
114
+ });
115
+ }
116
+ function buildModuleGraph(entryPath, handoff) {
117
+ return __awaiter(this, void 0, void 0, function* () {
118
+ var _a, _b, _c;
119
+ const baseConfig = Object.assign(Object.assign({}, exports.DEFAULT_SSR_BUILD_CONFIG), { entryPoints: [entryPath], logLevel: 'silent' });
120
+ const hookedConfig = ((_b = (_a = handoff === null || handoff === void 0 ? void 0 : handoff.config) === null || _a === void 0 ? void 0 : _a.hooks) === null || _b === void 0 ? void 0 : _b.ssrBuildConfig) ? handoff.config.hooks.ssrBuildConfig(baseConfig) : baseConfig;
121
+ try {
122
+ // The `empty` loader keeps stylesheets out of the JavaScript, and the metafile still records
123
+ // them as inputs, so the graph carries the order.
124
+ const result = yield esbuild_1.default.build(Object.assign(Object.assign({}, withStyleLoaders(hookedConfig)), { metafile: true }));
125
+ return (_c = result.metafile) !== null && _c !== void 0 ? _c : null;
126
+ }
127
+ catch (_d) {
128
+ return null;
129
+ }
130
+ });
131
+ }
33
132
  /**
34
133
  * Resolves a module from multiple search directories
35
134
  * @param id - Module ID to resolve
@@ -19,9 +19,10 @@ const build_1 = require("./build");
19
19
  function createModuleBuildConfig(entryPath, handoff) {
20
20
  var _a, _b;
21
21
  const defaultBuildConfig = Object.assign(Object.assign({}, build_1.DEFAULT_SSR_BUILD_CONFIG), { entryPoints: [entryPath] });
22
- return ((_b = (_a = handoff.config) === null || _a === void 0 ? void 0 : _a.hooks) === null || _b === void 0 ? void 0 : _b.ssrBuildConfig)
22
+ const hookedBuildConfig = ((_b = (_a = handoff.config) === null || _a === void 0 ? void 0 : _a.hooks) === null || _b === void 0 ? void 0 : _b.ssrBuildConfig)
23
23
  ? handoff.config.hooks.ssrBuildConfig(defaultBuildConfig)
24
24
  : defaultBuildConfig;
25
+ return (0, build_1.withStyleLoaders)(hookedBuildConfig);
25
26
  }
26
27
  function evaluateBuiltModule(code) {
27
28
  const mod = { exports: {} };
@@ -115,6 +115,100 @@ export interface ConfigCatalog {
115
115
  export type RuntimeMode = 'workspace' | 'registry';
116
116
  /** Format used when synthesizing local workspace declarations. */
117
117
  export type DeclarationFormat = 'ts' | 'js' | 'cjs';
118
+ /** How a connection's credential is supplied. Declared, never inferred from a missing key. */
119
+ export type AiCredentialKind = 'service' | 'user' | 'none';
120
+ /**
121
+ * An email option: a literal, baked into the build, or an environment reference, read by the deployed
122
+ * app at request time.
123
+ */
124
+ export type HandoffEmailOption<T> = T | RuntimeEnvReference<T>;
125
+ interface HandoffEmailBase {
126
+ /** Sender address, such as `Handoff <no-reply@example.com>`. Use a profile for per-environment values. */
127
+ from?: string;
128
+ }
129
+ /** Send through an SMTP server, such as Amazon SES, SendGrid, Postmark, Mailgun or Microsoft 365. */
130
+ export interface HandoffSmtpEmail extends HandoffEmailBase {
131
+ provider: 'smtp';
132
+ options: {
133
+ host: HandoffEmailOption<string>;
134
+ /** @default 465, or 587 when `secure` is false */
135
+ port?: HandoffEmailOption<number>;
136
+ /**
137
+ * Use TLS from the start of the connection. When false, the connection upgrades with STARTTLS if
138
+ * the server offers it. @default true, or false when `port` is set to a port other than 465
139
+ */
140
+ secure?: HandoffEmailOption<boolean>;
141
+ user?: HandoffEmailOption<string>;
142
+ /** Environment reference to the SMTP password. */
143
+ password?: RuntimeEnvReference<string>;
144
+ };
145
+ }
146
+ /** Send through a server-only module that default-exports a `defineEmailProvider()` result. */
147
+ export interface HandoffCustomEmail extends HandoffEmailBase {
148
+ provider: 'custom';
149
+ /** Server-only module path. It must be its own file so the build can trace it into the deployed registry. */
150
+ module: string;
151
+ /**
152
+ * Options passed to the provider factory. Literals are baked. Environment references are resolved
153
+ * at request time. `apiKey` and `password` must be environment references.
154
+ */
155
+ options?: Record<string, unknown>;
156
+ }
157
+ /** Registry email settings. `provider` selects the shape of `options`. */
158
+ export type HandoffEmailConfig = HandoffSmtpEmail | HandoffCustomEmail;
159
+ /**
160
+ * One AI connection the deployment declares. Connections are the whole surface: a reader can add a
161
+ * key for a declared connection and nothing else, so the server never calls a URL a reader chose.
162
+ *
163
+ * Every connection speaks the OpenAI-compatible `/chat/completions` API. A provider that fits
164
+ * nothing else names a server-only `module` instead of a `baseUrl`.
165
+ */
166
+ export interface HandoffAiConnection {
167
+ /** Stable id. A model is addressed as `<id>/<model>`. */
168
+ id: string;
169
+ /** Name shown to a reader in the model picker and on the account page. */
170
+ label: string;
171
+ /** OpenAI-compatible base URL, such as `https://api.openai.com/v1`. Omit only when `module` is set. */
172
+ baseUrl?: string;
173
+ /**
174
+ * Server-only module path default-exporting a `defineAiProvider()` result, for a provider that is
175
+ * not OpenAI-compatible. It must be its own file so the build can trace it into the deployed
176
+ * registry.
177
+ */
178
+ module?: string;
179
+ /**
180
+ * Environment reference to the deployment's own key. Its presence makes the connection a service
181
+ * key: the deployment pays and every reader can use it. The name is baked; the value is read at
182
+ * request time and never leaves the server.
183
+ */
184
+ apiKey?: RuntimeEnvReference<string>;
185
+ /**
186
+ * Set to `'user'` when each reader supplies their own key. Without it, a connection that needs a
187
+ * reader's key looks like one that needs none, and the reader gets a raw 401 instead of an
188
+ * instruction.
189
+ */
190
+ credential?: 'user';
191
+ /** The models this connection offers. A reader cannot use a model that is not listed. */
192
+ models?: string[];
193
+ /**
194
+ * Options passed to a `module` provider factory. Literals are baked. Environment references are
195
+ * resolved at request time, so secrets must be environment references.
196
+ */
197
+ options?: Record<string, unknown>;
198
+ }
199
+ /**
200
+ * The AI assistant block. Presence of the block opts the build in, and `enabled: false` turns it
201
+ * off. Like `mcp`, `enabled` is config-only and baked at build time, so a build without it leaves
202
+ * out both the chat route and the header control rather than showing a control that fails.
203
+ */
204
+ export interface HandoffAiConfig {
205
+ /** @default true when the `ai` block is present */
206
+ enabled?: boolean;
207
+ /** Declared connections. Merged by `id` with any supplied through `HANDOFF_AI_CONNECTIONS`. */
208
+ connections?: HandoffAiConnection[];
209
+ /** The `<connectionId>/<model>` used when a reader has not picked one. */
210
+ defaultModel?: string;
211
+ }
118
212
  /**
119
213
  * User-facing `runtime` configuration block. A single optional block that selects the runtime
120
214
  * mode and carries mode-specific host/connection settings.
@@ -135,6 +229,8 @@ export interface HandoffRuntimeConfig {
135
229
  * @default true
136
230
  */
137
231
  mcp?: boolean;
232
+ /** The docs AI assistant. Omit the block to leave it out of the build. See {@link HandoffAiConfig}. */
233
+ ai?: HandoffAiConfig;
138
234
  /** Workspace-mode settings. */
139
235
  workspace?: {
140
236
  entries?: ConfigEntries;
@@ -142,9 +238,9 @@ export interface HandoffRuntimeConfig {
142
238
  };
143
239
  /** Registry-mode host settings. Env-var values are stored as names, never as secrets. */
144
240
  registry?: {
145
- /** Environment reference to the database URL. @default fromEnv('DATABASE_URL') */
146
- databaseUrl?: RuntimeEnvReference<string>;
147
241
  database?: {
242
+ /** Environment reference to the database URL. @default fromEnv('DATABASE_URL') */
243
+ url?: RuntimeEnvReference<string>;
148
244
  /**
149
245
  * PostgreSQL is the supported database. `driver` selects the built-in connection driver — how
150
246
  * to connect, not which engine — over the same package-owned Postgres schema and migrations.
@@ -159,20 +255,26 @@ export interface HandoffRuntimeConfig {
159
255
  */
160
256
  assetStorage?: {
161
257
  /**
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.
258
+ * Active storage provider for new uploads. `database` keeps bytes inline in Postgres; use a
259
+ * `custom` module for object storage.
164
260
  * @default "database"
165
261
  */
166
- adapter?: 'database' | 'vercel-blob' | 'custom';
167
- /** For `adapter: "custom"` - server-only module path default-exporting a `defineAssetStorage` adapter. */
262
+ provider?: 'database' | 'custom';
263
+ /** For `provider: "custom"` - server-only module path default-exporting a `defineStorageProvider()` result. */
168
264
  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
265
  /** Max bytes kept inline in the database `bytea` column (larger uploads are rejected). @default 4194304 */
172
266
  maxInlineBytes?: number;
173
- /** Non-secret options, JSON-encoded into the bundle. Environment references are rejected. Pass variable names instead. */
267
+ /**
268
+ * Options passed to the provider factory. Literals are baked. Environment references are resolved
269
+ * at request time, so secrets must be environment references.
270
+ */
174
271
  options?: Record<string, unknown>;
175
272
  };
273
+ /**
274
+ * Invitation and password-reset email. Email stays off until `from` is set and the required
275
+ * options of the provider have values. Until then, invitation links are shown to an administrator.
276
+ */
277
+ email?: HandoffEmailConfig;
176
278
  };
177
279
  /**
178
280
  * Connected-workspace settings pointing at a remote registry. A connected workspace is
@@ -185,11 +287,18 @@ export interface HandoffRuntimeConfig {
185
287
  accessToken?: EnvSecret<string>;
186
288
  };
187
289
  }
290
+ /** Connections to external services. */
291
+ export interface ConfigIntegrations {
292
+ /** The Figma file that `fetch` reads foundations from. In a terminal, `fetch` asks for an empty value. */
293
+ figma?: {
294
+ /** The Figma file ID. @default fromEnv('HANDOFF_FIGMA_PROJECT_ID', { default: null }) */
295
+ projectId?: EnvValue<string | null>;
296
+ /** A Figma personal access token. Accepts only an environment reference. @default fromEnv('HANDOFF_DEV_ACCESS_TOKEN', { default: null }) */
297
+ accessToken?: EnvSecret<string | null>;
298
+ };
299
+ }
188
300
  export interface Config {
189
- dev_access_token?: EnvSecret<string | null>;
190
- devAccessToken?: EnvSecret<string | null>;
191
- figma_project_id?: EnvValue<string | null>;
192
- figmaProjectId?: EnvValue<string | null>;
301
+ integrations?: ConfigIntegrations;
193
302
  exportsOutputDirectory?: EnvValue<string>;
194
303
  sitesOutputDirectory?: EnvValue<string>;
195
304
  useVariables?: EnvValue<boolean>;
@@ -273,6 +382,9 @@ export interface Config {
273
382
  validateComponent?: (component: TransformComponentTokensResult) => Promise<Record<string, ValidationResult>>;
274
383
  /**
275
384
  * Optional hook to override the SSR build configuration used in the ssrRenderPlugin
385
+ *
386
+ * Stylesheet loaders are re-applied after this hook, so a `.css`/`.scss`/`.sass` import stays out
387
+ * of the JavaScript graph whatever the hook returns.
276
388
  * @param config - The default esbuild configuration
277
389
  * @returns Modified esbuild configuration
278
390
  * @example
@@ -286,6 +398,9 @@ export interface Config {
286
398
  ssrBuildConfig?: (config: BuildOptions) => BuildOptions;
287
399
  /**
288
400
  * Optional hook to override the client-side build configuration used in the ssrRenderPlugin
401
+ *
402
+ * Stylesheet loaders are re-applied after this hook, so a `.css`/`.scss`/`.sass` import stays out
403
+ * of the JavaScript graph whatever the hook returns.
289
404
  * @param config - The default esbuild configuration
290
405
  * @returns Modified esbuild configuration
291
406
  * @example
@@ -385,11 +500,13 @@ export interface ClientRuntimeConfig {
385
500
  connected: boolean;
386
501
  }
387
502
  /** Values available after config loading. Deferred references retain their names. */
388
- export type ResolvedConfig = Omit<Config, 'dev_access_token' | 'devAccessToken' | 'figma_project_id' | 'figmaProjectId' | 'exportsOutputDirectory' | 'sitesOutputDirectory' | 'useVariables' | 'app' | 'runtime'> & {
389
- dev_access_token?: string | null;
390
- devAccessToken?: string | null;
391
- figma_project_id?: string | null;
392
- figmaProjectId?: string | null;
503
+ export type ResolvedConfig = Omit<Config, 'integrations' | 'exportsOutputDirectory' | 'sitesOutputDirectory' | 'useVariables' | 'app' | 'runtime'> & {
504
+ integrations?: {
505
+ figma?: {
506
+ projectId?: string | null;
507
+ accessToken?: string | null;
508
+ };
509
+ };
393
510
  exportsOutputDirectory?: string;
394
511
  sitesOutputDirectory?: string;
395
512
  useVariables?: boolean;
@@ -1,8 +1,4 @@
1
- /**
2
- * Generates a filesystem-safe directory name from an absolute path
3
- * @param workingPath - The absolute working path to transform
4
- * @returns A filesystem-safe string suitable for use as a directory name
5
- */
1
+ /** `<base name>-<path hash>`; the hash keeps directories with the same base name apart. */
6
2
  export declare function generateFilesystemSafeId(workingPath: string): string;
7
3
  /**
8
4
  * Normalize a filesystem path for cross-platform string comparison.
@@ -8,31 +8,14 @@ exports.normalizePathForCompare = normalizePathForCompare;
8
8
  exports.arePathsEqual = arePathsEqual;
9
9
  exports.resolveWorkingPath = resolveWorkingPath;
10
10
  exports.isPathInside = isPathInside;
11
+ const crypto_1 = require("crypto");
11
12
  const path_1 = __importDefault(require("path"));
12
- /**
13
- * Generates a filesystem-safe directory name from an absolute path
14
- * @param workingPath - The absolute working path to transform
15
- * @returns A filesystem-safe string suitable for use as a directory name
16
- */
13
+ /** `<base name>-<path hash>`; the hash keeps directories with the same base name apart. */
17
14
  function generateFilesystemSafeId(workingPath) {
18
- // Normalize path separators to forward slashes for consistent processing
19
- let safeId = workingPath.replace(/\\/g, '/');
20
- // Replace forward slashes with dashes
21
- safeId = safeId.replace(/\//g, '-');
22
- // Remove or replace invalid filesystem characters
23
- safeId = safeId.replace(/[<>:"|?*\x00]/g, '-');
24
- // Remove leading/trailing dashes and spaces
25
- safeId = safeId.replace(/^[\s-]+|[\s-]+$/g, '');
26
- // Handle Windows reserved names (case-insensitive)
27
- const reservedNames = /^(CON|PRN|AUX|NUL|COM[1-9]|LPT[1-9])(\.|$)/i;
28
- if (reservedNames.test(safeId)) {
29
- safeId = `project-${safeId}`;
30
- }
31
- // Ensure we have at least one character
32
- if (!safeId) {
33
- safeId = 'default-project';
34
- }
35
- return safeId;
15
+ const baseName = path_1.default.basename(path_1.default.resolve(workingPath));
16
+ const name = baseName.replace(/[^A-Za-z0-9._-]/g, '-').replace(/^[.-]+|[.-]+$/g, '') || 'project';
17
+ const hash = (0, crypto_1.createHash)('sha256').update(normalizePathForCompare(workingPath)).digest('hex').slice(0, 8);
18
+ return `${name}-${hash}`;
36
19
  }
37
20
  /**
38
21
  * Normalize a filesystem path for cross-platform string comparison.
package/docs/api.md CHANGED
@@ -80,10 +80,12 @@ class constructor
80
80
  handoff.fetch();
81
81
  ```
82
82
 
83
- Fetch will connect to the defined Figma file id provided in the
84
- `env`. If no env or file is found, it will interactively request one. Then it
85
- will export all of the tokens and generated data into an `exported` directory
86
- in the local working root.
83
+ Fetch reads the Figma file ID and access token from `integrations.figma`. By
84
+ default, they come from `HANDOFF_FIGMA_PROJECT_ID` and
85
+ `HANDOFF_DEV_ACCESS_TOKEN`. In a terminal, fetch asks for an empty value.
86
+ Without a terminal, it throws `HandoffConfigError`. Then it exports all of the
87
+ tokens and generated data into an `exported` directory in the local working
88
+ root.
87
89
 
88
90
  ### build
89
91