@hugeicons/migrate 0.1.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/main.js ADDED
@@ -0,0 +1,744 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * Hugeicons Migration Tool CLI
4
+ * Command-line interface for migrating icon libraries to Hugeicons
5
+ */
6
+ import { Command } from 'commander';
7
+ import chalk from 'chalk';
8
+ import { select, input } from '@inquirer/prompts';
9
+ import { VERSION, TOOL_NAME } from '@hugeicons/migrate-core';
10
+ import { scanCommand } from './commands/scan.js';
11
+ import { planCommand } from './commands/plan.js';
12
+ import { applyCommand } from './commands/apply.js';
13
+ import { revertCommand } from './commands/revert.js';
14
+ import * as fs from 'fs';
15
+ import { existsSync } from 'fs';
16
+ import { resolve } from 'path';
17
+ const program = new Command();
18
+ program
19
+ .name('hugeicons-migrate')
20
+ .description(TOOL_NAME + ' - Migrate from popular icon libraries to Hugeicons')
21
+ .version(VERSION);
22
+ // Global options
23
+ program
24
+ .option('-r, --root <path>', 'Project root directory', '.')
25
+ .option('--include <glob>', 'Include file patterns (repeatable)', collect, [])
26
+ .option('--exclude <glob>', 'Exclude file patterns (repeatable)', collect, [])
27
+ .option('-c, --config <path>', 'Path to config file')
28
+ .option('--report <format>', 'Report format: text, json, md', 'text')
29
+ .option('-o, --output <path>', 'Output file for report/patch')
30
+ .option('-y, --yes', 'Non-interactive mode (CI)', false)
31
+ .option('--strict', 'Fail on unmapped/ambiguous icons', false)
32
+ .option('-v, --verbose', 'Verbose output', false);
33
+ // Scan command
34
+ program
35
+ .command('scan')
36
+ .description('Detect icon libraries and collect usage statistics')
37
+ .action(async () => {
38
+ const opts = program.opts();
39
+ await scanCommand(opts);
40
+ });
41
+ // Plan command
42
+ program
43
+ .command('plan')
44
+ .description('Generate migration plan with icon mappings')
45
+ .option('-s, --style <style>', 'Hugeicons style: stroke, solid, duotone, auto', 'stroke')
46
+ .option('--confidence <threshold>', 'Fuzzy match confidence threshold (0-1)', '0.9')
47
+ .option('-i, --interactive', 'Interactive mode for ambiguous mappings', false)
48
+ .action(async (cmdOpts) => {
49
+ const opts = { ...program.opts(), ...cmdOpts };
50
+ await planCommand(opts);
51
+ });
52
+ // Apply command
53
+ program
54
+ .command('apply')
55
+ .description('Apply migration transformations to source files')
56
+ .option('-s, --style <style>', 'Hugeicons style: stroke, solid, duotone, auto', 'stroke')
57
+ .option('--dry-run', 'Preview changes without writing files', false)
58
+ .option('-w, --write', 'Actually write changes to files', false)
59
+ .option('--patch', 'Generate patch file', false)
60
+ .option('--backup', 'Create backup before writing', false)
61
+ .option('--no-git-check', 'Skip git clean working tree check', false)
62
+ .option('--format', 'Run prettier after changes', false)
63
+ .option('--fix-deps', 'Update package.json dependencies', false)
64
+ .option('--html-report', 'Generate HTML report and open in browser', false)
65
+ .action(async (cmdOpts) => {
66
+ const opts = { ...program.opts(), ...cmdOpts };
67
+ await applyCommand(opts);
68
+ });
69
+ // Revert command
70
+ program
71
+ .command('revert')
72
+ .description('Restore files from a migration backup')
73
+ .option('-b, --backup <name>', 'Specific backup directory to restore from')
74
+ .action(async (cmdOpts) => {
75
+ const opts = { ...program.opts(), ...cmdOpts };
76
+ await revertCommand(opts);
77
+ });
78
+ // Helper function to collect repeatable options
79
+ function collect(value, previous) {
80
+ return previous.concat([value]);
81
+ }
82
+ /**
83
+ * Display welcome banner
84
+ */
85
+ function showWelcome() {
86
+ console.log();
87
+ console.log(chalk.cyan.bold(' ╭─────────────────────────────────────────────╮'));
88
+ console.log(chalk.cyan.bold(' │ │'));
89
+ console.log(chalk.cyan.bold(' │') + chalk.white.bold(' Hugeicons Migration Tool ') + chalk.yellow(`v${VERSION}`) + chalk.cyan.bold(' │'));
90
+ console.log(chalk.cyan.bold(' │ │'));
91
+ console.log(chalk.cyan.bold(' ╰─────────────────────────────────────────────╯'));
92
+ console.log();
93
+ console.log(chalk.gray(' Migrate from popular icon libraries to Hugeicons'));
94
+ console.log(chalk.gray(' Supports: Lucide, Heroicons, FontAwesome, and more'));
95
+ console.log();
96
+ console.log(chalk.yellow(' ⚠ Beta: Currently only supports Lucide with React'));
97
+ console.log();
98
+ }
99
+ /**
100
+ * Get custom project path from user input with retry option
101
+ */
102
+ async function getCustomProjectPath() {
103
+ while (true) {
104
+ const customPath = await input({
105
+ message: 'Enter project path:',
106
+ });
107
+ if (!customPath.trim()) {
108
+ const action = await select({
109
+ message: 'No path entered. What would you like to do?',
110
+ choices: [
111
+ { name: 'Enter a different path', value: 'retry' },
112
+ { name: 'Cancel', value: 'cancel' },
113
+ ],
114
+ });
115
+ if (action === 'cancel') {
116
+ return null;
117
+ }
118
+ continue;
119
+ }
120
+ const resolvedPath = resolve(customPath.trim());
121
+ if (!existsSync(resolvedPath)) {
122
+ console.log();
123
+ console.log(chalk.red(' ✗') + chalk.white(` Path does not exist: ${chalk.cyan(resolvedPath)}`));
124
+ console.log();
125
+ const action = await select({
126
+ message: 'What would you like to do?',
127
+ choices: [
128
+ { name: 'Enter a different path', value: 'retry' },
129
+ { name: 'Cancel', value: 'cancel' },
130
+ ],
131
+ });
132
+ if (action === 'cancel') {
133
+ return null;
134
+ }
135
+ continue;
136
+ }
137
+ const hasPackageJson = existsSync(resolve(resolvedPath, 'package.json'));
138
+ console.log();
139
+ if (hasPackageJson) {
140
+ console.log(chalk.green(' ✓') + chalk.white(` Project found: ${chalk.cyan(resolvedPath)}`));
141
+ }
142
+ else {
143
+ console.log(chalk.yellow(' ⚠') + chalk.white(` No package.json at: ${chalk.cyan(resolvedPath)}`));
144
+ console.log(chalk.gray(' Continuing anyway...'));
145
+ }
146
+ return resolvedPath;
147
+ }
148
+ }
149
+ /**
150
+ * Interactive mode - shows menu when no command is provided
151
+ */
152
+ async function interactiveMode() {
153
+ showWelcome();
154
+ // Check current directory for a project
155
+ const cwd = process.cwd();
156
+ let projectPath = cwd;
157
+ const hasPackageJson = existsSync(resolve(cwd, 'package.json'));
158
+ if (hasPackageJson) {
159
+ console.log(chalk.green(' ✓') + chalk.white(` Project detected: ${chalk.cyan(cwd)}`));
160
+ console.log();
161
+ // Ask if user wants to use detected project or enter a different path
162
+ const projectChoice = await select({
163
+ message: 'Select project:',
164
+ choices: [
165
+ {
166
+ name: 'Use current project',
167
+ value: 'current',
168
+ },
169
+ {
170
+ name: 'Enter a different project path',
171
+ value: 'custom',
172
+ },
173
+ ],
174
+ });
175
+ if (projectChoice === 'custom') {
176
+ const customPath = await getCustomProjectPath();
177
+ if (customPath === null) {
178
+ console.log(chalk.gray(' Cancelled.'));
179
+ process.exit(0);
180
+ }
181
+ projectPath = customPath;
182
+ }
183
+ console.log();
184
+ }
185
+ else {
186
+ console.log(chalk.yellow(' ⚠') + chalk.white(` No package.json found in current directory`));
187
+ console.log(chalk.gray(` Current path: ${cwd}`));
188
+ console.log();
189
+ // Ask user to enter a path or continue anyway
190
+ const projectChoice = await select({
191
+ message: 'What would you like to do?',
192
+ choices: [
193
+ {
194
+ name: 'Enter a project path',
195
+ value: 'custom',
196
+ },
197
+ {
198
+ name: 'Continue with current directory anyway',
199
+ value: 'current',
200
+ },
201
+ {
202
+ name: 'Exit',
203
+ value: 'exit',
204
+ },
205
+ ],
206
+ });
207
+ if (projectChoice === 'exit') {
208
+ console.log(chalk.gray(' Goodbye!'));
209
+ process.exit(0);
210
+ }
211
+ if (projectChoice === 'custom') {
212
+ const customPath = await getCustomProjectPath();
213
+ if (customPath === null) {
214
+ console.log(chalk.gray(' Cancelled.'));
215
+ process.exit(0);
216
+ }
217
+ projectPath = customPath;
218
+ }
219
+ console.log();
220
+ }
221
+ const baseOpts = {
222
+ root: projectPath,
223
+ include: [],
224
+ exclude: [],
225
+ config: undefined,
226
+ report: 'text',
227
+ output: undefined,
228
+ yes: false,
229
+ strict: false,
230
+ verbose: true,
231
+ };
232
+ // Main action loop
233
+ while (true) {
234
+ // Show action menu
235
+ const action = await select({
236
+ message: 'What would you like to do?',
237
+ choices: [
238
+ {
239
+ name: 'Scan - Detect icon libraries and usage statistics',
240
+ value: 'scan',
241
+ description: 'Analyze your project to find icon imports and usage',
242
+ },
243
+ {
244
+ name: 'Plan - Generate migration plan with icon mappings',
245
+ value: 'plan',
246
+ description: 'Create a detailed plan showing how icons will be mapped',
247
+ },
248
+ {
249
+ name: 'Apply - Apply migration transformations (dry-run)',
250
+ value: 'apply-dry',
251
+ description: 'Preview changes without modifying files',
252
+ },
253
+ {
254
+ name: 'Apply - Apply migration and write changes',
255
+ value: 'apply-write',
256
+ description: 'Actually modify your source files (creates backup)',
257
+ },
258
+ {
259
+ name: 'Revert - Restore files from a backup',
260
+ value: 'revert',
261
+ description: 'Undo migration by restoring from backup',
262
+ },
263
+ {
264
+ name: 'Exit',
265
+ value: 'exit',
266
+ },
267
+ ],
268
+ });
269
+ console.log();
270
+ let lastAction = null;
271
+ let lastStyle = null;
272
+ switch (action) {
273
+ case 'scan':
274
+ await scanCommand(baseOpts);
275
+ lastAction = 'scan';
276
+ break;
277
+ case 'plan': {
278
+ const style = await selectStyle(projectPath);
279
+ if (style === 'cancel') {
280
+ console.log(chalk.gray(' Cancelled.'));
281
+ break;
282
+ }
283
+ console.log();
284
+ await planCommand({ ...baseOpts, style, confidence: '0.9', interactive: false });
285
+ lastAction = 'plan';
286
+ lastStyle = style;
287
+ break;
288
+ }
289
+ case 'apply-dry': {
290
+ const style = await selectStyle(projectPath);
291
+ if (style === 'cancel') {
292
+ console.log(chalk.gray(' Cancelled.'));
293
+ break;
294
+ }
295
+ console.log();
296
+ await applyCommand({
297
+ ...baseOpts,
298
+ style,
299
+ dryRun: true,
300
+ write: false,
301
+ patch: false,
302
+ backup: false,
303
+ gitCheck: true,
304
+ format: false,
305
+ fixDeps: false,
306
+ });
307
+ lastAction = 'apply-dry';
308
+ lastStyle = style;
309
+ break;
310
+ }
311
+ case 'apply-write': {
312
+ const style = await selectStyle(projectPath);
313
+ if (style === 'cancel') {
314
+ console.log(chalk.gray(' Cancelled.'));
315
+ break;
316
+ }
317
+ console.log();
318
+ const confirmWrite = await select({
319
+ message: 'This will modify your source files. A backup will be created.',
320
+ choices: [
321
+ { name: 'Proceed', value: 'proceed' },
322
+ { name: 'Cancel', value: 'cancel' },
323
+ ],
324
+ });
325
+ if (confirmWrite === 'cancel') {
326
+ console.log(chalk.gray(' Cancelled.'));
327
+ break;
328
+ }
329
+ console.log();
330
+ await applyCommand({
331
+ ...baseOpts,
332
+ style,
333
+ dryRun: false,
334
+ write: true,
335
+ patch: false,
336
+ backup: true,
337
+ gitCheck: true,
338
+ format: false,
339
+ fixDeps: false,
340
+ htmlReport: true,
341
+ });
342
+ // Exit after successful write - applyCommand will show thanks message and exit
343
+ // This line should not be reached but just in case
344
+ process.exit(0);
345
+ }
346
+ case 'revert':
347
+ await revertCommand({
348
+ root: projectPath,
349
+ yes: false,
350
+ verbose: true,
351
+ });
352
+ lastAction = 'revert';
353
+ break;
354
+ case 'exit':
355
+ console.log(chalk.gray(' Goodbye!'));
356
+ process.exit(0);
357
+ }
358
+ // After command execution, ask if user wants to continue with suggested next steps
359
+ if (lastAction) {
360
+ console.log();
361
+ const nextChoice = await promptNextStep(lastAction, lastStyle, baseOpts);
362
+ if (nextChoice === 'exit') {
363
+ console.log();
364
+ console.log(chalk.gray(' Goodbye!'));
365
+ process.exit(0);
366
+ }
367
+ // If a direct action was chosen, execute it
368
+ if (nextChoice === 'plan' || nextChoice === 'apply-dry' || nextChoice === 'apply-write') {
369
+ console.log();
370
+ await executeAction(nextChoice, lastStyle, baseOpts);
371
+ }
372
+ console.log();
373
+ }
374
+ }
375
+ }
376
+ /**
377
+ * Prompt for next step with suggested actions based on last command
378
+ */
379
+ async function promptNextStep(lastAction, _lastStyle, _baseOpts) {
380
+ const choices = [];
381
+ // Add suggested next steps based on last action
382
+ switch (lastAction) {
383
+ case 'scan':
384
+ choices.push({ name: 'Plan - Generate migration plan (suggested)', value: 'plan' }, { name: 'Apply (dry-run) - Preview changes', value: 'apply-dry' });
385
+ break;
386
+ case 'plan':
387
+ choices.push({ name: 'Apply (dry-run) - Preview changes (suggested)', value: 'apply-dry' }, { name: 'Apply - Write changes to files', value: 'apply-write' });
388
+ break;
389
+ case 'apply-dry':
390
+ choices.push({ name: 'Apply - Write changes to files (suggested)', value: 'apply-write' }, { name: 'Plan - Regenerate migration plan', value: 'plan' });
391
+ break;
392
+ case 'apply-write':
393
+ choices.push({ name: 'Scan - Verify migration results', value: 'scan' });
394
+ break;
395
+ }
396
+ // Always add these options
397
+ choices.push({ name: 'Other commands...', value: 'continue' }, { name: 'Exit', value: 'exit' });
398
+ return await select({
399
+ message: 'What would you like to do next?',
400
+ choices,
401
+ });
402
+ }
403
+ /**
404
+ * Execute a direct action (used for suggested next steps)
405
+ */
406
+ async function executeAction(action, previousStyle, baseOpts) {
407
+ const projectRoot = baseOpts.root;
408
+ // Use previous style or ask for new one
409
+ let style = previousStyle;
410
+ if (!style || action === 'plan') {
411
+ const selectedStyle = await selectStyle(projectRoot);
412
+ if (selectedStyle === 'cancel') {
413
+ console.log(chalk.gray(' Cancelled.'));
414
+ return;
415
+ }
416
+ style = selectedStyle;
417
+ }
418
+ else {
419
+ // Confirm using previous style
420
+ const styleChoice = await select({
421
+ message: `Use previous style (${style})?`,
422
+ choices: [
423
+ { name: `Yes, use ${style}`, value: 'yes' },
424
+ { name: 'No, choose different style', value: 'no' },
425
+ { name: 'Cancel', value: 'cancel' },
426
+ ],
427
+ });
428
+ if (styleChoice === 'cancel') {
429
+ console.log(chalk.gray(' Cancelled.'));
430
+ return;
431
+ }
432
+ if (styleChoice === 'no') {
433
+ const selectedStyle = await selectStyle(projectRoot);
434
+ if (selectedStyle === 'cancel') {
435
+ console.log(chalk.gray(' Cancelled.'));
436
+ return;
437
+ }
438
+ style = selectedStyle;
439
+ }
440
+ }
441
+ console.log();
442
+ switch (action) {
443
+ case 'plan':
444
+ await planCommand({ ...baseOpts, style, confidence: '0.9', interactive: false });
445
+ break;
446
+ case 'apply-dry':
447
+ await applyCommand({
448
+ ...baseOpts,
449
+ style,
450
+ dryRun: true,
451
+ write: false,
452
+ patch: false,
453
+ backup: false,
454
+ gitCheck: true,
455
+ format: false,
456
+ fixDeps: false,
457
+ });
458
+ break;
459
+ case 'apply-write': {
460
+ const confirmWrite = await select({
461
+ message: 'This will modify your source files. A backup will be created.',
462
+ choices: [
463
+ { name: 'Proceed', value: 'proceed' },
464
+ { name: 'Cancel', value: 'cancel' },
465
+ ],
466
+ });
467
+ if (confirmWrite === 'cancel') {
468
+ console.log(chalk.gray(' Cancelled.'));
469
+ return;
470
+ }
471
+ console.log();
472
+ await applyCommand({
473
+ ...baseOpts,
474
+ style,
475
+ dryRun: false,
476
+ write: true,
477
+ patch: false,
478
+ backup: true,
479
+ gitCheck: true,
480
+ format: false,
481
+ fixDeps: false,
482
+ htmlReport: true,
483
+ });
484
+ // Exit after write - applyCommand will exit, but just in case
485
+ process.exit(0);
486
+ }
487
+ }
488
+ }
489
+ function detectPackageManager(projectRoot) {
490
+ // Check for lock files to determine package manager
491
+ if (existsSync(resolve(projectRoot, 'bun.lockb')) || existsSync(resolve(projectRoot, 'bun.lock'))) {
492
+ return 'bun';
493
+ }
494
+ if (existsSync(resolve(projectRoot, 'pnpm-lock.yaml'))) {
495
+ return 'pnpm';
496
+ }
497
+ if (existsSync(resolve(projectRoot, 'yarn.lock'))) {
498
+ return 'yarn';
499
+ }
500
+ // Default to npm
501
+ return 'npm';
502
+ }
503
+ /**
504
+ * Check if a Hugeicons Pro license is already configured
505
+ */
506
+ function hasLicenseFile(projectRoot) {
507
+ // Check .npmrc (npm, pnpm, bun)
508
+ const npmrcPath = resolve(projectRoot, '.npmrc');
509
+ if (existsSync(npmrcPath)) {
510
+ const content = fs.readFileSync(npmrcPath, 'utf-8');
511
+ if (content.includes('@hugeicons-pro:registry') || content.includes('//npm.hugeicons.com')) {
512
+ return true;
513
+ }
514
+ }
515
+ // Check .yarnrc.yml (yarn v2+)
516
+ const yarnrcPath = resolve(projectRoot, '.yarnrc.yml');
517
+ if (existsSync(yarnrcPath)) {
518
+ const content = fs.readFileSync(yarnrcPath, 'utf-8');
519
+ if (content.includes('hugeicons-pro') && content.includes('npm.hugeicons.com')) {
520
+ return true;
521
+ }
522
+ }
523
+ // Check bunfig.toml (bun alternative)
524
+ const bunfigPath = resolve(projectRoot, 'bunfig.toml');
525
+ if (existsSync(bunfigPath)) {
526
+ const content = fs.readFileSync(bunfigPath, 'utf-8');
527
+ if (content.includes('hugeicons-pro') && content.includes('npm.hugeicons.com')) {
528
+ return true;
529
+ }
530
+ }
531
+ return false;
532
+ }
533
+ /**
534
+ * Save license configuration for the detected package manager
535
+ */
536
+ function saveLicense(projectRoot, license) {
537
+ const packageManager = detectPackageManager(projectRoot);
538
+ // For yarn v2+, check if .yarnrc.yml exists (indicates yarn berry)
539
+ const isYarnBerry = existsSync(resolve(projectRoot, '.yarnrc.yml')) ||
540
+ (packageManager === 'yarn' && existsSync(resolve(projectRoot, '.yarn')));
541
+ if (isYarnBerry) {
542
+ return saveYarnLicense(projectRoot, license);
543
+ }
544
+ // For npm, pnpm, and bun - all support .npmrc
545
+ return saveNpmrcLicense(projectRoot, license, packageManager);
546
+ }
547
+ /**
548
+ * Save license to .npmrc file (works for npm, pnpm, bun)
549
+ */
550
+ function saveNpmrcLicense(projectRoot, license, packageManager) {
551
+ const npmrcPath = resolve(projectRoot, '.npmrc');
552
+ let content = '';
553
+ if (existsSync(npmrcPath)) {
554
+ content = fs.readFileSync(npmrcPath, 'utf-8');
555
+ // Remove existing hugeicons config if present
556
+ content = content
557
+ .split('\n')
558
+ .filter(line => !line.includes('@hugeicons-pro:registry') && !line.includes('//npm.hugeicons.com'))
559
+ .join('\n')
560
+ .trim();
561
+ if (content) {
562
+ content += '\n\n';
563
+ }
564
+ }
565
+ // Add hugeicons registry config
566
+ content += `# Hugeicons Pro License\n`;
567
+ content += `@hugeicons-pro:registry=https://npm.hugeicons.com/\n`;
568
+ content += `//npm.hugeicons.com/:_authToken=${license}\n`;
569
+ fs.writeFileSync(npmrcPath, content, 'utf-8');
570
+ return `.npmrc (${packageManager})`;
571
+ }
572
+ /**
573
+ * Save license to .yarnrc.yml file (yarn v2+/berry)
574
+ */
575
+ function saveYarnLicense(projectRoot, license) {
576
+ const yarnrcPath = resolve(projectRoot, '.yarnrc.yml');
577
+ let content = '';
578
+ if (existsSync(yarnrcPath)) {
579
+ content = fs.readFileSync(yarnrcPath, 'utf-8');
580
+ // Remove existing hugeicons config if present
581
+ const lines = content.split('\n');
582
+ const filteredLines = [];
583
+ let skipSection = false;
584
+ for (const line of lines) {
585
+ // Check if we're entering the npmScopes section for hugeicons-pro
586
+ if (line.match(/^\s*hugeicons-pro:/)) {
587
+ skipSection = true;
588
+ continue;
589
+ }
590
+ // Check if we're exiting the section (new top-level key or npmScopes entry)
591
+ if (skipSection && line.match(/^[a-zA-Z]/) || line.match(/^\s{2}[a-zA-Z]/)) {
592
+ skipSection = false;
593
+ }
594
+ if (!skipSection) {
595
+ filteredLines.push(line);
596
+ }
597
+ }
598
+ content = filteredLines.join('\n').trim();
599
+ if (content) {
600
+ content += '\n\n';
601
+ }
602
+ }
603
+ // Check if npmScopes already exists
604
+ if (!content.includes('npmScopes:')) {
605
+ content += `npmScopes:\n`;
606
+ }
607
+ // Add hugeicons-pro scope
608
+ content += ` hugeicons-pro:\n`;
609
+ content += ` npmAlwaysAuth: true\n`;
610
+ content += ` npmAuthToken: ${license}\n`;
611
+ content += ` npmRegistryServer: "https://npm.hugeicons.com/"\n`;
612
+ fs.writeFileSync(yarnrcPath, content, 'utf-8');
613
+ return '.yarnrc.yml (yarn)';
614
+ }
615
+ /**
616
+ * Select Hugeicons style with license handling for Pro styles
617
+ */
618
+ async function selectStyle(projectRoot) {
619
+ const styleChoice = await select({
620
+ message: 'Select Hugeicons style:',
621
+ choices: [
622
+ { name: 'Stroke Rounded (free)', value: 'stroke-rounded' },
623
+ { name: 'Pro styles...', value: 'pro' },
624
+ { name: 'Cancel', value: 'cancel' },
625
+ ],
626
+ });
627
+ if (styleChoice === 'cancel' || styleChoice === 'stroke-rounded') {
628
+ return styleChoice === 'stroke-rounded' ? 'stroke' : 'cancel';
629
+ }
630
+ // Pro style selected - show pro style options
631
+ const proStyle = await select({
632
+ message: 'Select Pro style:',
633
+ choices: [
634
+ { name: 'Rounded', value: 'rounded' },
635
+ { name: 'Standard', value: 'standard' },
636
+ { name: 'Sharp', value: 'sharp' },
637
+ { name: 'Back', value: 'back' },
638
+ { name: 'Cancel', value: 'cancel' },
639
+ ],
640
+ });
641
+ if (proStyle === 'cancel') {
642
+ return 'cancel';
643
+ }
644
+ if (proStyle === 'back') {
645
+ return await selectStyle(projectRoot);
646
+ }
647
+ // Select variant within the style family
648
+ let variantChoices = [];
649
+ switch (proStyle) {
650
+ case 'rounded':
651
+ variantChoices = [
652
+ { name: 'Solid', value: 'solid-rounded' },
653
+ { name: 'Duotone', value: 'duotone-rounded' },
654
+ { name: 'Twotone', value: 'twotone-rounded' },
655
+ { name: 'Bulk', value: 'bulk-rounded' },
656
+ ];
657
+ break;
658
+ case 'standard':
659
+ variantChoices = [
660
+ { name: 'Stroke', value: 'stroke-standard' },
661
+ { name: 'Solid', value: 'solid-standard' },
662
+ { name: 'Duotone', value: 'duotone-standard' },
663
+ ];
664
+ break;
665
+ case 'sharp':
666
+ variantChoices = [
667
+ { name: 'Stroke', value: 'stroke-sharp' },
668
+ { name: 'Solid', value: 'solid-sharp' },
669
+ ];
670
+ break;
671
+ }
672
+ variantChoices.push({ name: 'Back', value: 'back' }, { name: 'Cancel', value: 'cancel' });
673
+ const variant = await select({
674
+ message: `Select ${proStyle} variant:`,
675
+ choices: variantChoices,
676
+ });
677
+ if (variant === 'cancel') {
678
+ return 'cancel';
679
+ }
680
+ if (variant === 'back') {
681
+ return await selectStyle(projectRoot);
682
+ }
683
+ // Check if license exists
684
+ if (projectRoot && !hasLicenseFile(projectRoot)) {
685
+ console.log();
686
+ console.log(chalk.yellow(' Pro styles require a Hugeicons license.'));
687
+ console.log(chalk.gray(' Get your license at: https://hugeicons.com/pricing'));
688
+ console.log();
689
+ const hasLicense = await select({
690
+ message: 'Do you have a Hugeicons Pro license?',
691
+ choices: [
692
+ { name: 'Yes, I have a license', value: 'yes' },
693
+ { name: 'No, take me to pricing', value: 'no' },
694
+ { name: 'Cancel', value: 'cancel' },
695
+ ],
696
+ });
697
+ if (hasLicense === 'no') {
698
+ console.log();
699
+ console.log(chalk.gray(' Visit https://hugeicons.com/pricing to get a license.'));
700
+ return 'cancel';
701
+ }
702
+ if (hasLicense === 'cancel') {
703
+ return 'cancel';
704
+ }
705
+ const license = await input({
706
+ message: 'Enter your license key:',
707
+ validate: (value) => {
708
+ if (!value.trim()) {
709
+ return 'Please enter a license key';
710
+ }
711
+ if (value.trim().length < 10) {
712
+ return 'Invalid license key format';
713
+ }
714
+ return true;
715
+ },
716
+ });
717
+ // Save license to appropriate config file based on package manager
718
+ const configFile = saveLicense(projectRoot, license.trim());
719
+ console.log();
720
+ console.log(chalk.green(' ✓') + chalk.white(` License saved to ${configFile}`));
721
+ }
722
+ return variant;
723
+ }
724
+ // Check if no command was provided - run interactive mode
725
+ const args = process.argv.slice(2);
726
+ const hasCommand = args.some(arg => ['scan', 'plan', 'apply', 'revert', 'help', '-h', '--help', '-V', '--version'].includes(arg));
727
+ if (!hasCommand && args.length === 0) {
728
+ // No arguments - run interactive mode
729
+ interactiveMode().catch((err) => {
730
+ if (err.name === 'ExitPromptError') {
731
+ // User cancelled with Ctrl+C
732
+ console.log();
733
+ console.log(chalk.gray(' Cancelled.'));
734
+ process.exit(0);
735
+ }
736
+ console.error(chalk.red('Error:'), err.message);
737
+ process.exit(1);
738
+ });
739
+ }
740
+ else {
741
+ // Parse and run with commander
742
+ program.parse();
743
+ }
744
+ //# sourceMappingURL=main.js.map