cleanwind 0.3.1 → 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 +162 -52
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -1091,26 +1091,43 @@ function hasParseIssue(issues) {
1091
1091
 
1092
1092
  // src/index.ts
1093
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
+ };
1094
1105
  var program = new Command();
1095
- program.name("cleanwind").description("Clean imports and Tailwind CSS class names.").version("0.3.1");
1106
+ program.name("cleanwind").description("Clean imports and Tailwind CSS class names.").version("0.3.2");
1096
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) => {
1097
1108
  const runOptions = toRunOptions(options);
1098
1109
  if (options.write) {
1099
- const result2 = await fix({ ...runOptions, write: true });
1110
+ const result2 = await runWithProgress(
1111
+ "Fixing project",
1112
+ () => fix({ ...runOptions, write: true })
1113
+ );
1100
1114
  printFixResult(result2, options.verbose ?? false, runOptions.cwd ?? process.cwd());
1101
1115
  process.exitCode = result2.conflicts.length > 0 ? 1 : 0;
1102
1116
  return;
1103
1117
  }
1104
- const result = await check(runOptions);
1118
+ const result = await runWithProgress("Checking project", () => check(runOptions));
1105
1119
  printCheckResult(result, options.verbose ?? false, runOptions.cwd ?? process.cwd());
1106
1120
  process.exitCode = result.ok ? 0 : 1;
1107
1121
  });
1108
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) => {
1109
- const result = await fix({
1110
- ...toRunOptions(options),
1111
- write: options.check ? false : options.write ?? true,
1112
- staged: options.staged ?? false
1113
- });
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
+ );
1114
1131
  printFixResult(result, options.verbose ?? false, options.cwd ?? process.cwd());
1115
1132
  process.exitCode = result.conflicts.length > 0 ? 1 : 0;
1116
1133
  });
@@ -1125,18 +1142,74 @@ function toRunOptions(options) {
1125
1142
  if (options.format !== void 0) runOptions.format = options.format;
1126
1143
  return runOptions;
1127
1144
  }
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
+ }
1128
1194
  function printCheckResult(result, verbose, cwd) {
1129
1195
  if (result.ok) {
1130
- console.log(`${color.green("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")}`);
1131
1202
  return;
1132
1203
  }
1133
- console.log(`${color.bold("cleanwind check")}`);
1134
- console.log(
1135
- summaryLine(
1136
- result.changedFiles.length,
1137
- result.conflicts.length,
1138
- result.changedFiles.length === 1 ? "needs cleanup" : "need cleanup"
1139
- )
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
1140
1213
  );
1141
1214
  printFileList("Files", result.changedFiles, cwd, verbose);
1142
1215
  printDiagnostics(result, verbose, cwd);
@@ -1146,30 +1219,55 @@ function printFixResult(result, verbose, cwd) {
1146
1219
  const written = result.writtenFiles.length;
1147
1220
  const changed = result.changedFiles.length;
1148
1221
  const mode = written > 0 ? "fixed" : "would change";
1149
- console.log(`${color.bold("cleanwind fix")}`);
1150
- console.log(summaryLine(written > 0 ? written : changed, result.conflicts.length, mode));
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
+ );
1151
1232
  printFileList(written > 0 ? "Written" : "Would change", written > 0 ? result.writtenFiles : result.changedFiles, cwd, verbose);
1152
1233
  printDiagnostics(result, verbose, cwd);
1153
1234
  printNextStep(result);
1154
1235
  }
1155
- function summaryLine(fileCount, conflictCount, mode) {
1156
- const files = `${fileCount} file${fileCount === 1 ? "" : "s"}`;
1157
- const filePhrase = mode === "would change" ? `${mode} ${files}` : `${files} ${mode}`;
1158
- const conflicts = conflictCount > 0 ? `, ${color.yellow(`${conflictCount} conflict${conflictCount === 1 ? "" : "s"}`)}` : "";
1159
- return `${color.cyan(filePhrase)}${conflicts}.`;
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`);
1160
1255
  }
1161
1256
  function printFileList(title, files, cwd, verbose) {
1162
1257
  if (files.length === 0) {
1163
1258
  return;
1164
1259
  }
1165
1260
  const shown = verbose ? files : files.slice(0, defaultListLimit);
1166
- console.log("");
1167
- console.log(color.bold(title));
1168
- for (const file of shown) {
1169
- console.log(` ${formatPath(file, cwd)}`);
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)}`);
1170
1266
  }
