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,1024 @@
|
|
|
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 { AuthManager } from '../services/auth-manager.js';
|
|
7
|
+
import { analytics } from '../services/analytics.js';
|
|
8
|
+
import { getDeviceId } from '../services/device-id.js';
|
|
9
|
+
import { createSpinner, createMigrationTable, printError, printSuccess, printInfo, runStepSequence, sleep, } from '../utils/output.js';
|
|
10
|
+
import { colors, printCliHeader, renderHealthBar, getHealthStatus, printCostBox, printSuccessBox, printProjectHeader, printDiagnosis, printUserDetails, printCreditCheck, } from '../utils/design-system.js';
|
|
11
|
+
/**
|
|
12
|
+
* Smart command (DEFAULT)
|
|
13
|
+
*
|
|
14
|
+
* The main funnel:
|
|
15
|
+
* 1. Run audit analysis (FREE)
|
|
16
|
+
* 2. Show issues with cost
|
|
17
|
+
* 3. Prompt to pay and fix
|
|
18
|
+
* 4. If YES: auth → balance check → pay → apply fix
|
|
19
|
+
* 5. If NO: Save to session, user can run `fix` later
|
|
20
|
+
*
|
|
21
|
+
* Usage:
|
|
22
|
+
* npx depfixer
|
|
23
|
+
* npx depfixer --yes
|
|
24
|
+
*/
|
|
25
|
+
export async function smartCommand(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
|
+
const frameworkInfo = framework ? `${framework.charAt(0).toUpperCase() + framework.slice(1)}` : '';
|
|
39
|
+
// ================== CI MODE ==================
|
|
40
|
+
// CI mode uses dedicated endpoint with API key or JWT auth
|
|
41
|
+
// Returns complete analysis for pass/fail pipeline decisions
|
|
42
|
+
if (options.ci) {
|
|
43
|
+
const authManager = new AuthManager();
|
|
44
|
+
const authHeader = await authManager.getAuthHeader();
|
|
45
|
+
if (!authHeader) {
|
|
46
|
+
// No authentication available
|
|
47
|
+
if (options.json) {
|
|
48
|
+
console.log(JSON.stringify({
|
|
49
|
+
success: false,
|
|
50
|
+
error: 'Authentication required for CI mode',
|
|
51
|
+
help: 'Set DEPFIXER_TOKEN environment variable',
|
|
52
|
+
docs: 'https://depfixer.com/docs/ci-setup',
|
|
53
|
+
}, null, 2));
|
|
54
|
+
}
|
|
55
|
+
else {
|
|
56
|
+
console.log();
|
|
57
|
+
console.log(chalk.red(' Authentication required for CI mode'));
|
|
58
|
+
console.log();
|
|
59
|
+
console.log(chalk.bold(' Setup:'));
|
|
60
|
+
console.log(' 1. Get API token: https://app.depfixer.com/dashboard/api-keys');
|
|
61
|
+
console.log(' 2. Add to GitHub Secrets as DEPFIXER_TOKEN');
|
|
62
|
+
console.log(' 3. Use in workflow:');
|
|
63
|
+
console.log();
|
|
64
|
+
console.log(chalk.dim(' - run: npx depfixer --ci'));
|
|
65
|
+
console.log(chalk.dim(' env:'));
|
|
66
|
+
console.log(chalk.dim(' DEPFIXER_TOKEN: ${{ secrets.DEPFIXER_TOKEN }}'));
|
|
67
|
+
console.log();
|
|
68
|
+
}
|
|
69
|
+
process.exit(2);
|
|
70
|
+
}
|
|
71
|
+
// Call CI endpoint (full analysis with API key auth)
|
|
72
|
+
try {
|
|
73
|
+
const ciResponse = await apiClient.analyzeForCi(sanitized, framework);
|
|
74
|
+
if (!ciResponse.success || !ciResponse.data) {
|
|
75
|
+
if (options.json) {
|
|
76
|
+
console.log(JSON.stringify({ success: false, error: ciResponse.error || 'CI analysis failed' }, null, 2));
|
|
77
|
+
}
|
|
78
|
+
else {
|
|
79
|
+
console.log(chalk.red(` ${ciResponse.error || 'CI analysis failed'}`));
|
|
80
|
+
}
|
|
81
|
+
process.exit(2);
|
|
82
|
+
}
|
|
83
|
+
const ciData = ciResponse.data;
|
|
84
|
+
const issueCount = ciData.summary.critical + ciData.summary.high + ciData.summary.medium + ciData.summary.low;
|
|
85
|
+
// JSON output for CI pipelines (machine-readable)
|
|
86
|
+
if (options.json) {
|
|
87
|
+
console.log(JSON.stringify({
|
|
88
|
+
success: true,
|
|
89
|
+
mode: 'ci',
|
|
90
|
+
analysisId: ciData.analysisId,
|
|
91
|
+
healthScore: ciData.healthScore,
|
|
92
|
+
totalPackages: ciData.totalPackages,
|
|
93
|
+
summary: ciData.summary,
|
|
94
|
+
issueCount,
|
|
95
|
+
conflicts: ciData.conflicts,
|
|
96
|
+
framework: ciData.framework,
|
|
97
|
+
requiresAttention: ciData.requiresAttention,
|
|
98
|
+
}, null, 2));
|
|
99
|
+
process.exit(ciData.requiresAttention ? 1 : 0);
|
|
100
|
+
}
|
|
101
|
+
// Human-readable output for CI logs
|
|
102
|
+
console.log();
|
|
103
|
+
console.log(chalk.bold(' CI Mode - Dependency Analysis'));
|
|
104
|
+
console.log(chalk.dim(' ' + '─'.repeat(40)));
|
|
105
|
+
console.log(` Health Score: ${ciData.healthScore}/100`);
|
|
106
|
+
console.log(` Total Packages: ${ciData.totalPackages}`);
|
|
107
|
+
console.log(` Issues Found: ${issueCount}`);
|
|
108
|
+
if (ciData.summary.critical > 0)
|
|
109
|
+
console.log(chalk.red(` Critical: ${ciData.summary.critical}`));
|
|
110
|
+
if (ciData.summary.high > 0)
|
|
111
|
+
console.log(chalk.yellow(` High: ${ciData.summary.high}`));
|
|
112
|
+
if (ciData.summary.medium > 0)
|
|
113
|
+
console.log(chalk.blue(` Medium: ${ciData.summary.medium}`));
|
|
114
|
+
if (ciData.summary.low > 0)
|
|
115
|
+
console.log(chalk.dim(` Low: ${ciData.summary.low}`));
|
|
116
|
+
console.log();
|
|
117
|
+
if (ciData.requiresAttention) {
|
|
118
|
+
console.log(chalk.red(' Pipeline should fail - critical/high issues detected'));
|
|
119
|
+
process.exit(1);
|
|
120
|
+
}
|
|
121
|
+
else if (issueCount > 0) {
|
|
122
|
+
console.log(chalk.yellow(' Minor issues detected (medium/low severity)'));
|
|
123
|
+
process.exit(0);
|
|
124
|
+
}
|
|
125
|
+
else {
|
|
126
|
+
console.log(chalk.green(' No dependency issues found'));
|
|
127
|
+
process.exit(0);
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
catch (ciError) {
|
|
131
|
+
if (options.json) {
|
|
132
|
+
console.log(JSON.stringify({ success: false, error: ciError.message }, null, 2));
|
|
133
|
+
}
|
|
134
|
+
else {
|
|
135
|
+
console.log(chalk.red(` CI analysis failed: ${ciError.message}`));
|
|
136
|
+
}
|
|
137
|
+
process.exit(2);
|
|
138
|
+
}
|
|
139
|
+
return; // CI mode exits above, this is just for type safety
|
|
140
|
+
}
|
|
141
|
+
// ================== END CI MODE ==================
|
|
142
|
+
// Print CLI header (skip for JSON output)
|
|
143
|
+
if (!options.json) {
|
|
144
|
+
printCliHeader();
|
|
145
|
+
}
|
|
146
|
+
// Track: analyze_started
|
|
147
|
+
analytics.analyzeStarted({ command: 'smart' });
|
|
148
|
+
// Print project info (skip for JSON output)
|
|
149
|
+
if (!options.json) {
|
|
150
|
+
printProjectHeader(sanitized.name || 'unnamed', frameworkInfo);
|
|
151
|
+
}
|
|
152
|
+
// Calculate package.json hash for integrity check
|
|
153
|
+
const packageJsonHash = sessionManager.calculateHash(packageJsonContent);
|
|
154
|
+
// Analysis steps - enough to cover ~3-4 seconds before showing elapsed time
|
|
155
|
+
const analysisSteps = [
|
|
156
|
+
'Parsing dependency tree...',
|
|
157
|
+
`Detecting framework...${frameworkInfo ? ` ${frameworkInfo}` : ''}`,
|
|
158
|
+
'Loading compatibility matrix...',
|
|
159
|
+
'Scanning package versions...',
|
|
160
|
+
'Resolving peer dependencies...',
|
|
161
|
+
'Checking version constraints...',
|
|
162
|
+
'Analyzing transitive dependencies...',
|
|
163
|
+
'Detecting breaking changes...',
|
|
164
|
+
'Evaluating deprecation status...',
|
|
165
|
+
'Calculating version intersections...',
|
|
166
|
+
'Checking cross-package rules...',
|
|
167
|
+
'Generating recommendations...',
|
|
168
|
+
];
|
|
169
|
+
let response;
|
|
170
|
+
// First phase: Initial API call
|
|
171
|
+
if (options.json) {
|
|
172
|
+
// Silent mode for JSON output
|
|
173
|
+
response = await apiClient.analyzeAudit(sanitized, framework);
|
|
174
|
+
if (!response.success || !response.data) {
|
|
175
|
+
throw new Error(response.error || 'Unknown error');
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
else {
|
|
179
|
+
// Steps cycle through at 300ms each (~3.6s total), then show elapsed time for large projects
|
|
180
|
+
await runStepSequence(analysisSteps, async () => {
|
|
181
|
+
response = await apiClient.analyzeAudit(sanitized, framework);
|
|
182
|
+
if (!response.success || !response.data) {
|
|
183
|
+
throw new Error(response.error || 'Unknown error');
|
|
184
|
+
}
|
|
185
|
+
}, { successMessage: null, minStepDuration: 300 });
|
|
186
|
+
}
|
|
187
|
+
let data = response.data;
|
|
188
|
+
const { analysisId, prefetchId, hasPendingPackages } = data;
|
|
189
|
+
// Set project context for analytics
|
|
190
|
+
analytics.setProjectContext({
|
|
191
|
+
packageCount: data.totalPackages,
|
|
192
|
+
framework: data.framework?.name,
|
|
193
|
+
frameworkVersion: data.framework?.version,
|
|
194
|
+
projectHash: analytics.hashProject(sanitized),
|
|
195
|
+
});
|
|
196
|
+
// Track: project_detected
|
|
197
|
+
analytics.projectDetected({
|
|
198
|
+
packageCount: data.totalPackages,
|
|
199
|
+
framework: data.framework?.name,
|
|
200
|
+
});
|
|
201
|
+
// Audit mode: instant results from cache (no polling)
|
|
202
|
+
// Background re-analysis happens server-side automatically
|
|
203
|
+
// Polling will happen AFTER unlock when user is authenticated
|
|
204
|
+
if (!options.json) {
|
|
205
|
+
console.log(chalk.green(' ✓ Analysis complete'));
|
|
206
|
+
}
|
|
207
|
+
// Get cost from server response (database-driven tier pricing)
|
|
208
|
+
const cost = data.cost;
|
|
209
|
+
const tierName = data.tierName;
|
|
210
|
+
const packageCount = data.totalPackages;
|
|
211
|
+
// JSON output mode (no interactive prompts, clean output only)
|
|
212
|
+
// Note: CI mode with --json is handled earlier in the function
|
|
213
|
+
if (options.json) {
|
|
214
|
+
const issueCount = data.summary.critical + data.summary.high + data.summary.medium + data.summary.low;
|
|
215
|
+
const output = {
|
|
216
|
+
mode: 'audit',
|
|
217
|
+
analysisId,
|
|
218
|
+
healthScore: data.healthScore,
|
|
219
|
+
totalPackages: data.totalPackages,
|
|
220
|
+
summary: data.summary,
|
|
221
|
+
issueCount,
|
|
222
|
+
conflicts: data.conflicts,
|
|
223
|
+
framework: data.framework,
|
|
224
|
+
cost,
|
|
225
|
+
tierName,
|
|
226
|
+
hasCriticalIssues: data.summary.critical > 0,
|
|
227
|
+
hasHighIssues: data.summary.high > 0,
|
|
228
|
+
requiresAttention: data.summary.critical > 0 || data.summary.high > 0,
|
|
229
|
+
};
|
|
230
|
+
console.log(JSON.stringify(output, null, 2));
|
|
231
|
+
return;
|
|
232
|
+
}
|
|
233
|
+
// Pretty output - TEASER MODE (locked until payment)
|
|
234
|
+
const issueCount = data.summary.critical + data.summary.high + data.summary.medium + data.summary.low;
|
|
235
|
+
const healthInfo = getHealthStatus(data.healthScore);
|
|
236
|
+
// Smooth reveal of analysis report
|
|
237
|
+
console.log();
|
|
238
|
+
await sleep(100);
|
|
239
|
+
console.log(colors.whiteBold('📊 ANALYSIS REPORT'));
|
|
240
|
+
await sleep(80);
|
|
241
|
+
console.log(colors.gray('─'.repeat(50)));
|
|
242
|
+
await sleep(120);
|
|
243
|
+
console.log(`${colors.whiteBold('🏥 Health:')} ${renderHealthBar(data.healthScore)} ${healthInfo.color.bold(`${data.healthScore}/100`)} (${healthInfo.color(healthInfo.text)})`);
|
|
244
|
+
// Track: analysis_completed
|
|
245
|
+
analytics.analysisCompleted({
|
|
246
|
+
healthScore: data.healthScore,
|
|
247
|
+
issueCount,
|
|
248
|
+
criticalCount: data.summary.critical,
|
|
249
|
+
highCount: data.summary.high,
|
|
250
|
+
});
|
|
251
|
+
// Check if there are issues to fix FIRST
|
|
252
|
+
if (issueCount === 0) {
|
|
253
|
+
await sleep(100);
|
|
254
|
+
console.log();
|
|
255
|
+
printSuccess('No issues found! Your dependencies are healthy.');
|
|
256
|
+
// Check if migration is available (not on latest version)
|
|
257
|
+
if (data.framework?.name && data.framework?.version) {
|
|
258
|
+
try {
|
|
259
|
+
const currentMajor = parseInt(data.framework.version.split('.')[0], 10);
|
|
260
|
+
const versionsResponse = await apiClient.getFrameworkVersions(data.framework.name, currentMajor);
|
|
261
|
+
if (versionsResponse.success && versionsResponse.data) {
|
|
262
|
+
// Find the recommended/latest version
|
|
263
|
+
const recommended = versionsResponse.data.quickOptions?.find(opt => opt.isRecommended);
|
|
264
|
+
const latestMajor = recommended ? parseInt(recommended.value, 10) : null;
|
|
265
|
+
if (latestMajor && latestMajor > currentMajor) {
|
|
266
|
+
const versionsBehind = latestMajor - currentMajor;
|
|
267
|
+
console.log();
|
|
268
|
+
console.log(colors.whiteBold('💡 WHY NOT 100%?'));
|
|
269
|
+
console.log(colors.dim(` Your ${data.framework.name} version is `) + colors.warning(`${versionsBehind} major version${versionsBehind > 1 ? 's' : ''} behind`) + colors.dim(' the latest.'));
|
|
270
|
+
console.log(colors.dim(` This affects your health score even without dependency conflicts.`));
|
|
271
|
+
console.log();
|
|
272
|
+
console.log(colors.whiteBold(' 👉 UPGRADE:'));
|
|
273
|
+
console.log(` Run ${colors.brand('npx depfixer migrate')} to upgrade to ${data.framework.name} ${latestMajor}.`);
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
catch {
|
|
278
|
+
// Silently ignore - migration suggestion is optional
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
console.log();
|
|
282
|
+
return;
|
|
283
|
+
}
|
|
284
|
+
await sleep(100);
|
|
285
|
+
console.log(`${colors.whiteBold('⚠️ Issues:')} ${colors.dangerBold(`${issueCount}`)} Conflicts Found`);
|
|
286
|
+
await sleep(150);
|
|
287
|
+
console.log();
|
|
288
|
+
// Show LIMITED preview - protect small conflict counts from bypass
|
|
289
|
+
if (data.conflicts && data.conflicts.length > 0) {
|
|
290
|
+
// Filter out "not installed" packages from audit display
|
|
291
|
+
const installedConflicts = data.conflicts.filter((c) => c.currentVersion && c.currentVersion.toLowerCase() !== 'not installed');
|
|
292
|
+
const missingPackagesCount = data.conflicts.length - installedConflicts.length;
|
|
293
|
+
if (installedConflicts.length <= 3) {
|
|
294
|
+
// For small conflict counts, only show severity summary - no package names
|
|
295
|
+
// This prevents users from easily bypassing with AI
|
|
296
|
+
await sleep(80);
|
|
297
|
+
const W = 50; // Inner width
|
|
298
|
+
const row = (label, colorFn, count, desc) => {
|
|
299
|
+
const issueWord = count > 1 ? 'issues' : 'issue';
|
|
300
|
+
const content = ` ${label.padEnd(10)}${count} ${issueWord} ${desc}`;
|
|
301
|
+
console.log(colors.gray('│') + colorFn(content.padEnd(W)) + colors.gray('│'));
|
|
302
|
+
};
|
|
303
|
+
console.log(colors.gray('┌' + '─'.repeat(W) + '┐'));
|
|
304
|
+
console.log(colors.gray('│') + colors.whiteBold(' SEVERITY BREAKDOWN'.padEnd(W)) + colors.gray('│'));
|
|
305
|
+
console.log(colors.gray('├' + '─'.repeat(W) + '┤'));
|
|
306
|
+
if (data.summary.critical > 0)
|
|
307
|
+
row('CRITICAL', colors.dangerBold, data.summary.critical, 'require attention');
|
|
308
|
+
if (data.summary.high > 0)
|
|
309
|
+
row('HIGH', colors.danger, data.summary.high, 'with compatibility problems');
|
|
310
|
+
if (data.summary.medium > 0)
|
|
311
|
+
row('MEDIUM', colors.warning, data.summary.medium, 'with version conflicts');
|
|
312
|
+
if (data.summary.low > 0)
|
|
313
|
+
row('LOW', colors.dim, data.summary.low, 'to review');
|
|
314
|
+
console.log(colors.gray('└' + '─'.repeat(W) + '┘'));
|
|
315
|
+
await sleep(80);
|
|
316
|
+
// Show hint about missing packages if any
|
|
317
|
+
if (missingPackagesCount > 0) {
|
|
318
|
+
console.log(colors.dim(` + ${missingPackagesCount} missing peer ${missingPackagesCount > 1 ? 'dependencies' : 'dependency'} to install.`));
|
|
319
|
+
}
|
|
320
|
+
console.log(colors.dim(' Unlock to see details and recommended fixes.'));
|
|
321
|
+
}
|
|
322
|
+
else {
|
|
323
|
+
// For larger counts, show first 2 installed packages only
|
|
324
|
+
const tableLines = createTeaserTable(installedConflicts.slice(0, 2)).split('\n');
|
|
325
|
+
for (const line of tableLines) {
|
|
326
|
+
console.log(line);
|
|
327
|
+
await sleep(40);
|
|
328
|
+
}
|
|
329
|
+
await sleep(80);
|
|
330
|
+
const hiddenCount = installedConflicts.length - 2;
|
|
331
|
+
if (hiddenCount > 0) {
|
|
332
|
+
console.log(colors.dim(` + ${hiddenCount} other conflicts hidden.`));
|
|
333
|
+
}
|
|
334
|
+
// Show hint about missing packages if any
|
|
335
|
+
if (missingPackagesCount > 0) {
|
|
336
|
+
console.log(colors.dim(` + ${missingPackagesCount} missing peer ${missingPackagesCount > 1 ? 'dependencies' : 'dependency'} to install.`));
|
|
337
|
+
}
|
|
338
|
+
}
|
|
339
|
+
}
|
|
340
|
+
// Diagnosis section with smooth reveal
|
|
341
|
+
await sleep(150);
|
|
342
|
+
printDiagnosis(issueCount);
|
|
343
|
+
// Save session for potential later fix
|
|
344
|
+
await sessionManager.saveSession({
|
|
345
|
+
analysisId,
|
|
346
|
+
intent: 'ANALYZE',
|
|
347
|
+
originalFileHash: packageJsonHash,
|
|
348
|
+
cost,
|
|
349
|
+
status: 'UNPAID',
|
|
350
|
+
projectName: sanitized.name || 'unnamed',
|
|
351
|
+
packageCount,
|
|
352
|
+
tierName,
|
|
353
|
+
});
|
|
354
|
+
// Check if user is already logged in
|
|
355
|
+
const authManager = new AuthManager();
|
|
356
|
+
const isAlreadyLoggedIn = await authManager.isAuthenticated();
|
|
357
|
+
const paymentFlow = new PaymentFlowService();
|
|
358
|
+
// Track if user has active pass (set in both branches)
|
|
359
|
+
let userHasActivePass = false;
|
|
360
|
+
if (isAlreadyLoggedIn) {
|
|
361
|
+
// User is logged in - show account details and credit check
|
|
362
|
+
const balanceInfo = await paymentFlow.getBalanceInfo();
|
|
363
|
+
userHasActivePass = balanceInfo?.hasActivePass || false;
|
|
364
|
+
// Show user details
|
|
365
|
+
printUserDetails({
|
|
366
|
+
name: balanceInfo?.name,
|
|
367
|
+
email: balanceInfo?.email,
|
|
368
|
+
credits: balanceInfo?.credits || 0,
|
|
369
|
+
hasActivePass: userHasActivePass,
|
|
370
|
+
showHeader: false,
|
|
371
|
+
});
|
|
372
|
+
// Show credit check (needed vs available)
|
|
373
|
+
printCreditCheck({
|
|
374
|
+
needed: cost,
|
|
375
|
+
available: balanceInfo?.credits || 0,
|
|
376
|
+
hasActivePass: userHasActivePass,
|
|
377
|
+
});
|
|
378
|
+
// Cost box with prompt
|
|
379
|
+
printCostBox({
|
|
380
|
+
cost,
|
|
381
|
+
tierName,
|
|
382
|
+
prompt: userHasActivePass ? 'Continue? (Enter/Esc)' : `Deduct ${cost} credit${cost > 1 ? 's' : ''} to unlock? (Enter/Esc)`,
|
|
383
|
+
hasActivePass: userHasActivePass,
|
|
384
|
+
});
|
|
385
|
+
// Track: unlock_prompt_shown
|
|
386
|
+
analytics.unlockPromptShown({
|
|
387
|
+
creditsNeeded: cost,
|
|
388
|
+
creditsAvailable: balanceInfo?.credits || 0,
|
|
389
|
+
tier: tierName,
|
|
390
|
+
hasActivePass: userHasActivePass,
|
|
391
|
+
});
|
|
392
|
+
// Single prompt with cost info
|
|
393
|
+
const confirmUnlock = options.yes || await promptYesNo('' // Prompt already shown in cost box
|
|
394
|
+
);
|
|
395
|
+
if (!confirmUnlock) {
|
|
396
|
+
// Track: unlock_rejected
|
|
397
|
+
analytics.unlockRejected({ reason: 'user_cancelled' });
|
|
398
|
+
console.log();
|
|
399
|
+
printInfo('Solution saved. Run `npx depfixer fix` anytime to resume.');
|
|
400
|
+
return;
|
|
401
|
+
}
|
|
402
|
+
// Track: unlock_accepted
|
|
403
|
+
analytics.unlockAccepted({ creditsDeducted: userHasActivePass ? 0 : cost });
|
|
404
|
+
// Check balance is sufficient
|
|
405
|
+
if (!userHasActivePass && (balanceInfo?.credits || 0) < cost) {
|
|
406
|
+
// Track: unlock_failed (insufficient credits, but user will try to top up)
|
|
407
|
+
analytics.unlockFailed({
|
|
408
|
+
reason: 'insufficient_credits',
|
|
409
|
+
needed: cost,
|
|
410
|
+
available: balanceInfo?.credits || 0,
|
|
411
|
+
});
|
|
412
|
+
const hasBalance = await paymentFlow.ensureSufficientBalance(cost);
|
|
413
|
+
if (!hasBalance) {
|
|
414
|
+
console.log();
|
|
415
|
+
printInfo('Run `npx depfixer fix` when ready to continue.');
|
|
416
|
+
return;
|
|
417
|
+
}
|
|
418
|
+
// After top-up, show cost box again and confirm
|
|
419
|
+
printCostBox({
|
|
420
|
+
cost,
|
|
421
|
+
tierName,
|
|
422
|
+
prompt: `Deduct ${cost} credit${cost > 1 ? 's' : ''} to unlock? (Enter/Esc)`,
|
|
423
|
+
});
|
|
424
|
+
const confirmAfterTopUp = options.yes || await promptYesNo('');
|
|
425
|
+
if (!confirmAfterTopUp) {
|
|
426
|
+
console.log();
|
|
427
|
+
printInfo('Solution saved. Run `npx depfixer fix` anytime to resume.');
|
|
428
|
+
return;
|
|
429
|
+
}
|
|
430
|
+
}
|
|
431
|
+
}
|
|
432
|
+
else {
|
|
433
|
+
// User not logged in - show cost box and login prompt
|
|
434
|
+
printCostBox({
|
|
435
|
+
cost,
|
|
436
|
+
tierName,
|
|
437
|
+
prompt: '',
|
|
438
|
+
});
|
|
439
|
+
// Track: unlock_prompt_shown (anonymous user)
|
|
440
|
+
analytics.unlockPromptShown({
|
|
441
|
+
creditsNeeded: cost,
|
|
442
|
+
creditsAvailable: 0,
|
|
443
|
+
tier: tierName,
|
|
444
|
+
isAnonymous: true,
|
|
445
|
+
});
|
|
446
|
+
// Track: auth_required
|
|
447
|
+
analytics.authRequired({ creditsNeeded: cost });
|
|
448
|
+
console.log();
|
|
449
|
+
console.log(colors.warning('⚠️ Login required to unlock'));
|
|
450
|
+
console.log(colors.gray('[?] Continue to login? (Enter/Esc)'));
|
|
451
|
+
const wantsToUnlock = options.yes || await promptYesNo('');
|
|
452
|
+
if (!wantsToUnlock) {
|
|
453
|
+
// Track: auth_abandoned
|
|
454
|
+
analytics.authAbandoned({ reason: 'user_cancelled' });
|
|
455
|
+
console.log();
|
|
456
|
+
printInfo('Solution saved. Run `npx depfixer fix` anytime to resume.');
|
|
457
|
+
return;
|
|
458
|
+
}
|
|
459
|
+
// Run full auth + balance flow (shows user details after login)
|
|
460
|
+
const paymentResult = await paymentFlow.ensureReadyToPay(cost);
|
|
461
|
+
if (!paymentResult.ready) {
|
|
462
|
+
// Track: auth_abandoned (failed to complete auth/balance)
|
|
463
|
+
analytics.authAbandoned({ reason: 'auth_flow_incomplete' });
|
|
464
|
+
console.log();
|
|
465
|
+
printInfo('Run `npx depfixer fix` when ready to continue.');
|
|
466
|
+
return;
|
|
467
|
+
}
|
|
468
|
+
userHasActivePass = paymentResult.hasActivePass || false;
|
|
469
|
+
// Confirm after login (user details already shown by ensureReadyToPay)
|
|
470
|
+
printCostBox({
|
|
471
|
+
cost,
|
|
472
|
+
tierName,
|
|
473
|
+
prompt: userHasActivePass ? 'Continue? (Enter/Esc)' : `Confirm: Deduct ${cost} credit${cost > 1 ? 's' : ''}? (Enter/Esc)`,
|
|
474
|
+
hasActivePass: userHasActivePass,
|
|
475
|
+
});
|
|
476
|
+
const confirmUnlock = options.yes || await promptYesNo('');
|
|
477
|
+
if (!confirmUnlock) {
|
|
478
|
+
// Track: unlock_rejected (after login)
|
|
479
|
+
analytics.unlockRejected({ reason: 'user_cancelled_after_login' });
|
|
480
|
+
console.log();
|
|
481
|
+
printInfo('Solution saved. Run `npx depfixer fix` anytime to resume.');
|
|
482
|
+
return;
|
|
483
|
+
}
|
|
484
|
+
// Track: unlock_accepted (after login flow)
|
|
485
|
+
analytics.unlockAccepted({ creditsDeducted: userHasActivePass ? 0 : cost });
|
|
486
|
+
}
|
|
487
|
+
// Step 5: Deduct credits and get solution
|
|
488
|
+
const fixResult = await paymentFlow.deductCredits(analysisId, userHasActivePass);
|
|
489
|
+
if (!fixResult.success || !fixResult.solution) {
|
|
490
|
+
throw new Error(fixResult.error || 'Unknown error');
|
|
491
|
+
}
|
|
492
|
+
// Update session status
|
|
493
|
+
await sessionManager.updateStatus('PAID');
|
|
494
|
+
// If there were pending packages, poll until re-analysis completes
|
|
495
|
+
// User is now authenticated, so polling works
|
|
496
|
+
if (hasPendingPackages && prefetchId) {
|
|
497
|
+
const spinner = createSpinner('Finalizing complete analysis...').start();
|
|
498
|
+
let pollCount = 0;
|
|
499
|
+
const maxPolls = 120; // 2 minutes max
|
|
500
|
+
while (pollCount < maxPolls) {
|
|
501
|
+
try {
|
|
502
|
+
const status = await apiClient.pollPrefetchStatus(prefetchId);
|
|
503
|
+
if (status.isComplete && status.reanalysisStatus === 'completed') {
|
|
504
|
+
// Re-fetch complete data from DB
|
|
505
|
+
const updatedResponse = await apiClient.getAnalysisById(analysisId);
|
|
506
|
+
if (updatedResponse.success && updatedResponse.data) {
|
|
507
|
+
const updated = updatedResponse.data;
|
|
508
|
+
const analysisResult = updated.analysisResult || {};
|
|
509
|
+
// Update fixResult with fresh data from re-analysis
|
|
510
|
+
fixResult.conflicts = analysisResult.conflicts || fixResult.conflicts;
|
|
511
|
+
fixResult.missingDependencies = analysisResult.missingDependencies || fixResult.missingDependencies;
|
|
512
|
+
fixResult.deprecations = analysisResult.deprecations || fixResult.deprecations;
|
|
513
|
+
fixResult.healthScore = analysisResult.healthScore || fixResult.healthScore;
|
|
514
|
+
if (analysisResult.conflicts) {
|
|
515
|
+
fixResult.summary = calculateSummary(analysisResult.conflicts);
|
|
516
|
+
}
|
|
517
|
+
}
|
|
518
|
+
break;
|
|
519
|
+
}
|
|
520
|
+
// Update spinner with progress
|
|
521
|
+
if (status.percentage !== undefined) {
|
|
522
|
+
const percent = Math.round(status.percentage);
|
|
523
|
+
spinner.text = `Finalizing complete analysis... ${percent}%`;
|
|
524
|
+
}
|
|
525
|
+
await sleep(1000);
|
|
526
|
+
pollCount++;
|
|
527
|
+
}
|
|
528
|
+
catch (err) {
|
|
529
|
+
// Network error - retry
|
|
530
|
+
await sleep(2000);
|
|
531
|
+
pollCount++;
|
|
532
|
+
}
|
|
533
|
+
}
|
|
534
|
+
spinner.succeed('Complete analysis ready');
|
|
535
|
+
}
|
|
536
|
+
// Update data with full unfiltered results from DB
|
|
537
|
+
if (fixResult.conflicts) {
|
|
538
|
+
data = {
|
|
539
|
+
...data,
|
|
540
|
+
conflicts: fixResult.conflicts,
|
|
541
|
+
missingDependencies: fixResult.missingDependencies,
|
|
542
|
+
deprecations: fixResult.deprecations,
|
|
543
|
+
healthScore: fixResult.healthScore,
|
|
544
|
+
summary: fixResult.summary,
|
|
545
|
+
};
|
|
546
|
+
}
|
|
547
|
+
// Extract solution for type safety in closures
|
|
548
|
+
const solution = fixResult.solution;
|
|
549
|
+
// Step 6: Show FULL analysis with solutions (recommended versions) - smooth reveal
|
|
550
|
+
console.log();
|
|
551
|
+
await sleep(100);
|
|
552
|
+
console.log(chalk.bold.green('🔓 FULL ANALYSIS'));
|
|
553
|
+
await sleep(80);
|
|
554
|
+
console.log(chalk.dim('─'.repeat(50)));
|
|
555
|
+
await sleep(120);
|
|
556
|
+
console.log();
|
|
557
|
+
// Separate package conflicts from engine conflicts
|
|
558
|
+
const packageConflicts = data.conflicts.filter((c) => c.package !== 'Node.js' && c.package !== 'npm' && c.category !== 'engine');
|
|
559
|
+
const engineConflicts = data.conflicts.filter((c) => c.package === 'Node.js' || c.package === 'npm' || c.category === 'engine');
|
|
560
|
+
// Show full table with recommended versions - row by row (only if there are package conflicts)
|
|
561
|
+
if (packageConflicts.length > 0) {
|
|
562
|
+
const fullTableLines = createFullSolutionTable(packageConflicts, solution).split('\n');
|
|
563
|
+
for (const line of fullTableLines) {
|
|
564
|
+
console.log(line);
|
|
565
|
+
await sleep(35);
|
|
566
|
+
}
|
|
567
|
+
console.log();
|
|
568
|
+
}
|
|
569
|
+
// 1. Show engine requirements first (from solution.engines)
|
|
570
|
+
if (solution.engines && Object.keys(solution.engines).length > 0) {
|
|
571
|
+
console.log(colors.whiteBold('⚙️ Engine Requirements:'));
|
|
572
|
+
if (solution.engines.node) {
|
|
573
|
+
console.log(` ${colors.dim('•')} Node.js: ${colors.brand(solution.engines.node)}`);
|
|
574
|
+
}
|
|
575
|
+
if (solution.engines.npm) {
|
|
576
|
+
console.log(` ${colors.dim('•')} npm: ${colors.brand(solution.engines.npm)}`);
|
|
577
|
+
}
|
|
578
|
+
console.log();
|
|
579
|
+
}
|
|
580
|
+
else if (engineConflicts.length > 0) {
|
|
581
|
+
// Fallback to conflict data if solution.engines is empty
|
|
582
|
+
console.log(colors.whiteBold('⚙️ Engine Requirements:'));
|
|
583
|
+
for (const ec of engineConflicts) {
|
|
584
|
+
const engine = ec.package || 'Unknown';
|
|
585
|
+
let required = ec.recommendedVersion || ec.requiredVersion;
|
|
586
|
+
if (!required && ec.engineDetails?.requiredVersion) {
|
|
587
|
+
required = ec.engineDetails.requiredVersion.replace(/^>=/, '');
|
|
588
|
+
}
|
|
589
|
+
if (required) {
|
|
590
|
+
console.log(` ${colors.dim('•')} ${engine}: ${colors.brand('>=' + required)}`);
|
|
591
|
+
}
|
|
592
|
+
}
|
|
593
|
+
console.log();
|
|
594
|
+
}
|
|
595
|
+
// 2. Show packages to add (not installed)
|
|
596
|
+
const packagesToAdd = packageConflicts.filter((c) => !c.currentVersion || c.currentVersion.toLowerCase() === 'not installed');
|
|
597
|
+
if (packagesToAdd.length > 0) {
|
|
598
|
+
console.log(colors.whiteBold('📦 Packages to Add:'));
|
|
599
|
+
for (const pkg of packagesToAdd) {
|
|
600
|
+
await sleep(60);
|
|
601
|
+
// Build message from requiredBy if available, otherwise fallback
|
|
602
|
+
let message;
|
|
603
|
+
if (pkg.requiredBy && Array.isArray(pkg.requiredBy) && pkg.requiredBy.length > 0) {
|
|
604
|
+
const requiredRange = pkg.requiredRange || 'required version';
|
|
605
|
+
message = `Required as peer dependency by ${pkg.requiredBy.join(', ')} (${requiredRange})`;
|
|
606
|
+
}
|
|
607
|
+
else if (pkg.isPeerDependency && pkg.requiredRange) {
|
|
608
|
+
message = `Missing peer dependency (${pkg.requiredRange})`;
|
|
609
|
+
}
|
|
610
|
+
else {
|
|
611
|
+
message = pkg.description || 'Required dependency';
|
|
612
|
+
}
|
|
613
|
+
console.log(` ${colors.brand('+')} ${pkg.package}`);
|
|
614
|
+
console.log(` ${colors.dim(message)}`);
|
|
615
|
+
}
|
|
616
|
+
console.log();
|
|
617
|
+
}
|
|
618
|
+
// 3. Show removals
|
|
619
|
+
if (solution.removals && solution.removals.length > 0) {
|
|
620
|
+
await sleep(100);
|
|
621
|
+
console.log(colors.whiteBold('🗑 Packages to Remove:'));
|
|
622
|
+
const removalLines = createMigrationTable(solution.removals.map(r => ({
|
|
623
|
+
package: r.package,
|
|
624
|
+
currentVersion: '*',
|
|
625
|
+
targetVersion: 'REMOVE',
|
|
626
|
+
changeType: 'deprec',
|
|
627
|
+
isRemoval: true,
|
|
628
|
+
}))).split('\n');
|
|
629
|
+
for (const line of removalLines) {
|
|
630
|
+
console.log(line);
|
|
631
|
+
await sleep(30);
|
|
632
|
+
}
|
|
633
|
+
console.log();
|
|
634
|
+
}
|
|
635
|
+
// If no package conflicts and no removals, show a message
|
|
636
|
+
if (packageConflicts.length === 0 && (!solution.removals || solution.removals.length === 0)) {
|
|
637
|
+
console.log(colors.dim(' No package version changes needed.'));
|
|
638
|
+
console.log();
|
|
639
|
+
}
|
|
640
|
+
// Track: results_shown (full solution visible)
|
|
641
|
+
analytics.resultsShown({
|
|
642
|
+
conflictCount: data.conflicts.length,
|
|
643
|
+
removalsCount: solution.removals?.length || 0,
|
|
644
|
+
});
|
|
645
|
+
// Step 7: Ask to apply fix
|
|
646
|
+
// Explain what will be changed
|
|
647
|
+
await sleep(100);
|
|
648
|
+
console.log(colors.gray('────────────────────────────────────────'));
|
|
649
|
+
await sleep(50);
|
|
650
|
+
console.log(colors.gray(' 📝 Only ') + colors.white('package.json') + colors.gray(' will be modified'));
|
|
651
|
+
await sleep(50);
|
|
652
|
+
console.log(colors.gray(' 💾 A backup (') + colors.white('package.json.bak') + colors.gray(') will be created'));
|
|
653
|
+
await sleep(50);
|
|
654
|
+
console.log(colors.gray('────────────────────────────────────────'));
|
|
655
|
+
await sleep(100);
|
|
656
|
+
console.log();
|
|
657
|
+
// Track: fix_prompt_shown
|
|
658
|
+
analytics.fixPromptShown({
|
|
659
|
+
updatesCount: Object.keys(solution.dependencies || {}).length + Object.keys(solution.devDependencies || {}).length,
|
|
660
|
+
removalsCount: solution.removals?.length || 0,
|
|
661
|
+
});
|
|
662
|
+
const shouldApply = options.yes || await promptYesNo('Apply fix to package.json?');
|
|
663
|
+
if (!shouldApply) {
|
|
664
|
+
// Track: fix_deferred
|
|
665
|
+
analytics.fixDeferred({ reason: 'user_declined' });
|
|
666
|
+
console.log();
|
|
667
|
+
printInfo('Solution unlocked but not applied. Run `npx depfixer fix` to apply later.');
|
|
668
|
+
return;
|
|
669
|
+
}
|
|
670
|
+
// Track: fix_accepted
|
|
671
|
+
analytics.fixAccepted();
|
|
672
|
+
// Step 8: Apply the solution surgically (preserves formatting)
|
|
673
|
+
console.log();
|
|
674
|
+
console.log(chalk.bold('🔧 Applying fixes...'));
|
|
675
|
+
console.log();
|
|
676
|
+
// Get changes list for surgical update
|
|
677
|
+
const changes = packageJsonService.getChanges(parsed, solution);
|
|
678
|
+
// Map removals to the format expected by applySurgicalFixes
|
|
679
|
+
const removals = (solution.removals || []).map(r => ({
|
|
680
|
+
package: r.package,
|
|
681
|
+
reason: r.reason || 'Deprecated',
|
|
682
|
+
type: r.type,
|
|
683
|
+
}));
|
|
684
|
+
// Fix steps
|
|
685
|
+
const fixSteps = [
|
|
686
|
+
'Reading package.json...',
|
|
687
|
+
'Calculating safe versions...',
|
|
688
|
+
`Updating dependencies...`,
|
|
689
|
+
`Updating devDependencies...`,
|
|
690
|
+
'Validating changes...',
|
|
691
|
+
'Writing package.json...',
|
|
692
|
+
];
|
|
693
|
+
let fixResult2;
|
|
694
|
+
await runStepSequence(fixSteps, async () => {
|
|
695
|
+
fixResult2 = await packageJsonService.applySurgicalFixes(projectDir, changes, removals, solution.engines);
|
|
696
|
+
}, { successMessage: 'Fix complete', minStepDuration: 100 });
|
|
697
|
+
const { backupPath, applied, removed, enginesUpdated } = fixResult2;
|
|
698
|
+
// Show success
|
|
699
|
+
printSuccessBox({
|
|
700
|
+
updated: applied,
|
|
701
|
+
removed,
|
|
702
|
+
backupPath,
|
|
703
|
+
enginesUpdated,
|
|
704
|
+
});
|
|
705
|
+
// Track: fix_applied
|
|
706
|
+
analytics.fixApplied({
|
|
707
|
+
updatedCount: applied,
|
|
708
|
+
removedCount: removed,
|
|
709
|
+
enginesUpdated,
|
|
710
|
+
});
|
|
711
|
+
// Track: session_ended (successful completion)
|
|
712
|
+
await analytics.sessionEnded({
|
|
713
|
+
outcome: 'fix_applied',
|
|
714
|
+
healthScore: data.healthScore,
|
|
715
|
+
});
|
|
716
|
+
// Note: No exit(1) here - fixes were applied successfully
|
|
717
|
+
// CI mode already exits early before interactive prompts
|
|
718
|
+
}
|
|
719
|
+
catch (error) {
|
|
720
|
+
// Track: session_ended (error)
|
|
721
|
+
await analytics.sessionEnded({
|
|
722
|
+
outcome: 'error',
|
|
723
|
+
error: error.message,
|
|
724
|
+
});
|
|
725
|
+
if (options.json) {
|
|
726
|
+
console.log(JSON.stringify({ error: error.message }, null, 2));
|
|
727
|
+
}
|
|
728
|
+
else {
|
|
729
|
+
printError(error.message);
|
|
730
|
+
}
|
|
731
|
+
process.exit(options.ci ? 2 : 1);
|
|
732
|
+
}
|
|
733
|
+
}
|
|
734
|
+
/**
|
|
735
|
+
* Check if auto-confirm is enabled (dev mode only)
|
|
736
|
+
*/
|
|
737
|
+
function isAutoConfirmEnabled() {
|
|
738
|
+
const isDevMode = process.env.NODE_ENV === 'development' ||
|
|
739
|
+
(process.env.DEPFIXER_API_URL?.includes('localhost') ?? false);
|
|
740
|
+
return isDevMode && process.env.DEPFIXER_AUTO_CONFIRM === 'true';
|
|
741
|
+
}
|
|
742
|
+
/**
|
|
743
|
+
* Simple Enter/Esc prompt (Enter = Yes, Esc = No)
|
|
744
|
+
*/
|
|
745
|
+
async function promptYesNo(question) {
|
|
746
|
+
// Auto-confirm in dev mode when env var is set
|
|
747
|
+
if (isAutoConfirmEnabled()) {
|
|
748
|
+
if (question) {
|
|
749
|
+
console.log(`${question} ${colors.dim('(Enter/Esc)')} ${colors.success('Yes')} ${colors.dim('[auto]')}`);
|
|
750
|
+
}
|
|
751
|
+
else {
|
|
752
|
+
console.log(`${colors.success('Yes')} ${colors.dim('[auto]')}`);
|
|
753
|
+
}
|
|
754
|
+
return true;
|
|
755
|
+
}
|
|
756
|
+
return new Promise((resolve) => {
|
|
757
|
+
if (question) {
|
|
758
|
+
process.stdout.write(`${question} ${colors.dim('(Enter/Esc)')} `);
|
|
759
|
+
}
|
|
760
|
+
if (process.stdin.isTTY) {
|
|
761
|
+
process.stdin.setRawMode(true);
|
|
762
|
+
}
|
|
763
|
+
process.stdin.resume();
|
|
764
|
+
const onKeyPress = (key) => {
|
|
765
|
+
const char = key.toString();
|
|
766
|
+
// Enter key
|
|
767
|
+
if (char === '\r' || char === '\n') {
|
|
768
|
+
cleanup();
|
|
769
|
+
console.log(chalk.green('Yes'));
|
|
770
|
+
resolve(true);
|
|
771
|
+
}
|
|
772
|
+
// Escape key
|
|
773
|
+
else if (char === '\x1b') {
|
|
774
|
+
cleanup();
|
|
775
|
+
console.log(chalk.red('No'));
|
|
776
|
+
resolve(false);
|
|
777
|
+
}
|
|
778
|
+
// 'y' or 'Y'
|
|
779
|
+
else if (char.toLowerCase() === 'y') {
|
|
780
|
+
cleanup();
|
|
781
|
+
console.log(chalk.green('Yes'));
|
|
782
|
+
resolve(true);
|
|
783
|
+
}
|
|
784
|
+
// 'n' or 'N'
|
|
785
|
+
else if (char.toLowerCase() === 'n') {
|
|
786
|
+
cleanup();
|
|
787
|
+
console.log(chalk.red('No'));
|
|
788
|
+
resolve(false);
|
|
789
|
+
}
|
|
790
|
+
// Ctrl+C
|
|
791
|
+
else if (char === '\x03') {
|
|
792
|
+
cleanup();
|
|
793
|
+
process.exit(0);
|
|
794
|
+
}
|
|
795
|
+
};
|
|
796
|
+
const cleanup = () => {
|
|
797
|
+
process.stdin.removeListener('data', onKeyPress);
|
|
798
|
+
if (process.stdin.isTTY) {
|
|
799
|
+
process.stdin.setRawMode(false);
|
|
800
|
+
}
|
|
801
|
+
process.stdin.pause();
|
|
802
|
+
};
|
|
803
|
+
process.stdin.on('data', onKeyPress);
|
|
804
|
+
});
|
|
805
|
+
}
|
|
806
|
+
/**
|
|
807
|
+
* Apply fixes to package.json
|
|
808
|
+
*/
|
|
809
|
+
function applyFixes(original, solution) {
|
|
810
|
+
const updated = JSON.parse(JSON.stringify(original)); // Deep clone
|
|
811
|
+
// Update dependencies
|
|
812
|
+
if (solution.dependencies) {
|
|
813
|
+
for (const [pkg, version] of Object.entries(solution.dependencies)) {
|
|
814
|
+
if (updated.dependencies?.[pkg]) {
|
|
815
|
+
updated.dependencies[pkg] = version;
|
|
816
|
+
}
|
|
817
|
+
}
|
|
818
|
+
}
|
|
819
|
+
// Update devDependencies
|
|
820
|
+
if (solution.devDependencies) {
|
|
821
|
+
for (const [pkg, version] of Object.entries(solution.devDependencies)) {
|
|
822
|
+
if (updated.devDependencies?.[pkg]) {
|
|
823
|
+
updated.devDependencies[pkg] = version;
|
|
824
|
+
}
|
|
825
|
+
}
|
|
826
|
+
}
|
|
827
|
+
// Remove deprecated packages
|
|
828
|
+
if (solution.removals) {
|
|
829
|
+
for (const removal of solution.removals) {
|
|
830
|
+
if (removal.type === 'dependency') {
|
|
831
|
+
delete updated.dependencies?.[removal.package];
|
|
832
|
+
}
|
|
833
|
+
else {
|
|
834
|
+
delete updated.devDependencies?.[removal.package];
|
|
835
|
+
}
|
|
836
|
+
}
|
|
837
|
+
}
|
|
838
|
+
return updated;
|
|
839
|
+
}
|
|
840
|
+
/**
|
|
841
|
+
* Simple client-side framework detection
|
|
842
|
+
*/
|
|
843
|
+
function detectFramework(pkg) {
|
|
844
|
+
const deps = { ...pkg.dependencies, ...pkg.devDependencies };
|
|
845
|
+
if (deps['@angular/core'])
|
|
846
|
+
return 'angular';
|
|
847
|
+
if (deps['react'])
|
|
848
|
+
return 'react';
|
|
849
|
+
if (deps['vue'])
|
|
850
|
+
return 'vue';
|
|
851
|
+
if (deps['svelte'])
|
|
852
|
+
return 'svelte';
|
|
853
|
+
if (deps['next'])
|
|
854
|
+
return 'react';
|
|
855
|
+
if (deps['nuxt'])
|
|
856
|
+
return 'vue';
|
|
857
|
+
return undefined;
|
|
858
|
+
}
|
|
859
|
+
/**
|
|
860
|
+
* Calculate severity summary from conflicts array
|
|
861
|
+
*/
|
|
862
|
+
function calculateSummary(conflicts) {
|
|
863
|
+
return {
|
|
864
|
+
critical: conflicts.filter(c => c.severity === 'critical').length,
|
|
865
|
+
high: conflicts.filter(c => c.severity === 'high').length,
|
|
866
|
+
medium: conflicts.filter(c => c.severity === 'medium').length,
|
|
867
|
+
low: conflicts.filter(c => c.severity === 'low').length,
|
|
868
|
+
};
|
|
869
|
+
}
|
|
870
|
+
/**
|
|
871
|
+
* Wrap text to specified width
|
|
872
|
+
*/
|
|
873
|
+
function wrapText(text, maxWidth) {
|
|
874
|
+
const words = text.split(' ');
|
|
875
|
+
const lines = [];
|
|
876
|
+
let currentLine = '';
|
|
877
|
+
for (const word of words) {
|
|
878
|
+
if (currentLine.length + word.length + 1 <= maxWidth) {
|
|
879
|
+
currentLine += (currentLine ? ' ' : '') + word;
|
|
880
|
+
}
|
|
881
|
+
else {
|
|
882
|
+
if (currentLine) {
|
|
883
|
+
lines.push(currentLine);
|
|
884
|
+
}
|
|
885
|
+
currentLine = word;
|
|
886
|
+
}
|
|
887
|
+
}
|
|
888
|
+
if (currentLine) {
|
|
889
|
+
lines.push(currentLine);
|
|
890
|
+
}
|
|
891
|
+
return lines;
|
|
892
|
+
}
|
|
893
|
+
/**
|
|
894
|
+
* Create teaser table (limited info, no solutions)
|
|
895
|
+
* Shows only severity and package name with generic issue type
|
|
896
|
+
*/
|
|
897
|
+
function createTeaserTable(conflicts) {
|
|
898
|
+
// Column widths
|
|
899
|
+
const COL1 = 12; // SEVERITY
|
|
900
|
+
const COL2 = 30; // PACKAGE
|
|
901
|
+
const COL3 = 14; // ISSUE
|
|
902
|
+
const TOTAL_WIDTH = COL1 + COL2 + COL3;
|
|
903
|
+
const lines = [];
|
|
904
|
+
// Header row
|
|
905
|
+
lines.push(colors.whiteBold('SEVERITY'.padEnd(COL1)) +
|
|
906
|
+
colors.whiteBold('PACKAGE'.padEnd(COL2)) +
|
|
907
|
+
colors.whiteBold('ISSUE'.padEnd(COL3)));
|
|
908
|
+
// Single separator line
|
|
909
|
+
lines.push(colors.gray('─'.repeat(TOTAL_WIDTH)));
|
|
910
|
+
for (const conflict of conflicts) {
|
|
911
|
+
const severity = conflict.severity?.toUpperCase() || 'UNKNOWN';
|
|
912
|
+
const severityColor = severity === 'CRITICAL' ? colors.dangerBold :
|
|
913
|
+
severity === 'HIGH' ? colors.danger :
|
|
914
|
+
severity === 'MEDIUM' ? colors.warning :
|
|
915
|
+
colors.dim;
|
|
916
|
+
// Truncate package name if too long
|
|
917
|
+
const pkg = (conflict.package || '').substring(0, COL2 - 1).padEnd(COL2);
|
|
918
|
+
// Generic issue description (no details)
|
|
919
|
+
const issueType = severity === 'CRITICAL' ? 'Peer Clash' :
|
|
920
|
+
severity === 'HIGH' ? 'Version Gap' :
|
|
921
|
+
severity === 'MEDIUM' ? 'Conflict' : 'Minor';
|
|
922
|
+
lines.push(severityColor(severity.padEnd(COL1)) +
|
|
923
|
+
pkg +
|
|
924
|
+
issueType);
|
|
925
|
+
}
|
|
926
|
+
return lines.join('\n');
|
|
927
|
+
}
|
|
928
|
+
/**
|
|
929
|
+
* Create full solution table (shows recommended versions and removals)
|
|
930
|
+
* Clean design: simple headers with dash separator line
|
|
931
|
+
*/
|
|
932
|
+
function createFullSolutionTable(conflicts, solution) {
|
|
933
|
+
// Column widths
|
|
934
|
+
const COL1 = 35; // PACKAGE
|
|
935
|
+
const COL2 = 14; // CURRENT
|
|
936
|
+
const COL3 = 14; // TARGET
|
|
937
|
+
const COL4 = 10; // TYPE
|
|
938
|
+
const TOTAL_WIDTH = COL1 + COL2 + COL3 + COL4;
|
|
939
|
+
const lines = [];
|
|
940
|
+
// Header row
|
|
941
|
+
lines.push(colors.whiteBold('PACKAGE'.padEnd(COL1)) +
|
|
942
|
+
colors.whiteBold('CURRENT'.padEnd(COL2)) +
|
|
943
|
+
colors.whiteBold('TARGET'.padEnd(COL3)) +
|
|
944
|
+
colors.whiteBold('TYPE'.padEnd(COL4)));
|
|
945
|
+
// Single separator line
|
|
946
|
+
lines.push(colors.gray('─'.repeat(TOTAL_WIDTH)));
|
|
947
|
+
// Merge solution deps
|
|
948
|
+
const allSolutions = { ...solution.dependencies, ...solution.devDependencies };
|
|
949
|
+
// Separate conflicts into updates vs adds
|
|
950
|
+
const updates = [];
|
|
951
|
+
const adds = [];
|
|
952
|
+
for (const conflict of conflicts) {
|
|
953
|
+
const recommended = allSolutions[conflict.package] || conflict.recommendedVersion || '';
|
|
954
|
+
const currentRaw = conflict.currentVersion || '';
|
|
955
|
+
const isNotInstalled = !currentRaw || currentRaw.toLowerCase() === 'not installed';
|
|
956
|
+
if (isNotInstalled) {
|
|
957
|
+
// Package to add
|
|
958
|
+
adds.push({ conflict, recommended });
|
|
959
|
+
}
|
|
960
|
+
else if (recommended) {
|
|
961
|
+
// Package to update - determine change type
|
|
962
|
+
let changeType = 'patch';
|
|
963
|
+
const cleanCurrent = currentRaw.replace(/[~^]/g, '');
|
|
964
|
+
const cleanRec = recommended.replace(/[~^]/g, '');
|
|
965
|
+
const currMajor = parseInt(cleanCurrent.split('.')[0], 10);
|
|
966
|
+
const recMajor = parseInt(cleanRec.split('.')[0], 10);
|
|
967
|
+
if (!isNaN(currMajor) && !isNaN(recMajor)) {
|
|
968
|
+
if (recMajor > currMajor)
|
|
969
|
+
changeType = 'major';
|
|
970
|
+
else if (recMajor === currMajor) {
|
|
971
|
+
const currMinor = parseInt(cleanCurrent.split('.')[1] || '0', 10);
|
|
972
|
+
const recMinor = parseInt(cleanRec.split('.')[1] || '0', 10);
|
|
973
|
+
if (recMinor > currMinor)
|
|
974
|
+
changeType = 'minor';
|
|
975
|
+
}
|
|
976
|
+
}
|
|
977
|
+
updates.push({ conflict, recommended, changeType });
|
|
978
|
+
}
|
|
979
|
+
// Skip if no recommendation and already installed
|
|
980
|
+
}
|
|
981
|
+
// Render updates first
|
|
982
|
+
for (const { conflict, recommended, changeType } of updates) {
|
|
983
|
+
const pkg = (conflict.package || '').substring(0, COL1 - 1).padEnd(COL1);
|
|
984
|
+
let currentRaw = (conflict.currentVersion || '').replace(/[~^]/g, '').substring(0, COL2 - 1);
|
|
985
|
+
if (currentRaw.toLowerCase() === 'installed')
|
|
986
|
+
currentRaw = '—';
|
|
987
|
+
const currentPadded = currentRaw.padEnd(COL2);
|
|
988
|
+
const recommendedClean = recommended.replace(/[~^]/g, '').substring(0, COL3 - 1);
|
|
989
|
+
const recommendedPadded = recommendedClean.padEnd(COL3);
|
|
990
|
+
const typeLabel = changeType === 'major' ? 'Major' :
|
|
991
|
+
changeType === 'minor' ? 'Minor' : 'Patch';
|
|
992
|
+
const typeColor = changeType === 'major' ? colors.danger :
|
|
993
|
+
changeType === 'minor' ? colors.warning : colors.success;
|
|
994
|
+
lines.push(pkg +
|
|
995
|
+
colors.versionOld(currentPadded) +
|
|
996
|
+
colors.version(recommendedPadded) +
|
|
997
|
+
typeColor(typeLabel));
|
|
998
|
+
}
|
|
999
|
+
// Render adds
|
|
1000
|
+
for (const { conflict, recommended } of adds) {
|
|
1001
|
+
const pkg = (conflict.package || '').substring(0, COL1 - 1).padEnd(COL1);
|
|
1002
|
+
const currentPadded = '—'.padEnd(COL2);
|
|
1003
|
+
const recommendedClean = recommended ? recommended.replace(/[~^]/g, '').substring(0, COL3 - 1) : 'Add';
|
|
1004
|
+
const recommendedPadded = recommendedClean.padEnd(COL3);
|
|
1005
|
+
lines.push(pkg +
|
|
1006
|
+
colors.dim(currentPadded) +
|
|
1007
|
+
colors.version(recommendedPadded) +
|
|
1008
|
+
colors.brand('+ Add'));
|
|
1009
|
+
}
|
|
1010
|
+
// Render removals
|
|
1011
|
+
if (solution.removals && solution.removals.length > 0) {
|
|
1012
|
+
for (const removal of solution.removals) {
|
|
1013
|
+
const pkg = (removal.package || '').substring(0, COL1 - 1).padEnd(COL1);
|
|
1014
|
+
const currentPadded = 'installed'.padEnd(COL2);
|
|
1015
|
+
const targetPadded = '—'.padEnd(COL3);
|
|
1016
|
+
lines.push(pkg +
|
|
1017
|
+
colors.versionOld(currentPadded) +
|
|
1018
|
+
colors.dim(targetPadded) +
|
|
1019
|
+
colors.danger('Remove'));
|
|
1020
|
+
}
|
|
1021
|
+
}
|
|
1022
|
+
return lines.join('\n');
|
|
1023
|
+
}
|
|
1024
|
+
//# sourceMappingURL=smart.js.map
|