tailwind-unwind 0.1.1 → 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.
- package/README.md +78 -5
- package/dist/{chunk-N7HD4T2I.js → chunk-4GXMK3NB.js} +1079 -107
- package/dist/chunk-4GXMK3NB.js.map +1 -0
- package/dist/cli/index.js +190 -27
- package/dist/cli/index.js.map +1 -1
- package/dist/index.d.ts +204 -69
- package/dist/index.js +27 -1
- package/package.json +12 -2
- package/tailwind-unwind.config.example.json +27 -0
- package/dist/chunk-N7HD4T2I.js.map +0 -1
package/dist/cli/index.js
CHANGED
|
@@ -2,23 +2,142 @@
|
|
|
2
2
|
import {
|
|
3
3
|
analyzeCommand,
|
|
4
4
|
applyCommand,
|
|
5
|
-
generateCommand
|
|
6
|
-
|
|
5
|
+
generateCommand,
|
|
6
|
+
loadCommandOptions
|
|
7
|
+
} from "../chunk-4GXMK3NB.js";
|
|
7
8
|
|
|
8
9
|
// src/cli/index.ts
|
|
9
10
|
import { Command } from "commander";
|
|
11
|
+
import chalk from "chalk";
|
|
12
|
+
|
|
13
|
+
// src/cli/defaults.ts
|
|
14
|
+
var ANALYZE_DEFAULTS = {
|
|
15
|
+
minOccurrences: 5,
|
|
16
|
+
minSize: 2,
|
|
17
|
+
maxSize: 5,
|
|
18
|
+
top: 10
|
|
19
|
+
};
|
|
20
|
+
var GENERATE_DEFAULTS = {
|
|
21
|
+
minOccurrences: 3,
|
|
22
|
+
minSize: 2,
|
|
23
|
+
maxSize: 5,
|
|
24
|
+
top: 10,
|
|
25
|
+
prefix: "twu-"
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
// src/cli/parseOptions.ts
|
|
29
|
+
function splitPatterns(value) {
|
|
30
|
+
if (typeof value !== "string" || value.trim().length === 0) {
|
|
31
|
+
return void 0;
|
|
32
|
+
}
|
|
33
|
+
return value.split(",").map((part) => part.trim()).filter((part) => part.length > 0);
|
|
34
|
+
}
|
|
35
|
+
function optionalNumber(value) {
|
|
36
|
+
if (value === void 0 || value === null || value === "") {
|
|
37
|
+
return void 0;
|
|
38
|
+
}
|
|
39
|
+
const parsed = Number(value);
|
|
40
|
+
return Number.isFinite(parsed) ? parsed : void 0;
|
|
41
|
+
}
|
|
42
|
+
function cliNumber(value, fallback) {
|
|
43
|
+
const parsed = optionalNumber(value);
|
|
44
|
+
return parsed ?? fallback;
|
|
45
|
+
}
|
|
46
|
+
async function resolveCommandOptions(command, opts, targetPath) {
|
|
47
|
+
const resolved = await loadCommandOptions(
|
|
48
|
+
command,
|
|
49
|
+
{
|
|
50
|
+
configPath: opts.config,
|
|
51
|
+
minOccurrences: optionalNumber(opts.minOccurrences),
|
|
52
|
+
minSize: optionalNumber(opts.minSize),
|
|
53
|
+
maxSize: optionalNumber(opts.maxSize),
|
|
54
|
+
top: optionalNumber(opts.top),
|
|
55
|
+
prefix: opts.prefix,
|
|
56
|
+
dedupeSubsets: opts.dedupeSubsets,
|
|
57
|
+
include: splitPatterns(opts.include),
|
|
58
|
+
exclude: splitPatterns(opts.exclude),
|
|
59
|
+
output: opts.output,
|
|
60
|
+
dryRun: opts.dryRun,
|
|
61
|
+
prettier: opts.prettier,
|
|
62
|
+
fromReport: opts.fromReport,
|
|
63
|
+
extractableOnly: opts.extractableOnly
|
|
64
|
+
},
|
|
65
|
+
{ targetPath }
|
|
66
|
+
);
|
|
67
|
+
return {
|
|
68
|
+
minOccurrences: resolved.minOccurrences,
|
|
69
|
+
minSize: resolved.minSize,
|
|
70
|
+
maxSize: resolved.maxSize,
|
|
71
|
+
top: resolved.top,
|
|
72
|
+
prefix: resolved.prefix,
|
|
73
|
+
dedupeSubsets: resolved.dedupeSubsets,
|
|
74
|
+
include: resolved.include,
|
|
75
|
+
exclude: resolved.exclude,
|
|
76
|
+
configPath: resolved.configPath,
|
|
77
|
+
output: resolved.output,
|
|
78
|
+
names: resolved.names,
|
|
79
|
+
format: opts.format === "json" ? "json" : "console",
|
|
80
|
+
dryRun: opts.dryRun ?? resolved.dryRun,
|
|
81
|
+
prettier: opts.prettier ?? resolved.prettier,
|
|
82
|
+
fromReport: opts.fromReport ?? resolved.fromReport,
|
|
83
|
+
extractableOnly: opts.extractableOnly ?? resolved.extractableOnly
|
|
84
|
+
};
|
|
85
|
+
}
|
|
86
|
+
function withNumericDefaults(resolved, opts, defaults) {
|
|
87
|
+
return {
|
|
88
|
+
...resolved,
|
|
89
|
+
minOccurrences: resolved.minOccurrences ?? cliNumber(opts.minOccurrences, defaults.minOccurrences),
|
|
90
|
+
minSize: resolved.minSize ?? cliNumber(opts.minSize, defaults.minSize),
|
|
91
|
+
maxSize: resolved.maxSize ?? cliNumber(opts.maxSize, defaults.maxSize),
|
|
92
|
+
top: resolved.top ?? cliNumber(opts.top, defaults.top),
|
|
93
|
+
prefix: resolved.prefix ?? opts.prefix ?? defaults.prefix
|
|
94
|
+
};
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
// src/cli/version.ts
|
|
98
|
+
import { readFileSync } from "fs";
|
|
99
|
+
import path from "path";
|
|
100
|
+
import { fileURLToPath } from "url";
|
|
101
|
+
function readPackageVersion() {
|
|
102
|
+
const currentDir = path.dirname(fileURLToPath(import.meta.url));
|
|
103
|
+
const packageJsonPath = path.join(currentDir, "../../package.json");
|
|
104
|
+
const raw = readFileSync(packageJsonPath, "utf-8");
|
|
105
|
+
const pkg = JSON.parse(raw);
|
|
106
|
+
return pkg.version ?? "0.0.0";
|
|
107
|
+
}
|
|
108
|
+
var CLI_VERSION = readPackageVersion();
|
|
109
|
+
|
|
110
|
+
// src/cli/index.ts
|
|
10
111
|
var program = new Command();
|
|
11
|
-
|
|
12
|
-
|
|
112
|
+
function addSharedOptions(command) {
|
|
113
|
+
return command.option("--config <file>", "Path to tailwind-unwind config file").option(
|
|
114
|
+
"--include <patterns>",
|
|
115
|
+
'Comma-separated glob include patterns (e.g. "src/**/*.tsx")'
|
|
116
|
+
).option(
|
|
117
|
+
"--exclude <patterns>",
|
|
118
|
+
'Comma-separated glob exclude patterns (e.g. "**/*.test.tsx")'
|
|
119
|
+
);
|
|
120
|
+
}
|
|
121
|
+
program.name("tailwind-unwind").description("Analyze Tailwind CSS class usage in React/Next.js projects").version(CLI_VERSION);
|
|
122
|
+
addSharedOptions(
|
|
123
|
+
program.command("analyze").description("Scan a directory and report frequent Tailwind class combinations").argument("<path>", "Directory to analyze").option("--min-occurrences <n>", "Minimum occurrences threshold").option("--min-size <n>", "Minimum classes per combination").option("--max-size <n>", "Maximum classes per combination").option("--top <n>", "Number of top combinations to show").option("--format <type>", "Output format: console or json", "console").option("--no-dedupe-subsets", "Include subset combinations in results")
|
|
124
|
+
).action(async (targetPath, opts) => {
|
|
13
125
|
try {
|
|
14
|
-
const
|
|
126
|
+
const resolved = withNumericDefaults(
|
|
127
|
+
await resolveCommandOptions("analyze", opts, targetPath),
|
|
128
|
+
opts,
|
|
129
|
+
ANALYZE_DEFAULTS
|
|
130
|
+
);
|
|
15
131
|
await analyzeCommand(targetPath, {
|
|
16
|
-
minOccurrences:
|
|
17
|
-
minSize:
|
|
18
|
-
maxSize:
|
|
19
|
-
top:
|
|
20
|
-
format,
|
|
21
|
-
dedupeSubsets:
|
|
132
|
+
minOccurrences: resolved.minOccurrences,
|
|
133
|
+
minSize: resolved.minSize,
|
|
134
|
+
maxSize: resolved.maxSize,
|
|
135
|
+
top: resolved.top,
|
|
136
|
+
format: resolved.format,
|
|
137
|
+
dedupeSubsets: process.argv.includes("--no-dedupe-subsets") ? false : resolved.dedupeSubsets ?? true,
|
|
138
|
+
include: resolved.include,
|
|
139
|
+
exclude: resolved.exclude,
|
|
140
|
+
configPath: resolved.configPath
|
|
22
141
|
});
|
|
23
142
|
} catch (error) {
|
|
24
143
|
const message = error instanceof Error ? error.message : String(error);
|
|
@@ -26,15 +145,39 @@ program.command("analyze").description("Scan a directory and report frequent Tai
|
|
|
26
145
|
process.exit(1);
|
|
27
146
|
}
|
|
28
147
|
});
|
|
29
|
-
|
|
148
|
+
addSharedOptions(
|
|
149
|
+
program.command("generate").description("Generate @layer components CSS from repeated className sets").argument("[path]", "Directory to analyze").option("--output <file>", "Output CSS file path").option("--from-report <file>", "Generate from analyze JSON report").option("--extractable-only", "Only generate extractable patterns from analyze").option("--format <type>", "Output format: console or json", "console").option("--min-occurrences <n>", "Minimum occurrences threshold").option("--min-size <n>", "Minimum classes per combination").option("--max-size <n>", "Maximum classes per combination").option("--top <n>", "Number of combinations to generate").option("--prefix <name>", "Namespace prefix for generated classes")
|
|
150
|
+
).action(async (targetPath, opts) => {
|
|
30
151
|
try {
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
152
|
+
const resolved = withNumericDefaults(
|
|
153
|
+
await resolveCommandOptions("generate", opts, targetPath),
|
|
154
|
+
opts,
|
|
155
|
+
GENERATE_DEFAULTS
|
|
156
|
+
);
|
|
157
|
+
const output = opts.output ?? resolved.output;
|
|
158
|
+
const scanPath = targetPath ?? ".";
|
|
159
|
+
if (!output) {
|
|
160
|
+
console.error(chalk.red("Error: --output is required"));
|
|
161
|
+
process.exit(1);
|
|
162
|
+
}
|
|
163
|
+
if (!opts.fromReport && !resolved.fromReport && !targetPath) {
|
|
164
|
+
console.error(chalk.red("Error: <path> is required without --from-report"));
|
|
165
|
+
process.exit(1);
|
|
166
|
+
}
|
|
167
|
+
await generateCommand(scanPath, {
|
|
168
|
+
output,
|
|
169
|
+
minOccurrences: resolved.minOccurrences,
|
|
170
|
+
minSize: resolved.minSize,
|
|
171
|
+
maxSize: resolved.maxSize,
|
|
172
|
+
top: resolved.top,
|
|
173
|
+
prefix: resolved.prefix,
|
|
174
|
+
include: resolved.include,
|
|
175
|
+
exclude: resolved.exclude,
|
|
176
|
+
configPath: resolved.configPath,
|
|
177
|
+
names: resolved.names,
|
|
178
|
+
format: resolved.format,
|
|
179
|
+
fromReport: opts.fromReport ?? resolved.fromReport,
|
|
180
|
+
extractableOnly: Boolean(opts.extractableOnly || resolved.extractableOnly)
|
|
38
181
|
});
|
|
39
182
|
} catch (error) {
|
|
40
183
|
const message = error instanceof Error ? error.message : String(error);
|
|
@@ -42,16 +185,36 @@ program.command("generate").description("Generate @layer components CSS from rep
|
|
|
42
185
|
process.exit(1);
|
|
43
186
|
}
|
|
44
187
|
});
|
|
45
|
-
|
|
188
|
+
addSharedOptions(
|
|
189
|
+
program.command("apply").description("Replace repeated className strings with generated component classes").argument("<path>", "Directory to modify").option("--output <file>", "Output CSS file path").option("--from-report <file>", "Use component list from analyze JSON report").option("--extractable-only", "Only apply extractable patterns from analyze").option("--format <type>", "Output format: console or json", "console").option("--prettier", "Format modified files with Prettier when available").option("--min-occurrences <n>", "Minimum occurrences threshold").option("--min-size <n>", "Minimum classes per combination").option("--max-size <n>", "Maximum classes per combination").option("--top <n>", "Number of component classes to use").option("--dry-run", "Preview replacements without writing files").option("--prefix <name>", "Namespace prefix for generated classes")
|
|
190
|
+
).action(async (targetPath, opts) => {
|
|
46
191
|
try {
|
|
192
|
+
const resolved = withNumericDefaults(
|
|
193
|
+
await resolveCommandOptions("apply", opts, targetPath),
|
|
194
|
+
opts,
|
|
195
|
+
GENERATE_DEFAULTS
|
|
196
|
+
);
|
|
197
|
+
const output = opts.output ?? resolved.output;
|
|
198
|
+
if (!output) {
|
|
199
|
+
console.error(chalk.red("Error: --output is required"));
|
|
200
|
+
process.exit(1);
|
|
201
|
+
}
|
|
47
202
|
await applyCommand(targetPath, {
|
|
48
|
-
output
|
|
49
|
-
minOccurrences:
|
|
50
|
-
minSize:
|
|
51
|
-
maxSize:
|
|
52
|
-
top:
|
|
53
|
-
prefix:
|
|
54
|
-
|
|
203
|
+
output,
|
|
204
|
+
minOccurrences: resolved.minOccurrences,
|
|
205
|
+
minSize: resolved.minSize,
|
|
206
|
+
maxSize: resolved.maxSize,
|
|
207
|
+
top: resolved.top,
|
|
208
|
+
prefix: resolved.prefix,
|
|
209
|
+
include: resolved.include,
|
|
210
|
+
exclude: resolved.exclude,
|
|
211
|
+
configPath: resolved.configPath,
|
|
212
|
+
names: resolved.names,
|
|
213
|
+
format: resolved.format,
|
|
214
|
+
fromReport: opts.fromReport ?? resolved.fromReport,
|
|
215
|
+
extractableOnly: Boolean(opts.extractableOnly || resolved.extractableOnly),
|
|
216
|
+
dryRun: process.argv.includes("--dry-run") ? true : Boolean(resolved.dryRun),
|
|
217
|
+
prettier: Boolean(opts.prettier || resolved.prettier)
|
|
55
218
|
});
|
|
56
219
|
} catch (error) {
|
|
57
220
|
const message = error instanceof Error ? error.message : String(error);
|
package/dist/cli/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/cli/index.ts"],"sourcesContent":["#!/usr/bin/env node\n\nimport { Command } from 'commander';\nimport { analyzeCommand } from '../commands/analyze.js';\nimport { applyCommand } from '../commands/apply.js';\nimport { generateCommand } from '../commands/generate.js';\n\nconst program = new Command();\n\nprogram\n .name('tailwind-unwind')\n .description('Analyze Tailwind CSS class usage in React/Next.js projects')\n .version('0.1.0');\n\nprogram\n .command('analyze')\n .description('Scan a directory and report frequent Tailwind class combinations')\n .argument('<path>', 'Directory to analyze')\n .option('--min-occurrences <n>', 'Minimum occurrences threshold', '5')\n .option('--min-size <n>', 'Minimum classes per combination', '2')\n .option('--max-size <n>', 'Maximum classes per combination', '5')\n .option('--top <n>', 'Number of top combinations to show', '10')\n .option('--format <type>', 'Output format: console or json', 'console')\n .option('--no-dedupe-subsets', 'Include subset combinations in results')\n .action(async (targetPath: string, opts) => {\n try {\n const format = opts.format === 'json' ? 'json' : 'console';\n\n await analyzeCommand(targetPath, {\n minOccurrences: Number(opts.minOccurrences),\n minSize: Number(opts.minSize),\n maxSize: Number(opts.maxSize),\n top: Number(opts.top),\n format,\n dedupeSubsets: opts.dedupeSubsets,\n });\n } catch (error) {\n const message = error instanceof Error ? error.message : String(error);\n console.error('Unexpected error:', message);\n process.exit(1);\n }\n });\n\nprogram\n .command('generate')\n .description('Generate @layer components CSS from repeated className sets')\n .argument('<path>', 'Directory to analyze')\n .requiredOption('--output <file>', 'Output CSS file path')\n .option('--min-occurrences <n>', 'Minimum occurrences threshold', '3')\n .option('--min-size <n>', 'Minimum classes per combination', '2')\n .option('--max-size <n>', 'Maximum classes per combination', '5')\n .option('--top <n>', 'Number of combinations to generate', '10')\n .option('--prefix <name>', 'Namespace prefix for generated classes', 'twu-')\n .action(async (targetPath: string, opts) => {\n try {\n await generateCommand(targetPath, {\n output: opts.output,\n minOccurrences: Number(opts.minOccurrences),\n minSize: Number(opts.minSize),\n maxSize: Number(opts.maxSize),\n top: Number(opts.top),\n prefix: opts.prefix,\n });\n } catch (error) {\n const message = error instanceof Error ? error.message : String(error);\n console.error('Unexpected error:', message);\n process.exit(1);\n }\n });\n\nprogram\n .command('apply')\n .description('Replace repeated className strings with generated component classes')\n .argument('<path>', 'Directory to modify')\n .requiredOption('--output <file>', 'Output CSS file path')\n .option('--min-occurrences <n>', 'Minimum occurrences threshold', '3')\n .option('--min-size <n>', 'Minimum classes per combination', '2')\n .option('--max-size <n>', 'Maximum classes per combination', '5')\n .option('--top <n>', 'Number of component classes to use', '10')\n .option('--dry-run', 'Preview replacements without writing files')\n .option('--prefix <name>', 'Namespace prefix for generated classes', 'twu-')\n .action(async (targetPath: string, opts) => {\n try {\n await applyCommand(targetPath, {\n output: opts.output,\n minOccurrences: Number(opts.minOccurrences),\n minSize: Number(opts.minSize),\n maxSize: Number(opts.maxSize),\n top: Number(opts.top),\n prefix: opts.prefix,\n dryRun: Boolean(opts.dryRun),\n });\n } catch (error) {\n const message = error instanceof Error ? error.message : String(error);\n console.error('Unexpected error:', message);\n process.exit(1);\n }\n });\n\nprogram.parse();\n"],"mappings":";;;;;;;;AAEA,SAAS,eAAe;AAKxB,IAAM,UAAU,IAAI,QAAQ;AAE5B,QACG,KAAK,iBAAiB,EACtB,YAAY,4DAA4D,EACxE,QAAQ,OAAO;AAElB,QACG,QAAQ,SAAS,EACjB,YAAY,kEAAkE,EAC9E,SAAS,UAAU,sBAAsB,EACzC,OAAO,yBAAyB,iCAAiC,GAAG,EACpE,OAAO,kBAAkB,mCAAmC,GAAG,EAC/D,OAAO,kBAAkB,mCAAmC,GAAG,EAC/D,OAAO,aAAa,sCAAsC,IAAI,EAC9D,OAAO,mBAAmB,kCAAkC,SAAS,EACrE,OAAO,uBAAuB,wCAAwC,EACtE,OAAO,OAAO,YAAoB,SAAS;AAC1C,MAAI;AACF,UAAM,SAAS,KAAK,WAAW,SAAS,SAAS;AAEjD,UAAM,eAAe,YAAY;AAAA,MAC/B,gBAAgB,OAAO,KAAK,cAAc;AAAA,MAC1C,SAAS,OAAO,KAAK,OAAO;AAAA,MAC5B,SAAS,OAAO,KAAK,OAAO;AAAA,MAC5B,KAAK,OAAO,KAAK,GAAG;AAAA,MACpB;AAAA,MACA,eAAe,KAAK;AAAA,IACtB,CAAC;AAAA,EACH,SAAS,OAAO;AACd,UAAM,UAAU,iBAAiB,QAAQ,MAAM,UAAU,OAAO,KAAK;AACrE,YAAQ,MAAM,qBAAqB,OAAO;AAC1C,YAAQ,KAAK,CAAC;AAAA,EAChB;AACF,CAAC;AAEH,QACG,QAAQ,UAAU,EAClB,YAAY,6DAA6D,EACzE,SAAS,UAAU,sBAAsB,EACzC,eAAe,mBAAmB,sBAAsB,EACxD,OAAO,yBAAyB,iCAAiC,GAAG,EACpE,OAAO,kBAAkB,mCAAmC,GAAG,EAC/D,OAAO,kBAAkB,mCAAmC,GAAG,EAC/D,OAAO,aAAa,sCAAsC,IAAI,EAC9D,OAAO,mBAAmB,0CAA0C,MAAM,EAC1E,OAAO,OAAO,YAAoB,SAAS;AAC1C,MAAI;AACF,UAAM,gBAAgB,YAAY;AAAA,MAChC,QAAQ,KAAK;AAAA,MACb,gBAAgB,OAAO,KAAK,cAAc;AAAA,MAC1C,SAAS,OAAO,KAAK,OAAO;AAAA,MAC5B,SAAS,OAAO,KAAK,OAAO;AAAA,MAC5B,KAAK,OAAO,KAAK,GAAG;AAAA,MACpB,QAAQ,KAAK;AAAA,IACf,CAAC;AAAA,EACH,SAAS,OAAO;AACd,UAAM,UAAU,iBAAiB,QAAQ,MAAM,UAAU,OAAO,KAAK;AACrE,YAAQ,MAAM,qBAAqB,OAAO;AAC1C,YAAQ,KAAK,CAAC;AAAA,EAChB;AACF,CAAC;AAEH,QACG,QAAQ,OAAO,EACf,YAAY,qEAAqE,EACjF,SAAS,UAAU,qBAAqB,EACxC,eAAe,mBAAmB,sBAAsB,EACxD,OAAO,yBAAyB,iCAAiC,GAAG,EACpE,OAAO,kBAAkB,mCAAmC,GAAG,EAC/D,OAAO,kBAAkB,mCAAmC,GAAG,EAC/D,OAAO,aAAa,sCAAsC,IAAI,EAC9D,OAAO,aAAa,4CAA4C,EAChE,OAAO,mBAAmB,0CAA0C,MAAM,EAC1E,OAAO,OAAO,YAAoB,SAAS;AAC1C,MAAI;AACF,UAAM,aAAa,YAAY;AAAA,MAC7B,QAAQ,KAAK;AAAA,MACb,gBAAgB,OAAO,KAAK,cAAc;AAAA,MAC1C,SAAS,OAAO,KAAK,OAAO;AAAA,MAC5B,SAAS,OAAO,KAAK,OAAO;AAAA,MAC5B,KAAK,OAAO,KAAK,GAAG;AAAA,MACpB,QAAQ,KAAK;AAAA,MACb,QAAQ,QAAQ,KAAK,MAAM;AAAA,IAC7B,CAAC;AAAA,EACH,SAAS,OAAO;AACd,UAAM,UAAU,iBAAiB,QAAQ,MAAM,UAAU,OAAO,KAAK;AACrE,YAAQ,MAAM,qBAAqB,OAAO;AAC1C,YAAQ,KAAK,CAAC;AAAA,EAChB;AACF,CAAC;AAEH,QAAQ,MAAM;","names":[]}
|
|
1
|
+
{"version":3,"sources":["../../src/cli/index.ts","../../src/cli/defaults.ts","../../src/cli/parseOptions.ts","../../src/cli/version.ts"],"sourcesContent":["#!/usr/bin/env node\n\nimport { Command } from 'commander';\nimport chalk from 'chalk';\nimport { analyzeCommand } from '../commands/analyze.js';\nimport { applyCommand } from '../commands/apply.js';\nimport { generateCommand } from '../commands/generate.js';\nimport { ANALYZE_DEFAULTS, GENERATE_DEFAULTS } from './defaults.js';\nimport { resolveCommandOptions, withNumericDefaults } from './parseOptions.js';\nimport { CLI_VERSION } from './version.js';\n\nconst program = new Command();\n\nfunction addSharedOptions(command: Command): Command {\n return command\n .option('--config <file>', 'Path to tailwind-unwind config file')\n .option(\n '--include <patterns>',\n 'Comma-separated glob include patterns (e.g. \"src/**/*.tsx\")',\n )\n .option(\n '--exclude <patterns>',\n 'Comma-separated glob exclude patterns (e.g. \"**/*.test.tsx\")',\n );\n}\n\nprogram\n .name('tailwind-unwind')\n .description('Analyze Tailwind CSS class usage in React/Next.js projects')\n .version(CLI_VERSION);\n\naddSharedOptions(\n program\n .command('analyze')\n .description('Scan a directory and report frequent Tailwind class combinations')\n .argument('<path>', 'Directory to analyze')\n .option('--min-occurrences <n>', 'Minimum occurrences threshold')\n .option('--min-size <n>', 'Minimum classes per combination')\n .option('--max-size <n>', 'Maximum classes per combination')\n .option('--top <n>', 'Number of top combinations to show')\n .option('--format <type>', 'Output format: console or json', 'console')\n .option('--no-dedupe-subsets', 'Include subset combinations in results'),\n).action(async (targetPath: string, opts) => {\n try {\n const resolved = withNumericDefaults(\n await resolveCommandOptions('analyze', opts, targetPath),\n opts,\n ANALYZE_DEFAULTS,\n );\n\n await analyzeCommand(targetPath, {\n minOccurrences: resolved.minOccurrences,\n minSize: resolved.minSize,\n maxSize: resolved.maxSize,\n top: resolved.top,\n format: resolved.format,\n dedupeSubsets: process.argv.includes('--no-dedupe-subsets')\n ? false\n : (resolved.dedupeSubsets ?? true),\n include: resolved.include,\n exclude: resolved.exclude,\n configPath: resolved.configPath,\n });\n } catch (error) {\n const message = error instanceof Error ? error.message : String(error);\n console.error('Unexpected error:', message);\n process.exit(1);\n }\n});\n\naddSharedOptions(\n program\n .command('generate')\n .description('Generate @layer components CSS from repeated className sets')\n .argument('[path]', 'Directory to analyze')\n .option('--output <file>', 'Output CSS file path')\n .option('--from-report <file>', 'Generate from analyze JSON report')\n .option('--extractable-only', 'Only generate extractable patterns from analyze')\n .option('--format <type>', 'Output format: console or json', 'console')\n .option('--min-occurrences <n>', 'Minimum occurrences threshold')\n .option('--min-size <n>', 'Minimum classes per combination')\n .option('--max-size <n>', 'Maximum classes per combination')\n .option('--top <n>', 'Number of combinations to generate')\n .option('--prefix <name>', 'Namespace prefix for generated classes'),\n).action(async (targetPath: string | undefined, opts) => {\n try {\n const resolved = withNumericDefaults(\n await resolveCommandOptions('generate', opts, targetPath),\n opts,\n GENERATE_DEFAULTS,\n );\n const output = opts.output ?? resolved.output;\n const scanPath = targetPath ?? '.';\n\n if (!output) {\n console.error(chalk.red('Error: --output is required'));\n process.exit(1);\n }\n\n if (!opts.fromReport && !resolved.fromReport && !targetPath) {\n console.error(chalk.red('Error: <path> is required without --from-report'));\n process.exit(1);\n }\n\n await generateCommand(scanPath, {\n output,\n minOccurrences: resolved.minOccurrences,\n minSize: resolved.minSize,\n maxSize: resolved.maxSize,\n top: resolved.top,\n prefix: resolved.prefix,\n include: resolved.include,\n exclude: resolved.exclude,\n configPath: resolved.configPath,\n names: resolved.names,\n format: resolved.format,\n fromReport: opts.fromReport ?? resolved.fromReport,\n extractableOnly: Boolean(opts.extractableOnly || resolved.extractableOnly),\n });\n } catch (error) {\n const message = error instanceof Error ? error.message : String(error);\n console.error('Unexpected error:', message);\n process.exit(1);\n }\n});\n\naddSharedOptions(\n program\n .command('apply')\n .description('Replace repeated className strings with generated component classes')\n .argument('<path>', 'Directory to modify')\n .option('--output <file>', 'Output CSS file path')\n .option('--from-report <file>', 'Use component list from analyze JSON report')\n .option('--extractable-only', 'Only apply extractable patterns from analyze')\n .option('--format <type>', 'Output format: console or json', 'console')\n .option('--prettier', 'Format modified files with Prettier when available')\n .option('--min-occurrences <n>', 'Minimum occurrences threshold')\n .option('--min-size <n>', 'Minimum classes per combination')\n .option('--max-size <n>', 'Maximum classes per combination')\n .option('--top <n>', 'Number of component classes to use')\n .option('--dry-run', 'Preview replacements without writing files')\n .option('--prefix <name>', 'Namespace prefix for generated classes'),\n).action(async (targetPath: string, opts) => {\n try {\n const resolved = withNumericDefaults(\n await resolveCommandOptions('apply', opts, targetPath),\n opts,\n GENERATE_DEFAULTS,\n );\n const output = opts.output ?? resolved.output;\n\n if (!output) {\n console.error(chalk.red('Error: --output is required'));\n process.exit(1);\n }\n\n await applyCommand(targetPath, {\n output,\n minOccurrences: resolved.minOccurrences,\n minSize: resolved.minSize,\n maxSize: resolved.maxSize,\n top: resolved.top,\n prefix: resolved.prefix,\n include: resolved.include,\n exclude: resolved.exclude,\n configPath: resolved.configPath,\n names: resolved.names,\n format: resolved.format,\n fromReport: opts.fromReport ?? resolved.fromReport,\n extractableOnly: Boolean(opts.extractableOnly || resolved.extractableOnly),\n dryRun: process.argv.includes('--dry-run')\n ? true\n : Boolean(resolved.dryRun),\n prettier: Boolean(opts.prettier || resolved.prettier),\n });\n } catch (error) {\n const message = error instanceof Error ? error.message : String(error);\n console.error('Unexpected error:', message);\n process.exit(1);\n }\n});\n\nprogram.parse();\n","export const ANALYZE_DEFAULTS = {\n minOccurrences: 5,\n minSize: 2,\n maxSize: 5,\n top: 10,\n} as const;\n\nexport const GENERATE_DEFAULTS = {\n minOccurrences: 3,\n minSize: 2,\n maxSize: 5,\n top: 10,\n prefix: 'twu-',\n} as const;\n","import { loadCommandOptions } from '../config/loadConfig.js';\nimport type { CliCommand } from '../config/types.js';\nimport type { AnalyzeOptions } from '../parser/types.js';\n\nfunction splitPatterns(value: unknown): string[] | undefined {\n if (typeof value !== 'string' || value.trim().length === 0) {\n return undefined;\n }\n\n return value\n .split(',')\n .map((part) => part.trim())\n .filter((part) => part.length > 0);\n}\n\nfunction optionalNumber(value: unknown): number | undefined {\n if (value === undefined || value === null || value === '') {\n return undefined;\n }\n\n const parsed = Number(value);\n return Number.isFinite(parsed) ? parsed : undefined;\n}\n\nexport interface RawCliOptions {\n config?: string;\n minOccurrences?: string | number;\n minSize?: string | number;\n maxSize?: string | number;\n top?: string | number;\n prefix?: string;\n dedupeSubsets?: boolean;\n include?: string;\n exclude?: string;\n output?: string;\n format?: string;\n dryRun?: boolean;\n prettier?: boolean;\n fromReport?: string;\n extractableOnly?: boolean;\n}\n\nfunction cliNumber(\n value: string | number | undefined,\n fallback: number,\n): number {\n const parsed = optionalNumber(value);\n return parsed ?? fallback;\n}\n\n/**\n * Merge config file values with CLI flags (CLI wins).\n */\nexport async function resolveCommandOptions(\n command: CliCommand,\n opts: RawCliOptions,\n targetPath?: string,\n): Promise<\n AnalyzeOptions & { output?: string; dryRun?: boolean; names?: Record<string, string> }\n> {\n const resolved = await loadCommandOptions(\n command,\n {\n configPath: opts.config,\n minOccurrences: optionalNumber(opts.minOccurrences),\n minSize: optionalNumber(opts.minSize),\n maxSize: optionalNumber(opts.maxSize),\n top: optionalNumber(opts.top),\n prefix: opts.prefix,\n dedupeSubsets: opts.dedupeSubsets,\n include: splitPatterns(opts.include),\n exclude: splitPatterns(opts.exclude),\n output: opts.output,\n dryRun: opts.dryRun,\n prettier: opts.prettier,\n fromReport: opts.fromReport,\n extractableOnly: opts.extractableOnly,\n },\n { targetPath },\n );\n\n return {\n minOccurrences: resolved.minOccurrences,\n minSize: resolved.minSize,\n maxSize: resolved.maxSize,\n top: resolved.top,\n prefix: resolved.prefix,\n dedupeSubsets: resolved.dedupeSubsets,\n include: resolved.include,\n exclude: resolved.exclude,\n configPath: resolved.configPath,\n output: resolved.output,\n names: resolved.names,\n format: opts.format === 'json' ? 'json' : 'console',\n dryRun: opts.dryRun ?? resolved.dryRun,\n prettier: opts.prettier ?? resolved.prettier,\n fromReport: opts.fromReport ?? resolved.fromReport,\n extractableOnly: opts.extractableOnly ?? resolved.extractableOnly,\n };\n}\n\nexport function withNumericDefaults(\n resolved: Awaited<ReturnType<typeof resolveCommandOptions>>,\n opts: RawCliOptions,\n defaults: {\n minOccurrences: number;\n minSize: number;\n maxSize: number;\n top: number;\n prefix?: string;\n },\n) {\n return {\n ...resolved,\n minOccurrences:\n resolved.minOccurrences ??\n cliNumber(opts.minOccurrences, defaults.minOccurrences),\n minSize: resolved.minSize ?? cliNumber(opts.minSize, defaults.minSize),\n maxSize: resolved.maxSize ?? cliNumber(opts.maxSize, defaults.maxSize),\n top: resolved.top ?? cliNumber(opts.top, defaults.top),\n prefix: resolved.prefix ?? opts.prefix ?? defaults.prefix,\n };\n}\n\n/** @deprecated Use resolveCommandOptions */\nexport const resolveAnalyzeOptions = (\n opts: RawCliOptions,\n targetPath?: string,\n) => resolveCommandOptions('analyze', opts, targetPath);\n","import { readFileSync } from 'node:fs';\nimport path from 'node:path';\nimport { fileURLToPath } from 'node:url';\n\nfunction readPackageVersion(): string {\n const currentDir = path.dirname(fileURLToPath(import.meta.url));\n const packageJsonPath = path.join(currentDir, '../../package.json');\n const raw = readFileSync(packageJsonPath, 'utf-8');\n const pkg = JSON.parse(raw) as { version?: string };\n return pkg.version ?? '0.0.0';\n}\n\nexport const CLI_VERSION = readPackageVersion();\n"],"mappings":"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","names":[]}
|
package/dist/index.d.ts
CHANGED
|
@@ -1,4 +1,51 @@
|
|
|
1
|
-
import { Expression, JSXAttribute
|
|
1
|
+
import { Node, CallExpression, Expression, JSXAttribute } from '@babel/types';
|
|
2
|
+
|
|
3
|
+
type CliCommand = 'analyze' | 'generate' | 'apply';
|
|
4
|
+
interface CommandConfig {
|
|
5
|
+
minOccurrences?: number;
|
|
6
|
+
minSize?: number;
|
|
7
|
+
maxSize?: number;
|
|
8
|
+
top?: number;
|
|
9
|
+
prefix?: string;
|
|
10
|
+
output?: string;
|
|
11
|
+
dedupeSubsets?: boolean;
|
|
12
|
+
dryRun?: boolean;
|
|
13
|
+
prettier?: boolean;
|
|
14
|
+
fromReport?: string;
|
|
15
|
+
extractableOnly?: boolean;
|
|
16
|
+
}
|
|
17
|
+
/** Keys are space-separated utility strings; values are base class names (without prefix). */
|
|
18
|
+
type CustomNamesConfig = Record<string, string>;
|
|
19
|
+
interface TailwindUnwindConfigFile extends CommandConfig {
|
|
20
|
+
include?: string[];
|
|
21
|
+
exclude?: string[];
|
|
22
|
+
names?: CustomNamesConfig;
|
|
23
|
+
analyze?: CommandConfig;
|
|
24
|
+
generate?: CommandConfig;
|
|
25
|
+
apply?: CommandConfig;
|
|
26
|
+
}
|
|
27
|
+
interface TailwindUnwindConfig extends CommandConfig {
|
|
28
|
+
include?: string[];
|
|
29
|
+
exclude?: string[];
|
|
30
|
+
names?: CustomNamesConfig;
|
|
31
|
+
}
|
|
32
|
+
interface ResolvedCommandOptions extends TailwindUnwindConfig {
|
|
33
|
+
configPath?: string;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Load tailwind-unwind config for a command and merge CLI overrides on top.
|
|
38
|
+
*/
|
|
39
|
+
declare function loadCommandOptions(command: CliCommand, cliOptions: ResolvedCommandOptions, options?: {
|
|
40
|
+
cwd?: string;
|
|
41
|
+
targetPath?: string;
|
|
42
|
+
}): Promise<ResolvedCommandOptions>;
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Validate a parsed config object and throw with all errors at once.
|
|
46
|
+
*/
|
|
47
|
+
declare function validateConfigFile(raw: unknown, configPath: string): void;
|
|
48
|
+
declare function normalizeNamesConfig(names: CustomNamesConfig | undefined): Map<string, string>;
|
|
2
49
|
|
|
3
50
|
interface ClassNameExtraction {
|
|
4
51
|
classes: string[];
|
|
@@ -25,6 +72,8 @@ interface ClassCombination {
|
|
|
25
72
|
occurrences: number;
|
|
26
73
|
suggestion: string;
|
|
27
74
|
locations: CombinationLocation[];
|
|
75
|
+
/** True when generate/apply can extract this exact class set */
|
|
76
|
+
extractable?: boolean;
|
|
28
77
|
}
|
|
29
78
|
interface AnalysisStats {
|
|
30
79
|
filesScanned: number;
|
|
@@ -48,6 +97,14 @@ interface AnalyzeOptions {
|
|
|
48
97
|
dedupeSubsets?: boolean;
|
|
49
98
|
/** Namespace prefix for generated component classes (default: twu-) */
|
|
50
99
|
prefix?: string;
|
|
100
|
+
include?: string[];
|
|
101
|
+
exclude?: string[];
|
|
102
|
+
configPath?: string;
|
|
103
|
+
/** Custom base class names keyed by space-separated utility strings */
|
|
104
|
+
names?: Record<string, string>;
|
|
105
|
+
prettier?: boolean;
|
|
106
|
+
fromReport?: string;
|
|
107
|
+
extractableOnly?: boolean;
|
|
51
108
|
}
|
|
52
109
|
|
|
53
110
|
/**
|
|
@@ -55,20 +112,32 @@ interface AnalyzeOptions {
|
|
|
55
112
|
*/
|
|
56
113
|
declare function analyzeCommand(targetPath: string, options?: AnalyzeOptions): Promise<AnalysisReport>;
|
|
57
114
|
|
|
58
|
-
interface
|
|
59
|
-
|
|
60
|
-
|
|
115
|
+
interface GeneratedComponent {
|
|
116
|
+
className: string;
|
|
117
|
+
classes: string[];
|
|
118
|
+
occurrences: number;
|
|
61
119
|
}
|
|
62
|
-
interface
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
120
|
+
interface CssGeneratorOptions {
|
|
121
|
+
sourcePath: string;
|
|
122
|
+
combinations: ClassCombination[];
|
|
123
|
+
prefix?: string;
|
|
124
|
+
names?: CustomNamesConfig;
|
|
125
|
+
}
|
|
126
|
+
interface CssGeneratorResult {
|
|
127
|
+
css: string;
|
|
128
|
+
components: GeneratedComponent[];
|
|
67
129
|
}
|
|
130
|
+
interface AssignClassNamesOptions {
|
|
131
|
+
prefix?: string;
|
|
132
|
+
names?: CustomNamesConfig;
|
|
133
|
+
}
|
|
134
|
+
/** Assign unique, prefixed component class names. */
|
|
135
|
+
declare function assignComponentClassNames(combinations: ClassCombination[], options?: AssignClassNamesOptions): GeneratedComponent[];
|
|
68
136
|
/**
|
|
69
|
-
*
|
|
137
|
+
* Build a Tailwind CSS file with @layer components and @apply rules
|
|
138
|
+
* for the most frequent class combinations.
|
|
70
139
|
*/
|
|
71
|
-
declare function
|
|
140
|
+
declare function generateComponentCss(options: CssGeneratorOptions): CssGeneratorResult;
|
|
72
141
|
|
|
73
142
|
interface PatternFinderOptions {
|
|
74
143
|
minOccurrences?: number;
|
|
@@ -93,8 +162,92 @@ declare function findRepeatedClassSets(occurrences: ClassNameOccurrence[], optio
|
|
|
93
162
|
*/
|
|
94
163
|
declare function calculatePotentialReduction(occurrences: ClassNameOccurrence[], topCombinations: ClassCombination[]): number;
|
|
95
164
|
|
|
165
|
+
interface BuildComponentsOptions extends PatternFinderOptions {
|
|
166
|
+
sourcePath: string;
|
|
167
|
+
prefix?: string;
|
|
168
|
+
names?: Record<string, string>;
|
|
169
|
+
}
|
|
170
|
+
interface BuildComponentsResult {
|
|
171
|
+
components: GeneratedComponent[];
|
|
172
|
+
css: string;
|
|
173
|
+
replacementMap: Map<string, string>;
|
|
174
|
+
}
|
|
175
|
+
/** Build component classes, CSS, and a normalized-class → name lookup map. */
|
|
176
|
+
declare function buildComponents(occurrences: ClassNameOccurrence[], options: BuildComponentsOptions): BuildComponentsResult;
|
|
177
|
+
/** Build components from pre-selected combinations (e.g. analyze report). */
|
|
178
|
+
declare function buildComponentsFromCombinations(combinations: ClassCombination[], options: BuildComponentsOptions): BuildComponentsResult;
|
|
179
|
+
|
|
180
|
+
interface ClassReplacement {
|
|
181
|
+
filePath: string;
|
|
182
|
+
line?: number;
|
|
183
|
+
from: string;
|
|
184
|
+
to: string;
|
|
185
|
+
partial?: boolean;
|
|
186
|
+
}
|
|
187
|
+
interface SkippedReplacement {
|
|
188
|
+
filePath: string;
|
|
189
|
+
line?: number;
|
|
190
|
+
reason: string;
|
|
191
|
+
classes: string[];
|
|
192
|
+
}
|
|
193
|
+
interface ReplaceClassNamesResult {
|
|
194
|
+
source: string;
|
|
195
|
+
replacements: ClassReplacement[];
|
|
196
|
+
skipped: SkippedReplacement[];
|
|
197
|
+
changed: boolean;
|
|
198
|
+
}
|
|
199
|
+
/**
|
|
200
|
+
* Replace exact matching className/class values with generated component classes.
|
|
201
|
+
* Supports partial replacement inside cn()/clsx() when dynamic args are present.
|
|
202
|
+
*/
|
|
203
|
+
declare function replaceClassNamesInSource(source: string, replacementMap: Map<string, string>, filePath: string): ReplaceClassNamesResult;
|
|
204
|
+
|
|
205
|
+
interface GenerateJsonReport {
|
|
206
|
+
command: 'generate';
|
|
207
|
+
outputPath: string;
|
|
208
|
+
componentsGenerated: number;
|
|
209
|
+
components: GeneratedComponent[];
|
|
210
|
+
cssWritten: boolean;
|
|
211
|
+
}
|
|
212
|
+
interface ApplyJsonReport {
|
|
213
|
+
command: 'apply';
|
|
214
|
+
dryRun: boolean;
|
|
215
|
+
outputPath: string;
|
|
216
|
+
filesModified: number;
|
|
217
|
+
replacementsTotal: number;
|
|
218
|
+
componentsGenerated: number;
|
|
219
|
+
components: GeneratedComponent[];
|
|
220
|
+
replacements: ClassReplacement[];
|
|
221
|
+
skipped: SkippedReplacement[];
|
|
222
|
+
}
|
|
223
|
+
declare function printGenerateJsonReport(report: GenerateJsonReport): void;
|
|
224
|
+
declare function printApplyJsonReport(report: ApplyJsonReport): void;
|
|
225
|
+
|
|
226
|
+
interface ApplyOptions extends AnalyzeOptions {
|
|
227
|
+
output: string;
|
|
228
|
+
dryRun?: boolean;
|
|
229
|
+
}
|
|
230
|
+
interface ApplyResult {
|
|
231
|
+
filesModified: number;
|
|
232
|
+
replacementsTotal: number;
|
|
233
|
+
outputPath: string;
|
|
234
|
+
componentsGenerated: number;
|
|
235
|
+
components: Awaited<ReturnType<typeof buildComponents>>['components'];
|
|
236
|
+
replacements: ApplyJsonReport['replacements'];
|
|
237
|
+
skipped: ApplyJsonReport['skipped'];
|
|
238
|
+
prettierFormatted: string[];
|
|
239
|
+
}
|
|
240
|
+
/**
|
|
241
|
+
* Generate component CSS and replace matching className strings in source files.
|
|
242
|
+
*/
|
|
243
|
+
declare function applyCommand(targetPath: string, options: ApplyOptions): Promise<ApplyResult>;
|
|
244
|
+
|
|
96
245
|
interface ScanProjectOptions extends PatternFinderOptions {
|
|
97
246
|
targetPath: string;
|
|
247
|
+
include?: string[];
|
|
248
|
+
exclude?: string[];
|
|
249
|
+
/** Threshold used to mark combinations as extractable by generate/apply */
|
|
250
|
+
extractableMinOccurrences?: number;
|
|
98
251
|
}
|
|
99
252
|
interface ScanProjectResult {
|
|
100
253
|
resolvedPath: string;
|
|
@@ -114,72 +267,54 @@ interface GenerateOptions extends AnalyzeOptions {
|
|
|
114
267
|
interface GenerateResult {
|
|
115
268
|
outputPath: string;
|
|
116
269
|
componentsGenerated: number;
|
|
117
|
-
|
|
270
|
+
components: Awaited<ReturnType<typeof buildComponents>>['components'];
|
|
271
|
+
report: Awaited<ReturnType<typeof scanProject>>['report'] | null;
|
|
118
272
|
}
|
|
119
273
|
/**
|
|
120
274
|
* Analyze a project and write @layer components CSS to the output file.
|
|
121
275
|
*/
|
|
122
276
|
declare function generateCommand(targetPath: string, options: GenerateOptions): Promise<GenerateResult>;
|
|
123
277
|
|
|
124
|
-
interface
|
|
125
|
-
|
|
126
|
-
classes: string[];
|
|
127
|
-
occurrences: number;
|
|
128
|
-
}
|
|
129
|
-
interface CssGeneratorOptions {
|
|
130
|
-
sourcePath: string;
|
|
278
|
+
interface LoadedAnalyzeReport {
|
|
279
|
+
targetPath: string;
|
|
131
280
|
combinations: ClassCombination[];
|
|
132
|
-
prefix?: string;
|
|
133
|
-
}
|
|
134
|
-
interface CssGeneratorResult {
|
|
135
|
-
css: string;
|
|
136
|
-
components: GeneratedComponent[];
|
|
137
|
-
}
|
|
138
|
-
interface AssignClassNamesOptions {
|
|
139
|
-
prefix?: string;
|
|
140
281
|
}
|
|
141
|
-
/** Assign unique, prefixed component class names. */
|
|
142
|
-
declare function assignComponentClassNames(combinations: ClassCombination[], options?: AssignClassNamesOptions): GeneratedComponent[];
|
|
143
282
|
/**
|
|
144
|
-
*
|
|
145
|
-
* for the most frequent class combinations.
|
|
283
|
+
* Load an analyze JSON report and return extractable combinations for generate.
|
|
146
284
|
*/
|
|
147
|
-
declare function
|
|
285
|
+
declare function loadExtractableCombinations(reportPath: string, options?: {
|
|
286
|
+
extractableOnly?: boolean;
|
|
287
|
+
}): Promise<LoadedAnalyzeReport>;
|
|
148
288
|
|
|
149
|
-
interface
|
|
150
|
-
sourcePath: string;
|
|
151
|
-
prefix?: string;
|
|
152
|
-
}
|
|
153
|
-
interface BuildComponentsResult {
|
|
154
|
-
components: GeneratedComponent[];
|
|
155
|
-
css: string;
|
|
156
|
-
replacementMap: Map<string, string>;
|
|
157
|
-
}
|
|
158
|
-
/** Build component classes, CSS, and a normalized-class → name lookup map. */
|
|
159
|
-
declare function buildComponents(occurrences: ClassNameOccurrence[], options: BuildComponentsOptions): BuildComponentsResult;
|
|
160
|
-
|
|
161
|
-
interface ClassReplacement {
|
|
162
|
-
filePath: string;
|
|
163
|
-
line?: number;
|
|
164
|
-
from: string;
|
|
165
|
-
to: string;
|
|
166
|
-
}
|
|
167
|
-
interface SkippedReplacement {
|
|
289
|
+
interface FormatSourceOptions {
|
|
168
290
|
filePath: string;
|
|
169
|
-
|
|
170
|
-
reason: string;
|
|
171
|
-
classes: string[];
|
|
291
|
+
cwd?: string;
|
|
172
292
|
}
|
|
173
|
-
|
|
293
|
+
/**
|
|
294
|
+
* Format source with Prettier when available in the project.
|
|
295
|
+
* Returns original source unchanged if Prettier is not installed.
|
|
296
|
+
*/
|
|
297
|
+
declare function formatSource(source: string, options: FormatSourceOptions): Promise<{
|
|
174
298
|
source: string;
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
299
|
+
formatted: boolean;
|
|
300
|
+
}>;
|
|
301
|
+
declare function formatModifiedFiles(files: string[], sources: Map<string, string>, cwd?: string): Promise<{
|
|
302
|
+
formatted: string[];
|
|
303
|
+
skipped: string[];
|
|
304
|
+
}>;
|
|
305
|
+
|
|
306
|
+
/** Variant APIs that define reusable Tailwind class sets. */
|
|
307
|
+
declare const VARIANT_CALLEES: Set<string>;
|
|
308
|
+
declare function isVariantCallee(expression: Expression): boolean;
|
|
179
309
|
/**
|
|
180
|
-
*
|
|
310
|
+
* Extract Tailwind tokens from a cva()/tv() definition call.
|
|
181
311
|
*/
|
|
182
|
-
declare function
|
|
312
|
+
declare function extractClassesFromVariantCall(call: CallExpression): string[];
|
|
313
|
+
type VariantRegistry = Map<string, string[]>;
|
|
314
|
+
/**
|
|
315
|
+
* Collect `const x = cva(...)` / `const x = tv(...)` definitions in a file.
|
|
316
|
+
*/
|
|
317
|
+
declare function collectVariantRegistry(ast: Node): VariantRegistry;
|
|
183
318
|
|
|
184
319
|
/**
|
|
185
320
|
* Suggest a short, human-readable component class name from a utility list.
|
|
@@ -221,15 +356,11 @@ interface ExtractedClasses {
|
|
|
221
356
|
classes: string[];
|
|
222
357
|
isDynamic: boolean;
|
|
223
358
|
}
|
|
224
|
-
|
|
225
|
-
* Recursively pull static Tailwind tokens from JSX className expressions.
|
|
226
|
-
* Unknown/dynamic fragments set `isDynamic: true` but may still yield partial classes.
|
|
227
|
-
*/
|
|
228
|
-
declare function extractClassesFromExpression(expression: Expression): ExtractedClasses;
|
|
359
|
+
declare function extractClassesFromExpression(expression: Expression, registry?: VariantRegistry): ExtractedClasses;
|
|
229
360
|
|
|
230
361
|
declare function isClassAttribute(attr: JSXAttribute): boolean;
|
|
231
362
|
/** Extract Tailwind classes from a className/class JSX attribute. */
|
|
232
|
-
declare function extractFromJSXAttribute(attr: JSXAttribute): ClassNameExtraction | null;
|
|
363
|
+
declare function extractFromJSXAttribute(attr: JSXAttribute, registry?: VariantRegistry): ClassNameExtraction | null;
|
|
233
364
|
declare function parseSourceToAst(source: string): Node;
|
|
234
365
|
|
|
235
366
|
/**
|
|
@@ -241,10 +372,14 @@ declare function parseSource(source: string, filePath?: string): ParseResult;
|
|
|
241
372
|
*/
|
|
242
373
|
declare function parseFile(filePath: string): Promise<ParseResult>;
|
|
243
374
|
|
|
375
|
+
interface WalkSourceFilesOptions {
|
|
376
|
+
include?: string[];
|
|
377
|
+
exclude?: string[];
|
|
378
|
+
}
|
|
244
379
|
/**
|
|
245
380
|
* Recursively collect source files under `targetPath`, skipping common build/cache dirs.
|
|
246
381
|
*/
|
|
247
|
-
declare function walkSourceFiles(targetPath: string): Promise<string[]>;
|
|
382
|
+
declare function walkSourceFiles(targetPath: string, options?: WalkSourceFilesOptions): Promise<string[]>;
|
|
248
383
|
|
|
249
384
|
interface ConsoleReportOptions {
|
|
250
385
|
topLimit?: number;
|
|
@@ -262,4 +397,4 @@ declare const IGNORED_DIRECTORIES: readonly ["node_modules", ".next", "dist", "b
|
|
|
262
397
|
/** fast-glob ignore patterns — match directories at any depth. */
|
|
263
398
|
declare const IGNORE_PATTERNS: string[];
|
|
264
399
|
|
|
265
|
-
export { type AnalysisReport, type AnalysisStats, type AnalyzeOptions, type ApplyOptions, type ApplyResult, CLASS_MERGE_CALLEES, type ClassCombination, type ClassNameExtraction, type ClassNameOccurrence, type ClassReplacement, type CombinationLocation, type CssGeneratorOptions, type CssGeneratorResult, DEFAULT_CLASS_PREFIX, type GenerateOptions, type GenerateResult, type GeneratedComponent, IGNORED_DIRECTORIES, IGNORE_PATTERNS, type ParseResult, type ReplaceClassNamesResult, type SkippedReplacement, analyzeCommand, applyCommand, assignComponentClassNames, buildComponents, calculatePotentialReduction, dedupeSubsetCombinations, extractClassesFromExpression, extractFromJSXAttribute, findFrequentPatterns, findRepeatedClassSets, generateCombinations, generateCommand, generateComponentCss, isClassAttribute, isStrictSubset, normalizeClassPrefix, normalizeClasses, parseFile, parseSource, parseSourceToAst, printConsoleReport, printJsonReport, replaceClassNamesInSource, scanProject, splitClassString, suggestClassName, walkSourceFiles, withClassPrefix };
|
|
400
|
+
export { type AnalysisReport, type AnalysisStats, type AnalyzeOptions, type ApplyOptions, type ApplyResult, CLASS_MERGE_CALLEES, type ClassCombination, type ClassNameExtraction, type ClassNameOccurrence, type ClassReplacement, type CliCommand, type CombinationLocation, type CommandConfig, type CssGeneratorOptions, type CssGeneratorResult, type CustomNamesConfig, DEFAULT_CLASS_PREFIX, type GenerateOptions, type GenerateResult, type GeneratedComponent, IGNORED_DIRECTORIES, IGNORE_PATTERNS, type ParseResult, type ReplaceClassNamesResult, type SkippedReplacement, type TailwindUnwindConfig, type TailwindUnwindConfigFile, VARIANT_CALLEES, analyzeCommand, applyCommand, assignComponentClassNames, buildComponents, buildComponentsFromCombinations, calculatePotentialReduction, collectVariantRegistry, dedupeSubsetCombinations, extractClassesFromExpression, extractClassesFromVariantCall, extractFromJSXAttribute, findFrequentPatterns, findRepeatedClassSets, formatModifiedFiles, formatSource, generateCombinations, generateCommand, generateComponentCss, isClassAttribute, isStrictSubset, isVariantCallee, loadCommandOptions, loadExtractableCombinations, normalizeClassPrefix, normalizeClasses, normalizeNamesConfig, parseFile, parseSource, parseSourceToAst, printApplyJsonReport, printConsoleReport, printGenerateJsonReport, printJsonReport, replaceClassNamesInSource, scanProject, splitClassString, suggestClassName, validateConfigFile, walkSourceFiles, withClassPrefix };
|