tailwind-unwind 0.2.0 → 0.3.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.
@@ -12,6 +12,9 @@ var KNOWN_ROOT_KEYS = /* @__PURE__ */ new Set([
12
12
  "top",
13
13
  "dedupeSubsets",
14
14
  "dryRun",
15
+ "prettier",
16
+ "fromReport",
17
+ "extractableOnly",
15
18
  "analyze",
16
19
  "generate",
17
20
  "apply"
@@ -24,33 +27,36 @@ var KNOWN_COMMAND_KEYS = /* @__PURE__ */ new Set([
24
27
  "prefix",
25
28
  "output",
26
29
  "dedupeSubsets",
27
- "dryRun"
30
+ "dryRun",
31
+ "prettier",
32
+ "fromReport",
33
+ "extractableOnly"
28
34
  ]);
29
35
  function isRecord(value) {
30
36
  return typeof value === "object" && value !== null && !Array.isArray(value);
31
37
  }
32
- function assertPositiveNumber(value, path6, errors) {
38
+ function assertPositiveNumber(value, path7, errors) {
33
39
  if (value === void 0) {
34
40
  return;
35
41
  }
36
42
  if (typeof value !== "number" || !Number.isFinite(value) || value < 1) {
37
- errors.push(`${path6} must be a positive number`);
43
+ errors.push(`${path7} must be a positive number`);
38
44
  }
39
45
  }
40
- function assertBoolean(value, path6, errors) {
46
+ function assertBoolean(value, path7, errors) {
41
47
  if (value === void 0) {
42
48
  return;
43
49
  }
44
50
  if (typeof value !== "boolean") {
45
- errors.push(`${path6} must be a boolean`);
51
+ errors.push(`${path7} must be a boolean`);
46
52
  }
47
53
  }
48
- function assertStringArray(value, path6, errors) {
54
+ function assertStringArray(value, path7, errors) {
49
55
  if (value === void 0) {
50
56
  return;
51
57
  }
52
58
  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`);
59
+ errors.push(`${path7} must be an array of non-empty strings`);
54
60
  }
55
61
  }
56
62
  function validateCommandSection(value, section, errors) {
@@ -72,6 +78,11 @@ function validateCommandSection(value, section, errors) {
72
78
  assertPositiveNumber(value.top, `${section}.top`, errors);
73
79
  assertBoolean(value.dedupeSubsets, `${section}.dedupeSubsets`, errors);
74
80
  assertBoolean(value.dryRun, `${section}.dryRun`, errors);
81
+ assertBoolean(value.prettier, `${section}.prettier`, errors);
82
+ assertBoolean(value.extractableOnly, `${section}.extractableOnly`, errors);
83
+ if (value.fromReport !== void 0 && (typeof value.fromReport !== "string" || value.fromReport.length === 0)) {
84
+ errors.push(`${section}.fromReport must be a non-empty string`);
85
+ }
75
86
  if (value.prefix !== void 0 && (typeof value.prefix !== "string" || value.prefix.length === 0)) {
76
87
  errors.push(`${section}.prefix must be a non-empty string`);
77
88
  }
@@ -118,6 +129,11 @@ function validateConfigFile(raw, configPath) {
118
129
  assertPositiveNumber(source.top, "top", errors);
119
130
  assertBoolean(source.dedupeSubsets, "dedupeSubsets", errors);
120
131
  assertBoolean(source.dryRun, "dryRun", errors);
132
+ assertBoolean(source.prettier, "prettier", errors);
133
+ assertBoolean(source.extractableOnly, "extractableOnly", errors);
134
+ if (source.fromReport !== void 0 && (typeof source.fromReport !== "string" || source.fromReport.length === 0)) {
135
+ errors.push("fromReport must be a non-empty string");
136
+ }
121
137
  validateNames(source.names, errors);
122
138
  validateCommandSection(source.analyze, "analyze", errors);
123
139
  validateCommandSection(source.generate, "generate", errors);
@@ -189,6 +205,15 @@ function pickCommandConfig(source) {
189
205
  if (typeof source.dryRun === "boolean") {
190
206
  config.dryRun = source.dryRun;
191
207
  }
208
+ if (typeof source.prettier === "boolean") {
209
+ config.prettier = source.prettier;
210
+ }
211
+ if (typeof source.fromReport === "string" && source.fromReport.length > 0) {
212
+ config.fromReport = source.fromReport;
213
+ }
214
+ if (typeof source.extractableOnly === "boolean") {
215
+ config.extractableOnly = source.extractableOnly;
216
+ }
192
217
  return config;
193
218
  }
194
219
  function pickNames(source) {
@@ -936,6 +961,113 @@ function calculatePotentialReduction(occurrences, topCombinations) {
936
961
  return Math.min(100, Math.round(savable / totalClassUsages * 100));
937
962
  }
938
963
 
964
+ // src/parser/variantHelpers.ts
965
+ import babelTraverse from "@babel/traverse";
966
+ var VARIANT_CALLEES = /* @__PURE__ */ new Set(["tv", "cva"]);
967
+ function isVariantCallee(expression) {
968
+ if (expression.type === "Identifier") {
969
+ return VARIANT_CALLEES.has(expression.name);
970
+ }
971
+ if (expression.type === "MemberExpression" && expression.property.type === "Identifier") {
972
+ return VARIANT_CALLEES.has(expression.property.name);
973
+ }
974
+ return false;
975
+ }
976
+ function collectStringsFromObject(node) {
977
+ const classes = [];
978
+ for (const prop of node.properties) {
979
+ if (prop.type === "SpreadElement") {
980
+ continue;
981
+ }
982
+ if (prop.type !== "ObjectProperty") {
983
+ continue;
984
+ }
985
+ collectStringsFromPropertyValue(prop, classes);
986
+ }
987
+ return classes;
988
+ }
989
+ function collectStringsFromPropertyValue(prop, classes) {
990
+ const keyName = prop.key.type === "Identifier" ? prop.key.name : prop.key.type === "StringLiteral" ? prop.key.value : null;
991
+ const { value } = prop;
992
+ if (value.type === "StringLiteral") {
993
+ classes.push(...splitClassString(value.value));
994
+ return;
995
+ }
996
+ if (value.type === "ObjectExpression") {
997
+ if (keyName === "variants" || keyName === "compoundVariants") {
998
+ for (const nested of value.properties) {
999
+ if (nested.type === "ObjectProperty") {
1000
+ collectStringsFromPropertyValue(nested, classes);
1001
+ }
1002
+ }
1003
+ return;
1004
+ }
1005
+ classes.push(...collectStringsFromObject(value));
1006
+ return;
1007
+ }
1008
+ if (value.type === "ArrayExpression") {
1009
+ for (const element of value.elements) {
1010
+ if (element === null || element.type === "SpreadElement") {
1011
+ continue;
1012
+ }
1013
+ if (element.type === "StringLiteral") {
1014
+ classes.push(...splitClassString(element.value));
1015
+ } else if (element.type === "ObjectExpression") {
1016
+ classes.push(...collectStringsFromObject(element));
1017
+ }
1018
+ }
1019
+ }
1020
+ }
1021
+ function extractClassesFromVariantCall(call) {
1022
+ const classes = [];
1023
+ for (const arg of call.arguments) {
1024
+ if (arg.type === "SpreadElement" || arg.type === "ArgumentPlaceholder") {
1025
+ continue;
1026
+ }
1027
+ if (arg.type === "StringLiteral") {
1028
+ classes.push(...splitClassString(arg.value));
1029
+ continue;
1030
+ }
1031
+ if (arg.type === "ObjectExpression") {
1032
+ classes.push(...collectStringsFromObject(arg));
1033
+ }
1034
+ }
1035
+ return [...new Set(classes)];
1036
+ }
1037
+ function resolveTraverse(module) {
1038
+ if (typeof module === "function") {
1039
+ return module;
1040
+ }
1041
+ const withDefault = module;
1042
+ if (typeof withDefault.default === "function") {
1043
+ return withDefault.default;
1044
+ }
1045
+ throw new Error("Failed to load @babel/traverse");
1046
+ }
1047
+ var traverse = resolveTraverse(babelTraverse);
1048
+ function collectVariantRegistry(ast) {
1049
+ const registry = /* @__PURE__ */ new Map();
1050
+ traverse(ast, {
1051
+ VariableDeclarator(path7) {
1052
+ registerVariantDeclarator(path7.node, registry);
1053
+ }
1054
+ });
1055
+ return registry;
1056
+ }
1057
+ function registerVariantDeclarator(declarator, registry) {
1058
+ if (declarator.id.type !== "Identifier" || declarator.init?.type !== "CallExpression") {
1059
+ return;
1060
+ }
1061
+ const { init } = declarator;
1062
+ if (init.callee.type === "V8IntrinsicIdentifier" || !isVariantCallee(init.callee)) {
1063
+ return;
1064
+ }
1065
+ const classes = extractClassesFromVariantCall(init);
1066
+ if (classes.length > 0) {
1067
+ registry.set(declarator.id.name, classes);
1068
+ }
1069
+ }
1070
+
939
1071
  // src/parser/classHelpers.ts
940
1072
  var CLASS_MERGE_CALLEES = /* @__PURE__ */ new Set([
941
1073
  "cn",
@@ -978,18 +1110,34 @@ function isClassMergeCallee(expression) {
978
1110
  }
979
1111
  return false;
980
1112
  }
981
- function extractFromCallArguments(args) {
1113
+ function extractFromCallArguments(args, registry) {
982
1114
  const parts = [];
983
1115
  for (const arg of args) {
984
1116
  if (arg.type === "SpreadElement" || arg.type === "ArgumentPlaceholder") {
985
1117
  parts.push({ classes: [], isDynamic: true });
986
1118
  continue;
987
1119
  }
988
- parts.push(extractClassesFromExpression(arg));
1120
+ parts.push(extractClassesFromExpression(arg, registry));
989
1121
  }
990
1122
  return mergeExtractions(parts);
991
1123
  }
992
- function extractClassesFromExpression(expression) {
1124
+ function extractFromVariantCall(call, registry) {
1125
+ const { callee } = call;
1126
+ if (callee.type !== "V8IntrinsicIdentifier" && isVariantCallee(callee)) {
1127
+ const classes = extractClassesFromVariantCall(call);
1128
+ const hasDynamicArgs = call.arguments.some(
1129
+ (arg) => arg.type === "SpreadElement" || arg.type === "ArgumentPlaceholder"
1130
+ );
1131
+ return { classes, isDynamic: hasDynamicArgs };
1132
+ }
1133
+ if (callee.type === "Identifier" && registry?.has(callee.name)) {
1134
+ const classes = registry.get(callee.name) ?? [];
1135
+ const hasArgs = call.arguments.length > 0;
1136
+ return { classes, isDynamic: hasArgs };
1137
+ }
1138
+ return { classes: [], isDynamic: true };
1139
+ }
1140
+ function extractClassesFromExpression(expression, registry) {
993
1141
  switch (expression.type) {
994
1142
  case "StringLiteral":
995
1143
  return extractFromStringLiteral(expression.value);
@@ -998,40 +1146,44 @@ function extractClassesFromExpression(expression) {
998
1146
  case "CallExpression": {
999
1147
  const { callee } = expression;
1000
1148
  if (callee.type !== "V8IntrinsicIdentifier" && isClassMergeCallee(callee)) {
1001
- return extractFromCallArguments(expression.arguments);
1149
+ return extractFromCallArguments(expression.arguments, registry);
1150
+ }
1151
+ const variantExtraction = extractFromVariantCall(expression, registry);
1152
+ if (variantExtraction.classes.length > 0 || !variantExtraction.isDynamic) {
1153
+ return variantExtraction;
1002
1154
  }
1003
1155
  return { classes: [], isDynamic: true };
1004
1156
  }
1005
1157
  case "ConditionalExpression": {
1006
1158
  const merged = mergeExtractions([
1007
- extractClassesFromExpression(expression.consequent),
1008
- extractClassesFromExpression(expression.alternate)
1159
+ extractClassesFromExpression(expression.consequent, registry),
1160
+ extractClassesFromExpression(expression.alternate, registry)
1009
1161
  ]);
1010
1162
  return { ...merged, isDynamic: true };
1011
1163
  }
1012
1164
  case "LogicalExpression": {
1013
1165
  const merged = mergeExtractions([
1014
- extractClassesFromExpression(expression.left),
1015
- extractClassesFromExpression(expression.right)
1166
+ extractClassesFromExpression(expression.left, registry),
1167
+ extractClassesFromExpression(expression.right, registry)
1016
1168
  ]);
1017
1169
  return { ...merged, isDynamic: true };
1018
1170
  }
1019
1171
  case "ArrayExpression":
1020
- return extractFromArrayExpression(expression);
1172
+ return extractFromArrayExpression(expression, registry);
1021
1173
  case "ObjectExpression":
1022
1174
  return extractFromObjectExpression(expression);
1023
1175
  default:
1024
1176
  return { classes: [], isDynamic: true };
1025
1177
  }
1026
1178
  }
1027
- function extractFromArrayExpression(node) {
1179
+ function extractFromArrayExpression(node, registry) {
1028
1180
  const parts = [];
1029
1181
  for (const element of node.elements) {
1030
1182
  if (element === null || element.type === "SpreadElement") {
1031
1183
  parts.push({ classes: [], isDynamic: true });
1032
1184
  continue;
1033
1185
  }
1034
- parts.push(extractClassesFromExpression(element));
1186
+ parts.push(extractClassesFromExpression(element, registry));
1035
1187
  }
1036
1188
  return mergeExtractions(parts);
1037
1189
  }
@@ -1086,7 +1238,7 @@ function isClassAttribute(attr) {
1086
1238
  const name = getAttributeName(attr);
1087
1239
  return name !== null && CLASS_ATTRIBUTES.has(name);
1088
1240
  }
1089
- function extractFromJSXAttribute(attr) {
1241
+ function extractFromJSXAttribute(attr, registry) {
1090
1242
  if (!isClassAttribute(attr)) {
1091
1243
  return null;
1092
1244
  }
@@ -1096,7 +1248,7 @@ function extractFromJSXAttribute(attr) {
1096
1248
  return { classes: [], isDynamic: true, line };
1097
1249
  }
1098
1250
  if (value.type === "StringLiteral") {
1099
- const result = extractClassesFromExpression(value);
1251
+ const result = extractClassesFromExpression(value, registry);
1100
1252
  return { classes: result.classes, isDynamic: result.isDynamic, line };
1101
1253
  }
1102
1254
  if (value.type === "JSXExpressionContainer") {
@@ -1104,7 +1256,7 @@ function extractFromJSXAttribute(attr) {
1104
1256
  if (expr.type === "JSXEmptyExpression") {
1105
1257
  return { classes: [], isDynamic: true, line };
1106
1258
  }
1107
- const result = extractClassesFromExpression(expr);
1259
+ const result = extractClassesFromExpression(expr, registry);
1108
1260
  return { classes: result.classes, isDynamic: result.isDynamic, line };
1109
1261
  }
1110
1262
  return { classes: [], isDynamic: true, line };
@@ -1121,9 +1273,9 @@ function parseSourceToAst(source) {
1121
1273
  }
1122
1274
 
1123
1275
  // src/parser/jsxParser.ts
1124
- import babelTraverse from "@babel/traverse";
1276
+ import babelTraverse2 from "@babel/traverse";
1125
1277
  import fs2 from "fs/promises";
1126
- function resolveTraverse(module) {
1278
+ function resolveTraverse2(module) {
1127
1279
  if (typeof module === "function") {
1128
1280
  return module;
1129
1281
  }
@@ -1133,9 +1285,9 @@ function resolveTraverse(module) {
1133
1285
  }
1134
1286
  throw new Error("Failed to load @babel/traverse");
1135
1287
  }
1136
- var traverse = resolveTraverse(babelTraverse);
1137
- function isJSXElementWithClassAttribute(path6) {
1138
- const opening = path6.node.openingElement;
1288
+ var traverse2 = resolveTraverse2(babelTraverse2);
1289
+ function isJSXElementWithClassAttribute(path7) {
1290
+ const opening = path7.node.openingElement;
1139
1291
  return opening.attributes.some(
1140
1292
  (attr) => attr.type === "JSXAttribute" && isClassAttribute(attr)
1141
1293
  );
@@ -1143,15 +1295,16 @@ function isJSXElementWithClassAttribute(path6) {
1143
1295
  function collectExtractionsFromAst(ast, filePath) {
1144
1296
  const extractions = [];
1145
1297
  const warnings = [];
1146
- traverse(ast, {
1147
- JSXElement(path6) {
1148
- if (!isJSXElementWithClassAttribute(path6)) {
1298
+ const variantRegistry = collectVariantRegistry(ast);
1299
+ traverse2(ast, {
1300
+ JSXElement(path7) {
1301
+ if (!isJSXElementWithClassAttribute(path7)) {
1149
1302
  return;
1150
1303
  }
1151
- const opening = path6.node.openingElement;
1304
+ const opening = path7.node.openingElement;
1152
1305
  for (const attr of opening.attributes) {
1153
1306
  if (attr.type !== "JSXAttribute") continue;
1154
- const extraction = extractFromJSXAttribute(attr);
1307
+ const extraction = extractFromJSXAttribute(attr, variantRegistry);
1155
1308
  if (!extraction) continue;
1156
1309
  if (extraction.isDynamic && extraction.classes.length === 0) {
1157
1310
  const lineInfo = extraction.line ? `:${extraction.line}` : "";
@@ -1467,11 +1620,66 @@ async function analyzeCommand(targetPath, options = {}) {
1467
1620
  return report;
1468
1621
  }
1469
1622
 
1623
+ // src/codemod/formatSource.ts
1624
+ import { createRequire } from "module";
1625
+ import path4 from "path";
1626
+ var require2 = createRequire(import.meta.url);
1627
+ async function loadPrettier() {
1628
+ try {
1629
+ const prettier = await import("prettier");
1630
+ return prettier;
1631
+ } catch {
1632
+ try {
1633
+ return require2("prettier");
1634
+ } catch {
1635
+ return null;
1636
+ }
1637
+ }
1638
+ }
1639
+ async function formatSource(source, options) {
1640
+ const prettier = await loadPrettier();
1641
+ if (!prettier) {
1642
+ return { source, formatted: false };
1643
+ }
1644
+ try {
1645
+ const config = await prettier.resolveConfig(options.filePath);
1646
+ const formatted = await prettier.format(source, {
1647
+ ...config,
1648
+ filepath: options.filePath
1649
+ });
1650
+ return { source: formatted, formatted: true };
1651
+ } catch {
1652
+ return { source, formatted: false };
1653
+ }
1654
+ }
1655
+ async function formatModifiedFiles(files, sources, cwd = process.cwd()) {
1656
+ const formatted = [];
1657
+ const skipped = [];
1658
+ for (const file of files) {
1659
+ const source = sources.get(file);
1660
+ if (!source) {
1661
+ skipped.push(file);
1662
+ continue;
1663
+ }
1664
+ const result = await formatSource(source, {
1665
+ filePath: path4.resolve(cwd, file),
1666
+ cwd
1667
+ });
1668
+ if (result.formatted) {
1669
+ sources.set(file, result.source);
1670
+ formatted.push(file);
1671
+ } else {
1672
+ skipped.push(file);
1673
+ }
1674
+ }
1675
+ return { formatted, skipped };
1676
+ }
1677
+
1470
1678
  // src/codemod/replaceClassNames.ts
1471
1679
  import babelGenerate from "@babel/generator";
1472
- import babelTraverse2 from "@babel/traverse";
1680
+ import babelTraverse3 from "@babel/traverse";
1473
1681
  import * as t from "@babel/types";
1474
- function resolveTraverse2(module) {
1682
+ function resolveTraverse3(module) {
1475
1683
  if (typeof module === "function") {
1476
1684
  return module;
1477
1685
  }
@@ -1491,7 +1699,7 @@ function resolveGenerator(module) {
1491
1699
  }
1492
1700
  throw new Error("Failed to load @babel/generator");
1493
1701
  }
1494
- var traverse2 = resolveTraverse2(babelTraverse2);
1702
+ var traverse3 = resolveTraverse3(babelTraverse3);
1495
1703
  var generate = resolveGenerator(babelGenerate);
1496
1704
  function isClassMergeCallee2(expression) {
1497
1705
  if (expression.type === "Identifier") {
@@ -1586,16 +1794,54 @@ function tryReplaceMergeCall(call, replacementMap) {
1586
1794
  partial: true
1587
1795
  };
1588
1796
  }
1589
- function tryReplaceClassAttribute(attr, replacementMap) {
1797
+ function tryReplaceTemplateLiteral(attr, replacementMap, registry) {
1798
+ const value = attr.value;
1799
+ if (value?.type !== "JSXExpressionContainer") {
1800
+ return null;
1801
+ }
1802
+ const expression = value.expression;
1803
+ if (expression.type !== "TemplateLiteral" || expression.expressions.length === 0) {
1804
+ return null;
1805
+ }
1806
+ const extracted = extractClassesFromExpression(expression, registry);
1807
+ if (extracted.classes.length === 0) {
1808
+ return null;
1809
+ }
1810
+ const key = normalizeClasses(extracted.classes);
1811
+ const replacement = replacementMap.get(key);
1812
+ if (!replacement) {
1813
+ return null;
1814
+ }
1815
+ const newQuasis = expression.quasis.map((quasi, index) => {
1816
+ if (index !== 0) {
1817
+ return quasi;
1818
+ }
1819
+ const prefix = expression.expressions.length > 0 ? `${replacement} ` : replacement;
1820
+ return t.templateElement(
1821
+ { raw: prefix, cooked: prefix },
1822
+ quasi.tail
1823
+ );
1824
+ });
1825
+ return {
1826
+ expression: t.templateLiteral(newQuasis, [...expression.expressions]),
1827
+ from: key,
1828
+ to: replacement,
1829
+ partial: true
1830
+ };
1831
+ }
1832
+ function tryReplaceClassAttribute(attr, replacementMap, registry) {
1590
1833
  const value = attr.value;
1591
1834
  if (value?.type !== "JSXExpressionContainer") {
1592
1835
  return null;
1593
1836
  }
1594
1837
  const expression = value.expression;
1595
- if (expression.type === "JSXEmptyExpression" || !isClassMergeCall(expression)) {
1838
+ if (expression.type === "JSXEmptyExpression") {
1596
1839
  return null;
1597
1840
  }
1598
- return tryReplaceMergeCall(expression, replacementMap);
1841
+ if (isClassMergeCall(expression)) {
1842
+ return tryReplaceMergeCall(expression, replacementMap);
1843
+ }
1844
+ return tryReplaceTemplateLiteral(attr, replacementMap, registry);
1599
1845
  }
1600
1846
  function replaceClassNamesInSource(source, replacementMap, filePath) {
1601
1847
  const replacements = [];
@@ -1610,16 +1856,21 @@ function replaceClassNamesInSource(source, replacementMap, filePath) {
1610
1856
  const message = error instanceof Error ? error.message : String(error);
1611
1857
  throw new Error(`Failed to parse ${filePath}: ${message}`);
1612
1858
  }
1613
- traverse2(ast, {
1614
- JSXElement(path6) {
1615
- const opening = path6.node.openingElement;
1859
+ const variantRegistry = collectVariantRegistry(ast);
1860
+ traverse3(ast, {
1861
+ JSXElement(path7) {
1862
+ const opening = path7.node.openingElement;
1616
1863
  for (const attr of opening.attributes) {
1617
1864
  if (attr.type !== "JSXAttribute" || !isClassAttribute(attr)) {
1618
1865
  continue;
1619
1866
  }
1620
- const extraction = extractFromJSXAttribute(attr);
1867
+ const extraction = extractFromJSXAttribute(attr, variantRegistry);
1621
1868
  if (!extraction) continue;
1622
- const mergeReplacement = tryReplaceClassAttribute(attr, replacementMap);
1869
+ const mergeReplacement = tryReplaceClassAttribute(
1870
+ attr,
1871
+ replacementMap,
1872
+ variantRegistry
1873
+ );
1623
1874
  if (mergeReplacement) {
1624
1875
  if (mergeReplacement.expression.type === "StringLiteral") {
1625
1876
  setStringClassAttribute(attr, mergeReplacement.expression.value);
@@ -1775,10 +2026,61 @@ function buildComponents(occurrences, options) {
1775
2026
  }
1776
2027
  return { components, css, replacementMap };
1777
2028
  }
2029
+ function buildComponentsFromCombinations(combinations, options) {
2030
+ const { css, components } = generateComponentCss({
2031
+ sourcePath: options.sourcePath,
2032
+ combinations,
2033
+ prefix: options.prefix,
2034
+ names: options.names
2035
+ });
2036
+ const replacementMap = /* @__PURE__ */ new Map();
2037
+ for (const component of components) {
2038
+ const key = [...component.classes].sort().join(" ");
2039
+ replacementMap.set(key, component.className);
2040
+ }
2041
+ return { components, css, replacementMap };
2042
+ }
1778
2043
 
1779
- // src/commands/apply.ts
2044
+ // src/core/loadAnalyzeReport.ts
1780
2045
  import fs4 from "fs/promises";
1781
- import path4 from "path";
2046
+ function isAnalysisReport(value) {
2047
+ if (typeof value !== "object" || value === null) {
2048
+ return false;
2049
+ }
2050
+ const report = value;
2051
+ return typeof report.targetPath === "string" && typeof report.stats === "object" && Array.isArray(report.stats.topCombinations);
2052
+ }
2053
+ async function loadExtractableCombinations(reportPath, options = {}) {
2054
+ const raw = await fs4.readFile(reportPath, "utf-8");
2055
+ const parsed = JSON.parse(raw);
2056
+ if (!isAnalysisReport(parsed)) {
2057
+ throw new Error(`Invalid analyze report: ${reportPath}`);
2058
+ }
2059
+ const combinations = parsed.stats.topCombinations.filter(
2060
+ (combo) => options.extractableOnly === false ? true : combo.extractable === true
2061
+ );
2062
+ if (combinations.length === 0) {
2063
+ throw new Error(
2064
+ "No extractable combinations found in report. Re-run analyze or use --extractable-only=false."
2065
+ );
2066
+ }
2067
+ return {
2068
+ targetPath: parsed.targetPath,
2069
+ combinations
2070
+ };
2071
+ }
2072
+
2073
+ // src/reporters/operationJsonReporter.ts
2074
+ function printGenerateJsonReport(report) {
2075
+ console.log(JSON.stringify(report, null, 2));
2076
+ }
2077
+ function printApplyJsonReport(report) {
2078
+ console.log(JSON.stringify(report, null, 2));
2079
+ }
2080
+
2081
+ // src/commands/apply.ts
2082
+ import fs5 from "fs/promises";
2083
+ import path5 from "path";
1782
2084
  import chalk3 from "chalk";
1783
2085
  async function applyCommand(targetPath, options) {
1784
2086
  let scanResult;
@@ -1786,7 +2088,8 @@ async function applyCommand(targetPath, options) {
1786
2088
  scanResult = await scanProject({
1787
2089
  targetPath,
1788
2090
  include: options.include,
1789
- exclude: options.exclude
2091
+ exclude: options.exclude,
2092
+ extractableMinOccurrences: options.minOccurrences ?? 3
1790
2093
  });
1791
2094
  } catch (error) {
1792
2095
  const message = error instanceof Error ? error.message : String(error);
@@ -1794,20 +2097,57 @@ async function applyCommand(targetPath, options) {
1794
2097
  process.exit(1);
1795
2098
  }
1796
2099
  for (const warning of scanResult.warnings) {
1797
- console.warn(chalk3.yellow(`\u26A0 ${warning}`));
2100
+ if (options.format !== "json") {
2101
+ console.warn(chalk3.yellow(`\u26A0 ${warning}`));
2102
+ }
1798
2103
  }
1799
- const { components, css, replacementMap } = buildComponents(
1800
- scanResult.occurrences,
1801
- {
1802
- sourcePath: scanResult.resolvedPath,
1803
- minOccurrences: options.minOccurrences ?? 3,
1804
- minSize: options.minSize,
1805
- maxSize: options.maxSize,
1806
- topLimit: options.top,
1807
- prefix: options.prefix,
1808
- names: options.names
2104
+ let components;
2105
+ let css;
2106
+ let replacementMap;
2107
+ try {
2108
+ if (options.fromReport) {
2109
+ const loadedReport = await loadExtractableCombinations(options.fromReport, {
2110
+ extractableOnly: options.extractableOnly ?? true
2111
+ });
2112
+ const built = buildComponentsFromCombinations(loadedReport.combinations, {
2113
+ sourcePath: scanResult.resolvedPath,
2114
+ prefix: options.prefix,
2115
+ names: options.names
2116
+ });
2117
+ components = built.components;
2118
+ css = built.css;
2119
+ replacementMap = built.replacementMap;
2120
+ } else if (options.extractableOnly) {
2121
+ const combinations = scanResult.report.stats.topCombinations.filter(
2122
+ (combo) => combo.extractable
2123
+ );
2124
+ const built = buildComponentsFromCombinations(combinations, {
2125
+ sourcePath: scanResult.resolvedPath,
2126
+ prefix: options.prefix,
2127
+ names: options.names
2128
+ });
2129
+ components = built.components;
2130
+ css = built.css;
2131
+ replacementMap = built.replacementMap;
2132
+ } else {
2133
+ const built = buildComponents(scanResult.occurrences, {
2134
+ sourcePath: scanResult.resolvedPath,
2135
+ minOccurrences: options.minOccurrences ?? 3,
2136
+ minSize: options.minSize,
2137
+ maxSize: options.maxSize,
2138
+ topLimit: options.top,
2139
+ prefix: options.prefix,
2140
+ names: options.names
2141
+ });
2142
+ components = built.components;
2143
+ css = built.css;
2144
+ replacementMap = built.replacementMap;
1809
2145
  }
1810
- );
2146
+ } catch (error) {
2147
+ const message = error instanceof Error ? error.message : String(error);
2148
+ console.error(chalk3.red(`Error: ${message}`));
2149
+ process.exit(1);
2150
+ }
1811
2151
  if (components.length === 0) {
1812
2152
  console.error(
1813
2153
  chalk3.yellow(
@@ -1816,31 +2156,71 @@ async function applyCommand(targetPath, options) {
1816
2156
  );
1817
2157
  process.exit(1);
1818
2158
  }
1819
- const outputPath = path4.resolve(options.output);
2159
+ const outputPath = path5.resolve(options.output);
1820
2160
  let filesModified = 0;
1821
2161
  let replacementsTotal = 0;
1822
2162
  const allReplacements = [];
1823
2163
  const allSkipped = [];
2164
+ const modifiedSources = /* @__PURE__ */ new Map();
2165
+ const modifiedFiles = [];
1824
2166
  for (const file of scanResult.files) {
1825
- const original = await fs4.readFile(file, "utf-8");
1826
- const result = replaceClassNamesInSource(
2167
+ const original = await fs5.readFile(file, "utf-8");
2168
+ const result2 = replaceClassNamesInSource(
1827
2169
  original,
1828
2170
  replacementMap,
1829
2171
  file
1830
2172
  );
1831
- replacementsTotal += result.replacements.length;
1832
- allReplacements.push(...result.replacements);
1833
- allSkipped.push(...result.skipped);
1834
- if (result.changed) {
2173
+ replacementsTotal += result2.replacements.length;
2174
+ allReplacements.push(...result2.replacements);
2175
+ allSkipped.push(...result2.skipped);
2176
+ if (result2.changed) {
1835
2177
  filesModified += 1;
1836
- if (!options.dryRun) {
1837
- await fs4.writeFile(file, result.source, "utf-8");
1838
- }
2178
+ modifiedSources.set(file, result2.source);
2179
+ modifiedFiles.push(file);
1839
2180
  }
1840
2181
  }
2182
+ let prettierFormatted = [];
2183
+ if (!options.dryRun && options.prettier && modifiedFiles.length > 0) {
2184
+ const formatResult = await formatModifiedFiles(
2185
+ modifiedFiles,
2186
+ modifiedSources,
2187
+ process.cwd()
2188
+ );
2189
+ prettierFormatted = formatResult.formatted;
2190
+ }
1841
2191
  if (!options.dryRun) {
1842
- await fs4.mkdir(path4.dirname(outputPath), { recursive: true });
1843
- await fs4.writeFile(outputPath, css, "utf-8");
2192
+ for (const file of modifiedFiles) {
2193
+ const source = modifiedSources.get(file);
2194
+ if (source) {
2195
+ await fs5.writeFile(file, source, "utf-8");
2196
+ }
2197
+ }
2198
+ await fs5.mkdir(path5.dirname(outputPath), { recursive: true });
2199
+ await fs5.writeFile(outputPath, css, "utf-8");
2200
+ }
2201
+ const result = {
2202
+ filesModified,
2203
+ replacementsTotal,
2204
+ outputPath,
2205
+ componentsGenerated: components.length,
2206
+ components,
2207
+ replacements: allReplacements,
2208
+ skipped: allSkipped,
2209
+ prettierFormatted
2210
+ };
2211
+ if (options.format === "json") {
2212
+ printApplyJsonReport({
2213
+ command: "apply",
2214
+ dryRun: Boolean(options.dryRun),
2215
+ outputPath,
2216
+ filesModified,
2217
+ replacementsTotal,
2218
+ componentsGenerated: components.length,
2219
+ components,
2220
+ replacements: allReplacements,
2221
+ skipped: allSkipped
2222
+ });
2223
+ return result;
1844
2224
  }
1845
2225
  console.log("");
1846
2226
  if (options.dryRun) {
@@ -1858,6 +2238,11 @@ async function applyCommand(targetPath, options) {
1858
2238
  console.log(
1859
2239
  chalk3.gray(` Replacements: `) + chalk3.white(String(replacementsTotal))
1860
2240
  );
2241
+ if (prettierFormatted.length > 0) {
2242
+ console.log(
2243
+ chalk3.gray(` Prettier formatted: `) + chalk3.white(String(prettierFormatted.length))
2244
+ );
2245
+ }
1861
2246
  if (allReplacements.length > 0) {
1862
2247
  console.log("");
1863
2248
  console.log(chalk3.bold("Replacements:"));
@@ -1871,9 +2256,7 @@ async function applyCommand(targetPath, options) {
1871
2256
  }
1872
2257
  if (allSkipped.length > 0) {
1873
2258
  console.log("");
1874
- console.log(
1875
- chalk3.bold.yellow(`Skipped (${allSkipped.length}):`)
1876
- );
2259
+ console.log(chalk3.bold.yellow(`Skipped (${allSkipped.length}):`));
1877
2260
  for (const item of allSkipped) {
1878
2261
  const line = item.line ? `:${item.line}` : "";
1879
2262
  const classes = item.classes.join(" ");
@@ -1886,51 +2269,97 @@ async function applyCommand(targetPath, options) {
1886
2269
  if (!options.dryRun) {
1887
2270
  console.log(
1888
2271
  chalk3.cyan(
1889
- `Import ${path4.basename(outputPath)} in your global CSS if you haven't already.`
2272
+ `Import ${path5.basename(outputPath)} in your global CSS if you haven't already.`
1890
2273
  )
1891
2274
  );
1892
2275
  console.log("");
1893
2276
  }
1894
- return {
1895
- filesModified,
1896
- replacementsTotal,
1897
- outputPath,
1898
- componentsGenerated: components.length
1899
- };
2277
+ return result;
1900
2278
  }
1901
2279
 
1902
2280
  // src/commands/generate.ts
1903
- import fs5 from "fs/promises";
1904
- import path5 from "path";
2281
+ import fs6 from "fs/promises";
2282
+ import path6 from "path";
1905
2283
  import chalk4 from "chalk";
1906
2284
  async function generateCommand(targetPath, options) {
1907
- let scanResult;
2285
+ let scanResult = null;
2286
+ let components;
2287
+ let css;
1908
2288
  try {
1909
- scanResult = await scanProject({
1910
- targetPath,
1911
- include: options.include,
1912
- exclude: options.exclude
1913
- });
2289
+ if (options.fromReport) {
2290
+ const loadedReport = await loadExtractableCombinations(options.fromReport, {
2291
+ extractableOnly: options.extractableOnly ?? true
2292
+ });
2293
+ const built = buildComponentsFromCombinations(loadedReport.combinations, {
2294
+ sourcePath: loadedReport.targetPath || targetPath,
2295
+ prefix: options.prefix,
2296
+ names: options.names
2297
+ });
2298
+ components = built.components;
2299
+ css = built.css;
2300
+ } else {
2301
+ scanResult = await scanProject({
2302
+ targetPath,
2303
+ include: options.include,
2304
+ exclude: options.exclude,
2305
+ extractableMinOccurrences: options.minOccurrences ?? 3
2306
+ });
2307
+ if (options.extractableOnly) {
2308
+ const combinations = scanResult.report.stats.topCombinations.filter(
2309
+ (combo) => combo.extractable
2310
+ );
2311
+ const built = buildComponentsFromCombinations(combinations, {
2312
+ sourcePath: scanResult.resolvedPath,
2313
+ prefix: options.prefix,
2314
+ names: options.names
2315
+ });
2316
+ components = built.components;
2317
+ css = built.css;
2318
+ } else {
2319
+ const built = buildComponents(scanResult.occurrences, {
2320
+ sourcePath: scanResult.resolvedPath,
2321
+ minOccurrences: options.minOccurrences ?? 3,
2322
+ minSize: options.minSize,
2323
+ maxSize: options.maxSize,
2324
+ topLimit: options.top,
2325
+ prefix: options.prefix,
2326
+ names: options.names
2327
+ });
2328
+ components = built.components;
2329
+ css = built.css;
2330
+ }
2331
+ }
1914
2332
  } catch (error) {
1915
2333
  const message = error instanceof Error ? error.message : String(error);
1916
2334
  console.error(chalk4.red(`Error: ${message}`));
1917
2335
  process.exit(1);
1918
2336
  }
1919
- for (const warning of scanResult.warnings) {
1920
- console.warn(chalk4.yellow(`\u26A0 ${warning}`));
2337
+ if (scanResult) {
2338
+ for (const warning of scanResult.warnings) {
2339
+ if (options.format !== "json") {
2340
+ console.warn(chalk4.yellow(`\u26A0 ${warning}`));
2341
+ }
2342
+ }
2343
+ }
2344
+ const outputPath = path6.resolve(options.output);
2345
+ await fs6.mkdir(path6.dirname(outputPath), { recursive: true });
2346
+ await fs6.writeFile(outputPath, css, "utf-8");
2347
+ const result = {
2348
+ outputPath,
2349
+ componentsGenerated: components.length,
2350
+ components,
2351
+ report: scanResult?.report ?? null
2352
+ };
2353
+ if (options.format === "json") {
2354
+ printGenerateJsonReport({
2355
+ command: "generate",
2356
+ outputPath,
2357
+ componentsGenerated: components.length,
2358
+ components,
2359
+ cssWritten: true
2360
+ });
2361
+ return result;
1921
2362
  }
1922
- const { css, components } = buildComponents(scanResult.occurrences, {
1923
- sourcePath: scanResult.resolvedPath,
1924
- minOccurrences: options.minOccurrences ?? 3,
1925
- minSize: options.minSize,
1926
- maxSize: options.maxSize,
1927
- topLimit: options.top,
1928
- prefix: options.prefix,
1929
- names: options.names
1930
- });
1931
- const outputPath = path5.resolve(options.output);
1932
- await fs5.mkdir(path5.dirname(outputPath), { recursive: true });
1933
- await fs5.writeFile(outputPath, css, "utf-8");
1934
2363
  console.log("");
1935
2364
  console.log(chalk4.bold.green("\u2705 CSS generated successfully"));
1936
2365
  console.log(chalk4.gray(` Output: `) + chalk4.white(outputPath));
@@ -1959,11 +2388,7 @@ async function generateCommand(targetPath, options) {
1959
2388
  );
1960
2389
  }
1961
2390
  console.log("");
1962
- return {
1963
- outputPath,
1964
- componentsGenerated: components.length,
1965
- report: scanResult.report
1966
- };
2391
+ return result;
1967
2392
  }
1968
2393
 
1969
2394
  export {
@@ -1979,6 +2404,10 @@ export {
1979
2404
  findFrequentPatterns,
1980
2405
  findRepeatedClassSets,
1981
2406
  calculatePotentialReduction,
2407
+ VARIANT_CALLEES,
2408
+ isVariantCallee,
2409
+ extractClassesFromVariantCall,
2410
+ collectVariantRegistry,
1982
2411
  CLASS_MERGE_CALLEES,
1983
2412
  extractClassesFromExpression,
1984
2413
  isClassAttribute,
@@ -1993,6 +2422,8 @@ export {
1993
2422
  printConsoleReport,
1994
2423
  printJsonReport,
1995
2424
  analyzeCommand,
2425
+ formatSource,
2426
+ formatModifiedFiles,
1996
2427
  replaceClassNamesInSource,
1997
2428
  DEFAULT_CLASS_PREFIX,
1998
2429
  normalizeClassPrefix,
@@ -2000,7 +2431,11 @@ export {
2000
2431
  assignComponentClassNames,
2001
2432
  generateComponentCss,
2002
2433
  buildComponents,
2434
+ buildComponentsFromCombinations,
2435
+ loadExtractableCombinations,
2436
+ printGenerateJsonReport,
2437
+ printApplyJsonReport,
2003
2438
  applyCommand,
2004
2439
  generateCommand
2005
2440
  };
2006
- //# sourceMappingURL=chunk-FASYIEVZ.js.map
2441
+ //# sourceMappingURL=chunk-4GXMK3NB.js.map