handoff-app 2.0.0-1 → 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 (86) 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 +7 -9
  17. package/dist/app-builder/static-export.js +6 -6
  18. package/dist/app-builder/vercel-output.js +2 -2
  19. package/dist/artifacts/url.d.ts +7 -7
  20. package/dist/artifacts/url.js +7 -7
  21. package/dist/cli/create.js +48 -3
  22. package/dist/commands/checkout/index.d.ts +6 -6
  23. package/dist/commands/checkout/index.js +8 -12
  24. package/dist/commands/publish/index.d.ts +6 -5
  25. package/dist/commands/publish/index.js +8 -11
  26. package/dist/commands/utils.d.ts +6 -0
  27. package/dist/commands/utils.js +9 -1
  28. package/dist/index.d.ts +12 -8
  29. package/dist/index.js +26 -10
  30. package/dist/registry/asset-storage/resolve.d.ts +1 -1
  31. package/dist/registry/asset-storage/resolve.js +1 -1
  32. package/dist/registry/checkout/index.d.ts +10 -5
  33. package/dist/registry/checkout/index.js +62 -11
  34. package/dist/registry/client.d.ts +2 -1
  35. package/dist/registry/client.js +8 -0
  36. package/dist/registry/db/client.d.ts +9 -9
  37. package/dist/registry/db/client.js +7 -7
  38. package/dist/registry/db/{adapter.d.ts → driver.d.ts} +16 -14
  39. package/dist/registry/db/{adapter.js → driver.js} +17 -15
  40. package/dist/registry/db/index.d.ts +1 -1
  41. package/dist/registry/db/index.js +9 -9
  42. package/dist/registry/db/migrate.d.ts +1 -1
  43. package/dist/registry/db/migrate.js +5 -5
  44. package/dist/registry/publish/index.d.ts +8 -1
  45. package/dist/registry/publish/index.js +88 -2
  46. package/dist/registry/transfer.d.ts +13 -0
  47. package/dist/types/config.d.ts +7 -3
  48. package/drizzle.config.ts +1 -1
  49. package/package.json +1 -1
  50. package/src/app/components/Foundations/DisplayLogo.tsx +3 -3
  51. package/src/app/lib/docs-api/runtime-config.ts +10 -10
  52. package/src/app/lib/registry-api/transfer.ts +29 -0
  53. package/src/app/lib/registry-connection.ts +2 -2
  54. package/src/app/next.config.mjs +1 -1
  55. package/src/app/pages/api/registry/transfer/[kind]/index.ts +21 -0
  56. package/src/app/pages/foundations/icons/index.tsx +2 -2
  57. package/src/app/pages/foundations/icons/{[name] → library/[name]}/index.tsx +6 -6
  58. package/src/app-builder/build.ts +75 -55
  59. package/src/app-builder/client-config.ts +3 -3
  60. package/src/app-builder/paths.ts +7 -9
  61. package/src/app-builder/static-export.ts +6 -6
  62. package/src/app-builder/vercel-output.ts +2 -2
  63. package/src/artifacts/url.ts +7 -7
  64. package/src/cli/create.ts +58 -3
  65. package/src/commands/checkout/index.ts +10 -15
  66. package/src/commands/publish/index.ts +10 -14
  67. package/src/commands/utils.ts +12 -0
  68. package/src/index.ts +26 -12
  69. package/src/registry/asset-storage/resolve.ts +1 -1
  70. package/src/registry/checkout/index.ts +73 -11
  71. package/src/registry/client.ts +7 -1
  72. package/src/registry/db/client.ts +12 -12
  73. package/src/registry/db/{adapter.ts → driver.ts} +19 -17
  74. package/src/registry/db/index.ts +4 -4
  75. package/src/registry/db/migrate.ts +5 -5
  76. package/src/registry/publish/index.ts +100 -2
  77. package/src/registry/transfer.ts +14 -0
  78. package/src/types/config.ts +7 -3
  79. package/templates/default/README.md.tpl +40 -1
  80. package/templates/default/gitignore.tpl +18 -3
  81. package/templates/default/handoff.config.js.tpl +14 -0
  82. package/templates/default/handoff.config.ts.tpl +14 -0
  83. package/templates/default/handoff.config.with-examples.js.tpl +14 -0
  84. package/templates/default/handoff.config.with-examples.ts.tpl +14 -0
  85. package/templates/default/package.json.tpl +2 -1
  86. package/templates/default/vercel.json.tpl +4 -0
@@ -2,9 +2,9 @@
2
2
  /**
3
3
  * Bundled Drizzle client + migrator for the registry database.
4
4
  *
5
- * The package ships both built-in adapters (`pg` and `neon`) and the Drizzle client/migrator, so a
5
+ * The package ships both built-in drivers (`pg` and `neon`) and the Drizzle client/migrator, so a
6
6
  * consuming project needs no global Drizzle install or separate CLI. Driver modules are loaded
7
- * lazily (dynamic import) for the selected adapter only, so workspace dev/build never pulls the
7
+ * lazily (dynamic import) for the selected driver only, so workspace dev/build never pulls the
8
8
  * Postgres/Neon drivers into memory when the registry is not in use.
9
9
  */
10
10
  var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
@@ -43,13 +43,13 @@ Object.defineProperty(exports, "__esModule", { value: true });
43
43
  exports.createRegistryDbConnection = void 0;
44
44
  const schema = __importStar(require("./schema"));
45
45
  /**
46
- * Create a registry database connection for the resolved adapter. Both adapters target the
46
+ * Create a registry database connection for the resolved driver. Both drivers target the
47
47
  * Postgres dialect and share the same schema, so the returned connection is interchangeable for
48
48
  * migration and (later) the registry store.
49
49
  */
