tailwind-unwind 0.4.0 → 0.5.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
@@ -1,31 +1,22 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
+ ANALYZE_DEFAULTS,
4
+ DEFAULT_TARGET_PATH,
5
+ GENERATE_DEFAULTS,
3
6
  analyzeCommand,
4
7
  applyCommand,
8
+ checkCommand,
5
9
  generateCommand,
6
10
  initCommand,
7
- loadCommandOptions
8
- } from "../chunk-UXXIEFP4.js";
11
+ loadCommandOptions,
12
+ resolveOutputPath,
13
+ resolveTargetPath
14
+ } from "../chunk-RMTZCCPS.js";
9
15
 
10
16
  // src/cli/index.ts
11
17
  import { Command } from "commander";
12
18
  import chalk from "chalk";
13
19
 
14
- // src/cli/defaults.ts
15
- var ANALYZE_DEFAULTS = {
16
- minOccurrences: 5,
17
- minSize: 2,
18
- maxSize: 5,
19
- top: 10
20
- };
21
- var GENERATE_DEFAULTS = {
22
- minOccurrences: 3,
23
- minSize: 2,
24
- maxSize: 5,
25
- top: 10,
26
- prefix: "twu-"
27
- };
28
-
29
20
  // src/cli/parseOptions.ts
30
21
  function splitPatterns(value) {
31
22
  if (typeof value !== "string" || value.trim().length === 0) {
@@ -138,6 +129,13 @@ function addSharedOptions(command) {
138
129
  "Only scan git-changed files (optional ref, default: working tree vs HEAD)"
139
130
  );
140
131
  }
132
+ function optionalCliNumber(value) {
133
+ if (value === void 0 || value === null || value === "") {
134
+ return void 0;
135
+ }
136
+ const parsed = Number(value);
137
+ return Number.isFinite(parsed) ? parsed : void 0;
138
+ }
141
139
  function resolveChangedFlag(opts) {
142
140
  if (!process.argv.includes("--changed")) {
143
141
  return void 0;
@@ -148,14 +146,15 @@ function resolveChangedFlag(opts) {
148
146
  return true;
149
147
  }
150
148
  program.name("tailwind-unwind").description("Analyze Tailwind CSS class usage in React/Next.js projects").version(CLI_VERSION);
151
- program.command("init").description("Create a starter tailwind-unwind.config.json from project scan").argument("<path>", "Project directory").option("--output <file>", "Config output path").option("--force", "Overwrite existing config file").option("--min-occurrences <n>", "Minimum occurrences threshold").option("--top <n>", "Number of patterns to include in names").option("--prefix <name>", "Namespace prefix for generated classes").action(async (targetPath, opts) => {
149
+ program.command("init").description("Create a starter tailwind-unwind.config.json from project scan").argument("[path]", "Project directory", DEFAULT_TARGET_PATH).option("--output <file>", "Config output path").option("--force", "Overwrite existing config file").option("--min-occurrences <n>", "Minimum occurrences threshold").option("--top <n>", "Number of patterns to include in names").option("--prefix <name>", "Namespace prefix for generated classes").action(async (targetPath, opts) => {
152
150
  try {
151
+ const scanPath = resolveTargetPath(targetPath);
153
152
  const resolved = withNumericDefaults(
154
- await resolveCommandOptions("init", opts, targetPath),
153
+ await resolveCommandOptions("init", opts, scanPath),
155
154
  opts,
156
155
  ANALYZE_DEFAULTS
157
156
  );
158
- await initCommand(targetPath, {
157
+ await initCommand(scanPath, {
159
158
  output: opts.output ?? resolved.output,
160
159
  force: Boolean(opts.force || resolved.force),
161
160
  minOccurrences: resolved.minOccurrences,
@@ -171,16 +170,23 @@ program.command("init").description("Create a starter tailwind-unwind.config.jso
171
170
  }
172
171
  });
173
172
  addSharedOptions(
174
- 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")
173
+ program.command("analyze").description("Scan a directory and report frequent Tailwind class combinations").argument("[path]", "Directory to analyze", DEFAULT_TARGET_PATH).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")
175
174
  ).action(async (targetPath, opts) => {
176
175
  try {
176
+ const scanPath = resolveTargetPath(targetPath);
177
177
  const resolved = withNumericDefaults(
178
- await resolveCommandOptions("analyze", { ...opts, changed: resolveChangedFlag(opts) }, targetPath),
178
+ await resolveCommandOptions("analyze", { ...opts, changed: resolveChangedFlag(opts) }, scanPath),
179
179
  opts,
180
180
  ANALYZE_DEFAULTS
181
181
  );
182
- await analyzeCommand(targetPath, {
182
+ const generateResolved = withNumericDefaults(
183
+ await resolveCommandOptions("generate", { ...opts, changed: resolveChangedFlag(opts) }, scanPath),
184
+ opts,
185
+ GENERATE_DEFAULTS
186
+ );
187
+ await analyzeCommand(scanPath, {
183
188
  minOccurrences: resolved.minOccurrences,
189
+ extractableMinOccurrences: generateResolved.minOccurrences,
184
190
  minSize: resolved.minSize,
185
191
  maxSize: resolved.maxSize,
186
192
  top: resolved.top,
@@ -198,24 +204,16 @@ addSharedOptions(
198
204
  }
199
205
  });
200
206
  addSharedOptions(
201
- 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")
207
+ program.command("generate").description("Generate @layer components CSS from repeated className sets").argument("[path]", "Directory to scan", DEFAULT_TARGET_PATH).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")
202
208
  ).action(async (targetPath, opts) => {
203
209
  try {
210
+ const scanPath = resolveTargetPath(targetPath);
204
211
  const resolved = withNumericDefaults(
205
- await resolveCommandOptions("generate", { ...opts, changed: resolveChangedFlag(opts) }, targetPath),
212
+ await resolveCommandOptions("generate", { ...opts, changed: resolveChangedFlag(opts) }, scanPath),
206
213
  opts,
207
214
  GENERATE_DEFAULTS
208
215
  );
209
- const output = opts.output ?? resolved.output;
210
- const scanPath = targetPath ?? ".";
211
- if (!output) {
212
- console.error(chalk.red("Error: --output is required"));
213
- process.exit(1);
214
- }
215
- if (!opts.fromReport && !resolved.fromReport && !targetPath) {
216
- console.error(chalk.red("Error: <path> is required without --from-report"));
217
- process.exit(1);
218
- }
216
+ const output = resolveOutputPath(opts.output, resolved.output);
219
217
  await generateCommand(scanPath, {
220
218
  output,
221
219
  minOccurrences: resolved.minOccurrences,
@@ -239,20 +237,17 @@ addSharedOptions(
239
237
  }
240
238
  });
241
239
  addSharedOptions(
242
- 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")
240
+ program.command("apply").description("Replace repeated className strings with generated component classes").argument("[path]", "Directory to modify", DEFAULT_TARGET_PATH).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("--verbose-skipped", "List every skipped replacement location").option("--prefix <name>", "Namespace prefix for generated classes")
243
241
  ).action(async (targetPath, opts) => {
244
242
  try {
243
+ const scanPath = resolveTargetPath(targetPath);
245
244
  const resolved = withNumericDefaults(
246
- await resolveCommandOptions("apply", { ...opts, changed: resolveChangedFlag(opts) }, targetPath),
245
+ await resolveCommandOptions("apply", { ...opts, changed: resolveChangedFlag(opts) }, scanPath),
247
246
  opts,
248
247
  GENERATE_DEFAULTS
249
248
  );
250
- const output = opts.output ?? resolved.output;
251
- if (!output) {
252
- console.error(chalk.red("Error: --output is required"));
253
- process.exit(1);
254
- }
255
- await applyCommand(targetPath, {
249
+ const output = resolveOutputPath(opts.output, resolved.output);
250
+ await applyCommand(scanPath, {
256
251
  output,
257
252
  minOccurrences: resolved.minOccurrences,
258
253
  minSize: resolved.minSize,
@@ -268,7 +263,48 @@ addSharedOptions(
268
263
  fromReport: opts.fromReport ?? resolved.fromReport,
269
264
  extractableOnly: Boolean(opts.extractableOnly || resolved.extractableOnly),
270
265
  dryRun: process.argv.includes("--dry-run") ? true : Boolean(resolved.dryRun),
271
- prettier: Boolean(opts.prettier || resolved.prettier)
266
+ prettier: Boolean(opts.prettier || resolved.prettier),
267
+ verboseSkipped: Boolean(opts.verboseSkipped)
268
+ });
269
+ } catch (error) {
270
+ const message = error instanceof Error ? error.message : String(error);
271
+ console.error("Unexpected error:", message);
272
+ process.exit(1);
273
+ }
274
+ });
275
+ addSharedOptions(
276
+ program.command("check").description("Scan for extractable duplicates and preview apply (dry-run)").argument("[path]", "Directory to scan", DEFAULT_TARGET_PATH).option("--output <file>", "CSS output path used in the apply preview").option("--format <type>", "Output format: console or json", "console").option("--min-occurrences <n>", "Minimum occurrences for the analyze list").option("--top <n>", "Number of extractable patterns to show").option("--fail-on-extractable <n>", "Exit 1 when extractable patterns exceed n").option("--verbose-skipped", "List every skipped replacement location").option("--prefix <name>", "Namespace prefix for generated classes")
277
+ ).action(async (targetPath, opts) => {
278
+ try {
279
+ const scanPath = resolveTargetPath(targetPath);
280
+ const analyzeResolved = withNumericDefaults(
281
+ await resolveCommandOptions("analyze", { ...opts, changed: resolveChangedFlag(opts) }, scanPath),
282
+ opts,
283
+ ANALYZE_DEFAULTS
284
+ );
285
+ const generateResolved = withNumericDefaults(
286
+ await resolveCommandOptions("generate", { ...opts, changed: resolveChangedFlag(opts) }, scanPath),
287
+ opts,
288
+ GENERATE_DEFAULTS
289
+ );
290
+ const output = resolveOutputPath(opts.output, generateResolved.output);
291
+ const failOnExtractable = process.argv.includes("--fail-on-extractable") ? optionalCliNumber(opts.failOnExtractable) ?? 0 : void 0;
292
+ await checkCommand(scanPath, {
293
+ minOccurrences: analyzeResolved.minOccurrences,
294
+ extractableMinOccurrences: generateResolved.minOccurrences,
295
+ minSize: analyzeResolved.minSize,
296
+ maxSize: analyzeResolved.maxSize,
297
+ top: analyzeResolved.top,
298
+ prefix: generateResolved.prefix,
299
+ include: analyzeResolved.include,
300
+ exclude: analyzeResolved.exclude,
301
+ changed: analyzeResolved.changed,
302
+ configPath: analyzeResolved.configPath ?? generateResolved.configPath,
303
+ names: generateResolved.names,
304
+ output,
305
+ format: opts.format === "json" ? "json" : "console",
306
+ failOnExtractable,
307
+ verboseSkipped: Boolean(opts.verboseSkipped)
272
308
  });
273
309
  } catch (error) {
274
310
  const message = error instanceof Error ? error.message : String(error);
@@ -1 +1 @@
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 { initCommand } from '../commands/init.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 .option(\n '--changed [ref]',\n 'Only scan git-changed files (optional ref, default: working tree vs HEAD)',\n );\n}\n\nfunction resolveChangedFlag(opts: { changed?: string | boolean }): boolean | string | undefined {\n if (!process.argv.includes('--changed')) {\n return undefined;\n }\n\n if (typeof opts.changed === 'string' && opts.changed.length > 0) {\n return opts.changed;\n }\n\n return true;\n}\n\nprogram\n .name('tailwind-unwind')\n .description('Analyze Tailwind CSS class usage in React/Next.js projects')\n .version(CLI_VERSION);\n\nprogram\n .command('init')\n .description('Create a starter tailwind-unwind.config.json from project scan')\n .argument('<path>', 'Project directory')\n .option('--output <file>', 'Config output path')\n .option('--force', 'Overwrite existing config file')\n .option('--min-occurrences <n>', 'Minimum occurrences threshold')\n .option('--top <n>', 'Number of patterns to include in names')\n .option('--prefix <name>', 'Namespace prefix for generated classes')\n .action(async (targetPath: string, opts) => {\n try {\n const resolved = withNumericDefaults(\n await resolveCommandOptions('init', opts, targetPath),\n opts,\n ANALYZE_DEFAULTS,\n );\n\n await initCommand(targetPath, {\n output: opts.output ?? resolved.output,\n force: Boolean(opts.force || resolved.force),\n minOccurrences: resolved.minOccurrences,\n top: resolved.top,\n prefix: resolved.prefix ?? GENERATE_DEFAULTS.prefix,\n include: resolved.include,\n exclude: resolved.exclude,\n });\n } catch (error) {\n const message = error instanceof Error ? error.message : String(error);\n console.error(chalk.red(`Error: ${message}`));\n process.exit(1);\n }\n });\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, changed: resolveChangedFlag(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 changed: resolved.changed,\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, changed: resolveChangedFlag(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 changed: resolved.changed,\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, changed: resolveChangedFlag(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 changed: resolved.changed,\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 changed?: string | boolean;\n force?: 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 changed: parseChanged(opts.changed),\n force: opts.force,\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 changed: parseChanged(opts.changed) ?? resolved.changed,\n force: opts.force ?? resolved.force,\n };\n}\n\nfunction parseChanged(value: unknown): boolean | string | undefined {\n if (value === undefined || value === null || value === '') {\n return undefined;\n }\n\n if (value === true || value === 'true') {\n return true;\n }\n\n if (typeof value === 'string') {\n return value;\n }\n\n return undefined;\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":[]}
1
+ {"version":3,"sources":["../../src/cli/index.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 { checkCommand } from '../commands/check.js';\nimport { generateCommand } from '../commands/generate.js';\nimport { initCommand } from '../commands/init.js';\nimport {\n ANALYZE_DEFAULTS,\n DEFAULT_TARGET_PATH,\n GENERATE_DEFAULTS,\n resolveOutputPath,\n resolveTargetPath,\n} 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 .option(\n '--changed [ref]',\n 'Only scan git-changed files (optional ref, default: working tree vs HEAD)',\n );\n}\n\nfunction optionalCliNumber(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\nfunction resolveChangedFlag(opts: { changed?: string | boolean }): boolean | string | undefined {\n if (!process.argv.includes('--changed')) {\n return undefined;\n }\n\n if (typeof opts.changed === 'string' && opts.changed.length > 0) {\n return opts.changed;\n }\n\n return true;\n}\n\nprogram\n .name('tailwind-unwind')\n .description('Analyze Tailwind CSS class usage in React/Next.js projects')\n .version(CLI_VERSION);\n\nprogram\n .command('init')\n .description('Create a starter tailwind-unwind.config.json from project scan')\n .argument('[path]', 'Project directory', DEFAULT_TARGET_PATH)\n .option('--output <file>', 'Config output path')\n .option('--force', 'Overwrite existing config file')\n .option('--min-occurrences <n>', 'Minimum occurrences threshold')\n .option('--top <n>', 'Number of patterns to include in names')\n .option('--prefix <name>', 'Namespace prefix for generated classes')\n .action(async (targetPath: string, opts) => {\n try {\n const scanPath = resolveTargetPath(targetPath);\n const resolved = withNumericDefaults(\n await resolveCommandOptions('init', opts, scanPath),\n opts,\n ANALYZE_DEFAULTS,\n );\n\n await initCommand(scanPath, {\n output: opts.output ?? resolved.output,\n force: Boolean(opts.force || resolved.force),\n minOccurrences: resolved.minOccurrences,\n top: resolved.top,\n prefix: resolved.prefix ?? GENERATE_DEFAULTS.prefix,\n include: resolved.include,\n exclude: resolved.exclude,\n });\n } catch (error) {\n const message = error instanceof Error ? error.message : String(error);\n console.error(chalk.red(`Error: ${message}`));\n process.exit(1);\n }\n });\n\naddSharedOptions(\n program\n .command('analyze')\n .description('Scan a directory and report frequent Tailwind class combinations')\n .argument('[path]', 'Directory to analyze', DEFAULT_TARGET_PATH)\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 scanPath = resolveTargetPath(targetPath);\n const resolved = withNumericDefaults(\n await resolveCommandOptions('analyze', { ...opts, changed: resolveChangedFlag(opts) }, scanPath),\n opts,\n ANALYZE_DEFAULTS,\n );\n const generateResolved = withNumericDefaults(\n await resolveCommandOptions('generate', { ...opts, changed: resolveChangedFlag(opts) }, scanPath),\n opts,\n GENERATE_DEFAULTS,\n );\n\n await analyzeCommand(scanPath, {\n minOccurrences: resolved.minOccurrences,\n extractableMinOccurrences: generateResolved.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 changed: resolved.changed,\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 scan', DEFAULT_TARGET_PATH)\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 scanPath = resolveTargetPath(targetPath);\n const resolved = withNumericDefaults(\n await resolveCommandOptions('generate', { ...opts, changed: resolveChangedFlag(opts) }, scanPath),\n opts,\n GENERATE_DEFAULTS,\n );\n const output = resolveOutputPath(opts.output, resolved.output);\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 changed: resolved.changed,\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', DEFAULT_TARGET_PATH)\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('--verbose-skipped', 'List every skipped replacement location')\n .option('--prefix <name>', 'Namespace prefix for generated classes'),\n).action(async (targetPath: string, opts) => {\n try {\n const scanPath = resolveTargetPath(targetPath);\n const resolved = withNumericDefaults(\n await resolveCommandOptions('apply', { ...opts, changed: resolveChangedFlag(opts) }, scanPath),\n opts,\n GENERATE_DEFAULTS,\n );\n const output = resolveOutputPath(opts.output, resolved.output);\n\n await applyCommand(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 changed: resolved.changed,\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 verboseSkipped: Boolean(opts.verboseSkipped),\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('check')\n .description('Scan for extractable duplicates and preview apply (dry-run)')\n .argument('[path]', 'Directory to scan', DEFAULT_TARGET_PATH)\n .option('--output <file>', 'CSS output path used in the apply preview')\n .option('--format <type>', 'Output format: console or json', 'console')\n .option('--min-occurrences <n>', 'Minimum occurrences for the analyze list')\n .option('--top <n>', 'Number of extractable patterns to show')\n .option('--fail-on-extractable <n>', 'Exit 1 when extractable patterns exceed n')\n .option('--verbose-skipped', 'List every skipped replacement location')\n .option('--prefix <name>', 'Namespace prefix for generated classes'),\n).action(async (targetPath: string, opts) => {\n try {\n const scanPath = resolveTargetPath(targetPath);\n const analyzeResolved = withNumericDefaults(\n await resolveCommandOptions('analyze', { ...opts, changed: resolveChangedFlag(opts) }, scanPath),\n opts,\n ANALYZE_DEFAULTS,\n );\n const generateResolved = withNumericDefaults(\n await resolveCommandOptions('generate', { ...opts, changed: resolveChangedFlag(opts) }, scanPath),\n opts,\n GENERATE_DEFAULTS,\n );\n const output = resolveOutputPath(opts.output, generateResolved.output);\n const failOnExtractable = process.argv.includes('--fail-on-extractable')\n ? optionalCliNumber(opts.failOnExtractable) ?? 0\n : undefined;\n\n await checkCommand(scanPath, {\n minOccurrences: analyzeResolved.minOccurrences,\n extractableMinOccurrences: generateResolved.minOccurrences,\n minSize: analyzeResolved.minSize,\n maxSize: analyzeResolved.maxSize,\n top: analyzeResolved.top,\n prefix: generateResolved.prefix,\n include: analyzeResolved.include,\n exclude: analyzeResolved.exclude,\n changed: analyzeResolved.changed,\n configPath: analyzeResolved.configPath ?? generateResolved.configPath,\n names: generateResolved.names,\n output,\n format: opts.format === 'json' ? 'json' : 'console',\n failOnExtractable,\n verboseSkipped: Boolean(opts.verboseSkipped),\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","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 changed?: string | boolean;\n force?: 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 changed: parseChanged(opts.changed),\n force: opts.force,\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 changed: parseChanged(opts.changed) ?? resolved.changed,\n force: opts.force ?? resolved.force,\n };\n}\n\nfunction parseChanged(value: unknown): boolean | string | undefined {\n if (value === undefined || value === null || value === '') {\n return undefined;\n }\n\n if (value === true || value === 'true') {\n return true;\n }\n\n if (typeof value === 'string') {\n return value;\n }\n\n return undefined;\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
@@ -84,6 +84,12 @@ interface AnalysisStats {
84
84
  totalClassUsages: number;
85
85
  topCombinations: ClassCombination[];
86
86
  potentialReductionPercent: number;
87
+ /** Exact duplicates eligible for generate/apply (not limited by --top) */
88
+ extractablePatternCount: number;
89
+ /** analyze min-occurrences used for the frequent-pattern list */
90
+ analyzeMinOccurrences: number;
91
+ /** threshold used to mark patterns as extractable */
92
+ extractableMinOccurrences: number;
87
93
  }
88
94
  interface AnalysisReport {
89
95
  targetPath: string;
@@ -92,6 +98,8 @@ interface AnalysisReport {
92
98
  }
93
99
  interface AnalyzeOptions {
94
100
  minOccurrences?: number;
101
+ /** Threshold for marking patterns as extractable (defaults to generate min-occurrences) */
102
+ extractableMinOccurrences?: number;
95
103
  minSize?: number;
96
104
  maxSize?: number;
97
105
  top?: number;
@@ -129,53 +137,50 @@ declare function initCommand(targetPath: string, options?: InitOptions): Promise
129
137
  */
130
138
  declare function analyzeCommand(targetPath: string, options?: AnalyzeOptions): Promise<AnalysisReport>;
131
139
 
132
- interface ClassReplacement {
133
- filePath: string;
134
- line?: number;
135
- from: string;
136
- to: string;
137
- partial?: boolean;
138
- }
139
- interface SkippedReplacement {
140
- filePath: string;
141
- line?: number;
142
- reason: string;
143
- classes: string[];
144
- }
145
- interface ReplaceClassNamesResult {
146
- source: string;
147
- replacements: ClassReplacement[];
148
- skipped: SkippedReplacement[];
149
- changed: boolean;
140
+ interface PatternFinderOptions {
141
+ minOccurrences?: number;
142
+ minSize?: number;
143
+ maxSize?: number;
144
+ topLimit?: number;
145
+ dedupeSubsets?: boolean;
150
146
  }
151
147
  /**
152
- * Replace exact matching className/class values with generated component classes.
153
- * Supports partial replacement inside cn()/clsx() when dynamic args are present.
148
+ * From every className occurrence, enumerate class subsets,
149
+ * count normalized combinations, and return the most frequent ones.
154
150
  */
155
- declare function replaceClassNamesInSource(source: string, replacementMap: Map<string, string>, filePath: string): ReplaceClassNamesResult;
156
-
157
- interface SavingsReport {
158
- replacementCount: number;
159
- utilityTokensBefore: number;
160
- utilityTokensAfter: number;
161
- tokensSaved: number;
162
- percentReduction: number;
163
- }
151
+ declare function findFrequentPatterns(occurrences: ClassNameOccurrence[], options?: PatternFinderOptions): ClassCombination[];
164
152
  /**
165
- * Estimate utility-token savings from applied replacements.
166
- * Each replacement collapses N utilities into 1 component class.
153
+ * Find exact duplicate className sets (full class lists per element).
154
+ * Better suited for CSS generation than combinatorial subset search.
167
155
  */
168
- declare function calculateSavings(replacements: ClassReplacement[]): SavingsReport;
169
-
156
+ declare function findRepeatedClassSets(occurrences: ClassNameOccurrence[], options?: PatternFinderOptions): ClassCombination[];
170
157
  /**
171
- * List source files changed relative to a git ref (default: working tree vs HEAD).
158
+ * Estimate how much repeated utility usage could be collapsed into components.
159
+ * Uses the best full combination: each redundant instance could replace N utilities with 1.
172
160
  */
173
- declare function getChangedSourceFiles(rootPath: string, ref?: string): Promise<string[]>;
174
- declare function isGitRepository(rootPath: string): Promise<boolean>;
161
+ declare function calculatePotentialReduction(occurrences: ClassNameOccurrence[], topCombinations: ClassCombination[]): number;
162
+
163
+ interface ScanProjectOptions extends PatternFinderOptions {
164
+ targetPath: string;
165
+ include?: string[];
166
+ exclude?: string[];
167
+ /** Threshold used to mark combinations as extractable by generate/apply */
168
+ extractableMinOccurrences?: number;
169
+ /** Scan only git-changed files; string value is the git ref to diff against */
170
+ changed?: boolean | string;
171
+ }
172
+ interface ScanProjectResult {
173
+ resolvedPath: string;
174
+ files: string[];
175
+ occurrences: ClassNameOccurrence[];
176
+ warnings: string[];
177
+ report: AnalysisReport;
178
+ extractableCombinations: ClassCombination[];
179
+ }
175
180
  /**
176
- * Changed source files under `scopePath`, resolved to absolute paths.
181
+ * Scan a directory, parse className usage, and build an analysis report.
177
182
  */
178
- declare function getChangedFilesInScope(scopePath: string, ref?: string): Promise<string[]>;
183
+ declare function scanProject(options: ScanProjectOptions): Promise<ScanProjectResult>;
179
184
 
180
185
  interface GeneratedComponent {
181
186
  className: string;
@@ -204,29 +209,6 @@ declare function assignComponentClassNames(combinations: ClassCombination[], opt
204
209
  */
205
210
  declare function generateComponentCss(options: CssGeneratorOptions): CssGeneratorResult;
206
211
 
207
- interface PatternFinderOptions {
208
- minOccurrences?: number;
209
- minSize?: number;
210
- maxSize?: number;
211
- topLimit?: number;
212
- dedupeSubsets?: boolean;
213
- }
214
- /**
215
- * From every className occurrence, enumerate class subsets,
216
- * count normalized combinations, and return the most frequent ones.
217
- */
218
- declare function findFrequentPatterns(occurrences: ClassNameOccurrence[], options?: PatternFinderOptions): ClassCombination[];
219
- /**
220
- * Find exact duplicate className sets (full class lists per element).
221
- * Better suited for CSS generation than combinatorial subset search.
222
- */
223
- declare function findRepeatedClassSets(occurrences: ClassNameOccurrence[], options?: PatternFinderOptions): ClassCombination[];
224
- /**
225
- * Estimate how much repeated utility usage could be collapsed into components.
226
- * Uses the best full combination: each redundant instance could replace N utilities with 1.
227
- */
228
- declare function calculatePotentialReduction(occurrences: ClassNameOccurrence[], topCombinations: ClassCombination[]): number;
229
-
230
212
  interface BuildComponentsOptions extends PatternFinderOptions {
231
213
  sourcePath: string;
232
214
  prefix?: string;
@@ -242,6 +224,44 @@ declare function buildComponents(occurrences: ClassNameOccurrence[], options: Bu
242
224
  /** Build components from pre-selected combinations (e.g. analyze report). */
243
225
  declare function buildComponentsFromCombinations(combinations: ClassCombination[], options: BuildComponentsOptions): BuildComponentsResult;
244
226
 
227
+ interface ClassReplacement {
228
+ filePath: string;
229
+ line?: number;
230
+ from: string;
231
+ to: string;
232
+ partial?: boolean;
233
+ }
234
+ interface SkippedReplacement {
235
+ filePath: string;
236
+ line?: number;
237
+ reason: string;
238
+ classes: string[];
239
+ }
240
+ interface ReplaceClassNamesResult {
241
+ source: string;
242
+ replacements: ClassReplacement[];
243
+ skipped: SkippedReplacement[];
244
+ changed: boolean;
245
+ }
246
+ /**
247
+ * Replace exact matching className/class values with generated component classes.
248
+ * Supports partial replacement inside cn()/clsx() when dynamic args are present.
249
+ */
250
+ declare function replaceClassNamesInSource(source: string, replacementMap: Map<string, string>, filePath: string): ReplaceClassNamesResult;
251
+
252
+ interface SavingsReport {
253
+ replacementCount: number;
254
+ utilityTokensBefore: number;
255
+ utilityTokensAfter: number;
256
+ tokensSaved: number;
257
+ percentReduction: number;
258
+ }
259
+ /**
260
+ * Estimate utility-token savings from applied replacements.
261
+ * Each replacement collapses N utilities into 1 component class.
262
+ */
263
+ declare function calculateSavings(replacements: ClassReplacement[]): SavingsReport;
264
+
245
265
  interface GenerateJsonReport {
246
266
  command: 'generate';
247
267
  outputPath: string;
@@ -267,6 +287,9 @@ declare function printApplyJsonReport(report: ApplyJsonReport): void;
267
287
  interface ApplyOptions extends AnalyzeOptions {
268
288
  output: string;
269
289
  dryRun?: boolean;
290
+ verboseSkipped?: boolean;
291
+ /** Suppress console/json output (for internal callers like check) */
292
+ quiet?: boolean;
270
293
  }
271
294
  interface ApplyResult {
272
295
  filesModified: number;
@@ -284,26 +307,32 @@ interface ApplyResult {
284
307
  */
285
308
  declare function applyCommand(targetPath: string, options: ApplyOptions): Promise<ApplyResult>;
286
309
 
287
- interface ScanProjectOptions extends PatternFinderOptions {
288
- targetPath: string;
289
- include?: string[];
290
- exclude?: string[];
291
- /** Threshold used to mark combinations as extractable by generate/apply */
292
- extractableMinOccurrences?: number;
293
- /** Scan only git-changed files; string value is the git ref to diff against */
294
- changed?: boolean | string;
310
+ interface CheckOptions extends AnalyzeOptions {
311
+ output: string;
312
+ /** Exit with code 1 when extractablePatternCount exceeds this value */
313
+ failOnExtractable?: number;
314
+ verboseSkipped?: boolean;
295
315
  }
296
- interface ScanProjectResult {
297
- resolvedPath: string;
298
- files: string[];
299
- occurrences: ClassNameOccurrence[];
300
- warnings: string[];
301
- report: AnalysisReport;
316
+ interface CheckResult {
317
+ passed: boolean;
318
+ extractablePatternCount: number;
319
+ report: Awaited<ReturnType<typeof scanProject>>['report'];
320
+ preview: ApplyResult | null;
302
321
  }
303
322
  /**
304
- * Scan a directory, parse className usage, and build an analysis report.
323
+ * Scan for extractable duplicates and preview what apply would change (dry-run).
305
324
  */
306
- declare function scanProject(options: ScanProjectOptions): Promise<ScanProjectResult>;
325
+ declare function checkCommand(targetPath: string, options: CheckOptions): Promise<CheckResult>;
326
+
327
+ /**
328
+ * List source files changed relative to a git ref (default: working tree vs HEAD).
329
+ */
330
+ declare function getChangedSourceFiles(rootPath: string, ref?: string): Promise<string[]>;
331
+ declare function isGitRepository(rootPath: string): Promise<boolean>;
332
+ /**
333
+ * Changed source files under `scopePath`, resolved to absolute paths.
334
+ */
335
+ declare function getChangedFilesInScope(scopePath: string, ref?: string): Promise<string[]>;
307
336
 
308
337
  interface GenerateOptions extends AnalyzeOptions {
309
338
  output: string;
@@ -436,9 +465,45 @@ declare function printConsoleReport(report: AnalysisReport, options?: ConsoleRep
436
465
  /** Serialize the analysis report as formatted JSON. */
437
466
  declare function printJsonReport(report: AnalysisReport): void;
438
467
 
468
+ interface CheckJsonReport {
469
+ command: 'check';
470
+ passed: boolean;
471
+ outputPath: string;
472
+ extractablePatternCount: number;
473
+ analyzeMinOccurrences: number;
474
+ extractableMinOccurrences: number;
475
+ report: AnalysisReport;
476
+ preview: {
477
+ componentsGenerated: number;
478
+ filesModified: number;
479
+ replacementsTotal: number;
480
+ skippedTotal: number;
481
+ savings: ApplyResult['savings'];
482
+ } | null;
483
+ }
484
+ declare function printCheckJsonReport(report: CheckJsonReport): void;
485
+ interface CheckConsoleOptions {
486
+ outputPath: string;
487
+ topLimit?: number;
488
+ preview?: ApplyResult | null;
489
+ verboseSkipped?: boolean;
490
+ passed: boolean;
491
+ }
492
+ declare function printCheckConsoleReport(analysisReport: AnalysisReport, options: CheckConsoleOptions): void;
493
+
494
+ interface SkippedSummary {
495
+ reason: string;
496
+ count: number;
497
+ items: SkippedReplacement[];
498
+ }
499
+ declare function groupSkippedByReason(skipped: SkippedReplacement[]): SkippedSummary[];
500
+ declare function printSkippedReport(skipped: SkippedReplacement[], options?: {
501
+ verbose?: boolean;
502
+ }): void;
503
+
439
504
  /** Directories excluded from recursive file scanning. */
440
505
  declare const IGNORED_DIRECTORIES: readonly ["node_modules", ".next", "dist", "build", ".git"];
441
506
  /** fast-glob ignore patterns — match directories at any depth. */
442
507
  declare const IGNORE_PATTERNS: string[];
443
508
 
444
- 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, calculateSavings, collectVariantRegistry, dedupeSubsetCombinations, extractClassesFromExpression, extractClassesFromVariantCall, extractFromJSXAttribute, findFrequentPatterns, findRepeatedClassSets, formatModifiedFiles, formatSource, generateCombinations, generateCommand, generateComponentCss, getChangedFilesInScope, getChangedSourceFiles, initCommand, isClassAttribute, isGitRepository, isStrictSubset, isVariantCallee, loadCommandOptions, loadExtractableCombinations, normalizeClassPrefix, normalizeClasses, normalizeNamesConfig, parseFile, parseSource, parseSourceToAst, printApplyJsonReport, printConsoleReport, printGenerateJsonReport, printJsonReport, replaceClassNamesInSource, scanProject, splitClassString, suggestClassName, validateConfigFile, walkSourceFiles, withClassPrefix };
509
+ 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, calculateSavings, checkCommand, collectVariantRegistry, dedupeSubsetCombinations, extractClassesFromExpression, extractClassesFromVariantCall, extractFromJSXAttribute, findFrequentPatterns, findRepeatedClassSets, formatModifiedFiles, formatSource, generateCombinations, generateCommand, generateComponentCss, getChangedFilesInScope, getChangedSourceFiles, groupSkippedByReason, initCommand, isClassAttribute, isGitRepository, isStrictSubset, isVariantCallee, loadCommandOptions, loadExtractableCombinations, normalizeClassPrefix, normalizeClasses, normalizeNamesConfig, parseFile, parseSource, parseSourceToAst, printApplyJsonReport, printCheckConsoleReport, printCheckJsonReport, printConsoleReport, printGenerateJsonReport, printJsonReport, printSkippedReport, replaceClassNamesInSource, scanProject, splitClassString, suggestClassName, validateConfigFile, walkSourceFiles, withClassPrefix };