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.
Files changed (201) hide show
  1. package/README.md +174 -41
  2. package/UPGRADE.md +198 -0
  3. package/config/config.template.js +1 -4
  4. package/config/config.template.ts +1 -2
  5. package/dist/app-builder/build.js +11 -6
  6. package/dist/app-builder/client-config.js +1 -0
  7. package/dist/app-builder/config-diff/strategies/pattern.js +3 -3
  8. package/dist/app-builder/watchers/component.js +5 -5
  9. package/dist/app-builder/watchers/utils.d.ts +2 -3
  10. package/dist/app-builder/watchers.d.ts +8 -15
  11. package/dist/app-builder/watchers.js +53 -54
  12. package/dist/cache/build-cache.js +18 -22
  13. package/dist/catalog/define.d.ts +6 -0
  14. package/dist/catalog/define.js +72 -0
  15. package/dist/catalog/implementation.d.ts +27 -0
  16. package/dist/catalog/implementation.js +133 -0
  17. package/dist/catalog/index.d.ts +6 -0
  18. package/dist/catalog/index.js +22 -0
  19. package/dist/catalog/normalize.d.ts +16 -0
  20. package/dist/catalog/normalize.js +115 -0
  21. package/dist/catalog/previews.d.ts +18 -0
  22. package/dist/catalog/previews.js +106 -0
  23. package/dist/catalog/renderers.d.ts +43 -0
  24. package/dist/catalog/renderers.js +62 -0
  25. package/dist/catalog/types.d.ts +63 -0
  26. package/dist/cli/make.d.ts +0 -5
  27. package/dist/cli/make.js +22 -67
  28. package/dist/cli/scaffold.js +25 -33
  29. package/dist/commands/checkout/index.d.ts +7 -4
  30. package/dist/commands/checkout/index.js +9 -6
  31. package/dist/commands/compat/push-args.d.ts +2 -2
  32. package/dist/commands/index.js +0 -2
  33. package/dist/commands/publish/index.d.ts +9 -6
  34. package/dist/commands/publish/index.js +11 -8
  35. package/dist/commands/pull/index.js +1 -1
  36. package/dist/commands/push/index.js +1 -1
  37. package/dist/commands/utils.d.ts +15 -4
  38. package/dist/commands/utils.js +19 -6
  39. package/dist/config/catalog-include.d.ts +43 -0
  40. package/dist/config/catalog-include.js +203 -0
  41. package/dist/config/defaults.d.ts +2 -0
  42. package/dist/config/defaults.js +4 -1
  43. package/dist/config/index.d.ts +3 -3
  44. package/dist/config/index.js +6 -5
  45. package/dist/config/loader.js +9 -1
  46. package/dist/config/runtime.d.ts +5 -6
  47. package/dist/config/runtime.js +154 -154
  48. package/dist/handlebars.d.ts +9 -0
  49. package/dist/handlebars.js +16 -0
  50. package/dist/index.d.ts +15 -4
  51. package/dist/index.js +29 -15
  52. package/dist/pattern.d.ts +7 -0
  53. package/dist/pattern.js +13 -0
  54. package/dist/react.d.ts +43 -0
  55. package/dist/react.js +11 -0
  56. package/dist/registry/asset-storage/define.d.ts +1 -1
  57. package/dist/registry/asset-storage/define.js +1 -1
  58. package/dist/registry/checkout/index.d.ts +9 -0
  59. package/dist/registry/checkout/index.js +335 -149
  60. package/dist/registry/db/schema.d.ts +34 -0
  61. package/dist/registry/db/schema.js +2 -0
  62. package/dist/registry/publish/index.d.ts +13 -2
  63. package/dist/registry/publish/index.js +109 -25
  64. package/dist/registry/publish/package.js +14 -1
  65. package/dist/registry/selection.d.ts +20 -0
  66. package/dist/registry/selection.js +37 -1
  67. package/dist/store/filesystem.d.ts +1 -1
  68. package/dist/store/filesystem.js +7 -7
  69. package/dist/store/registry.js +24 -3
  70. package/dist/transformers/plugins/csf-render.js +4 -2
  71. package/dist/transformers/plugins/ssr-render.js +14 -5
  72. package/dist/transformers/preview/component/api.d.ts +8 -0
  73. package/dist/transformers/preview/component/api.js +11 -15
  74. package/dist/transformers/preview/component/artifacts.d.ts +1 -1
  75. package/dist/transformers/preview/component/artifacts.js +11 -7
  76. package/dist/transformers/preview/component/builder.js +15 -18
  77. package/dist/transformers/preview/component/css.d.ts +2 -0
  78. package/dist/transformers/preview/component/css.js +8 -11
  79. package/dist/transformers/preview/component/html.js +18 -16
  80. package/dist/transformers/preview/component/javascript.js +2 -4
  81. package/dist/transformers/preview/pattern/builder.js +31 -18
  82. package/dist/transformers/preview/types.d.ts +15 -1
  83. package/dist/types/config.d.ts +36 -11
  84. package/docs/api.md +3 -0
  85. package/docs/cli.md +1 -1
  86. package/drizzle/0006_add_component_source_format.sql +3 -0
  87. package/drizzle/meta/0006_snapshot.json +1941 -0
  88. package/drizzle/meta/_journal.json +8 -1
  89. package/package.json +37 -2
  90. package/src/app/components/Component/Preview.tsx +36 -50
  91. package/src/app/components/CopyToClipboard.tsx +27 -26
  92. package/src/app/components/Layout/Header.tsx +4 -12
  93. package/src/app/components/Layout/RuntimeModeBadge.tsx +18 -0
  94. package/src/app/components/Markdown/CodeHighlight.tsx +73 -70
  95. package/src/app/components/McpIntegration/McpConfigDialog.tsx +349 -0
  96. package/src/app/components/McpIntegration/McpIcon.tsx +10 -0
  97. package/src/app/components/Navigation/MobileNav.tsx +3 -1
  98. package/src/app/components/context/PreviewContext.tsx +5 -5
  99. package/src/app/components/ui/dialog.tsx +73 -0
  100. package/src/app/components/util/index.ts +7 -15
  101. package/src/app/lib/docs-api/backend.ts +5 -1
  102. package/src/app/lib/docs-api/page-discovery.ts +1 -6
  103. package/src/app/lib/docs-api/page-rendering.ts +21 -1
  104. package/src/app/lib/docs-api/records.ts +25 -10
  105. package/src/app/lib/docs-api/registry.ts +4 -2
  106. package/src/app/lib/docs-api/runtime-config.ts +5 -0
  107. package/src/app/lib/docs-api/search.ts +7 -2
  108. package/src/app/lib/mcp/auth.ts +64 -0
  109. package/src/app/lib/mcp/index.ts +2 -0
  110. package/src/app/lib/mcp/server.ts +343 -0
  111. package/src/app/lib/mcp/shape.ts +596 -0
  112. package/src/app/lib/registry-api/transfer.ts +7 -5
  113. package/src/app/next.config.mjs +3 -0
  114. package/src/app/pages/[...slug]/index.tsx +16 -30
  115. package/src/app/pages/api/mcp.ts +56 -0
  116. package/src/app/pages/index.tsx +10 -9
  117. package/src/app-builder/build.ts +12 -8
  118. package/src/app-builder/client-config.ts +2 -1
  119. package/src/app-builder/config-diff/strategies/pattern.ts +1 -1
  120. package/src/app-builder/watchers/component.ts +3 -3
  121. package/src/app-builder/watchers/utils.ts +2 -3
  122. package/src/app-builder/watchers.ts +46 -50
  123. package/src/cache/build-cache.ts +11 -14
  124. package/src/catalog/README.md +92 -0
  125. package/src/catalog/define.ts +79 -0
  126. package/src/catalog/implementation.ts +145 -0
  127. package/src/catalog/index.ts +31 -0
  128. package/src/catalog/normalize.ts +143 -0
  129. package/src/catalog/previews.ts +111 -0
  130. package/src/catalog/renderers.ts +94 -0
  131. package/src/catalog/types.ts +74 -0
  132. package/src/cli/README.md +1 -1
  133. package/src/cli/make.ts +20 -72
  134. package/src/cli/scaffold.ts +25 -31
  135. package/src/commands/README.md +1 -1
  136. package/src/commands/checkout/index.ts +17 -7
  137. package/src/commands/compat/push-args.ts +2 -2
  138. package/src/commands/index.ts +0 -2
  139. package/src/commands/publish/index.ts +19 -9
  140. package/src/commands/pull/index.ts +2 -2
  141. package/src/commands/push/index.ts +2 -2
  142. package/src/commands/utils.ts +22 -7
  143. package/src/config/README.md +2 -1
  144. package/src/config/catalog-include.ts +227 -0
  145. package/src/config/defaults.ts +3 -0
  146. package/src/config/index.ts +4 -4
  147. package/src/config/loader.ts +12 -0
  148. package/src/config/runtime.ts +186 -191
  149. package/src/handlebars.ts +18 -0
  150. package/src/index.ts +39 -26
  151. package/src/pattern.ts +14 -0
  152. package/src/react.ts +52 -0
  153. package/src/registry/asset-storage/define.ts +1 -1
  154. package/src/registry/checkout/index.ts +416 -152
  155. package/src/registry/db/schema.ts +2 -0
  156. package/src/registry/publish/index.ts +123 -26
  157. package/src/registry/publish/package.ts +14 -1
  158. package/src/registry/selection.ts +44 -0
  159. package/src/store/filesystem.ts +4 -4
  160. package/src/store/registry.ts +13 -2
  161. package/src/transformers/plugins/csf-render.ts +4 -2
  162. package/src/transformers/plugins/ssr-render.ts +15 -5
  163. package/src/transformers/preview/component/api.ts +13 -1
  164. package/src/transformers/preview/component/artifacts.ts +8 -4
  165. package/src/transformers/preview/component/builder.ts +17 -4
  166. package/src/transformers/preview/component/css.ts +6 -10
  167. package/src/transformers/preview/component/html.ts +30 -19
  168. package/src/transformers/preview/component/javascript.ts +1 -3
  169. package/src/transformers/preview/pattern/builder.ts +33 -20
  170. package/src/transformers/preview/types.ts +15 -1
  171. package/src/types/config.ts +38 -11
  172. package/templates/default/README.md.tpl +10 -0
  173. package/templates/default/components/button/button.react.handoff.js.tpl +19 -23
  174. package/templates/default/components/button/button.react.handoff.ts.tpl +22 -22
  175. package/templates/default/handoff.config.js.tpl +11 -18
  176. package/templates/default/handoff.config.ts.tpl +11 -18
  177. package/templates/default/handoff.config.with-examples.js.tpl +12 -16
  178. package/templates/default/handoff.config.with-examples.ts.tpl +12 -16
  179. package/config/templates/component/template.json +0 -49
  180. package/config/templates/template.html +0 -7
  181. package/dist/commands/make/template.d.ts +0 -8
  182. package/dist/commands/make/template.js +0 -43
  183. package/dist/config/entries.d.ts +0 -47
  184. package/dist/config/entries.js +0 -162
  185. package/dist/config/normalizers/declaration.d.ts +0 -11
  186. package/dist/config/normalizers/declaration.js +0 -122
  187. package/dist/config/normalizers/pattern.d.ts +0 -8
  188. package/dist/config/normalizers/pattern.js +0 -41
  189. package/dist/declarations/helpers.d.ts +0 -6
  190. package/dist/declarations/helpers.js +0 -19
  191. package/dist/declarations/index.d.ts +0 -2
  192. package/dist/declarations/index.js +0 -9
  193. package/dist/declarations/types.d.ts +0 -60
  194. package/src/commands/make/template.ts +0 -46
  195. package/src/config/entries.ts +0 -175
  196. package/src/config/normalizers/declaration.ts +0 -179
  197. package/src/config/normalizers/pattern.ts +0 -59
  198. package/src/declarations/helpers.ts +0 -36
  199. package/src/declarations/index.ts +0 -19
  200. package/src/declarations/types.ts +0 -70
  201. /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 entries_1 = require("../../config/entries");
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}: entries.${kind}s declares individual ${kind}s under different ` +
140
- `directories (${roots.map(relative).join(', ')}). Declare a single collection directory in handoff.config, ` +
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
- * `entries.{components|patterns}`. Each entry is either a collection directory (used as-is) or a
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 mean the config is
158
- * ambiguous, so we ask the user; with nothing configured we fall back to the default subdir.
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, _c, _d;
162
- const configuredRoots = kind === 'component' ? (_b = (_a = handoff.config) === null || _a === void 0 ? void 0 : _a.entries) === null || _b === void 0 ? void 0 : _b.components : (_d = (_c = handoff.config) === null || _c === void 0 ? void 0 : _c.entries) === null || _d === void 0 ? void 0 : _d.patterns;
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
- return roots.length === 1 ? roots[0] : promptForCollectionRoot(handoff, kind, roots);
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', 'json'].includes(ext) ? ext : undefined;
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
- * Rebuild the authored `entries` for a renderer: keep only the renderer's primary entry key (so the
244
- * normalizer-duplicated keys are dropped) plus the supporting source entries (`js`/`scss`/`schema`/
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, renderer) => {
249
+ const buildEntries = (entries, source, options = {}) => {
248
250
  if (!isPlainObject(entries)) {
249
251
  return undefined;
250
252
  }
251
253
  const result = {};
252
- const primaryKey = renderer ? PRIMARY_ENTRY_BY_RENDERER[renderer] : undefined;
253
- if (primaryKey && typeof entries[primaryKey] === 'string') {
254
- result[primaryKey] = entries[primaryKey];
255
- }
256
- else if (!primaryKey) {
257
- const fallback = PRIMARY_ENTRY_KEYS.find((key) => typeof entries[key] === 'string');
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 `previews` map (`DeclarationPreview`) from the stored previews, keeping only
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`). The component normalizer maps authored `args` to `values`, so the stored record
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.args !== undefined ? raw.args : raw.values;
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 (`GenericDeclarationConfig`) from the normalized
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, _e;
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 renderer = asString(item.renderer);
320
- if (renderer)
321
- declaration.renderer = renderer;
322
- const entries = buildEntries(item.entries, renderer);
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 = (_d = asStringArray(item.shouldDo)) !== null && _d !== void 0 ? _d : asStringArray(item.should_do);
342
+ const shouldDo = asStringArray(item.should_do);
335
343
  if (shouldDo)
