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
@@ -0,0 +1,115 @@
1
+ "use strict";
2
+ var __rest = (this && this.__rest) || function (s, e) {
3
+ var t = {};
4
+ for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0)
5
+ t[p] = s[p];
6
+ if (s != null && typeof Object.getOwnPropertySymbols === "function")
7
+ for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) {
8
+ if (e.indexOf(p[i]) < 0 && Object.prototype.propertyIsEnumerable.call(s, p[i]))
9
+ t[p[i]] = s[p[i]];
10
+ }
11
+ return t;
12
+ };
13
+ var __importDefault = (this && this.__importDefault) || function (mod) {
14
+ return (mod && mod.__esModule) ? mod : { "default": mod };
15
+ };
16
+ Object.defineProperty(exports, "__esModule", { value: true });
17
+ exports.normalizeCatalogItem = void 0;
18
+ const lodash_1 = require("lodash");
19
+ const path_1 = __importDefault(require("path"));
20
+ const define_1 = require("./define");
21
+ const implementation_1 = require("./implementation");
22
+ const previews_1 = require("./previews");
23
+ const renderers_1 = require("./renderers");
24
+ /** Imported component values need a file path recovered from the declaration source. */
25
+ const resolveImplementation = (item, options) => {
26
+ const stamped = item.implementation;
27
+ const source = { renderer: stamped.renderer, sourceFormat: stamped.format };
28
+ if (stamped.file) {
29
+ return Object.assign(Object.assign({}, source), { file: stamped.file });
30
+ }
31
+ const declarationDir = path_1.default.dirname(options.declarationPath);
32
+ const resolved = (0, implementation_1.resolvePropertySource)(options.declarationPath, 'implementation');
33
+ if (resolved) {
34
+ if (!(0, implementation_1.isInsideDirectory)(resolved.file, declarationDir)) {
35
+ options.warn(`Catalog item "${options.fallbackId}" resolves its implementation to "${resolved.file}", outside "${declarationDir}". ` +
36
+ `Previews still build, but the item cannot be published. Move the file into the item directory to publish it.`);
37
+ }
38
+ return Object.assign(Object.assign({}, source), { file: resolved.file, exportName: resolved.exportName });
39
+ }
40
+ const sibling = (0, implementation_1.findSiblingComponentFile)(declarationDir, options.declarationPath);
41
+ if (sibling) {
42
+ options.warn(`Catalog item "${options.fallbackId}" in "${options.declarationPath}" could not resolve the import behind ` +
43
+ `"implementation". Using "${path_1.default.basename(sibling)}". Set "implementation" to the file path to be explicit.`);
44
+ return Object.assign(Object.assign({}, source), { file: sibling });
45
+ }
46
+ options.warn(`Catalog item "${options.fallbackId}" in "${options.declarationPath}" could not resolve the import behind ` +
47
+ `"implementation". Set "implementation" to the file path instead of the imported value.`);
48
+ return source;
49
+ };
50
+ const quotedList = (values) => values.map((value) => `"${value}"`).join(' or ');
51
+ /** Maps the authoring contract directly into the runtime read model. */
52
+ const normalizeCatalogItem = (moduleExports, options) => {
53
+ var _a, _b, _c;
54
+ const item = (_a = moduleExports.default) !== null && _a !== void 0 ? _a : moduleExports;
55
+ (0, define_1.validateCatalogItem)(item);
56
+ const { implementation, composition, __args, name, shouldDo, shouldNotDo } = item, meta = __rest(item, ["implementation", "composition", "__args", "name", "shouldDo", "shouldNotDo"]);
57
+ const id = typeof meta.id === 'string' && meta.id.trim() ? meta.id.trim() : options.fallbackId;
58
+ const title = name || (0, lodash_1.startCase)(id);
59
+ const directory = path_1.default.dirname(options.declarationPath);
60
+ if (composition) {
61
+ return {
62
+ kind: 'pattern',
63
+ item: {
64
+ id,
65
+ title,
66
+ path: directory,
67
+ description: meta.description,
68
+ group: meta.group,
69
+ tags: meta.tags,
70
+ components: composition.map((ref) => ({
71
+ id: ref.ref.trim(),
72
+ preview: typeof ref.preview === 'string' ? ref.preview.trim() : undefined,
73
+ args: ref.args ? Object.assign({}, ref.args) : undefined,
74
+ })),
75
+ },
76
+ };
77
+ }
78
+ const source = resolveImplementation(item, options);
79
+ const entries = {};
80
+ for (const [key, value] of Object.entries((_b = meta.entries) !== null && _b !== void 0 ? _b : {})) {
81
+ if (value)
82
+ entries[key] = path_1.default.resolve(directory, value);
83
+ }
84
+ const entryKey = (0, renderers_1.entryKeyFor)(source.renderer, source.sourceFormat);
85
+ if (!source.file) {
86
+ throw new Error(`Catalog item "${id}" needs an implementation file. Set implementation to a file path.`);
87
+ }
88
+ entries[entryKey] = path_1.default.resolve(directory, source.file);
89
+ // Existing builders consume template as the primary build input for every renderer.
90
+ entries.template = entries[entryKey];
91
+ // A wrong renderer builds a broken preview without failing on its own, so a mismatch is fatal.
92
+ // `config/runtime.ts` turns the throw into a skipped item that retries on the next save.
93
+ const claim = (0, renderers_1.sourceForFile)(source.file);
94
+ if (claim && !claim.renderers.includes(source.renderer)) {
95
+ throw new Error(`Catalog item "${id}" declares renderer "${source.renderer}", but "${source.file}" is a ${quotedList(claim.renderers)} source. ` +
96
+ `Declare it from ${quotedList(claim.renderers.map(renderers_1.moduleFor))}.`);
97
+ }
98
+ // Only a suffix that names a format is conclusive. A `.stories.tsx` file is always CSF, while a
99
+ // plain `.tsx` file says nothing about its format.
100
+ const fileFormat = claim === null || claim === void 0 ? void 0 : claim.format;
101
+ // The comparison narrows a single-format union to `never`, so read the helper first.
102
+ const fileFormatHelper = fileFormat && renderers_1.SOURCE_FORMATS[fileFormat].helper;
103
+ if (fileFormat && fileFormat !== source.sourceFormat) {
104
+ throw new Error(`Catalog item "${id}" declares "${source.file}" as a plain source, but it is a "${fileFormat}" source. ` +
105
+ `Wrap it with ${fileFormatHelper}() from "${(0, renderers_1.moduleFor)(source.renderer)}".`);
106
+ }
107
+ if (((_c = meta.page) === null || _c === void 0 ? void 0 : _c.slices) && !Array.isArray(meta.page.slices)) {
108
+ throw new Error(`Catalog item "${id}" has invalid page.slices; expected an array.`);
109
+ }
110
+ return {
111
+ kind: 'component',
112
+ item: Object.assign(Object.assign(Object.assign(Object.assign({}, meta), { id, title: name || '', path: directory, should_do: shouldDo, should_not_do: shouldNotDo, renderer: source.renderer, sourceFormat: source.sourceFormat, entries }), (source.exportName && source.exportName !== 'default' ? { componentExport: source.exportName } : {})), { previews: (0, previews_1.createCatalogPreviews)(moduleExports, options.declarationPath) }),
113
+ };
114
+ };
115
+ exports.normalizeCatalogItem = normalizeCatalogItem;
@@ -0,0 +1,18 @@
1
+ import type { OptionalPreviewRender } from '../transformers/preview/types';
2
+ /**
3
+ * Exported names in the order the source declares them.
4
+ *
5
+ * The evaluated module cannot supply this order. esbuild emits its export map alphabetically, and
6
+ * an ESM namespace object has sorted keys by specification.
7
+ */
8
+ export declare const readExportOrderFromSource: (sourceCode: string) => string[];
9
+ /** Exported names in source order, or an empty list when the file cannot be read. */
10
+ export declare const readExportOrder: (filePath: string) => string[];
11
+ /**
12
+ * Re-keys previews into the given order. A preview the order does not mention is placed after the
13
+ * ordered ones, so an export form the parser does not recognize falls back to the evaluated order
14
+ * rather than dropping the preview.
15
+ */
16
+ export declare const orderPreviews: <T>(previews: Record<string, T>, order: string[]) => Record<string, T>;
17
+ /** Collects the named exports of a declaration module as previews, in declaration order. */
18
+ export declare const createCatalogPreviews: (moduleExports: Record<string, unknown>, declarationPath?: string) => Record<string, OptionalPreviewRender>;
@@ -0,0 +1,106 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.createCatalogPreviews = exports.orderPreviews = exports.readExportOrder = exports.readExportOrderFromSource = void 0;
7
+ const fs_extra_1 = __importDefault(require("fs-extra"));
8
+ const lodash_1 = require("lodash");
9
+ /**
10
+ * A declaration module exports more than previews: the item itself, the implementation it
11
+ * re-exports, helper values. Accept only a plain object that owns `args` or `name`, so an exported
12
+ * component or helper is never mistaken for a preview.
13
+ *
14
+ * `getCsfStoryEntries` does not fit here: on a CSF file every named export is a story, and on a
15
+ * declaration file it is not.
16
+ */
17
+ const isPreviewDeclaration = (value) => {
18
+ if (!value || typeof value !== 'object' || Array.isArray(value))
19
+ return false;
20
+ return Object.prototype.hasOwnProperty.call(value, 'args') || Object.prototype.hasOwnProperty.call(value, 'name');
21
+ };
22
+ /** Matches an exported binding, an export list, or a CommonJS export assignment. */
23
+ const EXPORT_PATTERNS = [
24
+ { pattern: /\bexport\s+(?:async\s+)?(?:const|let|var|function\s*\*?|class)\s+([A-Za-z_$][\w$]*)/g },
25
+ { pattern: /\bexport\s*(?!type[\s{])\{([^}]*)\}/g, list: true },
26
+ { pattern: /\bexports\.([A-Za-z_$][\w$]*)\s*=/g },
27
+ ];
28
+ /** Reads the exported name an export-list specifier binds: `A as B` exports `B`, `A` exports `A`. */
29
+ const readExportedName = (specifier) => {
30
+ const name = specifier.trim().replace(/^type\s+/, '');
31
+ if (!name)
32
+ return undefined;
33
+ const aliased = name.match(/^[A-Za-z_$][\w$]*\s+as\s+([A-Za-z_$][\w$]*)$/);
34
+ if (aliased)
35
+ return aliased[1];
36
+ return /^[A-Za-z_$][\w$]*$/.test(name) ? name : undefined;
37
+ };
38
+ /**
39
+ * Exported names in the order the source declares them.
40
+ *
41
+ * The evaluated module cannot supply this order. esbuild emits its export map alphabetically, and
42
+ * an ESM namespace object has sorted keys by specification.
43
+ */
44
+ const readExportOrderFromSource = (sourceCode) => {
45
+ const found = [];
46
+ for (const { pattern, list } of EXPORT_PATTERNS) {
47
+ for (const match of sourceCode.matchAll(pattern)) {
48
+ const names = list ? match[1].split(',').map(readExportedName) : [match[1]];
49
+ names.forEach((name, offset) => {
50
+ if (name)
51
+ found.push({ index: match.index + offset, name });
52
+ });
53
+ }
54
+ }
55
+ const order = [];
56
+ const seen = new Set();
57
+ for (const { name } of found.sort((a, b) => a.index - b.index)) {
58
+ if (seen.has(name))
59
+ continue;
60
+ seen.add(name);
61
+ order.push(name);
62
+ }
63
+ return order;
64
+ };
65
+ exports.readExportOrderFromSource = readExportOrderFromSource;
66
+ /** Exported names in source order, or an empty list when the file cannot be read. */
67
+ const readExportOrder = (filePath) => {
68
+ try {
69
+ return (0, exports.readExportOrderFromSource)(fs_extra_1.default.readFileSync(filePath, 'utf8'));
70
+ }
71
+ catch (_a) {
72
+ return [];
73
+ }
74
+ };
75
+ exports.readExportOrder = readExportOrder;
76
+ /**
77
+ * Re-keys previews into the given order. A preview the order does not mention is placed after the
78
+ * ordered ones, so an export form the parser does not recognize falls back to the evaluated order
79
+ * rather than dropping the preview.
80
+ */
81
+ const orderPreviews = (previews, order) => {
82
+ if (order.length === 0)
83
+ return previews;
84
+ const ranked = new Set(order.filter((name) => name in previews));
85
+ const rest = Object.keys(previews).filter((name) => !ranked.has(name));
86
+ return Object.fromEntries([...ranked, ...rest].map((name) => [name, previews[name]]));
87
+ };
88
+ exports.orderPreviews = orderPreviews;
89
+ /** Collects the named exports of a declaration module as previews, in declaration order. */
90
+ const createCatalogPreviews = (moduleExports, declarationPath) => {
91
+ var _a, _b;
92
+ const previews = {};
93
+ for (const [exportName, value] of Object.entries(moduleExports !== null && moduleExports !== void 0 ? moduleExports : {})) {
94
+ if (exportName === 'default' || exportName === '__esModule')
95
+ continue;
96
+ if (!isPreviewDeclaration(value))
97
+ continue;
98
+ if (value && typeof value === 'object' && ('values' in value || 'title' in value)) {
99
+ throw new Error(`Preview "${exportName}" uses removed fields. Use args and name. See UPGRADE.md#catalog-items.`);
100
+ }
101
+ const preview = value;
102
+ previews[exportName] = Object.assign({ title: preview.name || (0, lodash_1.startCase)(exportName), values: Object.assign({}, ((_a = preview.args) !== null && _a !== void 0 ? _a : {})), url: (_b = preview.url) !== null && _b !== void 0 ? _b : '' }, (preview.usage ? { usage: preview.usage } : {}));
103
+ }
104
+ return declarationPath ? (0, exports.orderPreviews)(previews, (0, exports.readExportOrder)(declarationPath)) : previews;
105
+ };
106
+ exports.createCatalogPreviews = createCatalogPreviews;
@@ -0,0 +1,43 @@
1
+ /**
2
+ * This module has no runtime imports, so every layer can use the renderer registry.
3
+ * Import it directly to avoid the catalog barrel dependencies, including `fs-extra`.
4
+ */
5
+ /** Add a renderer only with its preview support. */
6
+ export type RendererKind = 'react' | 'handlebars';
7
+ /** How an implementation file is written, independent of the renderer that renders it. */
8
+ export type SourceFormat = 'csf';
9
+ /** Key in `entries` that holds an implementation file. */
10
+ export type EntryKey = 'component' | 'template' | 'story';
11
+ export type ComponentSource = {
12
+ renderer: RendererKind;
13
+ sourceFormat?: SourceFormat;
14
+ };
15
+ type RendererDefinition = {
16
+ /** Declaration module that supplies this renderer. */
17
+ module: string;
18
+ /** Entry key normalization writes the implementation file to. */
19
+ entryKey: EntryKey;
20
+ };
21
+ type SourceFormatDefinition = {
22
+ /** Renderers this format applies to. */
23
+ renderers: RendererKind[];
24
+ entryKey: EntryKey;
25
+ /** Export of the renderer module that declares an implementation in this format. */
26
+ helper: string;
27
+ };
28
+ export declare const RENDERERS: Record<RendererKind, RendererDefinition>;
29
+ export declare const SOURCE_FORMATS: Record<SourceFormat, SourceFormatDefinition>;
30
+ /** Which renderers can own a file name, and the source format the name implies. */
31
+ export type SourceClaim = {
32
+ renderers: RendererKind[];
33
+ format?: SourceFormat;
34
+ };
35
+ export declare const isRendererKind: (value: unknown) => value is RendererKind;
36
+ export declare const isSourceFormat: (value: unknown) => value is SourceFormat;
37
+ /** What the file name says about a source, matched case-insensitively. Undefined when no suffix claims it. */
38
+ export declare const sourceForFile: (file: string | undefined) => SourceClaim | undefined;
39
+ /** Entry key normalization records the implementation file under, if the renderer is registered. */
40
+ export declare const entryKeyFor: (renderer: RendererKind | undefined, sourceFormat?: SourceFormat) => EntryKey | undefined;
41
+ /** Declaration module that supplies a renderer, or undefined when it has no module of its own. */
42
+ export declare const moduleFor: (renderer: RendererKind | undefined) => string | undefined;
43
+ export {};
@@ -0,0 +1,62 @@
1
+ "use strict";
2
+ /**
3
+ * This module has no runtime imports, so every layer can use the renderer registry.
4
+ * Import it directly to avoid the catalog barrel dependencies, including `fs-extra`.
5
+ */
6
+ Object.defineProperty(exports, "__esModule", { value: true });
7
+ exports.moduleFor = exports.entryKeyFor = exports.sourceForFile = exports.isSourceFormat = exports.isRendererKind = exports.SOURCE_FORMATS = exports.RENDERERS = void 0;
8
+ exports.RENDERERS = {
9
+ react: {
10
+ module: 'handoff-app/react',
11
+ entryKey: 'component',
12
+ },
13
+ handlebars: {
14
+ module: 'handoff-app/handlebars',
15
+ entryKey: 'template',
16
+ },
17
+ };
18
+ exports.SOURCE_FORMATS = {
19
+ csf: {
20
+ renderers: ['react'],
21
+ entryKey: 'story',
22
+ helper: 'fromCSF',
23
+ },
24
+ };
25
+ /**
26
+ * This table never decides a renderer. The declaration states it, and the table only checks the
27
+ * implementation file against it. Keying by suffix keeps one row per file name: a suffix that more
28
+ * than one renderer owns lists them all, and a duplicate key is a compile error. A file that no
29
+ * row claims is not checked.
30
+ */
31
+ const SOURCE_SUFFIXES = {
32
+ '.stories.tsx': { renderers: ['react'], format: 'csf' },
33
+ '.stories.jsx': { renderers: ['react'], format: 'csf' },
34
+ '.stories.ts': { renderers: ['react'], format: 'csf' },
35
+ '.stories.js': { renderers: ['react'], format: 'csf' },
36
+ '.tsx': { renderers: ['react'] },
37
+ '.jsx': { renderers: ['react'] },
38
+ '.hbs': { renderers: ['handlebars'] },
39
+ };
40
+ /** Longest first, so `.stories.tsx` reads as a CSF source rather than a plain React source. */
41
+ const SUFFIXES_BY_LENGTH = Object.keys(SOURCE_SUFFIXES).sort((first, second) => second.length - first.length);
42
+ const RENDERER_KINDS = Object.keys(exports.RENDERERS);
43
+ const SOURCE_FORMAT_KINDS = Object.keys(exports.SOURCE_FORMATS);
44
+ const isRendererKind = (value) => RENDERER_KINDS.includes(value);
45
+ exports.isRendererKind = isRendererKind;
46
+ const isSourceFormat = (value) => SOURCE_FORMAT_KINDS.includes(value);
47
+ exports.isSourceFormat = isSourceFormat;
48
+ /** What the file name says about a source, matched case-insensitively. Undefined when no suffix claims it. */
49
+ const sourceForFile = (file) => {
50
+ if (!file)
51
+ return undefined;
52
+ const lowerCased = file.toLowerCase();
53
+ const suffix = SUFFIXES_BY_LENGTH.find((candidate) => lowerCased.endsWith(candidate));
54
+ return suffix ? SOURCE_SUFFIXES[suffix] : undefined;
55
+ };
56
+ exports.sourceForFile = sourceForFile;
57
+ /** Entry key normalization records the implementation file under, if the renderer is registered. */
58
+ const entryKeyFor = (renderer, sourceFormat) => { var _a, _b; return sourceFormat ? (_a = exports.SOURCE_FORMATS[sourceFormat]) === null || _a === void 0 ? void 0 : _a.entryKey : renderer ? (_b = exports.RENDERERS[renderer]) === null || _b === void 0 ? void 0 : _b.entryKey : undefined; };
59
+ exports.entryKeyFor = entryKeyFor;
60
+ /** Declaration module that supplies a renderer, or undefined when it has no module of its own. */
61
+ const moduleFor = (renderer) => { var _a; return (renderer ? (_a = exports.RENDERERS[renderer]) === null || _a === void 0 ? void 0 : _a.module : undefined); };
62
+ exports.moduleFor = moduleFor;
@@ -0,0 +1,63 @@
1
+ import type { RendererKind, SourceFormat } from './renderers';
2
+ import type { ComponentObject } from '../transformers/preview/types';
3
+ /**
4
+ * How an implementation file is written. `fromCSF('./Button.stories.tsx')` is shorthand for
5
+ * `{ format: 'csf', file: './Button.stories.tsx' }`.
6
+ */
7
+ export type SourceDescriptor<TFormat extends string = string> = {
8
+ format: TFormat;
9
+ file: string;
10
+ };
11
+ export type CsfSource = SourceDescriptor<'csf'>;
12
+ /** The loader recovers the file for an imported component in `value` from the declaration source. */
13
+ export type NormalizedImplementation = {
14
+ renderer: RendererKind;
15
+ format?: SourceFormat;
16
+ file?: string;
17
+ value?: unknown;
18
+ };
19
+ export type CatalogItemEntries = Pick<NonNullable<ComponentObject['entries']>, 'js' | 'scss' | 'schema' | 'templates'>;
20
+ /**
21
+ * Metadata a catalog item can carry. `type` and `categories` are free-form classification, so a
22
+ * term such as atom, element, or block goes there.
23
+ */
24
+ export type CatalogItemMeta = Partial<Omit<ComponentObject, 'previews' | 'previewOrder' | 'entries' | 'title' | 'should_do' | 'should_not_do' | 'renderer' | 'sourceFormat' | 'componentExport'>> & {
25
+ id?: string;
26
+ name?: string;
27
+ description?: string;
28
+ group?: string;
29
+ entries?: CatalogItemEntries;
30
+ shouldDo?: string[];
31
+ shouldNotDo?: string[];
32
+ };
33
+ /** One member of a composition. `ref` is the id of another catalog item. */
34
+ export type CompositionRef = {
35
+ ref: string;
36
+ preview?: string;
37
+ args?: Record<string, unknown>;
38
+ };
39
+ /** A named preview, exported next to the catalog item it belongs to. */
40
+ export type CatalogPreview<TArgs = Record<string, unknown>> = {
41
+ /** Display title. Defaults to a start-cased form of the export name. */
42
+ name?: string;
43
+ args?: TArgs;
44
+ usage?: string;
45
+ url?: string;
46
+ };
47
+ /**
48
+ * `__args` exists only in the type system so `Preview<typeof item>` can infer the preview argument type.
49
+ */
50
+ export type CatalogItem<TArgs = Record<string, unknown>> = CatalogItemMeta & {
51
+ implementation?: unknown;
52
+ composition?: CompositionRef[];
53
+ readonly __args?: TArgs;
54
+ };
55
+ /** Preview type for a catalog item, with its argument type applied. */
56
+ export type Preview<TItem> = TItem extends {
57
+ readonly __args?: infer TArgs;
58
+ } ? CatalogPreview<Partial<NonNullable<TArgs>>> : never;
59
+ /** Input accepted by every `defineCatalogItem` entry point. */
60
+ export type CatalogItemInput = CatalogItemMeta & {
61
+ implementation?: unknown;
62
+ composition?: CompositionRef[];
63
+ };
@@ -1,9 +1,4 @@
1
1
  import Handoff from '../index';
