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/dist/cli/index.js CHANGED
@@ -2,23 +2,142 @@
2
2
  import {
3
3
  analyzeCommand,
4
4
  applyCommand,
5
- generateCommand
6
- } from "../chunk-N7HD4T2I.js";
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
- program.name("tailwind-unwind").description("Analyze Tailwind CSS class usage in React/Next.js projects").version("0.1.0");
12
- 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", "5").option("--min-size <n>", "Minimum classes per combination", "2").option("--max-size <n>", "Maximum classes per combination", "5").option("--top <n>", "Number of top combinations to show", "10").option("--format <type>", "Output format: console or json", "console").option("--no-dedupe-subsets", "Include subset combinations in results").action(async (targetPath, opts) => {
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 format = opts.format === "json" ? "json" : "console";
126
+ const resolved = withNumericDefaults(
127
+ await resolveCommandOptions("analyze", opts, targetPath),
128
+ opts,
129
+ ANALYZE_DEFAULTS
130
+ );
15
131
  await analyzeCommand(targetPath, {
16
- minOccurrences: Number(opts.minOccurrences),
17
- minSize: Number(opts.minSize),
18
- maxSize: Number(opts.maxSize),
19
- top: Number(opts.top),
20
- format,
21
- dedupeSubsets: opts.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
- program.command("generate").description("Generate @layer components CSS from repeated className sets").argument("<path>", "Directory to analyze").requiredOption("--output <file>", "Output CSS file path").option("--min-occurrences <n>", "Minimum occurrences threshold", "3").option("--min-size <n>", "Minimum classes per combination", "2").option("--max-size <n>", "Maximum classes per combination", "5").option("--top <n>", "Number of combinations to generate", "10").option("--prefix <name>", "Namespace prefix for generated classes", "twu-").action(async (targetPath, opts) => {
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
- await generateCommand(targetPath, {
32
- output: opts.output,
33
- minOccurrences: Number(opts.minOccurrences),
34
- minSize: Number(opts.minSize),
35
- maxSize: Number(opts.maxSize),
36
- top: Number(opts.top),
37
- prefix: opts.prefix
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
- program.command("apply").description("Replace repeated className strings with generated component classes").argument("<path>", "Directory to modify").requiredOption("--output <file>", "Output CSS file path").option("--min-occurrences <n>", "Minimum occurrences threshold", "3").option("--min-size <n>", "Minimum classes per combination", "2").option("--max-size <n>", "Maximum classes per combination", "5").option("--top <n>", "Number of component classes to use", "10").option("--dry-run", "Preview replacements without writing files").option("--prefix <name>", "Namespace prefix for generated classes", "twu-").action(async (targetPath, opts) => {
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: opts.output,
49
- minOccurrences: Number(opts.minOccurrences),
50
- minSize: Number(opts.minSize),
51
- maxSize: Number(opts.maxSize),
52
- top: Number(opts.top),
53
- prefix: opts.prefix,
54
- dryRun: Boolean(opts.dryRun)
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);
@@ -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":"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","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, Node } from '@babel/types';
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 ApplyOptions extends AnalyzeOptions {
59
- output: string;
60
- dryRun?: boolean;
115
+ interface GeneratedComponent {
116
+ className: string;
117
+ classes: string[];
118
+ occurrences: number;
61
119
  }
62
- interface ApplyResult {
63
- filesModified: number;
64
- replacementsTotal: number;
65
- outputPath: string;
66
- componentsGenerated: number;
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
- * Generate component CSS and replace matching className strings in source files.
137
+ * Build a Tailwind CSS file with @layer components and @apply rules
138
+ * for the most frequent class combinations.
70
139
  */
71
- declare function applyCommand(targetPath: string, options: ApplyOptions): Promise<ApplyResult>;
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
- report: Awaited<ReturnType<typeof scanProject>>['report'];
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 GeneratedComponent {
125
- className: string;
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
- * Build a Tailwind CSS file with @layer components and @apply rules
145
- * for the most frequent class combinations.
283
+ * Load an analyze JSON report and return extractable combinations for generate.
146
284
  */
147
- declare function generateComponentCss(options: CssGeneratorOptions): CssGeneratorResult;
285
+ declare function loadExtractableCombinations(reportPath: string, options?: {
286
+ extractableOnly?: boolean;
287
+ }): Promise<LoadedAnalyzeReport>;
148
288
 
149
- interface BuildComponentsOptions extends PatternFinderOptions {
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
- line?: number;
170
- reason: string;
171
- classes: string[];
291
+ cwd?: string;
172
292
  }
173
- interface ReplaceClassNamesResult {
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
- replacements: ClassReplacement[];
176
- skipped: SkippedReplacement[];
177
- changed: boolean;
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
- * Replace exact matching className/class values with generated component classes.
310
+ * Extract Tailwind tokens from a cva()/tv() definition call.
181
311
  */
182
- declare function replaceClassNamesInSource(source: string, replacementMap: Map<string, string>, filePath: string): ReplaceClassNamesResult;
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 };