cleanwind 0.1.0 → 0.1.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 (3) hide show
  1. package/README.md +3 -3
  2. package/dist/index.js +627 -2
  3. package/package.json +11 -4
package/README.md CHANGED
@@ -35,7 +35,7 @@ cleanwind fix --check
35
35
  cleanwind fix --cwd ./apps/web --verbose
36
36
  ```
37
37
 
38
- ## Packages
38
+ ## Package
39
39
 
40
- The CLI uses `@cleanwind/core` internally. Use `@cleanwind/core` directly when
41
- you want to build custom integrations.
40
+ The published CLI bundles the cleanwind core engine, so installing `cleanwind`
41
+ does not require a separate core package.
package/dist/index.js CHANGED
@@ -2,9 +2,634 @@
2
2
 
3
3
  // src/index.ts
4
4
  import { Command } from "commander";
5
- import { check, fix } from "@cleanwind/core";
5
+
6
+ // ../core/dist/index.js
7
+ import { existsSync } from "fs";
8
+ import path from "path";
9
+ import { createJiti } from "jiti";
10
+ import * as t2 from "@babel/types";
11
+ import MagicString from "magic-string";
12
+ import { parse } from "@babel/parser";
13
+ import * as t from "@babel/types";
14
+ import * as t3 from "@babel/types";
15
+ import MagicString2 from "magic-string";
16
+ import { promises as fs } from "fs";
17
+ import path3 from "path";
18
+ import { execFileSync } from "child_process";
19
+ import path2 from "path";
20
+ import fg from "fast-glob";
21
+ var defaultConfig = {
22
+ imports: true,
23
+ tailwind: true,
24
+ removeDuplicateImports: true,
25
+ removeDuplicateClasses: true,
26
+ sortImports: true,
27
+ sortTailwindClasses: true,
28
+ detectConflicts: true,
29
+ include: ["src/**/*.{js,jsx,ts,tsx}"],
30
+ exclude: ["node_modules", ".next", "dist"]
31
+ };
32
+ var configFiles = [
33
+ "cleanwind.config.ts",
34
+ "cleanwind.config.mts",
35
+ "cleanwind.config.js",
36
+ "cleanwind.config.mjs"
37
+ ];
38
+ async function loadConfig(cwd, configPath) {
39
+ const resolvedPath = configPath ? path.resolve(cwd, configPath) : findConfig(cwd);
40
+ if (!resolvedPath) {
41
+ return defaultConfig;
42
+ }
43
+ const jiti = createJiti(import.meta.url, { moduleCache: false });
44
+ const loaded = await jiti.import(resolvedPath, { default: true });
45
+ return {
46
+ ...defaultConfig,
47
+ ...loaded,
48
+ include: loaded.include ?? defaultConfig.include,
49
+ exclude: loaded.exclude ?? defaultConfig.exclude
50
+ };
51
+ }
52
+ function findConfig(cwd) {
53
+ for (const file of configFiles) {
54
+ const candidate = path.join(cwd, file);
55
+ if (existsSync(candidate)) {
56
+ return candidate;
57
+ }
58
+ }
59
+ return void 0;
60
+ }
61
+ var plugins = [
62
+ "jsx",
63
+ "typescript",
64
+ "decorators-legacy",
65
+ "classProperties",
66
+ "classPrivateProperties",
67
+ "classPrivateMethods",
68
+ "dynamicImport",
69
+ "importAttributes"
70
+ ];
71
+ function parseSource(source) {
72
+ return parse(source, {
73
+ sourceType: "module",
74
+ plugins,
75
+ errorRecovery: true,
76
+ tokens: true
77
+ });
78
+ }
79
+ var visitorKeys = t.VISITOR_KEYS;
80
+ function walkNode(node, enter, parent) {
81
+ enter(node, parent);
82
+ const keys = visitorKeys[node.type] ?? [];
83
+ const record = node;
84
+ for (const key of keys) {
85
+ const value = record[key];
86
+ if (Array.isArray(value)) {
87
+ for (const child of value) {
88
+ if (isNode(child)) {
89
+ walkNode(child, enter, node);
90
+ }
91
+ }
92
+ } else if (isNode(value)) {
93
+ walkNode(value, enter, node);
94
+ }
95
+ }
96
+ }
97
+ function isNode(value) {
98
+ return typeof value === "object" && value !== null && "type" in value;
99
+ }
100
+ var ImportCleaner = class {
101
+ /** Checks whether imports would change after cleanwind normalization. */
102
+ static check(context) {
103
+ const fixed = this.fix(context);
104
+ const issues = [];
105
+ if (fixed.output !== context.source) {
106
+ issues.push({
107
+ file: context.filePath,
108
+ line: 1,
109
+ kind: "imports",
110
+ message: "Imports are not tidy."
111
+ });
112
+ }
113
+ return { output: fixed.output, issues };
114
+ }
115
+ /** Returns source text with unused, duplicated, and unordered imports normalized. */
116
+ static fix(context) {
117
+ const ast = parseSource(context.source);
118
+ const imports = ast.program.body.filter(t2.isImportDeclaration);
119
+ if (imports.length === 0) {
120
+ return { output: context.source, issues: [] };
121
+ }
122
+ const usedIdentifiers = collectUsedIdentifiers(ast);
123
+ const records = buildImportRecords(imports, usedIdentifiers, context.config.sortImports);
124
+ const spans = imports.map((declaration) => spanForImport(context.source, declaration));
125
+ const output = replaceImports(context.source, spans, renderImportBlock(records));
126
+ return { output, issues: [] };
127
+ }
128
+ };
129
+ function collectUsedIdentifiers(ast) {
130
+ const identifiers = /* @__PURE__ */ new Set();
131
+ for (const statement of ast.program.body) {
132
+ if (t2.isImportDeclaration(statement)) {
133
+ continue;
134
+ }
135
+ walkNode(statement, (node, parent) => {
136
+ if (t2.isIdentifier(node) && isIdentifierReference(parent, node)) {
137
+ identifiers.add(node.name);
138
+ }
139
+ if (t2.isJSXIdentifier(node) && isJSXIdentifierReference(parent, node)) {
140
+ identifiers.add(node.name);
141
+ }
142
+ });
143
+ }
144
+ return identifiers;
145
+ }
146
+ function isIdentifierReference(parent, node) {
147
+ if (!parent) {
148
+ return true;
149
+ }
150
+ if (t2.isVariableDeclarator(parent) && parent.id === node) return false;
151
+ if ((t2.isFunctionDeclaration(parent) || t2.isFunctionExpression(parent)) && parent.id === node)
152
+ return false;
153
+ if ((t2.isClassDeclaration(parent) || t2.isClassExpression(parent)) && parent.id === node)
154
+ return false;
155
+ if (t2.isObjectProperty(parent) && parent.key === node && !parent.computed) return false;
156
+ if (t2.isObjectMethod(parent) && parent.key === node && !parent.computed) return false;
157
+ if (t2.isMemberExpression(parent) && parent.property === node && !parent.computed) return false;
158
+ if (t2.isLabeledStatement(parent) && parent.label === node) return false;
159
+ if (t2.isTSTypeAliasDeclaration(parent) && parent.id === node) return false;
160
+ if (t2.isTSInterfaceDeclaration(parent) && parent.id === node) return false;
161
+ return true;
162
+ }
163
+ function isJSXIdentifierReference(parent, node) {
164
+ if (!parent) {
165
+ return false;
166
+ }
167
+ if (t2.isJSXAttribute(parent)) {
168
+ return false;
169
+ }
170
+ return t2.isJSXOpeningElement(parent) && parent.name === node || t2.isJSXClosingElement(parent) && parent.name === node || t2.isJSXMemberExpression(parent) && parent.object === node;
171
+ }
172
+ function buildImportRecords(declarations, usedIdentifiers, sort) {
173
+ const records = /* @__PURE__ */ new Map();
174
+ const sideEffects = [];
175
+ for (const declaration of declarations) {
176
+ const source = declaration.source.value;
177
+ const sideEffect = declaration.specifiers.length === 0;
178
+ if (sideEffect) {
179
+ sideEffects.push({
180
+ source,
181
+ named: [],
182
+ sideEffect: true,
183
+ group: classifyImport(source)
184
+ });
185
+ continue;
186
+ }
187
+ const key = source;
188
+ const record = records.get(key) ?? {
189
+ source,
190
+ named: [],
191
+ sideEffect: false,
192
+ group: classifyImport(source)
193
+ };
194
+ for (const specifier of declaration.specifiers) {
195
+ const local = specifier.local.name;
196
+ if (!usedIdentifiers.has(local)) {
197
+ continue;
198
+ }
199
+ if (t2.isImportDefaultSpecifier(specifier)) {
200
+ record.defaultName ??= local;
201
+ } else if (t2.isImportNamespaceSpecifier(specifier)) {
202
+ record.namespaceName ??= local;
203
+ } else if (t2.isImportSpecifier(specifier)) {
204
+ const declarationKind = declaration.importKind === "type" ? "type" : "value";
205
+ record.named.push(importPartFromSpecifier(specifier, declarationKind));
206
+ }
207
+ }
208
+ if (record.defaultName || record.namespaceName || record.named.length > 0) {
209
+ records.set(key, dedupeRecord(record));
210
+ }
211
+ }
212
+ const merged = [...records.values(), ...sideEffects];
213
+ return sort ? sortRecords(merged) : merged;
214
+ }
215
+ function importPartFromSpecifier(specifier, declarationKind) {
216
+ const imported = t2.isIdentifier(specifier.imported) ? specifier.imported.name : specifier.imported.value;
217
+ return {
218
+ imported,
219
+ local: specifier.local.name,
220
+ kind: specifier.importKind === "type" ? "type" : declarationKind
221
+ };
222
+ }
223
+ function dedupeRecord(record) {
224
+ const named = /* @__PURE__ */ new Map();
225
+ for (const part of record.named) {
226
+ named.set(`${part.kind}:${part.imported}:${part.local}`, part);
227
+ }
228
+ return {
229
+ ...record,
230
+ named: [...named.values()].sort((left, right) => left.imported.localeCompare(right.imported))
231
+ };
232
+ }
233
+ function sortRecords(records) {
234
+ const groupRank = {
235
+ package: 0,
236
+ alias: 1,
237
+ relative: 2
238
+ };
239
+ return [...records].sort((left, right) => {
240
+ const groupDelta = groupRank[left.group] - groupRank[right.group];
241
+ if (groupDelta !== 0) {
242
+ return groupDelta;
243
+ }
244
+ const sideEffectDelta = Number(left.sideEffect) - Number(right.sideEffect);
245
+ if (sideEffectDelta !== 0) {
246
+ return sideEffectDelta;
247
+ }
248
+ return left.source.localeCompare(right.source);
249
+ });
250
+ }
251
+ function classifyImport(source) {
252
+ if (source.startsWith("@/")) {
253
+ return "alias";
254
+ }
255
+ if (source.startsWith(".")) {
256
+ return "relative";
257
+ }
258
+ return "package";
259
+ }
260
+ function renderImportBlock(records) {
261
+ const groups = ["package", "alias", "relative"];
262
+ const renderedGroups = groups.map((group) => records.filter((record) => record.group === group).map(renderImportRecord)).filter((group) => group.length > 0).map((group) => group.join("\n"));
263
+ return renderedGroups.length > 0 ? `${renderedGroups.join("\n\n")}
264
+
265
+ ` : "";
266
+ }
267
+ function renderImportRecord(record) {
268
+ if (record.sideEffect) {
269
+ return `import ${JSON.stringify(record.source)};`;
270
+ }
271
+ const parts = [];
272
+ if (record.defaultName) {
273
+ parts.push(record.defaultName);
274
+ }
275
+ if (record.namespaceName) {
276
+ parts.push(`* as ${record.namespaceName}`);
277
+ }
278
+ if (record.named.length > 0) {
279
+ parts.push(`{ ${record.named.map(renderNamedPart).join(", ")} }`);
280
+ }
281
+ return `import ${parts.join(", ")} from ${JSON.stringify(record.source)};`;
282
+ }
283
+ function renderNamedPart(part) {
284
+ const prefix = part.kind === "type" ? "type " : "";
285
+ const names = part.imported === part.local ? part.imported : `${part.imported} as ${part.local}`;
286
+ return `${prefix}${names}`;
287
+ }
288
+ function spanForImport(source, declaration) {
289
+ const start = declaration.start ?? 0;
290
+ let end = declaration.end ?? start;
291
+ while (end < source.length && (source[end] === "\r" || source[end] === "\n")) {
292
+ end += 1;
293
+ }
294
+ return { start, end };
295
+ }
296
+ function replaceImports(source, spans, importBlock) {
297
+ const magic = new MagicString(source);
298
+ const sortedSpans = [...spans].sort((left, right) => left.start - right.start);
299
+ const firstStart = sortedSpans[0]?.start ?? 0;
300
+ for (const span of sortedSpans) {
301
+ magic.remove(span.start, span.end);
302
+ }
303
+ magic.appendLeft(firstStart, importBlock);
304
+ return magic.toString().replace(/^\n+/u, "");
305
+ }
306
+ var displayClasses = /* @__PURE__ */ new Set([
307
+ "block",
308
+ "inline-block",
309
+ "inline",
310
+ "flex",
311
+ "inline-flex",
312
+ "grid",
313
+ "inline-grid",
314
+ "contents",
315
+ "hidden"
316
+ ]);
317
+ var positionClasses = /* @__PURE__ */ new Set(["static", "fixed", "absolute", "relative", "sticky"]);
318
+ function normalizeClassList(value, sortClasses) {
319
+ const unique = [...new Set(splitClasses(value))];
320
+ const classes = sortClasses ? unique.sort(compareTailwindClasses) : unique;
321
+ return classes.join(" ");
322
+ }
323
+ function splitClasses(value) {
324
+ return value.split(/\s+/u).map((item) => item.trim()).filter(Boolean);
325
+ }
326
+ function compareTailwindClasses(left, right) {
327
+ const leftClass = classifyClass(left);
328
+ const rightClass = classifyClass(right);
329
+ const variantDelta = leftClass.variant.localeCompare(rightClass.variant);
330
+ if (variantDelta !== 0) {
331
+ return variantDelta;
332
+ }
333
+ const rankDelta = leftClass.rank - rightClass.rank;
334
+ if (rankDelta !== 0) {
335
+ return rankDelta;
336
+ }
337
+ return leftClass.utility.localeCompare(rightClass.utility);
338
+ }
339
+ function conflictKey(className) {
340
+ const classified = classifyClass(className);
341
+ const utility = stripImportant(classified.utility);
342
+ const variantPrefix = classified.variant ? `${classified.variant}:` : "";
343
+ if (displayClasses.has(utility)) {
344
+ return `${variantPrefix}display`;
345
+ }
346
+ if (positionClasses.has(utility)) {
347
+ return `${variantPrefix}position`;
348
+ }
349
+ const spacing = spacingConflictKey(utility);
350
+ if (spacing) {
351
+ return `${variantPrefix}${spacing}`;
352
+ }
353
+ if (/^text-(xs|sm|base|lg|xl|[2-9]xl)$/u.test(utility)) {
354
+ return `${variantPrefix}font-size`;
355
+ }
356
+ if (utility.startsWith("bg-")) {
357
+ return `${variantPrefix}background-color`;
358
+ }
359
+ return void 0;
360
+ }
361
+ function spacingConflictKey(utility) {
362
+ const match = /^(?<property>[mp])(?<axis>x|y|t|r|b|l)?-/u.exec(utility);
363
+ if (!match?.groups) {
364
+ return void 0;
365
+ }
366
+ const property = match.groups.property;
367
+ const axis = match.groups.axis ?? "all";
368
+ return `${property}-${axis}`;
369
+ }
370
+ function classifyClass(className) {
371
+ const parts = splitVariant(className);
372
+ const utility = parts.utility;
373
+ return {
374
+ variant: parts.variant,
375
+ utility,
376
+ rank: rankUtility(stripImportant(utility))
377
+ };
378
+ }
379
+ function splitVariant(className) {
380
+ let bracketDepth = 0;
381
+ for (let index = className.length - 1; index >= 0; index -= 1) {
382
+ const char = className[index];
383
+ if (char === "]") {
384
+ bracketDepth += 1;
385
+ } else if (char === "[") {
386
+ bracketDepth -= 1;
387
+ } else if (char === ":" && bracketDepth === 0) {
388
+ return {
389
+ variant: className.slice(0, index),
390
+ utility: className.slice(index + 1)
391
+ };
392
+ }
393
+ }
394
+ return { variant: "", utility: className };
395
+ }
396
+ function stripImportant(utility) {
397
+ return utility.startsWith("!") ? utility.slice(1) : utility;
398
+ }
399
+ function rankUtility(utility) {
400
+ if (utility.startsWith("container")) return 0;
401
+ if (positionClasses.has(utility) || utility.startsWith("inset-")) return 10;
402
+ if (displayClasses.has(utility)) return 20;
403
+ if (utility.startsWith("float-") || utility.startsWith("clear-")) return 30;
404
+ if (utility.startsWith("flex-") || utility.startsWith("basis-") || utility.startsWith("grow") || utility.startsWith("shrink"))
405
+ return 40;
406
+ if (utility.startsWith("grid-") || utility.startsWith("col-") || utility.startsWith("row-") || utility.startsWith("gap-"))
407
+ return 50;
408
+ if (/^-?[mp][trblxy]?-/u.test(utility)) return 60;
409
+ if (/^(w|h|min-w|min-h|max-w|max-h)-/u.test(utility)) return 70;
410
+ if (utility.startsWith("font-") || utility.startsWith("text-") || utility.startsWith("leading-") || utility.startsWith("tracking-"))
411
+ return 80;
412
+ if (utility.startsWith("bg-")) return 90;
413
+ if (utility.startsWith("border") || utility.startsWith("rounded")) return 100;
414
+ if (utility.startsWith("shadow") || utility.startsWith("opacity-")) return 110;
415
+ if (utility.startsWith("transition") || utility.startsWith("duration-") || utility.startsWith("ease-"))
416
+ return 120;
417
+ if (utility.startsWith("animate-")) return 130;
418
+ return 1e3;
419
+ }
420
+ var TailwindCleaner = class {
421
+ /** Checks whether Tailwind class strings would change and reports conflicts. */
422
+ static check(context) {
423
+ const fixed = this.fix(context);
424
+ const issues = [];
425
+ if (fixed.output !== context.source) {
426
+ issues.push({
427
+ file: context.filePath,
428
+ line: 1,
429
+ kind: "tailwind",
430
+ message: "Tailwind classes are not tidy."
431
+ });
432
+ }
433
+ issues.push(...fixed.issues);
434
+ return fixed.conflicts ? { output: fixed.output, issues, conflicts: fixed.conflicts } : { output: fixed.output, issues };
435
+ }
436
+ /** Returns source text with duplicated and unordered Tailwind classes normalized. */
437
+ static fix(context) {
438
+ const ast = parseSource(context.source);
439
+ const segments = collectClassSegments(ast);
440
+ const magic = new MagicString2(context.source);
441
+ const conflicts = [];
442
+ for (const segment of segments) {
443
+ const nextValue = normalizeClassList(segment.value, context.config.sortTailwindClasses);
444
+ if (nextValue !== segment.value) {
445
+ magic.overwrite(segment.start, segment.end, nextValue);
446
+ }
447
+ if (context.config.detectConflicts) {
448
+ conflicts.push(
449
+ ...detectConflicts(context.filePath, segment.line, splitClasses(segment.value))
450
+ );
451
+ }
452
+ }
453
+ return {
454
+ output: magic.toString(),
455
+ issues: conflicts.map((conflict) => ({
456
+ file: conflict.file,
457
+ line: conflict.line,
458
+ kind: "conflict",
459
+ message: conflict.suggestion
460
+ })),
461
+ conflicts
462
+ };
463
+ }
464
+ };
465
+ function collectClassSegments(ast) {
466
+ const segments = [];
467
+ walkNode(ast, (node) => {
468
+ if (t3.isJSXAttribute(node)) {
469
+ if (!isClassAttribute(node)) {
470
+ return;
471
+ }
472
+ const value = node.value;
473
+ if (t3.isStringLiteral(value)) {
474
+ segments.push(segmentFromStringLiteral(value));
475
+ } else if (t3.isJSXExpressionContainer(value) && t3.isStringLiteral(value.expression)) {
476
+ segments.push(segmentFromStringLiteral(value.expression));
477
+ } else if (t3.isJSXExpressionContainer(value) && t3.isTemplateLiteral(value.expression) && value.expression.expressions.length === 0) {
478
+ const quasi = value.expression.quasis[0];
479
+ if (quasi) {
480
+ segments.push(segmentFromTemplateElement(quasi));
481
+ }
482
+ }
483
+ }
484
+ });
485
+ return segments;
486
+ }
487
+ function isClassAttribute(node) {
488
+ return t3.isJSXIdentifier(node.name) && (node.name.name === "className" || node.name.name === "class");
489
+ }
490
+ function segmentFromStringLiteral(node) {
491
+ const start = (node.start ?? 0) + 1;
492
+ const end = (node.end ?? start) - 1;
493
+ return {
494
+ start,
495
+ end,
496
+ value: node.value,
497
+ line: node.loc?.start.line ?? 1
498
+ };
499
+ }
500
+ function segmentFromTemplateElement(node) {
501
+ const start = (node.start ?? 0) + 1;
502
+ const end = (node.end ?? start) - 1;
503
+ return {
504
+ start,
505
+ end,
506
+ value: node.value.raw,
507
+ line: node.loc?.start.line ?? 1
508
+ };
509
+ }
510
+ function detectConflicts(file, line, classes) {
511
+ const groups = /* @__PURE__ */ new Map();
512
+ for (const className of classes) {
513
+ const key = conflictKey(className);
514
+ if (!key) {
515
+ continue;
516
+ }
517
+ const group = groups.get(key) ?? [];
518
+ group.push(className);
519
+ groups.set(key, group);
520
+ }
521
+ return [...groups.values()].flatMap((group) => {
522
+ const unique = [...new Set(group)];
523
+ if (unique.length <= 1) {
524
+ return [];
525
+ }
526
+ return {
527
+ file,
528
+ line,
529
+ conflictingClasses: unique,
530
+ suggestion: `Conflicting Tailwind utilities found: ${unique.join(", ")}. Choose the intended utility.`
531
+ };
532
+ });
533
+ }
534
+ var sourceExtensions = /* @__PURE__ */ new Set([".js", ".jsx", ".ts", ".tsx"]);
535
+ async function findProjectFiles(cwd, config, staged) {
536
+ if (staged) {
537
+ return findStagedFiles(cwd, config);
538
+ }
539
+ const files = await fg(config.include, {
540
+ cwd,
541
+ absolute: true,
542
+ dot: true,
543
+ ignore: config.exclude
544
+ });
545
+ return files.map((file) => path2.normalize(file));
546
+ }
547
+ function findStagedFiles(cwd, config) {
548
+ const output = execFileSync("git", ["diff", "--name-only", "--cached", "--diff-filter=ACMR"], {
549
+ cwd,
550
+ encoding: "utf8"
551
+ });
552
+ return output.split(/\r?\n/u).map((file) => file.trim()).filter(Boolean).filter((file) => sourceExtensions.has(path2.extname(file))).filter(
553
+ (file) => !config.exclude.some((excluded) => file === excluded || file.startsWith(`${excluded}/`))
554
+ ).map((file) => path2.resolve(cwd, file));
555
+ }
556
+ async function check(options = {}) {
557
+ const files = await processProjectFiles(options, "check");
558
+ const issues = [];
559
+ const conflicts = [];
560
+ const changedFiles = [];
561
+ for (const file of files) {
562
+ issues.push(...file.issues);
563
+ conflicts.push(...file.conflicts);
564
+ if (file.output !== file.source) {
565
+ changedFiles.push(file.filePath);
566
+ }
567
+ }
568
+ return {
569
+ ok: issues.length === 0 && conflicts.length === 0 && changedFiles.length === 0,
570
+ issues,
571
+ conflicts,
572
+ changedFiles
573
+ };
574
+ }
575
+ async function fix(options = {}) {
576
+ const files = await processProjectFiles(options, "fix");
577
+ const write = options.write ?? true;
578
+ const changes = [];
579
+ const writtenFiles = [];
580
+ const issues = files.flatMap((file) => file.issues);
581
+ const conflicts = files.flatMap((file) => file.conflicts);
582
+ for (const file of files) {
583
+ if (file.output === file.source) {
584
+ continue;
585
+ }
586
+ changes.push({ file: file.filePath, before: file.source, after: file.output });
587
+ if (write) {
588
+ await fs.writeFile(file.filePath, file.output, "utf8");
589
+ writtenFiles.push(file.filePath);
590
+ }
591
+ }
592
+ return {
593
+ ok: issues.length === 0 && conflicts.length === 0 && changes.length === 0,
594
+ issues,
595
+ conflicts,
596
+ changedFiles: changes.map((change) => change.file),
597
+ writtenFiles,
598
+ changes
599
+ };
600
+ }
601
+ async function processProjectFiles(options, mode) {
602
+ const cwd = path3.resolve(options.cwd ?? process.cwd());
603
+ const config = await loadConfig(cwd, options.configPath);
604
+ const files = await findProjectFiles(cwd, config, options.staged ?? false);
605
+ const processed = [];
606
+ for (const filePath of files) {
607
+ const source = await fs.readFile(filePath, "utf8");
608
+ let output = source;
609
+ const issues = [];
610
+ const conflicts = [];
611
+ if (config.imports) {
612
+ const result = runCleaner(ImportCleaner, mode, { filePath, source: output, config });
613
+ issues.push(...result.issues);
614
+ output = result.output ?? output;
615
+ }
616
+ if (config.tailwind) {
617
+ const result = runCleaner(TailwindCleaner, mode, { filePath, source: output, config });
618
+ issues.push(...result.issues);
619
+ conflicts.push(...result.conflicts ?? []);
620
+ output = result.output ?? output;
621
+ }
622
+ processed.push({ filePath, source, output, issues, conflicts });
623
+ }
624
+ return processed;
625
+ }
626
+ function runCleaner(cleaner, mode, context) {
627
+ return mode === "check" ? cleaner.check(context) : cleaner.fix(context);
628
+ }
629
+
630
+ // src/index.ts
6
631
  var program = new Command();
7
- program.name("cleanwind").description("Clean imports and Tailwind CSS class names.").version("0.1.0");
632
+ program.name("cleanwind").description("Clean imports and Tailwind CSS class names.").version("0.1.2");
8
633
  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").action(async (options) => {
9
634
  const runOptions = toRunOptions(options);
10
635
  if (options.write) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "cleanwind",
3
- "version": "0.1.0",
3
+ "version": "0.1.2",
4
4
  "description": "Clean imports and Tailwind CSS class names from the command line.",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -31,11 +31,18 @@
31
31
  "access": "public"
32
32
  },
33
33
  "scripts": {
34
- "build": "tsup src/index.ts --format esm --clean && tsc -p tsconfig.build.json --emitDeclarationOnly",
34
+ "build": "tsup && tsc -p tsconfig.build.json --emitDeclarationOnly",
35
35
  "typecheck": "tsc -p tsconfig.json --noEmit"
36
36
  },
37
37
  "dependencies": {
38
- "@cleanwind/core": "0.1.0",
39
- "commander": "^12.1.0"
38
+ "@babel/parser": "^7.26.3",
39
+ "@babel/types": "^7.26.3",
40
+ "commander": "^12.1.0",
41
+ "fast-glob": "^3.3.2",
42
+ "jiti": "^2.4.2",
43
+ "magic-string": "^0.30.17"
44
+ },
45
+ "devDependencies": {
46
+ "@cleanwind/core": "workspace:*"
40
47
  }
41
48
  }