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
@@ -5,8 +5,10 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
5
5
  Object.defineProperty(exports, "__esModule", { value: true });
6
6
  exports.initConfig = exports.initConfigWithMetadata = exports.resolveProfileSelection = exports.HandoffConfigError = exports.CONFIG_FILE_PREFERENCE = void 0;
7
7
  const esbuild_1 = __importDefault(require("esbuild"));
8
+ const build_1 = require("../transformers/utils/build");
8
9
  const fs_extra_1 = __importDefault(require("fs-extra"));
9
10
  const mergeWith_1 = __importDefault(require("lodash/mergeWith"));
11
+ const omit_1 = __importDefault(require("lodash/omit"));
10
12
  const module_1 = require("module");
11
13
  const path_1 = __importDefault(require("path"));
12
14
  const logger_1 = require("../utils/logger");
@@ -37,9 +39,10 @@ const evaluateTypeScriptConfig = (filePath, handoffModulePath) => {
37
39
  write: false,
38
40
  platform: 'node',
39
41
  format: 'cjs',
40
- target: 'node16',
42
+ target: 'node22',
41
43
  logLevel: 'silent',
42
44
  external: ['handoff-app'],
45
+ loader: build_1.STYLE_IMPORT_LOADERS,
43
46
  });
44
47
  const code = (_b = (_a = buildResult.outputFiles) === null || _a === void 0 ? void 0 : _a[0]) === null || _b === void 0 ? void 0 : _b.text;
