@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,761 @@
1
+ /**
2
+ * Runtime adapter for converting Storybook CSF modules to Segment definitions.
3
+ *
4
+ * This operates on IMPORTED modules at runtime, not source code parsing.
5
+ * By leveraging Vite's module system, we get 100% accurate render functions
6
+ * without any regex or AST parsing complexity.
7
+ *
8
+ * Supports Storybook 8.x with both CSF2 (Template.bind) and CSF3 (object stories).
9
+ */
10
+
11
+ import { createElement, type ComponentType, type ReactNode } from "react";
12
+ import { toId, storyNameFromExport, isExportStory } from "@storybook/csf";
13
+ import type {
14
+ SegmentDefinition,
15
+ SegmentMeta,
16
+ SegmentUsage,
17
+ PropDefinition,
18
+ SegmentVariant,
19
+ ControlType,
20
+ VariantLoader,
21
+ PlayFunction,
22
+ PlayFunctionContext,
23
+ VariantRenderOptions,
24
+ } from "./types.js";
25
+
26
+ // Re-export @storybook/csf utilities for use in other modules
27
+ export { toId, storyNameFromExport, isExportStory };
28
+
29
+ /**
30
+ * Storybook decorator function signature
31
+ */
32
+ export type Decorator = (
33
+ Story: () => ReactNode,
34
+ context: StoryContext
35
+ ) => ReactNode;
36
+
37
+ /**
38
+ * Storybook loader function signature
39
+ */
40
+ export type Loader = (context: StoryContext) => Promise<Record<string, unknown>>;
41
+
42
+ /**
43
+ * Storybook play function signature (internal, extends StoryContext)
44
+ */
45
+ type StorybookPlayFunction = (context: StorybookPlayFunctionContext) => Promise<void>;
46
+
47
+ /**
48
+ * Context passed to Storybook play functions (extends StoryContext for compatibility)
49
+ */
50
+ interface StorybookPlayFunctionContext extends StoryContext {
51
+ canvasElement: HTMLElement;
52
+ step: (name: string, fn: () => Promise<void>) => Promise<void>;
53
+ }
54
+
55
+ /**
56
+ * Context passed to decorators and render functions
57
+ */
58
+ export interface StoryContext {
59
+ args: Record<string, unknown>;
60
+ argTypes: Record<string, StoryArgType>;
61
+ globals: Record<string, unknown>;
62
+ parameters: Record<string, unknown>;
63
+ id: string;
64
+ kind: string;
65
+ name: string;
66
+ story: string;
67
+ viewMode: "story" | "docs";
68
+ loaded: Record<string, unknown>;
69
+ abortSignal: AbortSignal;
70
+ componentId: string;
71
+ title: string;
72
+ }
73
+
74
+ /**
75
+ * Storybook Meta (default export)
76
+ */
77
+ export interface StoryMeta {
78
+ title?: string;
79
+ component?: ComponentType<unknown>;
80
+ subcomponents?: Record<string, ComponentType<unknown>>;
81
+ tags?: string[];
82
+ parameters?: Record<string, unknown> & {
83
+ docs?: {
84
+ description?: {
85
+ component?: string;
86
+ };
87
+ };
88
+ };
89
+ argTypes?: Record<string, StoryArgType>;
90
+ args?: Record<string, unknown>;
91
+ decorators?: Decorator[];
92
+ loaders?: Loader[];
93
+ render?: (args: Record<string, unknown>, context?: StoryContext) => ReactNode;
94
+ // Story filtering
95
+ includeStories?: string[] | RegExp;
96
+ excludeStories?: string[] | RegExp;
97
+ }
98
+
99
+ /**
100
+ * Storybook argType definition
101
+ */
102
+ export interface StoryArgType {
103
+ control?:
104
+ | string
105
+ | false
106
+ | { type: string; min?: number; max?: number; step?: number; presetColors?: string[] };
107
+ options?: string[];
108
+ description?: string;
109
+ table?: {
110
+ defaultValue?: { summary: string };
111
+ type?: { summary: string };
112
+ category?: string;
113
+ subcategory?: string;
114
+ disable?: boolean;
115
+ };
116
+ type?: { name: string; required?: boolean };
117
+ name?: string;
118
+ defaultValue?: unknown;
119
+ if?: { arg?: string; exists?: boolean };
120
+ mapping?: Record<string, unknown>;
121
+ action?: string;
122
+ }
123
+
124
+ /**
125
+ * Storybook Story export (CSF3)
126
+ */
127
+ export interface Story {
128
+ args?: Record<string, unknown>;
129
+ argTypes?: Record<string, StoryArgType>;
130
+ render?: (args: Record<string, unknown>, context?: StoryContext) => ReactNode;
131
+ decorators?: Decorator[];
132
+ loaders?: Loader[];
133
+ play?: StorybookPlayFunction;
134
+ parameters?: Record<string, unknown> & {
135
+ docs?: {
136
+ description?: {
137
+ story?: string;
138
+ };
139
+ };
140
+ };
141
+ name?: string;
142
+ storyName?: string; // Legacy CSF2
143
+ tags?: string[];
144
+ }
145
+
146
+ /**
147
+ * CSF2 story function (from Template.bind({})) with args attached
148
+ */
149
+ export type CSF2Story = ((args: Record<string, unknown>) => ReactNode) & {
150
+ args?: Record<string, unknown>;
151
+ argTypes?: Record<string, StoryArgType>;
152
+ decorators?: Decorator[];
153
+ loaders?: Loader[];
154
+ play?: StorybookPlayFunction;
155
+ parameters?: Record<string, unknown>;
156
+ storyName?: string;
157
+ };
158
+
159
+ /**
160
+ * A complete Storybook module with default meta and named story exports
161
+ */
162
+ export interface StoryModule {
163
+ default: StoryMeta;
164
+ [exportName: string]: Story | CSF2Story | StoryMeta | unknown;
165
+ }
166
+
167
+ /**
168
+ * Global configuration from preview.tsx
169
+ */
170
+ export interface PreviewConfig {
171
+ decorators?: Decorator[];
172
+ parameters?: Record<string, unknown>;
173
+ globalTypes?: Record<string, unknown>;
174
+ args?: Record<string, unknown>;
175
+ argTypes?: Record<string, StoryArgType>;
176
+ loaders?: Loader[];
177
+ }
178
+
179
+ // Store for global preview config (set by previewLoader)
180
+ let globalPreviewConfig: PreviewConfig = {};
181
+
182
+ /**
183
+ * Set the global preview configuration loaded from .storybook/preview.tsx
184
+ */
185
+ export function setPreviewConfig(config: PreviewConfig): void {
186
+ globalPreviewConfig = config;
187
+ }
188
+
189
+ /**
190
+ * Get the current global preview configuration
191
+ */
192
+ export function getPreviewConfig(): PreviewConfig {
193
+ return globalPreviewConfig;
194
+ }
195
+
196
+ /**
197
+ * Convert a Storybook module to a Segment definition at runtime.
198
+ *
199
+ * @param storyModule - The imported Storybook module
200
+ * @param filePath - File path for metadata extraction
201
+ * @returns A complete SegmentDefinition ready for the viewer
202
+ */
203
+ export function storyModuleToSegment(
204
+ storyModule: StoryModule,
205
+ filePath: string
206
+ ): SegmentDefinition | null {
207
+ const meta = storyModule.default;
208
+ const component = meta.component;
209
+
210
+ // Stories without a component (e.g., documentation pages, icon galleries) are skipped
211
+ if (!component) {
212
+ return null;
213
+ }
214
+
215
+ const componentName = extractComponentName(meta, filePath);
216
+ const category = extractCategory(meta.title);
217
+ const props = convertArgTypes(meta.argTypes ?? {}, globalPreviewConfig.argTypes);
218
+ const variants = extractVariants(storyModule, component, meta);
219
+
220
+ // Extract Figma URL from parameters.design.url (storybook-addon-designs) or parameters.figma
221
+ const figmaUrl = extractFigmaUrl(meta.parameters);
222
+
223
+ const segmentMeta: SegmentMeta = {
224
+ name: componentName,
225
+ description:
226
+ meta.parameters?.docs?.description?.component ??
227
+ `${componentName} component`,
228
+ category,
229
+ tags: meta.tags?.filter((t) => t !== "autodocs"),
230
+ status: "stable",
231
+ figma: figmaUrl,
232
+ };
233
+
234
+ const usage: SegmentUsage = {
235
+ when: [`Use ${componentName} for its intended purpose`],
236
+ whenNot: ["When a more specific component is available"],
237
+ };
238
+
239
+ return {
240
+ component,
241
+ meta: segmentMeta,
242
+ usage,
243
+ props,
244
+ variants,
245
+ };
246
+ }
247
+
248
+ /**
249
+ * Extract component name from meta or file path
250
+ */
251
+ function extractComponentName(meta: StoryMeta, filePath: string): string {
252
+ // Try title (last segment of path like "Components/Forms/Button" -> "Button")
253
+ if (meta.title) {
254
+ const parts = meta.title.split("/");
255
+ return parts[parts.length - 1];
256
+ }
257
+
258
+ // Try component displayName
259
+ if (meta.component?.displayName) {
260
+ return meta.component.displayName;
261
+ }
262
+
263
+ // Try component name
264
+ if (meta.component?.name && meta.component.name !== "Component") {
265
+ return meta.component.name;
266
+ }
267
+
268
+ // Fallback: extract from file path
269
+ const match = filePath.match(/([^/\\]+)\.stories\.(tsx?|jsx?)$/);
270
+ return match?.[1] ?? "Unknown";
271
+ }
272
+
273
+ /**
274
+ * Extract category from Storybook title path
275
+ */
276
+ function extractCategory(title?: string): string {
277
+ if (!title) return "general";
278
+
279
+ const parts = title.split("/");
280
+ // "Components/Forms/Button" -> "forms" (need at least 3 parts for a subcategory)
281
+ if (parts.length >= 3) {
282
+ return parts[parts.length - 2].toLowerCase();
283
+ }
284
+ // "Components/Button" -> "general" (no subcategory specified)
285
+ return "general";
286
+ }
287
+
288
+ /**
289
+ * Extract Figma URL from Storybook parameters
290
+ * Supports storybook-addon-designs format and custom figma parameter
291
+ */
292
+ function extractFigmaUrl(parameters?: Record<string, unknown>): string | undefined {
293
+ if (!parameters) return undefined;
294
+
295
+ // Try storybook-addon-designs format: parameters.design.url
296
+ const design = parameters.design as { url?: string; type?: string } | undefined;
297
+ if (design?.url && typeof design.url === "string") {
298
+ return design.url;
299
+ }
300
+
301
+ // Try custom figma parameter: parameters.figma
302
+ if (typeof parameters.figma === "string") {
303
+ return parameters.figma;
304
+ }
305
+
306
+ return undefined;
307
+ }
308
+
309
+ /**
310
+ * Convert Storybook argTypes to Segment props
311
+ * Merges global argTypes from preview config with meta argTypes
312
+ */
313
+ function convertArgTypes(
314
+ argTypes: Record<string, StoryArgType>,
315
+ globalArgTypes?: Record<string, StoryArgType>
316
+ ): Record<string, PropDefinition> {
317
+ const props: Record<string, PropDefinition> = {};
318
+
319
+ // Merge global and meta argTypes (meta takes precedence)
320
+ const mergedArgTypes = { ...globalArgTypes, ...argTypes };
321
+
322
+ for (const [name, argType] of Object.entries(mergedArgTypes)) {
323
+ // Skip disabled argTypes
324
+ if (argType.table?.disable) continue;
325
+ // Skip action-only argTypes (no control)
326
+ if (argType.control === false && argType.action) continue;
327
+
328
+ // Extract control type and options
329
+ const { controlType, controlOptions } = extractControlInfo(argType);
330
+
331
+ props[name] = {
332
+ type: inferPropType(argType),
333
+ description: argType.description ?? `${name} prop`,
334
+ ...(argType.options && { values: argType.options }),
335
+ ...(argType.table?.defaultValue && {
336
+ default: argType.table.defaultValue.summary,
337
+ }),
338
+ ...(argType.defaultValue !== undefined && {
339
+ default: argType.defaultValue,
340
+ }),
341
+ ...(argType.type?.required && { required: true }),
342
+ ...(controlType && { controlType }),
343
+ ...(controlOptions && Object.keys(controlOptions).length > 0 && { controlOptions }),
344
+ };
345
+ }
346
+
347
+ return props;
348
+ }
349
+
350
+ /**
351
+ * Extract control type and options from a Storybook argType
352
+ */
353
+ function extractControlInfo(argType: StoryArgType): {
354
+ controlType?: ControlType;
355
+ controlOptions?: PropDefinition["controlOptions"];
356
+ } {
357
+ // Handle no control or explicitly disabled control
358
+ if (argType.control === undefined || argType.control === false) {
359
+ return {};
360
+ }
361
+
362
+ const control = typeof argType.control === "string"
363
+ ? { type: argType.control }
364
+ : argType.control;
365
+
366
+ // Map control type string to ControlType
367
+ const validControlTypes: ControlType[] = [
368
+ "text", "number", "range", "boolean", "select", "multi-select",
369
+ "radio", "inline-radio", "check", "inline-check", "object", "file", "color", "date"
370
+ ];
371
+
372
+ const controlType = validControlTypes.includes(control.type as ControlType)
373
+ ? (control.type as ControlType)
374
+ : undefined;
375
+
376
+ // Extract control options for controls that need them
377
+ const controlOptions: PropDefinition["controlOptions"] = {};
378
+
379
+ if (control.min !== undefined) controlOptions.min = control.min;
380
+ if (control.max !== undefined) controlOptions.max = control.max;
381
+ if (control.step !== undefined) controlOptions.step = control.step;
382
+ if (control.presetColors) controlOptions.presetColors = control.presetColors;
383
+
384
+ return {
385
+ controlType,
386
+ controlOptions: Object.keys(controlOptions).length > 0 ? controlOptions : undefined,
387
+ };
388
+ }
389
+
390
+ /**
391
+ * Infer prop type from Storybook control/type
392
+ * Handles all Storybook 8.x control types
393
+ */
394
+ function inferPropType(argType: StoryArgType): PropDefinition["type"] {
395
+ // Action argType → function
396
+ if (argType.action) return "function";
397
+
398
+ // If has options, it's an enum
399
+ if (argType.options?.length) return "enum";
400
+
401
+ // Check explicit type
402
+ if (argType.type?.name) {
403
+ const typeMap: Record<string, PropDefinition["type"]> = {
404
+ string: "string",
405
+ number: "number",
406
+ boolean: "boolean",
407
+ object: "object",
408
+ array: "array",
409
+ function: "function",
410
+ };
411
+ const mapped = typeMap[argType.type.name];
412
+ if (mapped) return mapped;
413
+ }
414
+
415
+ // Check control type
416
+ const control =
417
+ typeof argType.control === "string"
418
+ ? argType.control
419
+ : argType.control
420
+ ? argType.control.type
421
+ : undefined;
422
+
423
+ if (control) {
424
+ const controlMap: Record<string, PropDefinition["type"]> = {
425
+ // Text controls
426
+ text: "string",
427
+
428
+ // Number controls
429
+ number: "number",
430
+ range: "number",
431
+
432
+ // Boolean controls
433
+ boolean: "boolean",
434
+ check: "boolean",
435
+ "inline-check": "boolean",
436
+
437
+ // Enum/selection controls
438
+ select: "enum",
439
+ "multi-select": "enum",
440
+ radio: "enum",
441
+ "inline-radio": "enum",
442
+
443
+ // Object controls
444
+ object: "object",
445
+ file: "object",
446
+
447
+ // Special string controls
448
+ color: "string",
449
+ date: "string",
450
+ };
451
+ const mapped = controlMap[control];
452
+ if (mapped) return mapped;
453
+ }
454
+
455
+ return "string";
456
+ }
457
+
458
+ /**
459
+ * Check if a value looks like a Storybook story
460
+ * Handles both CSF 3 (objects) and CSF 2 (functions from Template.bind({}))
461
+ */
462
+ function isStory(value: unknown): value is Story | CSF2Story {
463
+ // CSF 3: Story is an object with args/render/play
464
+ if (typeof value === "object" && value !== null) {
465
+ const obj = value as Record<string, unknown>;
466
+ if ("args" in obj || "render" in obj || "play" in obj) return true;
467
+ }
468
+
469
+ // CSF 2: Story is a function (from Template.bind({})) with args attached
470
+ if (typeof value === "function") {
471
+ const fn = value as ((...args: unknown[]) => unknown) & { args?: unknown };
472
+ if ("args" in fn) return true;
473
+ }
474
+
475
+ return false;
476
+ }
477
+
478
+ /**
479
+ * Extract variants from story exports using @storybook/csf utilities
480
+ */
481
+ function extractVariants(
482
+ storyModule: StoryModule,
483
+ component: ComponentType<unknown>,
484
+ meta: StoryMeta
485
+ ): SegmentVariant[] {
486
+ const variants: SegmentVariant[] = [];
487
+
488
+ for (const [exportName, exportValue] of Object.entries(storyModule)) {
489
+ // Skip default export
490
+ if (exportName === "default") continue;
491
+
492
+ // Use isExportStory to filter based on includeStories/excludeStories
493
+ if (!isExportStory(exportName, meta)) continue;
494
+
495
+ // Check if it's a story
496
+ if (!isStory(exportValue)) continue;
497
+
498
+ const story = exportValue as Story | CSF2Story;
499
+
500
+ // Get story name using storyNameFromExport
501
+ const storyName =
502
+ (typeof story === "object" && story.name) ||
503
+ (typeof story === "object" && story.storyName) ||
504
+ (typeof story === "function" && story.storyName) ||
505
+ storyNameFromExport(exportName);
506
+
507
+ // Generate story ID matching Storybook format
508
+ const storyId = toId(meta.title || "Unknown", exportName);
509
+
510
+ // Extract description based on story format
511
+ let description = `${storyName} variant`;
512
+ if (typeof story === "object" && story.parameters?.docs?.description?.story) {
513
+ description = story.parameters.docs.description.story;
514
+ }
515
+
516
+ // Check for play function and capture it
517
+ const storyPlayFn = typeof story === "object" ? story.play : story.play;
518
+ const hasPlayFunction = !!storyPlayFn;
519
+
520
+ // Create wrapped play function that adapts Storybook context to our PlayFunctionContext
521
+ const wrappedPlay: PlayFunction | undefined = storyPlayFn
522
+ ? async (context: PlayFunctionContext): Promise<void> => {
523
+ // Build full Storybook context for compatibility
524
+ const args = {
525
+ ...globalPreviewConfig.args,
526
+ ...meta.args,
527
+ ...(typeof story === "function" ? story.args : story.args),
528
+ };
529
+ const fullContext = buildStoryContext(meta, story, args, storyId, storyName);
530
+
531
+ // Merge our context with Storybook context
532
+ const playContext = {
533
+ ...fullContext,
534
+ canvasElement: context.canvasElement,
535
+ args: context.args,
536
+ step: context.step,
537
+ };
538
+
539
+ await storyPlayFn(playContext as unknown as StorybookPlayFunctionContext);
540
+ }
541
+ : undefined;
542
+
543
+ // Get story tags
544
+ const storyTags = typeof story === "object" ? story.tags : undefined;
545
+
546
+ // Collect loaders from global, meta, and story (in order)
547
+ const loaders = collectLoaders(meta, story);
548
+
549
+ // Compute the merged args for this variant (for code generation)
550
+ const variantArgs = {
551
+ ...globalPreviewConfig.args,
552
+ ...meta.args,
553
+ ...(typeof story === "function" ? story.args : story.args),
554
+ };
555
+
556
+ // Only include args if there are any defined
557
+ const hasArgs = Object.keys(variantArgs).length > 0;
558
+
559
+ variants.push({
560
+ name: storyName,
561
+ description,
562
+ render: createRenderFunction(story, component, meta, storyId, storyName),
563
+ // Store Storybook-specific metadata
564
+ ...(hasPlayFunction && { hasPlayFunction: true }),
565
+ ...(wrappedPlay && { play: wrappedPlay }),
566
+ ...(storyId && { storyId }),
567
+ ...(storyTags && { tags: storyTags }),
568
+ ...(loaders.length > 0 && { loaders }),
569
+ ...(hasArgs && { args: variantArgs }),
570
+ });
571
+ }
572
+
573
+ return variants;
574
+ }
575
+
576
+ /**
577
+ * Collect loaders from global, meta, and story levels
578
+ * Returns wrapped loader functions that execute with context
579
+ */
580
+ function collectLoaders(
581
+ meta: StoryMeta,
582
+ story: Story | CSF2Story
583
+ ): VariantLoader[] {
584
+ const allLoaders: Loader[] = [
585
+ ...(globalPreviewConfig.loaders ?? []),
586
+ ...(meta.loaders ?? []),
587
+ ...(typeof story === "function" ? story.loaders ?? [] : story.loaders ?? []),
588
+ ];
589
+
590
+ if (allLoaders.length === 0) {
591
+ return [];
592
+ }
593
+
594
+ // Wrap each loader to execute without requiring context at call time
595
+ // The actual context will be built when the loader is executed
596
+ return allLoaders.map((loader) => {
597
+ return async (): Promise<Record<string, unknown>> => {
598
+ // Create a minimal context for loader execution
599
+ const minimalContext: StoryContext = {
600
+ args: {},
601
+ argTypes: {},
602
+ globals: {},
603
+ parameters: {},
604
+ id: "",
605
+ kind: meta.title || "Unknown",
606
+ name: "",
607
+ story: "",
608
+ viewMode: "story",
609
+ loaded: {},
610
+ abortSignal: new AbortController().signal,
611
+ componentId: "",
612
+ title: meta.title || "Unknown",
613
+ };
614
+ return loader(minimalContext);
615
+ };
616
+ });
617
+ }
618
+
619
+ /**
620
+ * Build a StoryContext for decorators and render functions
621
+ */
622
+ function buildStoryContext(
623
+ meta: StoryMeta,
624
+ story: Story | CSF2Story,
625
+ args: Record<string, unknown>,
626
+ storyId: string,
627
+ storyName: string,
628
+ loadedData?: Record<string, unknown>
629
+ ): StoryContext {
630
+ const mergedArgs = {
631
+ ...globalPreviewConfig.args,
632
+ ...meta.args,
633
+ ...(typeof story === "object" ? story.args : story.args),
634
+ ...args,
635
+ };
636
+
637
+ const mergedArgTypes = {
638
+ ...globalPreviewConfig.argTypes,
639
+ ...meta.argTypes,
640
+ ...(typeof story === "object" ? story.argTypes : story.argTypes),
641
+ };
642
+
643
+ const mergedParameters = {
644
+ ...globalPreviewConfig.parameters,
645
+ ...meta.parameters,
646
+ ...(typeof story === "object" ? story.parameters : story.parameters),
647
+ };
648
+
649
+ return {
650
+ args: mergedArgs,
651
+ argTypes: mergedArgTypes ?? {},
652
+ globals: {},
653
+ parameters: mergedParameters ?? {},
654
+ id: storyId,
655
+ kind: meta.title || "Unknown",
656
+ name: storyName,
657
+ story: storyName,
658
+ viewMode: "story",
659
+ loaded: loadedData ?? {},
660
+ abortSignal: new AbortController().signal,
661
+ componentId: toId(meta.title || "Unknown", ""),
662
+ title: meta.title || "Unknown",
663
+ };
664
+ }
665
+
666
+ /**
667
+ * Create a render function for a story
668
+ * Handles both CSF 3 (objects) and CSF 2 (functions)
669
+ * Applies decorators in correct order: story → meta → global (innermost first)
670
+ * Accepts optional args overrides and loaded data from loaders
671
+ */
672
+ function createRenderFunction(
673
+ story: Story | CSF2Story,
674
+ component: ComponentType<unknown>,
675
+ meta: StoryMeta,
676
+ storyId: string,
677
+ storyName: string
678
+ ): (options?: VariantRenderOptions) => ReactNode {
679
+ return (options?: VariantRenderOptions) => {
680
+ // Merge args: global → meta → story → runtime overrides
681
+ const args = {
682
+ ...globalPreviewConfig.args,
683
+ ...meta.args,
684
+ ...(typeof story === "function" ? story.args : story.args),
685
+ ...options?.args, // Runtime overrides from viewer props panel
686
+ };
687
+
688
+ const loadedData = options?.loadedData;
689
+
690
+ // Build the story context with loaded data
691
+ const context = buildStoryContext(meta, story, args, storyId, storyName, loadedData);
692
+
693
+ // Create the base render function
694
+ let renderFn: () => ReactNode;
695
+
696
+ if (typeof story === "function") {
697
+ // CSF 2: Story is a function (from Template.bind({}))
698
+ renderFn = () => story(args);
699
+ } else if (story.render) {
700
+ // CSF 3: Story has custom render function
701
+ // Support both render(args) and render(args, context) signatures
702
+ renderFn = () =>
703
+ story.render!.length >= 2
704
+ ? story.render!(args, context)
705
+ : story.render!(args);
706
+ } else if (meta.render) {
707
+ // CSF 3: Meta has default render function
708
+ renderFn = () =>
709
+ meta.render!.length >= 2
710
+ ? meta.render!(args, context)
711
+ : meta.render!(args);
712
+ } else {
713
+ // Default: render component with args
714
+ renderFn = () => createElement(component, args);
715
+ }
716
+
717
+ // Collect decorators in Storybook order
718
+ // story → meta → global, then reverse to apply innermost first
719
+ const allDecorators = [
720
+ ...(globalPreviewConfig.decorators ?? []),
721
+ ...(meta.decorators ?? []),
722
+ ...(typeof story === "function" ? story.decorators ?? [] : story.decorators ?? []),
723
+ ].reverse();
724
+
725
+ // Apply decorators if any
726
+ if (allDecorators.length > 0) {
727
+ return applyDecorators(renderFn, allDecorators, context);
728
+ }
729
+
730
+ return renderFn();
731
+ };
732
+ }
733
+
734
+ /**
735
+ * Apply decorators in the correct order
736
+ * Decorators wrap from innermost to outermost
737
+ */
738
+ function applyDecorators(
739
+ renderFn: () => ReactNode,
740
+ decorators: Decorator[],
741
+ context: StoryContext
742
+ ): ReactNode {
743
+ // Start with the base render function
744
+ let storyFn: () => ReactNode = renderFn;
745
+
746
+ // Each decorator wraps the previous one
747
+ for (const decorator of decorators) {
748
+ const wrappedFn = storyFn;
749
+ storyFn = () => decorator(wrappedFn, context);
750
+ }
751
+
752
+ return storyFn();
753
+ }
754
+
755
+ /**
756
+ * Convert PascalCase to Title Case
757
+ * @deprecated Use storyNameFromExport from @storybook/csf instead
758
+ */
759
+ function pascalToTitle(name: string): string {
760
+ return name.replace(/([A-Z])/g, " $1").trim();
761
+ }