handoff-app 2.0.0-1 → 2.0.0-3
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.
- package/README.md +170 -91
- package/config/docs/foundations/colors/guidelines.md +9 -0
- package/config/docs/foundations/effects/guidelines.md +9 -0
- package/config/docs/foundations/effects/resources.md +9 -0
- package/config/docs/foundations/grid.md +10 -0
- package/config/docs/foundations/icons/guidelines.md +9 -0
- package/config/docs/foundations/images/guidelines.md +9 -0
- package/config/docs/foundations/images/resources.md +9 -0
- package/config/docs/foundations/images.md +10 -0
- package/config/docs/foundations/logo/resources.md +9 -0
- package/config/docs/foundations/typography/guidelines.md +9 -0
- package/config/docs/foundations/typography/resources.md +9 -0
- package/config/docs/foundations.md +5 -3
- package/dist/app-builder/build.js +109 -49
- package/dist/app-builder/client-config.js +13 -12
- package/dist/app-builder/paths.js +7 -9
- package/dist/app-builder/static-export.js +6 -6
- package/dist/app-builder/vercel-output.js +2 -2
- package/dist/artifacts/url.d.ts +7 -7
- package/dist/artifacts/url.js +7 -7
- package/dist/cli/auth/browser.d.ts +2 -0
- package/dist/cli/auth/browser.js +33 -0
- package/dist/cli/auth/device.d.ts +11 -0
- package/dist/cli/auth/device.js +155 -0
- package/dist/cli/auth/store.d.ts +29 -0
- package/dist/cli/auth/store.js +146 -0
- package/dist/cli/create.js +48 -3
- package/dist/cli/scaffold.js +20 -154
- package/dist/commands/checkout/index.d.ts +6 -6
- package/dist/commands/checkout/index.js +8 -12
- package/dist/commands/index.js +4 -0
- package/dist/commands/login/index.d.ts +8 -0
- package/dist/commands/login/index.js +53 -0
- package/dist/commands/logout/index.d.ts +7 -0
- package/dist/commands/logout/index.js +54 -0
- package/dist/commands/publish/index.d.ts +6 -5
- package/dist/commands/publish/index.js +8 -11
- package/dist/commands/utils.d.ts +6 -0
- package/dist/commands/utils.js +9 -1
- package/dist/config/entries.d.ts +47 -0
- package/dist/config/entries.js +162 -0
- package/dist/config/index.d.ts +2 -0
- package/dist/config/index.js +5 -1
- package/dist/config/runtime.d.ts +9 -9
- package/dist/config/runtime.js +15 -15
- package/dist/index.d.ts +12 -8
- package/dist/index.js +26 -10
- package/dist/registry/asset-storage/resolve.d.ts +1 -1
- package/dist/registry/asset-storage/resolve.js +1 -1
- package/dist/registry/auth/access-tokens.d.ts +46 -0
- package/dist/registry/auth/access-tokens.js +165 -0
- package/dist/registry/auth/action-tokens.d.ts +66 -0
- package/dist/registry/auth/action-tokens.js +178 -0
- package/dist/registry/auth/crypto.d.ts +4 -0
- package/dist/registry/auth/crypto.js +16 -0
- package/dist/registry/auth/database.d.ts +7 -0
- package/dist/registry/auth/database.js +22 -0
- package/dist/registry/auth/device-authorizations.d.ts +51 -0
- package/dist/registry/auth/device-authorizations.js +170 -0
- package/dist/registry/auth/gravatar.d.ts +1 -0
- package/dist/registry/auth/gravatar.js +8 -0
- package/dist/registry/auth/index.d.ts +11 -0
- package/dist/registry/auth/index.js +72 -0
- package/dist/registry/auth/installation.d.ts +42 -0
- package/dist/registry/auth/installation.js +105 -0
- package/dist/registry/auth/password.d.ts +12 -0
- package/dist/registry/auth/password.js +58 -0
- package/dist/registry/auth/rate-limits.d.ts +22 -0
- package/dist/registry/auth/rate-limits.js +75 -0
- package/dist/registry/auth/types.d.ts +26 -0
- package/dist/registry/auth/types.js +9 -0
- package/dist/registry/auth/user-record.d.ts +13 -0
- package/dist/registry/auth/user-record.js +16 -0
- package/dist/registry/auth/users.d.ts +31 -0
- package/dist/registry/auth/users.js +135 -0
- package/dist/registry/auth/validation.d.ts +3 -0
- package/dist/registry/auth/validation.js +28 -0
- package/dist/registry/checkout/assets.js +4 -2
- package/dist/registry/checkout/index.d.ts +11 -6
- package/dist/registry/checkout/index.js +159 -30
- package/dist/registry/checkout/tokens.js +4 -2
- package/dist/registry/client.d.ts +2 -1
- package/dist/registry/client.js +8 -0
- package/dist/registry/connection.d.ts +9 -0
- package/dist/registry/connection.js +29 -1
- package/dist/registry/db/client.d.ts +9 -9
- package/dist/registry/db/client.js +7 -7
- package/dist/registry/db/driver.d.ts +46 -0
- package/dist/registry/db/{adapter.js → driver.js} +22 -19
- package/dist/registry/db/index.d.ts +2 -2
- package/dist/registry/db/index.js +15 -9
- package/dist/registry/db/migrate.d.ts +1 -1
- package/dist/registry/db/migrate.js +5 -5
- package/dist/registry/db/schema.d.ts +2521 -750
- package/dist/registry/db/schema.js +126 -1
- package/dist/registry/publish/assets.js +1 -1
- package/dist/registry/publish/errors.js +3 -3
- package/dist/registry/publish/index.d.ts +9 -2
- package/dist/registry/publish/index.js +96 -10
- package/dist/registry/publish/tokens.js +1 -1
- package/dist/registry/transfer.d.ts +13 -0
- package/dist/types/config.d.ts +11 -4
- package/docs/cli.md +4 -0
- package/drizzle/0005_add_registry_auth.sql +95 -0
- package/drizzle/meta/0005_snapshot.json +1926 -0
- package/drizzle/meta/_journal.json +8 -1
- package/drizzle.config.ts +1 -1
- package/package.json +5 -4
- package/src/app/components/Auth/AccountLayout.tsx +87 -0
- package/src/app/components/Auth/AuthControls.tsx +99 -0
- package/src/app/components/Auth/AuthShell.tsx +73 -0
- package/src/app/components/Auth/RegistryInstallGate.tsx +23 -0
- package/src/app/components/Auth/RegistrySessionProvider.tsx +15 -0
- package/src/app/components/Auth/api.ts +20 -0
- package/src/app/components/Foundations/DisplayLogo.tsx +3 -3
- package/src/app/components/Layout/Header.tsx +2 -0
- package/src/app/components/Navigation/MobileNav.tsx +2 -0
- package/src/app/lib/auth/api.ts +113 -0
- package/src/app/lib/auth/config.ts +175 -0
- package/src/app/lib/auth/email.ts +34 -0
- package/src/app/lib/docs-api/runtime-config.ts +10 -24
- package/src/app/lib/registry-api/auth.ts +29 -46
- package/src/app/lib/registry-api/errors.ts +2 -2
- package/src/app/lib/registry-api/handler.ts +19 -14
- package/src/app/lib/registry-api/token-transfer.ts +1 -1
- package/src/app/lib/registry-api/transfer.ts +31 -2
- package/src/app/lib/registry-connection.ts +2 -2
- package/src/app/next.config.mjs +13 -4
- package/src/app/pages/_app.tsx +10 -4
- package/src/app/pages/account/index.tsx +153 -0
- package/src/app/pages/account/tokens.tsx +230 -0
- package/src/app/pages/account/users.tsx +310 -0
- package/src/app/pages/api/account/index.ts +26 -0
- package/src/app/pages/api/account/tokens/[id].ts +17 -0
- package/src/app/pages/api/account/tokens/index.ts +44 -0
- package/src/app/pages/api/admin/users/[id]/[action].ts +89 -0
- package/src/app/pages/api/admin/users/index.ts +53 -0
- package/src/app/pages/api/auth/[...nextauth].ts +4 -0
- package/src/app/pages/api/auth/request-reset.ts +53 -0
- package/src/app/pages/api/auth/reset-password.ts +29 -0
- package/src/app/pages/api/install.ts +102 -0
- package/src/app/pages/api/oauth/device/approve.ts +23 -0
- package/src/app/pages/api/oauth/device/index.ts +41 -0
- package/src/app/pages/api/oauth/revoke.ts +24 -0
- package/src/app/pages/api/oauth/token.ts +31 -0
- package/src/app/pages/api/registry/health.ts +4 -3
- package/src/app/pages/api/registry/transfer/[kind]/[id].ts +3 -3
- package/src/app/pages/api/registry/transfer/[kind]/index.ts +21 -0
- package/src/app/pages/api/registry/transfer/assets/[collection].ts +2 -1
- package/src/app/pages/api/registry/transfer/assets/blobs/[hash].ts +2 -1
- package/src/app/pages/api/registry/transfer/tokens/[...setId].ts +2 -2
- package/src/app/pages/cli/device.tsx +143 -0
- package/src/app/pages/foundations/icons/index.tsx +2 -2
- package/src/app/pages/foundations/icons/{[name] → library/[name]}/index.tsx +6 -6
- package/src/app/pages/install.tsx +336 -0
- package/src/app/pages/login.tsx +117 -0
- package/src/app/pages/reset-password.tsx +159 -0
- package/src/app/types/next-auth.d.ts +27 -0
- package/src/app-builder/build.ts +96 -59
- package/src/app-builder/client-config.ts +12 -11
- package/src/app-builder/paths.ts +7 -9
- package/src/app-builder/static-export.ts +6 -6
- package/src/app-builder/vercel-output.ts +2 -2
- package/src/artifacts/url.ts +7 -7
- package/src/cli/auth/browser.ts +29 -0
- package/src/cli/auth/device.ts +181 -0
- package/src/cli/auth/store.ts +137 -0
- package/src/cli/create.ts +58 -3
- package/src/cli/scaffold.ts +20 -197
- package/src/commands/checkout/index.ts +10 -15
- package/src/commands/index.ts +4 -0
- package/src/commands/login/index.ts +48 -0
- package/src/commands/logout/index.ts +51 -0
- package/src/commands/publish/index.ts +10 -14
- package/src/commands/utils.ts +12 -0
- package/src/config/entries.ts +175 -0
- package/src/config/index.ts +4 -0
- package/src/config/runtime.ts +15 -14
- package/src/index.ts +26 -12
- package/src/registry/asset-storage/resolve.ts +1 -1
- package/src/registry/auth/access-tokens.ts +198 -0
- package/src/registry/auth/action-tokens.ts +225 -0
- package/src/registry/auth/crypto.ts +13 -0
- package/src/registry/auth/database.ts +15 -0
- package/src/registry/auth/device-authorizations.ts +223 -0
- package/src/registry/auth/gravatar.ts +6 -0
- package/src/registry/auth/index.ts +89 -0
- package/src/registry/auth/installation.ts +117 -0
- package/src/registry/auth/password.ts +46 -0
- package/src/registry/auth/rate-limits.ts +84 -0
- package/src/registry/auth/types.ts +34 -0
- package/src/registry/auth/user-record.ts +25 -0
- package/src/registry/auth/users.ts +145 -0
- package/src/registry/auth/validation.ts +24 -0
- package/src/registry/checkout/assets.ts +4 -2
- package/src/registry/checkout/index.ts +182 -30
- package/src/registry/checkout/tokens.ts +4 -2
- package/src/registry/client.ts +7 -1
- package/src/registry/connection.ts +23 -0
- package/src/registry/db/client.ts +12 -12
- package/src/registry/db/{adapter.ts → driver.ts} +24 -21
- package/src/registry/db/index.ts +16 -4
- package/src/registry/db/migrate.ts +5 -5
- package/src/registry/db/schema.ts +174 -1
- package/src/registry/publish/assets.ts +1 -1
- package/src/registry/publish/errors.ts +3 -3
- package/src/registry/publish/index.ts +108 -10
- package/src/registry/publish/tokens.ts +1 -1
- package/src/registry/transfer.ts +14 -0
- package/src/types/config.ts +11 -4
- package/templates/default/README.md.tpl +58 -1
- package/templates/default/gitignore.tpl +19 -3
- package/templates/default/handoff.config.js.tpl +13 -0
- package/templates/default/handoff.config.ts.tpl +13 -0
- package/templates/default/handoff.config.with-examples.js.tpl +13 -0
- package/templates/default/handoff.config.with-examples.ts.tpl +13 -0
- package/templates/default/package.json.tpl +2 -1
- package/templates/default/vercel.json.tpl +4 -0
- package/dist/registry/db/adapter.d.ts +0 -43
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
/**
|
|
3
3
|
* Connected-workspace checkout orchestration.
|
|
4
4
|
*
|
|
5
|
-
* `checkout <
|
|
5
|
+
* `checkout <components|patterns> <id>` reads a normalized registry record and its registry-safe
|
|
6
6
|
* source files from the connected registry through the shared registry client, then writes them
|
|
7
7
|
* into the local workspace in standard authoring form. The local **declaration is synthesized
|
|
8
8
|
* locally** in the configured `runtime.workspace.declarationFormat` — declarations are never read
|
|
@@ -60,10 +60,11 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
60
60
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
61
61
|
};
|
|
62
62
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
63
|
-
exports.checkoutEntity = exports.resolveConnectionOrThrow = exports.CheckoutError = void 0;
|
|
63
|
+
exports.checkoutEntities = exports.checkoutEntity = exports.resolveConnectionOrThrow = exports.CheckoutError = void 0;
|
|
64
64
|
const p = __importStar(require("@clack/prompts"));
|
|
65
65
|
const fs_extra_1 = __importDefault(require("fs-extra"));
|
|
66
66
|
const path_1 = __importDefault(require("path"));
|
|
67
|
+
const entries_1 = require("../../config/entries");
|
|
67
68
|
const runtime_1 = require("../../config/runtime");
|
|
68
69
|
const logger_1 = require("../../utils/logger");
|
|
69
70
|
const client_1 = require("../client");
|
|
@@ -92,24 +93,24 @@ const COMPONENT_EXTENSION = /\.(tsx|jsx|ts|js|cjs|mjs)$/i;
|
|
|
92
93
|
* host has no local workspace to write into) and a resolved registry URL + access token. Throws an
|
|
93
94
|
* actionable {@link CheckoutError} naming the exact misconfiguration.
|
|
94
95
|
*/
|
|
95
|
-
const resolveConnectionOrThrow = (handoff) => {
|
|
96
|
+
const resolveConnectionOrThrow = (handoff) => __awaiter(void 0, void 0, void 0, function* () {
|
|
96
97
|
var _a, _b, _c;
|
|
97
98
|
const mode = (_c = (_b = (_a = handoff.config) === null || _a === void 0 ? void 0 : _a.runtime) === null || _b === void 0 ? void 0 : _b.mode) !== null && _c !== void 0 ? _c : 'workspace';
|
|
98
99
|
if (mode !== 'workspace') {
|
|
99
100
|
throw new CheckoutError(`checkout is only available from a connected workspace (runtime.mode: "workspace"); this project is "${mode}". ` +
|
|
100
101
|
'A registry host stores and serves entities; it has no local workspace to check them out into.');
|
|
101
102
|
}
|
|
102
|
-
const connection = (0, connection_1.
|
|
103
|
+
const connection = yield (0, connection_1.resolveAuthenticatedRegistryConnection)(handoff.config, handoff.workingPath);
|
|
103
104
|
if (!connection.url) {
|
|
104
|
-
throw new CheckoutError(`No registry
|
|
105
|
-
|
|
105
|
+
throw new CheckoutError(`No registry is configured. Run \`handoff-app login --url <registry-url>\`, set runtime.registryConnection.url, ` +
|
|
106
|
+
`or set the "${connection.urlEnv}" environment variable to the base URL of the registry to checkout from.`);
|
|
106
107
|
}
|
|
107
108
|
if (!connection.accessToken) {
|
|
108
|
-
throw new CheckoutError(`No registry access token is configured.
|
|
109
|
-
"
|
|
109
|
+
throw new CheckoutError(`No registry access token is configured. Run \`handoff-app login --url ${connection.url}\`, or set the ` +
|
|
110
|
+
`"${connection.accessTokenEnv}" environment variable to a user-issued token for CI.`);
|
|
110
111
|
}
|
|
111
112
|
return connection;
|
|
112
|
-
};
|
|
113
|
+
});
|
|
113
114
|
exports.resolveConnectionOrThrow = resolveConnectionOrThrow;
|
|
114
115
|
/** Map a registry client error to an actionable checkout message. */
|
|
115
116
|
const describeFetchFailure = (error, kind, id, registryUrl) => {
|
|
@@ -119,28 +120,69 @@ const describeFetchFailure = (error, kind, id, registryUrl) => {
|
|
|
119
120
|
case 'runtime_mode_conflict':
|
|
120
121
|
return `The registry at ${registryUrl} is not running in registry mode, so it cannot serve a checkout: ${error.message}`;
|
|
121
122
|
case 'unauthorized':
|
|
122
|
-
return `The registry rejected the access token (401).
|
|
123
|
+
return `The registry rejected the access token (401). Run \`handoff-app login --url ${registryUrl}\` again, or replace the user-issued CI token.`;
|
|
124
|
+
case 'forbidden':
|
|
125
|
+
return `The registry token does not have permission to checkout this content (403). Authorize a token with registry:read access.`;
|
|
123
126
|
default:
|
|
124
127
|
return error.message;
|
|
125
128
|
}
|
|
126
129
|
};
|
|
127
130
|
/**
|
|
128
|
-
*
|
|
129
|
-
*
|
|
130
|
-
*
|
|
131
|
+
* Ask the user which collection directory a new entity should land in when the config is ambiguous.
|
|
132
|
+
* Under `--force` (or any non-interactive run) there's no one to ask, so we fail with an actionable
|
|
133
|
+
* message rather than guessing a root.
|
|
131
134
|
*/
|
|
132
|
-
const
|
|
135
|
+
const promptForCollectionRoot = (handoff, kind, roots) => __awaiter(void 0, void 0, void 0, function* () {
|
|
136
|
+
const relative = (root) => path_1.default.relative(handoff.workingPath, root) || '.';
|
|
137
|
+
if (handoff.force) {
|
|
138
|
+
throw new CheckoutError(`Cannot determine where to checkout the ${kind}: entries.${kind}s declares individual ${kind}s under different ` +
|
|
139
|
+
`directories (${roots.map(relative).join(', ')}). Declare a single collection directory in handoff.config, ` +
|
|
140
|
+
`or run checkout without --force to choose interactively.`);
|
|
141
|
+
}
|
|
142
|
+
const choice = yield p.select({
|
|
143
|
+
message: `Where should the new ${kind} be checked out?`,
|
|
144
|
+
options: roots.map((root) => ({ value: root, label: relative(root) })),
|
|
145
|
+
initialValue: roots[0],
|
|
146
|
+
});
|
|
147
|
+
if (p.isCancel(choice)) {
|
|
148
|
+
throw new CheckoutError('Checkout cancelled; no target directory chosen.');
|
|
149
|
+
}
|
|
150
|
+
return choice;
|
|
151
|
+
});
|
|
152
|
+
/**
|
|
153
|
+
* Resolve the collection directory a new entity is cloned into from the configured
|
|
154
|
+
* `entries.{components|patterns}`. Each entry is either a collection directory (used as-is) or a
|
|
155
|
+
* single declared entity directory, in which case its parent is the collection root so the new
|
|
156
|
+
* entity lands as a sibling rather than nested inside. Different parents mean the config is
|
|
157
|
+
* ambiguous, so we ask the user; with nothing configured we fall back to the default subdir.
|
|
158
|
+
*/
|
|
159
|
+
const resolveCollectionRoot = (handoff, kind) => __awaiter(void 0, void 0, void 0, function* () {
|
|
133
160
|
var _a, _b, _c, _d;
|
|
161
|
+
const configuredRoots = kind === 'component' ? (_b = (_a = handoff.config) === null || _a === void 0 ? void 0 : _a.entries) === null || _b === void 0 ? void 0 : _b.components : (_d = (_c = handoff.config) === null || _c === void 0 ? void 0 : _c.entries) === null || _d === void 0 ? void 0 : _d.patterns;
|
|
162
|
+
if (!(configuredRoots === null || configuredRoots === void 0 ? void 0 : configuredRoots.length)) {
|
|
163
|
+
return path_1.default.resolve(handoff.workingPath, DEFAULT_ENTITY_DIR[kind]);
|
|
164
|
+
}
|
|
165
|
+
const roots = [
|
|
166
|
+
...new Set(configuredRoots.map((entry) => {
|
|
167
|
+
const resolved = path_1.default.resolve(handoff.workingPath, entry);
|
|
168
|
+
return (0, runtime_1.isComponentDirectory)(resolved) ? path_1.default.dirname(resolved) : resolved;
|
|
169
|
+
})),
|
|
170
|
+
];
|
|
171
|
+
return roots.length === 1 ? roots[0] : promptForCollectionRoot(handoff, kind, roots);
|
|
172
|
+
});
|
|
173
|
+
/**
|
|
174
|
+
* Resolve the local directory the entity is written into. An already-checked-out or locally-declared
|
|
175
|
+
* entity keeps its existing source directory (matched by stable `id`); otherwise it lands as a
|
|
176
|
+
* sibling under its configured collection root (see {@link resolveCollectionRoot}).
|
|
177
|
+
*/
|
|
178
|
+
const resolveTargetDir = (handoff, kind, id) => __awaiter(void 0, void 0, void 0, function* () {
|
|
134
179
|
const store = kind === 'component' ? handoff.store.components : handoff.store.patterns;
|
|
135
180
|
const existing = yield store.get(id);
|
|
136
181
|
const existingDir = existing === null || existing === void 0 ? void 0 : existing.path;
|
|
137
182
|
if (existingDir) {
|
|
138
183
|
return existingDir;
|
|
139
184
|
}
|
|
140
|
-
const
|
|
141
|
-
const root = (configuredRoots === null || configuredRoots === void 0 ? void 0 : configuredRoots.length)
|
|
142
|
-
? path_1.default.resolve(handoff.workingPath, configuredRoots[0])
|
|
143
|
-
: path_1.default.resolve(handoff.workingPath, DEFAULT_ENTITY_DIR[kind]);
|
|
185
|
+
const root = yield resolveCollectionRoot(handoff, kind);
|
|
144
186
|
const targetDir = (0, path_2.resolvePathWithin)(root, id);
|
|
145
187
|
if (!targetDir) {
|
|
146
188
|
throw new CheckoutError(`Cannot checkout ${kind} with unsafe id "${id}".`);
|
|
@@ -551,33 +593,33 @@ const checkoutPage = (handoff, id, payload) => __awaiter(void 0, void 0, void 0,
|
|
|
551
593
|
logger_1.Logger.success(`Checked out page "${id}" into ${path_1.default.relative(handoff.workingPath, pagesRoot) || '.'} ` + `(${payload.files.length} markdown file(s)).`);
|
|
552
594
|
});
|
|
553
595
|
/**
|
|
554
|
-
* Checkout
|
|
555
|
-
*
|
|
556
|
-
*
|
|
557
|
-
* messaging on any precondition or fetch
|
|
596
|
+
* Checkout one entity through an already-resolved client: precondition checks → fetch the normalized
|
|
597
|
+
* record + source files → resolve the local target → explicit-overwrite guard → write source files
|
|
598
|
+
* and synthesize the local declaration. Shared by the single-id and bulk entry points so both behave
|
|
599
|
+
* identically. Throws {@link CheckoutError} with actionable messaging on any precondition or fetch
|
|
600
|
+
* failure.
|
|
558
601
|
*/
|
|
559
|
-
const
|
|
602
|
+
const checkoutSingle = (handoff, kind, id, client, registryUrl) => __awaiter(void 0, void 0, void 0, function* () {
|
|
560
603
|
const validId = kind === 'page' ? (0, path_2.isSafeRelativePath)(id) : (0, path_2.isSafePathSegment)(id);
|
|
561
604
|
if (!validId) {
|
|
562
605
|
throw new CheckoutError(`Cannot checkout ${kind} with unsafe id "${id}".`);
|
|
563
606
|
}
|
|
564
|
-
|
|
565
|
-
logger_1.Logger.info(`Fetching ${kind} "${id}" from ${connection.url}…`);
|
|
566
|
-
const client = (0, client_1.createRegistryClient)({ baseUrl: connection.url, accessToken: connection.accessToken });
|
|
607
|
+
logger_1.Logger.info(`Fetching ${kind} "${id}" from ${registryUrl}…`);
|
|
567
608
|
let payload;
|
|
568
609
|
try {
|
|
569
610
|
payload = yield client.checkout(kind, id);
|
|
570
611
|
}
|
|
571
612
|
catch (error) {
|
|
572
613
|
if (error instanceof client_1.RegistryClientError) {
|
|
573
|
-
throw new CheckoutError(describeFetchFailure(error, kind, id,
|
|
614
|
+
throw new CheckoutError(describeFetchFailure(error, kind, id, registryUrl));
|
|
574
615
|
}
|
|
575
616
|
throw error;
|
|
576
617
|
}
|
|
577
|
-
// Pages round-trip as a single verbatim `.md` with no declaration synthesis
|
|
618
|
+
// Pages round-trip as a single verbatim `.md` with no declaration synthesis, and aren't
|
|
619
|
+
// declared in `entries`, so there's nothing to register.
|
|
578
620
|
if (kind === 'page') {
|
|
579
621
|
yield checkoutPage(handoff, id, payload);
|
|
580
|
-
return;
|
|
622
|
+
return null;
|
|
581
623
|
}
|
|
582
624
|
const targetDir = yield resolveTargetDir(handoff, kind, id);
|
|
583
625
|
const format = resolveDeclarationFormat(handoff, targetDir);
|
|
@@ -594,7 +636,7 @@ const checkoutEntity = (handoff, kind, id) => __awaiter(void 0, void 0, void 0,
|
|
|
594
636
|
const conflicts = [...sourceTargets.map((entry) => entry.absolutePath), declarationPath].filter((file) => fs_extra_1.default.existsSync(file));
|
|
595
637
|
if (!(yield confirmOverwrite(handoff, conflicts))) {
|
|
596
638
|
logger_1.Logger.warn(`Checkout of ${kind} "${id}" cancelled; no files were written.`);
|
|
597
|
-
return;
|
|
639
|
+
return null;
|
|
598
640
|
}
|
|
599
641
|
yield fs_extra_1.default.ensureDir(targetDir);
|
|
600
642
|
const written = [];
|
|
@@ -606,5 +648,92 @@ const checkoutEntity = (handoff, kind, id) => __awaiter(void 0, void 0, void 0,
|
|
|
606
648
|
written.push(declarationPath);
|
|
607
649
|
logger_1.Logger.success(`Checked out ${kind} "${id}" into ${path_1.default.relative(handoff.workingPath, targetDir) || '.'} ` +
|
|
608
650
|
`(${payload.files.length} source file(s) + ${declarationFileName}).`);
|
|
651
|
+
return targetDir;
|
|
652
|
+
});
|
|
653
|
+
/**
|
|
654
|
+
* Declare freshly checked-out entities in `entries.{components|patterns}` so the workspace build
|
|
655
|
+
* picks them up. Ones already covered by a collection directory load on their own and are left
|
|
656
|
+
* alone; the rest are added to the config automatically. If the config can't be edited (a computed
|
|
657
|
+
* or unusual `entries` array), we print the paths for the user to add so nothing is silently orphaned.
|
|
658
|
+
*/
|
|
659
|
+
const registerCheckedOut = (handoff, kind, targetDirs) => __awaiter(void 0, void 0, void 0, function* () {
|
|
660
|
+
if (kind === 'page') {
|
|
661
|
+
return;
|
|
662
|
+
}
|
|
663
|
+
const entryKind = kind === 'component' ? 'components' : 'patterns';
|
|
664
|
+
const uncovered = targetDirs.filter((dir) => !(0, entries_1.isEntryCovered)(handoff, entryKind, dir));
|
|
665
|
+
if (uncovered.length === 0) {
|
|
666
|
+
return;
|
|
667
|
+
}
|
|
668
|
+
const result = yield (0, entries_1.writeEntries)(handoff, entryKind, uncovered);
|
|
669
|
+
if (result.status === 'added') {
|
|
670
|
+
const where = result.configPath ? path_1.default.relative(handoff.workingPath, result.configPath) || path_1.default.basename(result.configPath) : 'handoff.config';
|
|
671
|
+
logger_1.Logger.success(`Updated ${where} with ${result.added.length} ${entryKind} path(s).`);
|
|
672
|
+
return;
|
|
673
|
+
}
|
|
674
|
+
const where = result.configPath ? path_1.default.relative(handoff.workingPath, result.configPath) : 'handoff.config';
|
|
675
|
+
logger_1.Logger.warn(`Could not update ${where} automatically. Add these to entries.${entryKind} manually:`);
|
|
676
|
+
result.pending.forEach((rel) => logger_1.Logger.warn(` - ${rel}`));
|
|
677
|
+
});
|
|
678
|
+
/**
|
|
679
|
+
* Checkout a single component, pattern, or page from the connected registry into this workspace.
|
|
680
|
+
* Overwriting existing local files requires `--force` or an interactive confirmation.
|
|
681
|
+
*/
|
|
682
|
+
const checkoutEntity = (handoff, kind, id) => __awaiter(void 0, void 0, void 0, function* () {
|
|
683
|
+
const connection = yield (0, exports.resolveConnectionOrThrow)(handoff);
|
|
684
|
+
const client = (0, client_1.createRegistryClient)({ baseUrl: connection.url, accessToken: connection.accessToken });
|
|
685
|
+
const targetDir = yield checkoutSingle(handoff, kind, id, client, connection.url);
|
|
686
|
+
if (targetDir) {
|
|
687
|
+
yield registerCheckedOut(handoff, kind, [targetDir]);
|
|
688
|
+
}
|
|
609
689
|
});
|
|
610
690
|
exports.checkoutEntity = checkoutEntity;
|
|
691
|
+
/**
|
|
692
|
+
* Checkout every published component, pattern, or page of the kind into this workspace. Enumerates
|
|
693
|
+
* the registry's published ids, then checks each out through the shared client. A per-entity failure
|
|
694
|
+
* is collected and never aborts the rest; the run throws at the end if any entity failed. Overwrite
|
|
695
|
+
* prompting is per entity (skipped under `--force`).
|
|
696
|
+
*/
|
|
697
|
+
const checkoutEntities = (handoff, kind) => __awaiter(void 0, void 0, void 0, function* () {
|
|
698
|
+
const connection = yield (0, exports.resolveConnectionOrThrow)(handoff);
|
|
699
|
+
const client = (0, client_1.createRegistryClient)({ baseUrl: connection.url, accessToken: connection.accessToken });
|
|
700
|
+
let summaries;
|
|
701
|
+
try {
|
|
702
|
+
summaries = yield client.listEntities(kind);
|
|
703
|
+
}
|
|
704
|
+
catch (error) {
|
|
705
|
+
if (error instanceof client_1.RegistryClientError) {
|
|
706
|
+
throw new CheckoutError(`Could not list ${kind}s from the registry at ${connection.url}: ${error.message}`);
|
|
707
|
+
}
|
|
708
|
+
throw error;
|
|
709
|
+
}
|
|
710
|
+
if (summaries.length === 0) {
|
|
711
|
+
logger_1.Logger.success(`No ${kind}s are published in the registry; nothing to checkout.`);
|
|
712
|
+
return;
|
|
713
|
+
}
|
|
714
|
+
let checkedOut = 0;
|
|
715
|
+
const failed = [];
|
|
716
|
+
const targetDirs = [];
|
|
717
|
+
for (const { id } of summaries) {
|
|
718
|
+
try {
|
|
719
|
+
const targetDir = yield checkoutSingle(handoff, kind, id, client, connection.url);
|
|
720
|
+
if (targetDir) {
|
|
721
|
+
targetDirs.push(targetDir);
|
|
722
|
+
}
|
|
723
|
+
checkedOut += 1;
|
|
724
|
+
}
|
|
725
|
+
catch (error) {
|
|
726
|
+
failed.push({ id, message: error instanceof Error ? error.message : String(error) });
|
|
727
|
+
}
|
|
728
|
+
}
|
|
729
|
+
// Register everything checked out in one pass, so the config is edited (and confirmed) once.
|
|
730
|
+
yield registerCheckedOut(handoff, kind, targetDirs);
|
|
731
|
+
logger_1.Logger.success(`${kind[0].toUpperCase()}${kind.slice(1)}s checkout complete — ${checkedOut} checked out${failed.length ? `, ${failed.length} failed` : ''}.`);
|
|
732
|
+
if (failed.length > 0) {
|
|
733
|
+
for (const failure of failed) {
|
|
734
|
+
logger_1.Logger.error(` - ${failure.id}: ${failure.message}`);
|
|
735
|
+
}
|
|
736
|
+
throw new CheckoutError(`${failed.length} ${kind}(s) failed to checkout.`);
|
|
737
|
+
}
|
|
738
|
+
});
|
|
739
|
+
exports.checkoutEntities = checkoutEntities;
|
|
@@ -61,7 +61,9 @@ const describeFetchFailure = (error, target, registryUrl) => {
|
|
|
61
61
|
case 'runtime_mode_conflict':
|
|
62
62
|
return `The registry at ${registryUrl} is not running in registry mode, so it cannot serve a checkout: ${error.message}`;
|
|
63
63
|
case 'unauthorized':
|
|
64
|
-
return `The registry rejected the access token (401).
|
|
64
|
+
return `The registry rejected the access token (401). Run \`handoff-app login --url ${registryUrl}\` again, or replace the user-issued CI token.`;
|
|
65
|
+
case 'forbidden':
|
|
66
|
+
return `The registry token does not have permission to checkout design tokens (403). Authorize a token with registry:read access.`;
|
|
65
67
|
default:
|
|
66
68
|
return error.message;
|
|
67
69
|
}
|
|
@@ -99,7 +101,7 @@ const checkoutTokens = (handoff, setId) => __awaiter(void 0, void 0, void 0, fun
|
|
|
99
101
|
if (setId && !(0, sets_1.isTokenSetId)(setId)) {
|
|
100
102
|
throw new index_1.CheckoutError(`Unknown or unsafe token set id "${setId}".`);
|
|
101
103
|
}
|
|
102
|
-
const connection = (0, index_1.resolveConnectionOrThrow)(handoff);
|
|
104
|
+
const connection = yield (0, index_1.resolveConnectionOrThrow)(handoff);
|
|
103
105
|
const client = (0, client_1.createRegistryClient)({ baseUrl: connection.url, accessToken: connection.accessToken });
|
|
104
106
|
let ids;
|
|
105
107
|
if (setId) {
|
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
* surface a clear message instead of a raw fetch failure. Transport is the global `fetch` (Node 18+
|
|
8
8
|
* and the browser both provide it), so this module stays runtime-agnostic.
|
|
9
9
|
*/
|
|
10
|
-
import type { CheckoutPayload, TransferEntityKind, TransferPackage } from './transfer';
|
|
10
|
+
import type { CheckoutPayload, EntitySummary, TransferEntityKind, TransferPackage } from './transfer';
|
|
11
11
|
import type { TokenSetCheckoutPayload, TokenSetSummary, TokenSetTransferPackage } from './tokens/transfer';
|
|
12
12
|
import type { AssetCollectionCheckoutPayload, AssetCollectionSummary, AssetCollectionTransferPackage } from './assets/transfer';
|
|
13
13
|
/** Standard registry response envelope. */
|
|
@@ -44,6 +44,7 @@ export interface RegistryClientOptions {
|
|
|
44
44
|
export interface RegistryClient {
|
|
45
45
|
publish(kind: TransferEntityKind, id: string, pkg: TransferPackage): Promise<RegistryEnvelope>;
|
|
46
46
|
checkout(kind: TransferEntityKind, id: string): Promise<CheckoutPayload>;
|
|
47
|
+
listEntities(kind: TransferEntityKind): Promise<EntitySummary[]>;
|
|
47
48
|
listTokenSets(): Promise<TokenSetSummary[]>;
|
|
48
49
|
publishTokens(pkg: TokenSetTransferPackage): Promise<RegistryEnvelope>;
|
|
49
50
|
checkoutTokens(id: string): Promise<TokenSetCheckoutPayload>;
|
package/dist/registry/client.js
CHANGED
|
@@ -115,6 +115,14 @@ const createRegistryClient = ({ baseUrl, accessToken }) => {
|
|
|
115
115
|
return envelope.data;
|
|
116
116
|
});
|
|
117
117
|
},
|
|
118
|
+
/** List the registry's entities of one kind (id + build hashes) for skip-unchanged / bulk checkout. */
|
|
119
|
+
listEntities(kind) {
|
|
120
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
121
|
+
var _a, _b;
|
|
122
|
+
const envelope = yield request('GET', `/api/registry/transfer/${kind}`);
|
|
123
|
+
return (_b = (_a = envelope.data) === null || _a === void 0 ? void 0 : _a.entities) !== null && _b !== void 0 ? _b : [];
|
|
124
|
+
});
|
|
125
|
+
},
|
|
118
126
|
/** List the registry's token sets (id + kind + source hash) for skip-unchanged / bulk checkout. */
|
|
119
127
|
listTokenSets() {
|
|
120
128
|
return __awaiter(this, void 0, void 0, function* () {
|
|
@@ -28,3 +28,12 @@ export interface ResolvedRegistryConnection {
|
|
|
28
28
|
* Callers inspect the resolved URL and access token to surface specific configuration errors.
|
|
29
29
|
*/
|
|
30
30
|
export declare const resolveRegistryConnection: (config: Config | null | undefined) => ResolvedRegistryConnection;
|
|
31
|
+
/**
|
|
32
|
+
* Resolve the same connection for an authenticated CLI operation, letting a saved device login fill
|
|
33
|
+
* in what the environment does not provide. A valid login supplies both the registry URL and the
|
|
34
|
+
* access token, so `handoff-app login` alone is enough to publish and checkout. Environment values
|
|
35
|
+
* keep precedence for deterministic CI: `HANDOFF_REGISTRY_URL` (or inline config) wins over the
|
|
36
|
+
* saved URL, and `HANDOFF_REGISTRY_ACCESS_TOKEN` wins over the saved token. The saved token only
|
|
37
|
+
* applies to the URL it was issued for.
|
|
38
|
+
*/
|
|
39
|
+
export declare const resolveAuthenticatedRegistryConnection: (config: Config | null | undefined, workingPath?: string) => Promise<ResolvedRegistryConnection>;
|
|
@@ -8,8 +8,18 @@
|
|
|
8
8
|
* access token is always read from an env-var name (never persisted as a value). This is the single
|
|
9
9
|
* source the publish (and later checkout) client reads its target + credentials from.
|
|
10
10
|
*/
|
|
11
|
+
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
12
|
+
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
13
|
+
return new (P || (P = Promise))(function (resolve, reject) {
|
|
14
|
+
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
15
|
+
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
16
|
+
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
17
|
+
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
18
|
+
});
|
|
19
|
+
};
|
|
11
20
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
12
|
-
exports.resolveRegistryConnection = exports.DEFAULT_REGISTRY_ACCESS_TOKEN_ENV = exports.DEFAULT_REGISTRY_URL_ENV = void 0;
|
|
21
|
+
exports.resolveAuthenticatedRegistryConnection = exports.resolveRegistryConnection = exports.DEFAULT_REGISTRY_ACCESS_TOKEN_ENV = exports.DEFAULT_REGISTRY_URL_ENV = void 0;
|
|
22
|
+
const store_1 = require("../cli/auth/store");
|
|
13
23
|
/** Default env-var name holding the remote registry base URL. */
|
|
14
24
|
exports.DEFAULT_REGISTRY_URL_ENV = 'HANDOFF_REGISTRY_URL';
|
|
15
25
|
/** Default env-var name holding the connected-workspace access token. */
|
|
@@ -34,3 +44,21 @@ const resolveRegistryConnection = (config) => {
|
|
|
34
44
|
};
|
|
35
45
|
};
|
|
36
46
|
exports.resolveRegistryConnection = resolveRegistryConnection;
|
|
47
|
+
/**
|
|
48
|
+
* Resolve the same connection for an authenticated CLI operation, letting a saved device login fill
|
|
49
|
+
* in what the environment does not provide. A valid login supplies both the registry URL and the
|
|
50
|
+
* access token, so `handoff-app login` alone is enough to publish and checkout. Environment values
|
|
51
|
+
* keep precedence for deterministic CI: `HANDOFF_REGISTRY_URL` (or inline config) wins over the
|
|
52
|
+
* saved URL, and `HANDOFF_REGISTRY_ACCESS_TOKEN` wins over the saved token. The saved token only
|
|
53
|
+
* applies to the URL it was issued for.
|
|
54
|
+
*/
|
|
55
|
+
const resolveAuthenticatedRegistryConnection = (config_1, ...args_1) => __awaiter(void 0, [config_1, ...args_1], void 0, function* (config, workingPath = process.cwd()) {
|
|
56
|
+
const connection = (0, exports.resolveRegistryConnection)(config);
|
|
57
|
+
const auth = yield (0, store_1.readCliAuth)(workingPath);
|
|
58
|
+
if (!(0, store_1.cliAuthTokenIsValid)(auth))
|
|
59
|
+
return connection;
|
|
60
|
+
const url = connection.url || auth.remoteUrl;
|
|
61
|
+
const accessToken = connection.accessToken || ((0, store_1.cliAuthMatchesRegistry)(auth, url) ? auth.accessToken : '');
|
|
62
|
+
return Object.assign(Object.assign({}, connection), { url, accessToken });
|
|
63
|
+
});
|
|
64
|
+
exports.resolveAuthenticatedRegistryConnection = resolveAuthenticatedRegistryConnection;
|
|
@@ -1,21 +1,21 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Bundled Drizzle client + migrator for the registry database.
|
|
3
3
|
*
|
|
4
|
-
* The package ships both built-in
|
|
4
|
+
* The package ships both built-in drivers (`pg` and `neon`) and the Drizzle client/migrator, so a
|
|
5
5
|
* consuming project needs no global Drizzle install or separate CLI. Driver modules are loaded
|
|
6
|
-
* lazily (dynamic import) for the selected
|
|
6
|
+
* lazily (dynamic import) for the selected driver only, so workspace dev/build never pulls the
|
|
7
7
|
* Postgres/Neon drivers into memory when the registry is not in use.
|
|
8
8
|
*/
|
|
9
9
|
import type { NeonDatabase } from 'drizzle-orm/neon-serverless';
|
|
10
10
|
import type { NodePgDatabase } from 'drizzle-orm/node-postgres';
|
|
11
|
-
import type {
|
|
11
|
+
import type { RegistryDatabaseDriver } from './driver';
|
|
12
12
|
import * as schema from './schema';
|
|
13
|
-
/** Typed Drizzle database over the registry schema, regardless of which
|
|
13
|
+
/** Typed Drizzle database over the registry schema, regardless of which driver backs it. */
|
|
14
14
|
export type RegistryDatabase = NodePgDatabase<typeof schema> | NeonDatabase<typeof schema>;
|
|
15
15
|
/** A live registry database connection plus its lifecycle operations. */
|
|
16
16
|
export interface RegistryDbConnection {
|
|
17
|
-
/** Selected
|
|
18
|
-
|
|
17
|
+
/** Selected driver that produced this connection. */
|
|
18
|
+
driver: RegistryDatabaseDriver;
|
|
19
19
|
/** Typed Drizzle database. */
|
|
20
20
|
db: RegistryDatabase;
|
|
21
21
|
/** Apply the package-owned migrations found in `migrationsFolder`. */
|
|
@@ -24,12 +24,12 @@ export interface RegistryDbConnection {
|
|
|
24
24
|
close(): Promise<void>;
|
|
25
25
|
}
|
|
26
26
|
export interface CreateRegistryDbConnectionParams {
|
|
27
|
-
|
|
27
|
+
driver: RegistryDatabaseDriver;
|
|
28
28
|
connectionString: string;
|
|
29
29
|
}
|
|
30
30
|
/**
|
|
31
|
-
* Create a registry database connection for the resolved
|
|
31
|
+
* Create a registry database connection for the resolved driver. Both drivers target the
|
|
32
32
|
* Postgres dialect and share the same schema, so the returned connection is interchangeable for
|
|
33
33
|
* migration and (later) the registry store.
|
|
34
34
|
*/
|
|
35
|
-
export declare const createRegistryDbConnection: ({
|
|
35
|
+
export declare const createRegistryDbConnection: ({ driver, connectionString, }: CreateRegistryDbConnectionParams) => Promise<RegistryDbConnection>;
|
|
@@ -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
|
|
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
|
|
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
|
|
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* ({
|
|
50
|
+
const createRegistryDbConnection = (_a) => __awaiter(void 0, [_a], void 0, function* ({ driver, connectionString, }) {
|
|
51
51
|
var _b;
|
|
52
|
-
if (
|
|
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
|
-
|
|
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
|
-
|
|
88
|
+
driver,
|
|
89
89
|
db,
|
|
90
90
|
migrate: (migrationsFolder) => migrate(db, { migrationsFolder }),
|
|
91
91
|
close: () => pool.end(),
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Registry database driver resolution.
|
|
3
|
+
*
|
|
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.
|
|
11
|
+
*/
|
|
12
|
+
import type { Config } from '../../types/config';
|
|
13
|
+
/** Built-in registry database connection driver. Both ship with the package and target Postgres. */
|
|
14
|
+
export type RegistryDatabaseDriver = 'pg' | 'neon';
|
|
15
|
+
/** Default env-var name holding the database connection string. */
|
|
16
|
+
export declare const DEFAULT_DATABASE_URL_ENV = "DATABASE_URL";
|
|
17
|
+
/** @deprecated Fixed registry server secrets are no longer authorized. */
|
|
18
|
+
export declare const DEFAULT_REGISTRY_API_TOKEN_ENV = "HANDOFF_REGISTRY_API_TOKEN";
|
|
19
|
+
/** Default registry database driver when none is configured. */
|
|
20
|
+
export declare const DEFAULT_REGISTRY_DRIVER: RegistryDatabaseDriver;
|
|
21
|
+
/** Fully resolved registry database connection inputs. */
|
|
22
|
+
export interface ResolvedRegistryDatabase {
|
|
23
|
+
/** Selected built-in driver. */
|
|
24
|
+
driver: RegistryDatabaseDriver;
|
|
25
|
+
/** Name of the env var the connection string was read from. */
|
|
26
|
+
databaseUrlEnv: string;
|
|
27
|
+
/** Resolved connection string (never persisted in config). */
|
|
28
|
+
connectionString: string;
|
|
29
|
+
}
|
|
30
|
+
/** Resolve the env-var name holding the database URL (defaults to `DATABASE_URL`). */
|
|
31
|
+
export declare const resolveDatabaseUrlEnv: (config: Config | null | undefined) => string;
|
|
32
|
+
/**
|
|
33
|
+
* Resolve the legacy fixed-token env-var name for configuration compatibility.
|
|
34
|
+
*
|
|
35
|
+
* @deprecated The returned env var is intentionally ignored by registry authorization. Use a
|
|
36
|
+
* user-issued access token or `handoff-app login`.
|
|
37
|
+
*/
|
|
38
|
+
export declare const resolveApiTokenEnv: (config: Config | null | undefined) => string;
|
|
39
|
+
/** Resolve the configured registry database driver (defaults to `pg`). */
|
|
40
|
+
export declare const resolveRegistryDriver: (config: Config | null | undefined) => RegistryDatabaseDriver;
|
|
41
|
+
/**
|
|
42
|
+
* Resolve the registry database connection from config + environment. Throws an actionable error
|
|
43
|
+
* when the configured env var is unset so the CLI can surface a clear "missing database URL"
|
|
44
|
+
* message instead of failing deep inside the driver.
|
|
45
|
+
*/
|
|
46
|
+
export declare const resolveRegistryDatabase: (config: Config | null | undefined) => ResolvedRegistryDatabase;
|
|
@@ -1,21 +1,23 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
/**
|
|
3
|
-
* Registry database
|
|
3
|
+
* Registry database driver resolution.
|
|
4
4
|
*
|
|
5
|
-
* `runtime.registry.database.
|
|
6
|
-
* `handoff-app` package — `pg` (default) and `neon` —
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
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.
|
|
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
|
+
/** @deprecated Fixed registry server secrets are no longer authorized. */
|
|
16
18
|
exports.DEFAULT_REGISTRY_API_TOKEN_ENV = 'HANDOFF_REGISTRY_API_TOKEN';
|
|
17
|
-
/** Default registry database
|
|
18
|
-
exports.
|
|
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;
|
|
@@ -24,9 +26,10 @@ const resolveDatabaseUrlEnv = (config) => {
|
|
|
24
26
|
};
|
|
25
27
|
exports.resolveDatabaseUrlEnv = resolveDatabaseUrlEnv;
|
|
26
28
|
/**
|
|
27
|
-
* Resolve the env-var
|
|
28
|
-
*
|
|
29
|
-
*
|
|
29
|
+
* Resolve the legacy fixed-token env-var name for configuration compatibility.
|
|
30
|
+
*
|
|
31
|
+
* @deprecated The returned env var is intentionally ignored by registry authorization. Use a
|
|
32
|
+
* user-issued access token or `handoff-app login`.
|
|
30
33
|
*/
|
|
31
34
|
const resolveApiTokenEnv = (config) => {
|
|
32
35
|
var _a, _b, _c;
|
|
@@ -34,12 +37,12 @@ const resolveApiTokenEnv = (config) => {
|
|
|
34
37
|
return configured || exports.DEFAULT_REGISTRY_API_TOKEN_ENV;
|
|
35
38
|
};
|
|
36
39
|
exports.resolveApiTokenEnv = resolveApiTokenEnv;
|
|
37
|
-
/** Resolve the configured registry
|
|
38
|
-
const
|
|
40
|
+
/** Resolve the configured registry database driver (defaults to `pg`). */
|
|
41
|
+
const resolveRegistryDriver = (config) => {
|
|
39
42
|
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.
|
|
43
|
+
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
44
|
};
|
|
42
|
-
exports.
|
|
45
|
+
exports.resolveRegistryDriver = resolveRegistryDriver;
|
|
43
46
|
/**
|
|
44
47
|
* Resolve the registry database connection from config + environment. Throws an actionable error
|
|
45
48
|
* when the configured env var is unset so the CLI can surface a clear "missing database URL"
|
|
@@ -47,13 +50,13 @@ exports.resolveRegistryAdapter = resolveRegistryAdapter;
|
|
|
47
50
|
*/
|
|
48
51
|
const resolveRegistryDatabase = (config) => {
|
|
49
52
|
var _a;
|
|
50
|
-
const
|
|
53
|
+
const driver = (0, exports.resolveRegistryDriver)(config);
|
|
51
54
|
const databaseUrlEnv = (0, exports.resolveDatabaseUrlEnv)(config);
|
|
52
55
|
const connectionString = (_a = process.env[databaseUrlEnv]) === null || _a === void 0 ? void 0 : _a.trim();
|
|
53
56
|
if (!connectionString) {
|
|
54
57
|
throw new Error(`Registry database URL is not configured. Set the "${databaseUrlEnv}" environment variable to a ` +
|
|
55
58
|
`PostgreSQL connection string (or change "runtime.registry.databaseUrlEnv" to point at the env var you use).`);
|
|
56
59
|
}
|
|
57
|
-
return {
|
|
60
|
+
return { driver, databaseUrlEnv, connectionString };
|
|
58
61
|
};
|
|
59
62
|
exports.resolveRegistryDatabase = resolveRegistryDatabase;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
export { buildMetadata, componentFiles, components, docsArtifacts, patternFiles, patterns, registrySchema, type BuildMetadataEntityKind, type DocsArtifactEntityKind, type RegistryReviewMetadata, } from './schema';
|
|
1
|
+
export { buildMetadata, componentFiles, components, docsArtifacts, patternFiles, patterns, registryAccessTokens, registryAuthActionTokens, registryAuthRateLimits, registryDeviceAuthorizations, registryInstallations, registrySchema, registryUsers, type BuildMetadataEntityKind, type DocsArtifactEntityKind, type RegistryAccessScope, type RegistryAuthActionPurpose, type RegistryDeviceAuthorizationStatus, type RegistryRateLimitBucket, type RegistryReviewMetadata, type RegistryUserRole, type RegistryUserStatus, } from './schema';
|
|
2
2
|
export type { ArtifactBuildStatus as RegistryBuildStatus } from '../../artifacts/types';
|
|
3
|
-
export { DEFAULT_DATABASE_URL_ENV,
|
|
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';
|