2
- /**
3
- * Make a new exportable component
4
- * @param handoff
5
- */
6
- export declare const makeTemplate: (handoff: Handoff, component: string, state: string) => Promise<Handoff>;
7
2
  /**
8
3
  * Make a new docs page
9
4
  * @param handoff
package/dist/cli/make.js CHANGED
@@ -35,54 +35,13 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
35
35
  return (mod && mod.__esModule) ? mod : { "default": mod };
36
36
  };
37
37
  Object.defineProperty(exports, "__esModule", { value: true });
38
- exports.makeComponent = exports.makePage = exports.makeTemplate = void 0;
38
+ exports.makeComponent = exports.makePage = void 0;
39
+ const catalog_include_1 = require("../config/catalog-include");
40
+ const runtime_1 = require("../config/runtime");
39
41
  const p = __importStar(require("@clack/prompts"));
40
42
  const fs_extra_1 = __importDefault(require("fs-extra"));
41
43
  const path_1 = __importDefault(require("path"));
42
44
  const logger_1 = require("../utils/logger");
43
- /**
44
- * Make a new exportable component
45
- * @param handoff
46
- */
47
- const makeTemplate = (handoff, component, state) => __awaiter(void 0, void 0, void 0, function* () {
48
- var _a, _b;
49
- if (!((_b = (_a = handoff === null || handoff === void 0 ? void 0 : handoff.runtimeConfig) === null || _a === void 0 ? void 0 : _a.entries) === null || _b === void 0 ? void 0 : _b.templates)) {
50
- logger_1.Logger.error(`Runtime config does not specify entry for templates.`);
51
- return;
52
- }
53
- if (!component) {
54
- logger_1.Logger.error(`Template component must be set`);
55
- return;
56
- }
57
- if (!state) {
58
- state = 'default';
59
- }
60
- if (!/^[a-z0-9]+$/i.test(component)) {
61
- logger_1.Logger.error(`Template component must be alphanumeric and may contain dashes or underscores`);
62
- return;
63
- }
64
- if (!/^[a-z0-9]+$/i.test(state)) {
65
- logger_1.Logger.error(`Template state must be alphanumeric and may contain dashes or underscores`);
66
- return;
67
- }
68
- const workingPath = path_1.default.resolve(handoff.runtimeConfig.entries.templates, component);
69
- if (!fs_extra_1.default.existsSync(workingPath)) {
70
- fs_extra_1.default.mkdirSync(workingPath, { recursive: true });
71
- }
72
- const target = path_1.default.resolve(workingPath, `${state}.html`);
73
- if (fs_extra_1.default.existsSync(target)) {
74
- if (!handoff.force) {
75
- logger_1.Logger.warn(`'${state}' already exists as custom template. Use the --force flag revert it to default.`);
76
- return;
77
- }
78
- }
79
- const templatePath = path_1.default.resolve(path_1.default.join(handoff.modulePath, 'config/templates', 'template.html'));
80
- const template = fs_extra_1.default.readFileSync(templatePath, 'utf8');
81
- fs_extra_1.default.writeFileSync(target, template);
82
- logger_1.Logger.success(`New template ${state}.html was created in ${workingPath}`);
83
- return handoff;
84
- });
85
- exports.makeTemplate = makeTemplate;
86
45
  /**
87
46
  * Make a new docs page
88
47
  * @param handoff
@@ -136,22 +95,15 @@ exports.makePage = makePage;
136
95
  */
