scss-variable-extractor 1.6.4 → 1.6.6

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/src/refactorer.js CHANGED
@@ -1,322 +1,329 @@
1
- const fs = require('fs');
2
- const path = require('path');
3
-
4
- /**
5
- * Refactors SCSS files by replacing hardcoded values with variable references
6
- * @param {Array<string>} scssFiles - Array of SCSS file paths
7
- * @param {Object} analysis - Analysis results with variable mappings
8
- * @param {string} variablesFilePath - Path to the generated variables file
9
- * @param {Object} config - Configuration
10
- */
11
- function refactorScssFiles(scssFiles, analysis, variablesFilePath, config) {
12
- const refactoredFiles = [];
13
-
14
- scssFiles.forEach(filePath => {
15
- const content = fs.readFileSync(filePath, 'utf8');
16
- const refactored = refactorFile(content, analysis, variablesFilePath, filePath, config);
17
-
18
- if (refactored.modified) {
19
- fs.writeFileSync(filePath, refactored.content, 'utf8');
20
- refactoredFiles.push({
21
- path: filePath,
22
- changes: refactored.changes
23
- });
24
- }
25
- });
26
-
27
- return refactoredFiles;
28
- }
29
-
30
- /**
31
- * Refactors a single SCSS file
32
- */
33
- function refactorFile(content, analysis, variablesFilePath, currentFilePath, config) {
34
- let newContent = content;
35
- let modified = false;
36
- const changes = [];
37
-
38
- // Build value-to-variable mapping
39
- const valueMap = buildValueMap(analysis);
40
-
41
- // Check if file already has @use import for variables
42
- const hasVariablesImport = content.includes('@use') && content.includes('variables');
43
-
44
- // Replace values with variables
45
- // Sort by value length (longest first) to avoid partial replacements
46
- // E.g., replace "0.5" before "0" to prevent "0.5" becoming "$var.5"
47
- const sortedEntries = Object.entries(valueMap).sort((a, b) => b[0].length - a[0].length);
48
-
49
- sortedEntries.forEach(([value, variableName]) => {
50
- const regex = createReplacementRegex(value);
51
- const matches = [];
52
-
53
- let match;
54
- // Find matches in the current state of newContent (after previous replacements)
55
- while ((match = regex.exec(newContent)) !== null) {
56
- matches.push({
57
- index: match.index,
58
- matched: match[0],
59
- fullMatch: match
60
- });
61
- }
62
-
63
- // Check if replacements are in safe zones
64
- // Process matches in reverse order (highest index first) to avoid position shifts
65
- matches.reverse().forEach(m => {
66
- const inSafeZone = isInSafeZone(newContent, m.index, m.matched);
67
-
68
- if (!inSafeZone) {
69
- // Replace the value
70
- const before = newContent;
71
- newContent = replaceAt(newContent, m.matched, variableName, m.index);
72
-
73
- if (before !== newContent) {
74
- modified = true;
75
- changes.push({
76
- from: value,
77
- to: variableName
78
- });
79
- }
80
- }
81
- });
82
- });
83
-
84
- // Add @use import if modified and not already present
85
- if (modified && !hasVariablesImport) {
86
- const relativePath = getRelativeImportPath(currentFilePath, variablesFilePath);
87
- const useStatement = `@use '${relativePath}' as *;\n\n`;
88
- newContent = useStatement + newContent;
89
- }
90
-
91
- return {
92
- content: newContent,
93
- modified,
94
- changes
95
- };
96
- }
97
-
98
- /**
99
- * Builds a map of values to variable names
100
- */
101
- function buildValueMap(analysis) {
102
- const valueMap = {};
103
-
104
- Object.keys(analysis).forEach(category => {
105
- analysis[category].forEach(item => {
106
- // Use normalized value as key for consistent matching
107
- valueMap[item.value] = item.suggestedName;
108
- });
109
- });
110
-
111
- return valueMap;
112
- }
113
-
114
- /**
115
- * Creates a regex for replacing a specific value
116
- */
117
- function createReplacementRegex(value) {
118
- // Escape special regex characters
119
- const escaped = value.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
120
-
121
- // Add appropriate boundaries based on value type to prevent partial matches
122
-
123
- if (/^#[0-9a-fA-F]+$/.test(value)) {
124
- // Hex colors - no word boundary before #, but check after
125
- return new RegExp(`${escaped}\\b`, 'g');
126
- } else if (/\d+(?:px|%|em|rem|vh|vw|pt|ch|ex)$/.test(value)) {
127
- // Values with CSS units - ensure not part of larger number
128
- return new RegExp(`(?<!\\d)${escaped}\\b`, 'g');
129
- } else if (/^-?\d+(?:\.\d+)?$/.test(value)) {
130
- // Pure numbers/decimals (unitless) - ensure not part of larger number or before units
131
- // Negative lookbehind: not preceded by digit
132
- // Negative lookahead: not followed by digit, unit letters, or % (for keyframe selectors)
133
- return new RegExp(`(?<!\\d)${escaped}(?!\\d|[a-z%])`, 'g');
134
- } else if (/^rgba?\(/.test(value)) {
135
- // RGB/RGBA colors - match complete function
136
- return new RegExp(escaped, 'g');
137
- } else {
138
- // Other values (keywords, etc.) - use word boundaries
139
- return new RegExp(`\\b${escaped}\\b`, 'g');
140
- }
141
- }
142
-
143
- /**
144
- * Checks if a position in content is in a safe zone (should not be replaced)
145
- */
146
- function isInSafeZone(content, position, matchedValue) {
147
- const DEBUG = false; // Enable for debugging
148
-
149
- // Check for variable declarations
150
- const lineStart = content.lastIndexOf('\n', position);
151
- const lineEnd = content.indexOf('\n', position);
152
- const line = content.substring(lineStart, lineEnd);
153
-
154
- // Check if this line is a variable declaration: $variable-name: value;
155
- // Match pattern: optional whitespace, $, identifier, optional whitespace, colon
156
- const varDeclMatch = line.match(/^\s*\$([\w-]+)\s*:/);
157
- if (varDeclMatch) {
158
- // This line IS a variable declaration
159
- if (DEBUG) console.log(` → SAFE: Variable declaration line`);
160
- return true;
161
- }
162
-
163
- // Check if we're in the middle of a larger number or value
164
- const charBefore = content.charAt(position - 1);
165
- const charAfter = content.charAt(position + matchedValue.length);
166
-
167
- // Prevent replacement if surrounded by digits (partial number match)
168
- if (/\d/.test(charBefore) || (/^\d+$/.test(matchedValue) && /\d/.test(charAfter))) {
169
- if (DEBUG) console.log(` → SAFE: Surrounded by digits`);
170
- return true;
171
- }
172
-
173
- // Prevent replacement within hex colors
174
- if (/[0-9a-fA-F]/.test(charBefore) || /[0-9a-fA-F]/.test(charAfter)) {
175
- // Look back further to check if we're in a hex color
176
- const lookBehind = content.substring(Math.max(0, position - 10), position);
177
- if (/#[0-9a-fA-F]*$/.test(lookBehind)) {
178
- if (DEBUG) console.log(` → SAFE: Inside hex color`);
179
- return true;
180
- }
181
- }
182
-
183
- // Prevent replacement if it would create invalid variable concatenation
184
- if (charBefore === '$' || charAfter === '$') {
185
- if (DEBUG) console.log(` → SAFE: Near $ symbol`);
186
- return true;
187
- }
188
-
189
- // Prevent replacement for negative values (e.g., -16px should not become -$spacing-md)
190
- if (charBefore === '-' && /^\d/.test(matchedValue)) {
191
- return true;
192
- }
193
-
194
- // Check for @use, @forward, @import
195
- if (/@(?:use|forward|import)/.test(line)) {
196
- return true;
197
- }
198
-
199
- // Check for url(), calc(), transform functions, and other contexts
200
- const beforeContext = content.substring(Math.max(0, position - 100), position);
201
-
202
- // Prevent replacement inside calc() expressions
203
- if (/calc\s*\([^)]*$/.test(beforeContext)) {
204
- return true;
205
- }
206
-
207
- // Prevent replacement inside transform functions
208
- if (/(?:translate|rotate|scale|skew|matrix|perspective)\s*\([^)]*$/.test(beforeContext)) {
209
- return true;
210
- }
211
-
212
- if (/url\s*\(\s*[^)]*$/.test(beforeContext)) {
213
- return true;
214
- }
215
-
216
- // Check for content property
217
- if (/content\s*:\s*[^;]*$/.test(beforeContext)) {
218
- return true;
219
- }
220
-
221
- // Check for interpolation #{}
222
- if (/#\{[^}]*$/.test(beforeContext)) {
223
- return true;
224
- }
225
-
226
- // Check for Material functions
227
- if (/mat\.\w+\([^)]*$/.test(beforeContext)) {
228
- return true;
229
- }
230
-
231
- // Prevent replacement inside attribute selectors
232
- if (/\[[^\]]*$/.test(beforeContext) &&
233
- /^[^\[]*\]/.test(content.substring(position, position + 50))) {
234
- if (DEBUG) console.log(` → SAFE: Inside attribute selector`);
235
- return true;
236
- }
237
-
238
- // Prevent replacement in keyframe percentages (e.g., 0%, 50%, 100% in @keyframes)
239
- // Check if we're in a @keyframes block and this position is before the opening brace
240
- if (/@keyframes/.test(beforeContext)) {
241
- // Check if we're in the selector part (before {) of a keyframe rule
242
- // Get text from line start to current position and after
243
- const lineBeforePos = content.substring(lineStart, position + matchedValue.length);
244
- const lineAfterPos = content.substring(position + matchedValue.length, lineEnd);
245
-
246
- // If the line has a { after this position and before that { there's a %,
247
- // and we haven't passed the { yet, we're in the selector
248
- if (/%/.test(lineBeforePos) && /^\s*%/.test(content.substring(position + matchedValue.length, position + matchedValue.length + 5))) {
249
- // We're right before the % in a keyframe selector
250
- if (DEBUG) console.log(` → SAFE: Keyframe percentage selector`);
251
- return true;
252
- }
253
- if (/%[^{]*$/.test(lineBeforePos) && !lineBeforePos.includes('{')) {
254
- // We're somewhere in a percentage before the {
255
- if (DEBUG) console.log(` → SAFE: Keyframe percentage line`);
256
- return true;
257
- }
258
- }
259
-
260
- // Check for comments
261
- const commentStart = content.lastIndexOf('/*', position);
262
- const commentEnd = content.lastIndexOf('*/', position);
263
- if (commentStart > commentEnd) {
264
- if (DEBUG) console.log(` → SAFE: Inside block comment`);
265
- return true;
266
- }
267
-
268
- // Check for inline comments (//)
269
- const lineContent = content.substring(lineStart, position + matchedValue.length);
270
- const inlineCommentPos = lineContent.lastIndexOf('//');
271
- if (inlineCommentPos !== -1) {
272
- // Check if our position is after the // on this line
273
- const posInLine = position - lineStart;
274
- if (posInLine >= inlineCommentPos) {
275
- if (DEBUG) console.log(` → SAFE: Inside inline comment`);
276
- return true;
277
- }
278
- }
279
-
280
- if (DEBUG) console.log(` → NOT SAFE: Will replace`);
281
- return false;
282
- }
283
-
284
- /**
285
- * Replaces value at specific index
286
- */
287
- function replaceAt(content, searchValue, replaceValue, startIndex) {
288
- // Find the exact position and replace
289
- const before = content.substring(0, startIndex);
290
- const after = content.substring(startIndex);
291
-
292
- // Replace first occurrence in 'after'
293
- const replaced = after.replace(searchValue, replaceValue);
294
-
295
- return before + replaced;
296
- }
297
-
298
- /**
299
- * Gets relative import path from current file to variables file
300
- */
301
- function getRelativeImportPath(fromFile, toFile) {
302
- const fromDir = path.dirname(fromFile);
303
- let relativePath = path.relative(fromDir, toFile);
304
-
305
- // Convert to forward slashes for imports
306
- relativePath = relativePath.replace(/\\/g, '/');
307
-
308
- // Remove .scss extension
309
- relativePath = relativePath.replace(/\.scss$/, '');
310
-
311
- // Add ./ prefix if not starting with ../
312
- if (!relativePath.startsWith('..')) {
313
- relativePath = './' + relativePath;
314
- }
315
-
316
- return relativePath;
317
- }
318
-
319
- module.exports = {
320
- refactorScssFiles,
321
- refactorFile
322
- };
1
+ const fs = require('fs');
2
+ const path = require('path');
3
+
4
+ /**
5
+ * Refactors SCSS files by replacing hardcoded values with variable references
6
+ * @param {Array<string>} scssFiles - Array of SCSS file paths
7
+ * @param {Object} analysis - Analysis results with variable mappings
8
+ * @param {string} variablesFilePath - Path to the generated variables file
9
+ * @param {Object} config - Configuration
10
+ */
11
+ function refactorScssFiles(scssFiles, analysis, variablesFilePath, config) {
12
+ const refactoredFiles = [];
13
+
14
+ scssFiles.forEach(filePath => {
15
+ const content = fs.readFileSync(filePath, 'utf8');
16
+ const refactored = refactorFile(content, analysis, variablesFilePath, filePath, config);
17
+
18
+ if (refactored.modified) {
19
+ fs.writeFileSync(filePath, refactored.content, 'utf8');
20
+ refactoredFiles.push({
21
+ path: filePath,
22
+ changes: refactored.changes,
23
+ });
24
+ }
25
+ });
26
+
27
+ return refactoredFiles;
28
+ }
29
+
30
+ /**
31
+ * Refactors a single SCSS file
32
+ */
33
+ function refactorFile(content, analysis, variablesFilePath, currentFilePath, config) {
34
+ let newContent = content;
35
+ let modified = false;
36
+ const changes = [];
37
+
38
+ // Build value-to-variable mapping
39
+ const valueMap = buildValueMap(analysis);
40
+
41
+ // Check if file already has @use import for variables
42
+ const hasVariablesImport = content.includes('@use') && content.includes('variables');
43
+
44
+ // Replace values with variables
45
+ // Sort by value length (longest first) to avoid partial replacements
46
+ // E.g., replace "0.5" before "0" to prevent "0.5" becoming "$var.5"
47
+ const sortedEntries = Object.entries(valueMap).sort((a, b) => b[0].length - a[0].length);
48
+
49
+ sortedEntries.forEach(([value, variableName]) => {
50
+ const regex = createReplacementRegex(value);
51
+ const matches = [];
52
+
53
+ let match;
54
+ // Find matches in the current state of newContent (after previous replacements)
55
+ while ((match = regex.exec(newContent)) !== null) {
56
+ matches.push({
57
+ index: match.index,
58
+ matched: match[0],
59
+ fullMatch: match,
60
+ });
61
+ }
62
+
63
+ // Check if replacements are in safe zones
64
+ // Process matches in reverse order (highest index first) to avoid position shifts
65
+ matches.reverse().forEach(m => {
66
+ const inSafeZone = isInSafeZone(newContent, m.index, m.matched);
67
+
68
+ if (!inSafeZone) {
69
+ // Replace the value
70
+ const before = newContent;
71
+ newContent = replaceAt(newContent, m.matched, variableName, m.index);
72
+
73
+ if (before !== newContent) {
74
+ modified = true;
75
+ changes.push({
76
+ from: value,
77
+ to: variableName,
78
+ });
79
+ }
80
+ }
81
+ });
82
+ });
83
+
84
+ // Add @use import if modified and not already present
85
+ if (modified && !hasVariablesImport) {
86
+ const relativePath = getRelativeImportPath(currentFilePath, variablesFilePath);
87
+ const useStatement = `@use '${relativePath}' as *;\n\n`;
88
+ newContent = useStatement + newContent;
89
+ }
90
+
91
+ return {
92
+ content: newContent,
93
+ modified,
94
+ changes,
95
+ };
96
+ }
97
+
98
+ /**
99
+ * Builds a map of values to variable names
100
+ */
101
+ function buildValueMap(analysis) {
102
+ const valueMap = {};
103
+
104
+ Object.keys(analysis).forEach(category => {
105
+ analysis[category].forEach(item => {
106
+ // Use normalized value as key for consistent matching
107
+ valueMap[item.value] = item.suggestedName;
108
+ });
109
+ });
110
+
111
+ return valueMap;
112
+ }
113
+
114
+ /**
115
+ * Creates a regex for replacing a specific value
116
+ */
117
+ function createReplacementRegex(value) {
118
+ // Escape special regex characters
119
+ const escaped = value.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
120
+
121
+ // Add appropriate boundaries based on value type to prevent partial matches
122
+
123
+ if (/^#[0-9a-fA-F]+$/.test(value)) {
124
+ // Hex colors - no word boundary before #, but check after
125
+ return new RegExp(`${escaped}\\b`, 'g');
126
+ } else if (/\d+(?:px|%|em|rem|vh|vw|pt|ch|ex)$/.test(value)) {
127
+ // Values with CSS units - ensure not part of larger number
128
+ return new RegExp(`(?<!\\d)${escaped}\\b`, 'g');
129
+ } else if (/^-?\d+(?:\.\d+)?$/.test(value)) {
130
+ // Pure numbers/decimals (unitless) - ensure not part of larger number or before units
131
+ // Negative lookbehind: not preceded by digit
132
+ // Negative lookahead: not followed by digit, unit letters, or % (for keyframe selectors)
133
+ return new RegExp(`(?<!\\d)${escaped}(?!\\d|[a-z%])`, 'g');
134
+ } else if (/^rgba?\(/.test(value)) {
135
+ // RGB/RGBA colors - match complete function
136
+ return new RegExp(escaped, 'g');
137
+ } else {
138
+ // Other values (keywords, etc.) - use word boundaries
139
+ return new RegExp(`\\b${escaped}\\b`, 'g');
140
+ }
141
+ }
142
+
143
+ /**
144
+ * Checks if a position in content is in a safe zone (should not be replaced)
145
+ */
146
+ function isInSafeZone(content, position, matchedValue) {
147
+ const DEBUG = false; // Enable for debugging
148
+
149
+ // Check for variable declarations
150
+ const lineStart = content.lastIndexOf('\n', position);
151
+ const lineEnd = content.indexOf('\n', position);
152
+ const line = content.substring(lineStart, lineEnd);
153
+
154
+ // Check if this line is a variable declaration: $variable-name: value;
155
+ // Match pattern: optional whitespace, $, identifier, optional whitespace, colon
156
+ const varDeclMatch = line.match(/^\s*\$([\w-]+)\s*:/);
157
+ if (varDeclMatch) {
158
+ // This line IS a variable declaration
159
+ if (DEBUG) console.log(` → SAFE: Variable declaration line`);
160
+ return true;
161
+ }
162
+
163
+ // Check if we're in the middle of a larger number or value
164
+ const charBefore = content.charAt(position - 1);
165
+ const charAfter = content.charAt(position + matchedValue.length);
166
+
167
+ // Prevent replacement if surrounded by digits (partial number match)
168
+ if (/\d/.test(charBefore) || (/^\d+$/.test(matchedValue) && /\d/.test(charAfter))) {
169
+ if (DEBUG) console.log(` → SAFE: Surrounded by digits`);
170
+ return true;
171
+ }
172
+
173
+ // Prevent replacement within hex colors
174
+ if (/[0-9a-fA-F]/.test(charBefore) || /[0-9a-fA-F]/.test(charAfter)) {
175
+ // Look back further to check if we're in a hex color
176
+ const lookBehind = content.substring(Math.max(0, position - 10), position);
177
+ if (/#[0-9a-fA-F]*$/.test(lookBehind)) {
178
+ if (DEBUG) console.log(` → SAFE: Inside hex color`);
179
+ return true;
180
+ }
181
+ }
182
+
183
+ // Prevent replacement if it would create invalid variable concatenation
184
+ if (charBefore === '$' || charAfter === '$') {
185
+ if (DEBUG) console.log(` → SAFE: Near $ symbol`);
186
+ return true;
187
+ }
188
+
189
+ // Prevent replacement for negative values (e.g., -16px should not become -$spacing-md)
190
+ if (charBefore === '-' && /^\d/.test(matchedValue)) {
191
+ return true;
192
+ }
193
+
194
+ // Check for @use, @forward, @import
195
+ if (/@(?:use|forward|import)/.test(line)) {
196
+ return true;
197
+ }
198
+
199
+ // Check for url(), calc(), transform functions, and other contexts
200
+ const beforeContext = content.substring(Math.max(0, position - 100), position);
201
+
202
+ // Prevent replacement inside calc() expressions
203
+ if (/calc\s*\([^)]*$/.test(beforeContext)) {
204
+ return true;
205
+ }
206
+
207
+ // Prevent replacement inside transform functions
208
+ if (/(?:translate|rotate|scale|skew|matrix|perspective)\s*\([^)]*$/.test(beforeContext)) {
209
+ return true;
210
+ }
211
+
212
+ if (/url\s*\(\s*[^)]*$/.test(beforeContext)) {
213
+ return true;
214
+ }
215
+
216
+ // Check for content property
217
+ if (/content\s*:\s*[^;]*$/.test(beforeContext)) {
218
+ return true;
219
+ }
220
+
221
+ // Check for interpolation #{}
222
+ if (/#\{[^}]*$/.test(beforeContext)) {
223
+ return true;
224
+ }
225
+
226
+ // Check for Material functions
227
+ if (/mat\.\w+\([^)]*$/.test(beforeContext)) {
228
+ return true;
229
+ }
230
+
231
+ // Prevent replacement inside attribute selectors
232
+ if (
233
+ /\[[^\]]*$/.test(beforeContext) &&
234
+ /^[^\[]*\]/.test(content.substring(position, position + 50))
235
+ ) {
236
+ if (DEBUG) console.log(` → SAFE: Inside attribute selector`);
237
+ return true;
238
+ }
239
+
240
+ // Prevent replacement in keyframe percentages (e.g., 0%, 50%, 100% in @keyframes)
241
+ // Check if we're in a @keyframes block and this position is before the opening brace
242
+ if (/@keyframes/.test(beforeContext)) {
243
+ // Check if we're in the selector part (before {) of a keyframe rule
244
+ // Get text from line start to current position and after
245
+ const lineBeforePos = content.substring(lineStart, position + matchedValue.length);
246
+ const lineAfterPos = content.substring(position + matchedValue.length, lineEnd);
247
+
248
+ // If the line has a { after this position and before that { there's a %,
249
+ // and we haven't passed the { yet, we're in the selector
250
+ if (
251
+ /%/.test(lineBeforePos) &&
252
+ /^\s*%/.test(
253
+ content.substring(position + matchedValue.length, position + matchedValue.length + 5)
254
+ )
255
+ ) {
256
+ // We're right before the % in a keyframe selector
257
+ if (DEBUG) console.log(` → SAFE: Keyframe percentage selector`);
258
+ return true;
259
+ }
260
+ if (/%[^{]*$/.test(lineBeforePos) && !lineBeforePos.includes('{')) {
261
+ // We're somewhere in a percentage before the {
262
+ if (DEBUG) console.log(` → SAFE: Keyframe percentage line`);
263
+ return true;
264
+ }
265
+ }
266
+
267
+ // Check for comments
268
+ const commentStart = content.lastIndexOf('/*', position);
269
+ const commentEnd = content.lastIndexOf('*/', position);
270
+ if (commentStart > commentEnd) {
271
+ if (DEBUG) console.log(` → SAFE: Inside block comment`);
272
+ return true;
273
+ }
274
+
275
+ // Check for inline comments (//)
276
+ const lineContent = content.substring(lineStart, position + matchedValue.length);
277
+ const inlineCommentPos = lineContent.lastIndexOf('//');
278
+ if (inlineCommentPos !== -1) {
279
+ // Check if our position is after the // on this line
280
+ const posInLine = position - lineStart;
281
+ if (posInLine >= inlineCommentPos) {
282
+ if (DEBUG) console.log(` → SAFE: Inside inline comment`);
283
+ return true;
284
+ }
285
+ }
286
+
287
+ if (DEBUG) console.log(` → NOT SAFE: Will replace`);
288
+ return false;
289
+ }
290
+
291
+ /**
292
+ * Replaces value at specific index
293
+ */
294
+ function replaceAt(content, searchValue, replaceValue, startIndex) {
295
+ // Find the exact position and replace
296
+ const before = content.substring(0, startIndex);
297
+ const after = content.substring(startIndex);
298
+
299
+ // Replace first occurrence in 'after'
300
+ const replaced = after.replace(searchValue, replaceValue);
301
+
302
+ return before + replaced;
303
+ }
304
+
305
+ /**
306
+ * Gets relative import path from current file to variables file
307
+ */
308
+ function getRelativeImportPath(fromFile, toFile) {
309
+ const fromDir = path.dirname(fromFile);
310
+ let relativePath = path.relative(fromDir, toFile);
311
+
312
+ // Convert to forward slashes for imports
313
+ relativePath = relativePath.replace(/\\/g, '/');
314
+
315
+ // Remove .scss extension
316
+ relativePath = relativePath.replace(/\.scss$/, '');
317
+
318
+ // Add ./ prefix if not starting with ../
319
+ if (!relativePath.startsWith('..')) {
320
+ relativePath = './' + relativePath;
321
+ }
322
+
323
+ return relativePath;
324
+ }
325
+
326
+ module.exports = {
327
+ refactorScssFiles,
328
+ refactorFile,
329
+ };