handoff-app 2.0.0-2 → 2.0.0-4
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 +140 -83
- package/dist/app-builder/build.js +21 -4
- package/dist/app-builder/client-config.js +9 -8
- 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/scaffold.js +20 -154
- package/dist/cli.js +1 -1
- 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/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/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 +1 -1
- package/dist/registry/checkout/index.js +101 -23
- package/dist/registry/checkout/tokens.js +4 -2
- package/dist/registry/connection.d.ts +9 -0
- package/dist/registry/connection.js +29 -1
- package/dist/registry/db/driver.d.ts +5 -4
- package/dist/registry/db/driver.js +5 -4
- package/dist/registry/db/index.d.ts +1 -1
- package/dist/registry/db/index.js +7 -1
- 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 +1 -1
- package/dist/registry/publish/index.js +9 -9
- package/dist/registry/publish/tokens.js +1 -1
- package/dist/types/config.d.ts +4 -1
- 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/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/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 +0 -14
- 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 +4 -4
- package/src/app/next.config.mjs +12 -3
- 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/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/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 +22 -5
- package/src/app-builder/client-config.ts +10 -9
- 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/scaffold.ts +20 -197
- package/src/cli.ts +1 -1
- 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/config/entries.ts +175 -0
- package/src/config/index.ts +4 -0
- package/src/config/runtime.ts +15 -14
- 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 +114 -24
- package/src/registry/checkout/tokens.ts +4 -2
- package/src/registry/connection.ts +23 -0
- package/src/registry/db/driver.ts +5 -4
- package/src/registry/db/index.ts +12 -0
- 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 +9 -9
- package/src/registry/publish/tokens.ts +1 -1
- package/src/types/config.ts +4 -1
- package/templates/default/README.md.tpl +18 -0
- package/templates/default/gitignore.tpl +1 -0
- package/templates/default/handoff.config.js.tpl +0 -1
- package/templates/default/handoff.config.ts.tpl +0 -1
- package/templates/default/handoff.config.with-examples.js.tpl +0 -1
- package/templates/default/handoff.config.with-examples.ts.tpl +0 -1
|
@@ -64,6 +64,7 @@ exports.checkoutEntities = exports.checkoutEntity = exports.resolveConnectionOrT
|
|
|
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}".`);
|
|
@@ -573,10 +615,11 @@ const checkoutSingle = (handoff, kind, id, client, registryUrl) => __awaiter(voi
|
|
|
573
615
|
}
|
|
574
616
|
throw error;
|
|
575
617
|
}
|
|
576
|
-
// 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.
|
|
577
620
|
if (kind === 'page') {
|
|
578
621
|
yield checkoutPage(handoff, id, payload);
|
|
579
|
-
return;
|
|
622
|
+
return null;
|
|
580
623
|
}
|
|
581
624
|
const targetDir = yield resolveTargetDir(handoff, kind, id);
|
|
582
625
|
const format = resolveDeclarationFormat(handoff, targetDir);
|
|
@@ -593,7 +636,7 @@ const checkoutSingle = (handoff, kind, id, client, registryUrl) => __awaiter(voi
|
|
|
593
636
|
const conflicts = [...sourceTargets.map((entry) => entry.absolutePath), declarationPath].filter((file) => fs_extra_1.default.existsSync(file));
|
|
594
637
|
if (!(yield confirmOverwrite(handoff, conflicts))) {
|
|
595
638
|
logger_1.Logger.warn(`Checkout of ${kind} "${id}" cancelled; no files were written.`);
|
|
596
|
-
return;
|
|
639
|
+
return null;
|
|
597
640
|
}
|
|
598
641
|
yield fs_extra_1.default.ensureDir(targetDir);
|
|
599
642
|
const written = [];
|
|
@@ -605,15 +648,44 @@ const checkoutSingle = (handoff, kind, id, client, registryUrl) => __awaiter(voi
|
|
|
605
648
|
written.push(declarationPath);
|
|
606
649
|
logger_1.Logger.success(`Checked out ${kind} "${id}" into ${path_1.default.relative(handoff.workingPath, targetDir) || '.'} ` +
|
|
607
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}`));
|
|
608
677
|
});
|
|
609
678
|
/**
|
|
610
679
|
* Checkout a single component, pattern, or page from the connected registry into this workspace.
|
|
611
680
|
* Overwriting existing local files requires `--force` or an interactive confirmation.
|
|
612
681
|
*/
|
|
613
682
|
const checkoutEntity = (handoff, kind, id) => __awaiter(void 0, void 0, void 0, function* () {
|
|
614
|
-
const connection = (0, exports.resolveConnectionOrThrow)(handoff);
|
|
683
|
+
const connection = yield (0, exports.resolveConnectionOrThrow)(handoff);
|
|
615
684
|
const client = (0, client_1.createRegistryClient)({ baseUrl: connection.url, accessToken: connection.accessToken });
|
|
616
|
-
yield checkoutSingle(handoff, kind, id, client, connection.url);
|
|
685
|
+
const targetDir = yield checkoutSingle(handoff, kind, id, client, connection.url);
|
|
686
|
+
if (targetDir) {
|
|
687
|
+
yield registerCheckedOut(handoff, kind, [targetDir]);
|
|
688
|
+
}
|
|
617
689
|
});
|
|
618
690
|
exports.checkoutEntity = checkoutEntity;
|
|
619
691
|
/**
|
|
@@ -623,7 +695,7 @@ exports.checkoutEntity = checkoutEntity;
|
|
|
623
695
|
* prompting is per entity (skipped under `--force`).
|
|
624
696
|
*/
|
|
625
697
|
const checkoutEntities = (handoff, kind) => __awaiter(void 0, void 0, void 0, function* () {
|
|
626
|
-
const connection = (0, exports.resolveConnectionOrThrow)(handoff);
|
|
698
|
+
const connection = yield (0, exports.resolveConnectionOrThrow)(handoff);
|
|
627
699
|
const client = (0, client_1.createRegistryClient)({ baseUrl: connection.url, accessToken: connection.accessToken });
|
|
628
700
|
let summaries;
|
|
629
701
|
try {
|
|
@@ -641,15 +713,21 @@ const checkoutEntities = (handoff, kind) => __awaiter(void 0, void 0, void 0, fu
|
|
|
641
713
|
}
|
|
642
714
|
let checkedOut = 0;
|
|
643
715
|
const failed = [];
|
|
716
|
+
const targetDirs = [];
|
|
644
717
|
for (const { id } of summaries) {
|
|
645
718
|
try {
|
|
646
|
-
yield checkoutSingle(handoff, kind, id, client, connection.url);
|
|
719
|
+
const targetDir = yield checkoutSingle(handoff, kind, id, client, connection.url);
|
|
720
|
+
if (targetDir) {
|
|
721
|
+
targetDirs.push(targetDir);
|
|
722
|
+
}
|
|
647
723
|
checkedOut += 1;
|
|
648
724
|
}
|
|
649
725
|
catch (error) {
|
|
650
726
|
failed.push({ id, message: error instanceof Error ? error.message : String(error) });
|
|
651
727
|
}
|
|
652
728
|
}
|
|
729
|
+
// Register everything checked out in one pass, so the config is edited (and confirmed) once.
|
|
730
|
+
yield registerCheckedOut(handoff, kind, targetDirs);
|
|
653
731
|
logger_1.Logger.success(`${kind[0].toUpperCase()}${kind.slice(1)}s checkout complete — ${checkedOut} checked out${failed.length ? `, ${failed.length} failed` : ''}.`);
|
|
654
732
|
if (failed.length > 0) {
|
|
655
733
|
for (const failure of failed) {
|
|
@@ -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) {
|
|
@@ -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;
|
|
@@ -14,7 +14,7 @@ import type { Config } from '../../types/config';
|
|
|
14
14
|
export type RegistryDatabaseDriver = 'pg' | 'neon';
|
|
15
15
|
/** Default env-var name holding the database connection string. */
|
|
16
16
|
export declare const DEFAULT_DATABASE_URL_ENV = "DATABASE_URL";
|
|
17
|
-
/**
|
|
17
|
+
/** @deprecated Fixed registry server secrets are no longer authorized. */
|
|
18
18
|
export declare const DEFAULT_REGISTRY_API_TOKEN_ENV = "HANDOFF_REGISTRY_API_TOKEN";
|
|
19
19
|
/** Default registry database driver when none is configured. */
|
|
20
20
|
export declare const DEFAULT_REGISTRY_DRIVER: RegistryDatabaseDriver;
|
|
@@ -30,9 +30,10 @@ export interface ResolvedRegistryDatabase {
|
|
|
30
30
|
/** Resolve the env-var name holding the database URL (defaults to `DATABASE_URL`). */
|
|
31
31
|
export declare const resolveDatabaseUrlEnv: (config: Config | null | undefined) => string;
|
|
32
32
|
/**
|
|
33
|
-
* Resolve the env-var
|
|
34
|
-
*
|
|
35
|
-
*
|
|
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`.
|
|
36
37
|
*/
|
|
37
38
|
export declare const resolveApiTokenEnv: (config: Config | null | undefined) => string;
|
|
38
39
|
/** Resolve the configured registry database driver (defaults to `pg`). */
|
|
@@ -14,7 +14,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
14
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;
|
|
15
15
|
/** Default env-var name holding the database connection string. */
|
|
16
16
|
exports.DEFAULT_DATABASE_URL_ENV = 'DATABASE_URL';
|
|
17
|
-
/**
|
|
17
|
+
/** @deprecated Fixed registry server secrets are no longer authorized. */
|
|
18
18
|
exports.DEFAULT_REGISTRY_API_TOKEN_ENV = 'HANDOFF_REGISTRY_API_TOKEN';
|
|
19
19
|
/** Default registry database driver when none is configured. */
|
|
20
20
|
exports.DEFAULT_REGISTRY_DRIVER = 'pg';
|
|
@@ -26,9 +26,10 @@ const resolveDatabaseUrlEnv = (config) => {
|
|
|
26
26
|
};
|
|
27
27
|
exports.resolveDatabaseUrlEnv = resolveDatabaseUrlEnv;
|
|
28
28
|
/**
|
|
29
|
-
* Resolve the env-var
|
|
30
|
-
*
|
|
31
|
-
*
|
|
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`.
|
|
32
33
|
*/
|
|
33
34
|
const resolveApiTokenEnv = (config) => {
|
|
34
35
|
var _a, _b, _c;
|
|
@@ -1,4 +1,4 @@
|
|
|
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
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';
|
|
@@ -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.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;
|
|
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.registryUsers = exports.registrySchema = exports.registryInstallations = exports.registryDeviceAuthorizations = exports.registryAuthRateLimits = exports.registryAuthActionTokens = exports.registryAccessTokens = 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; } });
|
|
@@ -8,7 +8,13 @@ Object.defineProperty(exports, "components", { enumerable: true, get: function (
|
|
|
8
8
|
Object.defineProperty(exports, "docsArtifacts", { enumerable: true, get: function () { return schema_1.docsArtifacts; } });
|
|
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
|
+
Object.defineProperty(exports, "registryAccessTokens", { enumerable: true, get: function () { return schema_1.registryAccessTokens; } });
|
|
12
|
+
Object.defineProperty(exports, "registryAuthActionTokens", { enumerable: true, get: function () { return schema_1.registryAuthActionTokens; } });
|
|
13
|
+
Object.defineProperty(exports, "registryAuthRateLimits", { enumerable: true, get: function () { return schema_1.registryAuthRateLimits; } });
|
|
14
|
+
Object.defineProperty(exports, "registryDeviceAuthorizations", { enumerable: true, get: function () { return schema_1.registryDeviceAuthorizations; } });
|
|
15
|
+
Object.defineProperty(exports, "registryInstallations", { enumerable: true, get: function () { return schema_1.registryInstallations; } });
|
|
11
16
|
Object.defineProperty(exports, "registrySchema", { enumerable: true, get: function () { return schema_1.registrySchema; } });
|
|
17
|
+
Object.defineProperty(exports, "registryUsers", { enumerable: true, get: function () { return schema_1.registryUsers; } });
|
|
12
18
|
var driver_1 = require("./driver");
|
|
13
19
|
Object.defineProperty(exports, "DEFAULT_DATABASE_URL_ENV", { enumerable: true, get: function () { return driver_1.DEFAULT_DATABASE_URL_ENV; } });
|
|
14
20
|
Object.defineProperty(exports, "DEFAULT_REGISTRY_DRIVER", { enumerable: true, get: function () { return driver_1.DEFAULT_REGISTRY_DRIVER; } });
|