cleanwind 0.3.0 → 0.3.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (2) hide show
  1. package/dist/index.js +255 -30
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -1,6 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
 
3
3
  // src/index.ts
4
+ import path4 from "path";
4
5
  import { Command } from "commander";
5
6
 
6
7
  // ../core/dist/index.js
@@ -522,6 +523,7 @@ var displayClasses = /* @__PURE__ */ new Set([
522
523
  "hidden"
523
524
  ]);
524
525
  var positionClasses = /* @__PURE__ */ new Set(["static", "fixed", "absolute", "relative", "sticky"]);
526
+ var borderStyleClasses = /* @__PURE__ */ new Set(["solid", "dashed", "dotted", "double", "hidden", "none"]);
525
527
  var variantOrder = [
526
528
  "sm",
527
529
  "md",
@@ -614,8 +616,9 @@ function conflictKey(className) {
614
616
  if (utility.startsWith("rounded")) {
615
617
  return `${variantPrefix}${roundedConflictKey(utility)}`;
616
618
  }
617
- if (/^border(?:-[trblxy])?-/u.test(utility) || utility === "border") {
618
- return `${variantPrefix}${borderConflictKey(utility)}`;
619
+ const border = borderConflictKey(utility);
620
+ if (border) {
621
+ return `${variantPrefix}${border}`;
619
622
  }
620
623
  if (/^flex-(row|row-reverse|col|col-reverse)$/u.test(utility)) {
621
624
  return `${variantPrefix}flex-direction`;
@@ -702,8 +705,26 @@ function roundedConflictKey(utility) {
702
705
  return match?.groups?.side ? `border-radius-${match.groups.side}` : "border-radius";
703
706
  }
704
707
  function borderConflictKey(utility) {
705
- const match = /^border(?:-(?<side>[trblxy]))?(?:-|$)/u.exec(utility);
706
- return match?.groups?.side ? `border-${match.groups.side}` : "border";
708
+ if (utility === "border-collapse" || utility === "border-separate") {
709
+ return "border-collapse";
710
+ }
711
+ const match = /^border(?:-(?<side>[trblxy]))?(?:-(?<value>.+))?$/u.exec(utility);
712
+ if (!match?.groups) {
713
+ return void 0;
714
+ }
715
+ const side = match.groups.side;
716
+ const value = match.groups.value;
717
+ const sideSuffix = side ? `-${side}` : "";
718
+ if (!value || isBorderWidthValue(value)) {
719
+ return `border-width${sideSuffix}`;
720
+ }
721
+ if (!side && borderStyleClasses.has(value)) {
722
+ return "border-style";
723
+ }
724
+ return `border-color${sideSuffix}`;
725
+ }
726
+ function isBorderWidthValue(value) {
727
+ return /^(0|2|4|8|\d+|\[.+\])$/u.test(value);
707
728
  }
708
729
  function rankUtility(utility) {
709
730
  if (utility.startsWith("container")) return 0;
@@ -1069,30 +1090,47 @@ function hasParseIssue(issues) {
1069
1090
  }
1070
1091
 
1071
1092
  // src/index.ts
1093
+ var defaultListLimit = 12;
1094
+ var tree = {
1095
+ branch: "\u251C\u2500",
1096
+ last: "\u2514\u2500",
1097
+ pipe: "\u2502",
1098
+ tee: "\u2502 \u251C",
1099
+ elbow: "\u2502 \u2514",
1100
+ spacer: "\u2502 ",
1101
+ step: "*",
1102
+ primary: "\u25C6",
1103
+ secondary: "\u25CF"
1104
+ };
1072
1105
  var program = new Command();
1073
- program.name("cleanwind").description("Clean imports and Tailwind CSS class names.").version("0.3.0");
1106
+ program.name("cleanwind").description("Clean imports and Tailwind CSS class names.").version("0.3.2");
1074
1107
  program.command("check").description("Check files without writing changes.").option("--cwd <path>", "Working directory").option("--config <path>", "Path to cleanwind config").option("--write", "Write fixes while running check").option("--check", "Force check mode", true).option("--verbose", "Print detailed diagnostics").option("--format", "Format output with Prettier after cleanup").action(async (options) => {
1075
1108
  const runOptions = toRunOptions(options);
1076
1109
  if (options.write) {
1077
- const result2 = await fix({ ...runOptions, write: true });
1078
- printFixResult(result2, options.verbose ?? false);
1110
+ const result2 = await runWithProgress(
1111
+ "Fixing project",
1112
+ () => fix({ ...runOptions, write: true })
1113
+ );
1114
+ printFixResult(result2, options.verbose ?? false, runOptions.cwd ?? process.cwd());
1079
1115
  process.exitCode = result2.conflicts.length > 0 ? 1 : 0;
1080
1116
  return;
1081
1117
  }
1082
- const result = await check(runOptions);
1083
- printCheckResult(result, options.verbose ?? false);
1118
+ const result = await runWithProgress("Checking project", () => check(runOptions));
1119
+ printCheckResult(result, options.verbose ?? false, runOptions.cwd ?? process.cwd());
1084
1120
  process.exitCode = result.ok ? 0 : 1;
1085
1121
  });
1086
1122
  program.command("fix").description("Fix files in place.").option("--cwd <path>", "Working directory").option("--config <path>", "Path to cleanwind config").option("--write", "Write fixes", true).option("--check", "Preview fixes without writing").option("--verbose", "Print detailed diagnostics").option("--staged", "Only fix staged files").option("--format", "Format output with Prettier after cleanup").action(async (options) => {
1087
- const result = await fix({
1088
- ...toRunOptions(options),
1089
- write: options.check ? false : options.write ?? true,
1090
- staged: options.staged ?? false
1091
- });
1092
- printFixResult(result, options.verbose ?? false);
1123
+ const result = await runWithProgress(
1124
+ options.check ? "Previewing cleanup" : "Fixing project",
1125
+ () => fix({
1126
+ ...toRunOptions(options),
1127
+ write: options.check ? false : options.write ?? true,
1128
+ staged: options.staged ?? false
1129
+ })
1130
+ );
1131
+ printFixResult(result, options.verbose ?? false, options.cwd ?? process.cwd());
1093
1132
  process.exitCode = result.conflicts.length > 0 ? 1 : 0;
1094
1133
  });
1095
- await program.parseAsync();
1096
1134
  function toRunOptions(options) {
1097
1135
  const runOptions = {};
1098
1136
  if (options.cwd !== void 0) runOptions.cwd = options.cwd;
@@ -1104,31 +1142,218 @@ function toRunOptions(options) {
1104
1142
  if (options.format !== void 0) runOptions.format = options.format;
1105
1143
  return runOptions;
1106
1144
  }
1107
- function printCheckResult(result, verbose) {
1145
+ async function runWithProgress(label, task) {
1146
+ if (!shouldRenderProgress()) {
1147
+ return task();
1148
+ }
1149
+ let progress = 0;
1150
+ const timer = setInterval(() => {
1151
+ progress = nextProgress(progress);
1152
+ renderProgress(label, progress);
1153
+ }, 90);
1154
+ renderProgress(label, progress);
1155
+ try {
1156
+ const result = await task();
1157
+ progress = 100;
1158
+ renderProgress(label, progress);
1159
+ await wait(120);
1160
+ return result;
1161
+ } finally {
1162
+ clearInterval(timer);
1163
+ clearProgress();
1164
+ }
1165
+ }
1166
+ function nextProgress(progress) {
1167
+ if (progress < 55) {
1168
+ return progress + 7;
1169
+ }
1170
+ if (progress < 85) {
1171
+ return progress + 3;
1172
+ }
1173
+ return Math.min(progress + 1, 95);
1174
+ }
1175
+ function renderProgress(label, progress) {
1176
+ const width = 24;
1177
+ const filled = Math.round(progress / 100 * width);
1178
+ const bar = `${"\u2588".repeat(filled)}${"\u2591".repeat(width - filled)}`;
1179
+ process.stdout.write(
1180
+ `\r${color.cyan("cleanwind")} ${label} ${color.green(bar)} ${String(progress).padStart(3, " ")}%`
1181
+ );
1182
+ }
1183
+ function clearProgress() {
1184
+ process.stdout.write("\r\x1B[2K");
1185
+ }
1186
+ function shouldRenderProgress() {
1187
+ return process.stdout.isTTY === true && process.env.CI === void 0;
1188
+ }
1189
+ function wait(milliseconds) {
1190
+ return new Promise((resolve) => {
1191
+ setTimeout(resolve, milliseconds);
1192
+ });
1193
+ }
1194
+ function printCheckResult(result, verbose, cwd) {
1108
1195
  if (result.ok) {
1109
- console.log("cleanwind: all files are clean.");
1196
+ printHeader("check");
1197
+ printStep("Loaded config", "done");
1198
+ printStep("Checked project", "done");
1199
+ printStep("Built report", "done");
1200
+ printSpacer();
1201
+ console.log(`${color.green(tree.primary)} ${color.bold("All files are clean")}`);
1110
1202
  return;
1111
1203
  }
1112
- console.log(`cleanwind: ${result.changedFiles.length} file(s) need cleanup.`);
1113
- printDiagnostics(result, verbose);
1114
- }
1115
- function printFixResult(result, verbose) {
1204
+ printHeader("check");
1205
+ printStep("Loaded config", "done");
1206
+ printStep("Checked project", "done");
1207
+ printStep("Built report", "done");
1208
+ printSpacer();
1209
+ printMetrics(
1210
+ result.changedFiles.length,
1211
+ result.changedFiles.length === 1 ? "file needs cleanup" : "files need cleanup",
1212
+ result.conflicts.length
1213
+ );
1214
+ printFileList("Files", result.changedFiles, cwd, verbose);
1215
+ printDiagnostics(result, verbose, cwd);
1216
+ printNextStep(result);
1217
+ }
1218
+ function printFixResult(result, verbose, cwd) {
1116
1219
  const written = result.writtenFiles.length;
1117
1220
  const changed = result.changedFiles.length;
1118
1221
  const mode = written > 0 ? "fixed" : "would change";
1119
- console.log(`cleanwind: ${mode} ${written > 0 ? written : changed} file(s).`);
1120
- printDiagnostics(result, verbose);
1222
+ printHeader("fix");
1223
+ printStep("Loaded config", "done");
1224
+ printStep(written > 0 ? "Applied cleanup" : "Previewed cleanup", "done");
1225
+ printStep("Built report", "done");
1226
+ printSpacer();
1227
+ printMetrics(
1228
+ written > 0 ? written : changed,
1229
+ mode === "fixed" ? written === 1 ? "file fixed" : "files fixed" : changed === 1 ? "file would change" : "files would change",
1230
+ result.conflicts.length
1231
+ );
1232
+ printFileList(written > 0 ? "Written" : "Would change", written > 0 ? result.writtenFiles : result.changedFiles, cwd, verbose);
1233
+ printDiagnostics(result, verbose, cwd);
1234
+ printNextStep(result);
1235
+ }
1236
+ function printHeader(command) {
1237
+ console.log(`${color.cyan("cleanwind")} ${color.bold(command)}`);
1238
+ console.log(tree.pipe);
1239
+ }
1240
+ function printStep(label, status) {
1241
+ console.log(`${tree.branch} ${color.green(tree.step)} ${label} - ${color.bold(status)}`);
1242
+ }
1243
+ function printSpacer() {
1244
+ console.log(tree.pipe);
1245
+ }
1246
+ function printMetrics(fileCount, fileLabel, conflictCount) {
1247
+ console.log(`${color.green(tree.primary)} ${color.bold(`${fileCount} ${fileLabel}`)}`);
1248
+ if (conflictCount > 0) {
1249
+ console.log(
1250
+ `${color.yellow(tree.secondary)} ${color.bold(`${conflictCount} conflict${conflictCount === 1 ? "" : "s"}`)} need review`
1251
+ );
1252
+ return;
1253
+ }
1254
+ console.log(`${color.green(tree.secondary)} ${color.bold("0 conflicts")} found`);
1255
+ }
1256
+ function printFileList(title, files, cwd, verbose) {
1257
+ if (files.length === 0) {
1258
+ return;
1259
+ }
1260
+ const shown = verbose ? files : files.slice(0, defaultListLimit);
1261
+ printSpacer();
1262
+ console.log(`${tree.branch} ${color.bold(title)}`);
1263
+ for (const [index, file] of shown.entries()) {
1264
+ const prefix = index === shown.length - 1 && (verbose || files.length === shown.length) ? tree.elbow : tree.tee;
1265
+ console.log(`${prefix} ${formatPath(file, cwd)}`);
1266
+ }
1267
+ if (!verbose && files.length > shown.length) {
1268
+ console.log(
1269
+ `${tree.elbow} ${color.dim(`${files.length - shown.length} more. Run with --verbose to show all.`)}`
1270
+ );
1271
+ }
1272
+ }
1273
+ function printDiagnostics(result, verbose, cwd) {
1274
+ printConflicts(result, verbose, cwd);
1275
+ printIssues(result, verbose, cwd);
1121
1276
  }
1122
- function printDiagnostics(result, verbose) {
1123
- for (const conflict of result.conflicts) {
1124
- console.error(
1125
- `${conflict.file}:${conflict.line} ${conflict.suggestion} (${conflict.conflictingClasses.join(" ")})`
1277
+ function printConflicts(result, verbose, cwd) {
1278
+ if (result.conflicts.length === 0) {
1279
+ return;
1280
+ }
1281
+ const conflicts = verbose ? result.conflicts : result.conflicts.slice(0, defaultListLimit);
1282
+ const groups = groupConflictsByFile(conflicts, cwd);
1283
+ printSpacer();
1284
+ console.log(`${tree.branch} ${color.bold("Conflicts")}`);
1285
+ for (const [groupIndex, group] of groups.entries()) {
1286
+ const isLastGroup = groupIndex === groups.length - 1 && (verbose || result.conflicts.length === conflicts.length);
1287
+ console.log(`${isLastGroup ? tree.elbow : tree.tee} ${color.cyan(group.file)}`);
1288
+ for (const [conflictIndex, conflict] of group.conflicts.entries()) {
1289
+ const isLastConflict = conflictIndex === group.conflicts.length - 1;
1290
+ console.log(
1291
+ `${tree.spacer}${isLastConflict ? "\u2514" : "\u251C"} L${conflict.line} ${conflict.conflictingClasses.join(" + ")}`
1292
+ );
1293
+ }
1294
+ }
1295
+ if (!verbose && result.conflicts.length > conflicts.length) {
1296
+ console.log(
1297
+ `${tree.elbow} ${color.dim(`${result.conflicts.length - conflicts.length} more conflict(s). Run with --verbose to show all.`)}`
1126
1298
  );
1127
1299
  }
1300
+ }
1301
+ function printIssues(result, verbose, cwd) {
1128
1302
  if (!verbose) {
1129
1303
  return;
1130
1304
  }
1131
- for (const issue of result.issues) {
1132
- console.error(`${issue.file}:${issue.line} [${issue.kind}] ${issue.message}`);
1305
+ const issues = result.issues.filter((issue) => issue.kind !== "conflict");
1306
+ if (issues.length === 0) {
1307
+ return;
1308
+ }
1309
+ printSpacer();
1310
+ console.log(`${tree.branch} ${color.bold("Diagnostics")}`);
1311
+ for (const [index, issue] of issues.entries()) {
1312
+ const prefix = index === issues.length - 1 ? tree.elbow : tree.tee;
1313
+ console.log(
1314
+ `${prefix} ${color.cyan(formatPath(issue.file, cwd))}:${issue.line} ${color.dim(`[${issue.kind}]`)} ${issue.message}`
1315
+ );
1133
1316
  }
1134
1317
  }
1318
+ function printNextStep(result) {
1319
+ if (result.conflicts.length > 0) {
1320
+ printSpacer();
1321
+ console.log(`${tree.last} ${color.bold("Next:")} Resolve conflicts, then run ${color.cyan("npx cleanwind fix --format")}`);
1322
+ return;
1323
+ }
1324
+ if (result.changedFiles.length > 0) {
1325
+ printSpacer();
1326
+ console.log(`${tree.last} ${color.bold("Next:")} Apply cleanup with ${color.cyan("npx cleanwind fix --format")}`);
1327
+ }
1328
+ }
1329
+ function formatPath(file, cwd) {
1330
+ const relative = path4.relative(path4.resolve(cwd), file);
1331
+ return (relative && !relative.startsWith("..") ? relative : file).replaceAll("\\", "/");
1332
+ }
1333
+ function groupConflictsByFile(conflicts, cwd) {
1334
+ const groups = [];
1335
+ for (const conflict of conflicts) {
1336
+ const file = formatPath(conflict.file, cwd);
1337
+ const current = groups.at(-1);
1338
+ if (current?.file === file) {
1339
+ current.conflicts.push(conflict);
1340
+ } else {
1341
+ groups.push({ file, conflicts: [conflict] });
1342
+ }
1343
+ }
1344
+ return groups;
1345
+ }
1346
+ function supportsColor() {
1347
+ return process.env.NO_COLOR === void 0 && process.stdout.isTTY === true;
1348
+ }
1349
+ function paint(code, value) {
1350
+ return supportsColor() ? `\x1B[${code}m${value}\x1B[0m` : value;
1351
+ }
1352
+ var color = {
1353
+ bold: (value) => paint("1", value),
1354
+ cyan: (value) => paint("36", value),
1355
+ dim: (value) => paint("2", value),
1356
+ green: (value) => paint("32", value),
1357
+ yellow: (value) => paint("33", value)
1358
+ };
1359
+ await program.parseAsync();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "cleanwind",
3
- "version": "0.3.0",
3
+ "version": "0.3.2",
4
4
  "description": "Clean imports and Tailwind CSS class names from the command line.",
5
5
  "type": "module",
6
6
  "license": "MIT",