@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.
Files changed (259) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +106 -0
  3. package/dist/bin.d.ts +1 -0
  4. package/dist/bin.js +4783 -0
  5. package/dist/bin.js.map +1 -0
  6. package/dist/chunk-4FDQSGKX.js +786 -0
  7. package/dist/chunk-4FDQSGKX.js.map +1 -0
  8. package/dist/chunk-7H2MMGYG.js +369 -0
  9. package/dist/chunk-7H2MMGYG.js.map +1 -0
  10. package/dist/chunk-BSCG3IP7.js +619 -0
  11. package/dist/chunk-BSCG3IP7.js.map +1 -0
  12. package/dist/chunk-LY2CFFPY.js +898 -0
  13. package/dist/chunk-LY2CFFPY.js.map +1 -0
  14. package/dist/chunk-MUZ6CM66.js +6636 -0
  15. package/dist/chunk-MUZ6CM66.js.map +1 -0
  16. package/dist/chunk-OAENNG3G.js +1489 -0
  17. package/dist/chunk-OAENNG3G.js.map +1 -0
  18. package/dist/chunk-XHNKNI6J.js +235 -0
  19. package/dist/chunk-XHNKNI6J.js.map +1 -0
  20. package/dist/core-DWKLGY4N.js +68 -0
  21. package/dist/core-DWKLGY4N.js.map +1 -0
  22. package/dist/generate-4LQNJ7SX.js +249 -0
  23. package/dist/generate-4LQNJ7SX.js.map +1 -0
  24. package/dist/index.d.ts +775 -0
  25. package/dist/index.js +41 -0
  26. package/dist/index.js.map +1 -0
  27. package/dist/init-EMVI47QG.js +416 -0
  28. package/dist/init-EMVI47QG.js.map +1 -0
  29. package/dist/mcp-bin.d.ts +1 -0
  30. package/dist/mcp-bin.js +1117 -0
  31. package/dist/mcp-bin.js.map +1 -0
  32. package/dist/scan-4YPRF7FV.js +12 -0
  33. package/dist/scan-4YPRF7FV.js.map +1 -0
  34. package/dist/service-QSZMZJBJ.js +208 -0
  35. package/dist/service-QSZMZJBJ.js.map +1 -0
  36. package/dist/static-viewer-MIPGZ4Z7.js +12 -0
  37. package/dist/static-viewer-MIPGZ4Z7.js.map +1 -0
  38. package/dist/test-SQ5ZHXWU.js +1067 -0
  39. package/dist/test-SQ5ZHXWU.js.map +1 -0
  40. package/dist/tokens-HSGMYK64.js +173 -0
  41. package/dist/tokens-HSGMYK64.js.map +1 -0
  42. package/dist/viewer-YRF4SQE4.js +11101 -0
  43. package/dist/viewer-YRF4SQE4.js.map +1 -0
  44. package/package.json +107 -0
  45. package/src/ai.ts +266 -0
  46. package/src/analyze.ts +265 -0
  47. package/src/bin.ts +916 -0
  48. package/src/build.ts +248 -0
  49. package/src/commands/a11y.ts +302 -0
  50. package/src/commands/add.ts +313 -0
  51. package/src/commands/audit.ts +195 -0
  52. package/src/commands/baseline.ts +221 -0
  53. package/src/commands/build.ts +144 -0
  54. package/src/commands/compare.ts +337 -0
  55. package/src/commands/context.ts +107 -0
  56. package/src/commands/dev.ts +107 -0
  57. package/src/commands/enhance.ts +858 -0
  58. package/src/commands/generate.ts +391 -0
  59. package/src/commands/init.ts +531 -0
  60. package/src/commands/link/figma.ts +645 -0
  61. package/src/commands/link/index.ts +10 -0
  62. package/src/commands/link/storybook.ts +267 -0
  63. package/src/commands/list.ts +49 -0
  64. package/src/commands/metrics.ts +114 -0
  65. package/src/commands/reset.ts +242 -0
  66. package/src/commands/scan.ts +537 -0
  67. package/src/commands/storygen.ts +207 -0
  68. package/src/commands/tokens.ts +251 -0
  69. package/src/commands/validate.ts +93 -0
  70. package/src/commands/verify.ts +215 -0
  71. package/src/core/composition.test.ts +262 -0
  72. package/src/core/composition.ts +255 -0
  73. package/src/core/config.ts +84 -0
  74. package/src/core/constants.ts +111 -0
  75. package/src/core/context.ts +380 -0
  76. package/src/core/defineSegment.ts +137 -0
  77. package/src/core/discovery.ts +337 -0
  78. package/src/core/figma.ts +263 -0
  79. package/src/core/fragment-types.ts +214 -0
  80. package/src/core/generators/context.ts +389 -0
  81. package/src/core/generators/index.ts +23 -0
  82. package/src/core/generators/registry.ts +364 -0
  83. package/src/core/generators/typescript-extractor.ts +374 -0
  84. package/src/core/importAnalyzer.ts +217 -0
  85. package/src/core/index.ts +149 -0
  86. package/src/core/loader.ts +155 -0
  87. package/src/core/node.ts +63 -0
  88. package/src/core/parser.ts +551 -0
  89. package/src/core/previewLoader.ts +172 -0
  90. package/src/core/schema/fragment.schema.json +189 -0
  91. package/src/core/schema/registry.schema.json +137 -0
  92. package/src/core/schema.ts +182 -0
  93. package/src/core/storyAdapter.test.ts +571 -0
  94. package/src/core/storyAdapter.ts +761 -0
  95. package/src/core/token-types.ts +287 -0
  96. package/src/core/types.ts +754 -0
  97. package/src/diff.ts +323 -0
  98. package/src/index.ts +43 -0
  99. package/src/mcp/__tests__/projectFields.test.ts +130 -0
  100. package/src/mcp/bin.ts +36 -0
  101. package/src/mcp/index.ts +8 -0
  102. package/src/mcp/server.ts +1310 -0
  103. package/src/mcp/utils.ts +54 -0
  104. package/src/mcp-bin.ts +36 -0
  105. package/src/migrate/__tests__/argTypes/argTypes.test.ts +189 -0
  106. package/src/migrate/__tests__/args/args.test.ts +452 -0
  107. package/src/migrate/__tests__/meta/meta.test.ts +198 -0
  108. package/src/migrate/__tests__/stories/stories.test.ts +278 -0
  109. package/src/migrate/__tests__/utils/utils.test.ts +371 -0
  110. package/src/migrate/__tests__/values/values.test.ts +303 -0
  111. package/src/migrate/bin.ts +108 -0
  112. package/src/migrate/converter.ts +658 -0
  113. package/src/migrate/detect.ts +196 -0
  114. package/src/migrate/index.ts +45 -0
  115. package/src/migrate/migrate.ts +163 -0
  116. package/src/migrate/parser.ts +1136 -0
  117. package/src/migrate/report.ts +624 -0
  118. package/src/migrate/types.ts +169 -0
  119. package/src/screenshot.ts +249 -0
  120. package/src/service/__tests__/ast-utils.test.ts +426 -0
  121. package/src/service/__tests__/enhance-scanner.test.ts +200 -0
  122. package/src/service/__tests__/figma/figma.test.ts +652 -0
  123. package/src/service/__tests__/metrics-store.test.ts +409 -0
  124. package/src/service/__tests__/patch-generator.test.ts +186 -0
  125. package/src/service/__tests__/props-extractor.test.ts +365 -0
  126. package/src/service/__tests__/token-registry.test.ts +267 -0
  127. package/src/service/analytics.ts +659 -0
  128. package/src/service/ast-utils.ts +444 -0
  129. package/src/service/browser-pool.ts +339 -0
  130. package/src/service/capture.ts +267 -0
  131. package/src/service/diff.ts +279 -0
  132. package/src/service/enhance/aggregator.ts +489 -0
  133. package/src/service/enhance/cache.ts +275 -0
  134. package/src/service/enhance/codebase-scanner.ts +357 -0
  135. package/src/service/enhance/context-generator.ts +529 -0
  136. package/src/service/enhance/doc-extractor.ts +523 -0
  137. package/src/service/enhance/index.ts +131 -0
  138. package/src/service/enhance/props-extractor.ts +665 -0
  139. package/src/service/enhance/scanner.ts +445 -0
  140. package/src/service/enhance/storybook-parser.ts +552 -0
  141. package/src/service/enhance/types.ts +346 -0
  142. package/src/service/enhance/variant-renderer.ts +479 -0
  143. package/src/service/figma.ts +1008 -0
  144. package/src/service/index.ts +249 -0
  145. package/src/service/metrics-store.ts +333 -0
  146. package/src/service/patch-generator.ts +349 -0
  147. package/src/service/report.ts +854 -0
  148. package/src/service/storage.ts +401 -0
  149. package/src/service/token-fixes.ts +281 -0
  150. package/src/service/token-parser.ts +504 -0
  151. package/src/service/token-registry.ts +721 -0
  152. package/src/service/utils.ts +172 -0
  153. package/src/setup.ts +241 -0
  154. package/src/shared/command-wrapper.ts +81 -0
  155. package/src/shared/dev-server-client.ts +199 -0
  156. package/src/shared/index.ts +8 -0
  157. package/src/shared/segment-loader.ts +59 -0
  158. package/src/shared/types.ts +147 -0
  159. package/src/static-viewer.ts +715 -0
  160. package/src/test/discovery.ts +172 -0
  161. package/src/test/index.ts +281 -0
  162. package/src/test/reporters/console.ts +194 -0
  163. package/src/test/reporters/json.ts +190 -0
  164. package/src/test/reporters/junit.ts +186 -0
  165. package/src/test/runner.ts +598 -0
  166. package/src/test/types.ts +245 -0
  167. package/src/test/watch.ts +200 -0
  168. package/src/validators.ts +152 -0
  169. package/src/viewer/__tests__/jsx-parser.test.ts +502 -0
  170. package/src/viewer/__tests__/render-utils.test.ts +232 -0
  171. package/src/viewer/__tests__/style-utils.test.ts +404 -0
  172. package/src/viewer/bin.ts +86 -0
  173. package/src/viewer/cli/health.ts +256 -0
  174. package/src/viewer/cli/index.ts +33 -0
  175. package/src/viewer/cli/scan.ts +124 -0
  176. package/src/viewer/cli/utils.ts +174 -0
  177. package/src/viewer/components/AccessibilityPanel.tsx +1404 -0
  178. package/src/viewer/components/ActionCapture.tsx +172 -0
  179. package/src/viewer/components/ActionsPanel.tsx +371 -0
  180. package/src/viewer/components/App.tsx +638 -0
  181. package/src/viewer/components/BottomPanel.tsx +224 -0
  182. package/src/viewer/components/CodePanel.tsx +589 -0
  183. package/src/viewer/components/CommandPalette.tsx +336 -0
  184. package/src/viewer/components/ComponentGraph.tsx +394 -0
  185. package/src/viewer/components/ComponentHeader.tsx +85 -0
  186. package/src/viewer/components/ContractPanel.tsx +234 -0
  187. package/src/viewer/components/ErrorBoundary.tsx +85 -0
  188. package/src/viewer/components/FigmaEmbed.tsx +231 -0
  189. package/src/viewer/components/FragmentEditor.tsx +485 -0
  190. package/src/viewer/components/HealthDashboard.tsx +452 -0
  191. package/src/viewer/components/HmrStatusIndicator.tsx +71 -0
  192. package/src/viewer/components/Icons.tsx +417 -0
  193. package/src/viewer/components/InteractionsPanel.tsx +720 -0
  194. package/src/viewer/components/IsolatedPreviewFrame.tsx +321 -0
  195. package/src/viewer/components/IsolatedRender.tsx +111 -0
  196. package/src/viewer/components/KeyboardShortcutsHelp.tsx +89 -0
  197. package/src/viewer/components/LandingPage.tsx +441 -0
  198. package/src/viewer/components/Layout.tsx +22 -0
  199. package/src/viewer/components/LeftSidebar.tsx +391 -0
  200. package/src/viewer/components/MultiViewportPreview.tsx +429 -0
  201. package/src/viewer/components/PreviewArea.tsx +404 -0
  202. package/src/viewer/components/PreviewFrameHost.tsx +310 -0
  203. package/src/viewer/components/PreviewPane.tsx +150 -0
  204. package/src/viewer/components/PreviewToolbar.tsx +176 -0
  205. package/src/viewer/components/PropsEditor.tsx +512 -0
  206. package/src/viewer/components/PropsTable.tsx +98 -0
  207. package/src/viewer/components/RelationsSection.tsx +57 -0
  208. package/src/viewer/components/ResizablePanel.tsx +328 -0
  209. package/src/viewer/components/RightSidebar.tsx +118 -0
  210. package/src/viewer/components/ScreenshotButton.tsx +90 -0
  211. package/src/viewer/components/Sidebar.tsx +169 -0
  212. package/src/viewer/components/SkeletonLoader.tsx +156 -0
  213. package/src/viewer/components/StoryRenderer.tsx +128 -0
  214. package/src/viewer/components/ThemeProvider.tsx +96 -0
  215. package/src/viewer/components/Toast.tsx +67 -0
  216. package/src/viewer/components/TokenStylePanel.tsx +708 -0
  217. package/src/viewer/components/UsageSection.tsx +95 -0
  218. package/src/viewer/components/VariantMatrix.tsx +350 -0
  219. package/src/viewer/components/VariantRenderer.tsx +131 -0
  220. package/src/viewer/components/VariantTabs.tsx +84 -0
  221. package/src/viewer/components/ViewportSelector.tsx +165 -0
  222. package/src/viewer/components/_future/CreatePage.tsx +836 -0
  223. package/src/viewer/composition-renderer.ts +381 -0
  224. package/src/viewer/constants/index.ts +1 -0
  225. package/src/viewer/constants/ui.ts +185 -0
  226. package/src/viewer/entry.tsx +299 -0
  227. package/src/viewer/hooks/index.ts +2 -0
  228. package/src/viewer/hooks/useA11yCache.ts +383 -0
  229. package/src/viewer/hooks/useA11yService.ts +498 -0
  230. package/src/viewer/hooks/useActions.ts +138 -0
  231. package/src/viewer/hooks/useAppState.ts +124 -0
  232. package/src/viewer/hooks/useFigmaIntegration.ts +132 -0
  233. package/src/viewer/hooks/useHmrStatus.ts +109 -0
  234. package/src/viewer/hooks/useKeyboardShortcuts.ts +222 -0
  235. package/src/viewer/hooks/usePreviewBridge.ts +347 -0
  236. package/src/viewer/hooks/useScrollSpy.ts +78 -0
  237. package/src/viewer/hooks/useUrlState.ts +330 -0
  238. package/src/viewer/hooks/useViewSettings.ts +125 -0
  239. package/src/viewer/index.html +28 -0
  240. package/src/viewer/index.ts +14 -0
  241. package/src/viewer/intelligence/healthReport.ts +505 -0
  242. package/src/viewer/intelligence/styleDrift.ts +340 -0
  243. package/src/viewer/intelligence/usageScanner.ts +309 -0
  244. package/src/viewer/jsx-parser.ts +485 -0
  245. package/src/viewer/postcss.config.js +6 -0
  246. package/src/viewer/preview-frame-entry.tsx +25 -0
  247. package/src/viewer/preview-frame.html +109 -0
  248. package/src/viewer/render-template.html +68 -0
  249. package/src/viewer/render-utils.ts +170 -0
  250. package/src/viewer/server.ts +276 -0
  251. package/src/viewer/style-utils.ts +414 -0
  252. package/src/viewer/styles/globals.css +355 -0
  253. package/src/viewer/tailwind.config.js +37 -0
  254. package/src/viewer/types/a11y.ts +197 -0
  255. package/src/viewer/utils/a11y-fixes.ts +471 -0
  256. package/src/viewer/utils/actionExport.ts +372 -0
  257. package/src/viewer/utils/colorSchemes.ts +201 -0
  258. package/src/viewer/utils/detectRelationships.ts +256 -0
  259. 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
+ }