@oceanbase/codemod 1.0.0-alpha.7 → 1.0.0-alpha.9
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/README.md +85 -0
- package/bin/cli.js +39 -3
- package/package.json +4 -4
- package/transforms/__testfixtures__/less-to-cssvar/basic.input.less +16 -0
- package/transforms/__testfixtures__/less-to-cssvar/basic.output.less +14 -0
- package/transforms/__testfixtures__/less-to-cssvar/color-scales.input.less +23 -0
- package/transforms/__testfixtures__/less-to-cssvar/color-scales.output.less +21 -0
- package/transforms/__testfixtures__/less-to-cssvar/complex-values.input.less +22 -0
- package/transforms/__testfixtures__/less-to-cssvar/complex-values.output.less +20 -0
- package/transforms/__testfixtures__/less-to-cssvar/control-tokens.input.less +29 -0
- package/transforms/__testfixtures__/less-to-cssvar/control-tokens.output.less +27 -0
- package/transforms/__testfixtures__/less-to-cssvar/css-modules-global.input.less +21 -0
- package/transforms/__testfixtures__/less-to-cssvar/css-modules-global.output.less +19 -0
- package/transforms/__testfixtures__/less-to-cssvar/custom-prefix.input.less +9 -0
- package/transforms/__testfixtures__/less-to-cssvar/custom-prefix.output.less +7 -0
- package/transforms/__testfixtures__/less-to-cssvar/fill-tokens.input.less +36 -0
- package/transforms/__testfixtures__/less-to-cssvar/fill-tokens.output.less +34 -0
- package/transforms/__testfixtures__/less-to-cssvar/mixed-values.input.less +21 -0
- package/transforms/__testfixtures__/less-to-cssvar/mixed-values.output.less +19 -0
- package/transforms/__testfixtures__/less-to-cssvar/multiple-imports.input.less +9 -0
- package/transforms/__testfixtures__/less-to-cssvar/multiple-imports.output.less +8 -0
- package/transforms/__testfixtures__/less-to-cssvar/nested-selectors.input.less +24 -0
- package/transforms/__testfixtures__/less-to-cssvar/nested-selectors.output.less +22 -0
- package/transforms/__testfixtures__/less-to-cssvar/no-transform.input.less +8 -0
- package/transforms/__testfixtures__/less-to-cssvar/no-transform.output.less +8 -0
- package/transforms/__testfixtures__/less-to-cssvar/obui-import.input.less +7 -0
- package/transforms/__testfixtures__/less-to-cssvar/obui-import.output.less +5 -0
- package/transforms/__testfixtures__/less-to-cssvar/status-colors.input.less +25 -0
- package/transforms/__testfixtures__/less-to-cssvar/status-colors.output.less +23 -0
- package/transforms/__testfixtures__/less-to-token/case-insensitive.input.less +4 -0
- package/transforms/__testfixtures__/less-to-token/case-insensitive.output.less +4 -0
- package/transforms/__testfixtures__/style-to-token/function-component.input.js +2 -2
- package/transforms/__testfixtures__/style-to-token/function-component.output.js +2 -2
- package/transforms/__tests__/less-to-cssvar.test.ts +124 -0
- package/transforms/less-to-cssvar.js +482 -0
- package/transforms/utils/token.js +19 -0
|
@@ -0,0 +1,482 @@
|
|
|
1
|
+
const path = require('path');
|
|
2
|
+
const fs = require('fs');
|
|
3
|
+
const postcss = require('postcss');
|
|
4
|
+
const postcssLess = require('postcss-less');
|
|
5
|
+
const isDirectory = require('is-directory');
|
|
6
|
+
const { glob } = require('glob');
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Get Less tokens from @oceanbase/design theme Less file
|
|
10
|
+
* This reads all @variableName definitions from the theme file
|
|
11
|
+
*/
|
|
12
|
+
function getLessTokensFromTheme() {
|
|
13
|
+
try {
|
|
14
|
+
// Try to find the Less theme file from @oceanbase/design
|
|
15
|
+
const themeLessPath = require.resolve('@oceanbase/design/es/theme/style/default.less');
|
|
16
|
+
const content = fs.readFileSync(themeLessPath, 'utf-8');
|
|
17
|
+
|
|
18
|
+
// Extract all @variableName: definitions
|
|
19
|
+
const tokenRegex = /@([a-zA-Z][a-zA-Z0-9]*):/g;
|
|
20
|
+
const tokens = new Set();
|
|
21
|
+
let match;
|
|
22
|
+
while ((match = tokenRegex.exec(content)) !== null) {
|
|
23
|
+
tokens.add(match[1]);
|
|
24
|
+
}
|
|
25
|
+
return Array.from(tokens);
|
|
26
|
+
} catch (e) {
|
|
27
|
+
// Fallback: try lib path
|
|
28
|
+
try {
|
|
29
|
+
const themeLessPath = require.resolve('@oceanbase/design/lib/theme/style/default.less');
|
|
30
|
+
const content = fs.readFileSync(themeLessPath, 'utf-8');
|
|
31
|
+
|
|
32
|
+
const tokenRegex = /@([a-zA-Z][a-zA-Z0-9]*):/g;
|
|
33
|
+
const tokens = new Set();
|
|
34
|
+
let match;
|
|
35
|
+
while ((match = tokenRegex.exec(content)) !== null) {
|
|
36
|
+
tokens.add(match[1]);
|
|
37
|
+
}
|
|
38
|
+
return Array.from(tokens);
|
|
39
|
+
} catch (e2) {
|
|
40
|
+
// Final fallback: use theme token object
|
|
41
|
+
try {
|
|
42
|
+
const { default: defaultTheme } = require('@oceanbase/design/lib/theme/default');
|
|
43
|
+
return Object.keys(defaultTheme.token || {});
|
|
44
|
+
} catch (e3) {
|
|
45
|
+
console.warn('Warning: Could not load tokens from @oceanbase/design, using empty list');
|
|
46
|
+
return [];
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
// Get Less variable tokens dynamically from @oceanbase/design theme
|
|
53
|
+
const LESS_TOKENS = getLessTokensFromTheme();
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Convert camelCase to kebab-case
|
|
57
|
+
* @param {string} str - camelCase string
|
|
58
|
+
* @returns {string} - kebab-case string
|
|
59
|
+
*/
|
|
60
|
+
function camelToKebab(str) {
|
|
61
|
+
return str.replace(/([a-z0-9])([A-Z])/g, '$1-$2').toLowerCase();
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* Find all less files in the directory
|
|
66
|
+
* @param dir
|
|
67
|
+
* @returns
|
|
68
|
+
*/
|
|
69
|
+
const findAllLessFiles = dir => {
|
|
70
|
+
const lessFiles = [];
|
|
71
|
+
const isDir = isDirectory.sync(dir);
|
|
72
|
+
if (isDir) {
|
|
73
|
+
const files = fs.readdirSync(dir);
|
|
74
|
+
files.forEach(file => {
|
|
75
|
+
const filePath = path.join(dir, file);
|
|
76
|
+
if (isDirectory.sync(filePath)) {
|
|
77
|
+
if (filePath.includes('.umi') || filePath.includes('.umi-production')) {
|
|
78
|
+
return;
|
|
79
|
+
}
|
|
80
|
+
lessFiles.push(...findAllLessFiles(filePath));
|
|
81
|
+
} else if (file.endsWith('.less')) {
|
|
82
|
+
lessFiles.push(filePath);
|
|
83
|
+
}
|
|
84
|
+
});
|
|
85
|
+
} else if (dir.endsWith('.less')) {
|
|
86
|
+
lessFiles.push(dir);
|
|
87
|
+
}
|
|
88
|
+
return lessFiles;
|
|
89
|
+
};
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* Transform Less variable to CSS variable
|
|
93
|
+
* @param {string} value - CSS value that may contain Less variables
|
|
94
|
+
* @param {string} prefix - CSS variable prefix (default: 'ant')
|
|
95
|
+
* @returns {string} - Transformed value with CSS variables
|
|
96
|
+
*/
|
|
97
|
+
function transformLessVarToCssVar(value, prefix = 'ant') {
|
|
98
|
+
if (!value || typeof value !== 'string') {
|
|
99
|
+
return value;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
let result = value;
|
|
103
|
+
|
|
104
|
+
// Match Less variables like @colorPrimary, @fontSize, etc.
|
|
105
|
+
// Support both @tokenName and @{tokenName} syntax
|
|
106
|
+
const lessVarRegex = /@\{?([a-zA-Z][a-zA-Z0-9]*)\}?/g;
|
|
107
|
+
|
|
108
|
+
result = result.replace(lessVarRegex, (match, varName) => {
|
|
109
|
+
// Check if this is a known token
|
|
110
|
+
if (LESS_TOKENS.includes(varName)) {
|
|
111
|
+
const kebabName = camelToKebab(varName);
|
|
112
|
+
return `var(--${prefix}-${kebabName})`;
|
|
113
|
+
}
|
|
114
|
+
// Return original if not a known token
|
|
115
|
+
return match;
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
return result;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
/**
|
|
122
|
+
* Transform a single Less file to use CSS variables
|
|
123
|
+
* @param {string} file - File path
|
|
124
|
+
* @param {object} options - Transform options
|
|
125
|
+
* @param {string} options.prefix - CSS variable prefix (default: 'ant')
|
|
126
|
+
* @returns {Promise<string>} - Transformed content
|
|
127
|
+
*/
|
|
128
|
+
async function transform(file, options = {}) {
|
|
129
|
+
const { prefix = 'ant' } = options;
|
|
130
|
+
const content = fs.readFileSync(file, 'utf-8');
|
|
131
|
+
const { root: ast } = await postcss([]).process(content, {
|
|
132
|
+
syntax: postcssLess,
|
|
133
|
+
from: file,
|
|
134
|
+
});
|
|
135
|
+
|
|
136
|
+
// Track whether any transformations were made
|
|
137
|
+
let hasTransformations = false;
|
|
138
|
+
|
|
139
|
+
// Traverse AST
|
|
140
|
+
ast.walk(node => {
|
|
141
|
+
if (node.type === 'decl') {
|
|
142
|
+
// Transform property values that contain Less variables
|
|
143
|
+
const newValue = transformLessVarToCssVar(node.value, prefix);
|
|
144
|
+
if (newValue !== node.value) {
|
|
145
|
+
node.value = newValue;
|
|
146
|
+
hasTransformations = true;
|
|
147
|
+
}
|
|
148
|
+
} else if (node.type === 'atrule') {
|
|
149
|
+
// Remove the theme import
|
|
150
|
+
if (node.name === 'import') {
|
|
151
|
+
if (
|
|
152
|
+
node.params?.includes("'~@oceanbase/design/es/theme/index.less'") ||
|
|
153
|
+
node.params?.includes('"~@oceanbase/design/es/theme/index.less"') ||
|
|
154
|
+
node.params?.includes("'~@alipay/ob-ui/es/theme/index.less'") ||
|
|
155
|
+
node.params?.includes('"~@alipay/ob-ui/es/theme/index.less"')
|
|
156
|
+
) {
|
|
157
|
+
node.remove();
|
|
158
|
+
hasTransformations = true;
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
// Transform Less variables in at-rule params (e.g., media queries)
|
|
162
|
+
if (node.params) {
|
|
163
|
+
const newParams = transformLessVarToCssVar(node.params, prefix);
|
|
164
|
+
if (newParams !== node.params) {
|
|
165
|
+
node.params = newParams;
|
|
166
|
+
hasTransformations = true;
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
});
|
|
171
|
+
|
|
172
|
+
return {
|
|
173
|
+
content: ast.toString(postcssLess.stringify),
|
|
174
|
+
hasTransformations,
|
|
175
|
+
};
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
/**
|
|
179
|
+
* Convert Less single-line comments (//) to CSS block comments
|
|
180
|
+
* @param {string} content - File content
|
|
181
|
+
* @returns {string} - Content with converted comments
|
|
182
|
+
*/
|
|
183
|
+
function convertLessCommentsToCss(content) {
|
|
184
|
+
// Convert // comments to /* */ comments
|
|
185
|
+
// Match // at the start of a line or after whitespace, but not inside strings or existing block comments
|
|
186
|
+
const lines = content.split('\n');
|
|
187
|
+
const result = lines.map(line => {
|
|
188
|
+
// Skip if line is inside a block comment or contains a URL
|
|
189
|
+
if (line.includes('://')) {
|
|
190
|
+
// Handle URLs - only convert comments that are not part of URLs
|
|
191
|
+
return line.replace(/(?<!:)\/\/(.*)$/gm, (match, comment) => {
|
|
192
|
+
// Check if this is really a comment (not a URL protocol)
|
|
193
|
+
const beforeMatch = line.substring(0, line.lastIndexOf(match));
|
|
194
|
+
if (beforeMatch.match(/https?:$/) || beforeMatch.match(/url\([^)]*$/)) {
|
|
195
|
+
return match; // Keep as is for URLs
|
|
196
|
+
}
|
|
197
|
+
return `/* ${comment.trim()} */`;
|
|
198
|
+
});
|
|
199
|
+
}
|
|
200
|
+
// Convert standalone // comments
|
|
201
|
+
return line.replace(/^(\s*)\/\/(.*)$/, '$1/* $2 */').replace(/(\s)\/\/(.*)$/, '$1/* $2 */');
|
|
202
|
+
});
|
|
203
|
+
return result.join('\n');
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
/**
|
|
207
|
+
* Update import references in JS/TS files
|
|
208
|
+
* @param {string} baseDir - Base directory to search for JS/TS files
|
|
209
|
+
* @param {Array<{oldPath: string, newPath: string}>} renamedFiles - List of renamed files
|
|
210
|
+
*/
|
|
211
|
+
async function updateImportReferences(baseDir, renamedFiles) {
|
|
212
|
+
if (renamedFiles.length === 0) return;
|
|
213
|
+
|
|
214
|
+
// Find all JS/TS/JSX/TSX files
|
|
215
|
+
const jsFiles = await glob('**/*.{js,jsx,ts,tsx}', {
|
|
216
|
+
cwd: baseDir,
|
|
217
|
+
absolute: true,
|
|
218
|
+
ignore: ['**/node_modules/**', '**/.umi/**', '**/.umi-production/**'],
|
|
219
|
+
});
|
|
220
|
+
|
|
221
|
+
for (const jsFile of jsFiles) {
|
|
222
|
+
let content = fs.readFileSync(jsFile, 'utf-8');
|
|
223
|
+
let hasChanges = false;
|
|
224
|
+
|
|
225
|
+
for (const { oldPath, newPath } of renamedFiles) {
|
|
226
|
+
// Calculate relative paths from the JS file to the Less/CSS files
|
|
227
|
+
const jsFileDir = path.dirname(jsFile);
|
|
228
|
+
|
|
229
|
+
// Get the relative path from the JS file to the old Less file
|
|
230
|
+
let relativeOldPath = path.relative(jsFileDir, oldPath);
|
|
231
|
+
let relativeNewPath = path.relative(jsFileDir, newPath);
|
|
232
|
+
|
|
233
|
+
// Normalize path separators for the regex
|
|
234
|
+
relativeOldPath = relativeOldPath.replace(/\\/g, '/');
|
|
235
|
+
relativeNewPath = relativeNewPath.replace(/\\/g, '/');
|
|
236
|
+
|
|
237
|
+
// Add ./ prefix if needed
|
|
238
|
+
if (!relativeOldPath.startsWith('.') && !relativeOldPath.startsWith('/')) {
|
|
239
|
+
relativeOldPath = './' + relativeOldPath;
|
|
240
|
+
}
|
|
241
|
+
if (!relativeNewPath.startsWith('.') && !relativeNewPath.startsWith('/')) {
|
|
242
|
+
relativeNewPath = './' + relativeNewPath;
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
// Also handle the case without ./ prefix
|
|
246
|
+
const relativeOldPathNoDot = relativeOldPath.replace(/^\.\//, '');
|
|
247
|
+
const relativeNewPathNoDot = relativeNewPath.replace(/^\.\//, '');
|
|
248
|
+
|
|
249
|
+
// Create regex patterns to match import statements
|
|
250
|
+
// Match: import './style.less', import "./style.less", require('./style.less'), require("./style.less")
|
|
251
|
+
const patterns = [
|
|
252
|
+
// With ./ prefix
|
|
253
|
+
new RegExp(`(['"])${escapeRegExp(relativeOldPath)}\\1`, 'g'),
|
|
254
|
+
// Without ./ prefix (for some bundler configurations)
|
|
255
|
+
new RegExp(`(['"])${escapeRegExp(relativeOldPathNoDot)}\\1`, 'g'),
|
|
256
|
+
];
|
|
257
|
+
|
|
258
|
+
for (const pattern of patterns) {
|
|
259
|
+
const newContent = content.replace(pattern, (match, quote) => {
|
|
260
|
+
return `${quote}${relativeNewPath}${quote}`;
|
|
261
|
+
});
|
|
262
|
+
if (newContent !== content) {
|
|
263
|
+
content = newContent;
|
|
264
|
+
hasChanges = true;
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
if (hasChanges) {
|
|
270
|
+
fs.writeFileSync(jsFile, content);
|
|
271
|
+
console.log(` Updated imports: ${jsFile}`);
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
/**
|
|
277
|
+
* Escape special regex characters in a string
|
|
278
|
+
* @param {string} string - String to escape
|
|
279
|
+
* @returns {string} - Escaped string
|
|
280
|
+
*/
|
|
281
|
+
function escapeRegExp(string) {
|
|
282
|
+
return string.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
// Common global style file name patterns (case insensitive)
|
|
286
|
+
const GLOBAL_STYLE_PATTERNS = [
|
|
287
|
+
/^global\.less$/i,
|
|
288
|
+
/^globals\.less$/i,
|
|
289
|
+
/^reset\.less$/i,
|
|
290
|
+
/^normalize\.less$/i,
|
|
291
|
+
/^base\.less$/i,
|
|
292
|
+
/^common\.less$/i,
|
|
293
|
+
/^app\.less$/i,
|
|
294
|
+
/^index\.less$/i,
|
|
295
|
+
/^main\.less$/i,
|
|
296
|
+
/^style\.less$/i,
|
|
297
|
+
/^styles\.less$/i,
|
|
298
|
+
/^variables\.less$/i,
|
|
299
|
+
/^mixins\.less$/i,
|
|
300
|
+
/^theme\.less$/i,
|
|
301
|
+
];
|
|
302
|
+
|
|
303
|
+
/**
|
|
304
|
+
* Check if a Less file is imported as a CSS Module in any JS/TS file
|
|
305
|
+
* CSS Module import: import styles from './xxx.less'
|
|
306
|
+
* Global import: import './xxx.less'
|
|
307
|
+
* @param {string} lessFilePath - Path to the Less file
|
|
308
|
+
* @param {string} baseDir - Base directory to search for JS/TS files
|
|
309
|
+
* @returns {Promise<boolean>} - True if imported as CSS Module, false if global or not found
|
|
310
|
+
*/
|
|
311
|
+
async function isImportedAsCssModule(lessFilePath, baseDir) {
|
|
312
|
+
const fileName = path.basename(lessFilePath);
|
|
313
|
+
|
|
314
|
+
// Check if file name matches common global style patterns
|
|
315
|
+
for (const pattern of GLOBAL_STYLE_PATTERNS) {
|
|
316
|
+
if (pattern.test(fileName)) {
|
|
317
|
+
return false;
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
// Check if file already has .module in the name
|
|
322
|
+
if (/\.module\.less$/.test(lessFilePath)) {
|
|
323
|
+
return true;
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
try {
|
|
327
|
+
// Find all JS/TS/JSX/TSX files
|
|
328
|
+
const jsFiles = await glob('**/*.{js,jsx,ts,tsx}', {
|
|
329
|
+
cwd: baseDir,
|
|
330
|
+
absolute: true,
|
|
331
|
+
ignore: ['**/node_modules/**', '**/.umi/**', '**/.umi-production/**'],
|
|
332
|
+
});
|
|
333
|
+
|
|
334
|
+
for (const jsFile of jsFiles) {
|
|
335
|
+
const content = fs.readFileSync(jsFile, 'utf-8');
|
|
336
|
+
const jsFileDir = path.dirname(jsFile);
|
|
337
|
+
|
|
338
|
+
// Calculate relative path from JS file to Less file
|
|
339
|
+
let relativePath = path.relative(jsFileDir, lessFilePath).replace(/\\/g, '/');
|
|
340
|
+
if (!relativePath.startsWith('.') && !relativePath.startsWith('/')) {
|
|
341
|
+
relativePath = './' + relativePath;
|
|
342
|
+
}
|
|
343
|
+
const relativePathNoDot = relativePath.replace(/^\.\//, '');
|
|
344
|
+
|
|
345
|
+
// Escape for regex
|
|
346
|
+
const escapedPath = escapeRegExp(relativePath);
|
|
347
|
+
const escapedPathNoDot = escapeRegExp(relativePathNoDot);
|
|
348
|
+
|
|
349
|
+
// Check for CSS Module import pattern: import xxx from './xxx.less'
|
|
350
|
+
// Match: import styles from './xxx.less', import * as styles from './xxx.less'
|
|
351
|
+
const cssModuleImportRegex = new RegExp(
|
|
352
|
+
`import\\s+(?:\\*\\s+as\\s+)?\\w+\\s+from\\s+(['"])(?:${escapedPath}|${escapedPathNoDot})\\1`,
|
|
353
|
+
'm'
|
|
354
|
+
);
|
|
355
|
+
|
|
356
|
+
// Check for global import pattern: import './xxx.less'
|
|
357
|
+
const globalImportRegex = new RegExp(
|
|
358
|
+
`import\\s+(['"])(?:${escapedPath}|${escapedPathNoDot})\\1`,
|
|
359
|
+
'm'
|
|
360
|
+
);
|
|
361
|
+
|
|
362
|
+
// Check for require with assignment: const styles = require('./xxx.less')
|
|
363
|
+
const cssModuleRequireRegex = new RegExp(
|
|
364
|
+
`(?:const|let|var)\\s+\\w+\\s*=\\s*require\\s*\\(\\s*(['"])(?:${escapedPath}|${escapedPathNoDot})\\1\\s*\\)`,
|
|
365
|
+
'm'
|
|
366
|
+
);
|
|
367
|
+
|
|
368
|
+
// Check for require without assignment: require('./xxx.less')
|
|
369
|
+
const globalRequireRegex = new RegExp(
|
|
370
|
+
`(?<!(?:const|let|var)\\s+\\w+\\s*=\\s*)require\\s*\\(\\s*(['"])(?:${escapedPath}|${escapedPathNoDot})\\1\\s*\\)`,
|
|
371
|
+
'm'
|
|
372
|
+
);
|
|
373
|
+
|
|
374
|
+
if (cssModuleImportRegex.test(content) || cssModuleRequireRegex.test(content)) {
|
|
375
|
+
return true;
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
if (globalImportRegex.test(content) || globalRequireRegex.test(content)) {
|
|
379
|
+
// Found as global import, but continue checking other files
|
|
380
|
+
// in case it's also imported as CSS Module elsewhere
|
|
381
|
+
continue;
|
|
382
|
+
}
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
// Default to false (treat as global) if not found or only found as global import
|
|
386
|
+
return false;
|
|
387
|
+
} catch (e) {
|
|
388
|
+
// If scanning fails, default to user's preference
|
|
389
|
+
return true;
|
|
390
|
+
}
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
/**
|
|
394
|
+
* Get the new file path when renaming from .less to .css
|
|
395
|
+
* @param {string} filePath - Original file path
|
|
396
|
+
* @param {boolean} shouldAddModule - Whether to add .module suffix
|
|
397
|
+
* @returns {string} - New file path
|
|
398
|
+
*/
|
|
399
|
+
function getNewCssPath(filePath, shouldAddModule) {
|
|
400
|
+
// Check if file already has .module in the name
|
|
401
|
+
const hasModule = /\.module\.less$/.test(filePath);
|
|
402
|
+
|
|
403
|
+
if (hasModule) {
|
|
404
|
+
// Already has .module, just change extension
|
|
405
|
+
return filePath.replace(/\.less$/, '.css');
|
|
406
|
+
} else if (shouldAddModule) {
|
|
407
|
+
// Add .module before .css
|
|
408
|
+
return filePath.replace(/\.less$/, '.module.css');
|
|
409
|
+
} else {
|
|
410
|
+
// Just change extension
|
|
411
|
+
return filePath.replace(/\.less$/, '.css');
|
|
412
|
+
}
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
/**
|
|
416
|
+
* Run the less-to-cssvar transformation
|
|
417
|
+
* @param {string} file - File or directory path
|
|
418
|
+
* @param {object} options - Transform options
|
|
419
|
+
* @param {string} options.prefix - CSS variable prefix (default: 'ant')
|
|
420
|
+
* @param {boolean} options.renameToCss - Whether to rename .less to .css (default: true)
|
|
421
|
+
* @param {boolean} options.addModule - Whether to add .module suffix when renaming (default: true)
|
|
422
|
+
* - true (default): Auto-detect based on import style (CSS Module import → .module.css, global import → .css)
|
|
423
|
+
* - false: Skip detection, never add .module suffix
|
|
424
|
+
*/
|
|
425
|
+
async function lessToCssvar(file, options = {}) {
|
|
426
|
+
const { prefix = 'ant', renameToCss = true, addModule = true } = options;
|
|
427
|
+
const allLessFiles = findAllLessFiles(file);
|
|
428
|
+
const renamedFiles = [];
|
|
429
|
+
const baseDir = isDirectory.sync(file) ? file : path.dirname(file);
|
|
430
|
+
|
|
431
|
+
for await (const item of allLessFiles) {
|
|
432
|
+
let { content, hasTransformations } = await transform(item, { prefix });
|
|
433
|
+
|
|
434
|
+
// If renaming to CSS, convert Less comments to CSS comments
|
|
435
|
+
if (renameToCss) {
|
|
436
|
+
const convertedContent = convertLessCommentsToCss(content);
|
|
437
|
+
if (convertedContent !== content) {
|
|
438
|
+
content = convertedContent;
|
|
439
|
+
hasTransformations = true;
|
|
440
|
+
}
|
|
441
|
+
}
|
|
442
|
+
|
|
443
|
+
fs.writeFileSync(item, content);
|
|
444
|
+
|
|
445
|
+
// Rename .less to .css if option is enabled
|
|
446
|
+
if (renameToCss && hasTransformations) {
|
|
447
|
+
// Determine whether to add .module suffix
|
|
448
|
+
let shouldAddModule;
|
|
449
|
+
if (addModule) {
|
|
450
|
+
// Auto-detect based on import style
|
|
451
|
+
shouldAddModule = await isImportedAsCssModule(item, baseDir);
|
|
452
|
+
} else {
|
|
453
|
+
// Skip detection, never add .module
|
|
454
|
+
shouldAddModule = false;
|
|
455
|
+
}
|
|
456
|
+
|
|
457
|
+
const newPath = getNewCssPath(item, shouldAddModule);
|
|
458
|
+
if (newPath !== item) {
|
|
459
|
+
fs.renameSync(item, newPath);
|
|
460
|
+
console.log(` Renamed: ${item} -> ${newPath}`);
|
|
461
|
+
renamedFiles.push({ oldPath: item, newPath });
|
|
462
|
+
}
|
|
463
|
+
}
|
|
464
|
+
}
|
|
465
|
+
|
|
466
|
+
// Update import references in JS/TS files
|
|
467
|
+
if (renameToCss && renamedFiles.length > 0) {
|
|
468
|
+
console.log(`\n Updating import references...`);
|
|
469
|
+
await updateImportReferences(baseDir, renamedFiles);
|
|
470
|
+
}
|
|
471
|
+
}
|
|
472
|
+
|
|
473
|
+
module.exports = {
|
|
474
|
+
transform,
|
|
475
|
+
lessToCssvar,
|
|
476
|
+
transformLessVarToCssVar,
|
|
477
|
+
convertLessCommentsToCss,
|
|
478
|
+
updateImportReferences,
|
|
479
|
+
getLessTokensFromTheme,
|
|
480
|
+
camelToKebab,
|
|
481
|
+
LESS_TOKENS,
|
|
482
|
+
};
|
|
@@ -36,18 +36,24 @@ const TOKEN_MAP = {
|
|
|
36
36
|
'#1890ff': 'colorInfo',
|
|
37
37
|
'#40a9ff': 'colorInfo',
|
|
38
38
|
'#006aff': 'colorInfo',
|
|
39
|
+
'#1843ff': 'colorInfo',
|
|
40
|
+
'#597ef7': 'colorInfo',
|
|
39
41
|
'#f7f9fb': 'colorInfoBg',
|
|
42
|
+
'#91a9f8': 'colorInfoBg',
|
|
40
43
|
'#e6f7ff': 'colorInfoBgHover',
|
|
41
44
|
'#f3f9ff': 'colorInfoBgHover',
|
|
42
45
|
'#e6f7ff': 'colorInfoBgHover',
|
|
43
46
|
'#73d13d': 'colorSuccess',
|
|
44
47
|
'#52c41a': 'colorSuccess',
|
|
45
48
|
'#faad14': 'colorWarning',
|
|
49
|
+
'#ffa940': 'colorWarning',
|
|
46
50
|
'#fef6e7': 'colorWarningBg',
|
|
51
|
+
'#fed59c': 'colorWarningBg',
|
|
47
52
|
'#ff4d4f': 'colorError',
|
|
48
53
|
'#f5222d': 'colorError',
|
|
49
54
|
'#f8636b': 'colorError',
|
|
50
55
|
'#f93939': 'colorError',
|
|
56
|
+
'#eb4444': 'colorError',
|
|
51
57
|
'#d9d9d9': 'colorBorder',
|
|
52
58
|
'#bfbfbf': 'colorBorder',
|
|
53
59
|
'#e8e8e8': 'colorBorder',
|
|
@@ -70,6 +76,7 @@ const TOKEN_MAP = {
|
|
|
70
76
|
'rgba(0,0,0,0.45)': 'colorTextTertiary',
|
|
71
77
|
'#5c6b8a': 'colorTextSecondary',
|
|
72
78
|
'#5C6B8A': 'colorTextSecondary',
|
|
79
|
+
'#ced5e3': '@colorTextPlaceholder',
|
|
73
80
|
'rgba(0,0,0,0.25)': 'colorTextQuaternary',
|
|
74
81
|
'rgba(0,0,0,.85)': 'colorText',
|
|
75
82
|
'rgba(0,0,0,.65)': 'colorTextSecondary',
|
|
@@ -173,6 +180,18 @@ const PROPERTY_TOKEN_MAP = {
|
|
|
173
180
|
15: 'fontSizeLG',
|
|
174
181
|
16: 'fontSizeLG',
|
|
175
182
|
},
|
|
183
|
+
fontWeight: {
|
|
184
|
+
300: 'fontWeightWeak',
|
|
185
|
+
400: 'fontWeight',
|
|
186
|
+
500: 'fontWeightStrong',
|
|
187
|
+
600: 'fontWeightStrong',
|
|
188
|
+
},
|
|
189
|
+
borderRadius: {
|
|
190
|
+
2: 'borderRadiusSM',
|
|
191
|
+
4: 'borderRadius',
|
|
192
|
+
6: 'borderRadiusMD',
|
|
193
|
+
8: 'borderRadiusLG',
|
|
194
|
+
},
|
|
176
195
|
};
|
|
177
196
|
|
|
178
197
|
function propertyTokenParse(propertyName, value) {
|