handoff-app 2.0.0-5 → 2.0.0-7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +174 -41
- package/UPGRADE.md +198 -0
- package/config/config.template.js +1 -4
- package/config/config.template.ts +1 -2
- package/dist/app-builder/build.js +11 -6
- package/dist/app-builder/client-config.js +1 -0
- package/dist/app-builder/config-diff/strategies/pattern.js +3 -3
- package/dist/app-builder/watchers/component.js +5 -5
- package/dist/app-builder/watchers/utils.d.ts +2 -3
- package/dist/app-builder/watchers.d.ts +8 -15
- package/dist/app-builder/watchers.js +53 -54
- package/dist/cache/build-cache.js +18 -22
- package/dist/catalog/define.d.ts +6 -0
- package/dist/catalog/define.js +72 -0
- package/dist/catalog/implementation.d.ts +27 -0
- package/dist/catalog/implementation.js +133 -0
- package/dist/catalog/index.d.ts +6 -0
- package/dist/catalog/index.js +22 -0
- package/dist/catalog/normalize.d.ts +16 -0
- package/dist/catalog/normalize.js +115 -0
- package/dist/catalog/previews.d.ts +18 -0
- package/dist/catalog/previews.js +106 -0
- package/dist/catalog/renderers.d.ts +43 -0
- package/dist/catalog/renderers.js +62 -0
- package/dist/catalog/types.d.ts +63 -0
- package/dist/cli/make.d.ts +0 -5
- package/dist/cli/make.js +22 -67
- package/dist/cli/scaffold.js +25 -33
- package/dist/commands/checkout/index.d.ts +7 -4
- package/dist/commands/checkout/index.js +9 -6
- package/dist/commands/compat/push-args.d.ts +2 -2
- package/dist/commands/index.js +0 -2
- package/dist/commands/publish/index.d.ts +9 -6
- package/dist/commands/publish/index.js +11 -8
- package/dist/commands/pull/index.js +1 -1
- package/dist/commands/push/index.js +1 -1
- package/dist/commands/utils.d.ts +15 -4
- package/dist/commands/utils.js +19 -6
- package/dist/config/catalog-include.d.ts +43 -0
- package/dist/config/catalog-include.js +203 -0
- package/dist/config/defaults.d.ts +2 -0
- package/dist/config/defaults.js +4 -1
- package/dist/config/index.d.ts +3 -3
- package/dist/config/index.js +6 -5
- package/dist/config/loader.js +9 -1
- package/dist/config/runtime.d.ts +5 -6
- package/dist/config/runtime.js +154 -154
- package/dist/handlebars.d.ts +9 -0
- package/dist/handlebars.js +16 -0
- package/dist/index.d.ts +15 -4
- package/dist/index.js +29 -15
- package/dist/pattern.d.ts +7 -0
- package/dist/pattern.js +13 -0
- package/dist/react.d.ts +43 -0
- package/dist/react.js +11 -0
- package/dist/registry/asset-storage/define.d.ts +1 -1
- package/dist/registry/asset-storage/define.js +1 -1
- package/dist/registry/checkout/index.d.ts +9 -0
- package/dist/registry/checkout/index.js +335 -149
- package/dist/registry/db/schema.d.ts +34 -0
- package/dist/registry/db/schema.js +2 -0
- package/dist/registry/publish/index.d.ts +13 -2
- package/dist/registry/publish/index.js +109 -25
- package/dist/registry/publish/package.js +14 -1
- package/dist/registry/selection.d.ts +20 -0
- package/dist/registry/selection.js +37 -1
- package/dist/store/filesystem.d.ts +1 -1
- package/dist/store/filesystem.js +7 -7
- package/dist/store/registry.js +24 -3
- package/dist/transformers/plugins/csf-render.js +4 -2
- package/dist/transformers/plugins/ssr-render.js +14 -5
- package/dist/transformers/preview/component/api.d.ts +8 -0
- package/dist/transformers/preview/component/api.js +11 -15
- package/dist/transformers/preview/component/artifacts.d.ts +1 -1
- package/dist/transformers/preview/component/artifacts.js +11 -7
- package/dist/transformers/preview/component/builder.js +15 -18
- package/dist/transformers/preview/component/css.d.ts +2 -0
- package/dist/transformers/preview/component/css.js +8 -11
- package/dist/transformers/preview/component/html.js +18 -16
- package/dist/transformers/preview/component/javascript.js +2 -4
- package/dist/transformers/preview/pattern/builder.js +31 -18
- package/dist/transformers/preview/types.d.ts +15 -1
- package/dist/types/config.d.ts +36 -11
- package/docs/api.md +3 -0
- package/docs/cli.md +1 -1
- package/drizzle/0006_add_component_source_format.sql +3 -0
- package/drizzle/meta/0006_snapshot.json +1941 -0
- package/drizzle/meta/_journal.json +8 -1
- package/package.json +37 -2
- package/src/app/components/Component/Preview.tsx +36 -50
- package/src/app/components/CopyToClipboard.tsx +27 -26
- package/src/app/components/Layout/Header.tsx +4 -12
- package/src/app/components/Layout/RuntimeModeBadge.tsx +18 -0
- package/src/app/components/Markdown/CodeHighlight.tsx +73 -70
- package/src/app/components/McpIntegration/McpConfigDialog.tsx +349 -0
- package/src/app/components/McpIntegration/McpIcon.tsx +10 -0
- package/src/app/components/Navigation/MobileNav.tsx +3 -1
- package/src/app/components/context/PreviewContext.tsx +5 -5
- package/src/app/components/ui/dialog.tsx +73 -0
- package/src/app/components/util/index.ts +7 -15
- package/src/app/lib/docs-api/backend.ts +5 -1
- package/src/app/lib/docs-api/page-discovery.ts +1 -6
- package/src/app/lib/docs-api/page-rendering.ts +21 -1
- package/src/app/lib/docs-api/records.ts +25 -10
- package/src/app/lib/docs-api/registry.ts +4 -2
- package/src/app/lib/docs-api/runtime-config.ts +5 -0
- package/src/app/lib/docs-api/search.ts +7 -2
- package/src/app/lib/mcp/auth.ts +64 -0
- package/src/app/lib/mcp/index.ts +2 -0
- package/src/app/lib/mcp/server.ts +343 -0
- package/src/app/lib/mcp/shape.ts +596 -0
- package/src/app/lib/registry-api/transfer.ts +7 -5
- package/src/app/next.config.mjs +3 -0
- package/src/app/pages/[...slug]/index.tsx +16 -30
- package/src/app/pages/api/mcp.ts +56 -0
- package/src/app/pages/index.tsx +10 -9
- package/src/app-builder/build.ts +12 -8
- package/src/app-builder/client-config.ts +2 -1
- package/src/app-builder/config-diff/strategies/pattern.ts +1 -1
- package/src/app-builder/watchers/component.ts +3 -3
- package/src/app-builder/watchers/utils.ts +2 -3
- package/src/app-builder/watchers.ts +46 -50
- package/src/cache/build-cache.ts +11 -14
- package/src/catalog/README.md +92 -0
- package/src/catalog/define.ts +79 -0
- package/src/catalog/implementation.ts +145 -0
- package/src/catalog/index.ts +31 -0
- package/src/catalog/normalize.ts +143 -0
- package/src/catalog/previews.ts +111 -0
- package/src/catalog/renderers.ts +94 -0
- package/src/catalog/types.ts +74 -0
- package/src/cli/README.md +1 -1
- package/src/cli/make.ts +20 -72
- package/src/cli/scaffold.ts +25 -31
- package/src/commands/README.md +1 -1
- package/src/commands/checkout/index.ts +17 -7
- package/src/commands/compat/push-args.ts +2 -2
- package/src/commands/index.ts +0 -2
- package/src/commands/publish/index.ts +19 -9
- package/src/commands/pull/index.ts +2 -2
- package/src/commands/push/index.ts +2 -2
- package/src/commands/utils.ts +22 -7
- package/src/config/README.md +2 -1
- package/src/config/catalog-include.ts +227 -0
- package/src/config/defaults.ts +3 -0
- package/src/config/index.ts +4 -4
- package/src/config/loader.ts +12 -0
- package/src/config/runtime.ts +186 -191
- package/src/handlebars.ts +18 -0
- package/src/index.ts +39 -26
- package/src/pattern.ts +14 -0
- package/src/react.ts +52 -0
- package/src/registry/asset-storage/define.ts +1 -1
- package/src/registry/checkout/index.ts +416 -152
- package/src/registry/db/schema.ts +2 -0
- package/src/registry/publish/index.ts +123 -26
- package/src/registry/publish/package.ts +14 -1
- package/src/registry/selection.ts +44 -0
- package/src/store/filesystem.ts +4 -4
- package/src/store/registry.ts +13 -2
- package/src/transformers/plugins/csf-render.ts +4 -2
- package/src/transformers/plugins/ssr-render.ts +15 -5
- package/src/transformers/preview/component/api.ts +13 -1
- package/src/transformers/preview/component/artifacts.ts +8 -4
- package/src/transformers/preview/component/builder.ts +17 -4
- package/src/transformers/preview/component/css.ts +6 -10
- package/src/transformers/preview/component/html.ts +30 -19
- package/src/transformers/preview/component/javascript.ts +1 -3
- package/src/transformers/preview/pattern/builder.ts +33 -20
- package/src/transformers/preview/types.ts +15 -1
- package/src/types/config.ts +38 -11
- package/templates/default/README.md.tpl +10 -0
- package/templates/default/components/button/button.react.handoff.js.tpl +19 -23
- package/templates/default/components/button/button.react.handoff.ts.tpl +22 -22
- package/templates/default/handoff.config.js.tpl +11 -18
- package/templates/default/handoff.config.ts.tpl +11 -18
- package/templates/default/handoff.config.with-examples.js.tpl +12 -16
- package/templates/default/handoff.config.with-examples.ts.tpl +12 -16
- package/config/templates/component/template.json +0 -49
- package/config/templates/template.html +0 -7
- package/dist/commands/make/template.d.ts +0 -8
- package/dist/commands/make/template.js +0 -43
- package/dist/config/entries.d.ts +0 -47
- package/dist/config/entries.js +0 -162
- package/dist/config/normalizers/declaration.d.ts +0 -11
- package/dist/config/normalizers/declaration.js +0 -122
- package/dist/config/normalizers/pattern.d.ts +0 -8
- package/dist/config/normalizers/pattern.js +0 -41
- package/dist/declarations/helpers.d.ts +0 -6
- package/dist/declarations/helpers.js +0 -19
- package/dist/declarations/index.d.ts +0 -2
- package/dist/declarations/index.js +0 -9
- package/dist/declarations/types.d.ts +0 -60
- package/src/commands/make/template.ts +0 -46
- package/src/config/entries.ts +0 -175
- package/src/config/normalizers/declaration.ts +0 -179
- package/src/config/normalizers/pattern.ts +0 -59
- package/src/declarations/helpers.ts +0 -36
- package/src/declarations/index.ts +0 -19
- package/src/declarations/types.ts +0 -70
- /package/dist/{declarations → catalog}/types.js +0 -0
|
@@ -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
|
+
};
|
package/dist/cli/make.d.ts
CHANGED
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 =
|
|
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
|
|
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
|
-
|
|
146
|
-
|
|
147
|
-
|
|
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
|
-
|
|
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
|
|
151
|
+
const entriesBlock = declarationEntries.length ? `\n entries: {\n ${declarationEntries.join(',\n ')}\n },` : '';
|
|
152
|
+
const declarationContent = `const { defineCatalogItem } = require('handoff-app/handlebars');
|
|
199
153
|
|
|
200
|
-
|
|
154
|
+
exports.default = defineCatalogItem({
|
|
201
155
|
id: '${name}',
|
|
202
156
|
name: '',
|
|
203
157
|
description: '',
|
|
204
158
|
group: '',
|
|
205
159
|
type: 'element',
|
|
206
|
-
|
|
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;
|