tailwind-unwind 0.1.1 → 0.2.0
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 +64 -1
- package/dist/{chunk-N7HD4T2I.js → chunk-FASYIEVZ.js} +583 -46
- package/dist/chunk-FASYIEVZ.js.map +1 -0
- package/dist/cli/index.js +171 -26
- package/dist/cli/index.js.map +1 -1
- package/dist/index.d.ts +66 -2
- package/dist/index.js +7 -1
- package/package.json +3 -2
- package/tailwind-unwind.config.example.json +24 -0
- package/dist/chunk-N7HD4T2I.js.map +0 -1
|
@@ -1,3 +1,332 @@
|
|
|
1
|
+
// src/config/validate.ts
|
|
2
|
+
var CLASS_NAME_PATTERN = /^[a-zA-Z][a-zA-Z0-9_-]*$/;
|
|
3
|
+
var KNOWN_ROOT_KEYS = /* @__PURE__ */ new Set([
|
|
4
|
+
"include",
|
|
5
|
+
"exclude",
|
|
6
|
+
"names",
|
|
7
|
+
"prefix",
|
|
8
|
+
"output",
|
|
9
|
+
"minOccurrences",
|
|
10
|
+
"minSize",
|
|
11
|
+
"maxSize",
|
|
12
|
+
"top",
|
|
13
|
+
"dedupeSubsets",
|
|
14
|
+
"dryRun",
|
|
15
|
+
"analyze",
|
|
16
|
+
"generate",
|
|
17
|
+
"apply"
|
|
18
|
+
]);
|
|
19
|
+
var KNOWN_COMMAND_KEYS = /* @__PURE__ */ new Set([
|
|
20
|
+
"minOccurrences",
|
|
21
|
+
"minSize",
|
|
22
|
+
"maxSize",
|
|
23
|
+
"top",
|
|
24
|
+
"prefix",
|
|
25
|
+
"output",
|
|
26
|
+
"dedupeSubsets",
|
|
27
|
+
"dryRun"
|
|
28
|
+
]);
|
|
29
|
+
function isRecord(value) {
|
|
30
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
31
|
+
}
|
|
32
|
+
function assertPositiveNumber(value, path6, errors) {
|
|
33
|
+
if (value === void 0) {
|
|
34
|
+
return;
|
|
35
|
+
}
|
|
36
|
+
if (typeof value !== "number" || !Number.isFinite(value) || value < 1) {
|
|
37
|
+
errors.push(`${path6} must be a positive number`);
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
function assertBoolean(value, path6, errors) {
|
|
41
|
+
if (value === void 0) {
|
|
42
|
+
return;
|
|
43
|
+
}
|
|
44
|
+
if (typeof value !== "boolean") {
|
|
45
|
+
errors.push(`${path6} must be a boolean`);
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
function assertStringArray(value, path6, errors) {
|
|
49
|
+
if (value === void 0) {
|
|
50
|
+
return;
|
|
51
|
+
}
|
|
52
|
+
if (!Array.isArray(value) || !value.every((item) => typeof item === "string" && item.length > 0)) {
|
|
53
|
+
errors.push(`${path6} must be an array of non-empty strings`);
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
function validateCommandSection(value, section, errors) {
|
|
57
|
+
if (value === void 0) {
|
|
58
|
+
return;
|
|
59
|
+
}
|
|
60
|
+
if (!isRecord(value)) {
|
|
61
|
+
errors.push(`${section} must be an object`);
|
|
62
|
+
return;
|
|
63
|
+
}
|
|
64
|
+
for (const key of Object.keys(value)) {
|
|
65
|
+
if (!KNOWN_COMMAND_KEYS.has(key)) {
|
|
66
|
+
errors.push(`Unknown key "${key}" in ${section}`);
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
assertPositiveNumber(value.minOccurrences, `${section}.minOccurrences`, errors);
|
|
70
|
+
assertPositiveNumber(value.minSize, `${section}.minSize`, errors);
|
|
71
|
+
assertPositiveNumber(value.maxSize, `${section}.maxSize`, errors);
|
|
72
|
+
assertPositiveNumber(value.top, `${section}.top`, errors);
|
|
73
|
+
assertBoolean(value.dedupeSubsets, `${section}.dedupeSubsets`, errors);
|
|
74
|
+
assertBoolean(value.dryRun, `${section}.dryRun`, errors);
|
|
75
|
+
if (value.prefix !== void 0 && (typeof value.prefix !== "string" || value.prefix.length === 0)) {
|
|
76
|
+
errors.push(`${section}.prefix must be a non-empty string`);
|
|
77
|
+
}
|
|
78
|
+
if (value.output !== void 0 && (typeof value.output !== "string" || value.output.length === 0)) {
|
|
79
|
+
errors.push(`${section}.output must be a non-empty string`);
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
function validateNames(value, errors) {
|
|
83
|
+
if (value === void 0) {
|
|
84
|
+
return;
|
|
85
|
+
}
|
|
86
|
+
if (!isRecord(value)) {
|
|
87
|
+
errors.push('names must be an object of "utility string": "class-name" pairs');
|
|
88
|
+
return;
|
|
89
|
+
}
|
|
90
|
+
for (const [utilities, name] of Object.entries(value)) {
|
|
91
|
+
if (typeof utilities !== "string" || utilities.trim().length === 0) {
|
|
92
|
+
errors.push("names keys must be non-empty utility strings");
|
|
93
|
+
continue;
|
|
94
|
+
}
|
|
95
|
+
if (typeof name !== "string" || !CLASS_NAME_PATTERN.test(name)) {
|
|
96
|
+
errors.push(
|
|
97
|
+
`names["${utilities}"] must be a valid class name (letters, numbers, hyphens)`
|
|
98
|
+
);
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
function validateConfigFile(raw, configPath) {
|
|
103
|
+
if (!isRecord(raw)) {
|
|
104
|
+
throw new Error(`Invalid config in ${configPath}: root value must be an object`);
|
|
105
|
+
}
|
|
106
|
+
const source = isRecord(raw.default) ? raw.default : raw;
|
|
107
|
+
const errors = [];
|
|
108
|
+
for (const key of Object.keys(source)) {
|
|
109
|
+
if (!KNOWN_ROOT_KEYS.has(key)) {
|
|
110
|
+
errors.push(`Unknown config key "${key}"`);
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
assertStringArray(source.include, "include", errors);
|
|
114
|
+
assertStringArray(source.exclude, "exclude", errors);
|
|
115
|
+
assertPositiveNumber(source.minOccurrences, "minOccurrences", errors);
|
|
116
|
+
assertPositiveNumber(source.minSize, "minSize", errors);
|
|
117
|
+
assertPositiveNumber(source.maxSize, "maxSize", errors);
|
|
118
|
+
assertPositiveNumber(source.top, "top", errors);
|
|
119
|
+
assertBoolean(source.dedupeSubsets, "dedupeSubsets", errors);
|
|
120
|
+
assertBoolean(source.dryRun, "dryRun", errors);
|
|
121
|
+
validateNames(source.names, errors);
|
|
122
|
+
validateCommandSection(source.analyze, "analyze", errors);
|
|
123
|
+
validateCommandSection(source.generate, "generate", errors);
|
|
124
|
+
validateCommandSection(source.apply, "apply", errors);
|
|
125
|
+
if (source.prefix !== void 0 && (typeof source.prefix !== "string" || source.prefix.length === 0)) {
|
|
126
|
+
errors.push("prefix must be a non-empty string");
|
|
127
|
+
}
|
|
128
|
+
if (source.output !== void 0 && (typeof source.output !== "string" || source.output.length === 0)) {
|
|
129
|
+
errors.push("output must be a non-empty string");
|
|
130
|
+
}
|
|
131
|
+
if (errors.length > 0) {
|
|
132
|
+
throw new Error(
|
|
133
|
+
`Invalid config in ${configPath}:
|
|
134
|
+
${errors.map((error) => ` - ${error}`).join("\n")}`
|
|
135
|
+
);
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
function normalizeNamesConfig(names) {
|
|
139
|
+
const map = /* @__PURE__ */ new Map();
|
|
140
|
+
if (!names) {
|
|
141
|
+
return map;
|
|
142
|
+
}
|
|
143
|
+
for (const [utilities, baseName] of Object.entries(names)) {
|
|
144
|
+
const tokens = utilities.trim().split(/\s+/).filter((token) => token.length > 0);
|
|
145
|
+
const key = [...tokens].sort().join(" ");
|
|
146
|
+
map.set(key, baseName);
|
|
147
|
+
}
|
|
148
|
+
return map;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
// src/config/loadConfig.ts
|
|
152
|
+
import fs from "fs/promises";
|
|
153
|
+
import path from "path";
|
|
154
|
+
import { pathToFileURL } from "url";
|
|
155
|
+
var CONFIG_FILENAMES = [
|
|
156
|
+
"tailwind-unwind.config.js",
|
|
157
|
+
"tailwind-unwind.config.mjs",
|
|
158
|
+
"tailwind-unwind.config.cjs",
|
|
159
|
+
"tailwind-unwind.config.json",
|
|
160
|
+
".tailwind-unwindrc",
|
|
161
|
+
".tailwind-unwindrc.json"
|
|
162
|
+
];
|
|
163
|
+
function isRecord2(value) {
|
|
164
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
165
|
+
}
|
|
166
|
+
function pickNumber(source, key) {
|
|
167
|
+
const value = source[key];
|
|
168
|
+
return typeof value === "number" && Number.isFinite(value) ? value : void 0;
|
|
169
|
+
}
|
|
170
|
+
function pickCommandConfig(source) {
|
|
171
|
+
const config = {};
|
|
172
|
+
const minOccurrences = pickNumber(source, "minOccurrences");
|
|
173
|
+
if (minOccurrences !== void 0) config.minOccurrences = minOccurrences;
|
|
174
|
+
const minSize = pickNumber(source, "minSize");
|
|
175
|
+
if (minSize !== void 0) config.minSize = minSize;
|
|
176
|
+
const maxSize = pickNumber(source, "maxSize");
|
|
177
|
+
if (maxSize !== void 0) config.maxSize = maxSize;
|
|
178
|
+
const top = pickNumber(source, "top");
|
|
179
|
+
if (top !== void 0) config.top = top;
|
|
180
|
+
if (typeof source.prefix === "string" && source.prefix.length > 0) {
|
|
181
|
+
config.prefix = source.prefix;
|
|
182
|
+
}
|
|
183
|
+
if (typeof source.output === "string" && source.output.length > 0) {
|
|
184
|
+
config.output = source.output;
|
|
185
|
+
}
|
|
186
|
+
if (typeof source.dedupeSubsets === "boolean") {
|
|
187
|
+
config.dedupeSubsets = source.dedupeSubsets;
|
|
188
|
+
}
|
|
189
|
+
if (typeof source.dryRun === "boolean") {
|
|
190
|
+
config.dryRun = source.dryRun;
|
|
191
|
+
}
|
|
192
|
+
return config;
|
|
193
|
+
}
|
|
194
|
+
function pickNames(source) {
|
|
195
|
+
if (!isRecord2(source.names)) {
|
|
196
|
+
return void 0;
|
|
197
|
+
}
|
|
198
|
+
const names = {};
|
|
199
|
+
for (const [utilities, name] of Object.entries(source.names)) {
|
|
200
|
+
if (typeof utilities === "string" && typeof name === "string") {
|
|
201
|
+
names[utilities] = name;
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
return Object.keys(names).length > 0 ? names : void 0;
|
|
205
|
+
}
|
|
206
|
+
function normalizeLoadedConfig(raw) {
|
|
207
|
+
if (!isRecord2(raw)) {
|
|
208
|
+
return {};
|
|
209
|
+
}
|
|
210
|
+
const source = isRecord2(raw.default) ? raw.default : raw;
|
|
211
|
+
const config = {
|
|
212
|
+
...pickCommandConfig(source)
|
|
213
|
+
};
|
|
214
|
+
const names = pickNames(source);
|
|
215
|
+
if (names) {
|
|
216
|
+
config.names = names;
|
|
217
|
+
}
|
|
218
|
+
if (Array.isArray(source.include)) {
|
|
219
|
+
config.include = source.include.filter(
|
|
220
|
+
(item) => typeof item === "string" && item.length > 0
|
|
221
|
+
);
|
|
222
|
+
}
|
|
223
|
+
if (Array.isArray(source.exclude)) {
|
|
224
|
+
config.exclude = source.exclude.filter(
|
|
225
|
+
(item) => typeof item === "string" && item.length > 0
|
|
226
|
+
);
|
|
227
|
+
}
|
|
228
|
+
if (isRecord2(source.analyze)) {
|
|
229
|
+
config.analyze = pickCommandConfig(source.analyze);
|
|
230
|
+
}
|
|
231
|
+
if (isRecord2(source.generate)) {
|
|
232
|
+
config.generate = pickCommandConfig(source.generate);
|
|
233
|
+
}
|
|
234
|
+
if (isRecord2(source.apply)) {
|
|
235
|
+
config.apply = pickCommandConfig(source.apply);
|
|
236
|
+
}
|
|
237
|
+
return config;
|
|
238
|
+
}
|
|
239
|
+
function mergeCommandConfig(command, fileConfig) {
|
|
240
|
+
const { analyze, generate: generate2, apply, ...root } = fileConfig;
|
|
241
|
+
const commandSection = command === "analyze" ? analyze : command === "generate" ? generate2 : apply;
|
|
242
|
+
return {
|
|
243
|
+
...root,
|
|
244
|
+
...commandSection
|
|
245
|
+
};
|
|
246
|
+
}
|
|
247
|
+
async function pathExists(targetPath) {
|
|
248
|
+
try {
|
|
249
|
+
await fs.access(targetPath);
|
|
250
|
+
return true;
|
|
251
|
+
} catch {
|
|
252
|
+
return false;
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
async function collectSearchRoots(cwd, targetPath) {
|
|
256
|
+
const roots = [path.resolve(cwd)];
|
|
257
|
+
if (!targetPath) {
|
|
258
|
+
return roots;
|
|
259
|
+
}
|
|
260
|
+
let current = path.resolve(targetPath);
|
|
261
|
+
try {
|
|
262
|
+
const stat = await fs.stat(current);
|
|
263
|
+
if (stat.isFile()) {
|
|
264
|
+
current = path.dirname(current);
|
|
265
|
+
}
|
|
266
|
+
} catch {
|
|
267
|
+
return roots;
|
|
268
|
+
}
|
|
269
|
+
while (true) {
|
|
270
|
+
const resolved = path.resolve(current);
|
|
271
|
+
if (!roots.includes(resolved)) {
|
|
272
|
+
roots.push(resolved);
|
|
273
|
+
}
|
|
274
|
+
const parent = path.dirname(resolved);
|
|
275
|
+
if (parent === resolved) {
|
|
276
|
+
break;
|
|
277
|
+
}
|
|
278
|
+
current = parent;
|
|
279
|
+
}
|
|
280
|
+
return roots;
|
|
281
|
+
}
|
|
282
|
+
async function resolveConfigFile(explicitPath, searchRoots) {
|
|
283
|
+
if (explicitPath) {
|
|
284
|
+
const resolved = path.resolve(explicitPath);
|
|
285
|
+
if (!await pathExists(resolved)) {
|
|
286
|
+
throw new Error(`Config file not found: ${resolved}`);
|
|
287
|
+
}
|
|
288
|
+
return resolved;
|
|
289
|
+
}
|
|
290
|
+
for (const root of searchRoots) {
|
|
291
|
+
for (const filename of CONFIG_FILENAMES) {
|
|
292
|
+
const candidate = path.join(root, filename);
|
|
293
|
+
if (await pathExists(candidate)) {
|
|
294
|
+
return candidate;
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
return null;
|
|
299
|
+
}
|
|
300
|
+
async function importConfigModule(configPath) {
|
|
301
|
+
const moduleUrl = pathToFileURL(configPath).href;
|
|
302
|
+
const imported = await import(moduleUrl);
|
|
303
|
+
return imported;
|
|
304
|
+
}
|
|
305
|
+
async function readJsonConfig(configPath) {
|
|
306
|
+
const raw = await fs.readFile(configPath, "utf-8");
|
|
307
|
+
return JSON.parse(raw);
|
|
308
|
+
}
|
|
309
|
+
async function loadCommandOptions(command, cliOptions, options = {}) {
|
|
310
|
+
const cwd = options.cwd ?? process.cwd();
|
|
311
|
+
const searchRoots = await collectSearchRoots(cwd, options.targetPath);
|
|
312
|
+
const configPath = await resolveConfigFile(cliOptions.configPath, searchRoots);
|
|
313
|
+
if (!configPath) {
|
|
314
|
+
return cliOptions;
|
|
315
|
+
}
|
|
316
|
+
const isJson = configPath.endsWith(".json") || configPath.endsWith(".tailwind-unwindrc");
|
|
317
|
+
const loaded = isJson ? await readJsonConfig(configPath) : await importConfigModule(configPath);
|
|
318
|
+
validateConfigFile(loaded, configPath);
|
|
319
|
+
const fileConfig = mergeCommandConfig(command, normalizeLoadedConfig(loaded));
|
|
320
|
+
const { configPath: _ignored, ...cliOverrides } = cliOptions;
|
|
321
|
+
return {
|
|
322
|
+
...fileConfig,
|
|
323
|
+
...Object.fromEntries(
|
|
324
|
+
Object.entries(cliOverrides).filter(([, value]) => value !== void 0)
|
|
325
|
+
),
|
|
326
|
+
configPath
|
|
327
|
+
};
|
|
328
|
+
}
|
|
329
|
+
|
|
1
330
|
// src/analyzer/suggestions.ts
|
|
2
331
|
var BREAKPOINT_PREFIX = /^(sm|md|lg|xl|2xl):/;
|
|
3
332
|
function baseClass(cls) {
|
|
@@ -793,7 +1122,7 @@ function parseSourceToAst(source) {
|
|
|
793
1122
|
|
|
794
1123
|
// src/parser/jsxParser.ts
|
|
795
1124
|
import babelTraverse from "@babel/traverse";
|
|
796
|
-
import
|
|
1125
|
+
import fs2 from "fs/promises";
|
|
797
1126
|
function resolveTraverse(module) {
|
|
798
1127
|
if (typeof module === "function") {
|
|
799
1128
|
return module;
|
|
@@ -805,8 +1134,8 @@ function resolveTraverse(module) {
|
|
|
805
1134
|
throw new Error("Failed to load @babel/traverse");
|
|
806
1135
|
}
|
|
807
1136
|
var traverse = resolveTraverse(babelTraverse);
|
|
808
|
-
function isJSXElementWithClassAttribute(
|
|
809
|
-
const opening =
|
|
1137
|
+
function isJSXElementWithClassAttribute(path6) {
|
|
1138
|
+
const opening = path6.node.openingElement;
|
|
810
1139
|
return opening.attributes.some(
|
|
811
1140
|
(attr) => attr.type === "JSXAttribute" && isClassAttribute(attr)
|
|
812
1141
|
);
|
|
@@ -815,11 +1144,11 @@ function collectExtractionsFromAst(ast, filePath) {
|
|
|
815
1144
|
const extractions = [];
|
|
816
1145
|
const warnings = [];
|
|
817
1146
|
traverse(ast, {
|
|
818
|
-
JSXElement(
|
|
819
|
-
if (!isJSXElementWithClassAttribute(
|
|
1147
|
+
JSXElement(path6) {
|
|
1148
|
+
if (!isJSXElementWithClassAttribute(path6)) {
|
|
820
1149
|
return;
|
|
821
1150
|
}
|
|
822
|
-
const opening =
|
|
1151
|
+
const opening = path6.node.openingElement;
|
|
823
1152
|
for (const attr of opening.attributes) {
|
|
824
1153
|
if (attr.type !== "JSXAttribute") continue;
|
|
825
1154
|
const extraction = extractFromJSXAttribute(attr);
|
|
@@ -854,7 +1183,7 @@ function parseSource(source, filePath = "unknown") {
|
|
|
854
1183
|
return { filePath, extractions, warnings };
|
|
855
1184
|
}
|
|
856
1185
|
async function parseFile(filePath) {
|
|
857
|
-
const source = await
|
|
1186
|
+
const source = await fs2.readFile(filePath, "utf-8");
|
|
858
1187
|
return parseSource(source, filePath);
|
|
859
1188
|
}
|
|
860
1189
|
|
|
@@ -872,40 +1201,67 @@ var IGNORE_PATTERNS = IGNORED_DIRECTORIES.map(
|
|
|
872
1201
|
|
|
873
1202
|
// src/scanner/fileWalker.ts
|
|
874
1203
|
import fg from "fast-glob";
|
|
875
|
-
import
|
|
1204
|
+
import path2 from "path";
|
|
876
1205
|
var SOURCE_EXTENSIONS = ["tsx", "jsx", "ts", "js"];
|
|
877
|
-
|
|
878
|
-
const
|
|
879
|
-
|
|
880
|
-
|
|
1206
|
+
function toAbsolutePattern(basePath, pattern) {
|
|
1207
|
+
const normalized = pattern.replace(/\\/g, "/");
|
|
1208
|
+
if (path2.isAbsolute(normalized)) {
|
|
1209
|
+
return normalized;
|
|
1210
|
+
}
|
|
1211
|
+
return path2.join(basePath, normalized).replace(/\\/g, "/");
|
|
1212
|
+
}
|
|
1213
|
+
function buildIncludePatterns(basePath, include) {
|
|
1214
|
+
if (include && include.length > 0) {
|
|
1215
|
+
return include.map((pattern) => toAbsolutePattern(basePath, pattern));
|
|
1216
|
+
}
|
|
1217
|
+
return SOURCE_EXTENSIONS.map(
|
|
1218
|
+
(ext) => path2.join(basePath, `**/*.${ext}`).replace(/\\/g, "/")
|
|
881
1219
|
);
|
|
1220
|
+
}
|
|
1221
|
+
function buildIgnorePatterns(exclude) {
|
|
1222
|
+
const userExcludes = (exclude ?? []).map((pattern) => {
|
|
1223
|
+
const normalized = pattern.replace(/\\/g, "/");
|
|
1224
|
+
if (normalized.startsWith("**")) {
|
|
1225
|
+
return normalized;
|
|
1226
|
+
}
|
|
1227
|
+
return `**/${normalized}`;
|
|
1228
|
+
});
|
|
1229
|
+
return [...IGNORE_PATTERNS, ...userExcludes];
|
|
1230
|
+
}
|
|
1231
|
+
async function walkSourceFiles(targetPath, options = {}) {
|
|
1232
|
+
const absolutePath = path2.resolve(targetPath);
|
|
1233
|
+
const patterns = buildIncludePatterns(absolutePath, options.include);
|
|
1234
|
+
const ignore = buildIgnorePatterns(options.exclude);
|
|
882
1235
|
const files = await fg(patterns, {
|
|
883
1236
|
absolute: true,
|
|
884
1237
|
onlyFiles: true,
|
|
885
1238
|
unique: true,
|
|
886
|
-
ignore
|
|
1239
|
+
ignore,
|
|
887
1240
|
dot: false
|
|
888
1241
|
});
|
|
889
1242
|
return files.sort();
|
|
890
1243
|
}
|
|
891
1244
|
|
|
892
1245
|
// src/core/scanProject.ts
|
|
893
|
-
import
|
|
894
|
-
import
|
|
895
|
-
async function
|
|
1246
|
+
import fs3 from "fs/promises";
|
|
1247
|
+
import path3 from "path";
|
|
1248
|
+
async function pathExists2(targetPath) {
|
|
896
1249
|
try {
|
|
897
|
-
await
|
|
1250
|
+
await fs3.access(targetPath);
|
|
898
1251
|
return true;
|
|
899
1252
|
} catch {
|
|
900
1253
|
return false;
|
|
901
1254
|
}
|
|
902
1255
|
}
|
|
903
1256
|
async function scanProject(options) {
|
|
904
|
-
const resolvedPath =
|
|
905
|
-
if (!await
|
|
1257
|
+
const resolvedPath = path3.resolve(options.targetPath);
|
|
1258
|
+
if (!await pathExists2(resolvedPath)) {
|
|
906
1259
|
throw new Error(`Path does not exist: ${resolvedPath}`);
|
|
907
1260
|
}
|
|
908
|
-
const files = await walkSourceFiles(resolvedPath
|
|
1261
|
+
const files = await walkSourceFiles(resolvedPath, {
|
|
1262
|
+
include: options.include,
|
|
1263
|
+
exclude: options.exclude
|
|
1264
|
+
});
|
|
909
1265
|
if (files.length === 0) {
|
|
910
1266
|
throw new Error(
|
|
911
1267
|
`No source files (.tsx, .jsx, .ts, .js) found in: ${resolvedPath}`
|
|
@@ -931,13 +1287,26 @@ async function scanProject(options) {
|
|
|
931
1287
|
(occurrence) => normalizeClasses([...new Set(occurrence.classes)])
|
|
932
1288
|
)
|
|
933
1289
|
);
|
|
934
|
-
const
|
|
1290
|
+
const frequentCombinations = findFrequentPatterns(occurrences, {
|
|
935
1291
|
minOccurrences: options.minOccurrences,
|
|
936
1292
|
minSize: options.minSize,
|
|
937
1293
|
maxSize: options.maxSize,
|
|
938
1294
|
topLimit: options.topLimit,
|
|
939
1295
|
dedupeSubsets: options.dedupeSubsets
|
|
940
1296
|
});
|
|
1297
|
+
const extractableSets = findRepeatedClassSets(occurrences, {
|
|
1298
|
+
minOccurrences: options.extractableMinOccurrences ?? 3,
|
|
1299
|
+
minSize: options.minSize,
|
|
1300
|
+
maxSize: options.maxSize,
|
|
1301
|
+
topLimit: Number.POSITIVE_INFINITY
|
|
1302
|
+
});
|
|
1303
|
+
const extractableKeys = new Set(
|
|
1304
|
+
extractableSets.map((combo) => combo.normalized)
|
|
1305
|
+
);
|
|
1306
|
+
const topCombinations = frequentCombinations.map((combo) => ({
|
|
1307
|
+
...combo,
|
|
1308
|
+
extractable: extractableKeys.has(combo.normalized)
|
|
1309
|
+
}));
|
|
941
1310
|
const potentialReductionPercent = calculatePotentialReduction(
|
|
942
1311
|
occurrences,
|
|
943
1312
|
topCombinations
|
|
@@ -1015,6 +1384,15 @@ function printConsoleReport(report, options = {}) {
|
|
|
1015
1384
|
console.log(
|
|
1016
1385
|
chalk.gray(` Suggestion: `) + chalk.green(combo.suggestion)
|
|
1017
1386
|
);
|
|
1387
|
+
if (combo.extractable) {
|
|
1388
|
+
console.log(
|
|
1389
|
+
chalk.gray(` Extractable: `) + chalk.green("yes \u2014 use generate/apply")
|
|
1390
|
+
);
|
|
1391
|
+
} else {
|
|
1392
|
+
console.log(
|
|
1393
|
+
chalk.gray(` Extractable: `) + chalk.yellow("subset only \u2014 analyze hint")
|
|
1394
|
+
);
|
|
1395
|
+
}
|
|
1018
1396
|
console.log(
|
|
1019
1397
|
chalk.gray(` Found in: `) + chalk.dim(formatLocations(combo.locations))
|
|
1020
1398
|
);
|
|
@@ -1026,6 +1404,16 @@ function printConsoleReport(report, options = {}) {
|
|
|
1026
1404
|
`\u{1F4A1} Potential code reduction: ${stats.potentialReductionPercent}%`
|
|
1027
1405
|
)
|
|
1028
1406
|
);
|
|
1407
|
+
const extractableCount = stats.topCombinations.filter(
|
|
1408
|
+
(combo) => combo.extractable
|
|
1409
|
+
).length;
|
|
1410
|
+
if (extractableCount > 0) {
|
|
1411
|
+
console.log(
|
|
1412
|
+
chalk.magenta(
|
|
1413
|
+
`\u{1F4A1} ${extractableCount} pattern(s) ready for generate/apply`
|
|
1414
|
+
)
|
|
1415
|
+
);
|
|
1416
|
+
}
|
|
1029
1417
|
console.log(
|
|
1030
1418
|
chalk.magenta(
|
|
1031
1419
|
"\u{1F4A1} Generate CSS: npx tailwind-unwind generate <path> --output styles.css"
|
|
@@ -1055,7 +1443,10 @@ async function analyzeCommand(targetPath, options = {}) {
|
|
|
1055
1443
|
minSize: options.minSize,
|
|
1056
1444
|
maxSize: options.maxSize,
|
|
1057
1445
|
topLimit: options.top,
|
|
1058
|
-
dedupeSubsets: options.dedupeSubsets
|
|
1446
|
+
dedupeSubsets: options.dedupeSubsets,
|
|
1447
|
+
include: options.include,
|
|
1448
|
+
exclude: options.exclude,
|
|
1449
|
+
extractableMinOccurrences: 3
|
|
1059
1450
|
});
|
|
1060
1451
|
} catch (error) {
|
|
1061
1452
|
const message = error instanceof Error ? error.message : String(error);
|
|
@@ -1102,6 +1493,18 @@ function resolveGenerator(module) {
|
|
|
1102
1493
|
}
|
|
1103
1494
|
var traverse2 = resolveTraverse2(babelTraverse2);
|
|
1104
1495
|
var generate = resolveGenerator(babelGenerate);
|
|
1496
|
+
function isClassMergeCallee2(expression) {
|
|
1497
|
+
if (expression.type === "Identifier") {
|
|
1498
|
+
return CLASS_MERGE_CALLEES.has(expression.name);
|
|
1499
|
+
}
|
|
1500
|
+
if (expression.type === "MemberExpression" && expression.property.type === "Identifier") {
|
|
1501
|
+
return CLASS_MERGE_CALLEES.has(expression.property.name);
|
|
1502
|
+
}
|
|
1503
|
+
return false;
|
|
1504
|
+
}
|
|
1505
|
+
function isClassMergeCall(expression) {
|
|
1506
|
+
return expression.type === "CallExpression" && expression.callee.type !== "V8IntrinsicIdentifier" && isClassMergeCallee2(expression.callee);
|
|
1507
|
+
}
|
|
1105
1508
|
function lookupReplacement(extraction, replacementMap) {
|
|
1106
1509
|
if (extraction.isDynamic || extraction.classes.length === 0) {
|
|
1107
1510
|
return null;
|
|
@@ -1112,6 +1515,88 @@ function lookupReplacement(extraction, replacementMap) {
|
|
|
1112
1515
|
function setStringClassAttribute(attr, className) {
|
|
1113
1516
|
attr.value = t.stringLiteral(className);
|
|
1114
1517
|
}
|
|
1518
|
+
function tryReplaceMergeCall(call, replacementMap) {
|
|
1519
|
+
const staticClasses = [];
|
|
1520
|
+
const dynamicArgs = [];
|
|
1521
|
+
for (const arg of call.arguments) {
|
|
1522
|
+
if (arg.type === "SpreadElement" || arg.type === "ArgumentPlaceholder") {
|
|
1523
|
+
dynamicArgs.push(arg);
|
|
1524
|
+
continue;
|
|
1525
|
+
}
|
|
1526
|
+
const extracted = extractClassesFromExpression(arg);
|
|
1527
|
+
if (extracted.isDynamic) {
|
|
1528
|
+
dynamicArgs.push(arg);
|
|
1529
|
+
continue;
|
|
1530
|
+
}
|
|
1531
|
+
staticClasses.push(...extracted.classes);
|
|
1532
|
+
}
|
|
1533
|
+
const combinedKey = normalizeClasses([...new Set(staticClasses)]);
|
|
1534
|
+
const combinedReplacement = replacementMap.get(combinedKey);
|
|
1535
|
+
if (combinedReplacement && staticClasses.length > 0) {
|
|
1536
|
+
if (dynamicArgs.length === 0) {
|
|
1537
|
+
return {
|
|
1538
|
+
expression: t.stringLiteral(combinedReplacement),
|
|
1539
|
+
from: combinedKey,
|
|
1540
|
+
to: combinedReplacement,
|
|
1541
|
+
partial: false
|
|
1542
|
+
};
|
|
1543
|
+
}
|
|
1544
|
+
return {
|
|
1545
|
+
expression: t.callExpression(call.callee, [
|
|
1546
|
+
t.stringLiteral(combinedReplacement),
|
|
1547
|
+
...dynamicArgs
|
|
1548
|
+
]),
|
|
1549
|
+
from: combinedKey,
|
|
1550
|
+
to: combinedReplacement,
|
|
1551
|
+
partial: true
|
|
1552
|
+
};
|
|
1553
|
+
}
|
|
1554
|
+
let replaced = false;
|
|
1555
|
+
let replacedTo = "";
|
|
1556
|
+
let replacedFrom = "";
|
|
1557
|
+
const newArgs = [...call.arguments];
|
|
1558
|
+
for (let index = 0; index < call.arguments.length; index += 1) {
|
|
1559
|
+
const arg = call.arguments[index];
|
|
1560
|
+
if (arg === void 0 || arg.type === "SpreadElement" || arg.type === "ArgumentPlaceholder") {
|
|
1561
|
+
continue;
|
|
1562
|
+
}
|
|
1563
|
+
const extracted = extractClassesFromExpression(arg);
|
|
1564
|
+
if (extracted.isDynamic || extracted.classes.length === 0) {
|
|
1565
|
+
continue;
|
|
1566
|
+
}
|
|
1567
|
+
const argKey = normalizeClasses(extracted.classes);
|
|
1568
|
+
const replacement = replacementMap.get(argKey);
|
|
1569
|
+
if (!replacement) {
|
|
1570
|
+
continue;
|
|
1571
|
+
}
|
|
1572
|
+
newArgs[index] = t.stringLiteral(replacement);
|
|
1573
|
+
if (!replaced) {
|
|
1574
|
+
replacedFrom = argKey;
|
|
1575
|
+
replacedTo = replacement;
|
|
1576
|
+
}
|
|
1577
|
+
replaced = true;
|
|
1578
|
+
}
|
|
1579
|
+
if (!replaced) {
|
|
1580
|
+
return null;
|
|
1581
|
+
}
|
|
1582
|
+
return {
|
|
1583
|
+
expression: t.callExpression(call.callee, newArgs),
|
|
1584
|
+
from: replacedFrom,
|
|
1585
|
+
to: replacedTo,
|
|
1586
|
+
partial: true
|
|
1587
|
+
};
|
|
1588
|
+
}
|
|
1589
|
+
function tryReplaceClassAttribute(attr, replacementMap) {
|
|
1590
|
+
const value = attr.value;
|
|
1591
|
+
if (value?.type !== "JSXExpressionContainer") {
|
|
1592
|
+
return null;
|
|
1593
|
+
}
|
|
1594
|
+
const expression = value.expression;
|
|
1595
|
+
if (expression.type === "JSXEmptyExpression" || !isClassMergeCall(expression)) {
|
|
1596
|
+
return null;
|
|
1597
|
+
}
|
|
1598
|
+
return tryReplaceMergeCall(expression, replacementMap);
|
|
1599
|
+
}
|
|
1115
1600
|
function replaceClassNamesInSource(source, replacementMap, filePath) {
|
|
1116
1601
|
const replacements = [];
|
|
1117
1602
|
const skipped = [];
|
|
@@ -1126,14 +1611,30 @@ function replaceClassNamesInSource(source, replacementMap, filePath) {
|
|
|
1126
1611
|
throw new Error(`Failed to parse ${filePath}: ${message}`);
|
|
1127
1612
|
}
|
|
1128
1613
|
traverse2(ast, {
|
|
1129
|
-
JSXElement(
|
|
1130
|
-
const opening =
|
|
1614
|
+
JSXElement(path6) {
|
|
1615
|
+
const opening = path6.node.openingElement;
|
|
1131
1616
|
for (const attr of opening.attributes) {
|
|
1132
1617
|
if (attr.type !== "JSXAttribute" || !isClassAttribute(attr)) {
|
|
1133
1618
|
continue;
|
|
1134
1619
|
}
|
|
1135
1620
|
const extraction = extractFromJSXAttribute(attr);
|
|
1136
1621
|
if (!extraction) continue;
|
|
1622
|
+
const mergeReplacement = tryReplaceClassAttribute(attr, replacementMap);
|
|
1623
|
+
if (mergeReplacement) {
|
|
1624
|
+
if (mergeReplacement.expression.type === "StringLiteral") {
|
|
1625
|
+
setStringClassAttribute(attr, mergeReplacement.expression.value);
|
|
1626
|
+
} else {
|
|
1627
|
+
attr.value = t.jsxExpressionContainer(mergeReplacement.expression);
|
|
1628
|
+
}
|
|
1629
|
+
replacements.push({
|
|
1630
|
+
filePath,
|
|
1631
|
+
line: extraction.line,
|
|
1632
|
+
from: mergeReplacement.from,
|
|
1633
|
+
to: mergeReplacement.to,
|
|
1634
|
+
partial: mergeReplacement.partial
|
|
1635
|
+
});
|
|
1636
|
+
continue;
|
|
1637
|
+
}
|
|
1137
1638
|
const replacement = lookupReplacement(extraction, replacementMap);
|
|
1138
1639
|
if (!replacement) {
|
|
1139
1640
|
if (extraction.classes.length > 0) {
|
|
@@ -1192,10 +1693,15 @@ function withClassPrefix(baseName, prefix) {
|
|
|
1192
1693
|
}
|
|
1193
1694
|
|
|
1194
1695
|
// src/generator/cssGenerator.ts
|
|
1696
|
+
function resolveBaseClassName(classes, customNames) {
|
|
1697
|
+
const key = normalizeClasses(classes);
|
|
1698
|
+
return customNames.get(key) ?? suggestClassName(classes);
|
|
1699
|
+
}
|
|
1195
1700
|
function assignComponentClassNames(combinations, options = {}) {
|
|
1196
1701
|
const used = /* @__PURE__ */ new Set();
|
|
1702
|
+
const customNames = normalizeNamesConfig(options.names);
|
|
1197
1703
|
return combinations.map((combo) => {
|
|
1198
|
-
const base =
|
|
1704
|
+
const base = resolveBaseClassName(combo.classes, customNames);
|
|
1199
1705
|
let className = withClassPrefix(base, options.prefix);
|
|
1200
1706
|
let suffix = 2;
|
|
1201
1707
|
while (used.has(className)) {
|
|
@@ -1212,7 +1718,8 @@ function assignComponentClassNames(combinations, options = {}) {
|
|
|
1212
1718
|
}
|
|
1213
1719
|
function generateComponentCss(options) {
|
|
1214
1720
|
const components = assignComponentClassNames(options.combinations, {
|
|
1215
|
-
prefix: options.prefix
|
|
1721
|
+
prefix: options.prefix,
|
|
1722
|
+
names: options.names
|
|
1216
1723
|
});
|
|
1217
1724
|
const timestamp = (/* @__PURE__ */ new Date()).toISOString();
|
|
1218
1725
|
const classPrefix = normalizeClassPrefix(options.prefix);
|
|
@@ -1258,7 +1765,8 @@ function buildComponents(occurrences, options) {
|
|
|
1258
1765
|
const { css, components } = generateComponentCss({
|
|
1259
1766
|
sourcePath: options.sourcePath,
|
|
1260
1767
|
combinations,
|
|
1261
|
-
prefix: options.prefix
|
|
1768
|
+
prefix: options.prefix,
|
|
1769
|
+
names: options.names
|
|
1262
1770
|
});
|
|
1263
1771
|
const replacementMap = /* @__PURE__ */ new Map();
|
|
1264
1772
|
for (const component of components) {
|
|
@@ -1269,13 +1777,17 @@ function buildComponents(occurrences, options) {
|
|
|
1269
1777
|
}
|
|
1270
1778
|
|
|
1271
1779
|
// src/commands/apply.ts
|
|
1272
|
-
import
|
|
1273
|
-
import
|
|
1780
|
+
import fs4 from "fs/promises";
|
|
1781
|
+
import path4 from "path";
|
|
1274
1782
|
import chalk3 from "chalk";
|
|
1275
1783
|
async function applyCommand(targetPath, options) {
|
|
1276
1784
|
let scanResult;
|
|
1277
1785
|
try {
|
|
1278
|
-
scanResult = await scanProject({
|
|
1786
|
+
scanResult = await scanProject({
|
|
1787
|
+
targetPath,
|
|
1788
|
+
include: options.include,
|
|
1789
|
+
exclude: options.exclude
|
|
1790
|
+
});
|
|
1279
1791
|
} catch (error) {
|
|
1280
1792
|
const message = error instanceof Error ? error.message : String(error);
|
|
1281
1793
|
console.error(chalk3.red(`Error: ${message}`));
|
|
@@ -1292,7 +1804,8 @@ async function applyCommand(targetPath, options) {
|
|
|
1292
1804
|
minSize: options.minSize,
|
|
1293
1805
|
maxSize: options.maxSize,
|
|
1294
1806
|
topLimit: options.top,
|
|
1295
|
-
prefix: options.prefix
|
|
1807
|
+
prefix: options.prefix,
|
|
1808
|
+
names: options.names
|
|
1296
1809
|
}
|
|
1297
1810
|
);
|
|
1298
1811
|
if (components.length === 0) {
|
|
@@ -1303,12 +1816,13 @@ async function applyCommand(targetPath, options) {
|
|
|
1303
1816
|
);
|
|
1304
1817
|
process.exit(1);
|
|
1305
1818
|
}
|
|
1306
|
-
const outputPath =
|
|
1819
|
+
const outputPath = path4.resolve(options.output);
|
|
1307
1820
|
let filesModified = 0;
|
|
1308
1821
|
let replacementsTotal = 0;
|
|
1309
1822
|
const allReplacements = [];
|
|
1823
|
+
const allSkipped = [];
|
|
1310
1824
|
for (const file of scanResult.files) {
|
|
1311
|
-
const original = await
|
|
1825
|
+
const original = await fs4.readFile(file, "utf-8");
|
|
1312
1826
|
const result = replaceClassNamesInSource(
|
|
1313
1827
|
original,
|
|
1314
1828
|
replacementMap,
|
|
@@ -1316,16 +1830,17 @@ async function applyCommand(targetPath, options) {
|
|
|
1316
1830
|
);
|
|
1317
1831
|
replacementsTotal += result.replacements.length;
|
|
1318
1832
|
allReplacements.push(...result.replacements);
|
|
1833
|
+
allSkipped.push(...result.skipped);
|
|
1319
1834
|
if (result.changed) {
|
|
1320
1835
|
filesModified += 1;
|
|
1321
1836
|
if (!options.dryRun) {
|
|
1322
|
-
await
|
|
1837
|
+
await fs4.writeFile(file, result.source, "utf-8");
|
|
1323
1838
|
}
|
|
1324
1839
|
}
|
|
1325
1840
|
}
|
|
1326
1841
|
if (!options.dryRun) {
|
|
1327
|
-
await
|
|
1328
|
-
await
|
|
1842
|
+
await fs4.mkdir(path4.dirname(outputPath), { recursive: true });
|
|
1843
|
+
await fs4.writeFile(outputPath, css, "utf-8");
|
|
1329
1844
|
}
|
|
1330
1845
|
console.log("");
|
|
1331
1846
|
if (options.dryRun) {
|
|
@@ -1348,8 +1863,22 @@ async function applyCommand(targetPath, options) {
|
|
|
1348
1863
|
console.log(chalk3.bold("Replacements:"));
|
|
1349
1864
|
for (const item of allReplacements) {
|
|
1350
1865
|
const line = item.line ? `:${item.line}` : "";
|
|
1866
|
+
const partialTag = item.partial ? chalk3.dim(" (partial)") : "";
|
|
1351
1867
|
console.log(
|
|
1352
|
-
chalk3.gray(` ${item.filePath}${line}`) + chalk3.white(` "${item.from}" `) + chalk3.cyan("\u2192") + chalk3.green(` "${item.to}"`)
|
|
1868
|
+
chalk3.gray(` ${item.filePath}${line}`) + chalk3.white(` "${item.from}" `) + chalk3.cyan("\u2192") + chalk3.green(` "${item.to}"`) + partialTag
|
|
1869
|
+
);
|
|
1870
|
+
}
|
|
1871
|
+
}
|
|
1872
|
+
if (allSkipped.length > 0) {
|
|
1873
|
+
console.log("");
|
|
1874
|
+
console.log(
|
|
1875
|
+
chalk3.bold.yellow(`Skipped (${allSkipped.length}):`)
|
|
1876
|
+
);
|
|
1877
|
+
for (const item of allSkipped) {
|
|
1878
|
+
const line = item.line ? `:${item.line}` : "";
|
|
1879
|
+
const classes = item.classes.join(" ");
|
|
1880
|
+
console.log(
|
|
1881
|
+
chalk3.gray(` ${item.filePath}${line}`) + chalk3.yellow(` [${item.reason}]`) + chalk3.dim(` "${classes}"`)
|
|
1353
1882
|
);
|
|
1354
1883
|
}
|
|
1355
1884
|
}
|
|
@@ -1357,7 +1886,7 @@ async function applyCommand(targetPath, options) {
|
|
|
1357
1886
|
if (!options.dryRun) {
|
|
1358
1887
|
console.log(
|
|
1359
1888
|
chalk3.cyan(
|
|
1360
|
-
`Import ${
|
|
1889
|
+
`Import ${path4.basename(outputPath)} in your global CSS if you haven't already.`
|
|
1361
1890
|
)
|
|
1362
1891
|
);
|
|
1363
1892
|
console.log("");
|
|
@@ -1371,13 +1900,17 @@ async function applyCommand(targetPath, options) {
|
|
|
1371
1900
|
}
|
|
1372
1901
|
|
|
1373
1902
|
// src/commands/generate.ts
|
|
1374
|
-
import
|
|
1375
|
-
import
|
|
1903
|
+
import fs5 from "fs/promises";
|
|
1904
|
+
import path5 from "path";
|
|
1376
1905
|
import chalk4 from "chalk";
|
|
1377
1906
|
async function generateCommand(targetPath, options) {
|
|
1378
1907
|
let scanResult;
|
|
1379
1908
|
try {
|
|
1380
|
-
scanResult = await scanProject({
|
|
1909
|
+
scanResult = await scanProject({
|
|
1910
|
+
targetPath,
|
|
1911
|
+
include: options.include,
|
|
1912
|
+
exclude: options.exclude
|
|
1913
|
+
});
|
|
1381
1914
|
} catch (error) {
|
|
1382
1915
|
const message = error instanceof Error ? error.message : String(error);
|
|
1383
1916
|
console.error(chalk4.red(`Error: ${message}`));
|
|
@@ -1392,11 +1925,12 @@ async function generateCommand(targetPath, options) {
|
|
|
1392
1925
|
minSize: options.minSize,
|
|
1393
1926
|
maxSize: options.maxSize,
|
|
1394
1927
|
topLimit: options.top,
|
|
1395
|
-
prefix: options.prefix
|
|
1928
|
+
prefix: options.prefix,
|
|
1929
|
+
names: options.names
|
|
1396
1930
|
});
|
|
1397
|
-
const outputPath =
|
|
1398
|
-
await
|
|
1399
|
-
await
|
|
1931
|
+
const outputPath = path5.resolve(options.output);
|
|
1932
|
+
await fs5.mkdir(path5.dirname(outputPath), { recursive: true });
|
|
1933
|
+
await fs5.writeFile(outputPath, css, "utf-8");
|
|
1400
1934
|
console.log("");
|
|
1401
1935
|
console.log(chalk4.bold.green("\u2705 CSS generated successfully"));
|
|
1402
1936
|
console.log(chalk4.gray(` Output: `) + chalk4.white(outputPath));
|
|
@@ -1433,6 +1967,9 @@ async function generateCommand(targetPath, options) {
|
|
|
1433
1967
|
}
|
|
1434
1968
|
|
|
1435
1969
|
export {
|
|
1970
|
+
validateConfigFile,
|
|
1971
|
+
normalizeNamesConfig,
|
|
1972
|
+
loadCommandOptions,
|
|
1436
1973
|
suggestClassName,
|
|
1437
1974
|
isStrictSubset,
|
|
1438
1975
|
dedupeSubsetCombinations,
|
|
@@ -1466,4 +2003,4 @@ export {
|
|
|
1466
2003
|
applyCommand,
|
|
1467
2004
|
generateCommand
|
|
1468
2005
|
};
|
|
1469
|
-
//# sourceMappingURL=chunk-
|
|
2006
|
+
//# sourceMappingURL=chunk-FASYIEVZ.js.map
|