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.
- package/dist/commands/audit.d.ts +19 -0
- package/dist/commands/audit.d.ts.map +1 -0
- package/dist/commands/audit.js +170 -0
- package/dist/commands/audit.js.map +1 -0
- package/dist/commands/fix.d.ts +10 -3
- package/dist/commands/fix.d.ts.map +1 -1
- package/dist/commands/fix.js +407 -41
- package/dist/commands/fix.js.map +1 -1
- package/dist/commands/graph.d.ts +1 -1
- package/dist/commands/graph.d.ts.map +1 -1
- package/dist/commands/graph.js +17 -12
- package/dist/commands/graph.js.map +1 -1
- package/dist/commands/login.d.ts.map +1 -1
- package/dist/commands/login.js +120 -19
- package/dist/commands/login.js.map +1 -1
- package/dist/commands/logout.d.ts.map +1 -1
- package/dist/commands/logout.js +6 -0
- package/dist/commands/logout.js.map +1 -1
- package/dist/commands/migrate.d.ts +22 -0
- package/dist/commands/migrate.d.ts.map +1 -0
- package/dist/commands/migrate.js +874 -0
- package/dist/commands/migrate.js.map +1 -0
- package/dist/commands/smart.d.ts +23 -0
- package/dist/commands/smart.d.ts.map +1 -0
- package/dist/commands/smart.js +1024 -0
- package/dist/commands/smart.js.map +1 -0
- package/dist/commands/whoami.d.ts +14 -0
- package/dist/commands/whoami.d.ts.map +1 -0
- package/dist/commands/whoami.js +65 -0
- package/dist/commands/whoami.js.map +1 -0
- package/dist/index.js +165 -40
- package/dist/index.js.map +1 -1
- package/dist/services/analytics.d.ts +108 -0
- package/dist/services/analytics.d.ts.map +1 -0
- package/dist/services/analytics.js +305 -0
- package/dist/services/analytics.js.map +1 -0
- package/dist/services/api-client.d.ts +159 -0
- package/dist/services/api-client.d.ts.map +1 -1
- package/dist/services/api-client.js +192 -10
- package/dist/services/api-client.js.map +1 -1
- package/dist/services/auth-manager.d.ts +14 -0
- package/dist/services/auth-manager.d.ts.map +1 -1
- package/dist/services/auth-manager.js +30 -0
- package/dist/services/auth-manager.js.map +1 -1
- package/dist/services/cache-manager.d.ts +62 -22
- package/dist/services/cache-manager.d.ts.map +1 -1
- package/dist/services/cache-manager.js +116 -30
- package/dist/services/cache-manager.js.map +1 -1
- package/dist/services/device-id.d.ts +20 -0
- package/dist/services/device-id.d.ts.map +1 -0
- package/dist/services/device-id.js +70 -0
- package/dist/services/device-id.js.map +1 -0
- package/dist/services/package-json.d.ts +44 -5
- package/dist/services/package-json.d.ts.map +1 -1
- package/dist/services/package-json.js +273 -20
- package/dist/services/package-json.js.map +1 -1
- package/dist/services/payment-flow.d.ts +108 -0
- package/dist/services/payment-flow.d.ts.map +1 -0
- package/dist/services/payment-flow.js +476 -0
- package/dist/services/payment-flow.js.map +1 -0
- package/dist/services/session-manager.d.ts +92 -0
- package/dist/services/session-manager.d.ts.map +1 -0
- package/dist/services/session-manager.js +218 -0
- package/dist/services/session-manager.js.map +1 -0
- package/dist/utils/design-system.d.ts +90 -0
- package/dist/utils/design-system.d.ts.map +1 -0
- package/dist/utils/design-system.js +338 -0
- package/dist/utils/design-system.js.map +1 -0
- package/dist/utils/output.d.ts +48 -1
- package/dist/utils/output.d.ts.map +1 -1
- package/dist/utils/output.js +180 -17
- package/dist/utils/output.js.map +1 -1
- package/dist/utils/print.d.ts +116 -0
- package/dist/utils/print.d.ts.map +1 -0
- package/dist/utils/print.js +190 -0
- package/dist/utils/print.js.map +1 -0
- package/package.json +13 -2
- package/dist/commands/analyze.d.ts +0 -18
- package/dist/commands/analyze.d.ts.map +0 -1
- package/dist/commands/analyze.js +0 -404
- package/dist/commands/analyze.js.map +0 -1
- package/dist/services/gitignore.d.ts +0 -19
- package/dist/services/gitignore.d.ts.map +0 -1
- package/dist/services/gitignore.js +0 -64
- 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
|