50
- const createRegistryDbConnection = (_a) => __awaiter(void 0, [_a], void 0, function* ({ adapter, connectionString, }) {
50
+ const createRegistryDbConnection = (_a) => __awaiter(void 0, [_a], void 0, function* ({ driver, connectionString, }) {
51
51
  var _b;
52
- if (adapter === 'neon') {
52
+ if (driver === 'neon') {
53
53
  const { Pool, neonConfig } = yield Promise.resolve().then(() => __importStar(require('@neondatabase/serverless')));
54
54
  // Neon's serverless driver needs a WebSocket constructor in Node; reuse the bundled `ws`.
55
55
  const ws = yield Promise.resolve().then(() => __importStar(require('ws')));
@@ -66,7 +66,7 @@ const createRegistryDbConnection = (_a) => __awaiter(void 0, [_a], void 0, funct
66
66
  }
67
67
  const db = drizzle(pool, { schema });
68
68
  return {
69
- adapter,
69
+ driver,
70
70
  db,
71
71
  migrate: (migrationsFolder) => migrate(db, { migrationsFolder }),
72
72
  close: () => pool.end(),
@@ -85,7 +85,7 @@ const createRegistryDbConnection = (_a) => __awaiter(void 0, [_a], void 0, funct
85
85
  }
86
86
  const db = drizzle(pool, { schema });
87
87
  return {
88
- adapter,
88
+ driver,
89
89
  db,
90
90
  migrate: (migrationsFolder) => migrate(db, { migrationsFolder }),
91
91
  close: () => pool.end(),
@@ -1,25 +1,27 @@
1
1
  /**
2
- * Registry database adapter resolution.
2
+ * Registry database driver resolution.
3
3
  *
4
- * `runtime.registry.database.adapter` selects one of two built-in adapters shipped with the
5
- * `handoff-app` package — `pg` (default) and `neon` — both targeting the Postgres dialect.
6
- * `build --target registry` and `db:migrate` resolve the **same** adapter so build and migration
7
- * never diverge. Connection credentials are referenced by environment-variable name only and
8
- * resolved to a value at runtime; the value is never stored in config.
4
+ * PostgreSQL is the supported database. `runtime.registry.database.driver` selects one of two
5
+ * built-in connection drivers shipped with the `handoff-app` package — `pg` (default) and `neon` —
6
+ * both targeting the same Postgres dialect over one package-owned schema and migration set. The
7
+ * driver picks *how* to connect, not *which* database engine to use. `build --target registry` and
8
+ * `db:migrate` resolve the **same** driver so build and migration never diverge. Connection
9
+ * credentials are referenced by environment-variable name only and resolved to a value at runtime;
10
+ * the value is never stored in config.
9
11
  */
10
12
  import type { Config } from '../../types/config';
11
- /** Built-in registry database adapter. Both ship with the package and target Postgres. */
12
- export type RegistryDatabaseAdapter = 'pg' | 'neon';
13
+ /** Built-in registry database connection driver. Both ship with the package and target Postgres. */
14
+ export type RegistryDatabaseDriver = 'pg' | 'neon';
13
15
  /** Default env-var name holding the database connection string. */
14
16
  export declare const DEFAULT_DATABASE_URL_ENV = "DATABASE_URL";
15
17
  /** Default env-var name holding the registry management API bearer token. */
16
18
  export declare const DEFAULT_REGISTRY_API_TOKEN_ENV = "HANDOFF_REGISTRY_API_TOKEN";
17
- /** Default registry database adapter when none is configured. */
18
- export declare const DEFAULT_REGISTRY_ADAPTER: RegistryDatabaseAdapter;
19
+ /** Default registry database driver when none is configured. */
20
+ export declare const DEFAULT_REGISTRY_DRIVER: RegistryDatabaseDriver;
19
21
  /** Fully resolved registry database connection inputs. */
20
22
  export interface ResolvedRegistryDatabase {
21
- /** Selected built-in adapter. */
22
- adapter: RegistryDatabaseAdapter;
23
+ /** Selected built-in driver. */
24
+ driver: RegistryDatabaseDriver;
23
25
  /** Name of the env var the connection string was read from. */
24
26
  databaseUrlEnv: string;
25
27
  /** Resolved connection string (never persisted in config). */
@@ -33,8 +35,8 @@ export declare const resolveDatabaseUrlEnv: (config: Config | null | undefined)
33
35
  * value is resolved from the environment at request time and never written to config.
34
36
  */
35
37
  export declare const resolveApiTokenEnv: (config: Config | null | undefined) => string;
36
- /** Resolve the configured registry adapter (defaults to `pg`). */
37
- export declare const resolveRegistryAdapter: (config: Config | null | undefined) => RegistryDatabaseAdapter;
38
+ /** Resolve the configured registry database driver (defaults to `pg`). */
39
+ export declare const resolveRegistryDriver: (config: Config | null | undefined) => RegistryDatabaseDriver;
38
40
  /**
39
41
  * Resolve the registry database connection from config + environment. Throws an actionable error
40
42
  * when the configured env var is unset so the CLI can surface a clear "missing database URL"
@@ -1,21 +1,23 @@
1
1
  "use strict";
2
2
  /**
3
- * Registry database adapter resolution.
3
+ * Registry database driver resolution.
4
4
  *
5
- * `runtime.registry.database.adapter` selects one of two built-in adapters shipped with the
6
- * `handoff-app` package — `pg` (default) and `neon` — both targeting the Postgres dialect.
7
- * `build --target registry` and `db:migrate` resolve the **same** adapter so build and migration
8
- * never diverge. Connection credentials are referenced by environment-variable name only and
9
- * resolved to a value at runtime; the value is never stored in config.
5
+ * PostgreSQL is the supported database. `runtime.registry.database.driver` selects one of two
6
+ * built-in connection drivers shipped with the `handoff-app` package — `pg` (default) and `neon` —
7
+ * both targeting the same Postgres dialect over one package-owned schema and migration set. The
8
+ * driver picks *how* to connect, not *which* database engine to use. `build --target registry` and
9
+ * `db:migrate` resolve the **same** driver so build and migration never diverge. Connection
10
+ * credentials are referenced by environment-variable name only and resolved to a value at runtime;
11
+ * the value is never stored in config.
10
12
  */
11
13
  Object.defineProperty(exports, "__esModule", { value: true });
12
- exports.resolveRegistryDatabase = exports.resolveRegistryAdapter = exports.resolveApiTokenEnv = exports.resolveDatabaseUrlEnv = exports.DEFAULT_REGISTRY_ADAPTER = exports.DEFAULT_REGISTRY_API_TOKEN_ENV = exports.DEFAULT_DATABASE_URL_ENV = void 0;
14
+ exports.resolveRegistryDatabase = exports.resolveRegistryDriver = exports.resolveApiTokenEnv = exports.resolveDatabaseUrlEnv = exports.DEFAULT_REGISTRY_DRIVER = exports.DEFAULT_REGISTRY_API_TOKEN_ENV = exports.DEFAULT_DATABASE_URL_ENV = void 0;
13
15
  /** Default env-var name holding the database connection string. */
14
16
  exports.DEFAULT_DATABASE_URL_ENV = 'DATABASE_URL';
15
17
  /** Default env-var name holding the registry management API bearer token. */
16
18
  exports.DEFAULT_REGISTRY_API_TOKEN_ENV = 'HANDOFF_REGISTRY_API_TOKEN';
17
- /** Default registry database adapter when none is configured. */
18
- exports.DEFAULT_REGISTRY_ADAPTER = 'pg';
19
+ /** Default registry database driver when none is configured. */
20
+ exports.DEFAULT_REGISTRY_DRIVER = 'pg';
19
21
  /** Resolve the env-var name holding the database URL (defaults to `DATABASE_URL`). */
20
22
  const resolveDatabaseUrlEnv = (config) => {
21
23
  var _a, _b, _c;
@@ -34,12 +36,12 @@ const resolveApiTokenEnv = (config) => {
34
36
  return configured || exports.DEFAULT_REGISTRY_API_TOKEN_ENV;
35
37
  };
36
38
  exports.resolveApiTokenEnv = resolveApiTokenEnv;
37
- /** Resolve the configured registry adapter (defaults to `pg`). */
38
- const resolveRegistryAdapter = (config) => {
39
+ /** Resolve the configured registry database driver (defaults to `pg`). */
40
+ const resolveRegistryDriver = (config) => {
39
41
  var _a, _b, _c, _d;
40
- return (_d = (_c = (_b = (_a = config === null || config === void 0 ? void 0 : config.runtime) === null || _a === void 0 ? void 0 : _a.registry) === null || _b === void 0 ? void 0 : _b.database) === null || _c === void 0 ? void 0 : _c.adapter) !== null && _d !== void 0 ? _d : exports.DEFAULT_REGISTRY_ADAPTER;
42
+ return (_d = (_c = (_b = (_a = config === null || config === void 0 ? void 0 : config.runtime) === null || _a === void 0 ? void 0 : _a.registry) === null || _b === void 0 ? void 0 : _b.database) === null || _c === void 0 ? void 0 : _c.driver) !== null && _d !== void 0 ? _d : exports.DEFAULT_REGISTRY_DRIVER;
41
43
  };
42
- exports.resolveRegistryAdapter = resolveRegistryAdapter;
44
+ exports.resolveRegistryDriver = resolveRegistryDriver;
43
45
  /**
44
46
  * Resolve the registry database connection from config + environment. Throws an actionable error
45
47
  * when the configured env var is unset so the CLI can surface a clear "missing database URL"
@@ -47,13 +49,13 @@ exports.resolveRegistryAdapter = resolveRegistryAdapter;
47
49
  */
48
50
  const resolveRegistryDatabase = (config) => {
49
51
  var _a;
50
- const adapter = (0, exports.resolveRegistryAdapter)(config);
52
+ const driver = (0, exports.resolveRegistryDriver)(config);
51
53
  const databaseUrlEnv = (0, exports.resolveDatabaseUrlEnv)(config);
52
54
  const connectionString = (_a = process.env[databaseUrlEnv]) === null || _a === void 0 ? void 0 : _a.trim();
53
55
  if (!connectionString) {
54
56
  throw new Error(`Registry database URL is not configured. Set the "${databaseUrlEnv}" environment variable to a ` +
55
57
  `PostgreSQL connection string (or change "runtime.registry.databaseUrlEnv" to point at the env var you use).`);
56
58
  }
57
- return { adapter, databaseUrlEnv, connectionString };
59
+ return { driver, databaseUrlEnv, connectionString };
58
60
  };
59
61
  exports.resolveRegistryDatabase = resolveRegistryDatabase;
@@ -1,5 +1,5 @@
1
1
  export { buildMetadata, componentFiles, components, docsArtifacts, patternFiles, patterns, registrySchema, type BuildMetadataEntityKind, type DocsArtifactEntityKind, type RegistryReviewMetadata, } from './schema';
2
2
  export type { ArtifactBuildStatus as RegistryBuildStatus } from '../../artifacts/types';
3
- export { DEFAULT_DATABASE_URL_ENV, DEFAULT_REGISTRY_ADAPTER, DEFAULT_REGISTRY_API_TOKEN_ENV, resolveApiTokenEnv, resolveDatabaseUrlEnv, resolveRegistryAdapter, resolveRegistryDatabase, type RegistryDatabaseAdapter, type ResolvedRegistryDatabase, } from './adapter';
3
+ export { DEFAULT_DATABASE_URL_ENV, DEFAULT_REGISTRY_DRIVER, DEFAULT_REGISTRY_API_TOKEN_ENV, resolveApiTokenEnv, resolveDatabaseUrlEnv, resolveRegistryDriver, resolveRegistryDatabase, type RegistryDatabaseDriver, type ResolvedRegistryDatabase, } from './driver';
4
4
  export { createRegistryDbConnection, type CreateRegistryDbConnectionParams, type RegistryDatabase, type RegistryDbConnection, } from './client';
5
5
  export { getMigrationsFolder, runRegistryMigrations } from './migrate';
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.runRegistryMigrations = exports.getMigrationsFolder = exports.createRegistryDbConnection = exports.resolveRegistryDatabase = exports.resolveRegistryAdapter = exports.resolveDatabaseUrlEnv = exports.resolveApiTokenEnv = exports.DEFAULT_REGISTRY_API_TOKEN_ENV = exports.DEFAULT_REGISTRY_ADAPTER = exports.DEFAULT_DATABASE_URL_ENV = exports.registrySchema = exports.patterns = exports.patternFiles = exports.docsArtifacts = exports.components = exports.componentFiles = exports.buildMetadata = void 0;
3
+ exports.runRegistryMigrations = exports.getMigrationsFolder = exports.createRegistryDbConnection = exports.resolveRegistryDatabase = exports.resolveRegistryDriver = exports.resolveDatabaseUrlEnv = exports.resolveApiTokenEnv = exports.DEFAULT_REGISTRY_API_TOKEN_ENV = exports.DEFAULT_REGISTRY_DRIVER = exports.DEFAULT_DATABASE_URL_ENV = exports.registrySchema = exports.patterns = exports.patternFiles = exports.docsArtifacts = exports.components = exports.componentFiles = exports.buildMetadata = void 0;
4
4
  var schema_1 = require("./schema");
5
5
  Object.defineProperty(exports, "buildMetadata", { enumerable: true, get: function () { return schema_1.buildMetadata; } });
6
6
  Object.defineProperty(exports, "componentFiles", { enumerable: true, get: function () { return schema_1.componentFiles; } });
@@ -9,14 +9,14 @@ Object.defineProperty(exports, "docsArtifacts", { enumerable: true, get: functio
9
9
  Object.defineProperty(exports, "patternFiles", { enumerable: true, get: function () { return schema_1.patternFiles; } });
10
10
  Object.defineProperty(exports, "patterns", { enumerable: true, get: function () { return schema_1.patterns; } });
11
11
  Object.defineProperty(exports, "registrySchema", { enumerable: true, get: function () { return schema_1.registrySchema; } });
12
- var adapter_1 = require("./adapter");
13
- Object.defineProperty(exports, "DEFAULT_DATABASE_URL_ENV", { enumerable: true, get: function () { return adapter_1.DEFAULT_DATABASE_URL_ENV; } });
14
- Object.defineProperty(exports, "DEFAULT_REGISTRY_ADAPTER", { enumerable: true, get: function () { return adapter_1.DEFAULT_REGISTRY_ADAPTER; } });
15
- Object.defineProperty(exports, "DEFAULT_REGISTRY_API_TOKEN_ENV", { enumerable: true, get: function () { return adapter_1.DEFAULT_REGISTRY_API_TOKEN_ENV; } });
16
- Object.defineProperty(exports, "resolveApiTokenEnv", { enumerable: true, get: function () { return adapter_1.resolveApiTokenEnv; } });
17
- Object.defineProperty(exports, "resolveDatabaseUrlEnv", { enumerable: true, get: function () { return adapter_1.resolveDatabaseUrlEnv; } });
18
- Object.defineProperty(exports, "resolveRegistryAdapter", { enumerable: true, get: function () { return adapter_1.resolveRegistryAdapter; } });
19
- Object.defineProperty(exports, "resolveRegistryDatabase", { enumerable: true, get: function () { return adapter_1.resolveRegistryDatabase; } });
12
+ var driver_1 = require("./driver");
13
+ Object.defineProperty(exports, "DEFAULT_DATABASE_URL_ENV", { enumerable: true, get: function () { return driver_1.DEFAULT_DATABASE_URL_ENV; } });
14
+ Object.defineProperty(exports, "DEFAULT_REGISTRY_DRIVER", { enumerable: true, get: function () { return driver_1.DEFAULT_REGISTRY_DRIVER; } });
15
+ Object.defineProperty(exports, "DEFAULT_REGISTRY_API_TOKEN_ENV", { enumerable: true, get: function () { return driver_1.DEFAULT_REGISTRY_API_TOKEN_ENV; } });
16
+ Object.defineProperty(exports, "resolveApiTokenEnv", { enumerable: true, get: function () { return driver_1.resolveApiTokenEnv; } });
17
+ Object.defineProperty(exports, "resolveDatabaseUrlEnv", { enumerable: true, get: function () { return driver_1.resolveDatabaseUrlEnv; } });
18
+ Object.defineProperty(exports, "resolveRegistryDriver", { enumerable: true, get: function () { return driver_1.resolveRegistryDriver; } });
19
+ Object.defineProperty(exports, "resolveRegistryDatabase", { enumerable: true, get: function () { return driver_1.resolveRegistryDatabase; } });
20
20
  var client_1 = require("./client");
21
21
  Object.defineProperty(exports, "createRegistryDbConnection", { enumerable: true, get: function () { return client_1.createRegistryDbConnection; } });
22
22
  var migrate_1 = require("./migrate");
@@ -2,7 +2,7 @@
2
2
  * Registry migration runner.
3
3
  *
4
4
  * `handoff-app db:migrate` reads the consuming project's config + DB env vars, resolves the same
5
- * `runtime.registry.database.adapter` used at runtime/build, and applies the package-owned
5
+ * `runtime.registry.database.driver` used at runtime/build, and applies the package-owned
6
6
  * migration set with the bundled Drizzle migrator. Migrations are resolved relative to the
7
7
  * installed package (not the consuming project), so the schema is versioned with the tool. This
8
8
  * runs independently of `build`.
@@ -3,7 +3,7 @@
3
3
  * Registry migration runner.
4
4
  *
5
5
  * `handoff-app db:migrate` reads the consuming project's config + DB env vars, resolves the same
6
- * `runtime.registry.database.adapter` used at runtime/build, and applies the package-owned
6
+ * `runtime.registry.database.driver` used at runtime/build, and applies the package-owned
7
7
  * migration set with the bundled Drizzle migrator. Migrations are resolved relative to the
8
8
  * installed package (not the consuming project), so the schema is versioned with the tool. This
9
9
  * runs independently of `build`.
@@ -25,7 +25,7 @@ exports.runRegistryMigrations = exports.getMigrationsFolder = void 0;
25
25
  const fs_extra_1 = __importDefault(require("fs-extra"));
26
26
  const path_1 = __importDefault(require("path"));
27
27
  const logger_1 = require("../../utils/logger");
28
- const adapter_1 = require("./adapter");
28
+ const driver_1 = require("./driver");
29
29
  const client_1 = require("./client");
30
30
  /**
31
31
  * Absolute path to the package-owned migrations folder. Resolved relative to the installed
@@ -44,15 +44,15 @@ const runRegistryMigrations = (handoff) => __awaiter(void 0, void 0, void 0, fun
44
44
  if (!handoff.config) {
45
45
  throw new Error('Handoff is not initialized; cannot resolve registry database configuration.');
46
46
  }
47
- const { adapter, databaseUrlEnv, connectionString } = (0, adapter_1.resolveRegistryDatabase)(handoff.config);
47
+ const { driver, databaseUrlEnv, connectionString } = (0, driver_1.resolveRegistryDatabase)(handoff.config);
48
48
  const migrationsFolder = (0, exports.getMigrationsFolder)(handoff);
49
49
  const journalPath = path_1.default.join(migrationsFolder, 'meta', '_journal.json');
50
50
  if (!fs_extra_1.default.existsSync(journalPath)) {
51
51
  throw new Error(`Packaged registry migrations were not found at "${migrationsFolder}". This indicates a broken ` +
52
52
  `handoff-app installation — reinstall the package to restore the bundled migration set.`);
53
53
  }
54
- logger_1.Logger.info(`Running registry migrations (adapter: "${adapter}", database url from "${databaseUrlEnv}").`);
55
- const connection = yield (0, client_1.createRegistryDbConnection)({ adapter, connectionString });
54
+ logger_1.Logger.info(`Running registry migrations (driver: "${driver}", database url from "${databaseUrlEnv}").`);
55
+ const connection = yield (0, client_1.createRegistryDbConnection)({ driver, connectionString });
56
56
  try {
57
57
  yield connection.migrate(migrationsFolder);
58
58
  logger_1.Logger.success('Registry migrations applied successfully.');
@@ -1,7 +1,7 @@
1
1
  /**
2
2
  * Connected-workspace publish orchestration.
3
3
  *
4
- * `publish <component|pattern|page> <id>` prepares one entity and uploads its record, source files,
4
+ * `publish <components|patterns|pages> <id>` prepares one entity and uploads its record, source files,
5
5
  * applicable rendered artifacts, and build metadata through the shared registry client.
6
6
  *
7
7
  * The build is targeted: a component builds the global artifacts + that component; a pattern builds
@@ -25,3 +25,10 @@ export declare const resolveConnectionOrThrow: (handoff: Handoff) => import("../
25
25
  * receive a targeted build, while pages are packaged directly from their source.
26
26
  */
27
27
  export declare const publishEntity: (handoff: Handoff, kind: TransferEntityKind, id: string) => Promise<void>;
28
+ /**
29
+ * Publish every component, pattern, or page declared in this connected workspace. Builds the kind
30
+ * once, assembles each entity's package, skips entities whose content hash already matches the
31
+ * registry (unless `--force`), and reports published/unchanged/failed counts. A per-entity failure is
32
+ * collected and never aborts the rest; the run throws at the end if any entity failed.
33
+ */
34
+ export declare const publishEntities: (handoff: Handoff, kind: TransferEntityKind) => Promise<void>;
@@ -2,7 +2,7 @@
2
2
  /**
3
3
  * Connected-workspace publish orchestration.
4
4
  *
5
- * `publish <component|pattern|page> <id>` prepares one entity and uploads its record, source files,
5
+ * `publish <components|patterns|pages> <id>` prepares one entity and uploads its record, source files,
6
6
  * applicable rendered artifacts, and build metadata through the shared registry client.
7
7
  *
8
8
  * The build is targeted: a component builds the global artifacts + that component; a pattern builds
@@ -22,7 +22,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
22
22
  return (mod && mod.__esModule) ? mod : { "default": mod };
23
23
  };
24
24
  Object.defineProperty(exports, "__esModule", { value: true });
25
- exports.publishEntity = exports.resolveConnectionOrThrow = exports.PublishError = void 0;
25
+ exports.publishEntities = exports.publishEntity = exports.resolveConnectionOrThrow = exports.PublishError = void 0;
26
26
  const css_1 = require("../../transformers/preview/component/css");
27
27
  const javascript_1 = require("../../transformers/preview/component/javascript");
28
28
  const builder_1 = __importDefault(require("../../transformers/preview/component/builder"));
@@ -124,3 +124,89 @@ const publishEntity = (handoff, kind, id) => __awaiter(void 0, void 0, void 0, f
124
124
  logger_1.Logger.success(`Published ${kind} "${id}" to the registry.`);
125
125
  });
126
126
  exports.publishEntity = publishEntity;
127
+ /** The published-content hash a bulk publish compares for skip-unchanged (artifact for rendered kinds, source for pages). */
128
+ const entityHash = (value) => { var _a; return (_a = value.artifactHash) !== null && _a !== void 0 ? _a : value.sourceHash; };
129
+ /**
130
+ * Run one build covering every entity of the kind, so a bulk publish builds once instead of per id.
131
+ * Pages carry no rendered artifacts. Components need the global artifacts + all components; patterns
132
+ * additionally need all patterns composed (each references pre-built component HTML).
133
+ */
134
+ const runBulkBuild = (handoff, kind) => __awaiter(void 0, void 0, void 0, function* () {
135
+ if (kind === 'page') {
136
+ return;
137
+ }
138
+ yield (0, javascript_1.buildMainJS)(handoff);
139
+ yield (0, css_1.buildMainCss)(handoff);
140
+ yield (0, builder_1.default)(handoff);
141
+ if (kind === 'pattern') {
142
+ yield (0, builder_2.processPatterns)(handoff);
143
+ }
144
+ });
145
+ /** List every declared entity id of the kind from the workspace store. */
146
+ const listWorkspaceEntityIds = (handoff, kind) => __awaiter(void 0, void 0, void 0, function* () {
147
+ const store = kind === 'component' ? handoff.store.components : kind === 'pattern' ? handoff.store.patterns : handoff.store.pages;
148
+ return (yield store.list()).map((entity) => entity.id);
149
+ });
150
+ /**
151
+ * Publish every component, pattern, or page declared in this connected workspace. Builds the kind
152
+ * once, assembles each entity's package, skips entities whose content hash already matches the
153
+ * registry (unless `--force`), and reports published/unchanged/failed counts. A per-entity failure is
154
+ * collected and never aborts the rest; the run throws at the end if any entity failed.
155
+ */
156
+ const publishEntities = (handoff, kind) => __awaiter(void 0, void 0, void 0, function* () {
157
+ const connection = (0, exports.resolveConnectionOrThrow)(handoff);
158
+ logger_1.Logger.info(`Building ${kind}s for publish…`);
159
+ yield runBulkBuild(handoff, kind);
160
+ const ids = yield listWorkspaceEntityIds(handoff, kind);
161
+ if (ids.length === 0) {
162
+ logger_1.Logger.success(`No ${kind}s are declared in this workspace; nothing to publish.`);
163
+ return;
164
+ }
165
+ const client = (0, client_1.createRegistryClient)({ baseUrl: connection.url, accessToken: connection.accessToken });
166
+ let remote = [];
167
+ if (!handoff.force) {
168
+ try {
169
+ remote = yield client.listEntities(kind);
170
+ }
171
+ catch (_a) {
172
+ // If the summary listing fails we simply publish everything (the server still overwrites in place).
173
+ logger_1.Logger.info(`Could not read current registry ${kind}s; publishing all.`);
174
+ }
175
+ }
176
+ const remoteHashById = new Map(remote.map((entry) => [entry.id, entityHash(entry)]));
177
+ let published = 0;
178
+ let unchanged = 0;
179
+ const failed = [];
180
+ for (const id of ids) {
181
+ try {
182
+ const pkg = yield (0, package_1.buildPublishPackage)(handoff, kind, id);
183
+ (0, package_1.assertRequiredArtifactsPresent)(pkg);
184
+ const remoteHash = remoteHashById.get(id);
185
+ const localHash = entityHash(pkg.build);
186
+ if (!handoff.force && remoteHash && localHash && remoteHash === localHash) {
187
+ unchanged += 1;
188
+ logger_1.Logger.info(`Unchanged: ${id}`);
189
+ continue;
190
+ }
191
+ yield client.publish(kind, id, pkg);
192
+ published += 1;
193
+ logger_1.Logger.info(`Published: ${id} (${pkg.artifacts.length} artifact(s), ${pkg.files.length} source file(s))`);
194
+ }
195
+ catch (error) {
196
+ const message = error instanceof client_1.RegistryClientError
197
+ ? (0, errors_1.describeUploadFailure)(error, connection.url, 'package')
198
+ : error instanceof Error
199
+ ? error.message
200
+ : String(error);
201
+ failed.push({ id, message });
202
+ }
203
+ }
204
+ logger_1.Logger.success(`${kind[0].toUpperCase()}${kind.slice(1)}s publish complete — ${published} published, ${unchanged} unchanged${failed.length ? `, ${failed.length} failed` : ''}.`);
205
+ if (failed.length > 0) {
206
+ for (const failure of failed) {
207
+ logger_1.Logger.error(` - ${failure.id}: ${failure.message}`);
208
+ }
209
+ throw new PublishError(`${failed.length} ${kind}(s) failed to publish.`);
210
+ }
211
+ });
212
+ exports.publishEntities = publishEntities;
@@ -73,3 +73,16 @@ export interface CheckoutPayload {
73
73
  item: Record<string, unknown>;
74
74
  files: TransferFile[];
75
75
  }
76
+ /**
77
+ * A published entity's summary from `GET /api/registry/transfer/{component|pattern|page}`. Carries
78
+ * the build hashes so a connected workspace can skip unchanged entities on a bulk publish and
79
+ * enumerate published ids for a bulk checkout. Components/patterns key on `artifactHash`; pages key
80
+ * on `sourceHash`.
81
+ */
82
+ export interface EntitySummary {
83
+ id: string;
84
+ kind: TransferEntityKind;
85
+ status?: string;
86
+ artifactHash?: string;
87
+ sourceHash?: string;
88
+ }
@@ -139,8 +139,12 @@ export interface HandoffRuntimeConfig {
139
139
  /** Name of the env var holding the registry API token. @default "HANDOFF_REGISTRY_API_TOKEN" */
140
140
  apiTokenEnv?: string;
141
141
  database?: {
142
- /** @default "pg" */
143
- adapter?: 'pg' | 'neon';
142
+ /**
143
+ * PostgreSQL is the supported database. `driver` selects the built-in connection driver — how
144
+ * to connect, not which engine — over the same package-owned Postgres schema and migrations.
145
+ * @default "pg"
146
+ */
147
+ driver?: 'pg' | 'neon';
144
148
  };
145
149
  /**
146
150
  * Where published asset blobs (icons/logos/fonts/sprites/archives) are stored. Defaults to the
@@ -214,7 +218,7 @@ export interface Config {
214
218
  */
215
219
  entries?: ConfigEntries;
216
220
  /**
217
- * Override URLs for the asset zip download links. When unset, each link defaults to the canonical,
221
+ * Override URLs for the asset zip download links. When unset, each link defaults to the
218
222
  * basePath-aware asset route `{basePath}/api/docs/assets/{collection}/{collection}.zip`, served by
219
223
  * the docs read API in every runtime mode (from the DB in registry mode, from statically
220
224
  * materialized files in a static export).
package/drizzle.config.ts CHANGED
@@ -5,7 +5,7 @@ import { defineConfig } from 'drizzle-kit';
5
5
  *
6
6
  * Migrations are generated into `./drizzle` and shipped with the `handoff-app` package; the
7
7
  * bundled runner (`handoff-app db:migrate`) applies them at install/deploy time. Both built-in
8
- * adapters (`pg`, `neon`) target the Postgres dialect, so a single migration set serves both.
8
+ * drivers (`pg`, `neon`) target the Postgres dialect, so a single migration set serves both.
9
9
  *
10
10
  * Generate after schema changes with: `npx drizzle-kit generate`
11
11
  */
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "handoff-app",
3
- "version": "2.0.0-1",
3
+ "version": "2.0.0-2",
4
4
  "description": "Automated documentation toolchain for building client side documentation from figma",
5
5
  "author ": {
6
6
  "name": "Convertiv",
@@ -2,9 +2,9 @@ import React from 'react';
2
2
  import type { DisplayableAsset } from '../util/useCollectionAssets';
3
3
 
4
4
  export const DisplayLogo: React.FC<{ logo: DisplayableAsset; content?: string; correct?: boolean }> = ({ logo, content }) => {
5
- // Workspace/static ships the SVG body inline in tokens.json (`data`) — render it directly so the
6
- // logo has no dependency on the fetch-only `/api/assets/logos/*` files (absent on a static host).
7
- // Registry has no inline body; it falls back to the individual asset content URL (`src`).
5
+ // Workspace/static ships the SVG body inline in tokens.json (`data`), so render it directly and
6
+ // avoid depending on the fetch-only `/api/assets/logos/*` files that a static host won't have.
7
+ // Registry has no inline body, so it falls back to the individual asset content URL (`src`).
8
8
  const src = logo.src ?? `${process.env.HANDOFF_APP_BASE_PATH ?? ''}/api/assets/logos/${logo.path}`;
9
9
  return (
10
10
  <div className="flex flex-col gap-5">
@@ -3,10 +3,10 @@ import path from 'path';
3
3
  import type { RuntimeMode } from '@handoff/types/config';
4
4
  import {
5
5
  DEFAULT_DATABASE_URL_ENV,
6
- DEFAULT_REGISTRY_ADAPTER,
6
+ DEFAULT_REGISTRY_DRIVER,
7
7
  DEFAULT_REGISTRY_API_TOKEN_ENV,
8
- type RegistryDatabaseAdapter,
9
- } from '@handoff/registry/db/adapter';
8
+ type RegistryDatabaseDriver,
9
+ } from '@handoff/registry/db/driver';
10
10
  import { DEFAULT_ASSET_STORAGE_ADAPTER, type AssetStorageSettings } from '@handoff/registry/asset-storage/resolve';
11
11
 
12
12
  /**
@@ -15,7 +15,7 @@ import { DEFAULT_ASSET_STORAGE_ADAPTER, type AssetStorageSettings } from '@hando
15
15
  * The docs read API is mode-aware: workspace mode resolves artifacts from generated filesystem
16
16
  * files, registry mode resolves them from the database. The browser-facing `client.config.json`
17
17
  * carries only the resolved {@link RuntimeMode} (no connection details), so the server needs its
18
- * own non-secret source for the registry connection *inputs* — the selected adapter and the *name*
18
+ * own non-secret source for the registry connection *inputs* — the selected driver and the *name*
19
19
  * of the env var holding the database URL. The build persists these to `runtime.server.json`
20
20
  * alongside `client.config.json`; this module reads them back, never crossing secrets to the
21
21
  * browser (the connection-string value is resolved from the env var at request time, not here).
@@ -25,8 +25,8 @@ import { DEFAULT_ASSET_STORAGE_ADAPTER, type AssetStorageSettings } from '@hando
25
25
 
26
26
  /** Non-secret registry connection inputs resolved on the server. */
27
27
  export interface ServerRegistryRuntimeConfig {
28
- /** Selected built-in database adapter. */
29
- adapter: RegistryDatabaseAdapter;
28
+ /** Selected built-in database driver. */
29
+ driver: RegistryDatabaseDriver;
30
30
  /** Name of the env var holding the database connection string (value resolved at request time). */
31
31
  databaseUrlEnv: string;
32
32
  /**
@@ -49,11 +49,11 @@ export interface ServerRuntimeConfig {
49
49
 
50
50
  let cached: ServerRuntimeConfig | null = null;
51
51
 
52
- /** Safe defaults: workspace mode with the default Postgres adapter + `DATABASE_URL` env var. */
52
+ /** Safe defaults: workspace mode with the default Postgres driver + `DATABASE_URL` env var. */
53
53
  const defaults = (): ServerRuntimeConfig => ({
54
54
  mode: 'workspace',
55
55
  registry: {
56
- adapter: DEFAULT_REGISTRY_ADAPTER,
56
+ driver: DEFAULT_REGISTRY_DRIVER,
57
57
  databaseUrlEnv: DEFAULT_DATABASE_URL_ENV,
58
58
  apiTokenEnv: DEFAULT_REGISTRY_API_TOKEN_ENV,
59
59
  },
@@ -112,7 +112,7 @@ const fromEnv = (): ServerRuntimeConfig | null => {
112
112
  return {
113
113
  mode: mode === 'registry' ? 'registry' : 'workspace',
114
114
  registry: {
115
- adapter: process.env.HANDOFF_REGISTRY_ADAPTER?.trim() === 'neon' ? 'neon' : 'pg',
115
+ driver: process.env.HANDOFF_REGISTRY_DRIVER?.trim() === 'neon' ? 'neon' : 'pg',
116
116
  databaseUrlEnv,
117
117
  apiTokenEnv,
118
118
  },
@@ -156,7 +156,7 @@ export const getServerRuntimeConfig = (): ServerRuntimeConfig => {
156
156
  cached = {
157
157
  mode: parsed?.mode === 'registry' ? 'registry' : 'workspace',
158
158
  registry: {
159
- adapter: parsed?.registry?.adapter === 'neon' ? 'neon' : 'pg',
159
+ driver: parsed?.registry?.driver === 'neon' ? 'neon' : 'pg',
160
160
  databaseUrlEnv,
161
161
  apiTokenEnv,
162
162
  },
@@ -432,6 +432,35 @@ const upsertBuildMetadata = async (db: RegistryDatabase, kind: TransferEntityKin
432
432
  .onConflictDoUpdate({ target: [buildMetadata.entityKind, buildMetadata.entityId], set: values as any });
433
433
  };
434
434
 
435
+ /**
436
+ * Handles `GET /api/registry/transfer/{component|pattern|page}`. Lists summaries for one kind of
437
+ * entity: each id plus its build hashes and status. A connected workspace uses these to skip
438
+ * unchanged entities on a bulk publish and to enumerate published ids for a bulk checkout. The read
439
+ * is unauthenticated; only the registry-runtime and method guards apply.
440
+ */
441
+ export const handleEntitySummaryRoute = (req: NextApiRequest, res: NextApiResponse, kind: TransferEntityKind): Promise<void> =>
442
+ handleRegistryRoute(req, res, ['GET'], async ({ db }) => {
443
+ const table = ENTITY[kind].table;
444
+ const rows = await db
445
+ .select({
446
+ id: table.id,
447
+ status: buildMetadata.status,
448
+ artifactHash: buildMetadata.artifactHash,
449
+ sourceHash: buildMetadata.sourceHash,
450
+ })
451
+ .from(table)
452
+ .leftJoin(buildMetadata, and(eq(buildMetadata.entityKind, kind), eq(buildMetadata.entityId, table.id)));
453
+
454
+ const entities = rows.map((row) => ({
455
+ id: row.id,
456
+ kind,
457
+ status: row.status ?? undefined,
458
+ artifactHash: row.artifactHash ?? undefined,
459
+ sourceHash: row.sourceHash ?? undefined,
460
+ }));
461
+ sendRegistryData(res, 200, { entities }, buildMeta());
462
+ });
463
+
435
464
  /**
436
465
  * Handle `GET /api/registry/transfer/{component|pattern}/:id` — checkout read. Returns the
437
466
  * normalized record plus its registry-safe source files so a connected workspace can reconstruct
@@ -7,7 +7,7 @@ import { getServerRuntimeConfig } from './docs-api/runtime-config';
7
7
  * Both the registry-mode docs read API and the registry management API resolve their database
8
8
  * through this single helper so one connection pool backs the whole registry runtime rather than
9
9
  * one pool per consumer. The connection string is read from the configured env var *name* at
10
- * request time — never persisted in config — and the selected adapter comes from the server runtime
10
+ * request time — never persisted in config — and the selected driver comes from the server runtime
11
11
  * config baked at build time.
12
12
  *
13
13
  * Server-only: imported exclusively by registry-mode API route helpers, which are themselves only
@@ -48,7 +48,7 @@ export const getRegistryConnection = (): Promise<RegistryDbConnection> => {
48
48
  )
49
49
  );
50
50
  }
51
- connectionPromise = createRegistryDbConnection({ adapter: registry.adapter, connectionString }).catch((error) => {
51
+ connectionPromise = createRegistryDbConnection({ driver: registry.driver, connectionString }).catch((error) => {
52
52
  connectionPromise = null;
53
53
  // TODO: Route server errors through the shared logger.
54
54
  console.error('Registry database connection failed.', error);
@@ -95,7 +95,7 @@ const nextConfig = {
95
95
  // config-only — these are derived from `runtime.*`, not inferred from env at runtime. The DB
96
96
  // connection string itself is read from the named env var at request time (deployment-supplied).
97
97
  HANDOFF_RUNTIME_MODE: '%HANDOFF_RUNTIME_MODE%',
98
- HANDOFF_REGISTRY_ADAPTER: '%HANDOFF_REGISTRY_ADAPTER%',
98
+ HANDOFF_REGISTRY_DRIVER: '%HANDOFF_REGISTRY_DRIVER%',
99
99
  HANDOFF_REGISTRY_DATABASE_URL_ENV: '%HANDOFF_REGISTRY_DATABASE_URL_ENV%',
100
100
  HANDOFF_REGISTRY_API_TOKEN_ENV: '%HANDOFF_REGISTRY_API_TOKEN_ENV%',
101
101
  // Asset storage selection (provider + non-secret options + env-var names). Secret values (Blob
@@ -0,0 +1,21 @@
1
+ import type { NextApiRequest, NextApiResponse } from 'next';
2
+ import { singleQueryValue } from '@/lib/api/query';
3
+ import { handleRegistryRoute, sendRegistryError } from '@/lib/registry-api';
4
+ import { handleEntitySummaryRoute } from '@/lib/registry-api/transfer';
5
+
6
+ /**
7
+ * `GET /api/registry/transfer/{component|pattern|page}`. Lists the registry's published entity
8
+ * summaries for one kind (id, build hashes, status) so a connected workspace can skip unchanged
9
+ * entities on a bulk publish and enumerate published ids for a bulk checkout. Registry-runtime only,
10
+ * enforced by the guard stack. The `tokens` and `assets` summaries have their own static routes,
11
+ * which Next resolves before this dynamic segment.
12
+ */
13
+ export default function handler(req: NextApiRequest, res: NextApiResponse): Promise<void> {
14
+ const kind = singleQueryValue(req.query.kind);
15
+ if (kind !== 'component' && kind !== 'pattern' && kind !== 'page') {
16
+ return handleRegistryRoute(req, res, ['GET'], async () => {
17
+ sendRegistryError(res, 'not_found', `Unknown transfer entity kind "${kind ?? ''}".`);
18
+ });
19
+ }
20
+ return handleEntitySummaryRoute(req, res, kind);
21
+ }