1171
1267
  if (!verbose && files.length > shown.length) {
1172
- console.log(color.dim(` ... ${files.length - shown.length} more. Run with --verbose to show all.`));
1268
+ console.log(
1269
+ `${tree.elbow} ${color.dim(`${files.length - shown.length} more. Run with --verbose to show all.`)}`
1270
+ );
1173
1271
  }
1174
1272
  }
1175
1273
  function printDiagnostics(result, verbose, cwd) {
@@ -1181,24 +1279,22 @@ function printConflicts(result, verbose, cwd) {
1181
1279
  return;
1182
1280
  }
1183
1281
  const conflicts = verbose ? result.conflicts : result.conflicts.slice(0, defaultListLimit);
1184
- let currentFile = "";
1185
- console.error("");
1186
- console.error(color.bold("Conflicts"));
1187
- for (const conflict of conflicts) {
1188
- const file = formatPath(conflict.file, cwd);
1189
- if (file !== currentFile) {
1190
- currentFile = file;
1191
- console.error(` ${color.cyan(file)}`);
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
+ );
1192
1293
  }
1193
- console.error(
1194
- ` ${color.dim(String(conflict.line).padStart(4, " "))} ${conflict.conflictingClasses.join(" + ")}`
1195
- );
1196
1294
  }
1197
1295
  if (!verbose && result.conflicts.length > conflicts.length) {
1198
- console.error(
1199
- color.dim(
1200
- ` ... ${result.conflicts.length - conflicts.length} more conflict(s). Run with --verbose to show all.`
1201
- )
1296
+ console.log(
1297
+ `${tree.elbow} ${color.dim(`${result.conflicts.length - conflicts.length} more conflict(s). Run with --verbose to show all.`)}`
1202
1298
  );
1203
1299
  }
1204
1300
  }
@@ -1210,29 +1306,43 @@ function printIssues(result, verbose, cwd) {
1210
1306
  if (issues.length === 0) {
1211
1307
  return;
1212
1308
  }
1213
- console.error("");
1214
- console.error(color.bold("Diagnostics"));
1215
- for (const issue of issues) {
1216
- console.error(
1217
- ` ${color.cyan(formatPath(issue.file, cwd))}:${issue.line} ${color.dim(`[${issue.kind}]`)} ${issue.message}`
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}`
1218
1315
  );
1219
1316
  }
1220
1317
  }
1221
1318
  function printNextStep(result) {
1222
1319
  if (result.conflicts.length > 0) {
1223
- console.log("");
1224
- console.log(color.dim("Resolve conflicts manually, then run: npx cleanwind fix --format"));
1320
+ printSpacer();
1321
+ console.log(`${tree.last} ${color.bold("Next:")} Resolve conflicts, then run ${color.cyan("npx cleanwind fix --format")}`);
1225
1322
  return;
1226
1323
  }
1227
1324
  if (result.changedFiles.length > 0) {
1228
- console.log("");
1229
- console.log(color.dim("Apply cleanup with: npx cleanwind fix --format"));
1325
+ printSpacer();
1326
+ console.log(`${tree.last} ${color.bold("Next:")} Apply cleanup with ${color.cyan("npx cleanwind fix --format")}`);
1230
1327
  }
1231
1328
  }
1232
1329
  function formatPath(file, cwd) {
1233
1330
  const relative = path4.relative(path4.resolve(cwd), file);
1234
1331
  return (relative && !relative.startsWith("..") ? relative : file).replaceAll("\\", "/");
1235
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
+ }
1236
1346
  function supportsColor() {
1237
1347
  return process.env.NO_COLOR === void 0 && process.stdout.isTTY === true;
1238
1348
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "cleanwind",
3
- "version": "0.3.1",
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",