handoff-app 2.0.0-5 → 2.0.0-7
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 +174 -41
- package/UPGRADE.md +198 -0
- package/config/config.template.js +1 -4
- package/config/config.template.ts +1 -2
- package/dist/app-builder/build.js +11 -6
- package/dist/app-builder/client-config.js +1 -0
- package/dist/app-builder/config-diff/strategies/pattern.js +3 -3
- package/dist/app-builder/watchers/component.js +5 -5
- package/dist/app-builder/watchers/utils.d.ts +2 -3
- package/dist/app-builder/watchers.d.ts +8 -15
- package/dist/app-builder/watchers.js +53 -54
- package/dist/cache/build-cache.js +18 -22
- package/dist/catalog/define.d.ts +6 -0
- package/dist/catalog/define.js +72 -0
- package/dist/catalog/implementation.d.ts +27 -0
- package/dist/catalog/implementation.js +133 -0
- package/dist/catalog/index.d.ts +6 -0
- package/dist/catalog/index.js +22 -0
- package/dist/catalog/normalize.d.ts +16 -0
- package/dist/catalog/normalize.js +115 -0
- package/dist/catalog/previews.d.ts +18 -0
- package/dist/catalog/previews.js +106 -0
- package/dist/catalog/renderers.d.ts +43 -0
- package/dist/catalog/renderers.js +62 -0
- package/dist/catalog/types.d.ts +63 -0
- package/dist/cli/make.d.ts +0 -5
- package/dist/cli/make.js +22 -67
- package/dist/cli/scaffold.js +25 -33
- package/dist/commands/checkout/index.d.ts +7 -4
- package/dist/commands/checkout/index.js +9 -6
- package/dist/commands/compat/push-args.d.ts +2 -2
- package/dist/commands/index.js +0 -2
- package/dist/commands/publish/index.d.ts +9 -6
- package/dist/commands/publish/index.js +11 -8
- package/dist/commands/pull/index.js +1 -1
- package/dist/commands/push/index.js +1 -1
- package/dist/commands/utils.d.ts +15 -4
- package/dist/commands/utils.js +19 -6
- package/dist/config/catalog-include.d.ts +43 -0
- package/dist/config/catalog-include.js +203 -0
- package/dist/config/defaults.d.ts +2 -0
- package/dist/config/defaults.js +4 -1
- package/dist/config/index.d.ts +3 -3
- package/dist/config/index.js +6 -5
- package/dist/config/loader.js +9 -1
- package/dist/config/runtime.d.ts +5 -6
- package/dist/config/runtime.js +154 -154
- package/dist/handlebars.d.ts +9 -0
- package/dist/handlebars.js +16 -0
- package/dist/index.d.ts +15 -4
- package/dist/index.js +29 -15
- package/dist/pattern.d.ts +7 -0
- package/dist/pattern.js +13 -0
- package/dist/react.d.ts +43 -0
- package/dist/react.js +11 -0
- package/dist/registry/asset-storage/define.d.ts +1 -1
- package/dist/registry/asset-storage/define.js +1 -1
- package/dist/registry/checkout/index.d.ts +9 -0
- package/dist/registry/checkout/index.js +335 -149
- package/dist/registry/db/schema.d.ts +34 -0
- package/dist/registry/db/schema.js +2 -0
- package/dist/registry/publish/index.d.ts +13 -2
- package/dist/registry/publish/index.js +109 -25
- package/dist/registry/publish/package.js +14 -1
- package/dist/registry/selection.d.ts +20 -0
- package/dist/registry/selection.js +37 -1
- package/dist/store/filesystem.d.ts +1 -1
- package/dist/store/filesystem.js +7 -7
- package/dist/store/registry.js +24 -3
- package/dist/transformers/plugins/csf-render.js +4 -2
- package/dist/transformers/plugins/ssr-render.js +14 -5
- package/dist/transformers/preview/component/api.d.ts +8 -0
- package/dist/transformers/preview/component/api.js +11 -15
- package/dist/transformers/preview/component/artifacts.d.ts +1 -1
- package/dist/transformers/preview/component/artifacts.js +11 -7
- package/dist/transformers/preview/component/builder.js +15 -18
- package/dist/transformers/preview/component/css.d.ts +2 -0
- package/dist/transformers/preview/component/css.js +8 -11
- package/dist/transformers/preview/component/html.js +18 -16
- package/dist/transformers/preview/component/javascript.js +2 -4
- package/dist/transformers/preview/pattern/builder.js +31 -18
- package/dist/transformers/preview/types.d.ts +15 -1
- package/dist/types/config.d.ts +36 -11
- package/docs/api.md +3 -0
- package/docs/cli.md +1 -1
- package/drizzle/0006_add_component_source_format.sql +3 -0
- package/drizzle/meta/0006_snapshot.json +1941 -0
- package/drizzle/meta/_journal.json +8 -1
- package/package.json +37 -2
- package/src/app/components/Component/Preview.tsx +36 -50
- package/src/app/components/CopyToClipboard.tsx +27 -26
- package/src/app/components/Layout/Header.tsx +4 -12
- package/src/app/components/Layout/RuntimeModeBadge.tsx +18 -0
- package/src/app/components/Markdown/CodeHighlight.tsx +73 -70
- package/src/app/components/McpIntegration/McpConfigDialog.tsx +349 -0
- package/src/app/components/McpIntegration/McpIcon.tsx +10 -0
- package/src/app/components/Navigation/MobileNav.tsx +3 -1
- package/src/app/components/context/PreviewContext.tsx +5 -5
- package/src/app/components/ui/dialog.tsx +73 -0
- package/src/app/components/util/index.ts +7 -15
- package/src/app/lib/docs-api/backend.ts +5 -1
- package/src/app/lib/docs-api/page-discovery.ts +1 -6
- package/src/app/lib/docs-api/page-rendering.ts +21 -1
- package/src/app/lib/docs-api/records.ts +25 -10
- package/src/app/lib/docs-api/registry.ts +4 -2
- package/src/app/lib/docs-api/runtime-config.ts +5 -0
- package/src/app/lib/docs-api/search.ts +7 -2
- package/src/app/lib/mcp/auth.ts +64 -0
- package/src/app/lib/mcp/index.ts +2 -0
- package/src/app/lib/mcp/server.ts +343 -0
- package/src/app/lib/mcp/shape.ts +596 -0
- package/src/app/lib/registry-api/transfer.ts +7 -5
- package/src/app/next.config.mjs +3 -0
- package/src/app/pages/[...slug]/index.tsx +16 -30
- package/src/app/pages/api/mcp.ts +56 -0
- package/src/app/pages/index.tsx +10 -9
- package/src/app-builder/build.ts +12 -8
- package/src/app-builder/client-config.ts +2 -1
- package/src/app-builder/config-diff/strategies/pattern.ts +1 -1
- package/src/app-builder/watchers/component.ts +3 -3
- package/src/app-builder/watchers/utils.ts +2 -3
- package/src/app-builder/watchers.ts +46 -50
- package/src/cache/build-cache.ts +11 -14
- package/src/catalog/README.md +92 -0
- package/src/catalog/define.ts +79 -0
- package/src/catalog/implementation.ts +145 -0
- package/src/catalog/index.ts +31 -0
- package/src/catalog/normalize.ts +143 -0
- package/src/catalog/previews.ts +111 -0
- package/src/catalog/renderers.ts +94 -0
- package/src/catalog/types.ts +74 -0
- package/src/cli/README.md +1 -1
- package/src/cli/make.ts +20 -72
- package/src/cli/scaffold.ts +25 -31
- package/src/commands/README.md +1 -1
- package/src/commands/checkout/index.ts +17 -7
- package/src/commands/compat/push-args.ts +2 -2
- package/src/commands/index.ts +0 -2
- package/src/commands/publish/index.ts +19 -9
- package/src/commands/pull/index.ts +2 -2
- package/src/commands/push/index.ts +2 -2
- package/src/commands/utils.ts +22 -7
- package/src/config/README.md +2 -1
- package/src/config/catalog-include.ts +227 -0
- package/src/config/defaults.ts +3 -0
- package/src/config/index.ts +4 -4
- package/src/config/loader.ts +12 -0
- package/src/config/runtime.ts +186 -191
- package/src/handlebars.ts +18 -0
- package/src/index.ts +39 -26
- package/src/pattern.ts +14 -0
- package/src/react.ts +52 -0
- package/src/registry/asset-storage/define.ts +1 -1
- package/src/registry/checkout/index.ts +416 -152
- package/src/registry/db/schema.ts +2 -0
- package/src/registry/publish/index.ts +123 -26
- package/src/registry/publish/package.ts +14 -1
- package/src/registry/selection.ts +44 -0
- package/src/store/filesystem.ts +4 -4
- package/src/store/registry.ts +13 -2
- package/src/transformers/plugins/csf-render.ts +4 -2
- package/src/transformers/plugins/ssr-render.ts +15 -5
- package/src/transformers/preview/component/api.ts +13 -1
- package/src/transformers/preview/component/artifacts.ts +8 -4
- package/src/transformers/preview/component/builder.ts +17 -4
- package/src/transformers/preview/component/css.ts +6 -10
- package/src/transformers/preview/component/html.ts +30 -19
- package/src/transformers/preview/component/javascript.ts +1 -3
- package/src/transformers/preview/pattern/builder.ts +33 -20
- package/src/transformers/preview/types.ts +15 -1
- package/src/types/config.ts +38 -11
- package/templates/default/README.md.tpl +10 -0
- package/templates/default/components/button/button.react.handoff.js.tpl +19 -23
- package/templates/default/components/button/button.react.handoff.ts.tpl +22 -22
- package/templates/default/handoff.config.js.tpl +11 -18
- package/templates/default/handoff.config.ts.tpl +11 -18
- package/templates/default/handoff.config.with-examples.js.tpl +12 -16
- package/templates/default/handoff.config.with-examples.ts.tpl +12 -16
- package/config/templates/component/template.json +0 -49
- package/config/templates/template.html +0 -7
- package/dist/commands/make/template.d.ts +0 -8
- package/dist/commands/make/template.js +0 -43
- package/dist/config/entries.d.ts +0 -47
- package/dist/config/entries.js +0 -162
- package/dist/config/normalizers/declaration.d.ts +0 -11
- package/dist/config/normalizers/declaration.js +0 -122
- package/dist/config/normalizers/pattern.d.ts +0 -8
- package/dist/config/normalizers/pattern.js +0 -41
- package/dist/declarations/helpers.d.ts +0 -6
- package/dist/declarations/helpers.js +0 -19
- package/dist/declarations/index.d.ts +0 -2
- package/dist/declarations/index.js +0 -9
- package/dist/declarations/types.d.ts +0 -60
- package/src/commands/make/template.ts +0 -46
- package/src/config/entries.ts +0 -175
- package/src/config/normalizers/declaration.ts +0 -179
- package/src/config/normalizers/pattern.ts +0 -59
- package/src/declarations/helpers.ts +0 -36
- package/src/declarations/index.ts +0 -19
- package/src/declarations/types.ts +0 -70
- /package/dist/{declarations → catalog}/types.js +0 -0
|
@@ -60,11 +60,16 @@ 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.checkoutEntities = exports.checkoutEntity = exports.reportPlannedWrites = exports.resolveConnectionOrThrow = exports.CheckoutError = void 0;
|
|
63
|
+
exports.checkoutCatalog = exports.checkoutEntities = exports.checkoutEntity = exports.reportPlannedWrites = 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
|
+
const esbuild_1 = require("esbuild");
|
|
67
|
+
const lodash_1 = require("lodash");
|
|
66
68
|
const path_1 = __importDefault(require("path"));
|
|
67
|
-
const
|
|
69
|
+
const previews_1 = require("../../catalog/previews");
|
|
70
|
+
const define_1 = require("../../catalog/define");
|
|
71
|
+
const renderers_1 = require("../../catalog/renderers");
|
|
72
|
+
const catalog_include_1 = require("../../config/catalog-include");
|
|
68
73
|
const runtime_1 = require("../../config/runtime");
|
|
69
74
|
const logger_1 = require("../../utils/logger");
|
|
70
75
|
const client_1 = require("../client");
|
|
@@ -136,9 +141,9 @@ const describeFetchFailure = (error, kind, id, registryUrl) => {
|
|
|
136
141
|
const promptForCollectionRoot = (handoff, kind, roots) => __awaiter(void 0, void 0, void 0, function* () {
|
|
137
142
|
const relative = (root) => path_1.default.relative(handoff.workingPath, root) || '.';
|
|
138
143
|
if (handoff.force) {
|
|
139
|
-
throw new CheckoutError(`Cannot determine where to checkout the ${kind}:
|
|
140
|
-
`
|
|
141
|
-
`or run checkout without --force to choose interactively.`);
|
|
144
|
+
throw new CheckoutError(`Cannot determine where to checkout the ${kind}: catalog.include declares directories under different ` +
|
|
145
|
+
`parents (${roots.map(relative).join(', ')}), and none is named "${DEFAULT_ENTITY_DIR[kind]}". Declare a single ` +
|
|
146
|
+
`collection directory in handoff.config, or run checkout without --force to choose interactively.`);
|
|
142
147
|
}
|
|
143
148
|
const choice = yield p.select({
|
|
144
149
|
message: `Where should the new ${kind} be checked out?`,
|
|
@@ -152,14 +157,15 @@ const promptForCollectionRoot = (handoff, kind, roots) => __awaiter(void 0, void
|
|
|
152
157
|
});
|
|
153
158
|
/**
|
|
154
159
|
* Resolve the collection directory a new entity is cloned into from the configured
|
|
155
|
-
* `
|
|
160
|
+
* `catalog.include`. Each entry is either a collection directory (used as-is) or a
|
|
156
161
|
* single declared entity directory, in which case its parent is the collection root so the new
|
|
157
|
-
* entity lands as a sibling rather than nested inside. Different parents
|
|
158
|
-
*
|
|
162
|
+
* entity lands as a sibling rather than nested inside. Different parents are settled by the
|
|
163
|
+
* conventional directory name for the kind, and only then by asking the user. With nothing
|
|
164
|
+
* configured we fall back to the default subdir.
|
|
159
165
|
*/
|
|
160
166
|
const resolveCollectionRoot = (handoff, kind) => __awaiter(void 0, void 0, void 0, function* () {
|
|
161
|
-
var _a, _b
|
|
162
|
-
const configuredRoots =
|
|
167
|
+
var _a, _b;
|
|
168
|
+
const configuredRoots = (_b = (_a = handoff.config) === null || _a === void 0 ? void 0 : _a.catalog) === null || _b === void 0 ? void 0 : _b.include;
|
|
163
169
|
if (!(configuredRoots === null || configuredRoots === void 0 ? void 0 : configuredRoots.length)) {
|
|
164
170
|
return path_1.default.resolve(handoff.workingPath, DEFAULT_ENTITY_DIR[kind]);
|
|
165
171
|
}
|
|
@@ -169,7 +175,17 @@ const resolveCollectionRoot = (handoff, kind) => __awaiter(void 0, void 0, void
|
|
|
169
175
|
return (0, runtime_1.isComponentDirectory)(resolved) ? path_1.default.dirname(resolved) : resolved;
|
|
170
176
|
})),
|
|
171
177
|
];
|
|
172
|
-
|
|
178
|
+
if (roots.length === 1) {
|
|
179
|
+
return roots[0];
|
|
180
|
+
}
|
|
181
|
+
// `catalog.include` is one list for both lanes, so a project that registers `components` and
|
|
182
|
+
// `patterns` offers two roots for every checkout. Without this, every such project prompts, and a
|
|
183
|
+
// non-interactive `--force` run fails.
|
|
184
|
+
const conventional = roots.filter((root) => path_1.default.basename(root) === DEFAULT_ENTITY_DIR[kind]);
|
|
185
|
+
if (conventional.length === 1) {
|
|
186
|
+
return conventional[0];
|
|
187
|
+
}
|
|
188
|
+
return promptForCollectionRoot(handoff, kind, roots);
|
|
173
189
|
});
|
|
174
190
|
/**
|
|
175
191
|
* Resolve the local directory the entity is written into. An already-checked-out or locally-declared
|
|
@@ -200,7 +216,7 @@ const existingDeclarationFormat = (dir) => {
|
|
|
200
216
|
return undefined;
|
|
201
217
|
}
|
|
202
218
|
const ext = path_1.default.extname(declaration.fileName).slice(1).toLowerCase();
|
|
203
|
-
return ['ts', 'js', 'cjs'
|
|
219
|
+
return ['ts', 'js', 'cjs'].includes(ext) ? ext : undefined;
|
|
204
220
|
};
|
|
205
221
|
/**
|
|
206
222
|
* Resolve the declaration format: an existing local declaration's extension wins, else the
|
|
@@ -222,41 +238,25 @@ const resolveDeclarationFileName = (dir, id, format) => {
|
|
|
222
238
|
return `${id}.handoff.${format}`;
|
|
223
239
|
};
|
|
224
240
|
const isPlainObject = (value) => typeof value === 'object' && value !== null && !Array.isArray(value);
|
|
225
|
-
/**
|
|
226
|
-
* The single authored "primary" entry key per renderer. The normalizer derives the others (e.g. it
|
|
227
|
-
* mirrors a React `component` into `template`, and a CSF `story` into `template`), so the stored
|
|
228
|
-
* record carries duplicates that must not be re-authored. React authors `component`; handlebars and
|
|
229
|
-
* CSF author `template` (a `.stories.*` template is what marks a CSF component).
|
|
230
|
-
*/
|
|
231
|
-
const PRIMARY_ENTRY_BY_RENDERER = {
|
|
232
|
-
react: 'component',
|
|
233
|
-
handlebars: 'template',
|
|
234
|
-
csf: 'template',
|
|
235
|
-
};
|
|
236
|
-
/** Primary entry keys, in preference order, for an unknown renderer. */
|
|
237
|
-
const PRIMARY_ENTRY_KEYS = ['component', 'template', 'story'];
|
|
238
241
|
/** Supporting entry keys kept verbatim regardless of renderer. */
|
|
239
242
|
const SUPPORTING_ENTRY_KEYS = ['js', 'scss', 'schema', 'templates'];
|
|
240
243
|
const asString = (value) => (typeof value === 'string' && value ? value : undefined);
|
|
241
244
|
const asStringArray = (value) => Array.isArray(value) && value.every((item) => typeof item === 'string') ? value : undefined;
|
|
242
245
|
/**
|
|
243
|
-
*
|
|
244
|
-
*
|
|
245
|
-
* `templates`). An unknown renderer keeps the first available primary key by preference order.
|
|
246
|
+
* Normalization mirrors React `component` and CSF `story` entries into `template`. Checkout omits these duplicates
|
|
247
|
+
* and preserves supporting entries (`js`/`scss`/`schema`/`templates`).
|
|
246
248
|
*/
|
|
247
|
-
const buildEntries = (entries,
|
|
249
|
+
const buildEntries = (entries, source, options = {}) => {
|
|
248
250
|
if (!isPlainObject(entries)) {
|
|
249
251
|
return undefined;
|
|
250
252
|
}
|
|
251
253
|
const result = {};
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
if (fallback) {
|
|
259
|
-
result[fallback] = entries[fallback];
|
|
254
|
+
// A catalog item names its implementation through `implementation`, so re-emitting the primary
|
|
255
|
+
// entry key would state the same fact twice.
|
|
256
|
+
const primaryKey = (0, renderers_1.entryKeyFor)(source.renderer, source.sourceFormat);
|
|
257
|
+
if (options.includePrimary !== false) {
|
|
258
|
+
if (primaryKey && typeof entries[primaryKey] === 'string') {
|
|
259
|
+
result[primaryKey] = entries[primaryKey];
|
|
260
260
|
}
|
|
261
261
|
}
|
|
262
262
|
for (const key of SUPPORTING_ENTRY_KEYS) {
|
|
@@ -267,9 +267,9 @@ const buildEntries = (entries, renderer) => {
|
|
|
267
267
|
return Object.keys(result).length > 0 ? result : undefined;
|
|
268
268
|
};
|
|
269
269
|
/**
|
|
270
|
-
* Rebuild the authored
|
|
270
|
+
* Rebuild the authored preview map from the stored previews, keeping only
|
|
271
271
|
* authored fields (`title`, `args`) and dropping build-derived ones (rendered `url`, `usage`,
|
|
272
|
-
* `sourcePreview`).
|
|
272
|
+
* `sourcePreview`). Catalog normalization maps authored `args` to `values`, so the stored record
|
|
273
273
|
* carries `values`; checkout re-emits it as `args` to match the authoring convention (the form
|
|
274
274
|
* `make`/`scaffold` generate). Both rebuild identically.
|
|
275
275
|
*/
|
|
@@ -285,7 +285,7 @@ const buildPreviews = (previews) => {
|
|
|
285
285
|
const preview = {};
|
|
286
286
|
if (typeof raw.title === 'string')
|
|
287
287
|
preview.title = raw.title;
|
|
288
|
-
const args = raw.
|
|
288
|
+
const args = raw.values;
|
|
289
289
|
if (args !== undefined)
|
|
290
290
|
preview.args = args;
|
|
291
291
|
result[key] = preview;
|
|
@@ -293,7 +293,7 @@ const buildPreviews = (previews) => {
|
|
|
293
293
|
return Object.keys(result).length > 0 ? result : undefined;
|
|
294
294
|
};
|
|
295
295
|
/**
|
|
296
|
-
* Synthesize an authored component declaration
|
|
296
|
+
* Synthesize an authored component declaration from the normalized
|
|
297
297
|
* record. Only authored fields are kept; registry-/build-derived data (`path`, `image`,
|
|
298
298
|
* `properties`/docgen, rendered preview URLs, `internalPatternPreviews`, `validations`, `variant`,
|
|
299
299
|
* review `metadata`) is dropped so the local build regenerates it. `name` carries the record title
|
|
@@ -301,7 +301,7 @@ const buildPreviews = (previews) => {
|
|
|
301
301
|
* `shouldNotDo` are emitted in their camelCase authored form.
|
|
302
302
|
*/
|
|
303
303
|
const buildComponentDeclaration = (item) => {
|
|
304
|
-
var _a, _b, _c, _d
|
|
304
|
+
var _a, _b, _c, _d;
|
|
305
305
|
const declaration = {};
|
|
306
306
|
const id = asString(item.id);
|
|
307
307
|
if (id)
|
|
@@ -316,13 +316,21 @@ const buildComponentDeclaration = (item) => {
|
|
|
316
316
|
const type = asString(item.type);
|
|
317
317
|
if (type)
|
|
318
318
|
declaration.type = type;
|
|
319
|
-
const
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
319
|
+
const source = {
|
|
320
|
+
renderer: item.renderer,
|
|
321
|
+
sourceFormat: item.sourceFormat,
|
|
322
|
+
};
|
|
323
|
+
if (source.renderer)
|
|
324
|
+
declaration.renderer = source.renderer;
|
|
325
|
+
if (source.sourceFormat)
|
|
326
|
+
declaration.sourceFormat = source.sourceFormat;
|
|
327
|
+
const componentExport = asString(item.componentExport);
|
|
328
|
+
if (componentExport)
|
|
329
|
+
declaration.componentExport = componentExport;
|
|
330
|
+
const entries = buildEntries(item.entries, source);
|
|
323
331
|
if (entries)
|
|
324
332
|
declaration.entries = entries;
|
|
325
|
-
const previews = buildPreviews(item.previews);
|
|
333
|
+
const previews = buildPreviews(isPlainObject(item.previews) ? (0, previews_1.orderPreviews)(item.previews, (_d = asStringArray(item.previewOrder)) !== null && _d !== void 0 ? _d : []) : undefined);
|
|
326
334
|
if (previews)
|
|
327
335
|
declaration.previews = previews;
|
|
328
336
|
const categories = asStringArray(item.categories);
|
|
@@ -331,10 +339,10 @@ const buildComponentDeclaration = (item) => {
|
|
|
331
339
|
const tags = asStringArray(item.tags);
|
|
332
340
|
if (tags)
|
|
333
341
|
declaration.tags = tags;
|
|
334
|
-
const shouldDo =
|
|
342
|
+
const shouldDo = asStringArray(item.should_do);
|
|
335
343
|
if (shouldDo)
|
|
336
344
|
declaration.shouldDo = shouldDo;
|
|
337
|
-
const shouldNotDo =
|
|
345
|
+
const shouldNotDo = asStringArray(item.should_not_do);
|
|
338
346
|
if (shouldNotDo)
|
|
339
347
|
declaration.shouldNotDo = shouldNotDo;
|
|
340
348
|
const figma = asString(item.figma);
|
|
@@ -350,9 +358,9 @@ const buildComponentDeclaration = (item) => {
|
|
|
350
358
|
return declaration;
|
|
351
359
|
};
|
|
352
360
|
/**
|
|
353
|
-
* Rebuild the authored pattern component refs
|
|
361
|
+
* Rebuild the authored pattern component refs from the stored entries,
|
|
354
362
|
* keeping only `id`/`preview`/`args` and dropping build-time resolution fields (`resolvedPreview`,
|
|
355
|
-
* `resolved`).
|
|
363
|
+
* `resolved`, `unresolvedReason`).
|
|
356
364
|
*/
|
|
357
365
|
const buildPatternComponents = (components) => {
|
|
358
366
|
if (!Array.isArray(components)) {
|
|
@@ -372,7 +380,7 @@ const buildPatternComponents = (components) => {
|
|
|
372
380
|
});
|
|
373
381
|
};
|
|
374
382
|
/**
|
|
375
|
-
* Synthesize an authored pattern declaration
|
|
383
|
+
* Synthesize an authored pattern declaration from the normalized
|
|
376
384
|
* record. Only the authored fields (`id`, `name`, `description`, `group`, `tags`, `components`) are
|
|
377
385
|
* kept; derived fields (`path`, rendered `url`, review `metadata`) are dropped.
|
|
378
386
|
*/
|
|
@@ -395,35 +403,25 @@ const buildPatternDeclaration = (item) => {
|
|
|
395
403
|
declaration.components = buildPatternComponents(item.components);
|
|
396
404
|
return declaration;
|
|
397
405
|
};
|
|
398
|
-
/**
|
|
399
|
-
* Pick the renderer-specific `handoff-app` component factory from the record's renderer so the
|
|
400
|
-
* synthesized declaration matches the authored contract for that renderer. `react`/`handlebars`/`csf`
|
|
401
|
-
* each have a dedicated factory that stamps the renderer itself (so the renderer is dropped from the
|
|
402
|
-
* authored config); an unknown/absent renderer falls back to the generic `defineComponent`, which
|
|
403
|
-
* keeps `renderer` in the config.
|
|
404
|
-
*/
|
|
405
|
-
const resolveComponentFactory = (renderer) => {
|
|
406
|
-
switch (renderer) {
|
|
407
|
-
case 'react':
|
|
408
|
-
return { name: 'defineReactComponent', stampsRenderer: true };
|
|
409
|
-
case 'handlebars':
|
|
410
|
-
return { name: 'defineHandlebarsComponent', stampsRenderer: true };
|
|
411
|
-
case 'csf':
|
|
412
|
-
return { name: 'defineCsfComponent', stampsRenderer: true };
|
|
413
|
-
default:
|
|
414
|
-
return { name: 'defineComponent', stampsRenderer: false };
|
|
415
|
-
}
|
|
416
|
-
};
|
|
417
406
|
/** Drop a key from an object without mutating it. */
|
|
418
407
|
const omit = (object, key) => {
|
|
419
408
|
const _a = object, _b = key, _removed = _a[_b], rest = __rest(_a, [typeof _b === "symbol" ? _b : _b + ""]);
|
|
420
409
|
return rest;
|
|
421
410
|
};
|
|
411
|
+
const omitSource = (object) => omit(omit(object, 'renderer'), 'sourceFormat');
|
|
422
412
|
/** Turn a relative entry path into a module specifier (extension stripped, `./`-prefixed). */
|
|
423
413
|
const toImportSpecifier = (entryPath) => {
|
|
424
414
|
const withoutExt = entryPath.replace(COMPONENT_EXTENSION, '').split(path_1.default.sep).join('/');
|
|
425
415
|
return withoutExt.startsWith('.') ? withoutExt : `./${withoutExt}`;
|
|
426
416
|
};
|
|
417
|
+
/**
|
|
418
|
+
* Turn a relative entry path into a `./`-prefixed file path. Unlike a module specifier this keeps
|
|
419
|
+
* the extension, because `fromCSF('./Card.stories.tsx')` and a `.hbs` implementation name files.
|
|
420
|
+
*/
|
|
421
|
+
const toFilePath = (entryPath) => {
|
|
422
|
+
const normalized = entryPath.split(path_1.default.sep).join('/');
|
|
423
|
+
return normalized.startsWith('.') ? normalized : `./${normalized}`;
|
|
424
|
+
};
|
|
427
425
|
/** Derive a safe PascalCase identifier for the imported React component. */
|
|
428
426
|
const toComponentIdentifier = (entryPath, fallback) => {
|
|
429
427
|
const base = path_1.default.basename(entryPath).replace(COMPONENT_EXTENSION, '');
|
|
@@ -438,6 +436,12 @@ const toComponentIdentifier = (entryPath, fallback) => {
|
|
|
438
436
|
};
|
|
439
437
|
/** Keys matching this are emitted unquoted in JS/TS object literals; others are single-quoted. */
|
|
440
438
|
const JS_IDENTIFIER = /^[A-Za-z_$][A-Za-z0-9_$]*$/;
|
|
439
|
+
/** A value emitted verbatim into a literal, such as an imported identifier or a `fromCSF(...)` call. */
|
|
440
|
+
class RawExpression {
|
|
441
|
+
constructor(code) {
|
|
442
|
+
this.code = code;
|
|
443
|
+
}
|
|
444
|
+
}
|
|
441
445
|
/** Serialize a string as a single-quoted JS literal (repo style), escaping safely. */
|
|
442
446
|
const singleQuote = (value) => {
|
|
443
447
|
var _a;
|
|
@@ -470,6 +474,9 @@ const singleQuote = (value) => {
|
|
|
470
474
|
const toJsLiteral = (value, indentLevel = 0) => {
|
|
471
475
|
const pad = ' '.repeat(indentLevel);
|
|
472
476
|
const padInner = ' '.repeat(indentLevel + 1);
|
|
477
|
+
if (value instanceof RawExpression) {
|
|
478
|
+
return value.code;
|
|
479
|
+
}
|
|
473
480
|
if (value === null) {
|
|
474
481
|
return 'null';
|
|
475
482
|
}
|
|
@@ -496,53 +503,170 @@ const toJsLiteral = (value, indentLevel = 0) => {
|
|
|
496
503
|
});
|
|
497
504
|
return `{\n${lines.join(',\n')}\n${pad}}`;
|
|
498
505
|
};
|
|
499
|
-
/** Wrap a config object in a factory call for the given code format (unquoted identifier keys). */
|
|
500
|
-
const wrapFactory = (factory, config, format) => {
|
|
501
|
-
const literal = toJsLiteral(config);
|
|
502
|
-
return format === 'ts'
|
|
503
|
-
? `import { ${factory} } from 'handoff-app';\n\nexport default ${factory}(${literal});\n`
|
|
504
|
-
: `const { ${factory} } = require('handoff-app');\n\nmodule.exports = ${factory}(${literal});\n`;
|
|
505
|
-
};
|
|
506
506
|
/**
|
|
507
|
-
*
|
|
508
|
-
*
|
|
509
|
-
* factory; React additionally imports the component from its `entries.component` source and passes it
|
|
510
|
-
* as the factory's first argument (matching the authored `defineReactComponent` contract). When the
|
|
511
|
-
* renderer is `react` but no component entry is available, it falls back to the generic factory.
|
|
507
|
+
* Re-shape stored previews for the catalog API: the display title becomes `name`, and is omitted
|
|
508
|
+
* when it matches what `startCase` derives from the export name.
|
|
512
509
|
*/
|
|
513
|
-
const
|
|
510
|
+
const toCatalogPreviews = (previews) => {
|
|
511
|
+
if (!previews)
|
|
512
|
+
return undefined;
|
|
513
|
+
const result = {};
|
|
514
|
+
for (const [key, raw] of Object.entries(previews)) {
|
|
515
|
+
if (!isPlainObject(raw))
|
|
516
|
+
continue;
|
|
517
|
+
const preview = {};
|
|
518
|
+
const title = asString(raw.title);
|
|
519
|
+
if (title && title !== (0, lodash_1.startCase)(key))
|
|
520
|
+
preview.name = title;
|
|
521
|
+
if (raw.args !== undefined)
|
|
522
|
+
preview.args = raw.args;
|
|
523
|
+
result[key] = preview;
|
|
524
|
+
}
|
|
525
|
+
return result;
|
|
526
|
+
};
|
|
527
|
+
const previewKeysAreIdentifiers = (previews) => !previews || Object.keys(previews).every((key) => JS_IDENTIFIER.test(key));
|
|
528
|
+
const withImplementation = (item, implementation) => {
|
|
529
|
+
const ordered = {};
|
|
530
|
+
const placed = new Set(['implementation']);
|
|
531
|
+
for (const key of ['id', 'name', 'description', 'group']) {
|
|
532
|
+
if (item[key] !== undefined) {
|
|
533
|
+
ordered[key] = item[key];
|
|
534
|
+
placed.add(key);
|
|
535
|
+
}
|
|
536
|
+
}
|
|
537
|
+
ordered.implementation = implementation;
|
|
538
|
+
for (const [key, value] of Object.entries(item)) {
|
|
539
|
+
if (!placed.has(key))
|
|
540
|
+
ordered[key] = value;
|
|
541
|
+
}
|
|
542
|
+
return ordered;
|
|
543
|
+
};
|
|
544
|
+
const buildImplementationImport = (declaration, componentEntry, format) => {
|
|
514
545
|
var _a;
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
546
|
+
const specifier = toImportSpecifier(componentEntry);
|
|
547
|
+
const exportName = asString(declaration.componentExport);
|
|
548
|
+
const identifier = exportName !== null && exportName !== void 0 ? exportName : toComponentIdentifier(componentEntry, (_a = asString(declaration.id)) !== null && _a !== void 0 ? _a : 'Component');
|
|
549
|
+
if (format === 'ts') {
|
|
550
|
+
const clause = exportName ? `{ ${exportName} }` : identifier;
|
|
551
|
+
return { statement: `import ${clause} from '${specifier}';`, identifier };
|
|
552
|
+
}
|
|
553
|
+
const accessor = exportName ? `.${exportName}` : '.default';
|
|
554
|
+
return { statement: `const ${identifier} = require('${specifier}')${accessor};`, identifier };
|
|
555
|
+
};
|
|
556
|
+
const buildPreviewExports = (previews, format, previewType) => {
|
|
557
|
+
if (!previews || Object.keys(previews).length === 0) {
|
|
558
|
+
return '';
|
|
559
|
+
}
|
|
560
|
+
const statements = Object.entries(previews).map(([key, preview]) => {
|
|
561
|
+
const literal = toJsLiteral(preview);
|
|
525
562
|
return format === 'ts'
|
|
526
|
-
? `
|
|
527
|
-
: `
|
|
528
|
-
}
|
|
529
|
-
|
|
530
|
-
// the generic factory so the build still infers the renderer.
|
|
531
|
-
const effective = factory.name === 'defineReactComponent' ? { name: 'defineComponent', stampsRenderer: false } : factory;
|
|
532
|
-
const config = effective.stampsRenderer ? omit(declaration, 'renderer') : declaration;
|
|
533
|
-
return wrapFactory(effective.name, config, format);
|
|
563
|
+
? `export const ${key} = ${literal}${previewType ? ` satisfies ${previewType}` : ''};`
|
|
564
|
+
: `exports.${key} = ${literal};`;
|
|
565
|
+
});
|
|
566
|
+
return `\n${statements.join('\n\n')}\n`;
|
|
534
567
|
};
|
|
535
|
-
|
|
536
|
-
const
|
|
537
|
-
|
|
538
|
-
|
|
568
|
+
const renderCatalogDeclaration = (options) => {
|
|
569
|
+
const { format, entryPoint, imports, named, item, previews } = options;
|
|
570
|
+
const literal = toJsLiteral(item);
|
|
571
|
+
const hasPreviews = !!previews && Object.keys(previews).length > 0;
|
|
572
|
+
if (format === 'ts') {
|
|
573
|
+
const typeImport = hasPreviews ? [...named, 'type Preview'] : named;
|
|
574
|
+
const head = [`import { ${typeImport.join(', ')} } from '${entryPoint}';`, ...imports].join('\n');
|
|
575
|
+
if (!hasPreviews) {
|
|
576
|
+
return `${head}\n\nexport default defineCatalogItem(${literal});\n`;
|
|
577
|
+
}
|
|
578
|
+
return (`${head}\n\nconst item = defineCatalogItem(${literal});\n\nexport default item;\n\n` +
|
|
579
|
+
`type ItemPreview = Preview<typeof item>;\n${buildPreviewExports(previews, format, 'ItemPreview')}`);
|
|
539
580
|
}
|
|
540
|
-
|
|
581
|
+
const head = [`const { ${named.join(', ')} } = require('${entryPoint}');`, ...imports].join('\n');
|
|
582
|
+
return `${head}\n\nexports.default = defineCatalogItem(${literal});\n${buildPreviewExports(previews, format)}`;
|
|
583
|
+
};
|
|
584
|
+
/**
|
|
585
|
+
* How each renderer authors its implementation. A source format claims an item before its renderer,
|
|
586
|
+
* because a CSF item is also a React item and the React row would claim it first. The maps are
|
|
587
|
+
* exhaustive, so a new renderer fails to compile until checkout can write its declaration.
|
|
588
|
+
*/
|
|
589
|
+
const FORMAT_DECLARATIONS = {
|
|
590
|
+
// The story file owns the previews, so the declaration carries none.
|
|
591
|
+
csf: ({ entry }) => ({
|
|
592
|
+
entryPoint: (0, renderers_1.moduleFor)('react'),
|
|
593
|
+
imports: [],
|
|
594
|
+
named: ['defineCatalogItem', 'fromCSF'],
|
|
595
|
+
implementation: new RawExpression(`fromCSF('${toFilePath(entry)}')`),
|
|
596
|
+
}),
|
|
597
|
+
};
|
|
598
|
+
const RENDERER_DECLARATIONS = {
|
|
599
|
+
handlebars: ({ entry, previews }) => ({
|
|
600
|
+
entryPoint: (0, renderers_1.moduleFor)('handlebars'),
|
|
601
|
+
imports: [],
|
|
602
|
+
named: ['defineCatalogItem'],
|
|
603
|
+
implementation: toFilePath(entry),
|
|
604
|
+
previews: toCatalogPreviews(previews),
|
|
605
|
+
}),
|
|
606
|
+
react: ({ declaration, entry, format, previews }) => {
|
|
607
|
+
const { statement, identifier } = buildImplementationImport(declaration, entry, format);
|
|
608
|
+
return {
|
|
609
|
+
entryPoint: (0, renderers_1.moduleFor)('react'),
|
|
610
|
+
imports: [statement],
|
|
611
|
+
named: ['defineCatalogItem'],
|
|
612
|
+
implementation: new RawExpression(identifier),
|
|
613
|
+
previews: toCatalogPreviews(previews),
|
|
614
|
+
};
|
|
615
|
+
},
|
|
616
|
+
};
|
|
617
|
+
/** Emit only catalog declarations, with previews as named exports. */
|
|
618
|
+
const renderComponentDeclaration = (declaration, format) => {
|
|
619
|
+
const source = {
|
|
620
|
+
renderer: declaration.renderer,
|
|
621
|
+
sourceFormat: declaration.sourceFormat,
|
|
622
|
+
};
|
|
623
|
+
const { renderer, sourceFormat } = source;
|
|
624
|
+
const previews = isPlainObject(declaration.previews) ? declaration.previews : undefined;
|
|
625
|
+
const entries = isPlainObject(declaration.entries) ? declaration.entries : undefined;
|
|
626
|
+
if (!(0, renderers_1.isRendererKind)(renderer) ||
|
|
627
|
+
(sourceFormat !== undefined && (!(0, renderers_1.isSourceFormat)(sourceFormat) || !renderers_1.SOURCE_FORMATS[sourceFormat].renderers.includes(renderer)))) {
|
|
628
|
+
throw new CheckoutError(`Catalog item "${declaration.id}" has invalid renderer/source-format metadata. Rebuild and republish it.`);
|
|
629
|
+
}
|
|
630
|
+
if (!previewKeysAreIdentifiers(previews) && sourceFormat !== 'csf') {
|
|
631
|
+
throw new CheckoutError(`Catalog item "${declaration.id}" has preview names that cannot be exported. Rename them and update composition references before publishing.`);
|
|
632
|
+
}
|
|
633
|
+
const item = omit(omit(omitSource(declaration), 'previews'), 'componentExport');
|
|
634
|
+
const supportingEntries = buildEntries(declaration.entries, source, { includePrimary: false });
|
|
635
|
+
if (supportingEntries)
|
|
636
|
+
item.entries = supportingEntries;
|
|
637
|
+
else
|
|
638
|
+
delete item.entries;
|
|
639
|
+
const entry = entries ? asString(entries[(0, renderers_1.entryKeyFor)(renderer, sourceFormat)]) : undefined;
|
|
640
|
+
if (!entry) {
|
|
641
|
+
throw new CheckoutError(`Catalog item "${declaration.id}" has no valid implementation source. Rebuild and republish it.`);
|
|
642
|
+
}
|
|
643
|
+
const build = sourceFormat ? FORMAT_DECLARATIONS[sourceFormat] : RENDERER_DECLARATIONS[renderer];
|
|
644
|
+
const _a = build({ declaration, entry, format, previews }), { implementation } = _a, parts = __rest(_a, ["implementation"]);
|
|
645
|
+
return renderCatalogDeclaration(Object.assign(Object.assign({ format }, parts), { item: withImplementation(item, implementation) }));
|
|
646
|
+
};
|
|
647
|
+
/** Synthesize the composition declaration file via `defineCatalogItem`. */
|
|
648
|
+
const renderPatternDeclaration = (declaration, format) => {
|
|
649
|
+
const { components } = declaration, rest = __rest(declaration, ["components"]);
|
|
650
|
+
const item = Object.assign(Object.assign({}, rest), { composition: (Array.isArray(components) ? components : []).map((ref) => {
|
|
651
|
+
const _a = ref, { id } = _a, refRest = __rest(_a, ["id"]);
|
|
652
|
+
return Object.assign({ ref: id }, refRest);
|
|
653
|
+
}) });
|
|
654
|
+
(0, define_1.validateCatalogItem)(item);
|
|
655
|
+
return renderCatalogDeclaration({ format, entryPoint: 'handoff-app/pattern', imports: [], named: ['defineCatalogItem'], item });
|
|
541
656
|
};
|
|
542
657
|
/** Synthesize the declaration file contents for an entity, faithful to its renderer/kind contract. */
|
|
543
|
-
const synthesizeDeclaration = (kind, item, format) =>
|
|
544
|
-
|
|
545
|
-
|
|
658
|
+
const synthesizeDeclaration = (kind, item, format) => {
|
|
659
|
+
const declaration = kind === 'component'
|
|
660
|
+
? renderComponentDeclaration(buildComponentDeclaration(item), format)
|
|
661
|
+
: renderPatternDeclaration(buildPatternDeclaration(item), format);
|
|
662
|
+
try {
|
|
663
|
+
(0, esbuild_1.transformSync)(declaration, { loader: format === 'ts' ? 'ts' : 'js', logLevel: 'silent' });
|
|
664
|
+
}
|
|
665
|
+
catch (_a) {
|
|
666
|
+
throw new CheckoutError(`Catalog item "${item.id}" cannot be exported as ${format}. Use valid, distinct preview and component export names, then republish.`);
|
|
667
|
+
}
|
|
668
|
+
return declaration;
|
|
669
|
+
};
|
|
546
670
|
/**
|
|
547
671
|
* Confirm overwriting any local files the checkout would replace. Returns `true` to proceed. With
|
|
548
672
|
* `--force` (or when nothing would be overwritten) it proceeds without prompting; otherwise it asks
|
|
@@ -636,7 +760,7 @@ const checkoutSingle = (handoff, kind, id, client, registryUrl) => __awaiter(voi
|
|
|
636
760
|
throw error;
|
|
637
761
|
}
|
|
638
762
|
// Pages round-trip as a single verbatim `.md` with no declaration synthesis, and aren't
|
|
639
|
-
//
|
|
763
|
+
// listed in `catalog.include`, so there's nothing to register.
|
|
640
764
|
if (kind === 'page') {
|
|
641
765
|
yield checkoutPage(handoff, id, payload);
|
|
642
766
|
return null;
|
|
@@ -653,6 +777,7 @@ const checkoutSingle = (handoff, kind, id, client, registryUrl) => __awaiter(voi
|
|
|
653
777
|
}
|
|
654
778
|
return { file, absolutePath };
|
|
655
779
|
});
|
|
780
|
+
const declaration = synthesizeDeclaration(kind, payload.item, format);
|
|
656
781
|
const plannedWrites = [...sourceTargets.map((entry) => entry.absolutePath), declarationPath];
|
|
657
782
|
const conflicts = plannedWrites.filter((file) => fs_extra_1.default.existsSync(file));
|
|
658
783
|
if (handoff.dryRun) {
|
|
@@ -669,7 +794,6 @@ const checkoutSingle = (handoff, kind, id, client, registryUrl) => __awaiter(voi
|
|
|
669
794
|
for (const { file } of sourceTargets) {
|
|
670
795
|
written.push(yield writeSourceFile(targetDir, file));
|
|
671
796
|
}
|
|
672
|
-
const declaration = synthesizeDeclaration(kind, payload.item, format);
|
|
673
797
|
yield fs_extra_1.default.writeFile(declarationPath, declaration, 'utf8');
|
|
674
798
|
written.push(declarationPath);
|
|
675
799
|
logger_1.Logger.success(`Checked out ${kind} "${id}" into ${path_1.default.relative(handoff.workingPath, targetDir) || '.'} ` +
|
|
@@ -677,28 +801,30 @@ const checkoutSingle = (handoff, kind, id, client, registryUrl) => __awaiter(voi
|
|
|
677
801
|
return targetDir;
|
|
678
802
|
});
|
|
679
803
|
/**
|
|
680
|
-
* Declare freshly checked-out entities in `
|
|
804
|
+
* Declare freshly checked-out entities in `catalog.include` so the workspace build
|
|
681
805
|
* picks them up. Ones already covered by a collection directory load on their own and are left
|
|
682
806
|
* alone; the rest are added to the config automatically. If the config can't be edited (a computed
|
|
683
|
-
* or unusual `
|
|
807
|
+
* or unusual `include` array), we print the paths for the user to add so nothing is silently orphaned.
|
|
684
808
|
*/
|
|
685
809
|
const registerCheckedOut = (handoff, kind, targetDirs) => __awaiter(void 0, void 0, void 0, function* () {
|
|
686
810
|
if (kind === 'page') {
|
|
687
811
|
return;
|
|
688
812
|
}
|
|
689
|
-
const
|
|
690
|
-
const
|
|
691
|
-
if (
|
|
813
|
+
const kindLabel = kind === 'component' ? 'components' : 'patterns';
|
|
814
|
+
const unlisted = targetDirs.filter((dir) => !(0, catalog_include_1.isIncluded)(handoff, dir));
|
|
815
|
+
if (unlisted.length === 0) {
|
|
692
816
|
return;
|
|
693
817
|
}
|
|
694
|
-
const result = yield (0,
|
|
818
|
+
const result = yield (0, catalog_include_1.addToCatalog)(handoff, unlisted);
|
|
695
819
|
if (result.status === 'added') {
|
|
696
|
-
const where = result.configPath
|
|
697
|
-
|
|
820
|
+
const where = result.configPath
|
|
821
|
+
? path_1.default.relative(handoff.workingPath, result.configPath) || path_1.default.basename(result.configPath)
|
|
822
|
+
: 'handoff.config';
|
|
823
|
+
logger_1.Logger.success(`Updated ${where} with ${result.added.length} ${kindLabel} path(s).`);
|
|
698
824
|
return;
|
|
699
825
|
}
|
|
700
826
|
const where = result.configPath ? path_1.default.relative(handoff.workingPath, result.configPath) : 'handoff.config';
|
|
701
|
-
logger_1.Logger.warn(`Could not update ${where} automatically. Add these to
|
|
827
|
+
logger_1.Logger.warn(`Could not update ${where} automatically. Add these to catalog.include manually:`);
|
|
702
828
|
result.pending.forEach((rel) => logger_1.Logger.warn(` - ${rel}`));
|
|
703
829
|
});
|
|
704
830
|
/**
|
|
@@ -714,41 +840,33 @@ const checkoutEntity = (handoff, kind, id) => __awaiter(void 0, void 0, void 0,
|
|
|
714
840
|
}
|
|
715
841
|
});
|
|
716
842
|
exports.checkoutEntity = checkoutEntity;
|
|
717
|
-
/**
|
|
718
|
-
|
|
719
|
-
* the registry's published ids, then checks each out through the shared client. A per-entity failure
|
|
720
|
-
* is collected and never aborts the rest; the run throws at the end if any entity failed. Overwrite
|
|
721
|
-
* prompting is per entity (skipped under `--force`).
|
|
722
|
-
*/
|
|
723
|
-
const checkoutEntities = (handoff, kind, ids) => __awaiter(void 0, void 0, void 0, function* () {
|
|
724
|
-
const connection = yield (0, exports.resolveConnectionOrThrow)(handoff);
|
|
725
|
-
const client = (0, client_1.createRegistryClient)({ baseUrl: connection.url, accessToken: connection.accessToken });
|
|
726
|
-
let summaries;
|
|
843
|
+
/** List the ids the registry publishes for one kind, naming the registry in a listing failure. */
|
|
844
|
+
const listPublishedIds = (kind, client, registryUrl) => __awaiter(void 0, void 0, void 0, function* () {
|
|
727
845
|
try {
|
|
728
|
-
summaries = yield client.listEntities(kind);
|
|
846
|
+
const summaries = yield client.listEntities(kind);
|
|
847
|
+
return summaries.map((summary) => summary.id);
|
|
729
848
|
}
|
|
730
849
|
catch (error) {
|
|
731
850
|
if (error instanceof client_1.RegistryClientError) {
|
|
732
|
-
throw new CheckoutError(`Could not list ${kind}s from the registry at ${
|
|
851
|
+
throw new CheckoutError(`Could not list ${kind}s from the registry at ${registryUrl}: ${error.message}`);
|
|
733
852
|
}
|
|
734
853
|
throw error;
|
|
735
854
|
}
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
}
|
|
855
|
+
});
|
|
856
|
+
/**
|
|
857
|
+
* Check out the given ids of one kind and register what landed. The caller selects the ids, because a
|
|
858
|
+
* catalog run resolves them across both entity lanes before any of them is written.
|
|
859
|
+
*
|
|
860
|
+
* A per-entity failure is collected and never aborts the rest. Failures are reported here, and the
|
|
861
|
+
* count is returned so the caller raises one error for the whole run.
|
|
862
|
+
*/
|
|
863
|
+
const checkoutSelected = (handoff, kind, targets, client, registryUrl) => __awaiter(void 0, void 0, void 0, function* () {
|
|
746
864
|
let checkedOut = 0;
|
|
747
865
|
const failed = [];
|
|
748
866
|
const targetDirs = [];
|
|
749
867
|
for (const id of targets) {
|
|
750
868
|
try {
|
|
751
|
-
const targetDir = yield checkoutSingle(handoff, kind, id, client,
|
|
869
|
+
const targetDir = yield checkoutSingle(handoff, kind, id, client, registryUrl);
|
|
752
870
|
if (targetDir) {
|
|
753
871
|
targetDirs.push(targetDir);
|
|
754
872
|
}
|
|
@@ -762,11 +880,79 @@ const checkoutEntities = (handoff, kind, ids) => __awaiter(void 0, void 0, void
|
|
|
762
880
|
yield registerCheckedOut(handoff, kind, targetDirs);
|
|
763
881
|
logger_1.Logger.success(`${kind[0].toUpperCase()}${kind.slice(1)}s checkout complete — ${checkedOut} ${handoff.dryRun ? 'would be checked out' : 'checked out'}` +
|
|
764
882
|
`${failed.length ? `, ${failed.length} failed` : ''}.`);
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
883
|
+
for (const failure of failed) {
|
|
884
|
+
logger_1.Logger.error(` - ${failure.id}: ${failure.message}`);
|
|
885
|
+
}
|
|
886
|
+
return failed.length;
|
|
887
|
+
});
|
|
888
|
+
/**
|
|
889
|
+
* Checkout every published component, pattern, or page of the kind into this workspace. Enumerates
|
|
890
|
+
* the registry's published ids, then checks each out through the shared client. A per-entity failure
|
|
891
|
+
* is collected and never aborts the rest; the run throws at the end if any entity failed. Overwrite
|
|
892
|
+
* prompting is per entity (skipped under `--force`).
|
|
893
|
+
*/
|
|
894
|
+
const checkoutEntities = (handoff, kind, ids) => __awaiter(void 0, void 0, void 0, function* () {
|
|
895
|
+
const connection = yield (0, exports.resolveConnectionOrThrow)(handoff);
|
|
896
|
+
const client = (0, client_1.createRegistryClient)({ baseUrl: connection.url, accessToken: connection.accessToken });
|
|
897
|
+
const published = yield listPublishedIds(kind, client, connection.url);
|
|
898
|
+
const { selected: targets, unknown } = (0, selection_1.selectIds)(published, ids);
|
|
899
|
+
if (unknown.length > 0) {
|
|
900
|
+
throw new CheckoutError(`No ${kind} named ${(0, selection_1.quoteIds)(unknown)} is published in the registry. ` + `Published ${kind}s: ${published.join(', ') || '(none)'}.`);
|
|
901
|
+
}
|
|
902
|
+
if (targets.length === 0) {
|
|
903
|
+
logger_1.Logger.success(`No ${kind}s are published in the registry; nothing to checkout.`);
|
|
904
|
+
return;
|
|
905
|
+
}
|
|
906
|
+
const failed = yield checkoutSelected(handoff, kind, targets, client, connection.url);
|
|
907
|
+
if (failed > 0) {
|
|
908
|
+
throw new CheckoutError(`${failed} ${kind}(s) failed to checkout.`);
|
|
770
909
|
}
|
|
771
910
|
});
|
|
772
911
|
exports.checkoutEntities = checkoutEntities;
|
|
912
|
+
/** The entity lanes a catalog run visits, in dependency order: a pattern composes components. */
|
|
913
|
+
const CATALOG_LANES = ['component', 'pattern'];
|
|
914
|
+
/**
|
|
915
|
+
* Checkout published catalog items into this workspace: every one, or only `ids` when given. An item
|
|
916
|
+
* was published as a component or as a pattern depending on its declaration, so the lane is looked up
|
|
917
|
+
* in the registry and the caller never names one.
|
|
918
|
+
*
|
|
919
|
+
* A failing lane is reported and never aborts the other. The run throws at the end if any item
|
|
920
|
+
* failed.
|
|
921
|
+
*/
|
|
922
|
+
const checkoutCatalog = (handoff, ids) => __awaiter(void 0, void 0, void 0, function* () {
|
|
923
|
+
const connection = yield (0, exports.resolveConnectionOrThrow)(handoff);
|
|
924
|
+
const client = (0, client_1.createRegistryClient)({ baseUrl: connection.url, accessToken: connection.accessToken });
|
|
925
|
+
const available = {
|
|
926
|
+
component: yield listPublishedIds('component', client, connection.url),
|
|
927
|
+
pattern: yield listPublishedIds('pattern', client, connection.url),
|
|
928
|
+
};
|
|
929
|
+
let lanes;
|
|
930
|
+
if (ids) {
|
|
931
|
+
const split = (0, selection_1.splitCatalogIds)(ids, available);
|
|
932
|
+
if (split.ambiguous.length > 0) {
|
|
933
|
+
throw new CheckoutError(`${(0, selection_1.quoteIds)(split.ambiguous)} is published both as a component and as a pattern. Ask the registry owner to fix the id.`);
|
|
934
|
+
}
|
|
935
|
+
if (split.unknown.length > 0) {
|
|
936
|
+
const publishedIds = [...available.component, ...available.pattern];
|
|
937
|
+
throw new CheckoutError(`No catalog item named ${(0, selection_1.quoteIds)(split.unknown)} is published in the registry. ` +
|
|
938
|
+
`Published catalog items: ${publishedIds.join(', ') || '(none)'}.`);
|
|
939
|
+
}
|
|
940
|
+
lanes = CATALOG_LANES.map((kind) => ({ kind, targets: split.lanes[kind] }));
|
|
941
|
+
}
|
|
942
|
+
else {
|
|
943
|
+
lanes = CATALOG_LANES.map((kind) => ({ kind, targets: available[kind] }));
|
|
944
|
+
}
|
|
945
|
+
const selected = lanes.filter((lane) => lane.targets.length > 0);
|
|
946
|
+
if (selected.length === 0) {
|
|
947
|
+
logger_1.Logger.success('No catalog items are published in the registry; nothing to checkout.');
|
|
948
|
+
return;
|
|
949
|
+
}
|
|
950
|
+
let failed = 0;
|
|
951
|
+
for (const lane of selected) {
|
|
952
|
+
failed += yield checkoutSelected(handoff, lane.kind, lane.targets, client, connection.url);
|
|
953
|
+
}
|
|
954
|
+
if (failed > 0) {
|
|
955
|
+
throw new CheckoutError(`${failed} catalog item(s) failed to checkout.`);
|
|
956
|
+
}
|
|
957
|
+
});
|
|
958
|
+
exports.checkoutCatalog = checkoutCatalog;
|