depfixer 1.0.0 → 1.1.1

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.
Files changed (85) hide show
  1. package/dist/commands/audit.d.ts +19 -0
  2. package/dist/commands/audit.d.ts.map +1 -0
  3. package/dist/commands/audit.js +170 -0
  4. package/dist/commands/audit.js.map +1 -0
  5. package/dist/commands/fix.d.ts +10 -3
  6. package/dist/commands/fix.d.ts.map +1 -1
  7. package/dist/commands/fix.js +407 -41
  8. package/dist/commands/fix.js.map +1 -1
  9. package/dist/commands/graph.d.ts +1 -1
  10. package/dist/commands/graph.d.ts.map +1 -1
  11. package/dist/commands/graph.js +17 -12
  12. package/dist/commands/graph.js.map +1 -1
  13. package/dist/commands/login.d.ts.map +1 -1
  14. package/dist/commands/login.js +120 -19
  15. package/dist/commands/login.js.map +1 -1
  16. package/dist/commands/logout.d.ts.map +1 -1
  17. package/dist/commands/logout.js +6 -0
  18. package/dist/commands/logout.js.map +1 -1
  19. package/dist/commands/migrate.d.ts +22 -0
  20. package/dist/commands/migrate.d.ts.map +1 -0
  21. package/dist/commands/migrate.js +874 -0
  22. package/dist/commands/migrate.js.map +1 -0
  23. package/dist/commands/smart.d.ts +23 -0
  24. package/dist/commands/smart.d.ts.map +1 -0
  25. package/dist/commands/smart.js +1024 -0
  26. package/dist/commands/smart.js.map +1 -0
  27. package/dist/commands/whoami.d.ts +14 -0
  28. package/dist/commands/whoami.d.ts.map +1 -0
  29. package/dist/commands/whoami.js +65 -0
  30. package/dist/commands/whoami.js.map +1 -0
  31. package/dist/index.js +165 -40
  32. package/dist/index.js.map +1 -1
  33. package/dist/services/analytics.d.ts +108 -0
  34. package/dist/services/analytics.d.ts.map +1 -0
  35. package/dist/services/analytics.js +305 -0
  36. package/dist/services/analytics.js.map +1 -0
  37. package/dist/services/api-client.d.ts +159 -0
  38. package/dist/services/api-client.d.ts.map +1 -1
  39. package/dist/services/api-client.js +192 -10
  40. package/dist/services/api-client.js.map +1 -1
  41. package/dist/services/auth-manager.d.ts +14 -0
  42. package/dist/services/auth-manager.d.ts.map +1 -1
  43. package/dist/services/auth-manager.js +30 -0
  44. package/dist/services/auth-manager.js.map +1 -1
  45. package/dist/services/cache-manager.d.ts +62 -22
  46. package/dist/services/cache-manager.d.ts.map +1 -1
  47. package/dist/services/cache-manager.js +116 -30
  48. package/dist/services/cache-manager.js.map +1 -1
  49. package/dist/services/device-id.d.ts +20 -0
  50. package/dist/services/device-id.d.ts.map +1 -0
  51. package/dist/services/device-id.js +70 -0
  52. package/dist/services/device-id.js.map +1 -0
  53. package/dist/services/package-json.d.ts +44 -5
  54. package/dist/services/package-json.d.ts.map +1 -1
  55. package/dist/services/package-json.js +273 -20
  56. package/dist/services/package-json.js.map +1 -1
  57. package/dist/services/payment-flow.d.ts +108 -0
  58. package/dist/services/payment-flow.d.ts.map +1 -0
  59. package/dist/services/payment-flow.js +476 -0
  60. package/dist/services/payment-flow.js.map +1 -0
  61. package/dist/services/session-manager.d.ts +92 -0
  62. package/dist/services/session-manager.d.ts.map +1 -0
  63. package/dist/services/session-manager.js +218 -0
  64. package/dist/services/session-manager.js.map +1 -0
  65. package/dist/utils/design-system.d.ts +90 -0
  66. package/dist/utils/design-system.d.ts.map +1 -0
  67. package/dist/utils/design-system.js +338 -0
  68. package/dist/utils/design-system.js.map +1 -0
  69. package/dist/utils/output.d.ts +48 -1
  70. package/dist/utils/output.d.ts.map +1 -1
  71. package/dist/utils/output.js +180 -17
  72. package/dist/utils/output.js.map +1 -1
  73. package/dist/utils/print.d.ts +116 -0
  74. package/dist/utils/print.d.ts.map +1 -0
  75. package/dist/utils/print.js +190 -0
  76. package/dist/utils/print.js.map +1 -0
  77. package/package.json +13 -2
  78. package/dist/commands/analyze.d.ts +0 -18
  79. package/dist/commands/analyze.d.ts.map +0 -1
  80. package/dist/commands/analyze.js +0 -404
  81. package/dist/commands/analyze.js.map +0 -1
  82. package/dist/services/gitignore.d.ts +0 -19
  83. package/dist/services/gitignore.d.ts.map +0 -1
  84. package/dist/services/gitignore.js +0 -64
  85. package/dist/services/gitignore.js.map +0 -1
