handoff-app 2.0.0-0 → 2.0.0-2

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 (91) hide show
  1. package/README.md +39 -17
  2. package/config/docs/foundations/colors/guidelines.md +9 -0
  3. package/config/docs/foundations/effects/guidelines.md +9 -0
  4. package/config/docs/foundations/effects/resources.md +9 -0
  5. package/config/docs/foundations/grid.md +10 -0
  6. package/config/docs/foundations/icons/guidelines.md +9 -0
  7. package/config/docs/foundations/images/guidelines.md +9 -0
  8. package/config/docs/foundations/images/resources.md +9 -0
  9. package/config/docs/foundations/images.md +10 -0
  10. package/config/docs/foundations/logo/resources.md +9 -0
  11. package/config/docs/foundations/typography/guidelines.md +9 -0
  12. package/config/docs/foundations/typography/resources.md +9 -0
  13. package/config/docs/foundations.md +5 -3
  14. package/dist/app-builder/build.js +89 -46
  15. package/dist/app-builder/client-config.js +5 -5
  16. package/dist/app-builder/paths.js +23 -1
  17. package/dist/app-builder/static-export.js +25 -0
  18. package/dist/app-builder/vercel-output.js +2 -2
  19. package/dist/artifacts/index.d.ts +1 -1
  20. package/dist/artifacts/index.js +4 -1
  21. package/dist/artifacts/url.d.ts +18 -0
  22. package/dist/artifacts/url.js +31 -1
  23. package/dist/cli/create.js +48 -3
  24. package/dist/commands/checkout/index.d.ts +6 -6
  25. package/dist/commands/checkout/index.js +8 -12
  26. package/dist/commands/publish/index.d.ts +6 -5
  27. package/dist/commands/publish/index.js +8 -11
  28. package/dist/commands/utils.d.ts +6 -0
  29. package/dist/commands/utils.js +9 -1
  30. package/dist/index.d.ts +12 -8
  31. package/dist/index.js +26 -10
  32. package/dist/registry/asset-storage/resolve.d.ts +1 -1
  33. package/dist/registry/asset-storage/resolve.js +1 -1
  34. package/dist/registry/checkout/index.d.ts +10 -5
  35. package/dist/registry/checkout/index.js +62 -11
  36. package/dist/registry/client.d.ts +2 -1
  37. package/dist/registry/client.js +8 -0
  38. package/dist/registry/db/client.d.ts +9 -9
  39. package/dist/registry/db/client.js +7 -7
  40. package/dist/registry/db/{adapter.d.ts → driver.d.ts} +16 -14
  41. package/dist/registry/db/{adapter.js → driver.js} +17 -15
  42. package/dist/registry/db/index.d.ts +1 -1
  43. package/dist/registry/db/index.js +9 -9
  44. package/dist/registry/db/migrate.d.ts +1 -1
  45. package/dist/registry/db/migrate.js +5 -5
  46. package/dist/registry/publish/index.d.ts +8 -1
  47. package/dist/registry/publish/index.js +88 -2
  48. package/dist/registry/transfer.d.ts +13 -0
  49. package/dist/types/config.d.ts +12 -8
  50. package/drizzle.config.ts +1 -1
  51. package/package.json +1 -1
  52. package/src/app/components/Foundations/DisplayLogo.tsx +8 -2
  53. package/src/app/lib/docs-api/runtime-config.ts +10 -10
  54. package/src/app/lib/registry-api/transfer.ts +29 -0
  55. package/src/app/lib/registry-connection.ts +2 -2
  56. package/src/app/next.config.mjs +1 -1
  57. package/src/app/pages/api/registry/transfer/[kind]/index.ts +21 -0
  58. package/src/app/pages/assets/logos.tsx +4 -1
  59. package/src/app/pages/foundations/icons/index.tsx +7 -5
  60. package/src/app/pages/foundations/icons/{[name] → library/[name]}/index.tsx +8 -8
  61. package/src/app/pages/foundations/logo.tsx +5 -4
  62. package/src/app-builder/build.ts +75 -55
  63. package/src/app-builder/client-config.ts +3 -3
  64. package/src/app-builder/paths.ts +25 -1
  65. package/src/app-builder/static-export.ts +27 -0
  66. package/src/app-builder/vercel-output.ts +2 -2
  67. package/src/artifacts/index.ts +8 -1
  68. package/src/artifacts/url.ts +32 -0
  69. package/src/cli/create.ts +58 -3
  70. package/src/commands/checkout/index.ts +10 -15
  71. package/src/commands/publish/index.ts +10 -14
  72. package/src/commands/utils.ts +12 -0
  73. package/src/index.ts +26 -12
  74. package/src/registry/asset-storage/resolve.ts +1 -1
  75. package/src/registry/checkout/index.ts +73 -11
  76. package/src/registry/client.ts +7 -1
  77. package/src/registry/db/client.ts +12 -12
  78. package/src/registry/db/{adapter.ts → driver.ts} +19 -17
  79. package/src/registry/db/index.ts +4 -4
  80. package/src/registry/db/migrate.ts +5 -5
  81. package/src/registry/publish/index.ts +100 -2
  82. package/src/registry/transfer.ts +14 -0
  83. package/src/types/config.ts +12 -8
  84. package/templates/default/README.md.tpl +40 -1
  85. package/templates/default/gitignore.tpl +18 -3
  86. package/templates/default/handoff.config.js.tpl +14 -0
  87. package/templates/default/handoff.config.ts.tpl +14 -0
  88. package/templates/default/handoff.config.with-examples.js.tpl +14 -0
  89. package/templates/default/handoff.config.with-examples.ts.tpl +14 -0
  90. package/templates/default/package.json.tpl +2 -1
  91. package/templates/default/vercel.json.tpl +4 -0
