handoff-app 2.0.0-4 → 2.0.0-5
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/AGENTS.md +7 -0
- package/CLAUDE.md +7 -0
- package/README.md +302 -378
- package/dist/app-builder/build.js +2 -2
- package/dist/app-builder/nav-shell.js +8 -15
- package/dist/app-builder/vercel-output.js +4 -4
- package/dist/cli/make.js +2 -2
- package/dist/cli.js +38 -6
- package/dist/commands/build/app.js +1 -5
- package/dist/commands/build/components.js +1 -5
- package/dist/commands/build/index.js +1 -5
- package/dist/commands/checkout/index.d.ts +15 -12
- package/dist/commands/checkout/index.js +15 -54
- package/dist/commands/compat/deprecations.d.ts +46 -0
- package/dist/commands/compat/deprecations.js +62 -0
- package/dist/commands/compat/push-args.d.ts +48 -0
- package/dist/commands/compat/push-args.js +73 -0
- package/dist/commands/db/migrate.js +1 -5
- package/dist/commands/dev/index.js +1 -5
- package/dist/commands/eject/config.js +1 -5
- package/dist/commands/eject/pages.js +1 -5
- package/dist/commands/eject/theme.js +1 -5
- package/dist/commands/fetch/index.js +1 -5
- package/dist/commands/index.js +4 -0
- package/dist/commands/login/index.js +1 -5
- package/dist/commands/logout/index.js +1 -5
- package/dist/commands/make/component.js +1 -5
- package/dist/commands/make/page.js +3 -7
- package/dist/commands/make/template.js +1 -5
- package/dist/commands/publish/index.d.ts +18 -12
- package/dist/commands/publish/index.js +18 -54
- package/dist/commands/pull/index.d.ts +13 -0
- package/dist/commands/pull/index.js +20 -0
- package/dist/commands/push/index.d.ts +23 -0
- package/dist/commands/push/index.js +58 -0
- package/dist/commands/scaffold/index.js +1 -5
- package/dist/commands/start/index.js +1 -5
- package/dist/commands/types.d.ts +10 -0
- package/dist/commands/utils.d.ts +74 -6
- package/dist/commands/utils.js +89 -10
- package/dist/commands/validate/components.js +1 -5
- package/dist/config/index.d.ts +2 -1
- package/dist/config/index.js +2 -1
- package/dist/config/loader.d.ts +19 -6
- package/dist/config/loader.js +43 -13
- package/dist/config/normalizers/page.d.ts +4 -4
- package/dist/config/normalizers/page.js +2 -2
- package/dist/config/runtime.js +9 -25
- package/dist/index.d.ts +69 -22
- package/dist/index.js +148 -35
- package/dist/nav/resolver.js +3 -1
- package/dist/nav/workspace.js +2 -2
- package/dist/registry/auth/access-tokens.d.ts +11 -3
- package/dist/registry/auth/access-tokens.js +29 -3
- package/dist/registry/auth/index.d.ts +4 -3
- package/dist/registry/auth/index.js +10 -2
- package/dist/registry/auth/sync-secret.d.ts +27 -0
- package/dist/registry/auth/sync-secret.js +65 -0
- package/dist/registry/auth/types.d.ts +20 -1
- package/dist/registry/auth/types.js +3 -1
- package/dist/registry/auth/validation.d.ts +11 -0
- package/dist/registry/auth/validation.js +18 -1
- package/dist/registry/checkout/assets.d.ts +1 -1
- package/dist/registry/checkout/assets.js +21 -6
- package/dist/registry/checkout/index.d.ts +8 -1
- package/dist/registry/checkout/index.js +39 -6
- package/dist/registry/checkout/tokens.d.ts +1 -1
- package/dist/registry/checkout/tokens.js +12 -5
- package/dist/registry/connection.d.ts +10 -0
- package/dist/registry/connection.js +52 -7
- package/dist/registry/content-kinds.d.ts +27 -0
- package/dist/registry/content-kinds.js +33 -0
- package/dist/registry/db/client.d.ts +6 -1
- package/dist/registry/db/client.js +16 -3
- package/dist/registry/db/schema.d.ts +37 -3
- package/dist/registry/db/schema.js +9 -7
- package/dist/registry/publish/assets.d.ts +2 -2
- package/dist/registry/publish/assets.js +46 -35
- package/dist/registry/publish/index.d.ts +29 -5
- package/dist/registry/publish/index.js +87 -34
- package/dist/registry/publish/tokens.d.ts +2 -2
- package/dist/registry/publish/tokens.js +30 -24
- package/dist/registry/selection.d.ts +12 -0
- package/dist/registry/selection.js +22 -0
- package/dist/store/registry.d.ts +48 -0
- package/dist/store/registry.js +75 -12
- package/dist/transformers/preview/types.d.ts +2 -2
- package/dist/utils/markdown.d.ts +10 -0
- package/dist/utils/markdown.js +17 -0
- package/dist/utils/menu-shell.js +14 -7
- package/dist/utils/pages.d.ts +22 -0
- package/dist/utils/pages.js +73 -0
- package/dist/utils/path.d.ts +5 -0
- package/dist/utils/path.js +9 -0
- package/docs/api.md +1 -3
- package/docs/cli.md +1 -1
- package/drizzle/0002_add_pages.sql +5 -3
- package/drizzle/meta/0002_snapshot.json +17 -8
- package/drizzle/meta/0003_snapshot.json +17 -8
- package/drizzle/meta/0004_snapshot.json +17 -8
- package/drizzle/meta/0005_snapshot.json +17 -8
- package/package.json +1 -1
- package/src/app/components/util/index.ts +5 -23
- package/src/app/lib/docs-api/backend.ts +11 -0
- package/src/app/lib/docs-api/errors.ts +8 -7
- package/src/app/lib/docs-api/handler.ts +2 -3
- package/src/app/lib/docs-api/index.ts +2 -0
- package/src/app/lib/docs-api/page-discovery.ts +126 -0
- package/src/app/lib/docs-api/page-rendering.ts +17 -0
- package/src/app/lib/docs-api/page-search.ts +191 -0
- package/src/app/lib/docs-api/records.ts +11 -19
- package/src/app/lib/docs-api/registry.ts +90 -11
- package/src/app/lib/docs-api/search.ts +115 -0
- package/src/app/lib/registry-api/allowlist.ts +7 -5
- package/src/app/lib/registry-api/asset-transfer.ts +58 -31
- package/src/app/lib/registry-api/auth.ts +89 -9
- package/src/app/lib/registry-api/entity-routes.ts +17 -7
- package/src/app/lib/registry-api/errors.ts +10 -4
- package/src/app/lib/registry-api/handler.ts +13 -15
- package/src/app/lib/registry-api/meta.ts +4 -0
- package/src/app/lib/registry-api/revalidate.ts +31 -12
- package/src/app/lib/registry-api/store.ts +89 -26
- package/src/app/lib/registry-api/sync-compat.ts +166 -0
- package/src/app/lib/registry-api/token-transfer.ts +44 -12
- package/src/app/lib/registry-api/transfer-batch.ts +116 -0
- package/src/app/lib/registry-api/transfer.ts +62 -35
- package/src/app/lib/registry-api/validation.ts +33 -1
- package/src/app/lib/registry-connection.ts +1 -1
- package/src/app/next.config.mjs +6 -0
- package/src/app/pages/[...slug]/index.tsx +37 -22
- package/src/app/pages/api/account/tokens/index.ts +4 -13
- package/src/app/pages/api/docs/search/pages.json.ts +25 -0
- package/src/app/pages/api/oauth/revoke.ts +14 -7
- package/src/app/pages/api/registry/components/[id]/files/[...filePath].ts +1 -1
- package/src/app/pages/api/registry/components/[id].ts +2 -2
- package/src/app/pages/api/registry/components/index.ts +1 -1
- package/src/app/pages/api/registry/pages/[...id].ts +20 -0
- package/src/app/pages/api/registry/pages/index.ts +19 -0
- package/src/app/pages/api/registry/patterns/[id]/files/[...filePath].ts +1 -1
- package/src/app/pages/api/registry/patterns/[id].ts +2 -2
- package/src/app/pages/api/registry/patterns/index.ts +1 -1
- package/src/app/pages/api/registry/transfer/assets/blobs/have.ts +2 -2
- package/src/app/pages/api/sync/upload.ts +21 -0
- package/src/app/pages/index/index.tsx +14 -0
- package/src/app/pages/index.tsx +30 -5
- package/src/app-builder/build.ts +2 -2
- package/src/app-builder/nav-shell.ts +8 -13
- package/src/app-builder/vercel-output.ts +4 -4
- package/src/cli/make.ts +2 -2
- package/src/cli.ts +30 -6
- package/src/commands/build/app.ts +2 -3
- package/src/commands/build/components.ts +2 -3
- package/src/commands/build/index.ts +2 -3
- package/src/commands/checkout/index.ts +18 -44
- package/src/commands/compat/deprecations.ts +79 -0
- package/src/commands/compat/push-args.ts +106 -0
- package/src/commands/db/migrate.ts +2 -3
- package/src/commands/dev/index.ts +2 -3
- package/src/commands/eject/config.ts +2 -3
- package/src/commands/eject/pages.ts +2 -3
- package/src/commands/eject/theme.ts +2 -3
- package/src/commands/fetch/index.ts +2 -3
- package/src/commands/index.ts +4 -0
- package/src/commands/login/index.ts +2 -3
- package/src/commands/logout/index.ts +2 -3
- package/src/commands/make/component.ts +2 -3
- package/src/commands/make/page.ts +4 -5
- package/src/commands/make/template.ts +2 -3
- package/src/commands/publish/index.ts +21 -44
- package/src/commands/pull/index.ts +32 -0
- package/src/commands/push/index.ts +115 -0
- package/src/commands/scaffold/index.ts +2 -3
- package/src/commands/start/index.ts +2 -3
- package/src/commands/types.ts +11 -1
- package/src/commands/utils.ts +80 -11
- package/src/commands/validate/components.ts +2 -3
- package/src/config/index.ts +2 -1
- package/src/config/loader.ts +57 -12
- package/src/config/normalizers/page.ts +4 -4
- package/src/config/runtime.ts +9 -24
- package/src/index.ts +183 -42
- package/src/nav/resolver.ts +3 -1
- package/src/nav/workspace.ts +2 -2
- package/src/registry/auth/access-tokens.ts +29 -5
- package/src/registry/auth/index.ts +7 -1
- package/src/registry/auth/sync-secret.ts +67 -0
- package/src/registry/auth/types.ts +24 -1
- package/src/registry/auth/validation.ts +18 -0
- package/src/registry/checkout/assets.ts +24 -7
- package/src/registry/checkout/index.ts +41 -5
- package/src/registry/checkout/tokens.ts +12 -6
- package/src/registry/connection.ts +53 -5
- package/src/registry/content-kinds.ts +39 -0
- package/src/registry/db/client.ts +22 -2
- package/src/registry/db/schema.ts +9 -7
- package/src/registry/publish/assets.ts +47 -37
- package/src/registry/publish/index.ts +102 -38
- package/src/registry/publish/tokens.ts +35 -26
- package/src/registry/selection.ts +19 -0
- package/src/store/registry.ts +118 -13
- package/src/transformers/preview/types.ts +2 -2
- package/src/utils/README.md +1 -0
- package/src/utils/markdown.ts +11 -0
- package/src/utils/menu-shell.ts +14 -7
- package/src/utils/pages.ts +52 -0
- package/src/utils/path.ts +15 -6
- package/templates/default/package.json.tpl +4 -0
- package/config/docs/foundations/logos.md +0 -7
- package/src/app/lib/api/redact.ts +0 -46
package/dist/index.js
CHANGED
|
@@ -45,11 +45,68 @@ const eject_1 = require("./cli/eject");
|
|
|
45
45
|
const make_1 = require("./cli/make");
|
|
46
46
|
const config_1 = require("./config");
|
|
47
47
|
const pipeline_1 = __importStar(require("./pipeline"));
|
|
48
|
+
const content_kinds_1 = require("./registry/content-kinds");
|
|
48
49
|
const store_1 = require("./store");
|
|
49
50
|
const builder_1 = __importStar(require("./transformers/preview/component/builder"));
|
|
50
51
|
const logger_1 = require("./utils/logger");
|
|
51
52
|
const path_2 = require("./utils/path");
|
|
52
53
|
const path_3 = require("./utils/path");
|
|
54
|
+
/**
|
|
55
|
+
* Reject a kind we do not know. Plural kinds arrive from CLI arguments and HTTP payloads, so an
|
|
56
|
+
* unrecognized one has to stop here: the entity paths below pick their store by elimination, and an
|
|
57
|
+
* unchecked value would quietly resolve to pages.
|
|
58
|
+
*/
|
|
59
|
+
const assertContentKind = (kind) => {
|
|
60
|
+
if (!(0, content_kinds_1.isRegistryEntityKind)(kind)) {
|
|
61
|
+
throw new Error(`Unknown content kind "${kind}". Supported kinds: ${content_kinds_1.REGISTRY_ENTITY_KINDS.join(', ')}.`);
|
|
62
|
+
}
|
|
63
|
+
};
|
|
64
|
+
const normalizeTarget = (target) => typeof target === 'string' ? { kind: target } : target;
|
|
65
|
+
/**
|
|
66
|
+
* Normalize an optional single-or-list selection to a list, or `undefined` for "everything". An empty
|
|
67
|
+
* list means "everything" too: yargs defaults an omitted variadic positional to `[]`, and that has to
|
|
68
|
+
* read as "no narrowing", not "no targets".
|
|
69
|
+
*/
|
|
70
|
+
const toSelection = (value) => {
|
|
71
|
+
if (value === undefined)
|
|
72
|
+
return undefined;
|
|
73
|
+
const selection = Array.isArray(value) ? value : [value];
|
|
74
|
+
return selection.length > 0 ? selection : undefined;
|
|
75
|
+
};
|
|
76
|
+
/**
|
|
77
|
+
* The one id a selection names, if it names exactly one. Lets a single-target request keep the
|
|
78
|
+
* cheaper targeted-build path whether it arrived as a string or a one-element list.
|
|
79
|
+
*/
|
|
80
|
+
const onlyId = (value) => {
|
|
81
|
+
const selection = toSelection(value);
|
|
82
|
+
return (selection === null || selection === void 0 ? void 0 : selection.length) === 1 ? selection[0] : undefined;
|
|
83
|
+
};
|
|
84
|
+
/**
|
|
85
|
+
* Run one operation across several targets in order, reporting each failure as it happens and throwing
|
|
86
|
+
* once at the end. Shared by {@link Handoff.publishAll} and {@link Handoff.checkoutAll} so a
|
|
87
|
+
* multi-kind run behaves the same in both directions.
|
|
88
|
+
*/
|
|
89
|
+
const runKinds = (targets, verb, dryRun, run) => __awaiter(void 0, void 0, void 0, function* () {
|
|
90
|
+
const kinds = targets.map(normalizeTarget);
|
|
91
|
+
const failed = [];
|
|
92
|
+
for (const { kind, ids } of kinds) {
|
|
93
|
+
try {
|
|
94
|
+
yield run(kind, ids);
|
|
95
|
+
}
|
|
96
|
+
catch (error) {
|
|
97
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
98
|
+
failed.push({ kind, message });
|
|
99
|
+
logger_1.Logger.error(`Failed to ${verb} ${kind}: ${message}`);
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
const succeeded = kinds.length - failed.length;
|
|
103
|
+
const outcome = verb === 'publish' ? 'published' : 'checked out';
|
|
104
|
+
logger_1.Logger.success(`${dryRun ? 'Dry run' : `${verb[0].toUpperCase()}${verb.slice(1)} complete`} — ${succeeded}/${kinds.length} kind(s) ` +
|
|
105
|
+
`${dryRun ? `would be ${outcome}` : outcome}${failed.length ? `, ${failed.length} failed` : ''}.`);
|
|
106
|
+
if (failed.length > 0) {
|
|
107
|
+
throw new Error(`${failed.length} kind(s) failed to ${verb}: ${failed.map((entry) => entry.kind).join(', ')}.`);
|
|
108
|
+
}
|
|
109
|
+
});
|
|
53
110
|
class Handoff {
|
|
54
111
|
/**
|
|
55
112
|
* Normalized store over the current workspace runtime for components, patterns, pages, tokens,
|
|
@@ -61,30 +118,38 @@ class Handoff {
|
|
|
61
118
|
}
|
|
62
119
|
return this._store;
|
|
63
120
|
}
|
|
64
|
-
constructor(
|
|
121
|
+
constructor(options = {}) {
|
|
65
122
|
this.debug = false;
|
|
66
123
|
this.force = false;
|
|
124
|
+
this.dryRun = false;
|
|
125
|
+
this.skipBuild = false;
|
|
67
126
|
this.modulePath = path_1.default.resolve(__filename, '../..');
|
|
68
|
-
this.workingPath =
|
|
127
|
+
this.workingPath = (0, path_2.resolveWorkingPath)();
|
|
69
128
|
this.exportsDirectory = 'exported';
|
|
70
129
|
this.sitesDirectory = 'out';
|
|
71
|
-
this.
|
|
130
|
+
this._options = {};
|
|
72
131
|
this._configFilePaths = [];
|
|
73
132
|
this._configFileIndex = new Map();
|
|
74
|
-
this.
|
|
75
|
-
this.construct(
|
|
133
|
+
this._options = options;
|
|
134
|
+
this.construct(options);
|
|
76
135
|
}
|
|
77
|
-
construct(
|
|
136
|
+
construct(options) {
|
|
137
|
+
var _a, _b, _c, _d;
|
|
78
138
|
this.config = null;
|
|
79
|
-
this.debug = debug !== null &&
|
|
80
|
-
this.force = force !== null &&
|
|
139
|
+
this.debug = (_a = options.debug) !== null && _a !== void 0 ? _a : false;
|
|
140
|
+
this.force = (_b = options.force) !== null && _b !== void 0 ? _b : false;
|
|
141
|
+
this.dryRun = (_c = options.dryRun) !== null && _c !== void 0 ? _c : false;
|
|
142
|
+
this.skipBuild = (_d = options.skipBuild) !== null && _d !== void 0 ? _d : false;
|
|
81
143
|
logger_1.Logger.init({ debug: this.debug });
|
|
82
|
-
this.init(config);
|
|
144
|
+
this.init(options.config);
|
|
83
145
|
global.handoff = this;
|
|
84
146
|
}
|
|
85
147
|
init(configOverride) {
|
|
86
148
|
var _a, _b, _c;
|
|
87
|
-
const configResult = (0, config_1.initConfigWithMetadata)(configOverride !== null && configOverride !== void 0 ? configOverride : {}
|
|
149
|
+
const configResult = (0, config_1.initConfigWithMetadata)(configOverride !== null && configOverride !== void 0 ? configOverride : {}, {
|
|
150
|
+
workingPath: this.workingPath,
|
|
151
|
+
configPath: this._options.configPath,
|
|
152
|
+
});
|
|
88
153
|
const config = configResult.config;
|
|
89
154
|
this.config = config;
|
|
90
155
|
this._mainConfigFilePath = configResult.configPath;
|
|
@@ -97,7 +162,7 @@ class Handoff {
|
|
|
97
162
|
return this;
|
|
98
163
|
}
|
|
99
164
|
reload() {
|
|
100
|
-
this.construct(this.
|
|
165
|
+
this.construct(this._options);
|
|
101
166
|
return this;
|
|
102
167
|
}
|
|
103
168
|
preRunner(validate) {
|
|
@@ -160,42 +225,45 @@ class Handoff {
|
|
|
160
225
|
}
|
|
161
226
|
/**
|
|
162
227
|
* Publish components, patterns, or pages from this connected workspace to the configured remote
|
|
163
|
-
* registry. With `id` it publishes that one entity after a targeted build;
|
|
164
|
-
* the kind once and publishes every declared entity,
|
|
165
|
-
* the registry (`--force` re-uploads all). Pages upload
|
|
166
|
-
* they render at runtime. The publish module is loaded
|
|
167
|
-
* never enter the docs app bundle.
|
|
228
|
+
* registry. With a single `id` it publishes that one entity after a targeted build; with a list of
|
|
229
|
+
* ids, or none, it builds the kind once and publishes the selected (or every declared) entity,
|
|
230
|
+
* skipping ones whose content is unchanged on the registry (`--force` re-uploads all). Pages upload
|
|
231
|
+
* their record and markdown source because they render at runtime. The publish module is loaded
|
|
232
|
+
* lazily so the registry client and build code never enter the docs app bundle.
|
|
168
233
|
*/
|
|
169
234
|
publish(kind, id) {
|
|
170
235
|
return __awaiter(this, void 0, void 0, function* () {
|
|
171
236
|
this.preRunner();
|
|
172
|
-
|
|
237
|
+
const single = onlyId(id);
|
|
238
|
+
if (single) {
|
|
173
239
|
const { publishEntity } = yield Promise.resolve().then(() => __importStar(require('./registry/publish')));
|
|
174
|
-
yield publishEntity(this, kind,
|
|
240
|
+
yield publishEntity(this, kind, single);
|
|
175
241
|
}
|
|
176
242
|
else {
|
|
177
243
|
const { publishEntities } = yield Promise.resolve().then(() => __importStar(require('./registry/publish')));
|
|
178
|
-
yield publishEntities(this, kind);
|
|
244
|
+
yield publishEntities(this, kind, toSelection(id));
|
|
179
245
|
}
|
|
180
246
|
return this;
|
|
181
247
|
});
|
|
182
248
|
}
|
|
183
249
|
/**
|
|
184
250
|
* Checkout components, patterns, or pages from the connected remote registry into this workspace.
|
|
185
|
-
* With `id` it checks out that one entity;
|
|
186
|
-
* kind. Overwriting existing local files requires
|
|
187
|
-
* checkout module is loaded lazily so the registry
|
|
251
|
+
* With a single `id` it checks out that one entity; with a list of ids, or none, it checks out the
|
|
252
|
+
* selected (or every published) entity of the kind. Overwriting existing local files requires
|
|
253
|
+
* `--force` or an interactive confirmation. The checkout module is loaded lazily so the registry
|
|
254
|
+
* client never enters the docs app bundle.
|
|
188
255
|
*/
|
|
189
256
|
checkout(kind, id) {
|
|
190
257
|
return __awaiter(this, void 0, void 0, function* () {
|
|
191
258
|
this.preRunner();
|
|
192
|
-
|
|
259
|
+
const single = onlyId(id);
|
|
260
|
+
if (single) {
|
|
193
261
|
const { checkoutEntity } = yield Promise.resolve().then(() => __importStar(require('./registry/checkout')));
|
|
194
|
-
yield checkoutEntity(this, kind,
|
|
262
|
+
yield checkoutEntity(this, kind, single);
|
|
195
263
|
}
|
|
196
264
|
else {
|
|
197
265
|
const { checkoutEntities } = yield Promise.resolve().then(() => __importStar(require('./registry/checkout')));
|
|
198
|
-
yield checkoutEntities(this, kind);
|
|
266
|
+
yield checkoutEntities(this, kind, toSelection(id));
|
|
199
267
|
}
|
|
200
268
|
return this;
|
|
201
269
|
});
|
|
@@ -204,28 +272,28 @@ class Handoff {
|
|
|
204
272
|
* Publish design token sets from this connected workspace to the configured remote registry. Runs a
|
|
205
273
|
* fresh token build (Figma extract + style transformers), discovers the logical sets, and uploads
|
|
206
274
|
* each changed set (its extracted record + generated artifacts). Publishes every set when `setId` is
|
|
207
|
-
* omitted, or only the named set (`foundation/colors`, `component/<id>`). Loaded lazily so the
|
|
275
|
+
* omitted, or only the named set(s) (`foundation/colors`, `component/<id>`). Loaded lazily so the
|
|
208
276
|
* registry client/build code never enters the docs app bundle.
|
|
209
277
|
*/
|
|
210
278
|
publishTokens(setId) {
|
|
211
279
|
return __awaiter(this, void 0, void 0, function* () {
|
|
212
280
|
this.preRunner();
|
|
213
281
|
const { publishTokens } = yield Promise.resolve().then(() => __importStar(require('./registry/publish/tokens')));
|
|
214
|
-
yield publishTokens(this, setId);
|
|
282
|
+
yield publishTokens(this, toSelection(setId));
|
|
215
283
|
return this;
|
|
216
284
|
});
|
|
217
285
|
}
|
|
218
286
|
/**
|
|
219
287
|
* Checkout design token sets from the connected remote registry into this workspace: reconstruct
|
|
220
288
|
* the canonical local `tokens.json` and restore the generated token files to their configured output
|
|
221
|
-
* paths. Checks out every published set when `setId` is omitted, or only the named set. Loaded
|
|
222
|
-
* so the registry client never enters the docs app bundle.
|
|
289
|
+
* paths. Checks out every published set when `setId` is omitted, or only the named set(s). Loaded
|
|
290
|
+
* lazily so the registry client never enters the docs app bundle.
|
|
223
291
|
*/
|
|
224
292
|
checkoutTokens(setId) {
|
|
225
293
|
return __awaiter(this, void 0, void 0, function* () {
|
|
226
294
|
this.preRunner();
|
|
227
295
|
const { checkoutTokens } = yield Promise.resolve().then(() => __importStar(require('./registry/checkout/tokens')));
|
|
228
|
-
yield checkoutTokens(this, setId);
|
|
296
|
+
yield checkoutTokens(this, toSelection(setId));
|
|
229
297
|
return this;
|
|
230
298
|
});
|
|
231
299
|
}
|
|
@@ -233,28 +301,73 @@ class Handoff {
|
|
|
233
301
|
* Publish asset collections (icons/logos/fonts) from this connected workspace to the configured
|
|
234
302
|
* remote registry. Runs a fresh build (Figma extract + asset generation), discovers the collections,
|
|
235
303
|
* uploads only content hashes the registry lacks, and finalizes each collection manifest atomically.
|
|
236
|
-
* Publishes every collection when `collection` is omitted, or only the named one. Loaded lazily so
|
|
304
|
+
* Publishes every collection when `collection` is omitted, or only the named one(s). Loaded lazily so
|
|
237
305
|
* the registry client/build code never enters the docs app bundle.
|
|
238
306
|
*/
|
|
239
307
|
publishAssets(collection) {
|
|
240
308
|
return __awaiter(this, void 0, void 0, function* () {
|
|
241
309
|
this.preRunner();
|
|
242
310
|
const { publishAssets } = yield Promise.resolve().then(() => __importStar(require('./registry/publish/assets')));
|
|
243
|
-
yield publishAssets(this, collection);
|
|
311
|
+
yield publishAssets(this, toSelection(collection));
|
|
244
312
|
return this;
|
|
245
313
|
});
|
|
246
314
|
}
|
|
247
315
|
/**
|
|
248
316
|
* Checkout asset collections from the connected remote registry into this workspace: recreate the
|
|
249
317
|
* standard workspace asset files, collection JSON, icon sprite/manifest, and downloadable archives.
|
|
250
|
-
* Checks out every published collection when `collection` is omitted, or only the named one.
|
|
251
|
-
* lazily so the registry client never enters the docs app bundle.
|
|
318
|
+
* Checks out every published collection when `collection` is omitted, or only the named one(s).
|
|
319
|
+
* Loaded lazily so the registry client never enters the docs app bundle.
|
|
252
320
|
*/
|
|
253
321
|
checkoutAssets(collection) {
|
|
254
322
|
return __awaiter(this, void 0, void 0, function* () {
|
|
255
323
|
this.preRunner();
|
|
256
324
|
const { checkoutAssets } = yield Promise.resolve().then(() => __importStar(require('./registry/checkout/assets')));
|
|
257
|
-
yield checkoutAssets(this, collection);
|
|
325
|
+
yield checkoutAssets(this, toSelection(collection));
|
|
326
|
+
return this;
|
|
327
|
+
});
|
|
328
|
+
}
|
|
329
|
+
/**
|
|
330
|
+
* Publish one content kind named by its plural form (`components`, `tokens`, …). The entry point for
|
|
331
|
+
* callers working in the plural vocabulary, so the plural-to-transfer-path mapping lives in one
|
|
332
|
+
* place.
|
|
333
|
+
*/
|
|
334
|
+
publishKind(kind, id) {
|
|
335
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
336
|
+
assertContentKind(kind);
|
|
337
|
+
if (kind === 'tokens')
|
|
338
|
+
return this.publishTokens(id);
|
|
339
|
+
if (kind === 'assets')
|
|
340
|
+
return this.publishAssets(id);
|
|
341
|
+
return this.publish(content_kinds_1.ENTITY_WIRE_KIND[kind], id);
|
|
342
|
+
});
|
|
343
|
+
}
|
|
344
|
+
/** Checkout one content kind named by its plural form. Mirrors {@link publishKind}. */
|
|
345
|
+
checkoutKind(kind, id) {
|
|
346
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
347
|
+
assertContentKind(kind);
|
|
348
|
+
if (kind === 'tokens')
|
|
349
|
+
return this.checkoutTokens(id);
|
|
350
|
+
if (kind === 'assets')
|
|
351
|
+
return this.checkoutAssets(id);
|
|
352
|
+
return this.checkout(content_kinds_1.ENTITY_WIRE_KIND[kind], id);
|
|
353
|
+
});
|
|
354
|
+
}
|
|
355
|
+
/**
|
|
356
|
+
* Publish every content kind, or the given subset of kinds and ids, in dependency order: tokens and
|
|
357
|
+
* assets first (rendered component artifacts reference the CSS variables and asset URLs they
|
|
358
|
+
* produce), then components, patterns and pages. A failing kind is reported and never aborts the
|
|
359
|
+
* rest; the run throws at the end if any kind failed.
|
|
360
|
+
*/
|
|
361
|
+
publishAll() {
|
|
362
|
+
return __awaiter(this, arguments, void 0, function* (targets = content_kinds_1.ALL_KIND_ORDER) {
|
|
363
|
+
yield runKinds(targets, 'publish', this.dryRun, (kind, ids) => this.publishKind(kind, ids));
|
|
364
|
+
return this;
|
|
365
|
+
});
|
|
366
|
+
}
|
|
367
|
+
/** Checkout every content kind, or the given subset, in the same order. Mirrors {@link publishAll}. */
|
|
368
|
+
checkoutAll() {
|
|
369
|
+
return __awaiter(this, arguments, void 0, function* (targets = content_kinds_1.ALL_KIND_ORDER) {
|
|
370
|
+
yield runKinds(targets, 'checkout', this.dryRun, (kind, ids) => this.checkoutKind(kind, ids));
|
|
258
371
|
return this;
|
|
259
372
|
});
|
|
260
373
|
}
|
package/dist/nav/resolver.js
CHANGED
|
@@ -13,6 +13,7 @@ var __rest = (this && this.__rest) || function (s, e) {
|
|
|
13
13
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
14
14
|
exports.getNavData = void 0;
|
|
15
15
|
const lodash_1 = require("lodash");
|
|
16
|
+
const content_kinds_1 = require("../registry/content-kinds");
|
|
16
17
|
const normalizeBasePath = (basePath) => {
|
|
17
18
|
if (!basePath)
|
|
18
19
|
return '';
|
|
@@ -142,7 +143,8 @@ const buildChildPageItems = (parentId, pages) => {
|
|
|
142
143
|
const buildPageSections = (sources, keepEmpty) => {
|
|
143
144
|
const pages = sources.pages.filter((page) => page.enabled !== false);
|
|
144
145
|
return pages
|
|
145
|
-
|
|
146
|
+
// The home page is reachable through the header logo and must not become a duplicate nav section.
|
|
147
|
+
.filter((page) => page.id !== content_kinds_1.HOME_PAGE_ID && !page.id.includes('/'))
|
|
146
148
|
.map((page) => {
|
|
147
149
|
var _a, _b;
|
|
148
150
|
const declared = page.menu
|
package/dist/nav/workspace.js
CHANGED
|
@@ -37,8 +37,8 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
37
37
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
38
38
|
exports.getWorkspaceNavData = void 0;
|
|
39
39
|
const fs = __importStar(require("fs"));
|
|
40
|
-
const gray_matter_1 = __importDefault(require("gray-matter"));
|
|
41
40
|
const path_1 = __importDefault(require("path"));
|
|
41
|
+
const markdown_1 = require("../utils/markdown");
|
|
42
42
|
const menu_shell_1 = require("../utils/menu-shell");
|
|
43
43
|
const resolver_1 = require("./resolver");
|
|
44
44
|
const safeLoad = (load) => __awaiter(void 0, void 0, void 0, function* () {
|
|
@@ -58,7 +58,7 @@ const markdownTitle = (id, docRoot, workingPagesDir) => {
|
|
|
58
58
|
for (const candidate of candidates) {
|
|
59
59
|
if (!candidate || !fs.existsSync(candidate))
|
|
60
60
|
continue;
|
|
61
|
-
const title = (0,
|
|
61
|
+
const title = (0, markdown_1.parseMarkdown)(fs.readFileSync(candidate, 'utf8')).data.title;
|
|
62
62
|
if (typeof title === 'string' && title.trim())
|
|
63
63
|
return title;
|
|
64
64
|
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { RegistryDatabase } from '../db/client';
|
|
2
2
|
import { type RegistryAccessScope } from '../db/schema';
|
|
3
|
-
import { type
|
|
3
|
+
import { type RegistryTokenPrincipal, type RegistryUserRole } from './types';
|
|
4
4
|
export interface RegistryAccessTokenSummary {
|
|
5
5
|
id: string;
|
|
6
6
|
name: string;
|
|
@@ -18,7 +18,15 @@ export type CreateRegistryAccessTokenResult = {
|
|
|
18
18
|
ok: false;
|
|
19
19
|
reason: 'user_not_found' | 'user_inactive' | 'scope_forbidden' | 'invalid_name' | 'invalid_expiry';
|
|
20
20
|
};
|
|
21
|
-
|
|
21
|
+
/** Resolve one canonical scope or documented alias; anything else is not a scope we grant. */
|
|
22
|
+
export declare const resolveRegistryScope: (value: unknown) => RegistryAccessScope | null;
|
|
23
|
+
/** Resolve, dedupe and canonically order a scope list, dropping anything unrecognized. */
|
|
24
|
+
export declare const normalizeRegistryScopes: (scopes: readonly unknown[]) => RegistryAccessScope[];
|
|
25
|
+
/**
|
|
26
|
+
* Normalize a requested scope list into what a token is issued with: write implies read (a write-only
|
|
27
|
+
* token would be rejected by every read endpoint), and an empty request means read.
|
|
28
|
+
*/
|
|
29
|
+
export declare const expandRegistryScopes: (scopes: readonly unknown[]) => RegistryAccessScope[];
|
|
22
30
|
export declare const registryRoleAllowsScopes: (role: RegistryUserRole, scopes: readonly RegistryAccessScope[]) => boolean;
|
|
23
31
|
export declare const formatRegistryAccessToken: (id: string, secret: string) => string;
|
|
24
32
|
export declare const parseRegistryAccessToken: (token: string) => {
|
|
@@ -43,4 +51,4 @@ export declare const revokeAllRegistryAccessTokens: (db: RegistryDatabase, userI
|
|
|
43
51
|
* Resolve an opaque bearer credential into a live principal. User state is joined on every call,
|
|
44
52
|
* making deactivation effective even if a defensive token-revocation write was interrupted.
|
|
45
53
|
*/
|
|
46
|
-
export declare const authenticateRegistryAccessToken: (db: RegistryDatabase, token: string, now?: Date) => Promise<
|
|
54
|
+
export declare const authenticateRegistryAccessToken: (db: RegistryDatabase, token: string, now?: Date) => Promise<RegistryTokenPrincipal | null>;
|
|
@@ -9,7 +9,7 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
|
|
|
9
9
|
});
|
|
10
10
|
};
|
|
11
11
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
12
|
-
exports.authenticateRegistryAccessToken = exports.revokeAllRegistryAccessTokens = exports.revokeRegistryAccessToken = exports.listRegistryAccessTokens = exports.createRegistryAccessToken = exports.parseRegistryAccessToken = exports.formatRegistryAccessToken = exports.registryRoleAllowsScopes = exports.normalizeRegistryScopes = void 0;
|
|
12
|
+
exports.authenticateRegistryAccessToken = exports.revokeAllRegistryAccessTokens = exports.revokeRegistryAccessToken = exports.listRegistryAccessTokens = exports.createRegistryAccessToken = exports.parseRegistryAccessToken = exports.formatRegistryAccessToken = exports.registryRoleAllowsScopes = exports.expandRegistryScopes = exports.normalizeRegistryScopes = exports.resolveRegistryScope = void 0;
|
|
13
13
|
const drizzle_orm_1 = require("drizzle-orm");
|
|
14
14
|
const schema_1 = require("../db/schema");
|
|
15
15
|
const crypto_1 = require("./crypto");
|
|
@@ -17,12 +17,37 @@ const database_1 = require("./database");
|
|
|
17
17
|
const types_1 = require("./types");
|
|
18
18
|
const ACCESS_TOKEN_PREFIX = 'hnd';
|
|
19
19
|
const DEFAULT_ACCESS_TOKEN_LIFETIME_MS = 365 * 24 * 60 * 60 * 1000;
|
|
20
|
-
|
|
20
|
+
/**
|
|
21
|
+
* Documented scope names that mean the same as ours. The published API vocabulary calls these
|
|
22
|
+
* `sync:*`; they are resolved on the way in and never stored, so rows and responses stay canonical.
|
|
23
|
+
*/
|
|
24
|
+
const SCOPE_ALIASES = {
|
|
25
|
+
'sync:read': types_1.REGISTRY_READ_SCOPE,
|
|
26
|
+
'sync:write': types_1.REGISTRY_WRITE_SCOPE,
|
|
27
|
+
};
|
|
28
|
+
/** Resolve one canonical scope or documented alias; anything else is not a scope we grant. */
|
|
29
|
+
const resolveRegistryScope = (value) => {
|
|
30
|
+
var _a;
|
|
31
|
+
if (value === types_1.REGISTRY_READ_SCOPE || value === types_1.REGISTRY_WRITE_SCOPE)
|
|
32
|
+
return value;
|
|
33
|
+
return typeof value === 'string' ? ((_a = SCOPE_ALIASES[value]) !== null && _a !== void 0 ? _a : null) : null;
|
|
34
|
+
};
|
|
35
|
+
exports.resolveRegistryScope = resolveRegistryScope;
|
|
36
|
+
/** Resolve, dedupe and canonically order a scope list, dropping anything unrecognized. */
|
|
21
37
|
const normalizeRegistryScopes = (scopes) => {
|
|
22
|
-
const unique = new Set(scopes.filter(
|
|
38
|
+
const unique = new Set(scopes.map(exports.resolveRegistryScope).filter((scope) => scope !== null));
|
|
23
39
|
return [types_1.REGISTRY_READ_SCOPE, types_1.REGISTRY_WRITE_SCOPE].filter((scope) => unique.has(scope));
|
|
24
40
|
};
|
|
25
41
|
exports.normalizeRegistryScopes = normalizeRegistryScopes;
|
|
42
|
+
/**
|
|
43
|
+
* Normalize a requested scope list into what a token is issued with: write implies read (a write-only
|
|
44
|
+
* token would be rejected by every read endpoint), and an empty request means read.
|
|
45
|
+
*/
|
|
46
|
+
const expandRegistryScopes = (scopes) => {
|
|
47
|
+
const normalized = (0, exports.normalizeRegistryScopes)(scopes);
|
|
48
|
+
return normalized.includes(types_1.REGISTRY_WRITE_SCOPE) ? [types_1.REGISTRY_READ_SCOPE, types_1.REGISTRY_WRITE_SCOPE] : [types_1.REGISTRY_READ_SCOPE];
|
|
49
|
+
};
|
|
50
|
+
exports.expandRegistryScopes = expandRegistryScopes;
|
|
26
51
|
const registryRoleAllowsScopes = (role, scopes) => role === 'admin' || !scopes.includes(types_1.REGISTRY_WRITE_SCOPE);
|
|
27
52
|
exports.registryRoleAllowsScopes = registryRoleAllowsScopes;
|
|
28
53
|
const formatRegistryAccessToken = (id, secret) => `${ACCESS_TOKEN_PREFIX}_${id}_${secret}`;
|
|
@@ -154,6 +179,7 @@ const authenticateRegistryAccessToken = (db_1, token_1, ...args_1) => __awaiter(
|
|
|
154
179
|
if (!updated.length)
|
|
155
180
|
return null;
|
|
156
181
|
return {
|
|
182
|
+
kind: 'access_token',
|
|
157
183
|
tokenId: row.tokenId,
|
|
158
184
|
userId: row.userId,
|
|
159
185
|
email: row.email,
|
|
@@ -1,11 +1,12 @@
|
|
|
1
|
-
export { createRegistryAccessToken, listRegistryAccessTokens, revokeRegistryAccessToken, revokeAllRegistryAccessTokens, authenticateRegistryAccessToken, formatRegistryAccessToken, parseRegistryAccessToken, normalizeRegistryScopes, registryRoleAllowsScopes, type RegistryAccessTokenSummary, type CreateRegistryAccessTokenResult, } from './access-tokens';
|
|
1
|
+
export { createRegistryAccessToken, listRegistryAccessTokens, revokeRegistryAccessToken, revokeAllRegistryAccessTokens, authenticateRegistryAccessToken, formatRegistryAccessToken, parseRegistryAccessToken, normalizeRegistryScopes, expandRegistryScopes, resolveRegistryScope, registryRoleAllowsScopes, type RegistryAccessTokenSummary, type CreateRegistryAccessTokenResult, } from './access-tokens';
|
|
2
2
|
export { createUserInvitation, resendUserInvitation, acceptUserInvitation, createPasswordReset, resetPassword, INVITATION_LIFETIME_MS, PASSWORD_RESET_LIFETIME_MS, type CreateUserInvitationResult, type ResendUserInvitationResult, type ConsumeAuthActionResult, } from './action-tokens';
|
|
3
3
|
export { createOpaqueSecret, hashSecret, normalizeEmail, secretHashMatches } from './crypto';
|
|
4
4
|
export { buildGravatarUrl } from './gravatar';
|
|
5
|
+
export { authenticateRegistrySyncSecret, resolveRegistrySyncSecret, SYNC_SECRET_ENV } from './sync-secret';
|
|
5
6
|
export { createRegistryDeviceAuthorization, approveRegistryDeviceAuthorization, denyRegistryDeviceAuthorization, exchangeRegistryDeviceAuthorization, purgeExpiredRegistryDeviceAuthorizations, generateRegistryUserCode, normalizeRegistryUserCode, DEVICE_AUTHORIZATION_LIFETIME_SECONDS, DEVICE_AUTHORIZATION_POLL_INTERVAL_SECONDS, type CreateRegistryDeviceAuthorizationResult, type RegistryDeviceApprovalResult, type ExchangeRegistryDeviceAuthorizationResult, } from './device-authorizations';
|
|
6
7
|
export { getRegistryInstallation, getRegistryInstallationState, installRegistry, REGISTRY_AUTH_SCHEMA_VERSION, REGISTRY_INSTALLATION_ID, type RegistryInstallation, type RegistryInstallationState, type RegistryInstallInput, type RegistryInstallResult, } from './installation';
|
|
7
8
|
export { hashPassword, verifyPassword, validatePassword, MINIMUM_PASSWORD_LENGTH, MAXIMUM_PASSWORD_LENGTH, type PasswordValidationResult, } from './password';
|
|
8
9
|
export { consumeAuthRateLimit, clearAuthRateLimit, purgeExpiredAuthRateLimits, hashRateLimitIdentifier, type ConsumeAuthRateLimitInput, type AuthRateLimitResult, } from './rate-limits';
|
|
9
10
|
export { getRegistryUserById, listRegistryUsers, authenticateRegistryCredentials, updateRegistryUserProfile, updateRegistryUserRole, setRegistryUserStatus, type RegistryUserMutationResult, } from './users';
|
|
10
|
-
export { REGISTRY_READ_SCOPE, REGISTRY_WRITE_SCOPE, scopesForRegistryRole, registryPrincipalHasScope, type RegistryAccessScope, type RegistryPrincipal, type RegistryUser, type RegistryUserRole, type RegistryUserStatus, } from './types';
|
|
11
|
-
export { validateRegistryDisplayName, validateRegistryEmail, validateRegistryImageUrl } from './validation';
|
|
11
|
+
export { REGISTRY_READ_SCOPE, REGISTRY_WRITE_SCOPE, scopesForRegistryRole, registryPrincipalHasScope, isRegistryTokenPrincipal, type RegistryAccessScope, type RegistryPrincipal, type RegistryTokenPrincipal, type RegistrySyncSecretPrincipal, type RegistryUser, type RegistryUserRole, type RegistryUserStatus, } from './types';
|
|
12
|
+
export { isTransmittableCredential, validateRegistryDisplayName, validateRegistryEmail, validateRegistryImageUrl } from './validation';
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.
|
|
4
|
-
exports.validateRegistryImageUrl = exports.validateRegistryEmail = exports.validateRegistryDisplayName = exports.registryPrincipalHasScope = exports.scopesForRegistryRole = exports.REGISTRY_WRITE_SCOPE = exports.REGISTRY_READ_SCOPE = void 0;
|
|
3
|
+
exports.getRegistryUserById = exports.hashRateLimitIdentifier = exports.purgeExpiredAuthRateLimits = exports.clearAuthRateLimit = exports.consumeAuthRateLimit = exports.MAXIMUM_PASSWORD_LENGTH = exports.MINIMUM_PASSWORD_LENGTH = exports.validatePassword = exports.verifyPassword = exports.hashPassword = exports.REGISTRY_INSTALLATION_ID = exports.REGISTRY_AUTH_SCHEMA_VERSION = exports.installRegistry = exports.getRegistryInstallationState = exports.getRegistryInstallation = exports.DEVICE_AUTHORIZATION_POLL_INTERVAL_SECONDS = exports.DEVICE_AUTHORIZATION_LIFETIME_SECONDS = exports.normalizeRegistryUserCode = exports.generateRegistryUserCode = exports.purgeExpiredRegistryDeviceAuthorizations = exports.exchangeRegistryDeviceAuthorization = exports.denyRegistryDeviceAuthorization = exports.approveRegistryDeviceAuthorization = exports.createRegistryDeviceAuthorization = exports.SYNC_SECRET_ENV = exports.resolveRegistrySyncSecret = exports.authenticateRegistrySyncSecret = exports.buildGravatarUrl = exports.secretHashMatches = exports.normalizeEmail = exports.hashSecret = exports.createOpaqueSecret = exports.PASSWORD_RESET_LIFETIME_MS = exports.INVITATION_LIFETIME_MS = exports.resetPassword = exports.createPasswordReset = exports.acceptUserInvitation = exports.resendUserInvitation = exports.createUserInvitation = exports.registryRoleAllowsScopes = exports.resolveRegistryScope = exports.expandRegistryScopes = exports.normalizeRegistryScopes = exports.parseRegistryAccessToken = exports.formatRegistryAccessToken = exports.authenticateRegistryAccessToken = exports.revokeAllRegistryAccessTokens = exports.revokeRegistryAccessToken = exports.listRegistryAccessTokens = exports.createRegistryAccessToken = void 0;
|
|
4
|
+
exports.validateRegistryImageUrl = exports.validateRegistryEmail = exports.validateRegistryDisplayName = exports.isTransmittableCredential = exports.isRegistryTokenPrincipal = exports.registryPrincipalHasScope = exports.scopesForRegistryRole = exports.REGISTRY_WRITE_SCOPE = exports.REGISTRY_READ_SCOPE = exports.setRegistryUserStatus = exports.updateRegistryUserRole = exports.updateRegistryUserProfile = exports.authenticateRegistryCredentials = exports.listRegistryUsers = void 0;
|
|
5
5
|
var access_tokens_1 = require("./access-tokens");
|
|
6
6
|
Object.defineProperty(exports, "createRegistryAccessToken", { enumerable: true, get: function () { return access_tokens_1.createRegistryAccessToken; } });
|
|
7
7
|
Object.defineProperty(exports, "listRegistryAccessTokens", { enumerable: true, get: function () { return access_tokens_1.listRegistryAccessTokens; } });
|
|
@@ -11,6 +11,8 @@ Object.defineProperty(exports, "authenticateRegistryAccessToken", { enumerable:
|
|
|
11
11
|
Object.defineProperty(exports, "formatRegistryAccessToken", { enumerable: true, get: function () { return access_tokens_1.formatRegistryAccessToken; } });
|
|
12
12
|
Object.defineProperty(exports, "parseRegistryAccessToken", { enumerable: true, get: function () { return access_tokens_1.parseRegistryAccessToken; } });
|
|
13
13
|
Object.defineProperty(exports, "normalizeRegistryScopes", { enumerable: true, get: function () { return access_tokens_1.normalizeRegistryScopes; } });
|
|
14
|
+
Object.defineProperty(exports, "expandRegistryScopes", { enumerable: true, get: function () { return access_tokens_1.expandRegistryScopes; } });
|
|
15
|
+
Object.defineProperty(exports, "resolveRegistryScope", { enumerable: true, get: function () { return access_tokens_1.resolveRegistryScope; } });
|
|
14
16
|
Object.defineProperty(exports, "registryRoleAllowsScopes", { enumerable: true, get: function () { return access_tokens_1.registryRoleAllowsScopes; } });
|
|
15
17
|
var action_tokens_1 = require("./action-tokens");
|
|
16
18
|
Object.defineProperty(exports, "createUserInvitation", { enumerable: true, get: function () { return action_tokens_1.createUserInvitation; } });
|
|
@@ -27,6 +29,10 @@ Object.defineProperty(exports, "normalizeEmail", { enumerable: true, get: functi
|
|
|
27
29
|
Object.defineProperty(exports, "secretHashMatches", { enumerable: true, get: function () { return crypto_1.secretHashMatches; } });
|
|
28
30
|
var gravatar_1 = require("./gravatar");
|
|
29
31
|
Object.defineProperty(exports, "buildGravatarUrl", { enumerable: true, get: function () { return gravatar_1.buildGravatarUrl; } });
|
|
32
|
+
var sync_secret_1 = require("./sync-secret");
|
|
33
|
+
Object.defineProperty(exports, "authenticateRegistrySyncSecret", { enumerable: true, get: function () { return sync_secret_1.authenticateRegistrySyncSecret; } });
|
|
34
|
+
Object.defineProperty(exports, "resolveRegistrySyncSecret", { enumerable: true, get: function () { return sync_secret_1.resolveRegistrySyncSecret; } });
|
|
35
|
+
Object.defineProperty(exports, "SYNC_SECRET_ENV", { enumerable: true, get: function () { return sync_secret_1.SYNC_SECRET_ENV; } });
|
|
30
36
|
var device_authorizations_1 = require("./device-authorizations");
|
|
31
37
|
Object.defineProperty(exports, "createRegistryDeviceAuthorization", { enumerable: true, get: function () { return device_authorizations_1.createRegistryDeviceAuthorization; } });
|
|
32
38
|
Object.defineProperty(exports, "approveRegistryDeviceAuthorization", { enumerable: true, get: function () { return device_authorizations_1.approveRegistryDeviceAuthorization; } });
|
|
@@ -66,7 +72,9 @@ Object.defineProperty(exports, "REGISTRY_READ_SCOPE", { enumerable: true, get: f
|
|
|
66
72
|
Object.defineProperty(exports, "REGISTRY_WRITE_SCOPE", { enumerable: true, get: function () { return types_1.REGISTRY_WRITE_SCOPE; } });
|
|
67
73
|
Object.defineProperty(exports, "scopesForRegistryRole", { enumerable: true, get: function () { return types_1.scopesForRegistryRole; } });
|
|
68
74
|
Object.defineProperty(exports, "registryPrincipalHasScope", { enumerable: true, get: function () { return types_1.registryPrincipalHasScope; } });
|
|
75
|
+
Object.defineProperty(exports, "isRegistryTokenPrincipal", { enumerable: true, get: function () { return types_1.isRegistryTokenPrincipal; } });
|
|
69
76
|
var validation_1 = require("./validation");
|
|
77
|
+
Object.defineProperty(exports, "isTransmittableCredential", { enumerable: true, get: function () { return validation_1.isTransmittableCredential; } });
|
|
70
78
|
Object.defineProperty(exports, "validateRegistryDisplayName", { enumerable: true, get: function () { return validation_1.validateRegistryDisplayName; } });
|
|
71
79
|
Object.defineProperty(exports, "validateRegistryEmail", { enumerable: true, get: function () { return validation_1.validateRegistryEmail; } });
|
|
72
80
|
Object.defineProperty(exports, "validateRegistryImageUrl", { enumerable: true, get: function () { return validation_1.validateRegistryImageUrl; } });
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { type RegistrySyncSecretPrincipal } from './types';
|
|
2
|
+
/**
|
|
3
|
+
* Deployment-wide sync secret.
|
|
4
|
+
*
|
|
5
|
+
* The documented API describes a raw secret carried as a bearer credential for CI-style sync, which is
|
|
6
|
+
* what this module resolves. It is the credential itself, so presenting it issues no token and creates
|
|
7
|
+
* no session: no owner, no expiry, no usage trail, and unsetting the variable is the only way to
|
|
8
|
+
* withdraw it. User-issued `hnd_*` tokens stay the canonical path, and this is off whenever the
|
|
9
|
+
* variable is absent.
|
|
10
|
+
*/
|
|
11
|
+
/** Env var holding the raw secret. Read per call so unsetting it takes effect on restart alone. */
|
|
12
|
+
export declare const SYNC_SECRET_ENV = "HANDOFF_SYNC_SECRET";
|
|
13
|
+
/**
|
|
14
|
+
* The configured secret, or `''` when this method is unavailable. Never returns a value that could not
|
|
15
|
+
* be presented in every accepted header, or one using the access-token prefix: credentials are routed
|
|
16
|
+
* by shape, so such a secret would be permanently unreachable. Both cases warn instead.
|
|
17
|
+
*/
|
|
18
|
+
export declare const resolveRegistrySyncSecret: (env?: NodeJS.ProcessEnv) => string;
|
|
19
|
+
/**
|
|
20
|
+
* Resolve a raw credential into a sync-secret principal, granting registry read and write.
|
|
21
|
+
*
|
|
22
|
+
* The empty checks have to come first: `hashSecret('')` is a valid digest that matches itself, so
|
|
23
|
+
* hashing up front would authenticate an empty credential on any deployment that leaves
|
|
24
|
+
* {@link SYNC_SECRET_ENV} unset. The comparison runs over fixed-length digests, so it is constant time
|
|
25
|
+
* and leaks no length.
|
|
26
|
+
*/
|
|
27
|
+
export declare const authenticateRegistrySyncSecret: (credential: string, env?: NodeJS.ProcessEnv) => RegistrySyncSecretPrincipal | null;
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.authenticateRegistrySyncSecret = exports.resolveRegistrySyncSecret = exports.SYNC_SECRET_ENV = void 0;
|
|
4
|
+
const crypto_1 = require("./crypto");
|
|
5
|
+
const types_1 = require("./types");
|
|
6
|
+
const validation_1 = require("./validation");
|
|
7
|
+
/**
|
|
8
|
+
* Deployment-wide sync secret.
|
|
9
|
+
*
|
|
10
|
+
* The documented API describes a raw secret carried as a bearer credential for CI-style sync, which is
|
|
11
|
+
* what this module resolves. It is the credential itself, so presenting it issues no token and creates
|
|
12
|
+
* no session: no owner, no expiry, no usage trail, and unsetting the variable is the only way to
|
|
13
|
+
* withdraw it. User-issued `hnd_*` tokens stay the canonical path, and this is off whenever the
|
|
14
|
+
* variable is absent.
|
|
15
|
+
*/
|
|
16
|
+
/** Env var holding the raw secret. Read per call so unsetting it takes effect on restart alone. */
|
|
17
|
+
exports.SYNC_SECRET_ENV = 'HANDOFF_SYNC_SECRET';
|
|
18
|
+
/** Prefix reserved for issued access tokens; see `formatRegistryAccessToken`. */
|
|
19
|
+
const RESERVED_TOKEN_PREFIX = 'hnd_';
|
|
20
|
+
/** Reasons already reported, so a misconfigured deployment warns once rather than once per request. */
|
|
21
|
+
const warned = new Set();
|
|
22
|
+
const warnOnce = (reason, detail) => {
|
|
23
|
+
if (warned.has(reason))
|
|
24
|
+
return;
|
|
25
|
+
warned.add(reason);
|
|
26
|
+
console.warn(`${exports.SYNC_SECRET_ENV} is ignored: ${detail}`);
|
|
27
|
+
};
|
|
28
|
+
/**
|
|
29
|
+
* The configured secret, or `''` when this method is unavailable. Never returns a value that could not
|
|
30
|
+
* be presented in every accepted header, or one using the access-token prefix: credentials are routed
|
|
31
|
+
* by shape, so such a secret would be permanently unreachable. Both cases warn instead.
|
|
32
|
+
*/
|
|
33
|
+
const resolveRegistrySyncSecret = (env = process.env) => {
|
|
34
|
+
var _a, _b;
|
|
35
|
+
const configured = (_b = (_a = env[exports.SYNC_SECRET_ENV]) === null || _a === void 0 ? void 0 : _a.trim()) !== null && _b !== void 0 ? _b : '';
|
|
36
|
+
if (!configured)
|
|
37
|
+
return '';
|
|
38
|
+
if (!(0, validation_1.isTransmittableCredential)(configured)) {
|
|
39
|
+
warnOnce('untransmittable', 'the value must be printable ASCII with no spaces or commas to be sent in a request header.');
|
|
40
|
+
return '';
|
|
41
|
+
}
|
|
42
|
+
if (configured.startsWith(RESERVED_TOKEN_PREFIX)) {
|
|
43
|
+
warnOnce('token_shaped', `the value starts with the \`${RESERVED_TOKEN_PREFIX}\` prefix reserved for issued access tokens.`);
|
|
44
|
+
return '';
|
|
45
|
+
}
|
|
46
|
+
return configured;
|
|
47
|
+
};
|
|
48
|
+
exports.resolveRegistrySyncSecret = resolveRegistrySyncSecret;
|
|
49
|
+
/**
|
|
50
|
+
* Resolve a raw credential into a sync-secret principal, granting registry read and write.
|
|
51
|
+
*
|
|
52
|
+
* The empty checks have to come first: `hashSecret('')` is a valid digest that matches itself, so
|
|
53
|
+
* hashing up front would authenticate an empty credential on any deployment that leaves
|
|
54
|
+
* {@link SYNC_SECRET_ENV} unset. The comparison runs over fixed-length digests, so it is constant time
|
|
55
|
+
* and leaks no length.
|
|
56
|
+
*/
|
|
57
|
+
const authenticateRegistrySyncSecret = (credential, env = process.env) => {
|
|
58
|
+
const configured = (0, exports.resolveRegistrySyncSecret)(env);
|
|
59
|
+
if (!configured || !credential)
|
|
60
|
+
return null;
|
|
61
|
+
if (!(0, crypto_1.secretHashMatches)(credential, (0, crypto_1.hashSecret)(configured)))
|
|
62
|
+
return null;
|
|
63
|
+
return { kind: 'sync_secret', scopes: [types_1.REGISTRY_READ_SCOPE, types_1.REGISTRY_WRITE_SCOPE] };
|
|
64
|
+
};
|
|
65
|
+
exports.authenticateRegistrySyncSecret = authenticateRegistrySyncSecret;
|
|
@@ -14,7 +14,9 @@ export interface RegistryUser {
|
|
|
14
14
|
createdAt: Date;
|
|
15
15
|
updatedAt: Date;
|
|
16
16
|
}
|
|
17
|
-
|
|
17
|
+
/** A request authenticated by a user-issued, revocable `hnd_*` access token. */
|
|
18
|
+
export interface RegistryTokenPrincipal {
|
|
19
|
+
kind: 'access_token';
|
|
18
20
|
tokenId: string;
|
|
19
21
|
userId: string;
|
|
20
22
|
email: string;
|
|
@@ -22,5 +24,22 @@ export interface RegistryPrincipal {
|
|
|
22
24
|
role: RegistryUserRole;
|
|
23
25
|
scopes: RegistryAccessScope[];
|
|
24
26
|
}
|
|
27
|
+
/**
|
|
28
|
+
* A request authenticated by the deployment-wide `HANDOFF_SYNC_SECRET`. The secret is the credential
|
|
29
|
+
* itself, so there is no token row and no owner, only the scopes it grants. Nothing derived from the
|
|
30
|
+
* secret lives here — no hash, no label: response bodies are serialized verbatim, so any field added
|
|
31
|
+
* to this principal reaches a client as written.
|
|
32
|
+
*/
|
|
33
|
+
export interface RegistrySyncSecretPrincipal {
|
|
34
|
+
kind: 'sync_secret';
|
|
35
|
+
scopes: RegistryAccessScope[];
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* Whoever a registry request is acting as. Discriminated on `kind` because identity fields exist only
|
|
39
|
+
* for token principals, and both tsconfigs run without `strictNullChecks`: one interface with nullable
|
|
40
|
+
* fields would let `principal.userId` compile against a secret-authenticated request.
|
|
41
|
+
*/
|
|
42
|
+
export type RegistryPrincipal = RegistryTokenPrincipal | RegistrySyncSecretPrincipal;
|
|
43
|
+
export declare const isRegistryTokenPrincipal: (principal: RegistryPrincipal) => principal is RegistryTokenPrincipal;
|
|
25
44
|
export declare const scopesForRegistryRole: (role: RegistryUserRole) => RegistryAccessScope[];
|
|
26
45
|
export declare const registryPrincipalHasScope: (principal: RegistryPrincipal, scope: RegistryAccessScope) => boolean;
|
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.registryPrincipalHasScope = exports.scopesForRegistryRole = exports.REGISTRY_WRITE_SCOPE = exports.REGISTRY_READ_SCOPE = void 0;
|
|
3
|
+
exports.registryPrincipalHasScope = exports.scopesForRegistryRole = exports.isRegistryTokenPrincipal = exports.REGISTRY_WRITE_SCOPE = exports.REGISTRY_READ_SCOPE = void 0;
|
|
4
4
|
exports.REGISTRY_READ_SCOPE = 'registry:read';
|
|
5
5
|
exports.REGISTRY_WRITE_SCOPE = 'registry:write';
|
|
6
|
+
const isRegistryTokenPrincipal = (principal) => principal.kind === 'access_token';
|
|
7
|
+
exports.isRegistryTokenPrincipal = isRegistryTokenPrincipal;
|
|
6
8
|
const scopesForRegistryRole = (role) => role === 'admin' ? [exports.REGISTRY_READ_SCOPE, exports.REGISTRY_WRITE_SCOPE] : [exports.REGISTRY_READ_SCOPE];
|
|
7
9
|
exports.scopesForRegistryRole = scopesForRegistryRole;
|
|
8
10
|
const registryPrincipalHasScope = (principal, scope) => principal.scopes.includes(scope);
|