@@ -0,0 +1,874 @@
1
+ import chalk from 'chalk';
2
+ import { ApiClient } from '../services/api-client.js';
3
+ import { PackageJsonService } from '../services/package-json.js';
4
+ import { SessionManager } from '../services/session-manager.js';
5
+ import { PaymentFlowService } from '../services/payment-flow.js';
6
+ import { analytics } from '../services/analytics.js';
7
+ import { getDeviceId } from '../services/device-id.js';
8
+ import { createSpinner, createMigrationTable, printError, printSuccess, printInfo, runStepSequence, sleep, } from '../utils/output.js';
9
+ import { colors, printCliHeader, printMigrationPlanHeader, printProjectionStats, printCostBox, printSuccessBox, renderHealthBar, getHealthStatus, printUserDetails, } from '../utils/design-system.js';
10
+ /**
11
+ * Migrate command
12
+ *
13
+ * Interactive migration with version selection:
14
+ * 1. Detect framework and current version
15
+ * 2. Fetch available versions from server
16
+ * 3. Show version selector (minor patches vs major upgrades)
17
+ * 4. User selects target version
18
+ * 5. Show migration plan with cost
19
+ * 6. Confirm and execute
20
+ *
21
+ * Usage:
22
+ * npx depfixer migrate
23
+ * npx depfixer migrate --yes
24
+ */
25
+ export async function migrateCommand(options) {
26
+ const projectDir = options.path || process.cwd();
27
+ // Create device ID early (for anonymous user tracking before login)
28
+ getDeviceId();
29
+ try {
30
+ const packageJsonService = new PackageJsonService();
31
+ const apiClient = new ApiClient();
32
+ const sessionManager = new SessionManager(projectDir);
33
+ // Read and sanitize package.json
34
+ const { content: packageJsonContent, parsed } = await packageJsonService.read(projectDir);
35
+ const sanitized = packageJsonService.sanitize(parsed);
36
+ // Detect framework
37
+ const framework = detectFramework(sanitized);
38
+ if (!framework) {
39
+ printError('Could not detect framework. Migration requires Angular or React project.');
40
+ process.exit(1);
41
+ }
42
+ // Detect current version
43
+ const currentVersion = detectCurrentVersion(sanitized, framework);
44
+ const currentMajor = currentVersion ? parseInt(currentVersion.split('.')[0], 10) : 0;
45
+ const frameworkName = framework.charAt(0).toUpperCase() + framework.slice(1);
46
+ // Print CLI header
47
+ printCliHeader('migrate');
48
+ // Track: migrate_started
49
+ analytics.migrateStarted({ command: 'migrate' });
50
+ console.log();
51
+ console.log(colors.whiteBold(`📦 Project: ${colors.brand(sanitized.name || 'unnamed')}`));
52
+ console.log(colors.dim(` Framework: ${frameworkName} ${currentVersion || 'unknown'}`));
53
+ console.log();
54
+ let targetVersion;
55
+ // Interactive version selector
56
+ {
57
+ const spinner = createSpinner('Fetching available versions...').start();
58
+ try {
59
+ const versionsResponse = await apiClient.getFrameworkVersions(framework, currentMajor);
60
+ // Check if already on latest version (no newer versions available)
61
+ if (!versionsResponse.success) {
62
+ const errorMsg = versionsResponse.error || '';
63
+ // If error indicates no newer versions, user is on latest
64
+ if (errorMsg.includes('No newer versions') || errorMsg.includes('No supported versions')) {
65
+ spinner.succeed('Version check complete');
66
+ await showLatestVersionInfo(apiClient, sanitized, framework, frameworkName, currentVersion);
67
+ return;
68
+ }
69
+ spinner.fail('Failed to fetch versions');
70
+ throw new Error(errorMsg || 'Could not fetch available versions');
71
+ }
72
+ spinner.succeed('Versions loaded');
73
+ // Track: versions_loaded
74
+ analytics.versionsLoaded({
75
+ framework,
76
+ currentVersion,
77
+ groupCount: versionsResponse.data?.groups?.length || 0,
78
+ });
79
+ const groups = versionsResponse.data?.groups;
80
+ if (!groups || groups.length === 0) {
81
+ await showLatestVersionInfo(apiClient, sanitized, framework, frameworkName, currentVersion);
82
+ return;
83
+ }
84
+ // Build version options from groups - get latest version from each major
85
+ const versionOptions = [];
86
+ for (const group of groups) {
87
+ // Get the first (latest) option from each group
88
+ const latestInGroup = group.options[0];
89
+ if (latestInGroup) {
90
+ const major = parseInt(latestInGroup.value.split('.')[0], 10);
91
+ const isMajorUpgrade = major > currentMajor;
92
+ versionOptions.push({
93
+ version: latestInGroup.value,
94
+ major,
95
+ // Show full version number like "Angular 20.3.15 (Latest)"
96
+ label: `${frameworkName} ${latestInGroup.value}` + (latestInGroup.badge ? ` (${latestInGroup.badge})` : ''),
97
+ type: isMajorUpgrade ? 'major' : 'minor',
98
+ isLatest: latestInGroup.badge === 'Latest',
99
+ });
100
+ }
101
+ }
102
+ // Show version selector
103
+ console.log();
104
+ console.log(colors.whiteBold('🎯 Select target version:'));
105
+ console.log();
106
+ // Group by type
107
+ const minorOptions = versionOptions.filter(v => v.type === 'minor');
108
+ const majorOptions = versionOptions.filter(v => v.type === 'major');
109
+ if (minorOptions.length > 0) {
110
+ console.log(colors.dim(' Minor Patches:'));
111
+ for (const opt of minorOptions) {
112
+ console.log(colors.brand(` ${opt.label}`));
113
+ }
114
+ console.log();
115
+ }
116
+ if (majorOptions.length > 0) {
117
+ console.log(colors.dim(' Major Upgrades:'));
118
+ for (const opt of majorOptions) {
119
+ console.log(colors.action(` ${opt.label}`));
120
+ }
121
+ console.log();
122
+ }
123
+ // Interactive selection
124
+ const selectedVersion = await selectVersion(versionOptions, frameworkName);
125
+ if (!selectedVersion) {
126
+ console.log();
127
+ printInfo('Migration cancelled.');
128
+ return;
129
+ }
130
+ targetVersion = selectedVersion.version;
131
+ // Track: version_selected
132
+ analytics.versionSelected({
133
+ framework,
134
+ currentVersion,
135
+ targetVersion,
136
+ isMajorUpgrade: selectedVersion.type === 'major',
137
+ });
138
+ console.log();
139
+ console.log(colors.success(`✓ Selected: ${frameworkName} ${targetVersion}`));
140
+ }
141
+ catch (error) {
142
+ spinner.fail('Failed to fetch versions');
143
+ throw error;
144
+ }
145
+ }
146
+ // Validate version format if provided directly
147
+ if (!/^\d+(\.\d+)?(\.\d+)?$/.test(targetVersion)) {
148
+ printError('Invalid version format. Use major version (e.g., "19") or semver (e.g., "19.0.0")');
149
+ process.exit(1);
150
+ }
151
+ // Step 1: Get cost estimate from server (uses free audit endpoint)
152
+ const costSpinner = createSpinner('Getting cost estimate...').start();
153
+ let cost;
154
+ let tierName;
155
+ let packageCount;
156
+ let auditData;
157
+ try {
158
+ const auditResponse = await apiClient.analyzeAudit(sanitized, framework);
159
+ if (!auditResponse.success || !auditResponse.data) {
160
+ costSpinner.fail('Failed to get cost estimate');
161
+ throw new Error(auditResponse.error || 'Could not get cost estimate');
162
+ }
163
+ auditData = auditResponse.data;
164
+ cost = auditData.cost;
165
+ tierName = auditData.tierName;
166
+ packageCount = auditData.totalPackages;
167
+ costSpinner.succeed('Cost estimate ready');
168
+ // Set project context for analytics
169
+ analytics.setProjectContext({
170
+ packageCount,
171
+ framework,
172
+ frameworkVersion: currentVersion,
173
+ projectHash: analytics.hashProject(sanitized),
174
+ });
175
+ // Track: project_detected
176
+ analytics.projectDetected({
177
+ packageCount,
178
+ framework,
179
+ currentVersion,
180
+ });
181
+ }
182
+ catch (error) {
183
+ costSpinner.fail('Failed to get cost estimate');
184
+ throw error;
185
+ }
186
+ // Show project overview with packages to migrate - smooth reveal
187
+ const healthScore = auditData.healthScore || 0;
188
+ const healthInfo = getHealthStatus(healthScore);
189
+ console.log();
190
+ await sleep(100);
191
+ console.log(colors.whiteBold('📊 PROJECT OVERVIEW'));
192
+ await sleep(80);
193
+ console.log(colors.gray('─'.repeat(50)));
194
+ await sleep(120);
195
+ console.log(`${colors.whiteBold('🏥 Health:')} ${renderHealthBar(healthScore)} ${healthInfo.color.bold(`${healthScore}/100`)} (${healthInfo.color(healthInfo.text)})`);
196
+ await sleep(100);
197
+ console.log(`${colors.whiteBold('📦 Packages:')} ${colors.brand(`${packageCount}`)} to migrate`);
198
+ await sleep(150);
199
+ console.log();
200
+ console.log(colors.whiteBold('🚀 MIGRATION PLAN:'));
201
+ await sleep(80);
202
+ console.log(colors.dim(` ${frameworkName} ${currentVersion || '?'} → ${targetVersion}`));
203
+ await sleep(60);
204
+ console.log(colors.dim(' All dependencies will be aligned to the target version.'));
205
+ // Migration highlight box - smooth reveal
206
+ const targetMajor = parseInt(targetVersion.split('.')[0], 10);
207
+ const majorJump = targetMajor - currentMajor;
208
+ await sleep(150);
209
+ console.log();
210
+ // Box with 49 visible chars between borders
211
+ // Using simple ASCII to avoid emoji width issues
212
+ console.log(colors.brandBold('┌─────────────────────────────────────────────────┐'));
213
+ await sleep(40);
214
+ console.log(colors.brandBold('│') + colors.whiteBold(' MIGRATION HIGHLIGHT ') + colors.brandBold('│'));
215
+ await sleep(40);
216
+ console.log(colors.brandBold('├─────────────────────────────────────────────────┤'));
217
+ await sleep(60);
218
+ if (majorJump > 1) {
219
+ console.log(colors.brandBold('│') + colors.warning(` ${majorJump} major versions jump - Full ecosystem update `.padEnd(49)) + colors.brandBold('│'));
220
+ }
221
+ else if (majorJump === 1) {
222
+ console.log(colors.brandBold('│') + colors.success(' Single major version upgrade ') + colors.brandBold('│'));
223
+ }
224
+ else {
225
+ console.log(colors.brandBold('│') + colors.success(' Minor/patch update - Low risk ') + colors.brandBold('│'));
226
+ }
227
+ await sleep(50);
228
+ console.log(colors.brandBold('│') + colors.dim(' - TypeScript alignment included ') + colors.brandBold('│'));
229
+ await sleep(50);
230
+ console.log(colors.brandBold('│') + colors.dim(' - Peer dependency conflicts auto-resolved ') + colors.brandBold('│'));
231
+ await sleep(50);
232
+ console.log(colors.brandBold('│') + colors.dim(' - Deprecated packages flagged for removal ') + colors.brandBold('│'));
233
+ await sleep(40);
234
+ console.log(colors.brandBold('└─────────────────────────────────────────────────┘'));
235
+ // Payment flow - check auth and balance BEFORE running migration analysis
236
+ const paymentFlow = new PaymentFlowService();
237
+ // Step 2: Check authentication
238
+ console.log();
239
+ const authResult = await paymentFlow.ensureAuthenticated();
240
+ if (!authResult.success) {
241
+ console.log();
242
+ printInfo('Run `npx depfixer migrate` when ready to continue.');
243
+ return;
244
+ }
245
+ // Get balance info (needed for pass check and user display)
246
+ const balanceInfo = await paymentFlow.getBalanceInfo();
247
+ // Show user details if already logged in (if not already shown during login)
248
+ if (authResult.wasAlreadyLoggedIn && balanceInfo) {
249
+ printUserDetails({
250
+ name: balanceInfo.name,
251
+ email: balanceInfo.email,
252
+ credits: balanceInfo.credits,
253
+ hasActivePass: balanceInfo.hasActivePass,
254
+ showHeader: false,
255
+ });
256
+ }
257
+ console.log();
258
+ // Step 3: Show cost and confirm payment
259
+ const hasPass = balanceInfo?.hasActivePass;
260
+ printCostBox({
261
+ cost,
262
+ tierName,
263
+ prompt: hasPass ? 'Execute migration? (Enter/Esc)' : 'Execute migration? (Enter/Esc)',
264
+ isMigration: true,
265
+ hasActivePass: hasPass,
266
+ });
267
+ // Track: migration_prompt_shown
268
+ analytics.migrationPromptShown({
269
+ creditsNeeded: cost,
270
+ creditsAvailable: balanceInfo?.credits || 0,
271
+ tier: tierName,
272
+ hasActivePass: hasPass,
273
+ targetVersion,
274
+ });
275
+ const shouldExecute = options.yes || await promptYesNo('');
276
+ if (!shouldExecute) {
277
+ // Track: migration_rejected
278
+ analytics.migrationRejected({ reason: 'user_cancelled' });
279
+ console.log();
280
+ printInfo('Migration cancelled.');
281
+ return;
282
+ }
283
+ // Track: migration_accepted
284
+ analytics.migrationAccepted({ creditsDeducted: hasPass ? 0 : cost });
285
+ // Step 4: Check balance
286
+ const readyToPay = await paymentFlow.ensureSufficientBalance(cost);
287
+ if (!readyToPay) {
288
+ console.log();
289
+ printInfo('Run `npx depfixer migrate` when ready to continue.');
290
+ return;
291
+ }
292
+ // Step 5: Run migration analysis
293
+ console.log();
294
+ console.log(colors.whiteBold(`🔄 Analyzing Migration → ${frameworkName} ${targetVersion}`));
295
+ console.log(colors.gray('─'.repeat(50)));
296
+ const packageJsonHash = sessionManager.calculateHash(packageJsonContent);
297
+ const migrationSteps = [
298
+ 'Analyzing current state...',
299
+ `Mapping migration path: ${currentVersion || '?'} → ${targetVersion}`,
300
+ 'Checking ecosystem compatibility...',
301
+ 'Calculating optimal versions...',
302
+ 'Resolving dependency conflicts...',
303
+ 'Building migration plan...',
304
+ ];
305
+ let response;
306
+ await runStepSequence(migrationSteps, async () => {
307
+ response = await apiClient.analyzeMigrate(sanitized, targetVersion, framework);
308
+ if (!response.success || !response.data) {
309
+ throw new Error(response.error || 'Unknown error');
310
+ }
311
+ }, { successMessage: 'Migration plan ready', minStepDuration: 200 });
312
+ const data = response.data;
313
+ const { analysisId } = data;
314
+ // Track: migration_plan_ready
315
+ analytics.migrationPlanReady({
316
+ analysisId,
317
+ targetVersion,
318
+ conflictCount: (data.conflicts || []).length,
319
+ });
320
+ // Step 6: Deduct credits and get solution (payment happens HERE)
321
+ const fixResult = await paymentFlow.deductCredits(analysisId, hasPass);
322
+ if (!fixResult.success || !fixResult.solution) {
323
+ throw new Error(fixResult.error || 'Unknown error');
324
+ }
325
+ // Extract solution for type safety in closures
326
+ const solution = fixResult.solution;
327
+ // Save session as PAID
328
+ await sessionManager.saveSession({
329
+ analysisId,
330
+ intent: 'MIGRATE',
331
+ args: { target: targetVersion },
332
+ originalFileHash: packageJsonHash,
333
+ cost,
334
+ status: 'PAID',
335
+ projectName: sanitized.name || 'unnamed',
336
+ packageCount,
337
+ tierName,
338
+ });
339
+ // Step 7: Show FULL migration plan (user already paid)
340
+ // Get package changes first
341
+ const changes = packageJsonService.getChanges(parsed, solution);
342
+ const removals = (solution.removals || []).map(r => ({
343
+ package: r.package,
344
+ reason: r.reason || 'Deprecated',
345
+ type: r.type,
346
+ }));
347
+ // Calculate health scores
348
+ // Use audit healthScore for "before" (already shown in PROJECT OVERVIEW)
349
+ // Use migration API response for "after" (projected)
350
+ const currentHealthScore = healthScore; // From auditData (line 215)
351
+ const projectedHealthScore = typeof data.healthScore === 'object'
352
+ ? (data.healthScore.after || data.healthScore.projected || 0)
353
+ : (typeof data.healthScore === 'number' ? data.healthScore : 0);
354
+ const breakingChanges = (data.conflicts || []).filter((c) => c.severity === 'critical' || c.severity === 'high').length;
355
+ // Show migration plan header - smooth reveal
356
+ await sleep(100);
357
+ printMigrationPlanHeader(frameworkName, currentVersion || '?', targetVersion);
358
+ // Show projection stats
359
+ await sleep(150);
360
+ printProjectionStats({
361
+ currentHealth: currentHealthScore,
362
+ projectedHealth: projectedHealthScore,
363
+ packageCount: changes.length,
364
+ breakingChanges,
365
+ });
366
+ if (changes.length > 0) {
367
+ await sleep(150);
368
+ console.log();
369
+ console.log(colors.whiteBold('📦 Package Updates:'));
370
+ // Map and sort changes: major at top, then minor, then patch
371
+ const mappedChanges = changes.map((c) => ({
372
+ package: c.package,
373
+ currentVersion: c.from,
374
+ targetVersion: c.to,
375
+ changeType: getChangeType(c.from, c.to),
376
+ }));
377
+ // Sort by change type: major > minor > patch > none
378
+ const changeTypeOrder = { major: 0, minor: 1, patch: 2, none: 3 };
379
+ mappedChanges.sort((a, b) => {
380
+ const orderA = changeTypeOrder[a.changeType] ?? 3;
381
+ const orderB = changeTypeOrder[b.changeType] ?? 3;
382
+ return orderA - orderB;
383
+ });
384
+ // Show table rows with smooth reveal
385
+ const tableLines = createMigrationTable(mappedChanges).split('\n');
386
+ for (const line of tableLines) {
387
+ console.log(line);
388
+ await sleep(30);
389
+ }
390
+ }
391
+ // Show engine requirements (from solution.engines)
392
+ if (solution.engines && Object.keys(solution.engines).length > 0) {
393
+ await sleep(100);
394
+ console.log();
395
+ console.log(colors.whiteBold('⚙️ Engine Requirements:'));
396
+ if (solution.engines.node) {
397
+ console.log(` ${colors.dim('•')} Node.js: ${colors.brand(solution.engines.node)}`);
398
+ }
399
+ if (solution.engines.npm) {
400
+ console.log(` ${colors.dim('•')} npm: ${colors.brand(solution.engines.npm)}`);
401
+ }
402
+ }
403
+ // Show packages to add (missing peer dependencies)
404
+ const packagesToAdd = (data.conflicts || []).filter((c) => !c.currentVersion || c.currentVersion.toLowerCase() === 'not installed');
405
+ if (packagesToAdd.length > 0) {
406
+ await sleep(100);
407
+ console.log();
408
+ console.log(colors.whiteBold('📦 Packages to Add:'));
409
+ for (const pkg of packagesToAdd) {
410
+ await sleep(60);
411
+ // Build message from requiredBy if available
412
+ let message;
413
+ if (pkg.requiredBy && Array.isArray(pkg.requiredBy) && pkg.requiredBy.length > 0) {
414
+ const requiredRange = pkg.requiredRange || 'required version';
415
+ message = `Required as peer dependency by ${pkg.requiredBy.join(', ')} (${requiredRange})`;
416
+ }
417
+ else if (pkg.isPeerDependency && pkg.requiredRange) {
418
+ message = `Missing peer dependency (${pkg.requiredRange})`;
419
+ }
420
+ else {
421
+ message = pkg.description || 'Required dependency';
422
+ }
423
+ console.log(` ${colors.brand('+')} ${pkg.package}`);
424
+ console.log(` ${colors.dim(message)}`);
425
+ }
426
+ }
427
+ // Show packages to remove (using table format)
428
+ if (removals.length > 0) {
429
+ await sleep(100);
430
+ console.log();
431
+ console.log(colors.whiteBold('🗑 Packages to Remove:'));
432
+ const removalLines = createMigrationTable(removals.map(r => ({
433
+ package: r.package,
434
+ currentVersion: '*',
435
+ targetVersion: 'REMOVE',
436
+ changeType: 'deprec',
437
+ isRemoval: true,
438
+ }))).split('\n');
439
+ for (const line of removalLines) {
440
+ console.log(line);
441
+ await sleep(30);
442
+ }
443
+ }
444
+ // Step 8: Ask to apply (FREE - already paid)
445
+ await sleep(200);
446
+ console.log();
447
+ // Explain what will be changed
448
+ console.log(colors.gray('────────────────────────────────────────'));
449
+ await sleep(50);
450
+ console.log(colors.gray(' 📝 Only ') + colors.white('package.json') + colors.gray(' will be modified'));
451
+ await sleep(50);
452
+ console.log(colors.gray(' 💾 A backup (') + colors.white('package.json.bak') + colors.gray(') will be created'));
453
+ await sleep(50);
454
+ console.log(colors.gray('────────────────────────────────────────'));
455
+ await sleep(100);
456
+ console.log();
457
+ // Track: migration_apply_prompt
458
+ analytics.migrationApplyPrompt({
459
+ changeCount: changes.length,
460
+ removalCount: removals.length,
461
+ });
462
+ console.log(colors.gray('[?] Apply changes now? (Enter/Esc)'));
463
+ const shouldApplyFix = options.yes || await promptYesNo('');
464
+ if (!shouldApplyFix) {
465
+ // Track: migration_deferred
466
+ analytics.migrationDeferred({ reason: 'user_declined_apply' });
467
+ console.log();
468
+ printInfo('Solution saved. Run `npx depfixer fix` anytime to apply.');
469
+ return;
470
+ }
471
+ // Step 9: Apply fix (FREE) - smooth reveal
472
+ await sleep(150);
473
+ console.log();
474
+ console.log(colors.whiteBold(`🔧 Applying Migration...`));
475
+ await sleep(80);
476
+ console.log(colors.gray('─'.repeat(50)));
477
+ const upgradeCount = changes.length;
478
+ const removalCount = removals.length;
479
+ const migrationFixSteps = [
480
+ 'Reading package.json...',
481
+ `Applying ${upgradeCount} upgrade${upgradeCount !== 1 ? 's' : ''}...`,
482
+ 'Resolving peer conflicts...',
483
+ removalCount > 0 ? `Removing ${removalCount} deprecated package${removalCount !== 1 ? 's' : ''}...` : 'Checking for deprecated packages...',
484
+ 'Validating final state...',
485
+ 'Writing package.json...',
486
+ ];
487
+ let applyResult;
488
+ await sleep(100);
489
+ await runStepSequence(migrationFixSteps, async () => {
490
+ applyResult = await packageJsonService.applySurgicalFixes(projectDir, changes, removals, solution.engines);
491
+ }, { successMessage: 'Migration complete', minStepDuration: 150 });
492
+ const { backupPath, applied, removed, enginesUpdated } = applyResult;
493
+ // Show success using design system - smooth reveal
494
+ await sleep(200);
495
+ printSuccessBox({
496
+ updated: applied,
497
+ removed,
498
+ backupPath,
499
+ enginesUpdated,
500
+ });
501
+ // Track: migration_applied
502
+ analytics.migrationApplied({
503
+ updatedCount: applied,
504
+ removedCount: removed,
505
+ enginesUpdated,
506
+ targetVersion,
507
+ });
508
+ // Track: session_ended (successful migration)
509
+ await analytics.sessionEnded({
510
+ outcome: 'migration_applied',
511
+ targetVersion,
512
+ });
513
+ }
514
+ catch (error) {
515
+ // Track: session_ended (error)
516
+ await analytics.sessionEnded({
517
+ outcome: 'error',
518
+ error: error.message,
519
+ });
520
+ printError(error.message);
521
+ process.exit(1);
522
+ }
523
+ }
524
+ /**
525
+ * Interactive version selector using arrow keys
526
+ */
527
+ async function selectVersion(options, frameworkName) {
528
+ // Auto-select first option in dev mode when env var is set
529
+ if (isAutoConfirmEnabled() && options.length > 0) {
530
+ console.log(`${colors.dim(' Auto-selecting:')} ${colors.brand(options[0].label)} ${colors.dim('[auto]')}`);
531
+ return options[0];
532
+ }
533
+ return new Promise((resolve) => {
534
+ let selectedIndex = 0;
535
+ const render = () => {
536
+ // Move cursor up and clear previous render
537
+ if (selectedIndex > 0 || options.length > 0) {
538
+ process.stdout.write(`\x1b[${options.length + 2}A`);
539
+ }
540
+ console.log(colors.dim(' Use ↑↓ arrows to select, Enter to confirm, Esc to cancel'));
541
+ console.log();
542
+ for (let i = 0; i < options.length; i++) {
543
+ const opt = options[i];
544
+ const isSelected = i === selectedIndex;
545
+ const prefix = isSelected ? colors.brand('❯ ') : ' ';
546
+ const color = opt.type === 'major' ? colors.action : colors.brand;
547
+ const label = isSelected ? chalk.bold(color(opt.label)) : colors.dim(opt.label);
548
+ console.log(`${prefix}${label}`);
549
+ }
550
+ };
551
+ // Initial render
552
+ console.log(colors.dim(' Use ↑↓ arrows to select, Enter to confirm, Esc to cancel'));
553
+ console.log();
554
+ for (let i = 0; i < options.length; i++) {
555
+ const opt = options[i];
556
+ const isSelected = i === selectedIndex;
557
+ const prefix = isSelected ? colors.brand('❯ ') : ' ';
558
+ const color = opt.type === 'major' ? colors.action : colors.brand;
559
+ const label = isSelected ? chalk.bold(color(opt.label)) : colors.dim(opt.label);
560
+ console.log(`${prefix}${label}`);
561
+ }
562
+ if (process.stdin.isTTY) {
563
+ process.stdin.setRawMode(true);
564
+ }
565
+ process.stdin.resume();
566
+ const onKeyPress = (key) => {
567
+ const char = key.toString();
568
+ // Up arrow
569
+ if (char === '\x1b[A') {
570
+ selectedIndex = (selectedIndex - 1 + options.length) % options.length;
571
+ render();
572
+ }
573
+ // Down arrow
574
+ else if (char === '\x1b[B') {
575
+ selectedIndex = (selectedIndex + 1) % options.length;
576
+ render();
577
+ }
578
+ // Enter
579
+ else if (char === '\r' || char === '\n') {
580
+ cleanup();
581
+ resolve(options[selectedIndex]);
582
+ }
583
+ // Escape
584
+ else if (char === '\x1b' && key.length === 1) {
585
+ cleanup();
586
+ resolve(null);
587
+ }
588
+ // Ctrl+C
589
+ else if (char === '\x03') {
590
+ cleanup();
591
+ process.exit(0);
592
+ }
593
+ };
594
+ const cleanup = () => {
595
+ process.stdin.removeListener('data', onKeyPress);
596
+ if (process.stdin.isTTY) {
597
+ process.stdin.setRawMode(false);
598
+ }
599
+ process.stdin.pause();
600
+ };
601
+ process.stdin.on('data', onKeyPress);
602
+ });
603
+ }
604
+ /**
605
+ * Check if auto-confirm is enabled (dev mode only)
606
+ */
607
+ function isAutoConfirmEnabled() {
608
+ const isDevMode = process.env.NODE_ENV === 'development' ||
609
+ (process.env.DEPFIXER_API_URL?.includes('localhost') ?? false);
610
+ return isDevMode && process.env.DEPFIXER_AUTO_CONFIRM === 'true';
611
+ }
612
+ /**
613
+ * Simple Enter/Esc prompt (Enter = Yes, Esc = No)
614
+ */
615
+ async function promptYesNo(question) {
616
+ // Auto-confirm in dev mode when env var is set
617
+ if (isAutoConfirmEnabled()) {
618
+ if (question) {
619
+ console.log(`${question} ${colors.dim('(Enter/Esc)')} ${colors.success('Yes')} ${colors.dim('[auto]')}`);
620
+ }
621
+ else {
622
+ console.log(`${colors.success('Yes')} ${colors.dim('[auto]')}`);
623
+ }
624
+ return true;
625
+ }
626
+ return new Promise((resolve) => {
627
+ if (question) {
628
+ process.stdout.write(`${question} ${colors.dim('(Enter/Esc)')} `);
629
+ }
630
+ if (process.stdin.isTTY) {
631
+ process.stdin.setRawMode(true);
632
+ }
633
+ process.stdin.resume();
634
+ const onKeyPress = (key) => {
635
+ const char = key.toString();
636
+ if (char === '\r' || char === '\n') {
637
+ cleanup();
638
+ console.log(colors.success('Yes'));
639
+ resolve(true);
640
+ }
641
+ else if (char === '\x1b') {
642
+ cleanup();
643
+ console.log(colors.danger('No'));
644
+ resolve(false);
645
+ }
646
+ else if (char.toLowerCase() === 'y') {
647
+ cleanup();
648
+ console.log(colors.success('Yes'));
649
+ resolve(true);
650
+ }
651
+ else if (char.toLowerCase() === 'n') {
652
+ cleanup();
653
+ console.log(colors.danger('No'));
654
+ resolve(false);
655
+ }
656
+ else if (char === '\x03') {
657
+ cleanup();
658
+ process.exit(0);
659
+ }
660
+ };
661
+ const cleanup = () => {
662
+ process.stdin.removeListener('data', onKeyPress);
663
+ if (process.stdin.isTTY) {
664
+ process.stdin.setRawMode(false);
665
+ }
666
+ process.stdin.pause();
667
+ };
668
+ process.stdin.on('data', onKeyPress);
669
+ });
670
+ }
671
+ /**
672
+ * Simple client-side framework detection
673
+ */
674
+ function detectFramework(pkg) {
675
+ const deps = { ...pkg.dependencies, ...pkg.devDependencies };
676
+ if (deps['@angular/core'])
677
+ return 'angular';
678
+ if (deps['react'])
679
+ return 'react';
680
+ if (deps['vue'])
681
+ return 'vue';
682
+ if (deps['svelte'])
683
+ return 'svelte';
684
+ if (deps['next'])
685
+ return 'react';
686
+ if (deps['nuxt'])
687
+ return 'vue';
688
+ return undefined;
689
+ }
690
+ /**
691
+ * Detect current framework version
692
+ */
693
+ function detectCurrentVersion(pkg, framework) {
694
+ const deps = { ...pkg.dependencies, ...pkg.devDependencies };
695
+ switch (framework) {
696
+ case 'angular':
697
+ return extractVersion(deps['@angular/core']);
698
+ case 'react':
699
+ return extractVersion(deps['react']);
700
+ case 'vue':
701
+ return extractVersion(deps['vue']);
702
+ default:
703
+ return undefined;
704
+ }
705
+ }
706
+ /**
707
+ * Extract clean version from semver string
708
+ */
709
+ function extractVersion(version) {
710
+ if (!version)
711
+ return undefined;
712
+ const cleaned = version.replace(/[~^]/g, '');
713
+ return cleaned;
714
+ }
715
+ /**
716
+ * Determine change type for migration display
717
+ */
718
+ function getChangeType(current, target) {
719
+ if (!current || !target)
720
+ return 'none';
721
+ const currentMajor = parseInt(current.replace(/[~^]/g, '').split('.')[0], 10);
722
+ const targetMajor = parseInt(target.replace(/[~^]/g, '').split('.')[0], 10);
723
+ if (isNaN(currentMajor) || isNaN(targetMajor))
724
+ return 'none';
725
+ if (targetMajor > currentMajor)
726
+ return 'major';
727
+ const currentMinor = parseInt(current.replace(/[~^]/g, '').split('.')[1] || '0', 10);
728
+ const targetMinor = parseInt(target.replace(/[~^]/g, '').split('.')[1] || '0', 10);
729
+ if (targetMinor > currentMinor)
730
+ return 'minor';
731
+ return 'patch';
732
+ }
733
+ /**
734
+ * Get meaningful reason for migration
735
+ */
736
+ function getMigrationReason(packageName, framework) {
737
+ // Core framework packages
738
+ if (framework === 'angular') {
739
+ if (packageName.startsWith('@angular/')) {
740
+ return 'Core framework';
741
+ }
742
+ if (packageName === 'zone.js') {
743
+ return 'Angular requirement';
744
+ }
745
+ if (packageName === 'rxjs') {
746
+ return 'Angular dependency';
747
+ }
748
+ if (packageName === 'typescript') {
749
+ return 'Build requirement';
750
+ }
751
+ }
752
+ if (framework === 'react') {
753
+ if (packageName === 'react' || packageName === 'react-dom') {
754
+ return 'Core framework';
755
+ }
756
+ if (packageName.startsWith('@types/react')) {
757
+ return 'Type definitions';
758
+ }
759
+ }
760
+ // Type definitions
761
+ if (packageName.startsWith('@types/')) {
762
+ return 'Type definitions';
763
+ }
764
+ // Build tools
765
+ if (['typescript', 'webpack', 'esbuild', 'vite', 'rollup'].includes(packageName)) {
766
+ return 'Build tool';
767
+ }
768
+ // Testing
769
+ if (['jest', 'karma', 'jasmine', 'mocha', 'vitest'].some(t => packageName.includes(t))) {
770
+ return 'Testing';
771
+ }
772
+ // Linting
773
+ if (packageName.includes('eslint') || packageName.includes('prettier')) {
774
+ return 'Code quality';
775
+ }
776
+ return 'Compatibility';
777
+ }
778
+ /**
779
+ * Wrap text at specified width
780
+ */
781
+ function wrapText(text, maxWidth) {
782
+ if (!text)
783
+ return [];
784
+ const words = text.split(' ');
785
+ const lines = [];
786
+ let currentLine = '';
787
+ for (const word of words) {
788
+ if (currentLine.length + word.length + 1 <= maxWidth) {
789
+ currentLine += (currentLine ? ' ' : '') + word;
790
+ }
791
+ else {
792
+ if (currentLine)
793
+ lines.push(currentLine);
794
+ currentLine = word;
795
+ }
796
+ }
797
+ if (currentLine)
798
+ lines.push(currentLine);
799
+ return lines;
800
+ }
801
+ /**
802
+ * Create teaser table (limited info, no solutions)
803
+ * Shows only severity and package name with generic issue type
804
+ */
805
+ function createTeaserTable(conflicts) {
806
+ const lines = [];
807
+ lines.push(colors.gray('┌────────────┬─────────────────────────┬────────────────┐'));
808
+ lines.push(colors.gray('│') + colors.whiteBold(' SEVERITY ') + colors.gray('│') + colors.whiteBold(' PACKAGE ') + colors.gray('│') + colors.whiteBold(' ISSUE ') + colors.gray('│'));
809
+ lines.push(colors.gray('├────────────┼─────────────────────────┼────────────────┤'));
810
+ for (const conflict of conflicts) {
811
+ const severity = conflict.severity?.toUpperCase() || 'UNKNOWN';
812
+ const severityColor = severity === 'CRITICAL' ? colors.dangerBold :
813
+ severity === 'HIGH' ? colors.danger :
814
+ severity === 'MEDIUM' ? colors.warning :
815
+ colors.dim;
816
+ // Truncate package name if too long
817
+ const pkg = (conflict.package || '').substring(0, 23).padEnd(23);
818
+ // Generic issue description (no details)
819
+ const issueType = severity === 'CRITICAL' ? 'Peer Clash' :
820
+ severity === 'HIGH' ? 'Version Gap' :
821
+ severity === 'MEDIUM' ? 'Conflict' : 'Minor';
822
+ lines.push(colors.gray('│') +
823
+ ` ${severityColor(severity.padEnd(10))}` +
824
+ colors.gray('│') +
825
+ ` ${pkg}` +
826
+ colors.gray('│') +
827
+ ` ${issueType.padEnd(14)}` +
828
+ colors.gray('│'));
829
+ }
830
+ lines.push(colors.gray('└────────────┴─────────────────────────┴────────────────┘'));
831
+ return lines.join('\n');
832
+ }
833
+ /**
834
+ * Show project info when already on latest version
835
+ */
836
+ async function showLatestVersionInfo(apiClient, sanitized, framework, frameworkName, currentVersion) {
837
+ // Get audit data for health info
838
+ try {
839
+ const auditResponse = await apiClient.analyzeAudit(sanitized, framework);
840
+ if (auditResponse.success && auditResponse.data) {
841
+ const healthScore = auditResponse.data.healthScore || 0;
842
+ const packageCount = auditResponse.data.totalPackages || 0;
843
+ const conflicts = auditResponse.data.conflicts || [];
844
+ const issueCount = conflicts.length;
845
+ const healthInfo = getHealthStatus(healthScore);
846
+ console.log();
847
+ console.log(colors.whiteBold('📊 PROJECT OVERVIEW'));
848
+ console.log(colors.gray('─'.repeat(50)));
849
+ console.log(`${colors.whiteBold('🏥 Health:')} ${renderHealthBar(healthScore)} ${healthInfo.color.bold(`${healthScore}/100`)} (${healthInfo.color(healthInfo.text)})`);
850
+ console.log(`${colors.whiteBold('📦 Packages:')} ${colors.brand(`${packageCount}`)} total`);
851
+ console.log(`${colors.whiteBold('🎯 Version:')} ${colors.success(`${frameworkName} ${currentVersion || 'latest'}`)}`);
852
+ console.log();
853
+ console.log(colors.successBold('✓ You\'re already on the latest supported version!'));
854
+ console.log(colors.dim(' No migration needed.'));
855
+ // If there are issues, suggest running analyze
856
+ if (issueCount > 0) {
857
+ console.log();
858
+ console.log(colors.warningBold(`⚠️ ${issueCount} issue${issueCount !== 1 ? 's' : ''} detected in your dependencies`));
859
+ console.log(colors.dim(' Run the following command to analyze and fix:'));
860
+ console.log();
861
+ console.log(` ${colors.brand('npx depfixer')}`);
862
+ }
863
+ }
864
+ else {
865
+ console.log();
866
+ printSuccess(`You're already on the latest supported version of ${frameworkName}!`);
867
+ }
868
+ }
869
+ catch {
870
+ console.log();
871
+ printSuccess(`You're already on the latest supported version of ${frameworkName}!`);
872
+ }
873
+ }
874
+ //# sourceMappingURL=migrate.js.map