@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,551 @@
1
+ /**
2
+ * AST-Based Segment Parser
3
+ *
4
+ * Extracts segment metadata from .segment.tsx files WITHOUT executing them.
5
+ * This allows `segments build` to work without any project dependencies.
6
+ *
7
+ * Uses TypeScript's compiler API to parse and analyze the AST.
8
+ */
9
+
10
+ import ts from "typescript";
11
+ import type { SegmentMeta, SegmentUsage, PropDefinition } from "./types.js";
12
+
13
+ /**
14
+ * Parsed segment metadata (extracted statically from AST)
15
+ */
16
+ export interface ParsedSegmentMetadata {
17
+ /** Component import path (e.g., "./Button" or "@/components/Button") */
18
+ componentImport: string | null;
19
+
20
+ /** Component name as used in the file */
21
+ componentName: string | null;
22
+
23
+ /** Component metadata */
24
+ meta: Partial<SegmentMeta>;
25
+
26
+ /** Usage guidelines */
27
+ usage: Partial<SegmentUsage>;
28
+
29
+ /** Props documentation */
30
+ props: Record<string, Partial<PropDefinition>>;
31
+
32
+ /** Variant definitions */
33
+ variants: Array<{
34
+ name: string;
35
+ description: string;
36
+ code?: string;
37
+ figma?: string;
38
+ args?: Record<string, unknown>;
39
+ }>;
40
+
41
+ /** Related components */
42
+ relations: Array<{
43
+ component: string;
44
+ relationship: string;
45
+ note: string;
46
+ }>;
47
+
48
+ /** Parse warnings */
49
+ warnings: string[];
50
+ }
51
+
52
+ /**
53
+ * Parse a segment file and extract metadata without executing it.
54
+ */
55
+ export function parseSegmentFile(
56
+ fileContent: string,
57
+ filePath?: string
58
+ ): ParsedSegmentMetadata {
59
+ const warnings: string[] = [];
60
+
61
+ // Parse the file
62
+ const sourceFile = ts.createSourceFile(
63
+ filePath ?? "segment.tsx",
64
+ fileContent,
65
+ ts.ScriptTarget.Latest,
66
+ true,
67
+ ts.ScriptKind.TSX
68
+ );
69
+
70
+ // Find imports
71
+ const imports = extractImports(sourceFile);
72
+
73
+ // Find the defineSegment call
74
+ const defineSegmentCall = findDefineSegmentCall(sourceFile);
75
+
76
+ if (!defineSegmentCall) {
77
+ warnings.push("No defineSegment() call found");
78
+ return {
79
+ componentImport: null,
80
+ componentName: null,
81
+ meta: {},
82
+ usage: { when: [], whenNot: [] },
83
+ props: {},
84
+ variants: [],
85
+ relations: [],
86
+ warnings,
87
+ };
88
+ }
89
+
90
+ // Extract the argument (object literal)
91
+ const arg = defineSegmentCall.arguments[0];
92
+ if (!arg || !ts.isObjectLiteralExpression(arg)) {
93
+ warnings.push("defineSegment() argument is not an object literal");
94
+ return {
95
+ componentImport: null,
96
+ componentName: null,
97
+ meta: {},
98
+ usage: { when: [], whenNot: [] },
99
+ props: {},
100
+ variants: [],
101
+ relations: [],
102
+ warnings,
103
+ };
104
+ }
105
+
106
+ // Extract component reference
107
+ const componentProp = findProperty(arg, "component");
108
+ let componentName: string | null = null;
109
+ let componentImport: string | null = null;
110
+
111
+ if (componentProp && ts.isIdentifier(componentProp)) {
112
+ componentName = componentProp.text;
113
+ // Find the import for this component
114
+ componentImport = imports.get(componentName) ?? null;
115
+ }
116
+
117
+ // Extract meta
118
+ const meta = extractMeta(arg, warnings);
119
+
120
+ // Extract usage
121
+ const usage = extractUsage(arg, warnings);
122
+
123
+ // Extract props
124
+ const props = extractProps(arg, warnings);
125
+
126
+ // Extract variants
127
+ const variants = extractVariants(arg, sourceFile, warnings);
128
+
129
+ // Extract relations
130
+ const relations = extractRelations(arg, warnings);
131
+
132
+ return {
133
+ componentImport,
134
+ componentName,
135
+ meta,
136
+ usage,
137
+ props,
138
+ variants,
139
+ relations,
140
+ warnings,
141
+ };
142
+ }
143
+
144
+ /**
145
+ * Extract all imports from the source file.
146
+ * Returns a map of imported name -> module path
147
+ */
148
+ function extractImports(sourceFile: ts.SourceFile): Map<string, string> {
149
+ const imports = new Map<string, string>();
150
+
151
+ ts.forEachChild(sourceFile, (node) => {
152
+ if (ts.isImportDeclaration(node)) {
153
+ const moduleSpecifier = node.moduleSpecifier;
154
+ if (ts.isStringLiteral(moduleSpecifier)) {
155
+ const modulePath = moduleSpecifier.text;
156
+
157
+ // Handle named imports
158
+ const importClause = node.importClause;
159
+ if (importClause) {
160
+ // Default import
161
+ if (importClause.name) {
162
+ imports.set(importClause.name.text, modulePath);
163
+ }
164
+
165
+ // Named imports
166
+ const namedBindings = importClause.namedBindings;
167
+ if (namedBindings && ts.isNamedImports(namedBindings)) {
168
+ for (const element of namedBindings.elements) {
169
+ imports.set(element.name.text, modulePath);
170
+ }
171
+ }
172
+ }
173
+ }
174
+ }
175
+ });
176
+
177
+ return imports;
178
+ }
179
+
180
+ /**
181
+ * Find the defineSegment() call in the source file.
182
+ */
183
+ function findDefineSegmentCall(
184
+ sourceFile: ts.SourceFile
185
+ ): ts.CallExpression | null {
186
+ let result: ts.CallExpression | null = null;
187
+
188
+ function visit(node: ts.Node): void {
189
+ if (ts.isCallExpression(node)) {
190
+ const expression = node.expression;
191
+ if (ts.isIdentifier(expression) && expression.text === "defineSegment") {
192
+ result = node;
193
+ return;
194
+ }
195
+ }
196
+ ts.forEachChild(node, visit);
197
+ }
198
+
199
+ visit(sourceFile);
200
+ return result;
201
+ }
202
+
203
+ /**
204
+ * Find a property in an object literal by name.
205
+ */
206
+ function findProperty(
207
+ obj: ts.ObjectLiteralExpression,
208
+ name: string
209
+ ): ts.Expression | null {
210
+ for (const prop of obj.properties) {
211
+ if (ts.isPropertyAssignment(prop)) {
212
+ const propName = prop.name;
213
+ if (ts.isIdentifier(propName) && propName.text === name) {
214
+ return prop.initializer;
215
+ }
216
+ }
217
+ }
218
+ return null;
219
+ }
220
+
221
+ /**
222
+ * Extract meta object from defineSegment call.
223
+ */
224
+ function extractMeta(
225
+ arg: ts.ObjectLiteralExpression,
226
+ warnings: string[]
227
+ ): Partial<SegmentMeta> {
228
+ const metaProp = findProperty(arg, "meta");
229
+ if (!metaProp || !ts.isObjectLiteralExpression(metaProp)) {
230
+ warnings.push("No meta object found");
231
+ return {};
232
+ }
233
+
234
+ const meta: Partial<SegmentMeta> = {};
235
+
236
+ // Extract string properties
237
+ const name = extractStringProperty(metaProp, "name");
238
+ if (name) meta.name = name;
239
+
240
+ const description = extractStringProperty(metaProp, "description");
241
+ if (description) meta.description = description;
242
+
243
+ const category = extractStringProperty(metaProp, "category");
244
+ if (category) meta.category = category;
245
+
246
+ const status = extractStringProperty(metaProp, "status") as SegmentMeta["status"];
247
+ if (status) meta.status = status;
248
+
249
+ const since = extractStringProperty(metaProp, "since");
250
+ if (since) meta.since = since;
251
+
252
+ // Extract figma URL
253
+ const figma = extractStringProperty(metaProp, "figma");
254
+ if (figma) meta.figma = figma;
255
+
256
+ // Extract tags array
257
+ const tags = extractStringArray(metaProp, "tags");
258
+ if (tags.length > 0) meta.tags = tags;
259
+
260
+ return meta;
261
+ }
262
+
263
+ /**
264
+ * Extract usage object from defineSegment call.
265
+ */
266
+ function extractUsage(
267
+ arg: ts.ObjectLiteralExpression,
268
+ warnings: string[]
269
+ ): Partial<SegmentUsage> {
270
+ const usageProp = findProperty(arg, "usage");
271
+ if (!usageProp || !ts.isObjectLiteralExpression(usageProp)) {
272
+ return { when: [], whenNot: [] };
273
+ }
274
+
275
+ return {
276
+ when: extractStringArray(usageProp, "when"),
277
+ whenNot: extractStringArray(usageProp, "whenNot"),
278
+ guidelines: extractStringArray(usageProp, "guidelines"),
279
+ accessibility: extractStringArray(usageProp, "accessibility"),
280
+ };
281
+ }
282
+
283
+ /**
284
+ * Extract props object from defineSegment call.
285
+ */
286
+ function extractProps(
287
+ arg: ts.ObjectLiteralExpression,
288
+ warnings: string[]
289
+ ): Record<string, Partial<PropDefinition>> {
290
+ const propsProp = findProperty(arg, "props");
291
+ if (!propsProp || !ts.isObjectLiteralExpression(propsProp)) {
292
+ return {};
293
+ }
294
+
295
+ const props: Record<string, Partial<PropDefinition>> = {};
296
+
297
+ for (const prop of propsProp.properties) {
298
+ if (ts.isPropertyAssignment(prop) && ts.isIdentifier(prop.name)) {
299
+ const propName = prop.name.text;
300
+ const propValue = prop.initializer;
301
+
302
+ if (ts.isObjectLiteralExpression(propValue)) {
303
+ props[propName] = extractPropDefinition(propValue);
304
+ }
305
+ }
306
+ }
307
+
308
+ return props;
309
+ }
310
+
311
+ /**
312
+ * Extract a single prop definition.
313
+ */
314
+ function extractPropDefinition(
315
+ obj: ts.ObjectLiteralExpression
316
+ ): Partial<PropDefinition> {
317
+ const def: Partial<PropDefinition> = {};
318
+
319
+ const type = extractStringProperty(obj, "type");
320
+ if (type) def.type = type as PropDefinition["type"];
321
+
322
+ const description = extractStringProperty(obj, "description");
323
+ if (description) def.description = description;
324
+
325
+ const values = extractStringArray(obj, "values");
326
+ if (values.length > 0) def.values = values;
327
+
328
+ const required = extractBooleanProperty(obj, "required");
329
+ if (required !== null) def.required = required;
330
+
331
+ // Default value - try to extract as literal
332
+ const defaultProp = findProperty(obj, "default");
333
+ if (defaultProp) {
334
+ def.default = extractLiteralValue(defaultProp);
335
+ }
336
+
337
+ const constraints = extractStringArray(obj, "constraints");
338
+ if (constraints.length > 0) def.constraints = constraints;
339
+
340
+ return def;
341
+ }
342
+
343
+ /**
344
+ * Extract variants array from defineSegment call.
345
+ */
346
+ function extractVariants(
347
+ arg: ts.ObjectLiteralExpression,
348
+ sourceFile: ts.SourceFile,
349
+ warnings: string[]
350
+ ): Array<{ name: string; description: string; code?: string; figma?: string; args?: Record<string, unknown> }> {
351
+ const variantsProp = findProperty(arg, "variants");
352
+ if (!variantsProp || !ts.isArrayLiteralExpression(variantsProp)) {
353
+ return [];
354
+ }
355
+
356
+ const variants: Array<{ name: string; description: string; code?: string; figma?: string; args?: Record<string, unknown> }> = [];
357
+
358
+ for (const element of variantsProp.elements) {
359
+ if (ts.isObjectLiteralExpression(element)) {
360
+ const name = extractStringProperty(element, "name");
361
+ const description = extractStringProperty(element, "description");
362
+
363
+ if (name) {
364
+ const variant: { name: string; description: string; code?: string; figma?: string; args?: Record<string, unknown> } = {
365
+ name,
366
+ description: description ?? "",
367
+ };
368
+
369
+ // Try to extract code property if present
370
+ const codeProp = findProperty(element, "code");
371
+ if (codeProp && ts.isStringLiteral(codeProp)) {
372
+ variant.code = codeProp.text;
373
+ }
374
+
375
+ // Try to extract render function and convert to code string
376
+ const renderProp = findProperty(element, "render");
377
+ if (renderProp && !variant.code) {
378
+ variant.code = extractRenderCode(renderProp, sourceFile);
379
+ }
380
+
381
+ // Extract figma URL for variant
382
+ const figma = extractStringProperty(element, "figma");
383
+ if (figma) {
384
+ variant.figma = figma;
385
+ }
386
+
387
+ // Extract args object for code generation
388
+ const argsProp = findProperty(element, "args");
389
+ if (argsProp && ts.isObjectLiteralExpression(argsProp)) {
390
+ const argsValue = extractLiteralValue(argsProp);
391
+ if (argsValue && typeof argsValue === 'object' && !Array.isArray(argsValue)) {
392
+ variant.args = argsValue as Record<string, unknown>;
393
+ }
394
+ }
395
+
396
+ variants.push(variant);
397
+ }
398
+ }
399
+ }
400
+
401
+ return variants;
402
+ }
403
+
404
+ /**
405
+ * Extract the code from a render function.
406
+ */
407
+ function extractRenderCode(
408
+ renderProp: ts.Expression,
409
+ sourceFile: ts.SourceFile
410
+ ): string | undefined {
411
+ // Handle arrow function: () => <Component />
412
+ if (ts.isArrowFunction(renderProp)) {
413
+ const body = renderProp.body;
414
+ // Get the source text of the body
415
+ const start = body.getStart(sourceFile);
416
+ const end = body.getEnd();
417
+ let code = sourceFile.text.substring(start, end).trim();
418
+
419
+ // Remove parentheses if present
420
+ if (code.startsWith("(") && code.endsWith(")")) {
421
+ code = code.slice(1, -1).trim();
422
+ }
423
+
424
+ return code;
425
+ }
426
+
427
+ return undefined;
428
+ }
429
+
430
+ /**
431
+ * Extract relations array from defineSegment call.
432
+ */
433
+ function extractRelations(
434
+ arg: ts.ObjectLiteralExpression,
435
+ warnings: string[]
436
+ ): Array<{ component: string; relationship: string; note: string }> {
437
+ const relationsProp = findProperty(arg, "relations");
438
+ if (!relationsProp || !ts.isArrayLiteralExpression(relationsProp)) {
439
+ return [];
440
+ }
441
+
442
+ const relations: Array<{ component: string; relationship: string; note: string }> = [];
443
+
444
+ for (const element of relationsProp.elements) {
445
+ if (ts.isObjectLiteralExpression(element)) {
446
+ const component = extractStringProperty(element, "component");
447
+ const relationship = extractStringProperty(element, "relationship");
448
+ const note = extractStringProperty(element, "note");
449
+
450
+ if (component && relationship) {
451
+ relations.push({
452
+ component,
453
+ relationship,
454
+ note: note ?? "",
455
+ });
456
+ }
457
+ }
458
+ }
459
+
460
+ return relations;
461
+ }
462
+
463
+ /**
464
+ * Extract a string property from an object literal.
465
+ */
466
+ function extractStringProperty(
467
+ obj: ts.ObjectLiteralExpression,
468
+ name: string
469
+ ): string | null {
470
+ const prop = findProperty(obj, name);
471
+ if (prop && ts.isStringLiteral(prop)) {
472
+ return prop.text;
473
+ }
474
+ // Handle template literals without expressions
475
+ if (prop && ts.isNoSubstitutionTemplateLiteral(prop)) {
476
+ return prop.text;
477
+ }
478
+ return null;
479
+ }
480
+
481
+ /**
482
+ * Extract a boolean property from an object literal.
483
+ */
484
+ function extractBooleanProperty(
485
+ obj: ts.ObjectLiteralExpression,
486
+ name: string
487
+ ): boolean | null {
488
+ const prop = findProperty(obj, name);
489
+ if (prop) {
490
+ if (prop.kind === ts.SyntaxKind.TrueKeyword) return true;
491
+ if (prop.kind === ts.SyntaxKind.FalseKeyword) return false;
492
+ }
493
+ return null;
494
+ }
495
+
496
+ /**
497
+ * Extract a string array from an object literal.
498
+ */
499
+ function extractStringArray(
500
+ obj: ts.ObjectLiteralExpression,
501
+ name: string
502
+ ): string[] {
503
+ const prop = findProperty(obj, name);
504
+ if (!prop || !ts.isArrayLiteralExpression(prop)) {
505
+ return [];
506
+ }
507
+
508
+ const result: string[] = [];
509
+ for (const element of prop.elements) {
510
+ if (ts.isStringLiteral(element)) {
511
+ result.push(element.text);
512
+ } else if (ts.isNoSubstitutionTemplateLiteral(element)) {
513
+ result.push(element.text);
514
+ }
515
+ }
516
+ return result;
517
+ }
518
+
519
+ /**
520
+ * Extract a literal value from an expression.
521
+ */
522
+ function extractLiteralValue(expr: ts.Expression): unknown {
523
+ if (ts.isStringLiteral(expr)) {
524
+ return expr.text;
525
+ }
526
+ if (ts.isNumericLiteral(expr)) {
527
+ return Number(expr.text);
528
+ }
529
+ if (expr.kind === ts.SyntaxKind.TrueKeyword) {
530
+ return true;
531
+ }
532
+ if (expr.kind === ts.SyntaxKind.FalseKeyword) {
533
+ return false;
534
+ }
535
+ if (expr.kind === ts.SyntaxKind.NullKeyword) {
536
+ return null;
537
+ }
538
+ if (ts.isArrayLiteralExpression(expr)) {
539
+ return expr.elements.map(extractLiteralValue);
540
+ }
541
+ if (ts.isObjectLiteralExpression(expr)) {
542
+ const obj: Record<string, unknown> = {};
543
+ for (const prop of expr.properties) {
544
+ if (ts.isPropertyAssignment(prop) && ts.isIdentifier(prop.name)) {
545
+ obj[prop.name.text] = extractLiteralValue(prop.initializer);
546
+ }
547
+ }
548
+ return obj;
549
+ }
550
+ return undefined;
551
+ }
@@ -0,0 +1,172 @@
1
+ /**
2
+ * Loader for Storybook .storybook/preview.{tsx,ts,jsx,js} configuration.
3
+ *
4
+ * This module detects and loads Storybook preview configuration to extract:
5
+ * - decorators: Global decorators applied to all stories
6
+ * - parameters: Global parameters for all stories
7
+ * - globalTypes: Global type definitions (e.g., for toolbar controls)
8
+ * - args: Default args for all stories
9
+ * - argTypes: Default argTypes for all stories
10
+ * - loaders: Global loaders for all stories
11
+ */
12
+
13
+ import { existsSync } from "node:fs";
14
+ import { join, resolve } from "node:path";
15
+ import type { PreviewConfig } from "./storyAdapter.js";
16
+
17
+ /**
18
+ * Possible file extensions and names for preview config
19
+ */
20
+ const PREVIEW_FILES = [
21
+ "preview.tsx",
22
+ "preview.ts",
23
+ "preview.jsx",
24
+ "preview.js",
25
+ ];
26
+
27
+ /**
28
+ * Find the preview config file in a .storybook directory
29
+ *
30
+ * @param storybookDir - Path to the .storybook directory
31
+ * @returns The full path to the preview file, or null if not found
32
+ */
33
+ export function findPreviewConfigPath(storybookDir: string): string | null {
34
+ for (const fileName of PREVIEW_FILES) {
35
+ const filePath = join(storybookDir, fileName);
36
+ if (existsSync(filePath)) {
37
+ return filePath;
38
+ }
39
+ }
40
+ return null;
41
+ }
42
+
43
+ /**
44
+ * Find the .storybook directory starting from a project root.
45
+ * Checks common locations.
46
+ *
47
+ * @param projectRoot - The project root directory
48
+ * @returns The path to the .storybook directory, or null if not found
49
+ */
50
+ export function findStorybookDir(projectRoot: string): string | null {
51
+ const possiblePaths = [
52
+ join(projectRoot, ".storybook"),
53
+ join(projectRoot, "storybook"),
54
+ ];
55
+
56
+ for (const dir of possiblePaths) {
57
+ if (existsSync(dir)) {
58
+ return dir;
59
+ }
60
+ }
61
+ return null;
62
+ }
63
+
64
+ /**
65
+ * Load and parse the Storybook preview configuration.
66
+ *
67
+ * This function dynamically imports the preview file and extracts
68
+ * the relevant configuration exports.
69
+ *
70
+ * @param previewPath - Full path to the preview config file
71
+ * @returns The parsed preview configuration
72
+ */
73
+ export async function loadPreviewConfig(
74
+ previewPath: string
75
+ ): Promise<PreviewConfig> {
76
+ try {
77
+ // Dynamic import with file:// URL for ESM compatibility
78
+ const fileUrl = new URL(`file://${resolve(previewPath)}`);
79
+ const module = await import(fileUrl.href);
80
+
81
+ // Extract configuration from the module
82
+ const config: PreviewConfig = {
83
+ decorators: module.decorators ?? module.default?.decorators ?? [],
84
+ parameters: module.parameters ?? module.default?.parameters ?? {},
85
+ globalTypes: module.globalTypes ?? module.default?.globalTypes ?? {},
86
+ args: module.args ?? module.default?.args ?? {},
87
+ argTypes: module.argTypes ?? module.default?.argTypes ?? {},
88
+ loaders: module.loaders ?? module.default?.loaders ?? [],
89
+ };
90
+
91
+ return config;
92
+ } catch (error) {
93
+ console.warn(
94
+ `[Segments] Failed to load preview config from ${previewPath}:`,
95
+ error instanceof Error ? error.message : error
96
+ );
97
+ return {};
98
+ }
99
+ }
100
+
101
+ /**
102
+ * Auto-detect and load preview configuration from a project.
103
+ *
104
+ * @param projectRoot - The project root directory
105
+ * @returns The preview configuration, or empty config if not found
106
+ */
107
+ export async function autoLoadPreviewConfig(
108
+ projectRoot: string
109
+ ): Promise<PreviewConfig> {
110
+ const storybookDir = findStorybookDir(projectRoot);
111
+ if (!storybookDir) {
112
+ return {};
113
+ }
114
+
115
+ const previewPath = findPreviewConfigPath(storybookDir);
116
+ if (!previewPath) {
117
+ return {};
118
+ }
119
+
120
+ return loadPreviewConfig(previewPath);
121
+ }
122
+
123
+ /**
124
+ * Generate a virtual module that imports and re-exports preview config.
125
+ * This is used in the Vite plugin to make preview config available at runtime.
126
+ *
127
+ * @param previewPath - Full path to the preview config file, or null
128
+ * @returns JavaScript code for the virtual module
129
+ */
130
+ export function generatePreviewModule(previewPath: string | null): string {
131
+ if (!previewPath) {
132
+ // No preview config found - export empty defaults
133
+ return `
134
+ export const decorators = [];
135
+ export const parameters = {};
136
+ export const globalTypes = {};
137
+ export const args = {};
138
+ export const argTypes = {};
139
+ export const loaders = [];
140
+
141
+ export default {
142
+ decorators,
143
+ parameters,
144
+ globalTypes,
145
+ args,
146
+ argTypes,
147
+ loaders,
148
+ };
149
+ `;
150
+ }
151
+
152
+ // Import from the actual preview file and re-export
153
+ return `
154
+ import * as preview from "${previewPath}";
155
+
156
+ export const decorators = preview.decorators ?? preview.default?.decorators ?? [];
157
+ export const parameters = preview.parameters ?? preview.default?.parameters ?? {};
158
+ export const globalTypes = preview.globalTypes ?? preview.default?.globalTypes ?? {};
159
+ export const args = preview.args ?? preview.default?.args ?? {};
160
+ export const argTypes = preview.argTypes ?? preview.default?.argTypes ?? {};
161
+ export const loaders = preview.loaders ?? preview.default?.loaders ?? [];
162
+
163
+ export default {
164
+ decorators,
165
+ parameters,
166
+ globalTypes,
167
+ args,
168
+ argTypes,
169
+ loaders,
170
+ };
171
+ `;
172
+ }