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
@@ -193,6 +193,23 @@ export declare const components: import("drizzle-orm/pg-core").PgTableWithColumn
193
193
  identity: undefined;
194
194
  generated: undefined;
195
195
  }, {}, {}>;
196
+ sourceFormat: import("drizzle-orm/pg-core").PgColumn<{
197
+ name: "source_format";
198
+ tableName: "components";
199
+ dataType: "string";
200
+ columnType: "PgText";
201
+ data: string;
202
+ driverParam: string;
203
+ notNull: false;
204
+ hasDefault: false;
205
+ isPrimaryKey: false;
206
+ isAutoincrement: false;
207
+ hasRuntimeDefault: false;
208
+ enumValues: [string, ...string[]];
209
+ baseColumn: never;
210
+ identity: undefined;
211
+ generated: undefined;
212
+ }, {}, {}>;
196
213
  tags: import("drizzle-orm/pg-core").PgColumn<{
197
214
  name: "tags";
198
215
  tableName: "components";
@@ -3533,6 +3550,23 @@ export declare const registrySchema: {
3533
3550
  identity: undefined;
3534
3551
  generated: undefined;
3535
3552
  }, {}, {}>;
3553
+ sourceFormat: import("drizzle-orm/pg-core").PgColumn<{
3554
+ name: "source_format";
3555
+ tableName: "components";
3556
+ dataType: "string";
3557
+ columnType: "PgText";
3558
+ data: string;
3559
+ driverParam: string;
3560
+ notNull: false;
3561
+ hasDefault: false;
3562
+ isPrimaryKey: false;
3563
+ isAutoincrement: false;
3564
+ hasRuntimeDefault: false;
3565
+ enumValues: [string, ...string[]];
3566
+ baseColumn: never;
3567
+ identity: undefined;
3568
+ generated: undefined;
3569
+ }, {}, {}>;
3536
3570
  tags: import("drizzle-orm/pg-core").PgColumn<{
3537
3571
  name: "tags";
3538
3572
  tableName: "components";
@@ -51,6 +51,8 @@ exports.components = (0, pg_core_1.pgTable)('components', {
51
51
  group: (0, pg_core_1.text)('group'),
52
52
  type: (0, pg_core_1.text)('type'),
53
53
  renderer: (0, pg_core_1.text)('renderer'),
54
+ /** How the implementation file is written (`csf`), when the renderer alone does not say. */
55
+ sourceFormat: (0, pg_core_1.text)('source_format'),
54
56
  tags: (0, pg_core_1.jsonb)('tags').$type(),
55
57
  categories: (0, pg_core_1.jsonb)('categories').$type(),
56
58
  /** Full normalized component record. */
@@ -1,8 +1,9 @@
1
1
  /**
2
2
  * Connected-workspace publish orchestration.
3
3
  *
4
- * `publish <components|patterns|pages> <id>` prepares one entity and uploads its record, source files,
5
- * applicable rendered artifacts, and build metadata through the shared registry client.
4
+ * `publish <catalog|pages> <id>` prepares one entity and uploads its record, source files, applicable
5
+ * rendered artifacts, and build metadata through the shared registry client. A catalog item's lane
6
+ * comes from its declaration, so the caller names items and this module resolves the lane.
6
7
  *
7
8
  * The build is targeted: a component builds the global artifacts + that component; a pattern builds
8
9
  * the global artifacts + the components it composes + the pattern itself. This is the smallest build
@@ -56,3 +57,13 @@ export declare const publishEntity: (handoff: Handoff, kind: TransferEntityKind,
56
57
  * run throws at the end if any entity failed.
57
58
  */
58
59
  export declare const publishEntities: (handoff: Handoff, kind: TransferEntityKind, ids?: string[]) => Promise<void>;
60
+ /**
61
+ * Publish the catalog items declared in this connected workspace: every one, or only `ids` when
62
+ * given. An item takes the component lane or the pattern lane depending on whether it declares an
63
+ * implementation or a composition, so the caller names items and never names a lane.
64
+ *
65
+ * One build covers both lanes, because building the patterns already builds every component they
66
+ * compose. A failing lane is reported and never aborts the other. The run throws at the end if any
67
+ * item failed.
68
+ */
69
+ export declare const publishCatalog: (handoff: Handoff, ids?: string[]) => Promise<void>;
@@ -2,8 +2,9 @@
2
2
  /**
3
3
  * Connected-workspace publish orchestration.
4
4
  *
5
- * `publish <components|patterns|pages> <id>` prepares one entity and uploads its record, source files,
6
- * applicable rendered artifacts, and build metadata through the shared registry client.
5
+ * `publish <catalog|pages> <id>` prepares one entity and uploads its record, source files, applicable
6
+ * rendered artifacts, and build metadata through the shared registry client. A catalog item's lane
7
+ * comes from its declaration, so the caller names items and this module resolves the lane.
7
8
  *
8
9
  * The build is targeted: a component builds the global artifacts + that component; a pattern builds
9
10
  * the global artifacts + the components it composes + the pattern itself. This is the smallest build
@@ -22,7 +23,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
22
23
  return (mod && mod.__esModule) ? mod : { "default": mod };
23
24
  };
24
25
  Object.defineProperty(exports, "__esModule", { value: true });
25
- exports.publishEntities = exports.publishEntity = exports.publishedLabel = exports.resolveTransport = exports.resolveConnectionOrThrow = exports.assertPublishableWorkspace = exports.PublishError = void 0;
26
+ exports.publishCatalog = exports.publishEntities = exports.publishEntity = exports.publishedLabel = exports.resolveTransport = exports.resolveConnectionOrThrow = exports.assertPublishableWorkspace = exports.PublishError = void 0;
26
27
  const css_1 = require("../../transformers/preview/component/css");
27
28
  const javascript_1 = require("../../transformers/preview/component/javascript");
28
29
  const builder_1 = __importDefault(require("../../transformers/preview/component/builder"));
@@ -55,6 +56,22 @@ const assertPublishableWorkspace = (handoff) => {
55
56
  }
56
57
  };
57
58
  exports.assertPublishableWorkspace = assertPublishableWorkspace;
59
+ /** The entity lanes a catalog run visits, in dependency order: a pattern composes components. */
60
+ const CATALOG_LANES = ['component', 'pattern'];
61
+ const isCatalogLane = (kind) => CATALOG_LANES.some((lane) => lane === kind);
62
+ /**
63
+ * Refuse a catalog run while the workspace declares one id twice. Discovery keeps the first
64
+ * declaration and skips the rest, so a publish uploads a catalog that silently omits an item.
65
+ */
66
+ const assertUniqueCatalogIds = (handoff) => {
67
+ var _a, _b;
68
+ const duplicates = (_b = (_a = handoff.runtimeConfig) === null || _a === void 0 ? void 0 : _a.duplicateCatalogIds) !== null && _b !== void 0 ? _b : [];
69
+ if (duplicates.length === 0)
70
+ return;
71
+ throw new PublishError(`Catalog id${duplicates.length > 1 ? 's' : ''} declared twice: ` +
72
+ duplicates.map(({ id, kept, skipped }) => `"${id}" (${kept} and ${skipped})`).join('; ') +
73
+ '. Give each catalog item a unique id.');
74
+ };
58
75
  /**
59
76
  * Ensure the workspace is a connected workspace able to publish: workspace runtime mode (registry
60
77
  * hosts do not publish) and a resolved registry URL + access token. Throws an actionable
@@ -95,11 +112,11 @@ const publishedLabel = (handoff) => (handoff.dryRun ? 'Would publish' : 'Publish
95
112
  exports.publishedLabel = publishedLabel;
96
113
  /** Run the smallest build that guarantees the selected entity's artifacts are current. */
97
114
  const runTargetedBuild = (handoff, kind, id) => __awaiter(void 0, void 0, void 0, function* () {
98
- var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m;
115
+ var _a, _b, _c, _d;
99
116
  // Pages carry no rendered artifacts (raw markdown is rendered at runtime), so there is nothing to
100
117
  // build — the package is assembled directly from the discovered page record + its `.md`.
101
118
  if (kind === 'page') {
102
- if (!((_c = (_b = (_a = handoff.runtimeConfig) === null || _a === void 0 ? void 0 : _a.entries) === null || _b === void 0 ? void 0 : _b.pages) === null || _c === void 0 ? void 0 : _c[id])) {
119
+ if (!((_a = handoff.runtimeConfig) === null || _a === void 0 ? void 0 : _a.entities.pages[id])) {
103
120
  throw new package_1.PublishPackageError(`Page "${id}" is not declared in this workspace.`);
104
121
  }
105
122
  return;
@@ -109,13 +126,13 @@ const runTargetedBuild = (handoff, kind, id) => __awaiter(void 0, void 0, void 0
109
126
  yield (0, javascript_1.buildMainJS)(handoff);
110
127
  yield (0, css_1.buildMainCss)(handoff);
111
128
  if (kind === 'component') {
112
- if (!((_f = (_e = (_d = handoff.runtimeConfig) === null || _d === void 0 ? void 0 : _d.entries) === null || _e === void 0 ? void 0 : _e.components) === null || _f === void 0 ? void 0 : _f[id])) {
129
+ if (!((_b = handoff.runtimeConfig) === null || _b === void 0 ? void 0 : _b.entities.components[id])) {
113
130
  throw new package_1.PublishPackageError(`Component "${id}" is not declared in this workspace.`);
114
131
  }
115
132
  yield (0, builder_1.default)(handoff, id);
116
133
  return;
117
134
  }
118
- const pattern = (_j = (_h = (_g = handoff.runtimeConfig) === null || _g === void 0 ? void 0 : _g.entries) === null || _h === void 0 ? void 0 : _h.patterns) === null || _j === void 0 ? void 0 : _j[id];
135
+ const pattern = (_c = handoff.runtimeConfig) === null || _c === void 0 ? void 0 : _c.entities.patterns[id];
119
136
  if (!pattern) {
120
137
  throw new package_1.PublishPackageError(`Pattern "${id}" is not declared in this workspace.`);
121
138
  }
@@ -123,7 +140,7 @@ const runTargetedBuild = (handoff, kind, id) => __awaiter(void 0, void 0, void 0
123
140
  // then compose just this pattern.
124
141
  const referencedComponentIds = new Set(pattern.components.map((ref) => ref.id));
125
142
  for (const componentId of Array.from(referencedComponentIds)) {
126
- if ((_m = (_l = (_k = handoff.runtimeConfig) === null || _k === void 0 ? void 0 : _k.entries) === null || _l === void 0 ? void 0 : _l.components) === null || _m === void 0 ? void 0 : _m[componentId]) {
143
+ if ((_d = handoff.runtimeConfig) === null || _d === void 0 ? void 0 : _d.entities.components[componentId]) {
127
144
  yield (0, builder_1.default)(handoff, componentId);
128
145
  }
129
146
  }
@@ -134,6 +151,8 @@ const runTargetedBuild = (handoff, kind, id) => __awaiter(void 0, void 0, void 0
134
151
  * receive a targeted build, while pages are packaged directly from their source.
135
152
  */
136
153
  const publishEntity = (handoff, kind, id) => __awaiter(void 0, void 0, void 0, function* () {
154
+ if (isCatalogLane(kind))
155
+ assertUniqueCatalogIds(handoff);
137
156
  const { client, url } = yield (0, exports.resolveTransport)(handoff);
138
157
  if (!handoff.skipBuild) {
139
158
  logger_1.Logger.info(kind === 'page' ? `Preparing page "${id}" for publish…` : `Building ${kind} "${id}" for publish…`);
@@ -200,22 +219,18 @@ const selectEntityIds = (available, requested, kind) => {
200
219
  return selected;
201
220
  };
202
221
  /**
203
- * Publish the components, patterns or pages declared in this connected workspace: every one of the
204
- * kind, or only `ids` when given. Builds the kind once, assembles each entity's package, skips
205
- * entities whose content hash already matches the registry (unless `--force`), and reports
206
- * published/unchanged/failed counts. A per-entity failure is collected and never aborts the rest; the
207
- * run throws at the end if any entity failed.
222
+ * Assemble and upload the selected entities of one kind. The caller runs the build, because a catalog
223
+ * run covers both entity lanes from one build.
224
+ *
225
+ * A per-entity failure is collected and never aborts the rest. Failures are reported here, and the
226
+ * count is returned so the caller raises one error for the whole run.
208
227
  */
209
- const publishEntities = (handoff, kind, ids) => __awaiter(void 0, void 0, void 0, function* () {
210
- const { client, url } = yield (0, exports.resolveTransport)(handoff);
211
- if (!handoff.skipBuild) {
212
- logger_1.Logger.info(`Building ${kind}s for publish…`);
213
- yield runBulkBuild(handoff, kind);
214
- }
228
+ const uploadEntities = (handoff, transport, kind, ids) => __awaiter(void 0, void 0, void 0, function* () {
229
+ const { client, url } = transport;
215
230
  const targets = selectEntityIds(yield listWorkspaceEntityIds(handoff, kind), ids, kind);
216
231
  if (targets.length === 0) {
217
232
  logger_1.Logger.success(`No ${kind}s are declared in this workspace; nothing to publish.`);
218
- return;
233
+ return 0;
219
234
  }
220
235
  // Without a registry there is nothing to compare against, so a dry run reports every target.
221
236
  let remote = [];
@@ -255,11 +270,80 @@ const publishEntities = (handoff, kind, ids) => __awaiter(void 0, void 0, void 0
255
270
  }
256
271
  logger_1.Logger.success(`${kind[0].toUpperCase()}${kind.slice(1)}s publish complete — ${published} ${handoff.dryRun ? 'would be published' : 'published'}, ` +
257
272
  `${unchanged} unchanged${failed.length ? `, ${failed.length} failed` : ''}.`);
258
- if (failed.length > 0) {
259
- for (const failure of failed) {
260
- logger_1.Logger.error(` - ${failure.id}: ${failure.message}`);
261
- }
262
- throw new PublishError(`${failed.length} ${kind}(s) failed to publish.`);
273
+ for (const failure of failed) {
274
+ logger_1.Logger.error(` - ${failure.id}: ${failure.message}`);
275
+ }
276
+ return failed.length;
277
+ });
278
+ /**
279
+ * Publish the components, patterns or pages declared in this connected workspace: every one of the
280
+ * kind, or only `ids` when given. Builds the kind once, assembles each entity's package, skips
281
+ * entities whose content hash already matches the registry (unless `--force`), and reports
282
+ * published/unchanged/failed counts. A per-entity failure is collected and never aborts the rest; the
283
+ * run throws at the end if any entity failed.
284
+ */
285
+ const publishEntities = (handoff, kind, ids) => __awaiter(void 0, void 0, void 0, function* () {
286
+ if (isCatalogLane(kind))
287
+ assertUniqueCatalogIds(handoff);
288
+ const transport = yield (0, exports.resolveTransport)(handoff);
289
+ if (!handoff.skipBuild) {
290
+ logger_1.Logger.info(`Building ${kind}s for publish…`);
291
+ yield runBulkBuild(handoff, kind);
292
+ }
293
+ const failed = yield uploadEntities(handoff, transport, kind, ids);
294
+ if (failed > 0) {
295
+ throw new PublishError(`${failed} ${kind}(s) failed to publish.`);
263
296
  }
264
297
  });
265
298
  exports.publishEntities = publishEntities;
299
+ /**
300
+ * Resolve which lane each requested catalog id belongs to. Without ids every lane runs in full. An
301
+ * unknown id is reported against the whole catalog, because the caller never chose a lane to report
302
+ * it against.
303
+ */
304
+ const resolveCatalogLanes = (handoff, ids) => __awaiter(void 0, void 0, void 0, function* () {
305
+ if (!ids) {
306
+ return CATALOG_LANES.map((kind) => ({ kind }));
307
+ }
308
+ const available = {
309
+ component: yield listWorkspaceEntityIds(handoff, 'component'),
310
+ pattern: yield listWorkspaceEntityIds(handoff, 'pattern'),
311
+ };
312
+ const { lanes, unknown, ambiguous } = (0, selection_1.splitCatalogIds)(ids, available);
313
+ if (ambiguous.length > 0) {
314
+ throw new PublishError(`${(0, selection_1.quoteIds)(ambiguous)} names both a component and a pattern in this workspace. Give each catalog item a unique id.`);
315
+ }
316
+ if (unknown.length > 0) {
317
+ const declared = [...available.component, ...available.pattern];
318
+ throw new PublishError(`No catalog item named ${(0, selection_1.quoteIds)(unknown)} is declared in this workspace. ` +
319
+ `Declared catalog items: ${declared.join(', ') || '(none)'}.`);
320
+ }
321
+ return CATALOG_LANES.map((kind) => ({ kind, ids: lanes[kind] })).filter((lane) => lane.ids.length > 0);
322
+ });
323
+ /**
324
+ * Publish the catalog items declared in this connected workspace: every one, or only `ids` when
325
+ * given. An item takes the component lane or the pattern lane depending on whether it declares an
326
+ * implementation or a composition, so the caller names items and never names a lane.
327
+ *
328
+ * One build covers both lanes, because building the patterns already builds every component they
329
+ * compose. A failing lane is reported and never aborts the other. The run throws at the end if any
330
+ * item failed.
331
+ */
332
+ const publishCatalog = (handoff, ids) => __awaiter(void 0, void 0, void 0, function* () {
333
+ assertUniqueCatalogIds(handoff);
334
+ const transport = yield (0, exports.resolveTransport)(handoff);
335
+ const lanes = yield resolveCatalogLanes(handoff, ids);
336
+ if (!handoff.skipBuild) {
337
+ logger_1.Logger.info('Building catalog items for publish…');
338
+ // The pattern build runs the component build first, so one call covers both lanes.
339
+ yield runBulkBuild(handoff, 'pattern');
340
+ }
341
+ let failed = 0;
342
+ for (const lane of lanes) {
343
+ failed += yield uploadEntities(handoff, transport, lane.kind, lane.ids);
344
+ }
345
+ if (failed > 0) {
346
+ throw new PublishError(`${failed} catalog item(s) failed to publish.`);
347
+ }
348
+ });
349
+ exports.publishCatalog = publishCatalog;
@@ -52,12 +52,14 @@ class PublishPackageError extends Error {
52
52
  }
53
53
  exports.PublishPackageError = PublishPackageError;
54
54
  /**
55
- * Build a registry-safe `item` record: the built summary enriched with the renderer and the entry
55
+ * Build a registry-safe `item` record: the built summary enriched with the renderer, its source
56
+ * format, and the entry
56
57
  * source-file paths **remapped** from absolute workspace locations to the registry-safe relative
57
58
  * paths the uploaded source files use. Workspace-absolute paths never cross to the registry, and a
58
59
  * later checkout can reconstruct the entity from the relative entries + uploaded files.
59
60
  */
60
61
  const enrichItem = (handoff, kind, id, summary) => __awaiter(void 0, void 0, void 0, function* () {
62
+ var _a;
61
63
  const runtime = kind === 'component' ? yield handoff.store.components.get(id) : yield handoff.store.patterns.get(id);
62
64
  if (!runtime) {
63
65
  return summary;
@@ -69,6 +71,17 @@ const enrichItem = (handoff, kind, id, summary) => __awaiter(void 0, void 0, voi
69
71
  if (renderer) {
70
72
  item.renderer = renderer;
71
73
  }
74
+ const sourceFormat = runtime.sourceFormat;
75
+ if (sourceFormat) {
76
+ item.sourceFormat = sourceFormat;
77
+ }
78
+ const componentExport = runtime.componentExport;
79
+ if (componentExport)
80
+ item.componentExport = componentExport;
81
+ if (kind === 'component') {
82
+ // PostgreSQL jsonb does not preserve object key order.
83
+ item.previewOrder = Object.keys((_a = runtime.previews) !== null && _a !== void 0 ? _a : {});
84
+ }
72
85
  if (rawEntries && sourceDir) {
73
86
  const remapped = {};
74
87
  for (const [key, value] of Object.entries(rawEntries)) {
@@ -4,9 +4,29 @@
4
4
  * Every bulk operation discovers what is available (declared locally or published remotely) and may
5
5
  * be asked for only part of it. Splitting the request into what exists and what does not lets each
6
6
  * caller build its own actionable error while the matching lives in one place.
7
+ *
8
+ * A catalog request needs one more step. A catalog item normalizes into a component or a pattern
9
+ * from its declaration, so the CLI names the item and the lane is looked up here.
7
10
  */
8
11
  /** Narrow an available list to the requested subset, separating out ids that do not exist. */
9
12
  export declare const selectIds: (available: string[], requested?: string[]) => {
10
13
  selected: string[];
11
14
  unknown: string[];
12
15
  };
16
+ /** Catalog item ids grouped by lane. */
17
+ export interface CatalogLanes {
18
+ component: string[];
19
+ pattern: string[];
20
+ }
21
+ /**
22
+ * Route each requested catalog id to its lane. The two lanes are keyed separately, so nothing stops a
23
+ * workspace from declaring one id twice. Such an id is reported rather than guessed, because picking
24
+ * a lane silently would act on the wrong entity.
25
+ */
26
+ export declare const splitCatalogIds: (requested: string[], available: CatalogLanes) => {
27
+ lanes: CatalogLanes;
28
+ unknown: string[];
29
+ ambiguous: string[];
30
+ };
31
+ /** Quote ids for an error message: `"button", "card"`. */
32
+ export declare const quoteIds: (ids: string[]) => string;
@@ -5,9 +5,12 @@
5
5
  * Every bulk operation discovers what is available (declared locally or published remotely) and may
6
6
  * be asked for only part of it. Splitting the request into what exists and what does not lets each
7
7
  * caller build its own actionable error while the matching lives in one place.
8
+ *
9
+ * A catalog request needs one more step. A catalog item normalizes into a component or a pattern
10
+ * from its declaration, so the CLI names the item and the lane is looked up here.
8
11
  */
9
12
  Object.defineProperty(exports, "__esModule", { value: true });
10
- exports.selectIds = void 0;
13
+ exports.quoteIds = exports.splitCatalogIds = exports.selectIds = void 0;
11
14
  /** Narrow an available list to the requested subset, separating out ids that do not exist. */
12
15
  const selectIds = (available, requested) => {
13
16
  if (!requested) {
@@ -20,3 +23,36 @@ const selectIds = (available, requested) => {
20
23
  };
21
24
  };
22
25
  exports.selectIds = selectIds;
26
+ /**
27
+ * Route each requested catalog id to its lane. The two lanes are keyed separately, so nothing stops a
28
+ * workspace from declaring one id twice. Such an id is reported rather than guessed, because picking
29
+ * a lane silently would act on the wrong entity.
30
+ */
31
+ const splitCatalogIds = (requested, available) => {
32
+ const components = new Set(available.component);
33
+ const patterns = new Set(available.pattern);
34
+ const lanes = { component: [], pattern: [] };
35
+ const unknown = [];
36
+ const ambiguous = [];
37
+ for (const id of requested) {
38
+ const isComponent = components.has(id);
39
+ const isPattern = patterns.has(id);
40
+ if (isComponent && isPattern) {
41
+ ambiguous.push(id);
42
+ }
43
+ else if (isComponent) {
44
+ lanes.component.push(id);
45
+ }
46
+ else if (isPattern) {
47
+ lanes.pattern.push(id);
48
+ }
49
+ else {
50
+ unknown.push(id);
51
+ }
52
+ }
53
+ return { lanes, unknown, ambiguous };
54
+ };
55
+ exports.splitCatalogIds = splitCatalogIds;
56
+ /** Quote ids for an error message: `"button", "card"`. */
57
+ const quoteIds = (ids) => ids.map((id) => `"${id}"`).join(', ');
58
+ exports.quoteIds = quoteIds;
@@ -1,7 +1,7 @@
1
1
  /**
2
2
  * Filesystem-backed implementation of the normalized store abstraction.
3
3
  *
4
- * It is a thin read view over the already-resolved `runtimeConfig.entries`, so the records it
4
+ * It is a thin read view over the already-resolved `runtimeConfig.entities`, so the records it
5
5
  * returns are the same normalized component/pattern records the build consumes today — discovery
6
6
  * and normalization (modern `*.handoff.*` + legacy `{dirname}.*`, react/handlebars/csf/plain-object
7
7
  * declarations, CSF story discovery, `entries` resolution) all happen upstream in
@@ -2,7 +2,7 @@
2
2
  /**
3
3
  * Filesystem-backed implementation of the normalized store abstraction.
4
4
  *
5
- * It is a thin read view over the already-resolved `runtimeConfig.entries`, so the records it
5
+ * It is a thin read view over the already-resolved `runtimeConfig.entities`, so the records it
6
6
  * returns are the same normalized component/pattern records the build consumes today — discovery
7
7
  * and normalization (modern `*.handoff.*` + legacy `{dirname}.*`, react/handlebars/csf/plain-object
8
8
  * declarations, CSF story discovery, `entries` resolution) all happen upstream in
@@ -42,8 +42,8 @@ class FilesystemComponentStore {
42
42
  this.context = context;
43
43
  }
44
44
  get records() {
45
- var _a, _b, _c;
46
- return (_c = (_b = (_a = this.context.runtimeConfig) === null || _a === void 0 ? void 0 : _a.entries) === null || _b === void 0 ? void 0 : _b.components) !== null && _c !== void 0 ? _c : {};
45
+ var _a, _b;
46
+ return (_b = (_a = this.context.runtimeConfig) === null || _a === void 0 ? void 0 : _a.entities.components) !== null && _b !== void 0 ? _b : {};
47
47
  }
48
48
  list() {
49
49
  return Object.values(this.records);
@@ -68,8 +68,8 @@ class FilesystemPatternStore {
68
68
  this.context = context;
69
69
  }
70
70
  get records() {
71
- var _a, _b, _c;
72
- return (_c = (_b = (_a = this.context.runtimeConfig) === null || _a === void 0 ? void 0 : _a.entries) === null || _b === void 0 ? void 0 : _b.patterns) !== null && _c !== void 0 ? _c : {};
71
+ var _a, _b;
72
+ return (_b = (_a = this.context.runtimeConfig) === null || _a === void 0 ? void 0 : _a.entities.patterns) !== null && _b !== void 0 ? _b : {};
73
73
  }
74
74
  list() {
75
75
  return Object.values(this.records);
@@ -116,8 +116,8 @@ class FilesystemPageStore {
116
116
  this.context = context;
117
117
  }
118
118
  get records() {
119
- var _a, _b, _c;
120
- return (_c = (_b = (_a = this.context.runtimeConfig) === null || _a === void 0 ? void 0 : _a.entries) === null || _b === void 0 ? void 0 : _b.pages) !== null && _c !== void 0 ? _c : {};
119
+ var _a, _b;
120
+ return (_b = (_a = this.context.runtimeConfig) === null || _a === void 0 ? void 0 : _a.entities.pages) !== null && _b !== void 0 ? _b : {};
121
121
  }
122
122
  list() {
123
123
  return Object.values(this.records);
@@ -23,6 +23,17 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
23
23
  step((generator = generator.apply(thisArg, _arguments || [])).next());
24
24
  });
25
25
  };
26
+ var __rest = (this && this.__rest) || function (s, e) {
27
+ var t = {};
28
+ for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0)
29
+ t[p] = s[p];
30
+ if (s != null && typeof Object.getOwnPropertySymbols === "function")
31
+ for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) {
32
+ if (e.indexOf(p[i]) < 0 && Object.prototype.propertyIsEnumerable.call(s, p[i]))
33
+ t[p[i]] = s[p[i]];
34
+ }
35
+ return t;
36
+ };
26
37
  var __asyncValues = (this && this.__asyncValues) || function (o) {
27
38
  if (!Symbol.asyncIterator) throw new TypeError("Symbol.asyncIterator is not defined.");
28
39
  var m = o[Symbol.asyncIterator], i;
@@ -32,6 +43,7 @@ var __asyncValues = (this && this.__asyncValues) || function (o) {
32
43
  };
33
44
  Object.defineProperty(exports, "__esModule", { value: true });
34
45
  exports.createRegistryStore = exports.RegistryAssetStore = exports.RegistryTokenStore = exports.filterPublishedPageIds = exports.searchPageCandidates = exports.RegistryPageStore = exports.RegistryPatternStore = exports.RegistryComponentStore = void 0;
46
+ const previews_1 = require("../catalog/previews");
35
47
  const drizzle_orm_1 = require("drizzle-orm");
36
48
  const schema_1 = require("../registry/db/schema");
37
49
  /**
@@ -52,6 +64,16 @@ const toTextFileResource = (row) => {
52
64
  contentType: row.contentType,
53
65
  };
54
66
  };
67
+ /**
68
+ * Restore source order after jsonb storage without exposing transfer-only metadata. `entries` and
69
+ * `componentExport` describe the publisher's source layout and exist for checkout. The workspace
70
+ * has no equivalent, so the docs API must not serve them.
71
+ */
72
+ const readComponentRecord = (record) => {
73
+ var _a;
74
+ const { previewOrder, entries, componentExport } = record, item = __rest(record, ["previewOrder", "entries", "componentExport"]);
75
+ return Object.assign(Object.assign({}, item), { previews: (0, previews_1.orderPreviews)((_a = item.previews) !== null && _a !== void 0 ? _a : {}, previewOrder !== null && previewOrder !== void 0 ? previewOrder : []) });
76
+ };
55
77
  class RegistryComponentStore {
56
78
  constructor(context) {
57
79
  this.context = context;
@@ -59,14 +81,13 @@ class RegistryComponentStore {
59
81
  list() {
60
82
  return __awaiter(this, void 0, void 0, function* () {
61
83
  const rows = yield this.context.db.select({ record: schema_1.components.record }).from(schema_1.components);
62
- return rows.map((row) => row.record);
84
+ return rows.map((row) => readComponentRecord(row.record));
63
85
  });
64
86
  }
65
87
  get(id) {
66
88
  return __awaiter(this, void 0, void 0, function* () {
67
- var _a, _b;
68
89
  const rows = yield this.context.db.select({ record: schema_1.components.record }).from(schema_1.components).where((0, drizzle_orm_1.eq)(schema_1.components.id, id)).limit(1);
69
- return (_b = (_a = rows[0]) === null || _a === void 0 ? void 0 : _a.record) !== null && _b !== void 0 ? _b : null;
90
+ return rows[0] ? readComponentRecord(rows[0].record) : null;
70
91
  });
71
92
  }
72
93
  /**
@@ -19,6 +19,7 @@ const path_1 = __importDefault(require("path"));
19
19
  const react_1 = __importDefault(require("react"));
20
20
  const server_1 = __importDefault(require("react-dom/server"));
21
21
  const react_element_to_jsx_string_1 = __importDefault(require("react-element-to-jsx-string"));
22
+ const catalog_1 = require("../../catalog");
22
23
  const logger_1 = require("../../utils/logger");
23
24
  const docgen_1 = require("../docgen");
24
25
  const component_1 = require("../preview/component");
@@ -304,8 +305,9 @@ function csfRenderPlugin(componentData, _documentationComponents, handoff) {
304
305
  const generatedProperties = Object.assign({}, explicitProperties);
305
306
  const incomingPreviews = Object.assign({}, (componentData.previews || {}));
306
307
  const patternPreviews = Object.fromEntries(Object.entries(incomingPreviews).filter(([key]) => key.startsWith('__pattern_')));
307
- // Include all named stories as previews and preserve injected pattern previews.
308
- componentData.previews = Object.assign(Object.assign({}, (0, csf_1.createCsfStoryPreviews)(moduleExports)), patternPreviews);
308
+ // Include all named stories as previews and preserve injected pattern previews. Story order
309
+ // comes from the source text: esbuild emits its export map alphabetically.
310
+ componentData.previews = Object.assign(Object.assign({}, (0, catalog_1.orderPreviews)((0, csf_1.createCsfStoryPreviews)(moduleExports), (0, catalog_1.readExportOrderFromSource)(sourceCode))), patternPreviews);
309
311
  for (const [storyKey, storyValue] of stories) {
310
312
  const storyObj = (typeof storyValue === 'function' ? {} : storyValue);
311
313
  const mergedArgs = Object.assign(Object.assign({}, (meta.args || {})), (storyObj.args || {}));
@@ -49,6 +49,13 @@ const PLUGIN_CONSTANTS = {
49
49
  const CLIENT_ARTIFACT_SUFFIX = 'client.js';
50
50
  /** Logical artifact path of a component's client/hydration bundle. */
51
51
  const clientArtifactPath = (componentId) => `component/${componentId}.${CLIENT_ARTIFACT_SUFFIX}`;
52
+ function selectComponentExport(moduleExports, exportName) {
53
+ var _a;
54
+ if (exportName && exportName !== 'default' && (moduleExports === null || moduleExports === void 0 ? void 0 : moduleExports[exportName])) {
55
+ return moduleExports[exportName];
56
+ }
57
+ return (_a = moduleExports === null || moduleExports === void 0 ? void 0 : moduleExports.default) !== null && _a !== void 0 ? _a : null;
58
+ }
52
59
  /**
53
60
  * Loads and processes component schema using hierarchical approach
54
61
  * @param componentData - Component transformation data
@@ -70,7 +77,7 @@ function loadComponentSchemaAndModule(componentData, componentPath, handoff) {
70
77
  if (!((_b = componentData.entries) === null || _b === void 0 ? void 0 : _b.schema)) {
71
78
  try {
72
79
  const moduleExports = yield (0, module_1.buildAndEvaluateModule)(componentPath, handoff);
73
- component = moduleExports.exports.default;
80
+ component = selectComponentExport(moduleExports.exports, componentData.componentExport);
74
81
  // Try to load schema from component exports
75
82
  properties = yield (0, schema_loader_1.loadSchemaFromComponent)(moduleExports.exports, handoff);
76
83
  // If no schema found, use shared docgen fallback
@@ -86,7 +93,7 @@ function loadComponentSchemaAndModule(componentData, componentPath, handoff) {
86
93
  if (!component) {
87
94
  try {
88
95
  const moduleExports = yield (0, module_1.buildAndEvaluateModule)(componentPath, handoff);
89
- component = moduleExports.exports.default;
96
+ component = selectComponentExport(moduleExports.exports, componentData.componentExport);
90
97
  }
91
98
  catch (error) {
92
99
  logger_1.Logger.error(`Failed to load component for rendering "${componentPath}":`, error);
@@ -111,13 +118,15 @@ function loadComponentSchemaAndModule(componentData, componentPath, handoff) {
111
118
  *
112
119
  * @param componentId - Component identifier used to match pattern mount points to this bundle
113
120
  * @param componentPath - Path to the component file
121
+ * @param componentExport - Export holding the implementation; the default export when absent
114
122
  * @returns Client-side hydration source code
115
123
  */
116
- function generateClientHydrationSource(componentId, componentPath) {
124
+ function generateClientHydrationSource(componentId, componentPath, componentExport) {
125
+ const importClause = componentExport && componentExport !== 'default' ? `{ ${componentExport} as Component }` : 'Component';
117
126
  return `
118
127
  import React from 'react';
119
128
  import { hydrateRoot } from 'react-dom/client';
120
- import Component from '${(0, vite_1.normalizePath)(componentPath)}';
129
+ import ${importClause} from '${(0, vite_1.normalizePath)(componentPath)}';
121
130
 
122
131
  const parseProps = (propsId) => {
123
132
  const raw = propsId ? document.getElementById(propsId)?.textContent : '{}';
@@ -266,7 +275,7 @@ function ssrRenderPlugin(componentData, documentationComponents, handoff) {
266
275
  // `component/<id>.client.js` artifact. The hydration source only imports the component and
267
276
  // reads props from the in-document `__APP_PROPS__` element, so it is identical across every
268
277
  // preview of this component.
269
- const clientHydrationSource = generateClientHydrationSource(componentId, componentPath);
278
+ const clientHydrationSource = generateClientHydrationSource(componentId, componentPath, componentData.componentExport);
270
279
  const clientBuildConfig = Object.assign(Object.assign({}, build_1.DEFAULT_CLIENT_BUILD_CONFIG), { logLevel: 'silent', stdin: {
271
280
  contents: clientHydrationSource,
272
281
  resolveDir: process.cwd(),
@@ -1,5 +1,13 @@
1
1
  import Handoff from '../../../index';
2
2
  import { ComponentListObject, TransformComponentTokensResult } from '../types';
3
+ /**
4
+ * Strip the fields that must not reach the written artifact.
5
+ *
6
+ * The docs API serves this artifact publicly, and publish uploads it verbatim. An absolute path in
7
+ * the artifact exposes the filesystem layout of the build machine, so this drops the workspace
8
+ * locations `path` and `entries`. The build takes those locations from the runtime config, and
9
+ * publish remaps `entries` onto the registry record.
10
+ */
3
11
  export declare const sanitizeComponentApiData: (component: TransformComponentTokensResult) => TransformComponentTokensResult;
4
12
  export declare const getAPIPath: (handoff: Handoff) => string;
5
13
  export declare const writeComponentApi: (id: string, component: TransformComponentTokensResult, handoff: Handoff, preserveKeys?: string[]) => Promise<void>;