@fragments-sdk/cli 0.2.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +106 -0
- package/dist/bin.d.ts +1 -0
- package/dist/bin.js +4783 -0
- package/dist/bin.js.map +1 -0
- package/dist/chunk-4FDQSGKX.js +786 -0
- package/dist/chunk-4FDQSGKX.js.map +1 -0
- package/dist/chunk-7H2MMGYG.js +369 -0
- package/dist/chunk-7H2MMGYG.js.map +1 -0
- package/dist/chunk-BSCG3IP7.js +619 -0
- package/dist/chunk-BSCG3IP7.js.map +1 -0
- package/dist/chunk-LY2CFFPY.js +898 -0
- package/dist/chunk-LY2CFFPY.js.map +1 -0
- package/dist/chunk-MUZ6CM66.js +6636 -0
- package/dist/chunk-MUZ6CM66.js.map +1 -0
- package/dist/chunk-OAENNG3G.js +1489 -0
- package/dist/chunk-OAENNG3G.js.map +1 -0
- package/dist/chunk-XHNKNI6J.js +235 -0
- package/dist/chunk-XHNKNI6J.js.map +1 -0
- package/dist/core-DWKLGY4N.js +68 -0
- package/dist/core-DWKLGY4N.js.map +1 -0
- package/dist/generate-4LQNJ7SX.js +249 -0
- package/dist/generate-4LQNJ7SX.js.map +1 -0
- package/dist/index.d.ts +775 -0
- package/dist/index.js +41 -0
- package/dist/index.js.map +1 -0
- package/dist/init-EMVI47QG.js +416 -0
- package/dist/init-EMVI47QG.js.map +1 -0
- package/dist/mcp-bin.d.ts +1 -0
- package/dist/mcp-bin.js +1117 -0
- package/dist/mcp-bin.js.map +1 -0
- package/dist/scan-4YPRF7FV.js +12 -0
- package/dist/scan-4YPRF7FV.js.map +1 -0
- package/dist/service-QSZMZJBJ.js +208 -0
- package/dist/service-QSZMZJBJ.js.map +1 -0
- package/dist/static-viewer-MIPGZ4Z7.js +12 -0
- package/dist/static-viewer-MIPGZ4Z7.js.map +1 -0
- package/dist/test-SQ5ZHXWU.js +1067 -0
- package/dist/test-SQ5ZHXWU.js.map +1 -0
- package/dist/tokens-HSGMYK64.js +173 -0
- package/dist/tokens-HSGMYK64.js.map +1 -0
- package/dist/viewer-YRF4SQE4.js +11101 -0
- package/dist/viewer-YRF4SQE4.js.map +1 -0
- package/package.json +107 -0
- package/src/ai.ts +266 -0
- package/src/analyze.ts +265 -0
- package/src/bin.ts +916 -0
- package/src/build.ts +248 -0
- package/src/commands/a11y.ts +302 -0
- package/src/commands/add.ts +313 -0
- package/src/commands/audit.ts +195 -0
- package/src/commands/baseline.ts +221 -0
- package/src/commands/build.ts +144 -0
- package/src/commands/compare.ts +337 -0
- package/src/commands/context.ts +107 -0
- package/src/commands/dev.ts +107 -0
- package/src/commands/enhance.ts +858 -0
- package/src/commands/generate.ts +391 -0
- package/src/commands/init.ts +531 -0
- package/src/commands/link/figma.ts +645 -0
- package/src/commands/link/index.ts +10 -0
- package/src/commands/link/storybook.ts +267 -0
- package/src/commands/list.ts +49 -0
- package/src/commands/metrics.ts +114 -0
- package/src/commands/reset.ts +242 -0
- package/src/commands/scan.ts +537 -0
- package/src/commands/storygen.ts +207 -0
- package/src/commands/tokens.ts +251 -0
- package/src/commands/validate.ts +93 -0
- package/src/commands/verify.ts +215 -0
- package/src/core/composition.test.ts +262 -0
- package/src/core/composition.ts +255 -0
- package/src/core/config.ts +84 -0
- package/src/core/constants.ts +111 -0
- package/src/core/context.ts +380 -0
- package/src/core/defineSegment.ts +137 -0
- package/src/core/discovery.ts +337 -0
- package/src/core/figma.ts +263 -0
- package/src/core/fragment-types.ts +214 -0
- package/src/core/generators/context.ts +389 -0
- package/src/core/generators/index.ts +23 -0
- package/src/core/generators/registry.ts +364 -0
- package/src/core/generators/typescript-extractor.ts +374 -0
- package/src/core/importAnalyzer.ts +217 -0
- package/src/core/index.ts +149 -0
- package/src/core/loader.ts +155 -0
- package/src/core/node.ts +63 -0
- package/src/core/parser.ts +551 -0
- package/src/core/previewLoader.ts +172 -0
- package/src/core/schema/fragment.schema.json +189 -0
- package/src/core/schema/registry.schema.json +137 -0
- package/src/core/schema.ts +182 -0
- package/src/core/storyAdapter.test.ts +571 -0
- package/src/core/storyAdapter.ts +761 -0
- package/src/core/token-types.ts +287 -0
- package/src/core/types.ts +754 -0
- package/src/diff.ts +323 -0
- package/src/index.ts +43 -0
- package/src/mcp/__tests__/projectFields.test.ts +130 -0
- package/src/mcp/bin.ts +36 -0
- package/src/mcp/index.ts +8 -0
- package/src/mcp/server.ts +1310 -0
- package/src/mcp/utils.ts +54 -0
- package/src/mcp-bin.ts +36 -0
- package/src/migrate/__tests__/argTypes/argTypes.test.ts +189 -0
- package/src/migrate/__tests__/args/args.test.ts +452 -0
- package/src/migrate/__tests__/meta/meta.test.ts +198 -0
- package/src/migrate/__tests__/stories/stories.test.ts +278 -0
- package/src/migrate/__tests__/utils/utils.test.ts +371 -0
- package/src/migrate/__tests__/values/values.test.ts +303 -0
- package/src/migrate/bin.ts +108 -0
- package/src/migrate/converter.ts +658 -0
- package/src/migrate/detect.ts +196 -0
- package/src/migrate/index.ts +45 -0
- package/src/migrate/migrate.ts +163 -0
- package/src/migrate/parser.ts +1136 -0
- package/src/migrate/report.ts +624 -0
- package/src/migrate/types.ts +169 -0
- package/src/screenshot.ts +249 -0
- package/src/service/__tests__/ast-utils.test.ts +426 -0
- package/src/service/__tests__/enhance-scanner.test.ts +200 -0
- package/src/service/__tests__/figma/figma.test.ts +652 -0
- package/src/service/__tests__/metrics-store.test.ts +409 -0
- package/src/service/__tests__/patch-generator.test.ts +186 -0
- package/src/service/__tests__/props-extractor.test.ts +365 -0
- package/src/service/__tests__/token-registry.test.ts +267 -0
- package/src/service/analytics.ts +659 -0
- package/src/service/ast-utils.ts +444 -0
- package/src/service/browser-pool.ts +339 -0
- package/src/service/capture.ts +267 -0
- package/src/service/diff.ts +279 -0
- package/src/service/enhance/aggregator.ts +489 -0
- package/src/service/enhance/cache.ts +275 -0
- package/src/service/enhance/codebase-scanner.ts +357 -0
- package/src/service/enhance/context-generator.ts +529 -0
- package/src/service/enhance/doc-extractor.ts +523 -0
- package/src/service/enhance/index.ts +131 -0
- package/src/service/enhance/props-extractor.ts +665 -0
- package/src/service/enhance/scanner.ts +445 -0
- package/src/service/enhance/storybook-parser.ts +552 -0
- package/src/service/enhance/types.ts +346 -0
- package/src/service/enhance/variant-renderer.ts +479 -0
- package/src/service/figma.ts +1008 -0
- package/src/service/index.ts +249 -0
- package/src/service/metrics-store.ts +333 -0
- package/src/service/patch-generator.ts +349 -0
- package/src/service/report.ts +854 -0
- package/src/service/storage.ts +401 -0
- package/src/service/token-fixes.ts +281 -0
- package/src/service/token-parser.ts +504 -0
- package/src/service/token-registry.ts +721 -0
- package/src/service/utils.ts +172 -0
- package/src/setup.ts +241 -0
- package/src/shared/command-wrapper.ts +81 -0
- package/src/shared/dev-server-client.ts +199 -0
- package/src/shared/index.ts +8 -0
- package/src/shared/segment-loader.ts +59 -0
- package/src/shared/types.ts +147 -0
- package/src/static-viewer.ts +715 -0
- package/src/test/discovery.ts +172 -0
- package/src/test/index.ts +281 -0
- package/src/test/reporters/console.ts +194 -0
- package/src/test/reporters/json.ts +190 -0
- package/src/test/reporters/junit.ts +186 -0
- package/src/test/runner.ts +598 -0
- package/src/test/types.ts +245 -0
- package/src/test/watch.ts +200 -0
- package/src/validators.ts +152 -0
- package/src/viewer/__tests__/jsx-parser.test.ts +502 -0
- package/src/viewer/__tests__/render-utils.test.ts +232 -0
- package/src/viewer/__tests__/style-utils.test.ts +404 -0
- package/src/viewer/bin.ts +86 -0
- package/src/viewer/cli/health.ts +256 -0
- package/src/viewer/cli/index.ts +33 -0
- package/src/viewer/cli/scan.ts +124 -0
- package/src/viewer/cli/utils.ts +174 -0
- package/src/viewer/components/AccessibilityPanel.tsx +1404 -0
- package/src/viewer/components/ActionCapture.tsx +172 -0
- package/src/viewer/components/ActionsPanel.tsx +371 -0
- package/src/viewer/components/App.tsx +638 -0
- package/src/viewer/components/BottomPanel.tsx +224 -0
- package/src/viewer/components/CodePanel.tsx +589 -0
- package/src/viewer/components/CommandPalette.tsx +336 -0
- package/src/viewer/components/ComponentGraph.tsx +394 -0
- package/src/viewer/components/ComponentHeader.tsx +85 -0
- package/src/viewer/components/ContractPanel.tsx +234 -0
- package/src/viewer/components/ErrorBoundary.tsx +85 -0
- package/src/viewer/components/FigmaEmbed.tsx +231 -0
- package/src/viewer/components/FragmentEditor.tsx +485 -0
- package/src/viewer/components/HealthDashboard.tsx +452 -0
- package/src/viewer/components/HmrStatusIndicator.tsx +71 -0
- package/src/viewer/components/Icons.tsx +417 -0
- package/src/viewer/components/InteractionsPanel.tsx +720 -0
- package/src/viewer/components/IsolatedPreviewFrame.tsx +321 -0
- package/src/viewer/components/IsolatedRender.tsx +111 -0
- package/src/viewer/components/KeyboardShortcutsHelp.tsx +89 -0
- package/src/viewer/components/LandingPage.tsx +441 -0
- package/src/viewer/components/Layout.tsx +22 -0
- package/src/viewer/components/LeftSidebar.tsx +391 -0
- package/src/viewer/components/MultiViewportPreview.tsx +429 -0
- package/src/viewer/components/PreviewArea.tsx +404 -0
- package/src/viewer/components/PreviewFrameHost.tsx +310 -0
- package/src/viewer/components/PreviewPane.tsx +150 -0
- package/src/viewer/components/PreviewToolbar.tsx +176 -0
- package/src/viewer/components/PropsEditor.tsx +512 -0
- package/src/viewer/components/PropsTable.tsx +98 -0
- package/src/viewer/components/RelationsSection.tsx +57 -0
- package/src/viewer/components/ResizablePanel.tsx +328 -0
- package/src/viewer/components/RightSidebar.tsx +118 -0
- package/src/viewer/components/ScreenshotButton.tsx +90 -0
- package/src/viewer/components/Sidebar.tsx +169 -0
- package/src/viewer/components/SkeletonLoader.tsx +156 -0
- package/src/viewer/components/StoryRenderer.tsx +128 -0
- package/src/viewer/components/ThemeProvider.tsx +96 -0
- package/src/viewer/components/Toast.tsx +67 -0
- package/src/viewer/components/TokenStylePanel.tsx +708 -0
- package/src/viewer/components/UsageSection.tsx +95 -0
- package/src/viewer/components/VariantMatrix.tsx +350 -0
- package/src/viewer/components/VariantRenderer.tsx +131 -0
- package/src/viewer/components/VariantTabs.tsx +84 -0
- package/src/viewer/components/ViewportSelector.tsx +165 -0
- package/src/viewer/components/_future/CreatePage.tsx +836 -0
- package/src/viewer/composition-renderer.ts +381 -0
- package/src/viewer/constants/index.ts +1 -0
- package/src/viewer/constants/ui.ts +185 -0
- package/src/viewer/entry.tsx +299 -0
- package/src/viewer/hooks/index.ts +2 -0
- package/src/viewer/hooks/useA11yCache.ts +383 -0
- package/src/viewer/hooks/useA11yService.ts +498 -0
- package/src/viewer/hooks/useActions.ts +138 -0
- package/src/viewer/hooks/useAppState.ts +124 -0
- package/src/viewer/hooks/useFigmaIntegration.ts +132 -0
- package/src/viewer/hooks/useHmrStatus.ts +109 -0
- package/src/viewer/hooks/useKeyboardShortcuts.ts +222 -0
- package/src/viewer/hooks/usePreviewBridge.ts +347 -0
- package/src/viewer/hooks/useScrollSpy.ts +78 -0
- package/src/viewer/hooks/useUrlState.ts +330 -0
- package/src/viewer/hooks/useViewSettings.ts +125 -0
- package/src/viewer/index.html +28 -0
- package/src/viewer/index.ts +14 -0
- package/src/viewer/intelligence/healthReport.ts +505 -0
- package/src/viewer/intelligence/styleDrift.ts +340 -0
- package/src/viewer/intelligence/usageScanner.ts +309 -0
- package/src/viewer/jsx-parser.ts +485 -0
- package/src/viewer/postcss.config.js +6 -0
- package/src/viewer/preview-frame-entry.tsx +25 -0
- package/src/viewer/preview-frame.html +109 -0
- package/src/viewer/render-template.html +68 -0
- package/src/viewer/render-utils.ts +170 -0
- package/src/viewer/server.ts +276 -0
- package/src/viewer/style-utils.ts +414 -0
- package/src/viewer/styles/globals.css +355 -0
- package/src/viewer/tailwind.config.js +37 -0
- package/src/viewer/types/a11y.ts +197 -0
- package/src/viewer/utils/a11y-fixes.ts +471 -0
- package/src/viewer/utils/actionExport.ts +372 -0
- package/src/viewer/utils/colorSchemes.ts +201 -0
- package/src/viewer/utils/detectRelationships.ts +256 -0
- package/src/viewer/vite-plugin.ts +2143 -0
|
@@ -0,0 +1,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
|
+
}
|