package/README.md CHANGED
@@ -25,6 +25,7 @@ authoring locally or running a shared, deployed catalog.
25
25
  * [Building artifacts](#building-artifacts)
26
26
  * [Static site (default)](#static-site-default)
27
27
  * [Registry app](#registry-app)
28
+ * [Deploying](#deploying)
28
29
  * [Publishing and checkout](#publishing-and-checkout)
29
30
  * [Configuration](#configuration)
30
31
  * [The runtime block](#the-runtime-block)
@@ -144,7 +145,7 @@ my-handoff-project/
144
145
  ├─ patterns/ # multi-component compositions
145
146
  ├─ sass/ # optional global SCSS entry
146
147
  ├─ js/ # optional global JS entry
147
- ├─ exported/ # generated tokens and artifacts (gitignored)
148
+ ├─ exported/ # fetched tokens and assets (commit these so clean/CI builds have data)
148
149
  └─ out/ # build output (gitignored)
149
150
  ```
150
151
 
@@ -226,8 +227,9 @@ preview updates. The site boots at http://localhost:3000 by default.
226
227
 
227
228
  ## Building artifacts
228
229
 
229
- `handoff-app build` accepts a `--target` flag. The target, not `NODE_ENV`,
230
- decides what gets produced.
230
+ `handoff-app build` accepts two independent flags. `--target` decides *what* is
231
+ produced — the target, not `NODE_ENV`, drives this. The optional `--package`
232
+ decides *how* that target is packaged for deployment.
231
233
 
232
234
  ### Static site (default)
233
235
 
@@ -240,9 +242,8 @@ The static target builds your workspace components, patterns, and tokens,
240
242
  materializes the docs read model into route-shaped static files, and runs a
241
243
  static export to `out/<projectId>` (respecting `sitesOutputDirectory`). The
242
244
  output is plain static files: it renders list, detail, preview, and inspect
243
- pages and loads previews through canonical artifact URLs with no live server. It
244
- works on a managed host like Vercel and on a plain static file server with no
245
- host-specific rewrites.
245
+ pages and loads previews through canonical artifact URLs with no live server, so
246
+ any static file server or CDN can serve it with no host-specific rewrites.
246
247
 
247
248
  The build fails clearly if a required artifact referenced by generated HTML
248
249
  cannot be materialized, and it fails if the runtime is registry-only (there is
@@ -269,17 +270,38 @@ DATABASE_URL="postgres://…" node out/registry/server.js
269
270
  ```
270
271
 
271
272
  A generated `README.md` next to the bundle documents the required env vars and
272
- Docker/Vercel specifics. Apply database migrations from the CLI as a controlled
273
- release step (e.g. in CI/CD) before deploying:
273
+ how to run it. Apply database migrations from the CLI as a controlled release
274
+ step (e.g. in CI/CD) before deploying:
274
275
 
275
276
  ```bash
276
277
  handoff-app db:migrate
277
278
  ```
278
279
 
279
280
  `db:migrate` reads your project config and DB env vars, applies the
280
- package-owned migration set, and runs independently of `build`. The database
281
- adapter (`pg` or `neon`) is chosen with `runtime.registry.database.adapter`, and
282
- both adapters ship with the package.
281
+ package-owned migration set, and runs independently of `build`. PostgreSQL is
282
+ the supported database; `runtime.registry.database.driver` selects the
283
+ connection driver (`pg` or `neon`) — how to connect, not the database engine —
284
+ and both drivers ship with the package.
285
+
286
+ ### Deploying
287
+
288
+ * **Static** produces plain files under `out/<projectId>`. Serve them with any
289
+ static file server or CDN. Commit `exported/` so a clean checkout has token
290
+ data to build from.
291
+ * **Registry** produces a self-contained Node bundle under `out/registry`. Run it
292
+ on any Node host with `node out/registry/server.js` and a `DATABASE_URL` in the
293
+ environment.
294
+
295
+ The optional `--package` flag repackages a target without changing what it
296
+ builds:
297
+
298
+ | `--package` | Output |
299
+ | --- | --- |
300
+ | omitted (default) | `out/<projectId>` (static) or the `out/registry` Node bundle (registry) |
301
+ | `vercel` | `.vercel/output`, a Vercel Build Output API bundle |
302
+
303
+ `handoff-app init` can scaffold a `vercel.json` that runs a packaged build; edit
304
+ its `buildCommand` to change the target.
283
305
 
284
306
  ## Publishing and checkout
285
307
 
@@ -304,8 +326,8 @@ HANDOFF_REGISTRY_ACCESS_TOKEN=your-registry-token
304
326
  Then push and pull entities by `id`:
305
327
 
306
328
  ```bash
307
- handoff-app publish component button # build locally, upload that entity's package
308
- handoff-app checkout pattern hero # pull a registry entity into this workspace
329
+ handoff-app publish components button # build locally, upload that entity's package
330
+ handoff-app checkout patterns hero # pull a registry entity into this workspace
309
331
  ```
310
332
 
311
333
  `publish` runs a fresh targeted build of the selected entity and uploads only
@@ -373,7 +395,7 @@ runtime: {
373
395
  registry: {
374
396
  databaseUrlEnv: 'DATABASE_URL',
375
397
  apiTokenEnv: 'HANDOFF_REGISTRY_API_TOKEN',
376
- database: { adapter: 'pg' }, // 'pg' | 'neon'
398
+ database: { driver: 'pg' }, // connection driver: 'pg' | 'neon' (PostgreSQL only)
377
399
  },
378
400
  },
379
401
  ```
@@ -410,10 +432,10 @@ See [docs/api.md](docs/api.md#hooks) for hook arguments and examples.
410
432
  | `fetch` | Fetch design tokens from Figma |
411
433
  | `dev` | Start the local dev server with the live docs read API |
412
434
  | `start` | Start the dev server with file watchers for live authoring |
413
- | `build [--target static\|registry]` | Build the static site (default) or package the registry app |
435
+ | `build [--target static\|registry] [--package standalone\|vercel]` | Build the static site (default) or registry app; `--package vercel` emits a Vercel Build Output bundle |
414
436
  | `build:components [component]` | Build components without building the full app |
415
- | `publish <component\|pattern> <id>` | Build and publish an entity to the connected registry |
416
- | `checkout <component\|pattern> <id>` | Pull an entity from the connected registry |
437
+ | `publish <components\|patterns\|pages\|tokens\|assets> [id]` | Build and publish entities to the connected registry (all of the kind, or one by id) |
438
+ | `checkout <components\|patterns\|pages\|tokens\|assets> [id]` | Pull entities from the connected registry into this workspace |
417
439
  | `db:migrate` | Run registry database migrations (Drizzle / PostgreSQL) |
418
440
  | `make:component <name>` | Scaffold a new component |
419
441
  | `make:page <name> [parent]` | Scaffold a documentation page |
@@ -0,0 +1,9 @@
1
+ ---
2
+ title: Color Guidelines
3
+ description: How to apply the color palette consistently and accessibly across products.
4
+ metaTitle: 'Color Guidelines | Handoff Design System'
5
+ metaDescription: 'How to apply the color palette consistently and accessibly across products.'
6
+ ---
7
+
8
+ This is placeholder content for the **Color Guidelines** page. Replace it with your rules for
9
+ contrast, pairing, and semantic usage. Markdown and <b>HTML</b> are both supported.
@@ -0,0 +1,9 @@
1
+ ---
2
+ title: Effects Guidelines
3
+ description: When and how to apply shadows, blurs, and elevation across the interface.
4
+ metaTitle: 'Effects Guidelines | Handoff Design System'
5
+ metaDescription: 'When and how to apply shadows, blurs, and elevation across the interface.'
6
+ ---
7
+
8
+ This is placeholder content for the **Effects Guidelines** page. Replace it with your rules for
9
+ elevation, depth, and motion. Markdown and <b>HTML</b> are both supported.
@@ -0,0 +1,9 @@
1
+ ---
2
+ title: Effects Resources
3
+ description: Reference values and assets for reproducing the brand's effects in any tool.
4
+ metaTitle: 'Effects Resources | Handoff Design System'
5
+ metaDescription: 'Reference values and assets for reproducing effects in any tool.'
6
+ ---
7
+
8
+ This is placeholder content for the **Effects Resources** page. Replace it with your shadow tokens,
9
+ blur values, and export notes. Markdown and <b>HTML</b> are both supported.
@@ -0,0 +1,10 @@
1
+ ---
2
+ title: Grid
3
+ description: Layout grid and spacing system that keeps compositions aligned and balanced.
4
+ image: hero-design
5
+ metaTitle: 'Grid | Handoff Design System'
6
+ metaDescription: 'Layout grid and spacing system that keeps compositions aligned and balanced.'
7
+ ---
8
+
9
+ This is placeholder content for the **Grid** page. Replace it with your columns, gutters, and
10
+ breakpoint rules. Markdown and <b>HTML</b> are both supported.
@@ -0,0 +1,9 @@
1
+ ---
2
+ title: Icon Guidelines
3
+ description: Sizing, spacing, and usage rules that keep the icon set consistent.
4
+ metaTitle: 'Icon Guidelines | Handoff Design System'
5
+ metaDescription: 'Sizing, spacing, and usage rules that keep the icon set consistent.'
6
+ ---
7
+
8
+ This is placeholder content for the **Icon Guidelines** page. Replace it with your rules for grid
9
+ sizing, stroke, and when to use each icon. Markdown and <b>HTML</b> are both supported.
@@ -0,0 +1,9 @@
1
+ ---
2
+ title: Image Guidelines
3
+ description: How to select, crop, and treat imagery so it stays on brand.
4
+ metaTitle: 'Image Guidelines | Handoff Design System'
5
+ metaDescription: 'How to select, crop, and treat imagery so it stays on brand.'
6
+ ---
7
+
8
+ This is placeholder content for the **Image Guidelines** page. Replace it with your rules for
9
+ composition, cropping, and treatments. Markdown and <b>HTML</b> are both supported.
@@ -0,0 +1,9 @@
1
+ ---
2
+ title: Image Resources
3
+ description: Source files and downloads for approved photography and illustration.
4
+ metaTitle: 'Image Resources | Handoff Design System'
5
+ metaDescription: 'Source files and downloads for approved photography and illustration.'
6
+ ---
7
+
8
+ This is placeholder content for the **Image Resources** page. Replace it with your image library,
9
+ downloads, and licensing notes. Markdown and <b>HTML</b> are both supported.
@@ -0,0 +1,10 @@
1
+ ---
2
+ title: Images
3
+ description: Photography and illustration style that shapes how the brand looks and feels.
4
+ image: hero-design
5
+ metaTitle: 'Images | Handoff Design System'
6
+ metaDescription: 'Photography and illustration style that shapes how the brand looks and feels.'
7
+ ---
8
+
9
+ This is placeholder content for the **Images** page. Replace it with your art direction,
10
+ photography style, and illustration principles. Markdown and <b>HTML</b> are both supported.
@@ -0,0 +1,9 @@
1
+ ---
2
+ title: Logo Resources
3
+ description: Downloadable logo files and everything teams need to represent the brand correctly.
4
+ metaTitle: 'Logo Resources | Handoff Design System'
5
+ metaDescription: 'Downloadable logo files and everything teams need to represent the brand correctly.'
6
+ ---
7
+
8
+ This is placeholder content for the **Logo Resources** page. Replace it with your logo downloads,
9
+ file formats, and usage notes. Markdown and <b>HTML</b> are both supported.
@@ -0,0 +1,9 @@
1
+ ---
2
+ title: Typography Guidelines
3
+ description: Best practices for type scale, hierarchy, and readability across the system.
4
+ metaTitle: 'Typography Guidelines | Handoff Design System'
5
+ metaDescription: 'Best practices for type scale, hierarchy, and readability across the system.'
6
+ ---
7
+
8
+ This is placeholder content for the **Typography Guidelines** page. Replace it with your rules for
9
+ hierarchy, spacing, and responsive type. Markdown and <b>HTML</b> are both supported.
@@ -0,0 +1,9 @@
1
+ ---
2
+ title: Typography Resources
3
+ description: Font files, licenses, and assets needed to work with the brand typography.
4
+ metaTitle: 'Typography Resources | Handoff Design System'
5
+ metaDescription: 'Font files, licenses, and assets needed to work with the brand typography.'
6
+ ---
7
+
8
+ This is placeholder content for the **Typography Resources** page. Replace it with your font
9
+ downloads, licensing details, and platform notes. Markdown and <b>HTML</b> are both supported.
@@ -67,8 +67,10 @@ menu:
67
67
  title: Images
68
68
  icon: image
69
69
  menu:
70
- - path: foundations/icons
71
- title: Library
72
- - path: foundations/icons/guidelines
70
+ - path: foundations/images
71
+ title: Overview
72
+ - path: foundations/images/guidelines
73
73
  title: Guidelines
74
+ - path: foundations/images/resources
75
+ title: Resources
74
76
  ---
@@ -1,4 +1,27 @@
1
1
  "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || function (mod) {
19
+ if (mod && mod.__esModule) return mod;
20
+ var result = {};
21
+ if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
22
+ __setModuleDefault(result, mod);
23
+ return result;
24
+ };
2
25
  var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
3
26
  function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
4
27
  return new (P || (P = Promise))(function (resolve, reject) {
@@ -13,6 +36,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
13
36
  };
14
37
  Object.defineProperty(exports, "__esModule", { value: true });
15
38
  exports.devApp = exports.watchApp = exports.getRegistryBuildOutputPath = exports.HandoffBuildError = exports.DEFAULT_BUILD_TARGET = void 0;
39
+ const p = __importStar(require("@clack/prompts"));
16
40
  const cross_spawn_1 = __importDefault(require("cross-spawn"));
17
41
  const fs_extra_1 = __importDefault(require("fs-extra"));
18
42
  const path_1 = __importDefault(require("path"));
@@ -21,7 +45,7 @@ const patterns_1 = require("../pipeline/patterns");
21
45
  const builder_1 = __importDefault(require("../transformers/preview/component/builder"));
22
46
  const css_1 = require("../transformers/preview/component/css");
23
47
  const javascript_1 = require("../transformers/preview/component/javascript");
24
- const adapter_1 = require("../registry/db/adapter");
48
+ const driver_1 = require("../registry/db/driver");
25
49
  const resolve_1 = require("../registry/asset-storage/resolve");
26
50
  const logger_1 = require("../utils/logger");
27
51
  const client_config_1 = require("./client-config");
@@ -78,6 +102,51 @@ const assertStaticBuildAllowed = (handoff) => {
78
102
  'or package the registry app with `handoff-app build --target registry`.');
79
103
  }
80
104
  };
105
+ /**
106
+ * Run the Next.js production build while keeping its delayed output out of the successful CLI flow.
107
+ * Captured output is replayed only when the child fails so compiler diagnostics remain available.
108
+ */
109
+ const runNextBuild = (appPath, target, errorLabel) => __awaiter(void 0, void 0, void 0, function* () {
110
+ const spinner = p.spinner();
111
+ spinner.start('Building Next.js app...');
112
+ const stdout = [];
113
+ const stderr = [];
114
+ yield new Promise((resolve, reject) => {
115
+ var _a, _b;
116
+ const buildProcess = (0, cross_spawn_1.default)('npx', ['next', 'build'], {
117
+ cwd: appPath,
118
+ stdio: ['inherit', 'pipe', 'pipe'],
119
+ env: Object.assign(Object.assign({}, process.env), { NODE_ENV: 'production', HANDOFF_BUILD_TARGET: target }),
120
+ });
121
+ (_a = buildProcess.stdout) === null || _a === void 0 ? void 0 : _a.on('data', (chunk) => stdout.push(Buffer.from(chunk)));
122
+ (_b = buildProcess.stderr) === null || _b === void 0 ? void 0 : _b.on('data', (chunk) => stderr.push(Buffer.from(chunk)));
123
+ let settled = false;
124
+ const fail = (message) => {
125
+ if (settled)
126
+ return;
127
+ settled = true;
128
+ spinner.stop('Next.js app build failed');
129
+ logger_1.Logger.childProcessBuffer(Buffer.concat(stdout));
130
+ logger_1.Logger.childProcessBuffer(Buffer.concat(stderr));
131
+ reject(new Error(message));
132
+ };
133
+ buildProcess.once('error', (error) => {
134
+ fail(`${errorLabel} failed to start\nSpawn error: ${error.message}`);
135
+ });
136
+ buildProcess.once('close', (code, signal) => {
137
+ if (settled)
138
+ return;
139
+ if (code === 0) {
140
+ settled = true;
141
+ spinner.stop('Next.js app built successfully');
142
+ resolve();
143
+ return;
144
+ }
145
+ const reason = signal ? ` because it was terminated by signal ${signal}` : ` with exit code ${code}`;
146
+ fail(`${errorLabel} failed${reason}`);
147
+ });
148
+ });
149
+ });
81
150
  /**
82
151
  * Output directory for the packaged registry app. Lives under the configurable sites output
83
152
  * directory (`sitesOutputDirectory`, default `out`) alongside the static target's `out/<projectId>`
@@ -147,9 +216,9 @@ const initializeProjectApp = (handoff_1, ...args_1) => __awaiter(void 0, [handof
147
216
  // Resolved runtime mode + registry connection inputs (names only) baked into the Next bundle so the
148
217
  // deployed registry app resolves its mode/DB env-var name without any build-machine filesystem.
149
218
  const escapedRuntimeMode = escapeForSingleQuotedJsString(runtimeMode);
150
- const escapedRegistryAdapter = escapeForSingleQuotedJsString((0, adapter_1.resolveRegistryAdapter)(handoff.config));
151
- const escapedDatabaseUrlEnv = escapeForSingleQuotedJsString((0, adapter_1.resolveDatabaseUrlEnv)(handoff.config));
152
- const escapedApiTokenEnv = escapeForSingleQuotedJsString((0, adapter_1.resolveApiTokenEnv)(handoff.config));
219
+ const escapedRegistryDriver = escapeForSingleQuotedJsString((0, driver_1.resolveRegistryDriver)(handoff.config));
220
+ const escapedDatabaseUrlEnv = escapeForSingleQuotedJsString((0, driver_1.resolveDatabaseUrlEnv)(handoff.config));
221
+ const escapedApiTokenEnv = escapeForSingleQuotedJsString((0, driver_1.resolveApiTokenEnv)(handoff.config));
153
222
  // Asset storage selection baked (provider + module + env-var names + non-secret options JSON).
154
223
  const assetStorage = (0, resolve_1.resolveAssetStorageFromConfig)(handoff.config);
155
224
  const escapedAssetStorageAdapter = escapeForSingleQuotedJsString(assetStorage.adapterKind);
@@ -165,7 +234,7 @@ const initializeProjectApp = (handoff_1, ...args_1) => __awaiter(void 0, [handof
165
234
  '%HANDOFF_EXPORT_PATH%': escapedExportPath,
166
235
  '%HANDOFF_WEBSOCKET_PORT%': escapedWebsocketPort,
167
236
  '%HANDOFF_RUNTIME_MODE%': escapedRuntimeMode,
168
- '%HANDOFF_REGISTRY_ADAPTER%': escapedRegistryAdapter,
237
+ '%HANDOFF_REGISTRY_DRIVER%': escapedRegistryDriver,
169
238
  '%HANDOFF_REGISTRY_DATABASE_URL_ENV%': escapedDatabaseUrlEnv,
170
239
  '%HANDOFF_REGISTRY_API_TOKEN_ENV%': escapedApiTokenEnv,
171
240
  '%HANDOFF_ASSET_STORAGE_ADAPTER%': escapedAssetStorageAdapter,
@@ -216,20 +285,7 @@ const buildApp = (handoff_1, ...args_1) => __awaiter(void 0, [handoff_1, ...args
216
285
  (0, static_export_1.validateReferencedArtifacts)(handoff);
217
286
  // Build app. The static target drives Next's `output: 'export'` via HANDOFF_BUILD_TARGET so the
218
287
  // export gate is tied to the resolved target, not to NODE_ENV.
219
- const buildResult = cross_spawn_1.default.sync('npx', ['next', 'build'], {
220
- cwd: appPath,
221
- stdio: ['inherit', 'pipe', 'pipe'],
222
- env: Object.assign(Object.assign({}, process.env), { NODE_ENV: 'production', HANDOFF_BUILD_TARGET: target }),
223
- });
224
- logger_1.Logger.childProcessBuffer(buildResult.stdout);
225
- logger_1.Logger.childProcessBuffer(buildResult.stderr);
226
- if (buildResult.status !== 0) {
227
- let errorMsg = `Next.js build failed with exit code ${buildResult.status}`;
228
- if (buildResult.error) {
229
- errorMsg += `\nSpawn error: ${buildResult.error.message}`;
230
- }
231
- throw new Error(errorMsg);
232
- }
288
+ yield runNextBuild(appPath, target, 'Next.js build');
233
289
  // Reproduce the docs read API (`/api/docs/*`) as route-shaped static files in the export output,
234
290
  // since `output: 'export'` disables the live API routes.
235
291
  const exportDir = path_1.default.resolve(appPath, 'out');
@@ -306,7 +362,7 @@ pipeline) before deploying new code:
306
362
  ${databaseUrlEnv}="postgres://…" handoff-app db:migrate
307
363
  \`\`\`
308
364
 
309
- \`db:migrate\` reads your project config + DB env vars, resolves the same database adapter the app was
365
+ \`db:migrate\` reads your project config + DB env vars, resolves the same database driver the app was
310
366
  built with, and applies the package-owned migration set. It runs independently of \`build\` and of
311
367
  starting the server, so run it as a release/one-shot job.
312
368
 
@@ -352,15 +408,15 @@ containers, custom Node servers, and other non-Vercel hosts.
352
408
  });
353
409
  /**
354
410
  * Resolve the set of npm packages the packaged registry app must be able to `require`/`import` at
355
- * runtime, given the configured database adapter. `next`/`react`/`react-dom` run the server and React
411
+ * runtime, given the configured database driver. `next`/`react`/`react-dom` run the server and React
356
412
  * runtime; `drizzle-orm` backs both the request-time DB client and the migration runner; the driver
357
- * is adapter-specific (the Neon serverless driver also needs `ws` for its Node WebSocket transport).
413
+ * package is driver-specific (the Neon serverless driver also needs `ws` for its Node WebSocket transport).
358
414
  */
359
415
  const getRequiredRegistryRuntimeModules = (handoff) => {
360
416
  var _a;
361
417
  const base = ['next', 'react', 'react-dom', 'drizzle-orm'];
362
- const adapter = (0, adapter_1.resolveRegistryAdapter)(handoff.config);
363
- const driver = adapter === 'neon' ? ['@neondatabase/serverless', 'ws'] : ['pg'];
418
+ const driver = (0, driver_1.resolveRegistryDriver)(handoff.config);
419
+ const driverModules = driver === 'neon' ? ['@neondatabase/serverless', 'ws'] : ['pg'];
364
420
  // Asset-storage SDKs the deployed registry must be able to load at request time. The pre-packaged
365
421
  // Vercel Blob adapter needs `@vercel/blob`; a custom adapter may declare its installed SDK
366
422
  // package(s) via `assetStorage.options.sdkModules` so the trace/assertion covers them.
@@ -373,7 +429,7 @@ const getRequiredRegistryRuntimeModules = (handoff) => {
373
429
  if (Array.isArray(sdkModules)) {
374
430
  storage.push(...sdkModules.filter((mod) => typeof mod === 'string'));
375
431
  }
376
- return [...base, ...driver, ...storage];
432
+ return [...base, ...driverModules, ...storage];
377
433
  };
378
434
  /**
379
435
  * Build-time guard against the empty-`node_modules` regression (the artifact would otherwise boot-fail
@@ -482,7 +538,7 @@ const assembleRegistryStandalone = (handoff, appPath, standaloneRoot, entryDir)
482
538
  * - `vercel` → the Vercel Build Output API directory (`.vercel/output`) at the repo root: the
483
539
  * traced bundle wrapped as a Node function plus CDN static assets and a route table.
484
540
  *
485
- * Mode is forced to `registry`; the database adapter is honored from config (single-sourced so build
541
+ * Mode is forced to `registry`; the database driver is honored from config (single-sourced so build
486
542
  * and `db:migrate` never diverge). No connection-string value is ever baked — only env-var *names*.
487
543
  */
488
544
  const buildRegistryApp = (handoff_1, ...args_1) => __awaiter(void 0, [handoff_1, ...args_1], void 0, function* (handoff, buildPackage = 'standalone') {
@@ -495,15 +551,15 @@ const buildRegistryApp = (handoff_1, ...args_1) => __awaiter(void 0, [handoff_1,
495
551
  if (sourceMode !== 'registry') {
496
552
  logger_1.Logger.info(`Source runtime.mode is "${sourceMode}"; forcing "registry" mode in the packaged registry app.`);
497
553
  }
498
- // The adapter stays single-sourced from config (never flipped by the package flag), so build and
554
+ // The driver stays single-sourced from config (never flipped by the package flag), so build and
499
555
  // `db:migrate` always resolve the same one. `pg` uses long-lived TCP connections that pool poorly on
500
556
  // serverless; warn (non-fatally) so the operator points DATABASE_URL at a pooled endpoint or selects
501
- // the Neon adapter. The connection-string value is not available at build time, so this cannot be
557
+ // the Neon driver. The connection-string value is not available at build time, so this cannot be
502
558
  // validated — it is guidance only and never fails the build.
503
- if (buildPackage === 'vercel' && (0, adapter_1.resolveRegistryAdapter)(handoff.config) === 'pg') {
504
- logger_1.Logger.warn('Registry Vercel build uses the "pg" adapter (standard TCP connections), which pools poorly on ' +
559
+ if (buildPackage === 'vercel' && (0, driver_1.resolveRegistryDriver)(handoff.config) === 'pg') {
560
+ logger_1.Logger.warn('Registry Vercel build uses the "pg" driver (standard TCP connections), which pools poorly on ' +
505
561
  'serverless and can exhaust the database connection limit. Point DATABASE_URL at a pooled endpoint ' +
506
- '(PgBouncer / transaction pooling), or set runtime.registry.database.adapter: "neon" for a ' +
562
+ '(PgBouncer / transaction pooling), or set runtime.registry.database.driver: "neon" for a ' +
507
563
  'serverless-native HTTP/WebSocket driver. The build will continue.');
508
564
  }
509
565
  // Stage the app without any workspace-derived artifacts — the registry serves from the database.
@@ -524,20 +580,7 @@ const buildRegistryApp = (handoff_1, ...args_1) => __awaiter(void 0, [handoff_1,
524
580
  yield fs_extra_1.default.remove((0, vercel_output_1.getVercelOutputPath)(handoff));
525
581
  // Build the dynamic app. `output: 'standalone'` (driven by HANDOFF_BUILD_TARGET) traces the runtime
526
582
  // and the selected Postgres/Neon driver into a self-contained bundle — never a static export.
527
- const buildResult = cross_spawn_1.default.sync('npx', ['next', 'build'], {
528
- cwd: appPath,
529
- stdio: ['inherit', 'pipe', 'pipe'],
530
- env: Object.assign(Object.assign({}, process.env), { NODE_ENV: 'production', HANDOFF_BUILD_TARGET: 'registry' }),
531
- });
532
- logger_1.Logger.childProcessBuffer(buildResult.stdout);
533
- logger_1.Logger.childProcessBuffer(buildResult.stderr);
534
- if (buildResult.status !== 0) {
535
- let errorMsg = `Registry app build failed with exit code ${buildResult.status}`;
536
- if (buildResult.error) {
537
- errorMsg += `\nSpawn error: ${buildResult.error.message}`;
538
- }
539
- throw new Error(errorMsg);
540
- }
583
+ yield runNextBuild(appPath, 'registry', 'Registry app build');
541
584
  // Assemble the deployable package. Standalone tracing is rooted at the package (modulePath), so the
542
585
  // app's traced files land under `<relAppDir>` inside the standalone tree; static assets and public
543
586
  // files are not traced and must be copied alongside the server entry.
@@ -561,7 +604,7 @@ const buildRegistryApp = (handoff_1, ...args_1) => __awaiter(void 0, [handoff_1,
561
604
  // `handoff-app` source/CLI (`handoff-app db:migrate`) as a controlled release step (e.g. CI/CD),
562
605
  // never from the deployed bundle. See the generated README for the migrate-then-deploy flow.
563
606
  const entryRelativePath = path_1.default.relative(output, serverEntry).split(path_1.default.sep).join('/');
564
- yield writeRegistryDeploymentReadme(output, entryRelativePath, (0, adapter_1.resolveDatabaseUrlEnv)(handoff.config));
607
+ yield writeRegistryDeploymentReadme(output, entryRelativePath, (0, driver_1.resolveDatabaseUrlEnv)(handoff.config));
565
608
  logger_1.Logger.success(`Packaged registry app at ${output} (start: \`node ${entryRelativePath}\`, migrate: \`handoff-app db:migrate\`).`);
566
609
  });
567
610
  /**
@@ -17,7 +17,7 @@ const fs_extra_1 = __importDefault(require("fs-extra"));
17
17
  const path_1 = __importDefault(require("path"));
18
18
  const config_1 = require("../config");
19
19
  const resolve_1 = require("../registry/asset-storage/resolve");
20
- const adapter_1 = require("../registry/db/adapter");
20
+ const driver_1 = require("../registry/db/driver");
21
21
  const paths_1 = require("./paths");
22
22
  /**
23
23
  * Publishes the tokens API files to the public directory.
@@ -55,7 +55,7 @@ exports.generateTokensApi = generateTokensApi;
55
55
  * Build the server-only runtime config consumed by the docs read API.
56
56
  *
57
57
  * The browser-facing `client.config.json` carries only the resolved mode; the server additionally
58
- * needs the registry connection *inputs* (selected adapter + the *name* of the database-URL env
58
+ * needs the registry connection *inputs* (selected driver + the *name* of the database-URL env
59
59
  * var) to back the registry-mode docs read API. These are non-secret — the connection-string value
60
60
  * itself is never persisted, only resolved from the env var at request time.
61
61
  */
@@ -65,9 +65,9 @@ const buildServerRuntimeConfig = (config, modeOverride) => {
65
65
  return {
66
66
  mode: (_b = modeOverride !== null && modeOverride !== void 0 ? modeOverride : (_a = config === null || config === void 0 ? void 0 : config.runtime) === null || _a === void 0 ? void 0 : _a.mode) !== null && _b !== void 0 ? _b : 'workspace',
67
67
  registry: {
68
- adapter: (0, adapter_1.resolveRegistryAdapter)(config),
69
- databaseUrlEnv: (0, adapter_1.resolveDatabaseUrlEnv)(config),
70
- apiTokenEnv: (0, adapter_1.resolveApiTokenEnv)(config),
68
+ driver: (0, driver_1.resolveRegistryDriver)(config),
69
+ databaseUrlEnv: (0, driver_1.resolveDatabaseUrlEnv)(config),
70
+ apiTokenEnv: (0, driver_1.resolveApiTokenEnv)(config),
71
71
  },
72
72
  assetStorage: {
73
73
  adapter: assetStorage.adapterKind,
@@ -50,14 +50,33 @@ const mirrorDirectory = (sourcePath, destinationPath) => __awaiter(void 0, void
50
50
  yield fs_extra_1.default.ensureDir(path_1.default.dirname(destinationPath));
51
51
  yield fs_extra_1.default.copy(sourcePath, destinationPath, { overwrite: true });
52
52
  });
53
+ /**
54
+ * Copy the fetch-produced asset bundles (`exported/<id>/{collection}.zip`) into the app's public
55
+ * root, where the docs read API serves them at `/api/docs/assets/{collection}/{collection}.zip`.
56
+ * `fetch` writes these bundles, but `build`/`start` cleans and re-stages `.handoff` without them, so
57
+ * the download links 404 until the next fetch. Copying them here keeps the route working in workspace
58
+ * dev and static builds. Projects with no logos/icons have no bundles, so those are skipped.
59
+ */
60
+ const stageAssetDownloadBundles = (handoff, destinationPublicPath) => __awaiter(void 0, void 0, void 0, function* () {
61
+ const bundles = [
62
+ { src: handoff.getLogosZipFilePath(), name: 'logos.zip' },
63
+ { src: handoff.getIconsZipFilePath(), name: 'icons.zip' },
64
+ ];
65
+ for (const { src, name } of bundles) {
66
+ if (fs_extra_1.default.existsSync(src) && fs_extra_1.default.statSync(src).isFile()) {
67
+ yield fs_extra_1.default.ensureDir(destinationPublicPath);
68
+ yield fs_extra_1.default.copy(src, path_1.default.resolve(destinationPublicPath, name), { overwrite: true });
69
+ }
70
+ }
71
+ });
53
72
  /**
54
73
  * Copy the public dir from the working dir to the module dir.
55
74
  */
56
75
  const syncPublicFiles = (handoff) => __awaiter(void 0, void 0, void 0, function* () {
57
76
  const appPath = (0, exports.getAppPath)(handoff);
77
+ const destinationPublicPath = path_1.default.resolve(appPath, 'public');
58
78
  const workingPublicPath = (0, exports.getWorkingPublicPath)(handoff);
59
79
  if (workingPublicPath) {
60
- const destinationPublicPath = path_1.default.resolve(appPath, 'public');
61
80
  const sourceApiPath = path_1.default.resolve(workingPublicPath, 'api');
62
81
  const destinationApiPath = path_1.default.resolve(destinationPublicPath, 'api');
63
82
  yield fs_extra_1.default.copy(workingPublicPath, destinationPublicPath, {
@@ -69,5 +88,8 @@ const syncPublicFiles = (handoff) => __awaiter(void 0, void 0, void 0, function*
69
88
  });
70
89
  yield mirrorDirectory(sourceApiPath, destinationApiPath);
71
90
  }
91
+ // Stage the asset bundles even when the workspace has no `public/` dir; they come from the fetch
92
+ // output, not the workspace public tree.
93
+ yield stageAssetDownloadBundles(handoff, destinationPublicPath);
72
94
  });
73
95
  exports.syncPublicFiles = syncPublicFiles;
@@ -176,6 +176,31 @@ const materializeDocsReadModel = (handoff, outDir) => __awaiter(void 0, void 0,
176
176
  const detail = Object.assign(Object.assign({}, pattern), { build: { status: deriveBuildStatus(artifactRoot, path_1.default.join('pattern', `${pattern.id}.json`)) } });
177
177
  yield fs_extra_1.default.writeJson(path_1.default.join(docsRoot, 'patterns', `${pattern.id}.json`), detail);
178
178
  }
179
+ yield materializeAssetDownloads(handoff, docsRoot);
179
180
  logger_1.Logger.success(`Materialized docs read model (${components.length} component(s), ${patterns.length} pattern(s)) for static export.`);
180
181
  });
181
182
  exports.materializeDocsReadModel = materializeDocsReadModel;
183
+ /**
184
+ * Write the whole-collection asset bundles to the asset route
185
+ * (`api/docs/assets/{collection}/{collection}.zip`), the same URL the docs read API serves in
186
+ * registry mode, so the "Download" links work identically on a static host. The bundles come from the
187
+ * fetch output (`exported/<id>/{collection}.zip`); `next build` never re-creates them and the
188
+ * fetch-time copy into `public/` is wiped by the build's clean+restage, so we copy them here again.
189
+ * Projects with no logos/icons have no bundles, so those are skipped rather than treated as an error.
190
+ */
191
+ const materializeAssetDownloads = (handoff, docsRoot) => __awaiter(void 0, void 0, void 0, function* () {
192
+ const bundles = [
193
+ { collection: 'logos', src: handoff.getLogosZipFilePath() },
194
+ { collection: 'icons', src: handoff.getIconsZipFilePath() },
195
+ ];
196
+ let copied = 0;
197
+ for (const { collection, src } of bundles) {
198
+ if (fs_extra_1.default.existsSync(src) && fs_extra_1.default.statSync(src).isFile()) {
199
+ yield fs_extra_1.default.copy(src, path_1.default.join(docsRoot, 'assets', collection, `${collection}.zip`), { overwrite: true });
200
+ copied += 1;
201
+ }
202
+ }
203
+ if (copied > 0) {
204
+ logger_1.Logger.info(`Materialized ${copied} asset download bundle(s) for static export.`);
205
+ }
206
+ });
@@ -74,7 +74,7 @@ const writeStaticVercelOutput = (handoff, exportDir) => __awaiter(void 0, void 0
74
74
  });
75
75
  exports.writeStaticVercelOutput = writeStaticVercelOutput;
76
76
  /**
77
- * Node.js runtime identifier baked into the registry function's `.vc-config.json`. The `pg` adapter
77
+ * Node.js runtime identifier baked into the registry function's `.vc-config.json`. The `pg` driver
78
78
  * (and the Neon serverless driver in its Node fallback) need the Node.js runtime, not the edge
79
79
  * runtime — this is the required baseline. Vercel resolves the patch
80
80
  * version within this major; pinning the major keeps the artifact stable.
@@ -102,7 +102,7 @@ const MUTABLE_REGISTRY_PAGE_SRC_ROUTE = '/[...slug]';
102
102
  * data routes resolve the same way they do under the standalone server. `minimalMode:false` keeps
103
103
  * the function doing full in-process routing (the catch-all route forwards everything here).
104
104
  *
105
- * Runtime mode, adapter, and the DB env-var *name* are already baked into the resolved config the
105
+ * Runtime mode, driver, and the DB env-var *name* are already baked into the resolved config the
106
106
  * standalone build wrote to `.next/required-server-files.json`; the connection-string value is still
107
107
  * read from the named env var at request time, never baked.
108
108
  */
@@ -1,2 +1,2 @@
1
1
  export { ARTIFACT_METADATA_PATH, REACT_PREVIEW_FORMAT_VERSION, SHARED_MAIN_CSS_ARTIFACT_PATH, SHARED_MAIN_JS_ARTIFACT_PATH, SHARED_STYLES_CSS_ARTIFACT_PATH, type ArtifactBuildMetadata, type ArtifactBuildStatus, type ArtifactDescriptor, type ArtifactKind, type ArtifactOwnerKind, type ArtifactReference, type ArtifactReferenceKind, type ArtifactSizeDiagnostics, } from './types';
2
- export { ARTIFACTS_ROUTE_SEGMENT, buildArtifactUrl, normalizeBasePath } from './url';
2
+ export { ARTIFACTS_ROUTE_SEGMENT, ASSETS_ROUTE_SEGMENT, buildArtifactUrl, buildAssetDownloadUrl, buildAssetUrl, normalizeBasePath, } from './url';