137
96
  const DEFAULT_COMPONENTS_DIR = 'components';
138
97
  const makeComponent = (handoff, name) => __awaiter(void 0, void 0, void 0, function* () {
139
- var _a, _b, _c;
98
+ var _a, _b;
140
99
  if (!name) {
141
100
  logger_1.Logger.error(`Component name must be set`);
142
101
  return;
143
102
  }
144
103
  name = name.replace('.html', '');
145
- let componentsRoot;
146
- if ((_c = (_b = (_a = handoff.config) === null || _a === void 0 ? void 0 : _a.entries) === null || _b === void 0 ? void 0 : _b.components) === null || _c === void 0 ? void 0 : _c.length) {
147
- componentsRoot = path_1.default.resolve(handoff.workingPath, handoff.config.entries.components[0]);
148
- }
149
- else {
150
- componentsRoot = path_1.default.resolve(handoff.workingPath, DEFAULT_COMPONENTS_DIR);
151
- logger_1.Logger.warn(`No entries.components configured in handoff.config.*. ` +
152
- `Scaffolding into "${DEFAULT_COMPONENTS_DIR}/". ` +
153
- `Add this path to entries.components in your config so the build picks it up.`);
154
- }
104
+ const configuredRoot = (_b = (_a = handoff.config.catalog) === null || _a === void 0 ? void 0 : _a.include) === null || _b === void 0 ? void 0 : _b[0];
105
+ const root = path_1.default.resolve(handoff.workingPath, configuredRoot !== null && configuredRoot !== void 0 ? configuredRoot : DEFAULT_COMPONENTS_DIR);
106
+ const componentsRoot = (0, runtime_1.isComponentDirectory)(root) ? path_1.default.dirname(root) : root;
155
107
  let workingPath = path_1.default.resolve(componentsRoot, name);
156
108
  if (!fs_extra_1.default.existsSync(workingPath)) {
157
109
  fs_extra_1.default.mkdirSync(workingPath, { recursive: true });
@@ -188,34 +140,37 @@ const makeComponent = (handoff, name) => __awaiter(void 0, void 0, void 0, funct
188
140
  const scssTemplate = fs_extra_1.default.readFileSync(scssPath, 'utf8');
189
141
  fs_extra_1.default.writeFileSync(path_1.default.resolve(workingPath, `${name}.scss`), scssTemplate);
190
142
  }
191
- const declarationEntries = [`template: './${name}.hbs'`];
143
+ // `implementation` names the template, so `entries` carries only supporting files.
144
+ const declarationEntries = [];
192
145
  if (writeJSFile === true) {
193
146
  declarationEntries.push(`js: './${name}.js'`);
194
147
  }
195
148
  if (writeSassFile === true) {
196
149
  declarationEntries.push(`scss: './${name}.scss'`);
197
150
  }
198
- const declarationContent = `const { defineHandlebarsComponent } = require('handoff-app');
151
+ const entriesBlock = declarationEntries.length ? `\n entries: {\n ${declarationEntries.join(',\n ')}\n },` : '';
152
+ const declarationContent = `const { defineCatalogItem } = require('handoff-app/handlebars');
199
153
 
200
- module.exports = defineHandlebarsComponent({
154
+ exports.default = defineCatalogItem({
201
155
  id: '${name}',
202
156
  name: '',
203
157
  description: '',
204
158
  group: '',
205
159
  type: 'element',
206
- entries: {
207
- ${declarationEntries.join(',\n ')}
208
- },
209
- previews: {
210
- default: {
211
- title: 'Default',
212
- args: {}
213
- }
214
- }
160
+ implementation: './${name}.hbs',${entriesBlock}
215
161
  });
162
+
163
+ exports.Default = {
164
+ args: {},
165
+ };
216
166
  `;
217
167
  fs_extra_1.default.writeFileSync(path_1.default.resolve(workingPath, `${name}.handoff.js`), declarationContent);
218
168
  logger_1.Logger.success(`New component declaration ${name}.handoff.js was created in ${workingPath}`);
169
+ if (!(0, catalog_include_1.isIncluded)(handoff, workingPath)) {
170
+ const result = yield (0, catalog_include_1.addToCatalog)(handoff, [workingPath]);
171
+ if (result.status === 'unsupported')
172
+ logger_1.Logger.warn(`Add ${result.pending.join(', ')} to catalog.include in handoff.config.`);
173
+ }
219
174
  return handoff;
220
175
  });
221
176
  exports.makeComponent = makeComponent;