45
48
  if (!code) {
@@ -170,6 +173,29 @@ const replaceValues = (destination, source) => {
170
173
  return undefined;
171
174
  return Array.isArray(source) || (0, from_env_1.isEnvReference)(source) || (0, from_env_1.isEnvReference)(destination) ? source : undefined;
172
175
  };
176
+ /**
177
+ * A layer that changes `runtime.registry.email.provider` starts from fresh `options` and `module`, so
178
+ * the deep merge never mixes the settings of two providers.
179
+ */
180
+ const resetEmailOnProviderChange = (layers) => {
181
+ let provider;
182
+ let start = 0;
183
+ layers.forEach((layer, index) => {
184
+ var _a, _b, _c;
185
+ const next = (_c = (_b = (_a = layer.runtime) === null || _a === void 0 ? void 0 : _a.registry) === null || _b === void 0 ? void 0 : _b.email) === null || _c === void 0 ? void 0 : _c.provider;
186
+ if (next !== undefined && next !== provider) {
187
+ provider = next;
188
+ start = index;
189
+ }
190
+ });
191
+ return layers.map((layer, index) => {
192
+ var _a, _b;
193
+ const email = (_b = (_a = layer.runtime) === null || _a === void 0 ? void 0 : _a.registry) === null || _b === void 0 ? void 0 : _b.email;
194
+ if (index >= start || !email)
195
+ return layer;
196
+ return Object.assign(Object.assign({}, layer), { runtime: Object.assign(Object.assign({}, layer.runtime), { registry: Object.assign(Object.assign({}, layer.runtime.registry), { email: (0, omit_1.default)(email, ['options', 'module']) }) }) });
197
+ });
198
+ };
173
199
  /**
174
200
  * Loads the handoff configuration for the given working directory and returns metadata.
175
201
  */
@@ -197,7 +223,7 @@ const initConfigWithMetadata = (configOverride, context) => {
197
223
  : 'none selected';
198
224
  logger_1.Logger.debug(`Config profile: ${profileLabel}`);
199
225
  logger_1.Logger.debug(`Config files loaded: ${[configPath, profileConfigPath].filter(Boolean).join(', ') || 'none, using defaults'}`);
200
- const merged = (0, mergeWith_1.default)({}, ...layers, replaceValues);
226
+ const merged = (0, mergeWith_1.default)({}, ...resetEmailOnProviderChange(layers), replaceValues);
201
227
  const config = (0, resolve_env_1.resolveConfigEnv)(merged, profileSelection === null || profileSelection === void 0 ? void 0 : profileSelection.name, layers[0]);
202
228
  // Guards run on the resolved config so a profile cannot slip a removed setting past them.
203
229
  const entries = config.entries;
@@ -1,5 +1,6 @@
1
1
  import type { Config, ResolvedConfig } from '../types/config';
2
- export declare const DEFERRED_PATHS: Set<string>;
2
+ /** Paths whose value the deployed app reads at request time, so only the variable name is stored. */
3
+ export declare const DEFERRED_PATHS: string[];
3
4
  export declare const configEnvName: (config: ResolvedConfig | null | undefined, path: string) => string | undefined;
4
5
  /** Resolve only the merged winner. Functions and non-plain objects retain their identity. */
5
6
  export declare const resolveConfigEnv: (config: Config, profile?: string, defaults?: Config) => ResolvedConfig;
@@ -8,11 +8,32 @@ const isPlainObject_1 = __importDefault(require("lodash/isPlainObject"));
8
8
  const logger_1 = require("../utils/logger");
9
9
  const from_env_1 = require("./from-env");
10
10
  const errors_1 = require("./errors");
11
- exports.DEFERRED_PATHS = new Set(['runtime.registry.databaseUrl', 'runtime.registry.assetStorage.token']);
12
- const SECRET_PATHS = new Set(['dev_access_token', 'devAccessToken', 'runtime.registryConnection.accessToken']);
13
- /** Path prefixes whose resolved contents are JSON-baked into the bundle, so a reference would bake its value. */
14
- const BAKED_PATHS = ['runtime.registry.assetStorage.options'];
15
- const isBaked = (path) => BAKED_PATHS.some((baked) => path === baked || path.startsWith(`${baked}.`));
11
+ /**
12
+ * Match a dot path against a pattern. A `*` segment matches exactly one segment, so one pattern
13
+ * covers every element of a list. With `prefix`, the pattern also matches anything nested under it.
14
+ */
15
+ const pathMatches = (pattern, path, prefix = false) => {
16
+ const expected = pattern.split('.');
17
+ const actual = path.split('.');
18
+ if (prefix ? expected.length > actual.length : expected.length !== actual.length)
19
+ return false;
20
+ return expected.every((segment, index) => segment === '*' || segment === actual[index]);
21
+ };
22
+ /** Paths whose value the deployed app reads at request time, so only the variable name is stored. */
23
+ exports.DEFERRED_PATHS = [
24
+ 'runtime.registry.database.url',
25
+ 'runtime.registry.email.options.apiKey',
26
+ 'runtime.registry.email.options.password',
27
+ 'runtime.ai.connections.*.apiKey',
28
+ ];
29
+ /**
30
+ * Path prefixes whose nested references the deployed app reads at request time. Literals below them
31
+ * are baked. `DEFERRED_PATHS` entries below them still reject literals.
32
+ */
33
+ const DEFERRED_PREFIXES = ['runtime.registry.email.options', 'runtime.registry.assetStorage.options', 'runtime.ai.connections.*.options'];
34
+ const SECRET_PATHS = new Set(['integrations.figma.accessToken', 'runtime.registryConnection.accessToken']);
35
+ const isDeferred = (path) => exports.DEFERRED_PATHS.some((deferred) => pathMatches(deferred, path));
36
+ const isUnderDeferredPrefix = (path) => DEFERRED_PREFIXES.some((prefix) => pathMatches(`${prefix}.*`, path, true));
16
37
  const ENV_NAME = /^[A-Za-z_][A-Za-z0-9_]*$/;
17
38
  const ALIASES = {
18
39
  HANDOFF_REGISTRY_URL: 'HANDOFF_CLOUD_URL',
@@ -44,17 +65,12 @@ const resolveConfigEnv = (config, profile, defaults) => {
44
65
  };
45
66
  const walk = (value, path, seed) => {
46
67
  var _a;
47
- const deferred = exports.DEFERRED_PATHS.has(path);
68
+ const deferred = isDeferred(path);
48
69
  const secret = SECRET_PATHS.has(path);
49
70
  if ((secret || deferred) && value !== undefined && !(0, from_env_1.isEnvReference)(value)) {
50
71
  fail(path, 'Expected an environment reference, such as { $env: "VARIABLE_NAME" }. Literal values are not allowed.');
51
72
  }
52
73
  if ((0, from_env_1.isEnvReference)(value)) {
53
- // Checked before the name, so the message names the supported pattern instead of the reference shape.
54
- if (isBaked(path)) {
55
- fail(path, 'Environment references are not allowed here because the resolved value is baked into the build. ' +
56
- "Put the variable name in options and read it through the adapter factory's `env` argument.");
57
- }
58
74
  if (typeof value.$env !== 'string' || !ENV_NAME.test(value.$env)) {
59
75
  fail(path, 'Invalid environment variable name. Use letters, digits, and underscores, starting with a letter or underscore.');
60
76
  }
@@ -63,7 +79,7 @@ const resolveConfigEnv = (config, profile, defaults) => {
63
79
  if (Object.keys(value).some((key) => key !== '$env' && key !== 'default')) {
64
80
  fail(path, `Environment variable "${name}" has an invalid reference shape.`);
65
81
  }
66
- if (deferred) {
82
+ if (deferred || isUnderDeferredPrefix(path)) {
67
83
  if ('default' in value)
68
84
  fail(path, `Environment variable "${name}" cannot have a default because it is read by the deployed app.`);
69
85
  return { $env: name };
@@ -5,6 +5,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
5
5
  Object.defineProperty(exports, "__esModule", { value: true });
6
6
  exports.toLowerCaseKeysAndValues = exports.getComponentsForPath = exports.isComponentDirectory = exports.initRuntimeConfig = exports.getCatalogItemDirectories = exports.resolveComponentDeclaration = void 0;
7
7
  const esbuild_1 = __importDefault(require("esbuild"));
8
+ const build_1 = require("../transformers/utils/build");
8
9
  const fs_extra_1 = __importDefault(require("fs-extra"));
9
10
  const module_1 = require("module");
10
11
  const path_1 = __importDefault(require("path"));
@@ -17,7 +18,8 @@ const markdown_1 = require("../utils/markdown");
17
18
  const pages_1 = require("../utils/pages");
18
19
  const path_2 = require("../utils/path");
19
20
  const page_1 = require("./normalizers/page");
20
- const DECLARATION_EXTENSIONS = ['ts', 'js', 'cjs'];
21
+ /** `.tsx`/`.jsx` are accepted so a preview can write its `render` as JSX. */
22
+ const DECLARATION_EXTENSIONS = ['tsx', 'ts', 'jsx', 'js', 'cjs'];
21
23
  const getDeclarationFiles = (componentBaseName) => DECLARATION_EXTENSIONS.map((ext) => `${componentBaseName}.handoff.${ext}`);
22
24
  const findPreferredDeclaration = (componentDir, componentBaseName) => {
23
25
  const exactCandidates = getDeclarationFiles(componentBaseName);
@@ -25,7 +27,7 @@ const findPreferredDeclaration = (componentDir, componentBaseName) => {
25
27
  if (exactMatch)
26
28
  return exactMatch;
27
29
  const allFiles = fs_extra_1.default.existsSync(componentDir) ? fs_extra_1.default.readdirSync(componentDir) : [];
28
- const declarationFiles = allFiles.filter((file) => /\.handoff\.(ts|js|cjs)$/.test(file)).sort((a, b) => a.localeCompare(b));
30
+ const declarationFiles = allFiles.filter((file) => /\.handoff\.(tsx|ts|jsx|js|cjs)$/.test(file)).sort((a, b) => a.localeCompare(b));
29
31
  if (!declarationFiles.length)
30
32
  return undefined;
31
33
  for (const ext of DECLARATION_EXTENSIONS) {
@@ -48,10 +50,11 @@ const evaluateDeclaration = (filePath, handoffModulePath) => {
48
50
  write: false,
49
51
  platform: 'node',
50
52
  format: 'cjs',
51
- target: 'node16',
53
+ target: 'node22',
52
54
  logLevel: 'silent',
53
55
  jsx: 'automatic',
54
56
  external: ['react', 'react-dom', 'handoff-app'],
57
+ loader: build_1.STYLE_IMPORT_LOADERS,
55
58
  });
56
59
  const code = (_b = (_a = buildResult.outputFiles) === null || _a === void 0 ? void 0 : _a[0]) === null || _b === void 0 ? void 0 : _b.text;
57
60
  if (!code) {
@@ -178,7 +181,7 @@ const initRuntimeConfig = (handoff) => {
178
181
  const declaration = (0, exports.resolveComponentDeclaration)(itemPath, itemBaseName);
179
182
  if (!declaration) {
180
183
  const declarationFiles = getDeclarationFiles(itemBaseName);
181
- logger_1.Logger.warn(`Missing catalog declaration (use .handoff.ts, .handoff.js, or .handoff.cjs; see UPGRADE.md#catalog-items): ${path_1.default.resolve(itemPath, declarationFiles.join(' or '))}`);
184
+ logger_1.Logger.warn(`Missing catalog declaration (use .handoff.tsx, .handoff.ts, .handoff.jsx, .handoff.js, or .handoff.cjs; see UPGRADE.md#catalog-items): ${path_1.default.resolve(itemPath, declarationFiles.join(' or '))}`);
182
185
  continue;
183
186
  }
184
187
  const declarationPath = path_1.default.resolve(itemPath, declaration.fileName);
@@ -361,8 +364,8 @@ const injectPatternPreviews = (result) => {
361
364
  }
362
365
  // Case 3: args present (with or without preview base) -> create synthetic preview
363
366
  let resolvedValues = {};
367
+ const basePreview = ref.preview ? (_b = component.previews) === null || _b === void 0 ? void 0 : _b[ref.preview] : undefined;
364
368
  if (ref.preview) {
365
- const basePreview = (_b = component.previews) === null || _b === void 0 ? void 0 : _b[ref.preview];
366
369
  if (basePreview) {
367
370
  resolvedValues = Object.assign({}, basePreview.values);
368
371
  }
@@ -374,12 +377,7 @@ const injectPatternPreviews = (result) => {
374
377
  resolvedValues = Object.assign(Object.assign({}, resolvedValues), ref.args);
375
378
  const syntheticKey = `__pattern_${patternId}_${i}`;
376
379
  component.internalPatternPreviews = component.internalPatternPreviews || {};
377
- component.internalPatternPreviews[syntheticKey] = {
378
- title: syntheticKey,
379
- values: resolvedValues,
380
- url: '',
381
- sourcePreview: ref.preview,
382
- };
380
+ component.internalPatternPreviews[syntheticKey] = Object.assign({ title: syntheticKey, values: resolvedValues, url: '', sourcePreview: ref.preview }, ((basePreview === null || basePreview === void 0 ? void 0 : basePreview.render) ? { render: basePreview.render } : {}));
383
381
  ref.resolvedPreview = syntheticKey;
384
382
  if (ref.resolved === undefined) {
385
383
  ref.resolved = true;
@@ -1,5 +1,13 @@
1
- import type { CatalogItem, CatalogItemMeta } from './catalog/types';
2
- export type { CatalogItem, CatalogItemMeta, CatalogPreview, CompositionRef, Preview, SourceDescriptor } from './catalog/types';
1
+ import type { CatalogItem, CatalogItemMeta, CatalogPreview as RendererPreview } from './catalog/types';
2
+ export type { CatalogItem, CatalogItemMeta, CompositionRef, SourceDescriptor } from './catalog/types';
3
+ /** A template renderer never calls a preview `render`, so declaring one is an error, not a no-op. */
4
+ export type CatalogPreview<TArgs = Record<string, unknown>> = Omit<RendererPreview<TArgs>, 'render'> & {
5
+ render?: never;
6
+ };
7
+ /** Preview type for a Handlebars catalog item, with its argument type applied. */
8
+ export type Preview<TItem> = TItem extends {
9
+ readonly __args?: infer TArgs;
10
+ } ? CatalogPreview<Partial<NonNullable<TArgs>>> : never;
3
11
  /**
4
12
  * Declares a Handlebars catalog item. `implementation` is the path to its `.hbs` template. The
5
13
  * renderer comes from this module, so the build fails an item whose file does not match it.
package/dist/index.d.ts CHANGED
@@ -83,7 +83,7 @@ declare class Handoff {
83
83
  private profilesWithoutConfig;
84
84
  init(configOverride?: Partial<Config>): Handoff;
85
85
  reload(): Handoff;
86
- preRunner(validate?: boolean): Handoff;
86
+ preRunner(): Handoff;
87
87
  fetch(): Promise<Handoff>;
88
88
  component(name: string | null): Promise<Handoff>;
89
89
  pattern(): Promise<Handoff>;
@@ -186,10 +186,7 @@ declare class Handoff {
186
186
  */
187
187
  getDocumentationObject(): Promise<CoreTypes.IDocumentationObject | undefined>;
188
188
  getRunner(): Promise<ReturnType<typeof HandoffRunner>>;
189
- /**
190
- * Gets the project ID, falling back to filesystem-safe working path if figma_project_id is missing
191
- * @returns {string} The project ID to use for path construction
192
- */
189
+ /** Keys `.handoff/<projectId>`, so workspaces that share one installed package stage apart. */
193
190
  getProjectId(): string;
194
191
  /**
195
192
  * Gets the output path for the current project
@@ -236,6 +233,10 @@ declare class Handoff {
236
233
  * Gets the selected main config file path if one exists.
237
234
  */
238
235
  getMainConfigFilePath(): string | undefined;
236
+ /**
237
+ * Gets the selected profile config file path if one exists.
238
+ */
239
+ getProfileConfigFilePath(): string | undefined;
239
240
  getProfile(): string | undefined;
240
241
  /**
241
242
  * True for the base config file and for the profile file merged onto it. Both carry project-wide
@@ -260,7 +261,11 @@ export { defineConfig } from './config';
260
261
  export { fromEnv } from './config/from-env';
261
262
  export type { EnvValue, EnvSecret, RuntimeEnvReference } from './config/from-env';
262
263
  export type { CatalogItem, CatalogItemEntries, CatalogItemInput, CatalogItemMeta, CatalogPreview, CompositionRef, Preview, SourceDescriptor, SourceFormat, RendererKind, } from './catalog';
263
- export { defineAssetStorage } from './registry/asset-storage/define';
264
- export type { AssetStorage, AssetStorageContext, AssetStorageFactory, AssetStorageInput, AssetStorageReadResult, } from './registry/asset-storage/types';
264
+ export { defineStorageProvider } from './registry/asset-storage/define';
265
+ export type { StorageProvider, StorageProviderContext, StorageProviderFactory, StorageInput, StorageReadResult, AssetUpload, AssetUploadInput, } from './registry/asset-storage/types';
266
+ export { defineEmailProvider } from './registry/email/define';
267
+ export type { EmailMessage, EmailProvider, EmailProviderContext, EmailProviderFactory } from './registry/email/types';
268
+ export { defineAiProvider } from './ai/define';
269
+ export type { AiProvider, AiProviderContext, AiProviderFactory } from './ai/types';
265
270
  export { Transformers as CoreTransformers, TransformerUtils as CoreTransformerUtils, Types as CoreTypes } from 'handoff-core';
266
271
  export default Handoff;
package/dist/index.js CHANGED
@@ -35,7 +35,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
35
35
  return (mod && mod.__esModule) ? mod : { "default": mod };
36
36
  };
37
37
  Object.defineProperty(exports, "__esModule", { value: true });
38
- exports.CoreTypes = exports.CoreTransformerUtils = exports.CoreTransformers = exports.defineAssetStorage = exports.fromEnv = exports.defineConfig = void 0;
38
+ exports.CoreTypes = exports.CoreTransformerUtils = exports.CoreTransformers = exports.defineAiProvider = exports.defineEmailProvider = exports.defineStorageProvider = exports.fromEnv = exports.defineConfig = void 0;
39
39
  require("./config/env");
40
40
  const fs_extra_1 = __importDefault(require("fs-extra"));
41
41
  const handoff_core_1 = require("handoff-core");
@@ -46,6 +46,7 @@ const make_1 = require("./cli/make");
46
46
  const store_1 = require("./cli/auth/store");
47
47
  const config_1 = require("./config");
48
48
  const pipeline_1 = __importStar(require("./pipeline"));
49
+ const figma_1 = require("./pipeline/figma");
49
50
  const content_kinds_1 = require("./registry/content-kinds");
50
51
  const store_2 = require("./store");
51
52
  const builder_1 = __importStar(require("./transformers/preview/component/builder"));
@@ -193,13 +194,10 @@ class Handoff {
193
194
  this.construct(this._options);
194
195
  return this;
195
196
  }
196
- preRunner(validate) {
197
+ preRunner() {
197
198
  if (!this.config) {
198
199
  throw Error('Handoff not initialized');
199
200
  }
200
- if (validate) {
201
- this.config = (0, config_1.validateConfig)(this.config);
202
- }
203
201
  return this;
204
202
  }
205
203
  fetch() {
@@ -504,12 +502,8 @@ class Handoff {
504
502
  if (!!this._handoffRunner) {
505
503
  return this._handoffRunner;
506
504
  }
507
- const apiCredentials = {
508
- projectId: this.config.figma_project_id,
509
- accessToken: this.config.dev_access_token,
510
- };
511
- // Initialize the provider
512
- const provider = handoff_core_1.Providers.RestApiProvider(apiCredentials);
505
+ const { projectId, accessToken } = (0, figma_1.resolveFigmaConnection)(this.config);
506
+ const provider = handoff_core_1.Providers.RestApiProvider({ projectId, accessToken });
513
507
  this._handoffRunner = (0, handoff_core_1.Handoff)(provider, {
514
508
  options: {
515
509
  transformer: this.runtimeConfig.options,
@@ -531,16 +525,8 @@ class Handoff {
531
525
  return this._handoffRunner;
532
526
  });
533
527
  }
534
- /**
535
- * Gets the project ID, falling back to filesystem-safe working path if figma_project_id is missing
536
- * @returns {string} The project ID to use for path construction
537
- */
528
+ /** Keys `.handoff/<projectId>`, so workspaces that share one installed package stage apart. */
538
529
  getProjectId() {
539
- var _a;
540
- if ((_a = this.config) === null || _a === void 0 ? void 0 : _a.figma_project_id) {
541
- return this.config.figma_project_id;
542
- }
543
- // Fallback to filesystem-safe transformation of working path
544
530
  return (0, path_3.generateFilesystemSafeId)(this.workingPath);
545
531
  }
546
532
  /**
@@ -548,7 +534,7 @@ class Handoff {
548
534
  * @returns {string} The absolute path to the output directory
549
535
  */
550
536
  getOutputPath() {
551
- return path_1.default.resolve(this.workingPath, this.exportsDirectory, this.getProjectId());
537
+ return path_1.default.resolve(this.workingPath, this.exportsDirectory);
552
538
  }
553
539
  /**
554
540
  * Gets the path to the tokens.json file
@@ -607,6 +593,12 @@ class Handoff {
607
593
  getMainConfigFilePath() {
608
594
  return this._mainConfigFilePath;
609
595
  }
596
+ /**
597
+ * Gets the selected profile config file path if one exists.
598
+ */
599
+ getProfileConfigFilePath() {
600
+ return this._profileConfigFilePath;
601
+ }
610
602
  getProfile() {
611
603
  return this._profile;
612
604
  }
@@ -651,7 +643,11 @@ Object.defineProperty(exports, "defineConfig", { enumerable: true, get: function
651
643
  var from_env_1 = require("./config/from-env");
652
644
  Object.defineProperty(exports, "fromEnv", { enumerable: true, get: function () { return from_env_1.fromEnv; } });
653
645
  var define_1 = require("./registry/asset-storage/define");
654
- Object.defineProperty(exports, "defineAssetStorage", { enumerable: true, get: function () { return define_1.defineAssetStorage; } });
646
+ Object.defineProperty(exports, "defineStorageProvider", { enumerable: true, get: function () { return define_1.defineStorageProvider; } });
647
+ var define_2 = require("./registry/email/define");
648
+ Object.defineProperty(exports, "defineEmailProvider", { enumerable: true, get: function () { return define_2.defineEmailProvider; } });
649
+ var define_3 = require("./ai/define");
650
+ Object.defineProperty(exports, "defineAiProvider", { enumerable: true, get: function () { return define_3.defineAiProvider; } });
655
651
  // Export transformers and types from handoff-core
656
652
  var handoff_core_2 = require("handoff-core");
657
653
  Object.defineProperty(exports, "CoreTransformers", { enumerable: true, get: function () { return handoff_core_2.Transformers; } });
@@ -78,6 +78,6 @@ export interface NavData {
78
78
  export { getNavData } from './resolver';
79
79
  export type { GetNavDataInput } from './resolver';
80
80
  export { getWorkspaceNavData } from './workspace';
81
- export type { WorkspaceNavAdapterOptions, WorkspaceNavLoaders } from './workspace';
81
+ export type { WorkspaceNavOptions, WorkspaceNavLoaders } from './workspace';
82
82
  export { getRegistryNavData } from './registry';
83
- export type { RegistryNavAdapterOptions, RegistryNavRecords } from './registry';
83
+ export type { RegistryNavOptions, RegistryNavRecords } from './registry';
@@ -5,11 +5,11 @@ export interface RegistryNavRecords {
5
5
  pages: NavPageRecord[];
6
6
  tokenSets: NavTokenSet[];
7
7
  }
8
- export interface RegistryNavAdapterOptions {
8
+ export interface RegistryNavOptions {
9
9
  shell: SectionLink[];
10
10
  load: NavLoad;
11
11
  basePath?: string;
12
12
  fetchRecords: () => Promise<RegistryNavRecords>;
13
13
  }
14
- /** Registry adapter: shell-only initial load, or one best-effort runtime-record fetch on refresh. */
15
- export declare const getRegistryNavData: (options: RegistryNavAdapterOptions) => Promise<NavData>;
14
+ /** Registry nav data: shell-only initial load, or one best-effort runtime-record fetch on refresh. */
15
+ export declare const getRegistryNavData: (options: RegistryNavOptions) => Promise<NavData>;
@@ -13,7 +13,7 @@ exports.getRegistryNavData = void 0;
13
13
  const menu_shell_1 = require("../utils/menu-shell");
14
14
  const resolver_1 = require("./resolver");
15
15
  const emptyRecords = () => ({ components: [], patterns: [], pages: [], tokenSets: [] });
16
- /** Registry adapter: shell-only initial load, or one best-effort runtime-record fetch on refresh. */
16
+ /** Registry nav data: shell-only initial load, or one best-effort runtime-record fetch on refresh. */
17
17
  const getRegistryNavData = (options) => __awaiter(void 0, void 0, void 0, function* () {
18
18
  var _a;
19
19
  let records = emptyRecords();
@@ -6,13 +6,13 @@ export interface WorkspaceNavLoaders {
6
6
  pages: () => Awaitable<NavPageRecord[]>;
7
7
  tokenSets: () => Awaitable<NavTokenSet[]>;
8
8
  }
9
- export interface WorkspaceNavAdapterOptions {
9
+ export interface WorkspaceNavOptions {
10
10
  docRoot: string;
11
11
  workingPagesDir?: string;
12
12
  basePath?: string;
13
13
  load?: NavLoad;
14
14
  loaders: WorkspaceNavLoaders;
15
15
  }
16
- /** Workspace/static adapter: live filesystem shell plus best-effort local snapshot loaders. */
17
- export declare const getWorkspaceNavData: (options: WorkspaceNavAdapterOptions) => Promise<NavData>;
16
+ /** Workspace/static nav data: live filesystem shell plus best-effort local snapshot loaders. */
17
+ export declare const getWorkspaceNavData: (options: WorkspaceNavOptions) => Promise<NavData>;
18
18
  export {};
@@ -64,7 +64,7 @@ const markdownTitle = (id, docRoot, workingPagesDir) => {
64
64
  }
65
65
  return undefined;
66
66
  };
67
- /** Workspace/static adapter: live filesystem shell plus best-effort local snapshot loaders. */
67
+ /** Workspace/static nav data: live filesystem shell plus best-effort local snapshot loaders. */
68
68
  const getWorkspaceNavData = (options) => __awaiter(void 0, void 0, void 0, function* () {
69
69
  var _a;
70
70
  const [loadedComponents, patterns, pages, loadedTokenSets] = yield Promise.all([
@@ -1,8 +1,19 @@
1
1
  import { Types as HandoffTypes } from 'handoff-core';
2
2
  import Handoff from '..';
3
+ import type { ResolvedConfig } from '../types/config';
4
+ /** Figma values resolved at config load, with the variable names they come from. */
5
+ export type FigmaConnection = {
6
+ projectId: string;
7
+ accessToken: string;
8
+ /** Unset when the config gives a literal file ID. */
9
+ projectIdEnv?: string;
10
+ accessTokenEnv?: string;
11
+ };
12
+ export declare const resolveFigmaConnection: (config: ResolvedConfig | null | undefined) => FigmaConnection;
3
13
  /**
4
- * Validate the figma auth tokens.
5
- * Prompts the user interactively if credentials are missing.
14
+ * Makes sure that `fetch` has a Figma file ID and access token. In a terminal, it asks for an empty
15
+ * value and can save it to the env file of the selected profile. Without a terminal, it throws
16
+ * `HandoffConfigError`.
6
17
  */
7
18
  export declare const validateFigmaAuth: (handoff: Handoff) => Promise<void>;
8
19
  /**