@fragments-sdk/cli 0.2.2
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/LICENSE +21 -0
- package/README.md +106 -0
- package/dist/bin.d.ts +1 -0
- package/dist/bin.js +4783 -0
- package/dist/bin.js.map +1 -0
- package/dist/chunk-4FDQSGKX.js +786 -0
- package/dist/chunk-4FDQSGKX.js.map +1 -0
- package/dist/chunk-7H2MMGYG.js +369 -0
- package/dist/chunk-7H2MMGYG.js.map +1 -0
- package/dist/chunk-BSCG3IP7.js +619 -0
- package/dist/chunk-BSCG3IP7.js.map +1 -0
- package/dist/chunk-LY2CFFPY.js +898 -0
- package/dist/chunk-LY2CFFPY.js.map +1 -0
- package/dist/chunk-MUZ6CM66.js +6636 -0
- package/dist/chunk-MUZ6CM66.js.map +1 -0
- package/dist/chunk-OAENNG3G.js +1489 -0
- package/dist/chunk-OAENNG3G.js.map +1 -0
- package/dist/chunk-XHNKNI6J.js +235 -0
- package/dist/chunk-XHNKNI6J.js.map +1 -0
- package/dist/core-DWKLGY4N.js +68 -0
- package/dist/core-DWKLGY4N.js.map +1 -0
- package/dist/generate-4LQNJ7SX.js +249 -0
- package/dist/generate-4LQNJ7SX.js.map +1 -0
- package/dist/index.d.ts +775 -0
- package/dist/index.js +41 -0
- package/dist/index.js.map +1 -0
- package/dist/init-EMVI47QG.js +416 -0
- package/dist/init-EMVI47QG.js.map +1 -0
- package/dist/mcp-bin.d.ts +1 -0
- package/dist/mcp-bin.js +1117 -0
- package/dist/mcp-bin.js.map +1 -0
- package/dist/scan-4YPRF7FV.js +12 -0
- package/dist/scan-4YPRF7FV.js.map +1 -0
- package/dist/service-QSZMZJBJ.js +208 -0
- package/dist/service-QSZMZJBJ.js.map +1 -0
- package/dist/static-viewer-MIPGZ4Z7.js +12 -0
- package/dist/static-viewer-MIPGZ4Z7.js.map +1 -0
- package/dist/test-SQ5ZHXWU.js +1067 -0
- package/dist/test-SQ5ZHXWU.js.map +1 -0
- package/dist/tokens-HSGMYK64.js +173 -0
- package/dist/tokens-HSGMYK64.js.map +1 -0
- package/dist/viewer-YRF4SQE4.js +11101 -0
- package/dist/viewer-YRF4SQE4.js.map +1 -0
- package/package.json +107 -0
- package/src/ai.ts +266 -0
- package/src/analyze.ts +265 -0
- package/src/bin.ts +916 -0
- package/src/build.ts +248 -0
- package/src/commands/a11y.ts +302 -0
- package/src/commands/add.ts +313 -0
- package/src/commands/audit.ts +195 -0
- package/src/commands/baseline.ts +221 -0
- package/src/commands/build.ts +144 -0
- package/src/commands/compare.ts +337 -0
- package/src/commands/context.ts +107 -0
- package/src/commands/dev.ts +107 -0
- package/src/commands/enhance.ts +858 -0
- package/src/commands/generate.ts +391 -0
- package/src/commands/init.ts +531 -0
- package/src/commands/link/figma.ts +645 -0
- package/src/commands/link/index.ts +10 -0
- package/src/commands/link/storybook.ts +267 -0
- package/src/commands/list.ts +49 -0
- package/src/commands/metrics.ts +114 -0
- package/src/commands/reset.ts +242 -0
- package/src/commands/scan.ts +537 -0
- package/src/commands/storygen.ts +207 -0
- package/src/commands/tokens.ts +251 -0
- package/src/commands/validate.ts +93 -0
- package/src/commands/verify.ts +215 -0
- package/src/core/composition.test.ts +262 -0
- package/src/core/composition.ts +255 -0
- package/src/core/config.ts +84 -0
- package/src/core/constants.ts +111 -0
- package/src/core/context.ts +380 -0
- package/src/core/defineSegment.ts +137 -0
- package/src/core/discovery.ts +337 -0
- package/src/core/figma.ts +263 -0
- package/src/core/fragment-types.ts +214 -0
- package/src/core/generators/context.ts +389 -0
- package/src/core/generators/index.ts +23 -0
- package/src/core/generators/registry.ts +364 -0
- package/src/core/generators/typescript-extractor.ts +374 -0
- package/src/core/importAnalyzer.ts +217 -0
- package/src/core/index.ts +149 -0
- package/src/core/loader.ts +155 -0
- package/src/core/node.ts +63 -0
- package/src/core/parser.ts +551 -0
- package/src/core/previewLoader.ts +172 -0
- package/src/core/schema/fragment.schema.json +189 -0
- package/src/core/schema/registry.schema.json +137 -0
- package/src/core/schema.ts +182 -0
- package/src/core/storyAdapter.test.ts +571 -0
- package/src/core/storyAdapter.ts +761 -0
- package/src/core/token-types.ts +287 -0
- package/src/core/types.ts +754 -0
- package/src/diff.ts +323 -0
- package/src/index.ts +43 -0
- package/src/mcp/__tests__/projectFields.test.ts +130 -0
- package/src/mcp/bin.ts +36 -0
- package/src/mcp/index.ts +8 -0
- package/src/mcp/server.ts +1310 -0
- package/src/mcp/utils.ts +54 -0
- package/src/mcp-bin.ts +36 -0
- package/src/migrate/__tests__/argTypes/argTypes.test.ts +189 -0
- package/src/migrate/__tests__/args/args.test.ts +452 -0
- package/src/migrate/__tests__/meta/meta.test.ts +198 -0
- package/src/migrate/__tests__/stories/stories.test.ts +278 -0
- package/src/migrate/__tests__/utils/utils.test.ts +371 -0
- package/src/migrate/__tests__/values/values.test.ts +303 -0
- package/src/migrate/bin.ts +108 -0
- package/src/migrate/converter.ts +658 -0
- package/src/migrate/detect.ts +196 -0
- package/src/migrate/index.ts +45 -0
- package/src/migrate/migrate.ts +163 -0
- package/src/migrate/parser.ts +1136 -0
- package/src/migrate/report.ts +624 -0
- package/src/migrate/types.ts +169 -0
- package/src/screenshot.ts +249 -0
- package/src/service/__tests__/ast-utils.test.ts +426 -0
- package/src/service/__tests__/enhance-scanner.test.ts +200 -0
- package/src/service/__tests__/figma/figma.test.ts +652 -0
- package/src/service/__tests__/metrics-store.test.ts +409 -0
- package/src/service/__tests__/patch-generator.test.ts +186 -0
- package/src/service/__tests__/props-extractor.test.ts +365 -0
- package/src/service/__tests__/token-registry.test.ts +267 -0
- package/src/service/analytics.ts +659 -0
- package/src/service/ast-utils.ts +444 -0
- package/src/service/browser-pool.ts +339 -0
- package/src/service/capture.ts +267 -0
- package/src/service/diff.ts +279 -0
- package/src/service/enhance/aggregator.ts +489 -0
- package/src/service/enhance/cache.ts +275 -0
- package/src/service/enhance/codebase-scanner.ts +357 -0
- package/src/service/enhance/context-generator.ts +529 -0
- package/src/service/enhance/doc-extractor.ts +523 -0
- package/src/service/enhance/index.ts +131 -0
- package/src/service/enhance/props-extractor.ts +665 -0
- package/src/service/enhance/scanner.ts +445 -0
- package/src/service/enhance/storybook-parser.ts +552 -0
- package/src/service/enhance/types.ts +346 -0
- package/src/service/enhance/variant-renderer.ts +479 -0
- package/src/service/figma.ts +1008 -0
- package/src/service/index.ts +249 -0
- package/src/service/metrics-store.ts +333 -0
- package/src/service/patch-generator.ts +349 -0
- package/src/service/report.ts +854 -0
- package/src/service/storage.ts +401 -0
- package/src/service/token-fixes.ts +281 -0
- package/src/service/token-parser.ts +504 -0
- package/src/service/token-registry.ts +721 -0
- package/src/service/utils.ts +172 -0
- package/src/setup.ts +241 -0
- package/src/shared/command-wrapper.ts +81 -0
- package/src/shared/dev-server-client.ts +199 -0
- package/src/shared/index.ts +8 -0
- package/src/shared/segment-loader.ts +59 -0
- package/src/shared/types.ts +147 -0
- package/src/static-viewer.ts +715 -0
- package/src/test/discovery.ts +172 -0
- package/src/test/index.ts +281 -0
- package/src/test/reporters/console.ts +194 -0
- package/src/test/reporters/json.ts +190 -0
- package/src/test/reporters/junit.ts +186 -0
- package/src/test/runner.ts +598 -0
- package/src/test/types.ts +245 -0
- package/src/test/watch.ts +200 -0
- package/src/validators.ts +152 -0
- package/src/viewer/__tests__/jsx-parser.test.ts +502 -0
- package/src/viewer/__tests__/render-utils.test.ts +232 -0
- package/src/viewer/__tests__/style-utils.test.ts +404 -0
- package/src/viewer/bin.ts +86 -0
- package/src/viewer/cli/health.ts +256 -0
- package/src/viewer/cli/index.ts +33 -0
- package/src/viewer/cli/scan.ts +124 -0
- package/src/viewer/cli/utils.ts +174 -0
- package/src/viewer/components/AccessibilityPanel.tsx +1404 -0
- package/src/viewer/components/ActionCapture.tsx +172 -0
- package/src/viewer/components/ActionsPanel.tsx +371 -0
- package/src/viewer/components/App.tsx +638 -0
- package/src/viewer/components/BottomPanel.tsx +224 -0
- package/src/viewer/components/CodePanel.tsx +589 -0
- package/src/viewer/components/CommandPalette.tsx +336 -0
- package/src/viewer/components/ComponentGraph.tsx +394 -0
- package/src/viewer/components/ComponentHeader.tsx +85 -0
- package/src/viewer/components/ContractPanel.tsx +234 -0
- package/src/viewer/components/ErrorBoundary.tsx +85 -0
- package/src/viewer/components/FigmaEmbed.tsx +231 -0
- package/src/viewer/components/FragmentEditor.tsx +485 -0
- package/src/viewer/components/HealthDashboard.tsx +452 -0
- package/src/viewer/components/HmrStatusIndicator.tsx +71 -0
- package/src/viewer/components/Icons.tsx +417 -0
- package/src/viewer/components/InteractionsPanel.tsx +720 -0
- package/src/viewer/components/IsolatedPreviewFrame.tsx +321 -0
- package/src/viewer/components/IsolatedRender.tsx +111 -0
- package/src/viewer/components/KeyboardShortcutsHelp.tsx +89 -0
- package/src/viewer/components/LandingPage.tsx +441 -0
- package/src/viewer/components/Layout.tsx +22 -0
- package/src/viewer/components/LeftSidebar.tsx +391 -0
- package/src/viewer/components/MultiViewportPreview.tsx +429 -0
- package/src/viewer/components/PreviewArea.tsx +404 -0
- package/src/viewer/components/PreviewFrameHost.tsx +310 -0
- package/src/viewer/components/PreviewPane.tsx +150 -0
- package/src/viewer/components/PreviewToolbar.tsx +176 -0
- package/src/viewer/components/PropsEditor.tsx +512 -0
- package/src/viewer/components/PropsTable.tsx +98 -0
- package/src/viewer/components/RelationsSection.tsx +57 -0
- package/src/viewer/components/ResizablePanel.tsx +328 -0
- package/src/viewer/components/RightSidebar.tsx +118 -0
- package/src/viewer/components/ScreenshotButton.tsx +90 -0
- package/src/viewer/components/Sidebar.tsx +169 -0
- package/src/viewer/components/SkeletonLoader.tsx +156 -0
- package/src/viewer/components/StoryRenderer.tsx +128 -0
- package/src/viewer/components/ThemeProvider.tsx +96 -0
- package/src/viewer/components/Toast.tsx +67 -0
- package/src/viewer/components/TokenStylePanel.tsx +708 -0
- package/src/viewer/components/UsageSection.tsx +95 -0
- package/src/viewer/components/VariantMatrix.tsx +350 -0
- package/src/viewer/components/VariantRenderer.tsx +131 -0
- package/src/viewer/components/VariantTabs.tsx +84 -0
- package/src/viewer/components/ViewportSelector.tsx +165 -0
- package/src/viewer/components/_future/CreatePage.tsx +836 -0
- package/src/viewer/composition-renderer.ts +381 -0
- package/src/viewer/constants/index.ts +1 -0
- package/src/viewer/constants/ui.ts +185 -0
- package/src/viewer/entry.tsx +299 -0
- package/src/viewer/hooks/index.ts +2 -0
- package/src/viewer/hooks/useA11yCache.ts +383 -0
- package/src/viewer/hooks/useA11yService.ts +498 -0
- package/src/viewer/hooks/useActions.ts +138 -0
- package/src/viewer/hooks/useAppState.ts +124 -0
- package/src/viewer/hooks/useFigmaIntegration.ts +132 -0
- package/src/viewer/hooks/useHmrStatus.ts +109 -0
- package/src/viewer/hooks/useKeyboardShortcuts.ts +222 -0
- package/src/viewer/hooks/usePreviewBridge.ts +347 -0
- package/src/viewer/hooks/useScrollSpy.ts +78 -0
- package/src/viewer/hooks/useUrlState.ts +330 -0
- package/src/viewer/hooks/useViewSettings.ts +125 -0
- package/src/viewer/index.html +28 -0
- package/src/viewer/index.ts +14 -0
- package/src/viewer/intelligence/healthReport.ts +505 -0
- package/src/viewer/intelligence/styleDrift.ts +340 -0
- package/src/viewer/intelligence/usageScanner.ts +309 -0
- package/src/viewer/jsx-parser.ts +485 -0
- package/src/viewer/postcss.config.js +6 -0
- package/src/viewer/preview-frame-entry.tsx +25 -0
- package/src/viewer/preview-frame.html +109 -0
- package/src/viewer/render-template.html +68 -0
- package/src/viewer/render-utils.ts +170 -0
- package/src/viewer/server.ts +276 -0
- package/src/viewer/style-utils.ts +414 -0
- package/src/viewer/styles/globals.css +355 -0
- package/src/viewer/tailwind.config.js +37 -0
- package/src/viewer/types/a11y.ts +197 -0
- package/src/viewer/utils/a11y-fixes.ts +471 -0
- package/src/viewer/utils/actionExport.ts +372 -0
- package/src/viewer/utils/colorSchemes.ts +201 -0
- package/src/viewer/utils/detectRelationships.ts +256 -0
- package/src/viewer/vite-plugin.ts +2143 -0
|
@@ -0,0 +1,552 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Storybook Parser
|
|
3
|
+
*
|
|
4
|
+
* Extracts examples and documentation from Storybook story files.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { parse } from "@babel/parser";
|
|
8
|
+
import _traverse from "@babel/traverse";
|
|
9
|
+
import * as t from "@babel/types";
|
|
10
|
+
|
|
11
|
+
// Handle CommonJS/ESM interop
|
|
12
|
+
const traverse = (_traverse as unknown as { default: typeof _traverse }).default || _traverse;
|
|
13
|
+
import { readFile } from "node:fs/promises";
|
|
14
|
+
import fg from "fast-glob";
|
|
15
|
+
import { basename, dirname, join, relative } from "node:path";
|
|
16
|
+
|
|
17
|
+
export interface StoryExample {
|
|
18
|
+
name: string;
|
|
19
|
+
displayName: string;
|
|
20
|
+
description?: string;
|
|
21
|
+
args: Record<string, unknown>;
|
|
22
|
+
code: string;
|
|
23
|
+
isDefault: boolean;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export interface StorybookMeta {
|
|
27
|
+
componentName: string;
|
|
28
|
+
title?: string;
|
|
29
|
+
description?: string;
|
|
30
|
+
argTypes?: Record<string, ArgType>;
|
|
31
|
+
parameters?: Record<string, unknown>;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export interface ArgType {
|
|
35
|
+
description?: string;
|
|
36
|
+
defaultValue?: unknown;
|
|
37
|
+
control?: string;
|
|
38
|
+
options?: string[];
|
|
39
|
+
table?: {
|
|
40
|
+
category?: string;
|
|
41
|
+
type?: { summary: string };
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export interface ParsedStoryFile {
|
|
46
|
+
filePath: string;
|
|
47
|
+
meta: StorybookMeta;
|
|
48
|
+
stories: StoryExample[];
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Parse a Storybook story file
|
|
53
|
+
*/
|
|
54
|
+
export async function parseStoryFile(
|
|
55
|
+
filePath: string
|
|
56
|
+
): Promise<ParsedStoryFile> {
|
|
57
|
+
const content = await readFile(filePath, "utf-8");
|
|
58
|
+
return parseStorySource(content, filePath);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* Parse Storybook story source code
|
|
63
|
+
*/
|
|
64
|
+
export function parseStorySource(
|
|
65
|
+
source: string,
|
|
66
|
+
filePath: string
|
|
67
|
+
): ParsedStoryFile {
|
|
68
|
+
const isTypeScript = filePath.endsWith(".ts") || filePath.endsWith(".tsx");
|
|
69
|
+
const isJSX = filePath.endsWith(".tsx") || filePath.endsWith(".jsx");
|
|
70
|
+
|
|
71
|
+
const ast = parse(source, {
|
|
72
|
+
sourceType: "module",
|
|
73
|
+
plugins: [
|
|
74
|
+
...(isTypeScript ? (["typescript"] as const) : []),
|
|
75
|
+
...(isJSX ? (["jsx"] as const) : []),
|
|
76
|
+
"decorators-legacy",
|
|
77
|
+
],
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
const result: ParsedStoryFile = {
|
|
81
|
+
filePath,
|
|
82
|
+
meta: {
|
|
83
|
+
componentName: inferComponentFromPath(filePath),
|
|
84
|
+
},
|
|
85
|
+
stories: [],
|
|
86
|
+
};
|
|
87
|
+
|
|
88
|
+
// Track what's been exported as default (the meta)
|
|
89
|
+
let defaultExportNode: t.Node | null = null;
|
|
90
|
+
|
|
91
|
+
// First pass: find default export (meta)
|
|
92
|
+
traverse(ast, {
|
|
93
|
+
ExportDefaultDeclaration(path) {
|
|
94
|
+
defaultExportNode = path.node.declaration;
|
|
95
|
+
},
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
// Parse the meta object
|
|
99
|
+
if (defaultExportNode) {
|
|
100
|
+
const metaObject = extractMetaObject(defaultExportNode, ast);
|
|
101
|
+
if (metaObject) {
|
|
102
|
+
result.meta = parseMetaObject(metaObject, result.meta.componentName);
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
// Second pass: find named exports (stories)
|
|
107
|
+
traverse(ast, {
|
|
108
|
+
ExportNamedDeclaration(path) {
|
|
109
|
+
const declaration = path.node.declaration;
|
|
110
|
+
|
|
111
|
+
if (t.isVariableDeclaration(declaration)) {
|
|
112
|
+
for (const declarator of declaration.declarations) {
|
|
113
|
+
if (!t.isIdentifier(declarator.id)) continue;
|
|
114
|
+
|
|
115
|
+
const storyName = declarator.id.name;
|
|
116
|
+
// Skip non-story exports (usually lowercase)
|
|
117
|
+
if (!/^[A-Z]/.test(storyName)) continue;
|
|
118
|
+
|
|
119
|
+
const story = parseStoryDeclarator(
|
|
120
|
+
storyName,
|
|
121
|
+
declarator,
|
|
122
|
+
source,
|
|
123
|
+
result.meta.componentName
|
|
124
|
+
);
|
|
125
|
+
if (story) {
|
|
126
|
+
result.stories.push(story);
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
},
|
|
131
|
+
});
|
|
132
|
+
|
|
133
|
+
// Mark Default story
|
|
134
|
+
const defaultStory = result.stories.find(
|
|
135
|
+
(s) => s.name === "Default" || s.name === "Primary"
|
|
136
|
+
);
|
|
137
|
+
if (defaultStory) {
|
|
138
|
+
defaultStory.isDefault = true;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
return result;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
/**
|
|
145
|
+
* Extract meta object from default export node
|
|
146
|
+
*/
|
|
147
|
+
function extractMetaObject(
|
|
148
|
+
node: t.Node,
|
|
149
|
+
ast: ReturnType<typeof parse>
|
|
150
|
+
): t.ObjectExpression | null {
|
|
151
|
+
// Direct object expression: export default { ... }
|
|
152
|
+
if (t.isObjectExpression(node)) {
|
|
153
|
+
return node;
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
// Reference to variable: export default meta
|
|
157
|
+
if (t.isIdentifier(node)) {
|
|
158
|
+
const metaName = node.name;
|
|
159
|
+
let foundObject: t.ObjectExpression | null = null;
|
|
160
|
+
traverse(ast, {
|
|
161
|
+
VariableDeclarator(path) {
|
|
162
|
+
if (
|
|
163
|
+
t.isIdentifier(path.node.id) &&
|
|
164
|
+
path.node.id.name === metaName &&
|
|
165
|
+
t.isObjectExpression(path.node.init)
|
|
166
|
+
) {
|
|
167
|
+
foundObject = path.node.init;
|
|
168
|
+
}
|
|
169
|
+
},
|
|
170
|
+
});
|
|
171
|
+
return foundObject;
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
// Type assertion: export default { ... } as Meta<typeof Button>
|
|
175
|
+
if (t.isTSAsExpression(node) && t.isObjectExpression(node.expression)) {
|
|
176
|
+
return node.expression;
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
// Satisfies expression: export default { ... } satisfies Meta<typeof Button>
|
|
180
|
+
if (
|
|
181
|
+
t.isTSSatisfiesExpression &&
|
|
182
|
+
t.isTSSatisfiesExpression(node) &&
|
|
183
|
+
t.isObjectExpression(node.expression)
|
|
184
|
+
) {
|
|
185
|
+
return node.expression;
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
return null;
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
/**
|
|
192
|
+
* Parse the meta (default export) object
|
|
193
|
+
*/
|
|
194
|
+
function parseMetaObject(
|
|
195
|
+
obj: t.ObjectExpression,
|
|
196
|
+
fallbackName: string
|
|
197
|
+
): StorybookMeta {
|
|
198
|
+
const meta: StorybookMeta = {
|
|
199
|
+
componentName: fallbackName,
|
|
200
|
+
};
|
|
201
|
+
|
|
202
|
+
for (const prop of obj.properties) {
|
|
203
|
+
if (!t.isObjectProperty(prop) || !t.isIdentifier(prop.key)) continue;
|
|
204
|
+
|
|
205
|
+
const key = prop.key.name;
|
|
206
|
+
|
|
207
|
+
switch (key) {
|
|
208
|
+
case "title":
|
|
209
|
+
if (t.isStringLiteral(prop.value)) {
|
|
210
|
+
meta.title = prop.value.value;
|
|
211
|
+
// Extract component name from title (e.g., "Components/Button" -> "Button")
|
|
212
|
+
const parts = meta.title.split("/");
|
|
213
|
+
meta.componentName = parts[parts.length - 1];
|
|
214
|
+
}
|
|
215
|
+
break;
|
|
216
|
+
|
|
217
|
+
case "component":
|
|
218
|
+
if (t.isIdentifier(prop.value)) {
|
|
219
|
+
meta.componentName = prop.value.name;
|
|
220
|
+
}
|
|
221
|
+
break;
|
|
222
|
+
|
|
223
|
+
case "parameters":
|
|
224
|
+
if (t.isObjectExpression(prop.value)) {
|
|
225
|
+
meta.parameters = extractObjectLiteral(prop.value);
|
|
226
|
+
// Extract description from parameters.docs.description.component
|
|
227
|
+
const docs = meta.parameters?.docs as Record<string, unknown>;
|
|
228
|
+
if (docs?.description) {
|
|
229
|
+
const desc = docs.description as Record<string, string>;
|
|
230
|
+
if (desc.component) {
|
|
231
|
+
meta.description = desc.component;
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
break;
|
|
236
|
+
|
|
237
|
+
case "argTypes":
|
|
238
|
+
if (t.isObjectExpression(prop.value)) {
|
|
239
|
+
meta.argTypes = parseArgTypes(prop.value);
|
|
240
|
+
}
|
|
241
|
+
break;
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
return meta;
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
/**
|
|
249
|
+
* Parse argTypes object
|
|
250
|
+
*/
|
|
251
|
+
function parseArgTypes(obj: t.ObjectExpression): Record<string, ArgType> {
|
|
252
|
+
const argTypes: Record<string, ArgType> = {};
|
|
253
|
+
|
|
254
|
+
for (const prop of obj.properties) {
|
|
255
|
+
if (!t.isObjectProperty(prop) || !t.isIdentifier(prop.key)) continue;
|
|
256
|
+
|
|
257
|
+
const propName = prop.key.name;
|
|
258
|
+
const argType: ArgType = {};
|
|
259
|
+
|
|
260
|
+
if (t.isObjectExpression(prop.value)) {
|
|
261
|
+
for (const inner of prop.value.properties) {
|
|
262
|
+
if (!t.isObjectProperty(inner) || !t.isIdentifier(inner.key)) continue;
|
|
263
|
+
|
|
264
|
+
const innerKey = inner.key.name;
|
|
265
|
+
|
|
266
|
+
if (innerKey === "description" && t.isStringLiteral(inner.value)) {
|
|
267
|
+
argType.description = inner.value.value;
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
if (innerKey === "defaultValue") {
|
|
271
|
+
argType.defaultValue = extractLiteralValue(inner.value);
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
if (innerKey === "control") {
|
|
275
|
+
if (t.isStringLiteral(inner.value)) {
|
|
276
|
+
argType.control = inner.value.value;
|
|
277
|
+
} else if (t.isObjectExpression(inner.value)) {
|
|
278
|
+
const controlObj = extractObjectLiteral(inner.value);
|
|
279
|
+
argType.control = controlObj?.type as string;
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
if (innerKey === "options" && t.isArrayExpression(inner.value)) {
|
|
284
|
+
argType.options = inner.value.elements
|
|
285
|
+
.filter((el): el is t.StringLiteral => t.isStringLiteral(el))
|
|
286
|
+
.map((el) => el.value);
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
argTypes[propName] = argType;
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
return argTypes;
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
/**
|
|
298
|
+
* Parse a story variable declarator
|
|
299
|
+
*/
|
|
300
|
+
function parseStoryDeclarator(
|
|
301
|
+
name: string,
|
|
302
|
+
declarator: t.VariableDeclarator,
|
|
303
|
+
source: string,
|
|
304
|
+
componentName: string
|
|
305
|
+
): StoryExample | null {
|
|
306
|
+
const init = declarator.init;
|
|
307
|
+
if (!init) return null;
|
|
308
|
+
|
|
309
|
+
const story: StoryExample = {
|
|
310
|
+
name,
|
|
311
|
+
displayName: camelToTitle(name),
|
|
312
|
+
args: {},
|
|
313
|
+
code: "",
|
|
314
|
+
isDefault: false,
|
|
315
|
+
};
|
|
316
|
+
|
|
317
|
+
// Handle object-style story: export const Primary = { args: { ... } }
|
|
318
|
+
if (t.isObjectExpression(init)) {
|
|
319
|
+
parseStoryObject(init, story);
|
|
320
|
+
story.code = generateStoryCode(componentName, story.args);
|
|
321
|
+
return story;
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
// Handle: export const Primary: Story = { ... }
|
|
325
|
+
if (
|
|
326
|
+
t.isTSAsExpression(init) &&
|
|
327
|
+
t.isObjectExpression(init.expression)
|
|
328
|
+
) {
|
|
329
|
+
parseStoryObject(init.expression, story);
|
|
330
|
+
story.code = generateStoryCode(componentName, story.args);
|
|
331
|
+
return story;
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
// Handle render function style: export const Primary = () => <Button />
|
|
335
|
+
if (t.isArrowFunctionExpression(init) || t.isFunctionExpression(init)) {
|
|
336
|
+
// Extract the code from source
|
|
337
|
+
if (init.start != null && init.end != null) {
|
|
338
|
+
const fnCode = source.slice(init.start, init.end);
|
|
339
|
+
story.code = fnCode;
|
|
340
|
+
}
|
|
341
|
+
return story;
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
// Handle satisfies expression: export const Primary = { ... } satisfies Story
|
|
345
|
+
if (t.isTSSatisfiesExpression && t.isTSSatisfiesExpression(init)) {
|
|
346
|
+
if (t.isObjectExpression(init.expression)) {
|
|
347
|
+
parseStoryObject(init.expression, story);
|
|
348
|
+
story.code = generateStoryCode(componentName, story.args);
|
|
349
|
+
return story;
|
|
350
|
+
}
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
return null;
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
/**
|
|
357
|
+
* Parse story object properties
|
|
358
|
+
*/
|
|
359
|
+
function parseStoryObject(obj: t.ObjectExpression, story: StoryExample): void {
|
|
360
|
+
for (const prop of obj.properties) {
|
|
361
|
+
if (!t.isObjectProperty(prop) || !t.isIdentifier(prop.key)) continue;
|
|
362
|
+
|
|
363
|
+
const key = prop.key.name;
|
|
364
|
+
|
|
365
|
+
switch (key) {
|
|
366
|
+
case "name":
|
|
367
|
+
if (t.isStringLiteral(prop.value)) {
|
|
368
|
+
story.displayName = prop.value.value;
|
|
369
|
+
}
|
|
370
|
+
break;
|
|
371
|
+
|
|
372
|
+
case "args":
|
|
373
|
+
if (t.isObjectExpression(prop.value)) {
|
|
374
|
+
story.args = extractObjectLiteral(prop.value);
|
|
375
|
+
}
|
|
376
|
+
break;
|
|
377
|
+
|
|
378
|
+
case "parameters":
|
|
379
|
+
if (t.isObjectExpression(prop.value)) {
|
|
380
|
+
const params = extractObjectLiteral(prop.value);
|
|
381
|
+
const docs = params?.docs as Record<string, unknown>;
|
|
382
|
+
if (docs?.description) {
|
|
383
|
+
const desc = docs.description as Record<string, string>;
|
|
384
|
+
if (desc.story) {
|
|
385
|
+
story.description = desc.story;
|
|
386
|
+
}
|
|
387
|
+
}
|
|
388
|
+
}
|
|
389
|
+
break;
|
|
390
|
+
}
|
|
391
|
+
}
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
/**
|
|
395
|
+
* Extract a literal value from an AST node
|
|
396
|
+
*/
|
|
397
|
+
function extractLiteralValue(node: t.Node): unknown {
|
|
398
|
+
if (t.isStringLiteral(node)) return node.value;
|
|
399
|
+
if (t.isNumericLiteral(node)) return node.value;
|
|
400
|
+
if (t.isBooleanLiteral(node)) return node.value;
|
|
401
|
+
if (t.isNullLiteral(node)) return null;
|
|
402
|
+
if (t.isIdentifier(node) && node.name === "undefined") return undefined;
|
|
403
|
+
|
|
404
|
+
if (t.isArrayExpression(node)) {
|
|
405
|
+
return node.elements
|
|
406
|
+
.filter((el): el is t.Expression => el !== null && t.isExpression(el))
|
|
407
|
+
.map(extractLiteralValue);
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
if (t.isObjectExpression(node)) {
|
|
411
|
+
return extractObjectLiteral(node);
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
// Can't extract complex expressions
|
|
415
|
+
return undefined;
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
/**
|
|
419
|
+
* Extract an object literal into a plain object
|
|
420
|
+
*/
|
|
421
|
+
function extractObjectLiteral(
|
|
422
|
+
node: t.ObjectExpression
|
|
423
|
+
): Record<string, unknown> {
|
|
424
|
+
const obj: Record<string, unknown> = {};
|
|
425
|
+
|
|
426
|
+
for (const prop of node.properties) {
|
|
427
|
+
if (t.isSpreadElement(prop)) continue;
|
|
428
|
+
if (!t.isObjectProperty(prop)) continue;
|
|
429
|
+
|
|
430
|
+
let key: string;
|
|
431
|
+
if (t.isIdentifier(prop.key)) {
|
|
432
|
+
key = prop.key.name;
|
|
433
|
+
} else if (t.isStringLiteral(prop.key)) {
|
|
434
|
+
key = prop.key.value;
|
|
435
|
+
} else {
|
|
436
|
+
continue;
|
|
437
|
+
}
|
|
438
|
+
|
|
439
|
+
obj[key] = extractLiteralValue(prop.value);
|
|
440
|
+
}
|
|
441
|
+
|
|
442
|
+
return obj;
|
|
443
|
+
}
|
|
444
|
+
|
|
445
|
+
/**
|
|
446
|
+
* Generate example code from component name and args
|
|
447
|
+
*/
|
|
448
|
+
function generateStoryCode(
|
|
449
|
+
componentName: string,
|
|
450
|
+
args: Record<string, unknown>
|
|
451
|
+
): string {
|
|
452
|
+
const props = Object.entries(args)
|
|
453
|
+
.filter(([, value]) => value !== undefined)
|
|
454
|
+
.map(([key, value]) => {
|
|
455
|
+
if (typeof value === "string") {
|
|
456
|
+
return `${key}="${value}"`;
|
|
457
|
+
}
|
|
458
|
+
if (typeof value === "boolean" && value) {
|
|
459
|
+
return key;
|
|
460
|
+
}
|
|
461
|
+
return `${key}={${JSON.stringify(value)}}`;
|
|
462
|
+
})
|
|
463
|
+
.join(" ");
|
|
464
|
+
|
|
465
|
+
if (props) {
|
|
466
|
+
return `<${componentName} ${props} />`;
|
|
467
|
+
}
|
|
468
|
+
return `<${componentName} />`;
|
|
469
|
+
}
|
|
470
|
+
|
|
471
|
+
/**
|
|
472
|
+
* Convert camelCase to Title Case
|
|
473
|
+
*/
|
|
474
|
+
function camelToTitle(str: string): string {
|
|
475
|
+
return str
|
|
476
|
+
.replace(/([A-Z])/g, " $1")
|
|
477
|
+
.replace(/^./, (s) => s.toUpperCase())
|
|
478
|
+
.trim();
|
|
479
|
+
}
|
|
480
|
+
|
|
481
|
+
/**
|
|
482
|
+
* Infer component name from story file path
|
|
483
|
+
*/
|
|
484
|
+
function inferComponentFromPath(filePath: string): string {
|
|
485
|
+
const fileName = basename(filePath);
|
|
486
|
+
// Remove .stories.tsx etc
|
|
487
|
+
let name = fileName.replace(/\.stories\.(tsx?|jsx?|mdx?)$/, "");
|
|
488
|
+
// Handle patterns like Button.stories.tsx
|
|
489
|
+
return name;
|
|
490
|
+
}
|
|
491
|
+
|
|
492
|
+
/**
|
|
493
|
+
* Find all story files in a directory
|
|
494
|
+
*/
|
|
495
|
+
export async function findStoryFiles(
|
|
496
|
+
rootDir: string,
|
|
497
|
+
include: string[] = ["**/*.stories.{tsx,ts,jsx,js}"],
|
|
498
|
+
exclude: string[] = ["**/node_modules/**", "**/dist/**"]
|
|
499
|
+
): Promise<string[]> {
|
|
500
|
+
return fg(include, {
|
|
501
|
+
cwd: rootDir,
|
|
502
|
+
ignore: exclude,
|
|
503
|
+
absolute: true,
|
|
504
|
+
onlyFiles: true,
|
|
505
|
+
});
|
|
506
|
+
}
|
|
507
|
+
|
|
508
|
+
/**
|
|
509
|
+
* Parse all story files and group by component
|
|
510
|
+
*/
|
|
511
|
+
export async function parseAllStories(
|
|
512
|
+
rootDir: string
|
|
513
|
+
): Promise<Map<string, ParsedStoryFile>> {
|
|
514
|
+
const storyFiles = await findStoryFiles(rootDir);
|
|
515
|
+
const results = new Map<string, ParsedStoryFile>();
|
|
516
|
+
|
|
517
|
+
for (const filePath of storyFiles) {
|
|
518
|
+
try {
|
|
519
|
+
const parsed = await parseStoryFile(filePath);
|
|
520
|
+
results.set(parsed.meta.componentName, parsed);
|
|
521
|
+
} catch (error) {
|
|
522
|
+
console.warn(
|
|
523
|
+
`Failed to parse story file ${relative(rootDir, filePath)}:`,
|
|
524
|
+
(error as Error).message
|
|
525
|
+
);
|
|
526
|
+
}
|
|
527
|
+
}
|
|
528
|
+
|
|
529
|
+
return results;
|
|
530
|
+
}
|
|
531
|
+
|
|
532
|
+
/**
|
|
533
|
+
* Merge Storybook data into extracted docs
|
|
534
|
+
*/
|
|
535
|
+
export function mergeStorybookIntoDoc(
|
|
536
|
+
storyFile: ParsedStoryFile,
|
|
537
|
+
existingExamples: string[] = []
|
|
538
|
+
): string[] {
|
|
539
|
+
const examples = [...existingExamples];
|
|
540
|
+
|
|
541
|
+
for (const story of storyFile.stories) {
|
|
542
|
+
if (story.code && !examples.includes(story.code)) {
|
|
543
|
+
// Add with story name as comment
|
|
544
|
+
const example = story.description
|
|
545
|
+
? `// ${story.displayName}: ${story.description}\n${story.code}`
|
|
546
|
+
: `// ${story.displayName}\n${story.code}`;
|
|
547
|
+
examples.push(example);
|
|
548
|
+
}
|
|
549
|
+
}
|
|
550
|
+
|
|
551
|
+
return examples;
|
|
552
|
+
}
|