depfixer 1.1.9 → 1.2.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/commands/check.d.ts +8 -0
- package/dist/commands/check.d.ts.map +1 -0
- package/dist/commands/check.js +742 -0
- package/dist/commands/check.js.map +1 -0
- package/dist/commands/migrate.d.ts +1 -7
- package/dist/commands/migrate.d.ts.map +1 -1
- package/dist/commands/migrate.js +702 -706
- package/dist/commands/migrate.js.map +1 -1
- package/dist/commands/smart.d.ts.map +1 -1
- package/dist/commands/smart.js +954 -911
- package/dist/commands/smart.js.map +1 -1
- package/dist/constants/analysis.constants.d.ts +2 -0
- package/dist/constants/analysis.constants.d.ts.map +1 -1
- package/dist/constants/analysis.constants.js +9 -0
- package/dist/constants/analysis.constants.js.map +1 -1
- package/dist/index.js +57 -17
- package/dist/index.js.map +1 -1
- package/dist/services/api-client.d.ts +89 -0
- package/dist/services/api-client.d.ts.map +1 -1
- package/dist/services/api-client.js +94 -0
- package/dist/services/api-client.js.map +1 -1
- package/dist/services/framework-detector.d.ts +23 -0
- package/dist/services/framework-detector.d.ts.map +1 -0
- package/dist/services/framework-detector.js +230 -0
- package/dist/services/framework-detector.js.map +1 -0
- package/dist/services/payment-flow.d.ts +3 -1
- package/dist/services/payment-flow.d.ts.map +1 -1
- package/dist/services/payment-flow.js +7 -0
- package/dist/services/payment-flow.js.map +1 -1
- package/dist/utils/framework-utils.d.ts +29 -0
- package/dist/utils/framework-utils.d.ts.map +1 -0
- package/dist/utils/framework-utils.js +45 -0
- package/dist/utils/framework-utils.js.map +1 -0
- package/dist/utils/interactive-picker.d.ts +12 -0
- package/dist/utils/interactive-picker.d.ts.map +1 -0
- package/dist/utils/interactive-picker.js +109 -0
- package/dist/utils/interactive-picker.js.map +1 -0
- package/dist/utils/package-parser.d.ts +24 -0
- package/dist/utils/package-parser.d.ts.map +1 -0
- package/dist/utils/package-parser.js +63 -0
- package/dist/utils/package-parser.js.map +1 -0
- package/dist/utils/prompt.d.ts +13 -0
- package/dist/utils/prompt.d.ts.map +1 -0
- package/dist/utils/prompt.js +71 -0
- package/dist/utils/prompt.js.map +1 -0
- package/dist/utils/table-builders.d.ts +40 -0
- package/dist/utils/table-builders.d.ts.map +1 -0
- package/dist/utils/table-builders.js +175 -0
- package/dist/utils/table-builders.js.map +1 -0
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/package.json +23 -3
package/dist/commands/migrate.js
CHANGED
|
@@ -1,3 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Migrate Command
|
|
3
|
+
*
|
|
4
|
+
* Interactive migration planner for framework upgrades.
|
|
5
|
+
* Supports Angular, React (web), and Vue framework migrations.
|
|
6
|
+
*
|
|
7
|
+
* Flow:
|
|
8
|
+
* 1. Detect framework via API (server-side rules)
|
|
9
|
+
* 2. Fetch available versions from server
|
|
10
|
+
* 3. Show version selector (minor patches vs major upgrades)
|
|
11
|
+
* 4. User selects target version
|
|
12
|
+
* 5. Show migration plan with cost
|
|
13
|
+
* 6. Confirm and execute
|
|
14
|
+
*/
|
|
1
15
|
import chalk from 'chalk';
|
|
2
16
|
import { ApiClient } from '../services/api-client.js';
|
|
3
17
|
import { PackageJsonService } from '../services/package-json.js';
|
|
@@ -7,16 +21,52 @@ import { analytics } from '../services/analytics.js';
|
|
|
7
21
|
import { getDeviceId } from '../services/device-id.js';
|
|
8
22
|
import { createSpinner, createMigrationTable, printError, printSuccess, printInfo, runStepSequence, sleep, } from '../utils/output.js';
|
|
9
23
|
import { colors, printCliHeader, printMigrationPlanHeader, printProjectionStats, printCostBox, printSuccessBox, renderHealthBar, getHealthStatus, printUserDetails, } from '../utils/design-system.js';
|
|
24
|
+
import { promptYesNo } from '../utils/prompt.js';
|
|
25
|
+
import { getChangeType } from '../utils/framework-utils.js';
|
|
26
|
+
// ============================================================================
|
|
27
|
+
// DEV MODE HELPERS
|
|
28
|
+
// ============================================================================
|
|
29
|
+
/**
|
|
30
|
+
* Check if auto-confirm is enabled (dev mode only)
|
|
31
|
+
*/
|
|
32
|
+
function isAutoConfirmEnabled() {
|
|
33
|
+
const isDevMode = process.env.NODE_ENV === 'development' ||
|
|
34
|
+
(process.env.DEPFIXER_API_URL?.includes('localhost') ?? false);
|
|
35
|
+
return isDevMode && process.env.DEPFIXER_AUTO_CONFIRM === 'true';
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* Prompt for confirmation with dev mode auto-confirm support
|
|
39
|
+
*/
|
|
40
|
+
async function confirmPrompt(question, options) {
|
|
41
|
+
// --yes flag overrides
|
|
42
|
+
if (options.yes) {
|
|
43
|
+
if (question) {
|
|
44
|
+
console.log(`${question} ${colors.dim('(Enter/Esc)')} ${colors.success('Yes')} ${colors.dim('[--yes]')}`);
|
|
45
|
+
}
|
|
46
|
+
else {
|
|
47
|
+
console.log(`${colors.success('Yes')} ${colors.dim('[--yes]')}`);
|
|
48
|
+
}
|
|
49
|
+
return true;
|
|
50
|
+
}
|
|
51
|
+
// Dev mode auto-confirm
|
|
52
|
+
if (isAutoConfirmEnabled()) {
|
|
53
|
+
if (question) {
|
|
54
|
+
console.log(`${question} ${colors.dim('(Enter/Esc)')} ${colors.success('Yes')} ${colors.dim('[auto]')}`);
|
|
55
|
+
}
|
|
56
|
+
else {
|
|
57
|
+
console.log(`${colors.success('Yes')} ${colors.dim('[auto]')}`);
|
|
58
|
+
}
|
|
59
|
+
return true;
|
|
60
|
+
}
|
|
61
|
+
return promptYesNo(question);
|
|
62
|
+
}
|
|
63
|
+
// ============================================================================
|
|
64
|
+
// MAIN COMMAND
|
|
65
|
+
// ============================================================================
|
|
10
66
|
/**
|
|
11
67
|
* Migrate command
|
|
12
68
|
*
|
|
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
|
|
69
|
+
* Interactive migration with version selection.
|
|
20
70
|
*
|
|
21
71
|
* Usage:
|
|
22
72
|
* npx depfixer migrate
|
|
@@ -27,506 +77,669 @@ export async function migrateCommand(options) {
|
|
|
27
77
|
// Create device ID early (for anonymous user tracking before login)
|
|
28
78
|
getDeviceId();
|
|
29
79
|
try {
|
|
30
|
-
|
|
31
|
-
const
|
|
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);
|
|
80
|
+
// Initialize context with API-based framework detection
|
|
81
|
+
const ctx = await initializeMigrationContext(projectDir, options);
|
|
46
82
|
// Print CLI header
|
|
47
83
|
printCliHeader('migrate');
|
|
48
84
|
// Track: migrate_started
|
|
49
85
|
analytics.migrateStarted({ command: 'migrate' });
|
|
86
|
+
// Show project info
|
|
50
87
|
console.log();
|
|
51
|
-
console.log(colors.whiteBold(`📦 Project: ${colors.brand(sanitized.name || 'unnamed')}`));
|
|
52
|
-
console.log(colors.dim(` Framework: ${frameworkName} ${currentVersion || 'unknown'}`));
|
|
88
|
+
console.log(colors.whiteBold(`📦 Project: ${colors.brand(ctx.sanitized.name || 'unnamed')}`));
|
|
89
|
+
console.log(colors.dim(` Framework: ${ctx.frameworkName} ${ctx.currentVersion || 'unknown'}`));
|
|
53
90
|
console.log();
|
|
54
|
-
let
|
|
55
|
-
|
|
56
|
-
{
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
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;
|
|
91
|
+
// Fetch versions and let user select target
|
|
92
|
+
const targetVersion = await fetchAndSelectVersion(ctx);
|
|
93
|
+
if (!targetVersion) {
|
|
94
|
+
return; // User cancelled or already on latest
|
|
95
|
+
}
|
|
96
|
+
// Get cost estimate
|
|
97
|
+
const costEstimate = await getCostEstimate(ctx);
|
|
98
|
+
// Show project overview with migration plan
|
|
99
|
+
await showProjectOverview(ctx, costEstimate, targetVersion);
|
|
100
|
+
// Handle payment flow
|
|
101
|
+
const paymentFlow = new PaymentFlowService();
|
|
102
|
+
const paymentReady = await handleMigrationPaymentFlow(ctx, costEstimate, paymentFlow, targetVersion);
|
|
103
|
+
if (!paymentReady.success) {
|
|
104
|
+
return;
|
|
185
105
|
}
|
|
186
|
-
//
|
|
187
|
-
const
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
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('│'));
|
|
106
|
+
// Run migration analysis
|
|
107
|
+
const migrationResult = await runMigrationAnalysis(ctx, targetVersion, costEstimate);
|
|
108
|
+
// Deduct credits and get solution
|
|
109
|
+
const fixResult = await paymentFlow.deductCredits(migrationResult.analysisId, paymentReady.hasActivePass);
|
|
110
|
+
if (!fixResult.success || !fixResult.solution) {
|
|
111
|
+
throw new Error(fixResult.error || 'Unknown error');
|
|
220
112
|
}
|
|
221
|
-
|
|
222
|
-
|
|
113
|
+
// Save session as PAID
|
|
114
|
+
await saveMigrationSession(ctx, migrationResult.analysisId, targetVersion, costEstimate);
|
|
115
|
+
// Show full migration plan
|
|
116
|
+
const changes = ctx.packageJsonService.getChanges(ctx.parsed, fixResult.solution);
|
|
117
|
+
const removals = mapRemovals(fixResult.solution.removals);
|
|
118
|
+
await showMigrationPlan(ctx, costEstimate, targetVersion, migrationResult.data, fixResult.solution, changes, removals);
|
|
119
|
+
// Ask to apply and execute
|
|
120
|
+
await applyMigrationFix(ctx, changes, removals, fixResult.solution, targetVersion);
|
|
121
|
+
}
|
|
122
|
+
catch (error) {
|
|
123
|
+
await handleMigrationError(error);
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
// ============================================================================
|
|
127
|
+
// INITIALIZATION
|
|
128
|
+
// ============================================================================
|
|
129
|
+
/**
|
|
130
|
+
* Initialize migration context with API-based framework detection
|
|
131
|
+
*/
|
|
132
|
+
async function initializeMigrationContext(projectDir, options) {
|
|
133
|
+
const packageJsonService = new PackageJsonService();
|
|
134
|
+
const apiClient = new ApiClient();
|
|
135
|
+
const sessionManager = new SessionManager(projectDir);
|
|
136
|
+
// Read and sanitize package.json
|
|
137
|
+
const { content: packageJsonContent, parsed } = await packageJsonService.read(projectDir);
|
|
138
|
+
const sanitized = packageJsonService.sanitize(parsed);
|
|
139
|
+
// Detect framework using server API (more accurate than local detection)
|
|
140
|
+
let framework;
|
|
141
|
+
let currentVersion;
|
|
142
|
+
let currentMajor = 0;
|
|
143
|
+
const spinner = createSpinner('Detecting framework...').start();
|
|
144
|
+
try {
|
|
145
|
+
const detectResponse = await apiClient.detectFramework(sanitized);
|
|
146
|
+
if (detectResponse.success && detectResponse.data) {
|
|
147
|
+
framework = detectResponse.data.name;
|
|
148
|
+
currentVersion = detectResponse.data.version;
|
|
149
|
+
currentMajor = detectResponse.data.majorVersion || 0;
|
|
150
|
+
spinner.succeed(`Detected ${framework} ${currentVersion}`);
|
|
223
151
|
}
|
|
224
152
|
else {
|
|
225
|
-
|
|
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;
|
|
153
|
+
spinner.fail('Framework detection failed');
|
|
244
154
|
}
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
155
|
+
}
|
|
156
|
+
catch (error) {
|
|
157
|
+
spinner.fail('Framework detection failed');
|
|
158
|
+
// If API fails, framework will be undefined
|
|
159
|
+
}
|
|
160
|
+
if (!framework) {
|
|
161
|
+
printError('Could not detect framework. Migration requires Angular, React, or Vue project.');
|
|
162
|
+
printInfo('Note: Next.js, React Native, Expo, and Svelte are not supported for migration.');
|
|
163
|
+
process.exit(1);
|
|
164
|
+
}
|
|
165
|
+
const frameworkName = framework.charAt(0).toUpperCase() + framework.slice(1);
|
|
166
|
+
// Calculate package.json hash
|
|
167
|
+
const packageJsonHash = sessionManager.calculateHash(packageJsonContent);
|
|
168
|
+
return {
|
|
169
|
+
projectDir,
|
|
170
|
+
options,
|
|
171
|
+
packageJsonService,
|
|
172
|
+
apiClient,
|
|
173
|
+
sessionManager,
|
|
174
|
+
packageJsonContent,
|
|
175
|
+
parsed,
|
|
176
|
+
sanitized,
|
|
177
|
+
framework,
|
|
178
|
+
frameworkName,
|
|
179
|
+
currentVersion,
|
|
180
|
+
currentMajor,
|
|
181
|
+
packageJsonHash,
|
|
182
|
+
};
|
|
183
|
+
}
|
|
184
|
+
// ============================================================================
|
|
185
|
+
// VERSION SELECTION
|
|
186
|
+
// ============================================================================
|
|
187
|
+
/**
|
|
188
|
+
* Fetch available versions and let user select target
|
|
189
|
+
*/
|
|
190
|
+
async function fetchAndSelectVersion(ctx) {
|
|
191
|
+
const { apiClient, sanitized, framework, frameworkName, currentVersion, currentMajor, options } = ctx;
|
|
192
|
+
const spinner = createSpinner('Fetching available versions...').start();
|
|
193
|
+
try {
|
|
194
|
+
const versionsResponse = await apiClient.getFrameworkVersions(framework, currentMajor);
|
|
195
|
+
// Check if already on latest version
|
|
196
|
+
if (!versionsResponse.success) {
|
|
197
|
+
const errorMsg = versionsResponse.error || '';
|
|
198
|
+
if (errorMsg.includes('No newer versions') || errorMsg.includes('No supported versions')) {
|
|
199
|
+
spinner.succeed('Version check complete');
|
|
200
|
+
await showLatestVersionInfo(apiClient, sanitized, framework, frameworkName, currentVersion);
|
|
201
|
+
return null;
|
|
202
|
+
}
|
|
203
|
+
spinner.fail('Failed to fetch versions');
|
|
204
|
+
throw new Error(errorMsg || 'Could not fetch available versions');
|
|
205
|
+
}
|
|
206
|
+
spinner.succeed('Versions loaded');
|
|
207
|
+
// Track: versions_loaded
|
|
208
|
+
analytics.versionsLoaded({
|
|
209
|
+
framework,
|
|
210
|
+
currentVersion,
|
|
211
|
+
groupCount: versionsResponse.data?.groups?.length || 0,
|
|
274
212
|
});
|
|
275
|
-
const
|
|
276
|
-
if (!
|
|
277
|
-
|
|
278
|
-
|
|
213
|
+
const groups = versionsResponse.data?.groups;
|
|
214
|
+
if (!groups || groups.length === 0) {
|
|
215
|
+
await showLatestVersionInfo(apiClient, sanitized, framework, frameworkName, currentVersion);
|
|
216
|
+
return null;
|
|
217
|
+
}
|
|
218
|
+
// Build version options
|
|
219
|
+
const versionOptions = buildVersionOptions(groups, frameworkName, currentMajor);
|
|
220
|
+
// Show version selector
|
|
221
|
+
displayVersionOptions(versionOptions);
|
|
222
|
+
// Interactive selection
|
|
223
|
+
const selectedVersion = await selectVersion(versionOptions, frameworkName, options);
|
|
224
|
+
if (!selectedVersion) {
|
|
279
225
|
console.log();
|
|
280
226
|
printInfo('Migration cancelled.');
|
|
281
|
-
return;
|
|
282
|
-
}
|
|
283
|
-
// Track:
|
|
284
|
-
analytics.
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
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,
|
|
227
|
+
return null;
|
|
228
|
+
}
|
|
229
|
+
// Track: version_selected
|
|
230
|
+
analytics.versionSelected({
|
|
231
|
+
framework,
|
|
232
|
+
currentVersion,
|
|
233
|
+
targetVersion: selectedVersion.version,
|
|
234
|
+
isMajorUpgrade: selectedVersion.type === 'major',
|
|
319
235
|
});
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
236
|
+
console.log();
|
|
237
|
+
console.log(colors.success(`✓ Selected: ${frameworkName} ${selectedVersion.version}`));
|
|
238
|
+
// Validate version format
|
|
239
|
+
if (!/^\d+(\.\d+)?(\.\d+)?$/.test(selectedVersion.version)) {
|
|
240
|
+
printError('Invalid version format. Use major version (e.g., "19") or semver (e.g., "19.0.0")');
|
|
241
|
+
process.exit(1);
|
|
324
242
|
}
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
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;
|
|
243
|
+
return selectedVersion.version;
|
|
244
|
+
}
|
|
245
|
+
catch (error) {
|
|
246
|
+
spinner.fail('Failed to fetch versions');
|
|
247
|
+
throw error;
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
/**
|
|
251
|
+
* Build version options from API response groups
|
|
252
|
+
*/
|
|
253
|
+
function buildVersionOptions(groups, frameworkName, currentMajor) {
|
|
254
|
+
const versionOptions = [];
|
|
255
|
+
for (const group of groups) {
|
|
256
|
+
const latestInGroup = group.options[0];
|
|
257
|
+
if (latestInGroup) {
|
|
258
|
+
const major = parseInt(latestInGroup.value.split('.')[0], 10);
|
|
259
|
+
const isMajorUpgrade = major > currentMajor;
|
|
260
|
+
versionOptions.push({
|
|
261
|
+
version: latestInGroup.value,
|
|
262
|
+
major,
|
|
263
|
+
label: `${frameworkName} ${latestInGroup.value}` + (latestInGroup.badge ? ` (${latestInGroup.badge})` : ''),
|
|
264
|
+
type: isMajorUpgrade ? 'major' : 'minor',
|
|
265
|
+
isLatest: latestInGroup.badge === 'Latest',
|
|
383
266
|
});
|
|
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
267
|
}
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
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
|
-
}
|
|
268
|
+
}
|
|
269
|
+
return versionOptions;
|
|
270
|
+
}
|
|
271
|
+
/**
|
|
272
|
+
* Display version options grouped by type
|
|
273
|
+
*/
|
|
274
|
+
function displayVersionOptions(versionOptions) {
|
|
275
|
+
console.log();
|
|
276
|
+
console.log(colors.whiteBold('🎯 Select target version:'));
|
|
277
|
+
console.log();
|
|
278
|
+
const minorOptions = versionOptions.filter(v => v.type === 'minor');
|
|
279
|
+
const majorOptions = versionOptions.filter(v => v.type === 'major');
|
|
280
|
+
if (minorOptions.length > 0) {
|
|
281
|
+
console.log(colors.dim(' Minor Patches:'));
|
|
282
|
+
for (const opt of minorOptions) {
|
|
283
|
+
console.log(colors.brand(` ${opt.label}`));
|
|
443
284
|
}
|
|
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
285
|
console.log();
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
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;
|
|
286
|
+
}
|
|
287
|
+
if (majorOptions.length > 0) {
|
|
288
|
+
console.log(colors.dim(' Major Upgrades:'));
|
|
289
|
+
for (const opt of majorOptions) {
|
|
290
|
+
console.log(colors.action(` ${opt.label}`));
|
|
470
291
|
}
|
|
471
|
-
// Step 9: Apply fix (FREE) - smooth reveal
|
|
472
|
-
await sleep(150);
|
|
473
292
|
console.log();
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
//
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
enginesUpdated,
|
|
500
|
-
});
|
|
501
|
-
// Track: migration_applied
|
|
502
|
-
analytics.migrationApplied({
|
|
503
|
-
updatedCount: applied,
|
|
504
|
-
removedCount: removed,
|
|
505
|
-
enginesUpdated,
|
|
506
|
-
targetVersion,
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
// ============================================================================
|
|
296
|
+
// COST ESTIMATION
|
|
297
|
+
// ============================================================================
|
|
298
|
+
/**
|
|
299
|
+
* Get cost estimate from server
|
|
300
|
+
*/
|
|
301
|
+
async function getCostEstimate(ctx) {
|
|
302
|
+
const { apiClient, sanitized, framework, currentVersion } = ctx;
|
|
303
|
+
const costSpinner = createSpinner('Getting cost estimate...').start();
|
|
304
|
+
try {
|
|
305
|
+
const auditResponse = await apiClient.analyzeAudit(sanitized, framework);
|
|
306
|
+
if (!auditResponse.success || !auditResponse.data) {
|
|
307
|
+
costSpinner.fail('Failed to get cost estimate');
|
|
308
|
+
throw new Error(auditResponse.error || 'Could not get cost estimate');
|
|
309
|
+
}
|
|
310
|
+
const auditData = auditResponse.data;
|
|
311
|
+
costSpinner.succeed('Cost estimate ready');
|
|
312
|
+
// Set project context for analytics
|
|
313
|
+
analytics.setProjectContext({
|
|
314
|
+
packageCount: auditData.totalPackages,
|
|
315
|
+
framework,
|
|
316
|
+
frameworkVersion: currentVersion,
|
|
317
|
+
projectHash: analytics.hashProject(sanitized),
|
|
507
318
|
});
|
|
508
|
-
// Track:
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
319
|
+
// Track: project_detected
|
|
320
|
+
analytics.projectDetected({
|
|
321
|
+
packageCount: auditData.totalPackages,
|
|
322
|
+
framework,
|
|
323
|
+
currentVersion,
|
|
512
324
|
});
|
|
325
|
+
return {
|
|
326
|
+
cost: auditData.cost,
|
|
327
|
+
tierName: auditData.tierName,
|
|
328
|
+
packageCount: auditData.totalPackages,
|
|
329
|
+
healthScore: auditData.healthScore || 0,
|
|
330
|
+
auditData,
|
|
331
|
+
};
|
|
513
332
|
}
|
|
514
333
|
catch (error) {
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
334
|
+
costSpinner.fail('Failed to get cost estimate');
|
|
335
|
+
throw error;
|
|
336
|
+
}
|
|
337
|
+
}
|
|
338
|
+
// ============================================================================
|
|
339
|
+
// PROJECT OVERVIEW
|
|
340
|
+
// ============================================================================
|
|
341
|
+
/**
|
|
342
|
+
* Show project overview with migration plan
|
|
343
|
+
*/
|
|
344
|
+
async function showProjectOverview(ctx, costEstimate, targetVersion) {
|
|
345
|
+
const { frameworkName, currentVersion, currentMajor } = ctx;
|
|
346
|
+
const { packageCount, healthScore } = costEstimate;
|
|
347
|
+
const healthInfo = getHealthStatus(healthScore);
|
|
348
|
+
console.log();
|
|
349
|
+
await sleep(100);
|
|
350
|
+
console.log(colors.whiteBold('📊 PROJECT OVERVIEW'));
|
|
351
|
+
await sleep(80);
|
|
352
|
+
console.log(colors.gray('─'.repeat(50)));
|
|
353
|
+
await sleep(120);
|
|
354
|
+
console.log(`${colors.whiteBold('🏥 Health:')} ${renderHealthBar(healthScore)} ${healthInfo.color.bold(`${healthScore}/100`)} (${healthInfo.color(healthInfo.text)})`);
|
|
355
|
+
await sleep(100);
|
|
356
|
+
console.log(`${colors.whiteBold('📦 Packages:')} ${colors.brand(`${packageCount}`)} to migrate`);
|
|
357
|
+
await sleep(150);
|
|
358
|
+
console.log();
|
|
359
|
+
console.log(colors.whiteBold('🚀 MIGRATION PLAN:'));
|
|
360
|
+
await sleep(80);
|
|
361
|
+
console.log(colors.dim(` ${frameworkName} ${currentVersion || '?'} → ${targetVersion}`));
|
|
362
|
+
await sleep(60);
|
|
363
|
+
console.log(colors.dim(' All dependencies will be aligned to the target version.'));
|
|
364
|
+
// Migration highlight box
|
|
365
|
+
const targetMajor = parseInt(targetVersion.split('.')[0], 10);
|
|
366
|
+
const majorJump = targetMajor - currentMajor;
|
|
367
|
+
await displayMigrationHighlight(majorJump);
|
|
368
|
+
}
|
|
369
|
+
/**
|
|
370
|
+
* Display migration highlight box
|
|
371
|
+
*/
|
|
372
|
+
async function displayMigrationHighlight(majorJump) {
|
|
373
|
+
await sleep(150);
|
|
374
|
+
console.log();
|
|
375
|
+
console.log(colors.brandBold('┌─────────────────────────────────────────────────┐'));
|
|
376
|
+
await sleep(40);
|
|
377
|
+
console.log(colors.brandBold('│') + colors.whiteBold(' MIGRATION HIGHLIGHT ') + colors.brandBold('│'));
|
|
378
|
+
await sleep(40);
|
|
379
|
+
console.log(colors.brandBold('├─────────────────────────────────────────────────┤'));
|
|
380
|
+
await sleep(60);
|
|
381
|
+
if (majorJump > 1) {
|
|
382
|
+
console.log(colors.brandBold('│') + colors.warning(` ${majorJump} major versions jump - Full ecosystem update `.padEnd(49)) + colors.brandBold('│'));
|
|
383
|
+
}
|
|
384
|
+
else if (majorJump === 1) {
|
|
385
|
+
console.log(colors.brandBold('│') + colors.success(' Single major version upgrade ') + colors.brandBold('│'));
|
|
386
|
+
}
|
|
387
|
+
else {
|
|
388
|
+
console.log(colors.brandBold('│') + colors.success(' Minor/patch update - Low risk ') + colors.brandBold('│'));
|
|
389
|
+
}
|
|
390
|
+
await sleep(50);
|
|
391
|
+
console.log(colors.brandBold('│') + colors.dim(' - TypeScript alignment included ') + colors.brandBold('│'));
|
|
392
|
+
await sleep(50);
|
|
393
|
+
console.log(colors.brandBold('│') + colors.dim(' - Peer dependency conflicts auto-resolved ') + colors.brandBold('│'));
|
|
394
|
+
await sleep(50);
|
|
395
|
+
console.log(colors.brandBold('│') + colors.dim(' - Deprecated packages flagged for removal ') + colors.brandBold('│'));
|
|
396
|
+
await sleep(40);
|
|
397
|
+
console.log(colors.brandBold('└─────────────────────────────────────────────────┘'));
|
|
398
|
+
}
|
|
399
|
+
/**
|
|
400
|
+
* Handle migration payment flow
|
|
401
|
+
*/
|
|
402
|
+
async function handleMigrationPaymentFlow(ctx, costEstimate, paymentFlow, targetVersion) {
|
|
403
|
+
const { cost, tierName } = costEstimate;
|
|
404
|
+
const { options } = ctx;
|
|
405
|
+
// Check authentication
|
|
406
|
+
console.log();
|
|
407
|
+
const authResult = await paymentFlow.ensureAuthenticated();
|
|
408
|
+
if (!authResult.success) {
|
|
409
|
+
console.log();
|
|
410
|
+
printInfo('Run `npx depfixer migrate` when ready to continue.');
|
|
411
|
+
return { success: false, hasActivePass: false };
|
|
412
|
+
}
|
|
413
|
+
// Get balance info
|
|
414
|
+
const balanceInfo = await paymentFlow.getBalanceInfo();
|
|
415
|
+
// Show user details if already logged in
|
|
416
|
+
if (authResult.wasAlreadyLoggedIn && balanceInfo) {
|
|
417
|
+
printUserDetails({
|
|
418
|
+
name: balanceInfo.name,
|
|
419
|
+
email: balanceInfo.email,
|
|
420
|
+
credits: balanceInfo.credits,
|
|
421
|
+
hasActivePass: balanceInfo.hasActivePass,
|
|
422
|
+
showHeader: false,
|
|
519
423
|
});
|
|
520
|
-
printError(error.message);
|
|
521
|
-
process.exit(1);
|
|
522
424
|
}
|
|
425
|
+
console.log();
|
|
426
|
+
// Show cost and confirm payment
|
|
427
|
+
const hasPass = balanceInfo?.hasActivePass;
|
|
428
|
+
printCostBox({
|
|
429
|
+
cost,
|
|
430
|
+
tierName,
|
|
431
|
+
prompt: hasPass ? 'Execute migration? (Enter/Esc)' : 'Execute migration? (Enter/Esc)',
|
|
432
|
+
isMigration: true,
|
|
433
|
+
hasActivePass: hasPass,
|
|
434
|
+
});
|
|
435
|
+
// Track: migration_prompt_shown
|
|
436
|
+
analytics.migrationPromptShown({
|
|
437
|
+
creditsNeeded: cost,
|
|
438
|
+
creditsAvailable: balanceInfo?.credits || 0,
|
|
439
|
+
tier: tierName,
|
|
440
|
+
hasActivePass: hasPass,
|
|
441
|
+
targetVersion,
|
|
442
|
+
});
|
|
443
|
+
const shouldExecute = await confirmPrompt('', options);
|
|
444
|
+
if (!shouldExecute) {
|
|
445
|
+
analytics.migrationRejected({ reason: 'user_cancelled' });
|
|
446
|
+
console.log();
|
|
447
|
+
printInfo('Migration cancelled.');
|
|
448
|
+
return { success: false, hasActivePass: hasPass || false };
|
|
449
|
+
}
|
|
450
|
+
analytics.migrationAccepted({ creditsDeducted: hasPass ? 0 : cost });
|
|
451
|
+
// Check balance
|
|
452
|
+
const readyToPay = await paymentFlow.ensureSufficientBalance(cost);
|
|
453
|
+
if (!readyToPay) {
|
|
454
|
+
console.log();
|
|
455
|
+
printInfo('Run `npx depfixer migrate` when ready to continue.');
|
|
456
|
+
return { success: false, hasActivePass: hasPass || false };
|
|
457
|
+
}
|
|
458
|
+
return { success: true, hasActivePass: hasPass || false };
|
|
459
|
+
}
|
|
460
|
+
/**
|
|
461
|
+
* Run migration analysis
|
|
462
|
+
*/
|
|
463
|
+
async function runMigrationAnalysis(ctx, targetVersion, costEstimate) {
|
|
464
|
+
const { apiClient, sanitized, framework, frameworkName, currentVersion } = ctx;
|
|
465
|
+
console.log();
|
|
466
|
+
console.log(colors.whiteBold(`🔄 Analyzing Migration → ${frameworkName} ${targetVersion}`));
|
|
467
|
+
console.log(colors.gray('─'.repeat(50)));
|
|
468
|
+
const migrationSteps = [
|
|
469
|
+
'Analyzing current state...',
|
|
470
|
+
`Mapping migration path: ${currentVersion || '?'} → ${targetVersion}`,
|
|
471
|
+
'Checking ecosystem compatibility...',
|
|
472
|
+
'Calculating optimal versions...',
|
|
473
|
+
'Resolving dependency conflicts...',
|
|
474
|
+
'Building migration plan...',
|
|
475
|
+
];
|
|
476
|
+
let response;
|
|
477
|
+
await runStepSequence(migrationSteps, async () => {
|
|
478
|
+
response = await apiClient.analyzeMigrate(sanitized, targetVersion, framework);
|
|
479
|
+
if (!response.success || !response.data) {
|
|
480
|
+
throw new Error(response.error || 'Unknown error');
|
|
481
|
+
}
|
|
482
|
+
}, { successMessage: 'Migration plan ready', minStepDuration: 200 });
|
|
483
|
+
const data = response.data;
|
|
484
|
+
// Track: migration_plan_ready
|
|
485
|
+
analytics.migrationPlanReady({
|
|
486
|
+
analysisId: data.analysisId,
|
|
487
|
+
targetVersion,
|
|
488
|
+
conflictCount: (data.conflicts || []).length,
|
|
489
|
+
});
|
|
490
|
+
return {
|
|
491
|
+
analysisId: data.analysisId,
|
|
492
|
+
data,
|
|
493
|
+
};
|
|
494
|
+
}
|
|
495
|
+
// ============================================================================
|
|
496
|
+
// SESSION MANAGEMENT
|
|
497
|
+
// ============================================================================
|
|
498
|
+
/**
|
|
499
|
+
* Save migration session
|
|
500
|
+
*/
|
|
501
|
+
async function saveMigrationSession(ctx, analysisId, targetVersion, costEstimate) {
|
|
502
|
+
const { sessionManager, sanitized, packageJsonHash } = ctx;
|
|
503
|
+
const { cost, tierName, packageCount } = costEstimate;
|
|
504
|
+
await sessionManager.saveSession({
|
|
505
|
+
analysisId,
|
|
506
|
+
intent: 'MIGRATE',
|
|
507
|
+
args: { target: targetVersion },
|
|
508
|
+
originalFileHash: packageJsonHash,
|
|
509
|
+
cost,
|
|
510
|
+
status: 'PAID',
|
|
511
|
+
projectName: sanitized.name || 'unnamed',
|
|
512
|
+
packageCount,
|
|
513
|
+
tierName,
|
|
514
|
+
});
|
|
515
|
+
}
|
|
516
|
+
// ============================================================================
|
|
517
|
+
// MIGRATION PLAN DISPLAY
|
|
518
|
+
// ============================================================================
|
|
519
|
+
/**
|
|
520
|
+
* Map removals to expected format
|
|
521
|
+
*/
|
|
522
|
+
function mapRemovals(removals) {
|
|
523
|
+
return (removals || []).map(r => ({
|
|
524
|
+
package: r.package,
|
|
525
|
+
reason: r.reason || 'Deprecated',
|
|
526
|
+
type: r.type,
|
|
527
|
+
}));
|
|
523
528
|
}
|
|
529
|
+
/**
|
|
530
|
+
* Show full migration plan
|
|
531
|
+
*/
|
|
532
|
+
async function showMigrationPlan(ctx, costEstimate, targetVersion, migrationData, solution, changes, removals) {
|
|
533
|
+
const { frameworkName, currentVersion } = ctx;
|
|
534
|
+
const { healthScore } = costEstimate;
|
|
535
|
+
// Calculate projected health score
|
|
536
|
+
const projectedHealthScore = typeof migrationData.healthScore === 'object'
|
|
537
|
+
? (migrationData.healthScore.after || migrationData.healthScore.projected || 0)
|
|
538
|
+
: (typeof migrationData.healthScore === 'number' ? migrationData.healthScore : 0);
|
|
539
|
+
const breakingChanges = (migrationData.conflicts || []).filter((c) => c.severity === 'critical' || c.severity === 'high').length;
|
|
540
|
+
// Show migration plan header
|
|
541
|
+
await sleep(100);
|
|
542
|
+
printMigrationPlanHeader(frameworkName, currentVersion || '?', targetVersion);
|
|
543
|
+
// Show projection stats
|
|
544
|
+
await sleep(150);
|
|
545
|
+
printProjectionStats({
|
|
546
|
+
currentHealth: healthScore,
|
|
547
|
+
projectedHealth: projectedHealthScore,
|
|
548
|
+
packageCount: changes.length,
|
|
549
|
+
breakingChanges,
|
|
550
|
+
});
|
|
551
|
+
// Show package updates
|
|
552
|
+
if (changes.length > 0) {
|
|
553
|
+
await displayPackageUpdates(changes);
|
|
554
|
+
}
|
|
555
|
+
// Show engine requirements
|
|
556
|
+
await displayEngineRequirements(solution);
|
|
557
|
+
// Show packages to add
|
|
558
|
+
await displayPackagesToAdd(migrationData);
|
|
559
|
+
// Show packages to remove
|
|
560
|
+
await displayRemovals(removals);
|
|
561
|
+
}
|
|
562
|
+
/**
|
|
563
|
+
* Display package updates table
|
|
564
|
+
*/
|
|
565
|
+
async function displayPackageUpdates(changes) {
|
|
566
|
+
await sleep(150);
|
|
567
|
+
console.log();
|
|
568
|
+
console.log(colors.whiteBold('📦 Package Updates:'));
|
|
569
|
+
// Map and sort changes
|
|
570
|
+
const mappedChanges = changes.map((c) => ({
|
|
571
|
+
package: c.package,
|
|
572
|
+
currentVersion: c.from,
|
|
573
|
+
targetVersion: c.to,
|
|
574
|
+
changeType: getChangeType(c.from, c.to),
|
|
575
|
+
}));
|
|
576
|
+
const changeTypeOrder = { major: 0, minor: 1, patch: 2, none: 3 };
|
|
577
|
+
mappedChanges.sort((a, b) => {
|
|
578
|
+
const orderA = changeTypeOrder[a.changeType] ?? 3;
|
|
579
|
+
const orderB = changeTypeOrder[b.changeType] ?? 3;
|
|
580
|
+
return orderA - orderB;
|
|
581
|
+
});
|
|
582
|
+
const tableLines = createMigrationTable(mappedChanges).split('\n');
|
|
583
|
+
for (const line of tableLines) {
|
|
584
|
+
console.log(line);
|
|
585
|
+
await sleep(30);
|
|
586
|
+
}
|
|
587
|
+
}
|
|
588
|
+
/**
|
|
589
|
+
* Display engine requirements
|
|
590
|
+
*/
|
|
591
|
+
async function displayEngineRequirements(solution) {
|
|
592
|
+
if (!solution.engines || Object.keys(solution.engines).length === 0)
|
|
593
|
+
return;
|
|
594
|
+
await sleep(100);
|
|
595
|
+
console.log();
|
|
596
|
+
console.log(colors.whiteBold('⚙️ Engine Requirements:'));
|
|
597
|
+
if (solution.engines.node) {
|
|
598
|
+
console.log(` ${colors.dim('•')} Node.js: ${colors.brand(solution.engines.node)}`);
|
|
599
|
+
}
|
|
600
|
+
if (solution.engines.npm) {
|
|
601
|
+
console.log(` ${colors.dim('•')} npm: ${colors.brand(solution.engines.npm)}`);
|
|
602
|
+
}
|
|
603
|
+
}
|
|
604
|
+
/**
|
|
605
|
+
* Display packages to add
|
|
606
|
+
*/
|
|
607
|
+
async function displayPackagesToAdd(migrationData) {
|
|
608
|
+
const packagesToAdd = (migrationData.conflicts || []).filter((c) => !c.currentVersion || c.currentVersion.toLowerCase() === 'not installed');
|
|
609
|
+
if (packagesToAdd.length === 0)
|
|
610
|
+
return;
|
|
611
|
+
await sleep(100);
|
|
612
|
+
console.log();
|
|
613
|
+
console.log(colors.whiteBold('📦 Packages to Add:'));
|
|
614
|
+
for (const pkg of packagesToAdd) {
|
|
615
|
+
await sleep(60);
|
|
616
|
+
let message;
|
|
617
|
+
if (pkg.requiredBy && Array.isArray(pkg.requiredBy) && pkg.requiredBy.length > 0) {
|
|
618
|
+
const requiredRange = pkg.requiredRange || 'required version';
|
|
619
|
+
message = `Required as peer dependency by ${pkg.requiredBy.join(', ')} (${requiredRange})`;
|
|
620
|
+
}
|
|
621
|
+
else if (pkg.isPeerDependency && pkg.requiredRange) {
|
|
622
|
+
message = `Missing peer dependency (${pkg.requiredRange})`;
|
|
623
|
+
}
|
|
624
|
+
else {
|
|
625
|
+
message = pkg.description || 'Required dependency';
|
|
626
|
+
}
|
|
627
|
+
console.log(` ${colors.brand('+')} ${pkg.package}`);
|
|
628
|
+
console.log(` ${colors.dim(message)}`);
|
|
629
|
+
}
|
|
630
|
+
}
|
|
631
|
+
/**
|
|
632
|
+
* Display removals
|
|
633
|
+
*/
|
|
634
|
+
async function displayRemovals(removals) {
|
|
635
|
+
if (removals.length === 0)
|
|
636
|
+
return;
|
|
637
|
+
await sleep(100);
|
|
638
|
+
console.log();
|
|
639
|
+
console.log(colors.whiteBold('🗑 Packages to Remove:'));
|
|
640
|
+
const removalLines = createMigrationTable(removals.map(r => ({
|
|
641
|
+
package: r.package,
|
|
642
|
+
currentVersion: '*',
|
|
643
|
+
targetVersion: 'REMOVE',
|
|
644
|
+
changeType: 'deprec',
|
|
645
|
+
isRemoval: true,
|
|
646
|
+
}))).split('\n');
|
|
647
|
+
for (const line of removalLines) {
|
|
648
|
+
console.log(line);
|
|
649
|
+
await sleep(30);
|
|
650
|
+
}
|
|
651
|
+
}
|
|
652
|
+
// ============================================================================
|
|
653
|
+
// FIX APPLICATION
|
|
654
|
+
// ============================================================================
|
|
655
|
+
/**
|
|
656
|
+
* Apply migration fix to package.json
|
|
657
|
+
*/
|
|
658
|
+
async function applyMigrationFix(ctx, changes, removals, solution, targetVersion) {
|
|
659
|
+
const { projectDir, packageJsonService, options } = ctx;
|
|
660
|
+
// Show what will be changed
|
|
661
|
+
await sleep(200);
|
|
662
|
+
console.log();
|
|
663
|
+
console.log(colors.gray('────────────────────────────────────────'));
|
|
664
|
+
await sleep(50);
|
|
665
|
+
console.log(colors.gray(' 📝 Only ') + colors.white('package.json') + colors.gray(' will be modified'));
|
|
666
|
+
await sleep(50);
|
|
667
|
+
console.log(colors.gray(' 💾 A backup (') + colors.white('package.json.bak') + colors.gray(') will be created'));
|
|
668
|
+
await sleep(50);
|
|
669
|
+
console.log(colors.gray('────────────────────────────────────────'));
|
|
670
|
+
await sleep(100);
|
|
671
|
+
console.log();
|
|
672
|
+
// Track: migration_apply_prompt
|
|
673
|
+
analytics.migrationApplyPrompt({
|
|
674
|
+
changeCount: changes.length,
|
|
675
|
+
removalCount: removals.length,
|
|
676
|
+
});
|
|
677
|
+
console.log(colors.gray('[?] Apply changes now? (Enter/Esc)'));
|
|
678
|
+
const shouldApplyFix = await confirmPrompt('', options);
|
|
679
|
+
if (!shouldApplyFix) {
|
|
680
|
+
analytics.migrationDeferred({ reason: 'user_declined_apply' });
|
|
681
|
+
console.log();
|
|
682
|
+
printInfo('Solution saved. Run `npx depfixer fix` anytime to apply.');
|
|
683
|
+
return;
|
|
684
|
+
}
|
|
685
|
+
// Apply fix
|
|
686
|
+
await executeMigrationFix(projectDir, packageJsonService, changes, removals, solution, targetVersion);
|
|
687
|
+
}
|
|
688
|
+
/**
|
|
689
|
+
* Execute migration fix
|
|
690
|
+
*/
|
|
691
|
+
async function executeMigrationFix(projectDir, packageJsonService, changes, removals, solution, targetVersion) {
|
|
692
|
+
await sleep(150);
|
|
693
|
+
console.log();
|
|
694
|
+
console.log(colors.whiteBold(`🔧 Applying Migration...`));
|
|
695
|
+
await sleep(80);
|
|
696
|
+
console.log(colors.gray('─'.repeat(50)));
|
|
697
|
+
const upgradeCount = changes.length;
|
|
698
|
+
const removalCount = removals.length;
|
|
699
|
+
const migrationFixSteps = [
|
|
700
|
+
'Reading package.json...',
|
|
701
|
+
`Applying ${upgradeCount} upgrade${upgradeCount !== 1 ? 's' : ''}...`,
|
|
702
|
+
'Resolving peer conflicts...',
|
|
703
|
+
removalCount > 0 ? `Removing ${removalCount} deprecated package${removalCount !== 1 ? 's' : ''}...` : 'Checking for deprecated packages...',
|
|
704
|
+
'Validating final state...',
|
|
705
|
+
'Writing package.json...',
|
|
706
|
+
];
|
|
707
|
+
let applyResult;
|
|
708
|
+
await sleep(100);
|
|
709
|
+
await runStepSequence(migrationFixSteps, async () => {
|
|
710
|
+
applyResult = await packageJsonService.applySurgicalFixes(projectDir, changes, removals, solution.engines);
|
|
711
|
+
}, { successMessage: 'Migration complete', minStepDuration: 150 });
|
|
712
|
+
const { backupPath, applied, removed, enginesUpdated } = applyResult;
|
|
713
|
+
// Show success
|
|
714
|
+
await sleep(200);
|
|
715
|
+
printSuccessBox({
|
|
716
|
+
updated: applied,
|
|
717
|
+
removed,
|
|
718
|
+
backupPath,
|
|
719
|
+
enginesUpdated,
|
|
720
|
+
});
|
|
721
|
+
// Track: migration_applied
|
|
722
|
+
analytics.migrationApplied({
|
|
723
|
+
updatedCount: applied,
|
|
724
|
+
removedCount: removed,
|
|
725
|
+
enginesUpdated,
|
|
726
|
+
targetVersion,
|
|
727
|
+
});
|
|
728
|
+
// Track: session_ended
|
|
729
|
+
await analytics.sessionEnded({
|
|
730
|
+
outcome: 'migration_applied',
|
|
731
|
+
targetVersion,
|
|
732
|
+
});
|
|
733
|
+
}
|
|
734
|
+
// ============================================================================
|
|
735
|
+
// VERSION SELECTOR
|
|
736
|
+
// ============================================================================
|
|
524
737
|
/**
|
|
525
738
|
* Interactive version selector using arrow keys
|
|
526
739
|
*/
|
|
527
|
-
async function selectVersion(options, frameworkName) {
|
|
528
|
-
// Auto-select first option in dev mode
|
|
529
|
-
if (isAutoConfirmEnabled() && options.length > 0) {
|
|
740
|
+
async function selectVersion(options, frameworkName, migrateOptions) {
|
|
741
|
+
// Auto-select first option in dev mode or with --yes flag
|
|
742
|
+
if ((migrateOptions.yes || isAutoConfirmEnabled()) && options.length > 0) {
|
|
530
743
|
console.log(`${colors.dim(' Auto-selecting:')} ${colors.brand(options[0].label)} ${colors.dim('[auto]')}`);
|
|
531
744
|
return options[0];
|
|
532
745
|
}
|
|
@@ -601,240 +814,13 @@ async function selectVersion(options, frameworkName) {
|
|
|
601
814
|
process.stdin.on('data', onKeyPress);
|
|
602
815
|
});
|
|
603
816
|
}
|
|
604
|
-
|
|
605
|
-
|
|
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
|
-
}
|
|
817
|
+
// ============================================================================
|
|
818
|
+
// HELPER FUNCTIONS
|
|
819
|
+
// ============================================================================
|
|
833
820
|
/**
|
|
834
821
|
* Show project info when already on latest version
|
|
835
822
|
*/
|
|
836
823
|
async function showLatestVersionInfo(apiClient, sanitized, framework, frameworkName, currentVersion) {
|
|
837
|
-
// Get audit data for health info
|
|
838
824
|
try {
|
|
839
825
|
const auditResponse = await apiClient.analyzeAudit(sanitized, framework);
|
|
840
826
|
if (auditResponse.success && auditResponse.data) {
|
|
@@ -852,7 +838,6 @@ async function showLatestVersionInfo(apiClient, sanitized, framework, frameworkN
|
|
|
852
838
|
console.log();
|
|
853
839
|
console.log(colors.successBold('✓ You\'re already on the latest supported version!'));
|
|
854
840
|
console.log(colors.dim(' No migration needed.'));
|
|
855
|
-
// If there are issues, suggest running analyze
|
|
856
841
|
if (issueCount > 0) {
|
|
857
842
|
console.log();
|
|
858
843
|
console.log(colors.warningBold(`⚠️ ${issueCount} issue${issueCount !== 1 ? 's' : ''} detected in your dependencies`));
|
|
@@ -871,4 +856,15 @@ async function showLatestVersionInfo(apiClient, sanitized, framework, frameworkN
|
|
|
871
856
|
printSuccess(`You're already on the latest supported version of ${frameworkName}!`);
|
|
872
857
|
}
|
|
873
858
|
}
|
|
859
|
+
/**
|
|
860
|
+
* Handle migration error
|
|
861
|
+
*/
|
|
862
|
+
async function handleMigrationError(error) {
|
|
863
|
+
await analytics.sessionEnded({
|
|
864
|
+
outcome: 'error',
|
|
865
|
+
error: error.message,
|
|
866
|
+
});
|
|
867
|
+
printError(error.message);
|
|
868
|
+
process.exit(1);
|
|
869
|
+
}
|
|
874
870
|
//# sourceMappingURL=migrate.js.map
|