336
344
  declaration.shouldDo = shouldDo;
337
- const shouldNotDo = (_e = asStringArray(item.shouldNotDo)) !== null && _e !== void 0 ? _e : asStringArray(item.should_not_do);
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 (`PatternComponentRef`) from the stored entries,
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 (`GenericPatternDeclarationConfig`) from the normalized
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
- * Synthesize the component declaration file. JSON declarations are the bare object (carrying
508
- * `renderer`/`entries`, which the loader reads directly). Code declarations use the renderer-specific
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 renderComponentDeclaration = (declaration, format) => {
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
- if (format === 'json') {
516
- return `${JSON.stringify(declaration, null, 2)}\n`;
517
- }
518
- const renderer = asString(declaration.renderer);
519
- const factory = resolveComponentFactory(renderer);
520
- const componentEntry = isPlainObject(declaration.entries) ? asString(declaration.entries.component) : undefined;
521
- if (factory.name === 'defineReactComponent' && componentEntry) {
522
- const specifier = toImportSpecifier(componentEntry);
523
- const identifier = toComponentIdentifier(componentEntry, (_a = asString(declaration.id)) !== null && _a !== void 0 ? _a : 'Component');
524
- const literal = toJsLiteral(omit(declaration, 'renderer'));
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
- ? `import { defineReactComponent } from 'handoff-app';\nimport ${identifier} from '${specifier}';\n\nexport default defineReactComponent(${identifier}, ${literal});\n`
527
- : `const { defineReactComponent } = require('handoff-app');\nconst ${identifier} = require('${specifier}').default;\n\nmodule.exports = defineReactComponent(${identifier}, ${literal});\n`;
528
- }
529
- // React without a resolvable component entry can't use defineReactComponent; keep renderer and use
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
- /** Synthesize the pattern declaration file via `definePattern` (or a bare object for JSON). */
536
- const renderPatternDeclaration = (declaration, format) => {
537
- if (format === 'json') {
538
- return `${JSON.stringify(declaration, null, 2)}\n`;
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
- return wrapFactory('definePattern', declaration, format);
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) => kind === 'component'
544
- ? renderComponentDeclaration(buildComponentDeclaration(item), format)
545
- : renderPatternDeclaration(buildPatternDeclaration(item), format);
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
- // declared in `entries`, so there's nothing to register.
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 `entries.{components|patterns}` so the workspace build
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 `entries` array), we print the paths for the user to add so nothing is silently orphaned.
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 entryKind = kind === 'component' ? 'components' : 'patterns';
690
- const uncovered = targetDirs.filter((dir) => !(0, entries_1.isEntryCovered)(handoff, entryKind, dir));
691
- if (uncovered.length === 0) {
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, entries_1.writeEntries)(handoff, entryKind, uncovered);
818
+ const result = yield (0, catalog_include_1.addToCatalog)(handoff, unlisted);
695
819
  if (result.status === 'added') {
696
- const where = result.configPath ? path_1.default.relative(handoff.workingPath, result.configPath) || path_1.default.basename(result.configPath) : 'handoff.config';
697
- logger_1.Logger.success(`Updated ${where} with ${result.added.length} ${entryKind} path(s).`);
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 entries.${entryKind} manually:`);
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
- * Checkout every published component, pattern, or page of the kind into this workspace. Enumerates
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 ${connection.url}: ${error.message}`);
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
- const published = summaries.map((summary) => summary.id);
737
- const { selected: targets, unknown } = (0, selection_1.selectIds)(published, ids);
738
- if (unknown.length > 0) {
739
- throw new CheckoutError(`No ${kind} named ${unknown.map((id) => `"${id}"`).join(', ')} is published in the registry. ` +
740
- `Published ${kind}s: ${published.join(', ') || '(none)'}.`);
741
- }
742
- if (targets.length === 0) {
743
- logger_1.Logger.success(`No ${kind}s are published in the registry; nothing to checkout.`);
744
- return;
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, connection.url);
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
- if (failed.length > 0) {
766
- for (const failure of failed) {
767
- logger_1.Logger.error(` - ${failure.id}: ${failure.message}`);
768
- }
769
- throw new CheckoutError(`${failed.length} ${kind}(s) failed to checkout.`);
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;