@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/README.md +282 -0
- package/dist/commands/apply.d.ts +27 -0
- package/dist/commands/apply.d.ts.map +1 -0
- package/dist/commands/apply.js +504 -0
- package/dist/commands/apply.js.map +1 -0
- package/dist/commands/plan.d.ts +20 -0
- package/dist/commands/plan.d.ts.map +1 -0
- package/dist/commands/plan.js +100 -0
- package/dist/commands/plan.js.map +1 -0
- package/dist/commands/revert.d.ts +13 -0
- package/dist/commands/revert.d.ts.map +1 -0
- package/dist/commands/revert.js +195 -0
- package/dist/commands/revert.js.map +1 -0
- package/dist/commands/scan.d.ts +16 -0
- package/dist/commands/scan.d.ts.map +1 -0
- package/dist/commands/scan.js +71 -0
- package/dist/commands/scan.js.map +1 -0
- package/dist/main.d.ts +7 -0
- package/dist/main.d.ts.map +1 -0
- package/dist/main.js +744 -0
- package/dist/main.js.map +1 -0
- package/dist/utils/html-report.d.ts +22 -0
- package/dist/utils/html-report.d.ts.map +1 -0
- package/dist/utils/html-report.js +628 -0
- package/dist/utils/html-report.js.map +1 -0
- package/package.json +68 -0
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
|