@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,444 @@
1
+ /**
2
+ * AST Utilities
3
+ *
4
+ * Provides AST-based style extraction and patching capabilities using Babel.
5
+ * Used for precisely locating style definitions in source files and generating
6
+ * accurate patches for token replacement.
7
+ */
8
+
9
+ import { parse, type ParserOptions } from "@babel/parser";
10
+ import traverse, { type NodePath } from "@babel/traverse";
11
+ import generate from "@babel/generator";
12
+ import * as t from "@babel/types";
13
+
14
+ /**
15
+ * Style location information
16
+ */
17
+ export interface StyleLocation {
18
+ /** Source file path */
19
+ file: string;
20
+
21
+ /** Line number (1-indexed) */
22
+ line: number;
23
+
24
+ /** Column number (0-indexed) */
25
+ column: number;
26
+
27
+ /** End line number */
28
+ endLine?: number;
29
+
30
+ /** End column number */
31
+ endColumn?: number;
32
+
33
+ /** Type of style definition */
34
+ type: "inline" | "styled-components" | "emotion" | "css-module" | "css-prop";
35
+
36
+ /** Property name (camelCase for inline, kebab-case for CSS) */
37
+ property: string;
38
+
39
+ /** Current value at this location */
40
+ value: string;
41
+
42
+ /** Full text of the style definition for context */
43
+ context?: string;
44
+ }
45
+
46
+ /**
47
+ * Babel parser options for React/TypeScript files
48
+ */
49
+ const PARSER_OPTIONS: ParserOptions = {
50
+ sourceType: "module",
51
+ plugins: [
52
+ "jsx",
53
+ "typescript",
54
+ ["decorators", { decoratorsBeforeExport: true }],
55
+ "classProperties",
56
+ "classPrivateProperties",
57
+ "classPrivateMethods",
58
+ "exportDefaultFrom",
59
+ "exportNamespaceFrom",
60
+ "dynamicImport",
61
+ "nullishCoalescingOperator",
62
+ "optionalChaining",
63
+ "objectRestSpread",
64
+ ],
65
+ };
66
+
67
+ /**
68
+ * Extract style locations from a React/TypeScript source file
69
+ *
70
+ * Finds style definitions in:
71
+ * - Inline style={{...}} attributes
72
+ * - styled-components template literals
73
+ * - Emotion css={{...}} or css`...`
74
+ * - CSS module className references (returns the class reference, not styles)
75
+ */
76
+ export function extractStyleLocations(
77
+ sourceCode: string,
78
+ filePath: string
79
+ ): StyleLocation[] {
80
+ const locations: StyleLocation[] = [];
81
+
82
+ let ast: t.File;
83
+ try {
84
+ ast = parse(sourceCode, PARSER_OPTIONS);
85
+ } catch (error) {
86
+ console.error(`Failed to parse ${filePath}:`, error);
87
+ return locations;
88
+ }
89
+
90
+ traverse(ast, {
91
+ // Handle inline style={{...}} and Emotion css={{...}}
92
+ JSXAttribute(path) {
93
+ const attrName = t.isJSXIdentifier(path.node.name) ? path.node.name.name : null;
94
+
95
+ // Handle style={{...}}
96
+ if (attrName === "style") {
97
+ const value = path.node.value;
98
+ if (t.isJSXExpressionContainer(value)) {
99
+ const expr = value.expression;
100
+ if (t.isObjectExpression(expr)) {
101
+ extractFromObjectExpression(expr, path, filePath, "inline", locations);
102
+ }
103
+ }
104
+ return;
105
+ }
106
+
107
+ // Handle Emotion css={{...}} prop
108
+ if (attrName === "css") {
109
+ const value = path.node.value;
110
+ if (t.isJSXExpressionContainer(value)) {
111
+ const expr = value.expression;
112
+ if (t.isObjectExpression(expr)) {
113
+ extractFromObjectExpression(expr, path, filePath, "emotion", locations);
114
+ }
115
+ }
116
+ return;
117
+ }
118
+ },
119
+
120
+ // Handle styled-components: styled.div`...`
121
+ TaggedTemplateExpression(path) {
122
+ const tag = path.node.tag;
123
+
124
+ // Check for styled.element or styled(Component)
125
+ const isStyledCall =
126
+ (t.isMemberExpression(tag) &&
127
+ t.isIdentifier(tag.object, { name: "styled" })) ||
128
+ (t.isCallExpression(tag) &&
129
+ t.isIdentifier(tag.callee, { name: "styled" }));
130
+
131
+ // Check for css`` from emotion or styled-components
132
+ const isCssCall = t.isIdentifier(tag, { name: "css" });
133
+
134
+ if (isStyledCall || isCssCall) {
135
+ extractFromTemplateLiteral(
136
+ path.node.quasi,
137
+ path,
138
+ filePath,
139
+ "styled-components",
140
+ locations
141
+ );
142
+ }
143
+ },
144
+ });
145
+
146
+ return locations;
147
+ }
148
+
149
+ /**
150
+ * Extract style properties from an object expression
151
+ */
152
+ function extractFromObjectExpression(
153
+ obj: t.ObjectExpression,
154
+ path: NodePath,
155
+ filePath: string,
156
+ type: StyleLocation["type"],
157
+ locations: StyleLocation[]
158
+ ): void {
159
+ for (const prop of obj.properties) {
160
+ if (!t.isObjectProperty(prop)) continue;
161
+
162
+ // Get property name
163
+ let propertyName: string | null = null;
164
+ if (t.isIdentifier(prop.key)) {
165
+ propertyName = prop.key.name;
166
+ } else if (t.isStringLiteral(prop.key)) {
167
+ propertyName = prop.key.value;
168
+ }
169
+
170
+ if (!propertyName) continue;
171
+
172
+ // Get property value
173
+ let value: string | null = null;
174
+ if (t.isStringLiteral(prop.value)) {
175
+ value = prop.value.value;
176
+ } else if (t.isNumericLiteral(prop.value)) {
177
+ value = String(prop.value.value);
178
+ } else if (t.isTemplateLiteral(prop.value) && prop.value.quasis.length === 1) {
179
+ value = prop.value.quasis[0].value.raw;
180
+ }
181
+
182
+ if (!value) continue;
183
+
184
+ const loc = prop.loc;
185
+ if (!loc) continue;
186
+
187
+ locations.push({
188
+ file: filePath,
189
+ line: loc.start.line,
190
+ column: loc.start.column,
191
+ endLine: loc.end.line,
192
+ endColumn: loc.end.column,
193
+ type,
194
+ property: propertyName,
195
+ value,
196
+ });
197
+ }
198
+ }
199
+
200
+ /**
201
+ * Extract style properties from a template literal (styled-components, emotion css``)
202
+ */
203
+ function extractFromTemplateLiteral(
204
+ template: t.TemplateLiteral,
205
+ path: NodePath,
206
+ filePath: string,
207
+ type: StyleLocation["type"],
208
+ locations: StyleLocation[]
209
+ ): void {
210
+ // Combine all quasis to get the full CSS text
211
+ const cssText = template.quasis.map((q) => q.value.raw).join("${...}");
212
+
213
+ // Parse CSS properties using regex (simplified parser)
214
+ const propertyPattern = /([a-z-]+)\s*:\s*([^;{}]+);/gi;
215
+ let match;
216
+
217
+ while ((match = propertyPattern.exec(cssText)) !== null) {
218
+ const [, property, value] = match;
219
+
220
+ // Calculate approximate line number from the template start
221
+ const loc = template.loc;
222
+ if (!loc) continue;
223
+
224
+ // Count newlines before this match to estimate line
225
+ const beforeMatch = cssText.slice(0, match.index);
226
+ const lineOffset = (beforeMatch.match(/\n/g) || []).length;
227
+
228
+ locations.push({
229
+ file: filePath,
230
+ line: loc.start.line + lineOffset,
231
+ column: 0, // Approximate
232
+ type,
233
+ property: property.trim(),
234
+ value: value.trim(),
235
+ context: match[0],
236
+ });
237
+ }
238
+ }
239
+
240
+ /**
241
+ * Result of applying a patch
242
+ */
243
+ export interface PatchResult {
244
+ /** Whether the patch was successfully applied */
245
+ success: boolean;
246
+
247
+ /** Modified source code */
248
+ code?: string;
249
+
250
+ /** Unified diff of the change */
251
+ diff?: string;
252
+
253
+ /** Error message if failed */
254
+ error?: string;
255
+ }
256
+
257
+ /**
258
+ * Apply a patch to replace a style value
259
+ *
260
+ * Uses Babel to parse, modify, and regenerate the code with preserved formatting.
261
+ */
262
+ export function applyPatch(
263
+ sourceCode: string,
264
+ styleLocation: StyleLocation,
265
+ oldValue: string,
266
+ newValue: string
267
+ ): PatchResult {
268
+ let ast: t.File;
269
+ try {
270
+ ast = parse(sourceCode, PARSER_OPTIONS);
271
+ } catch (error) {
272
+ return {
273
+ success: false,
274
+ error: `Failed to parse source: ${error instanceof Error ? error.message : String(error)}`,
275
+ };
276
+ }
277
+
278
+ let modified = false;
279
+ const originalLines = sourceCode.split("\n");
280
+
281
+ traverse(ast, {
282
+ // Handle inline style={{...}}
283
+ ObjectProperty(path) {
284
+ if (modified) return;
285
+
286
+ const loc = path.node.loc;
287
+ if (!loc) return;
288
+
289
+ // Check if this is at the right location
290
+ if (loc.start.line !== styleLocation.line) return;
291
+
292
+ // Get property name
293
+ let propertyName: string | null = null;
294
+ if (t.isIdentifier(path.node.key)) {
295
+ propertyName = path.node.key.name;
296
+ } else if (t.isStringLiteral(path.node.key)) {
297
+ propertyName = path.node.key.value;
298
+ }
299
+
300
+ if (propertyName !== styleLocation.property) return;
301
+
302
+ // Check and update value
303
+ if (t.isStringLiteral(path.node.value) && path.node.value.value === oldValue) {
304
+ path.node.value = t.stringLiteral(newValue);
305
+ modified = true;
306
+ }
307
+ },
308
+ });
309
+
310
+ if (!modified) {
311
+ return {
312
+ success: false,
313
+ error: `Could not find style property '${styleLocation.property}' with value '${oldValue}' at line ${styleLocation.line}`,
314
+ };
315
+ }
316
+
317
+ // Generate modified code
318
+ const result = generate(ast, {
319
+ retainLines: true,
320
+ compact: false,
321
+ });
322
+
323
+ // Generate unified diff
324
+ const newLines = result.code.split("\n");
325
+ const diff = generateUnifiedDiff(
326
+ styleLocation.file,
327
+ originalLines,
328
+ newLines,
329
+ styleLocation.line
330
+ );
331
+
332
+ return {
333
+ success: true,
334
+ code: result.code,
335
+ diff,
336
+ };
337
+ }
338
+
339
+ /**
340
+ * Generate a unified diff for a file change
341
+ */
342
+ function generateUnifiedDiff(
343
+ file: string,
344
+ originalLines: string[],
345
+ newLines: string[],
346
+ changedLine: number
347
+ ): string {
348
+ const diffLines: string[] = [];
349
+
350
+ diffLines.push(`--- a/${file}`);
351
+ diffLines.push(`+++ b/${file}`);
352
+
353
+ // Find the changed region with context
354
+ const contextSize = 3;
355
+ const start = Math.max(0, changedLine - contextSize - 1);
356
+ const end = Math.min(originalLines.length, changedLine + contextSize);
357
+
358
+ diffLines.push(`@@ -${start + 1},${end - start} +${start + 1},${end - start} @@`);
359
+
360
+ for (let i = start; i < end; i++) {
361
+ const origLine = originalLines[i] || "";
362
+ const newLine = newLines[i] || "";
363
+
364
+ if (origLine !== newLine) {
365
+ diffLines.push(`-${origLine}`);
366
+ diffLines.push(`+${newLine}`);
367
+ } else {
368
+ diffLines.push(` ${origLine}`);
369
+ }
370
+ }
371
+
372
+ return diffLines.join("\n");
373
+ }
374
+
375
+ /**
376
+ * Find all style property occurrences matching a value
377
+ */
378
+ export function findStyleOccurrences(
379
+ sourceCode: string,
380
+ filePath: string,
381
+ propertyPattern: string | RegExp,
382
+ valuePattern: string | RegExp
383
+ ): StyleLocation[] {
384
+ const locations = extractStyleLocations(sourceCode, filePath);
385
+
386
+ const propRegex = typeof propertyPattern === "string"
387
+ ? new RegExp(`^${propertyPattern}$`, "i")
388
+ : propertyPattern;
389
+
390
+ const valueRegex = typeof valuePattern === "string"
391
+ ? new RegExp(`^${escapeRegExp(valuePattern)}$`, "i")
392
+ : valuePattern;
393
+
394
+ return locations.filter((loc) => {
395
+ return propRegex.test(loc.property) && valueRegex.test(loc.value);
396
+ });
397
+ }
398
+
399
+ /**
400
+ * Escape special regex characters
401
+ */
402
+ function escapeRegExp(string: string): string {
403
+ return string.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
404
+ }
405
+
406
+ /**
407
+ * Batch apply multiple patches to a source file
408
+ */
409
+ export function applyPatches(
410
+ sourceCode: string,
411
+ patches: Array<{
412
+ location: StyleLocation;
413
+ oldValue: string;
414
+ newValue: string;
415
+ }>
416
+ ): PatchResult {
417
+ // Sort patches by line number descending to avoid offset issues
418
+ const sortedPatches = [...patches].sort((a, b) => b.location.line - a.location.line);
419
+
420
+ let currentCode = sourceCode;
421
+ const allDiffs: string[] = [];
422
+
423
+ for (const patch of sortedPatches) {
424
+ const result = applyPatch(currentCode, patch.location, patch.oldValue, patch.newValue);
425
+
426
+ if (!result.success) {
427
+ return {
428
+ success: false,
429
+ error: `Failed to apply patch at line ${patch.location.line}: ${result.error}`,
430
+ };
431
+ }
432
+
433
+ currentCode = result.code!;
434
+ if (result.diff) {
435
+ allDiffs.push(result.diff);
436
+ }
437
+ }
438
+
439
+ return {
440
+ success: true,
441
+ code: currentCode,
442
+ diff: allDiffs.join("\n\n"),
443
+ };
444
+ }