devcompass 2.5.0 → 2.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +400 -93
- package/data/known-malicious.json +57 -0
- package/package.json +13 -3
- package/src/alerts/github-tracker.js +53 -19
- package/src/alerts/predictive.js +10 -4
- package/src/analyzers/license-risk.js +225 -0
- package/src/analyzers/package-quality.js +368 -0
- package/src/analyzers/security-recommendations.js +274 -0
- package/src/analyzers/supply-chain.js +217 -0
- package/src/commands/analyze.js +466 -17
- package/src/utils/json-formatter.js +118 -28
package/src/commands/analyze.js
CHANGED
|
@@ -26,6 +26,17 @@ const { loadConfig, filterAlerts } = require('../config/loader');
|
|
|
26
26
|
const { getCached, setCache } = require('../cache/manager');
|
|
27
27
|
const { formatAsJson } = require('../utils/json-formatter');
|
|
28
28
|
const { handleCiMode } = require('../utils/ci-handler');
|
|
29
|
+
|
|
30
|
+
// NEW v2.7.0 imports
|
|
31
|
+
const { analyzeSupplyChain, getSupplyChainStats } = require('../analyzers/supply-chain');
|
|
32
|
+
const { analyzeLicenseRisks, getLicenseRiskScore } = require('../analyzers/license-risk');
|
|
33
|
+
const { analyzePackageQuality } = require('../analyzers/package-quality');
|
|
34
|
+
const {
|
|
35
|
+
generateSecurityRecommendations,
|
|
36
|
+
groupByPriority,
|
|
37
|
+
calculateExpectedImpact
|
|
38
|
+
} = require('../analyzers/security-recommendations');
|
|
39
|
+
|
|
29
40
|
const packageJson = require('../../package.json');
|
|
30
41
|
|
|
31
42
|
async function analyze(options) {
|
|
@@ -184,7 +195,7 @@ async function analyze(options) {
|
|
|
184
195
|
}
|
|
185
196
|
}
|
|
186
197
|
|
|
187
|
-
// Check for predictive warnings (GitHub Issues) -
|
|
198
|
+
// Check for predictive warnings (GitHub Issues) - v2.6.0
|
|
188
199
|
const { getTrackedPackageCount, TRACKED_REPOS } = require('../alerts/github-tracker');
|
|
189
200
|
const totalTracked = getTrackedPackageCount();
|
|
190
201
|
|
|
@@ -192,12 +203,14 @@ async function analyze(options) {
|
|
|
192
203
|
const installedTrackedCount = Object.keys(dependencies).filter(pkg => TRACKED_REPOS[pkg]).length;
|
|
193
204
|
|
|
194
205
|
if (installedTrackedCount > 0) {
|
|
195
|
-
spinner.text = `Checking GitHub activity (
|
|
206
|
+
spinner.text = `Checking GitHub activity (0/${installedTrackedCount} packages)...`;
|
|
196
207
|
} else {
|
|
197
208
|
spinner.text = 'Checking GitHub activity...';
|
|
198
209
|
}
|
|
199
210
|
|
|
200
211
|
let predictiveWarnings = [];
|
|
212
|
+
let githubCheckTime = 0;
|
|
213
|
+
let githubData = [];
|
|
201
214
|
|
|
202
215
|
if (config.cache) {
|
|
203
216
|
predictiveWarnings = getCached(projectPath, 'predictive');
|
|
@@ -206,7 +219,31 @@ async function analyze(options) {
|
|
|
206
219
|
if (!predictiveWarnings) {
|
|
207
220
|
try {
|
|
208
221
|
const { generatePredictiveWarnings } = require('../alerts/predictive');
|
|
209
|
-
|
|
222
|
+
const { checkGitHubIssues } = require('../alerts/github-tracker');
|
|
223
|
+
|
|
224
|
+
// Track performance
|
|
225
|
+
const startTime = Date.now();
|
|
226
|
+
|
|
227
|
+
// Get raw GitHub data for quality analysis
|
|
228
|
+
githubData = await checkGitHubIssues(dependencies, {
|
|
229
|
+
concurrency: 5,
|
|
230
|
+
onProgress: (current, total, packageName) => {
|
|
231
|
+
if (outputMode === 'normal') {
|
|
232
|
+
spinner.text = `Checking GitHub activity (${current}/${total} packages) - ${packageName}`;
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
});
|
|
236
|
+
|
|
237
|
+
// Generate predictive warnings
|
|
238
|
+
predictiveWarnings = await generatePredictiveWarnings(dependencies, {
|
|
239
|
+
onProgress: (current, total, packageName) => {
|
|
240
|
+
if (outputMode === 'normal') {
|
|
241
|
+
spinner.text = `Checking GitHub activity (${current}/${total} packages) - ${packageName}`;
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
});
|
|
245
|
+
|
|
246
|
+
githubCheckTime = Date.now() - startTime;
|
|
210
247
|
|
|
211
248
|
if (config.cache && predictiveWarnings.length > 0) {
|
|
212
249
|
setCache(projectPath, 'predictive', predictiveWarnings);
|
|
@@ -260,6 +297,75 @@ async function analyze(options) {
|
|
|
260
297
|
}
|
|
261
298
|
}
|
|
262
299
|
|
|
300
|
+
// NEW v2.7.0 - Supply Chain Analysis
|
|
301
|
+
spinner.text = 'Analyzing supply chain security...';
|
|
302
|
+
let supplyChainWarnings = [];
|
|
303
|
+
|
|
304
|
+
if (config.cache) {
|
|
305
|
+
supplyChainWarnings = getCached(projectPath, 'supplyChain');
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
if (!supplyChainWarnings || supplyChainWarnings.length === 0) {
|
|
309
|
+
try {
|
|
310
|
+
supplyChainWarnings = await analyzeSupplyChain(projectPath, dependencies);
|
|
311
|
+
if (config.cache) {
|
|
312
|
+
setCache(projectPath, 'supplyChain', supplyChainWarnings);
|
|
313
|
+
}
|
|
314
|
+
} catch (error) {
|
|
315
|
+
if (outputMode !== 'silent') {
|
|
316
|
+
console.log(chalk.yellow('\n⚠️ Could not analyze supply chain'));
|
|
317
|
+
console.log(chalk.gray(` Error: ${error.message}\n`));
|
|
318
|
+
}
|
|
319
|
+
supplyChainWarnings = [];
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
// NEW v2.7.0 - Enhanced License Risk Analysis
|
|
324
|
+
spinner.text = 'Analyzing license risks...';
|
|
325
|
+
let licenseRiskData = { warnings: [], stats: {}, projectLicense: 'MIT' };
|
|
326
|
+
|
|
327
|
+
if (config.cache) {
|
|
328
|
+
const cached = getCached(projectPath, 'licenseRisk');
|
|
329
|
+
if (cached) licenseRiskData = cached;
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
if (!licenseRiskData.warnings || licenseRiskData.warnings.length === 0) {
|
|
333
|
+
try {
|
|
334
|
+
licenseRiskData = await analyzeLicenseRisks(projectPath, licenses);
|
|
335
|
+
if (config.cache) {
|
|
336
|
+
setCache(projectPath, 'licenseRisk', licenseRiskData);
|
|
337
|
+
}
|
|
338
|
+
} catch (error) {
|
|
339
|
+
if (outputMode !== 'silent') {
|
|
340
|
+
console.log(chalk.yellow('\n⚠️ Could not analyze license risks'));
|
|
341
|
+
console.log(chalk.gray(` Error: ${error.message}\n`));
|
|
342
|
+
}
|
|
343
|
+
}
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
// NEW v2.7.0 - Package Quality Analysis
|
|
347
|
+
spinner.text = 'Analyzing package quality...';
|
|
348
|
+
let qualityData = { results: [], stats: {} };
|
|
349
|
+
|
|
350
|
+
if (config.cache) {
|
|
351
|
+
const cached = getCached(projectPath, 'quality');
|
|
352
|
+
if (cached) qualityData = cached;
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
if (!qualityData.results || qualityData.results.length === 0) {
|
|
356
|
+
try {
|
|
357
|
+
qualityData = await analyzePackageQuality(dependencies, githubData);
|
|
358
|
+
if (config.cache) {
|
|
359
|
+
setCache(projectPath, 'quality', qualityData);
|
|
360
|
+
}
|
|
361
|
+
} catch (error) {
|
|
362
|
+
if (outputMode !== 'silent') {
|
|
363
|
+
console.log(chalk.yellow('\n⚠️ Could not analyze package quality'));
|
|
364
|
+
console.log(chalk.gray(` Error: ${error.message}\n`));
|
|
365
|
+
}
|
|
366
|
+
}
|
|
367
|
+
}
|
|
368
|
+
|
|
263
369
|
// Calculate score
|
|
264
370
|
const alertPenalty = calculateAlertPenalty(alerts);
|
|
265
371
|
const securityPenalty = calculateSecurityPenalty(securityData.metadata);
|
|
@@ -275,17 +381,76 @@ async function analyze(options) {
|
|
|
275
381
|
|
|
276
382
|
spinner.succeed(chalk.green(`Scanned ${totalDeps} dependencies in project`));
|
|
277
383
|
|
|
384
|
+
// Show performance info if GitHub check was performed
|
|
385
|
+
if (githubCheckTime > 0 && outputMode === 'normal' && installedTrackedCount > 0) {
|
|
386
|
+
const timeInSeconds = (githubCheckTime / 1000).toFixed(2);
|
|
387
|
+
console.log(chalk.gray(`⚡ GitHub check completed in ${timeInSeconds}s (parallel processing)`));
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
// NEW v2.7.0 - Generate Security Recommendations
|
|
391
|
+
const recommendations = generateSecurityRecommendations({
|
|
392
|
+
supplyChainWarnings,
|
|
393
|
+
licenseWarnings: licenseRiskData.warnings,
|
|
394
|
+
qualityResults: qualityData.results,
|
|
395
|
+
securityVulnerabilities: securityData.metadata,
|
|
396
|
+
ecosystemAlerts: alerts,
|
|
397
|
+
unusedDeps,
|
|
398
|
+
outdatedPackages: outdatedDeps
|
|
399
|
+
});
|
|
400
|
+
|
|
278
401
|
// Handle different output modes
|
|
279
402
|
if (outputMode === 'json') {
|
|
280
|
-
const jsonOutput = formatAsJson(
|
|
403
|
+
const jsonOutput = formatAsJson(
|
|
404
|
+
alerts,
|
|
405
|
+
unusedDeps,
|
|
406
|
+
outdatedDeps,
|
|
407
|
+
score,
|
|
408
|
+
totalDeps,
|
|
409
|
+
securityData,
|
|
410
|
+
bundleSizes,
|
|
411
|
+
licenses,
|
|
412
|
+
predictiveWarnings,
|
|
413
|
+
supplyChainWarnings,
|
|
414
|
+
licenseRiskData,
|
|
415
|
+
qualityData,
|
|
416
|
+
recommendations
|
|
417
|
+
);
|
|
281
418
|
console.log(jsonOutput);
|
|
282
419
|
} else if (outputMode === 'ci') {
|
|
283
|
-
displayResults(
|
|
420
|
+
displayResults(
|
|
421
|
+
alerts,
|
|
422
|
+
unusedDeps,
|
|
423
|
+
outdatedDeps,
|
|
424
|
+
score,
|
|
425
|
+
totalDeps,
|
|
426
|
+
securityData,
|
|
427
|
+
bundleSizes,
|
|
428
|
+
licenses,
|
|
429
|
+
predictiveWarnings,
|
|
430
|
+
supplyChainWarnings,
|
|
431
|
+
licenseRiskData,
|
|
432
|
+
qualityData,
|
|
433
|
+
recommendations
|
|
434
|
+
);
|
|
284
435
|
handleCiMode(score, config, alerts, unusedDeps);
|
|
285
436
|
} else if (outputMode === 'silent') {
|
|
286
437
|
// Silent mode - no output
|
|
287
438
|
} else {
|
|
288
|
-
displayResults(
|
|
439
|
+
displayResults(
|
|
440
|
+
alerts,
|
|
441
|
+
unusedDeps,
|
|
442
|
+
outdatedDeps,
|
|
443
|
+
score,
|
|
444
|
+
totalDeps,
|
|
445
|
+
securityData,
|
|
446
|
+
bundleSizes,
|
|
447
|
+
licenses,
|
|
448
|
+
predictiveWarnings,
|
|
449
|
+
supplyChainWarnings,
|
|
450
|
+
licenseRiskData,
|
|
451
|
+
qualityData,
|
|
452
|
+
recommendations
|
|
453
|
+
);
|
|
289
454
|
}
|
|
290
455
|
|
|
291
456
|
} catch (error) {
|
|
@@ -298,7 +463,21 @@ async function analyze(options) {
|
|
|
298
463
|
}
|
|
299
464
|
}
|
|
300
465
|
|
|
301
|
-
function displayResults(
|
|
466
|
+
function displayResults(
|
|
467
|
+
alerts,
|
|
468
|
+
unusedDeps,
|
|
469
|
+
outdatedDeps,
|
|
470
|
+
score,
|
|
471
|
+
totalDeps,
|
|
472
|
+
securityData,
|
|
473
|
+
bundleSizes,
|
|
474
|
+
licenses,
|
|
475
|
+
predictiveWarnings,
|
|
476
|
+
supplyChainWarnings = [],
|
|
477
|
+
licenseRiskData = {},
|
|
478
|
+
qualityData = {},
|
|
479
|
+
recommendations = []
|
|
480
|
+
) {
|
|
302
481
|
logDivider();
|
|
303
482
|
|
|
304
483
|
// SECURITY VULNERABILITIES
|
|
@@ -331,6 +510,59 @@ function displayResults(alerts, unusedDeps, outdatedDeps, score, totalDeps, secu
|
|
|
331
510
|
|
|
332
511
|
logDivider();
|
|
333
512
|
|
|
513
|
+
// NEW v2.7.0 - SUPPLY CHAIN SECURITY
|
|
514
|
+
if (supplyChainWarnings.length > 0) {
|
|
515
|
+
const stats = getSupplyChainStats(supplyChainWarnings);
|
|
516
|
+
|
|
517
|
+
logSection('🛡️ SUPPLY CHAIN SECURITY', supplyChainWarnings.length);
|
|
518
|
+
|
|
519
|
+
// Group by type
|
|
520
|
+
const malicious = supplyChainWarnings.filter(w => w.type === 'malicious');
|
|
521
|
+
const typosquat = supplyChainWarnings.filter(w => w.type === 'typosquatting' || w.type === 'typosquatting_suspected');
|
|
522
|
+
const scripts = supplyChainWarnings.filter(w => w.type === 'install_script');
|
|
523
|
+
|
|
524
|
+
// Malicious packages (CRITICAL)
|
|
525
|
+
if (malicious.length > 0) {
|
|
526
|
+
log(chalk.red.bold('\n🔴 MALICIOUS PACKAGES DETECTED\n'));
|
|
527
|
+
malicious.forEach(w => {
|
|
528
|
+
log(` ${chalk.red.bold(w.package)}`);
|
|
529
|
+
log(` ${chalk.red(w.message)}`);
|
|
530
|
+
log(` ${chalk.yellow('→')} ${w.recommendation}\n`);
|
|
531
|
+
});
|
|
532
|
+
}
|
|
533
|
+
|
|
534
|
+
// Typosquatting (HIGH)
|
|
535
|
+
if (typosquat.length > 0) {
|
|
536
|
+
log(chalk.red('\n🟠 TYPOSQUATTING RISK\n'));
|
|
537
|
+
typosquat.forEach(w => {
|
|
538
|
+
const display = getSeverityDisplay(w.severity);
|
|
539
|
+
log(` ${display.emoji} ${chalk.bold(w.package)}`);
|
|
540
|
+
log(` Similar to: ${chalk.green(w.official)} (official package)`);
|
|
541
|
+
log(` ${chalk.yellow('→')} ${w.recommendation}\n`);
|
|
542
|
+
});
|
|
543
|
+
}
|
|
544
|
+
|
|
545
|
+
// Install scripts (MEDIUM/HIGH)
|
|
546
|
+
if (scripts.length > 0) {
|
|
547
|
+
log(chalk.yellow('\n🟡 INSTALL SCRIPT WARNINGS\n'));
|
|
548
|
+
scripts.slice(0, 3).forEach(w => {
|
|
549
|
+
log(` ${chalk.bold(w.package)}`);
|
|
550
|
+
log(` Script: ${chalk.gray(w.script)}`);
|
|
551
|
+
log(` Patterns: ${chalk.yellow(w.patterns.join(', '))}`);
|
|
552
|
+
log(` ${chalk.yellow('→')} ${w.recommendation}\n`);
|
|
553
|
+
});
|
|
554
|
+
|
|
555
|
+
if (scripts.length > 3) {
|
|
556
|
+
log(chalk.gray(` ... and ${scripts.length - 3} more install script warnings\n`));
|
|
557
|
+
}
|
|
558
|
+
}
|
|
559
|
+
} else {
|
|
560
|
+
logSection('✅ SUPPLY CHAIN SECURITY');
|
|
561
|
+
log(chalk.green(' No supply chain risks detected!\n'));
|
|
562
|
+
}
|
|
563
|
+
|
|
564
|
+
logDivider();
|
|
565
|
+
|
|
334
566
|
// ECOSYSTEM ALERTS
|
|
335
567
|
if (alerts.length > 0) {
|
|
336
568
|
logSection('🚨 ECOSYSTEM ALERTS', alerts.length);
|
|
@@ -371,7 +603,7 @@ function displayResults(alerts, unusedDeps, outdatedDeps, score, totalDeps, secu
|
|
|
371
603
|
|
|
372
604
|
logDivider();
|
|
373
605
|
|
|
374
|
-
// PREDICTIVE WARNINGS
|
|
606
|
+
// PREDICTIVE WARNINGS
|
|
375
607
|
if (predictiveWarnings.length > 0) {
|
|
376
608
|
const { getTrackedPackageCount } = require('../alerts/github-tracker');
|
|
377
609
|
const totalTracked = getTrackedPackageCount();
|
|
@@ -404,6 +636,124 @@ function displayResults(alerts, unusedDeps, outdatedDeps, score, totalDeps, secu
|
|
|
404
636
|
|
|
405
637
|
logDivider();
|
|
406
638
|
|
|
639
|
+
// NEW v2.7.0 - LICENSE RISK ANALYSIS
|
|
640
|
+
if (licenseRiskData.warnings && licenseRiskData.warnings.length > 0) {
|
|
641
|
+
logSection('⚖️ LICENSE RISK ANALYSIS', licenseRiskData.warnings.length);
|
|
642
|
+
|
|
643
|
+
const { warnings, projectLicense } = licenseRiskData;
|
|
644
|
+
|
|
645
|
+
log(chalk.gray(` Project License: ${projectLicense}\n`));
|
|
646
|
+
|
|
647
|
+
// Group by severity
|
|
648
|
+
const critical = warnings.filter(w => w.severity === 'critical');
|
|
649
|
+
const high = warnings.filter(w => w.severity === 'high');
|
|
650
|
+
const medium = warnings.filter(w => w.severity === 'medium');
|
|
651
|
+
|
|
652
|
+
if (critical.length > 0) {
|
|
653
|
+
log(chalk.red.bold('🔴 CRITICAL LICENSE RISKS\n'));
|
|
654
|
+
critical.forEach(w => {
|
|
655
|
+
log(` ${chalk.red.bold(w.package)}`);
|
|
656
|
+
log(` License: ${chalk.red(w.license)}`);
|
|
657
|
+
log(` ${chalk.yellow(w.message)}`);
|
|
658
|
+
log(` ${chalk.cyan('→')} ${w.recommendation}\n`);
|
|
659
|
+
});
|
|
660
|
+
}
|
|
661
|
+
|
|
662
|
+
if (high.length > 0) {
|
|
663
|
+
log(chalk.red('\n🟠 HIGH RISK LICENSES\n'));
|
|
664
|
+
high.slice(0, 3).forEach(w => {
|
|
665
|
+
log(` ${chalk.bold(w.package)}`);
|
|
666
|
+
log(` License: ${chalk.yellow(w.license)}`);
|
|
667
|
+
log(` ${chalk.gray(w.message)}`);
|
|
668
|
+
log(` ${chalk.cyan('→')} ${w.recommendation}\n`);
|
|
669
|
+
});
|
|
670
|
+
|
|
671
|
+
if (high.length > 3) {
|
|
672
|
+
log(chalk.gray(` ... and ${high.length - 3} more high-risk licenses\n`));
|
|
673
|
+
}
|
|
674
|
+
}
|
|
675
|
+
|
|
676
|
+
if (medium.length > 0) {
|
|
677
|
+
log(chalk.gray(`\n ${medium.length} medium-risk licenses detected\n`));
|
|
678
|
+
}
|
|
679
|
+
} else {
|
|
680
|
+
logSection('✅ LICENSE COMPLIANCE');
|
|
681
|
+
log(chalk.green(' All licenses are compliant!\n'));
|
|
682
|
+
}
|
|
683
|
+
|
|
684
|
+
logDivider();
|
|
685
|
+
|
|
686
|
+
// NEW v2.7.0 - PACKAGE QUALITY METRICS
|
|
687
|
+
if (qualityData.results && qualityData.results.length > 0) {
|
|
688
|
+
const { results, stats } = qualityData;
|
|
689
|
+
|
|
690
|
+
logSection('📊 PACKAGE QUALITY METRICS', results.length);
|
|
691
|
+
|
|
692
|
+
// Healthy packages
|
|
693
|
+
if (stats.healthy > 0) {
|
|
694
|
+
log(chalk.green(`\n✅ HEALTHY PACKAGES (${stats.healthy})\n`));
|
|
695
|
+
const healthy = results.filter(r => r.status === 'healthy');
|
|
696
|
+
const display = healthy.slice(0, 10).map(r => r.package).join(', ');
|
|
697
|
+
log(chalk.gray(` ${display}${healthy.length > 10 ? '...' : ''}\n`));
|
|
698
|
+
}
|
|
699
|
+
|
|
700
|
+
// Needs attention
|
|
701
|
+
if (stats.needsAttention > 0) {
|
|
702
|
+
log(chalk.yellow(`\n🟡 NEEDS ATTENTION (${stats.needsAttention})\n`));
|
|
703
|
+
const attention = results.filter(r => r.status === 'needs_attention');
|
|
704
|
+
attention.slice(0, 3).forEach(r => {
|
|
705
|
+
log(` ${chalk.bold(r.package)}`);
|
|
706
|
+
log(` Health Score: ${chalk.yellow(r.healthScore + '/10')}`);
|
|
707
|
+
log(` Last Update: ${chalk.gray(r.daysSincePublish)} days ago`);
|
|
708
|
+
if (r.githubMetrics) {
|
|
709
|
+
log(` Open Issues: ${chalk.gray(r.githubMetrics.totalIssues)}`);
|
|
710
|
+
}
|
|
711
|
+
log('');
|
|
712
|
+
});
|
|
713
|
+
}
|
|
714
|
+
|
|
715
|
+
// Stale packages
|
|
716
|
+
if (stats.stale > 0) {
|
|
717
|
+
log(chalk.red(`\n🟠 STALE PACKAGES (${stats.stale})\n`));
|
|
718
|
+
const stale = results.filter(r => r.status === 'stale');
|
|
719
|
+
stale.slice(0, 3).forEach(r => {
|
|
720
|
+
log(` ${chalk.bold(r.package)}`);
|
|
721
|
+
log(` Health Score: ${chalk.red(r.healthScore + '/10')}`);
|
|
722
|
+
log(` Last Update: ${chalk.red(Math.floor(r.daysSincePublish / 30))} months ago`);
|
|
723
|
+
log(` ${chalk.cyan('→')} Consider finding actively maintained alternative\n`);
|
|
724
|
+
});
|
|
725
|
+
}
|
|
726
|
+
|
|
727
|
+
// Abandoned packages
|
|
728
|
+
if (stats.abandoned > 0) {
|
|
729
|
+
log(chalk.red.bold(`\n🔴 ABANDONED PACKAGES (${stats.abandoned})\n`));
|
|
730
|
+
const abandoned = results.filter(r => r.status === 'abandoned');
|
|
731
|
+
abandoned.forEach(r => {
|
|
732
|
+
log(` ${chalk.red.bold(r.package)}`);
|
|
733
|
+
log(` Health Score: ${chalk.red(r.healthScore + '/10')}`);
|
|
734
|
+
log(` Last Update: ${chalk.red(Math.floor(r.daysSincePublish / 365))} years ago`);
|
|
735
|
+
log(` Maintainer: ${chalk.red('Inactive')}`);
|
|
736
|
+
log(` ${chalk.cyan('→')} Migrate to actively maintained alternative\n`);
|
|
737
|
+
});
|
|
738
|
+
}
|
|
739
|
+
|
|
740
|
+
// Deprecated packages
|
|
741
|
+
if (stats.deprecated > 0) {
|
|
742
|
+
log(chalk.red.bold(`\n🔴 DEPRECATED PACKAGES (${stats.deprecated})\n`));
|
|
743
|
+
const deprecated = results.filter(r => r.status === 'deprecated');
|
|
744
|
+
deprecated.forEach(r => {
|
|
745
|
+
log(` ${chalk.red.bold(r.package)}`);
|
|
746
|
+
log(` ${chalk.red('Package is officially deprecated')}`);
|
|
747
|
+
log(` ${chalk.cyan('→')} Find alternative immediately\n`);
|
|
748
|
+
});
|
|
749
|
+
}
|
|
750
|
+
} else {
|
|
751
|
+
logSection('📊 PACKAGE QUALITY');
|
|
752
|
+
log(chalk.green(' Quality analysis in progress...\n'));
|
|
753
|
+
}
|
|
754
|
+
|
|
755
|
+
logDivider();
|
|
756
|
+
|
|
407
757
|
// UNUSED DEPENDENCIES
|
|
408
758
|
if (unusedDeps.length > 0) {
|
|
409
759
|
logSection('🔴 UNUSED DEPENDENCIES', unusedDeps.length);
|
|
@@ -465,9 +815,9 @@ function displayResults(alerts, unusedDeps, outdatedDeps, score, totalDeps, secu
|
|
|
465
815
|
logDivider();
|
|
466
816
|
}
|
|
467
817
|
|
|
468
|
-
// LICENSE WARNINGS
|
|
818
|
+
// LICENSE WARNINGS (legacy - for packages not caught by license risk)
|
|
469
819
|
const problematicLicenses = findProblematicLicenses(licenses);
|
|
470
|
-
if (problematicLicenses.length > 0) {
|
|
820
|
+
if (problematicLicenses.length > 0 && (!licenseRiskData.warnings || licenseRiskData.warnings.length === 0)) {
|
|
471
821
|
logSection('⚖️ LICENSE WARNINGS', problematicLicenses.length);
|
|
472
822
|
|
|
473
823
|
problematicLicenses.forEach(pkg => {
|
|
@@ -478,13 +828,9 @@ function displayResults(alerts, unusedDeps, outdatedDeps, score, totalDeps, secu
|
|
|
478
828
|
});
|
|
479
829
|
|
|
480
830
|
log(chalk.gray('\n Note: Restrictive licenses may require legal review\n'));
|
|
481
|
-
|
|
482
|
-
logSection('✅ LICENSE COMPLIANCE');
|
|
483
|
-
log(chalk.green(' All licenses are permissive!\n'));
|
|
831
|
+
logDivider();
|
|
484
832
|
}
|
|
485
833
|
|
|
486
|
-
logDivider();
|
|
487
|
-
|
|
488
834
|
// PROJECT HEALTH
|
|
489
835
|
logSection('📊 PROJECT HEALTH');
|
|
490
836
|
|
|
@@ -496,6 +842,10 @@ function displayResults(alerts, unusedDeps, outdatedDeps, score, totalDeps, secu
|
|
|
496
842
|
log(` Security Vulnerabilities: ${chalk.red(securityData.metadata.total)}`);
|
|
497
843
|
}
|
|
498
844
|
|
|
845
|
+
if (supplyChainWarnings.length > 0) {
|
|
846
|
+
log(` Supply Chain Warnings: ${chalk.red(supplyChainWarnings.length)}`);
|
|
847
|
+
}
|
|
848
|
+
|
|
499
849
|
if (alerts.length > 0) {
|
|
500
850
|
log(` Ecosystem Alerts: ${chalk.red(alerts.length)}`);
|
|
501
851
|
}
|
|
@@ -504,15 +854,114 @@ function displayResults(alerts, unusedDeps, outdatedDeps, score, totalDeps, secu
|
|
|
504
854
|
log(` Predictive Warnings: ${chalk.yellow(predictiveWarnings.length)}`);
|
|
505
855
|
}
|
|
506
856
|
|
|
857
|
+
if (licenseRiskData.warnings && licenseRiskData.warnings.length > 0) {
|
|
858
|
+
log(` License Risks: ${chalk.yellow(licenseRiskData.warnings.length)}`);
|
|
859
|
+
}
|
|
860
|
+
|
|
861
|
+
if (qualityData.stats) {
|
|
862
|
+
const { abandoned, deprecated, stale } = qualityData.stats;
|
|
863
|
+
const total = (abandoned || 0) + (deprecated || 0) + (stale || 0);
|
|
864
|
+
if (total > 0) {
|
|
865
|
+
log(` Quality Issues: ${chalk.yellow(total)}`);
|
|
866
|
+
}
|
|
867
|
+
}
|
|
868
|
+
|
|
507
869
|
log(` Unused: ${chalk.red(unusedDeps.length)}`);
|
|
508
870
|
log(` Outdated: ${chalk.yellow(outdatedDeps.length)}\n`);
|
|
509
871
|
|
|
510
872
|
logDivider();
|
|
511
873
|
|
|
512
|
-
//
|
|
513
|
-
|
|
874
|
+
// NEW v2.7.0 - SECURITY RECOMMENDATIONS
|
|
875
|
+
if (recommendations.length > 0) {
|
|
876
|
+
displaySecurityRecommendations(recommendations, score.total);
|
|
877
|
+
} else {
|
|
878
|
+
displayQuickWins(alerts, unusedDeps, outdatedDeps, score, totalDeps, securityData);
|
|
879
|
+
}
|
|
880
|
+
}
|
|
881
|
+
|
|
882
|
+
// NEW v2.7.0 - Display Security Recommendations
|
|
883
|
+
function displaySecurityRecommendations(recommendations, currentScore) {
|
|
884
|
+
const grouped = groupByPriority(recommendations);
|
|
885
|
+
const impact = calculateExpectedImpact(recommendations, currentScore);
|
|
886
|
+
|
|
887
|
+
logSection('💡 SECURITY RECOMMENDATIONS (Prioritized)');
|
|
888
|
+
|
|
889
|
+
// CRITICAL
|
|
890
|
+
if (grouped.critical.length > 0) {
|
|
891
|
+
log(chalk.red.bold('\n🔴 CRITICAL (Fix Immediately)\n'));
|
|
892
|
+
grouped.critical.forEach((rec, index) => {
|
|
893
|
+
log(` ${index + 1}. ${chalk.bold(rec.issue)}`);
|
|
894
|
+
if (rec.package) {
|
|
895
|
+
log(` Package: ${chalk.red(rec.package)}`);
|
|
896
|
+
}
|
|
897
|
+
log(` ${chalk.cyan('Action:')} ${rec.action}`);
|
|
898
|
+
if (rec.command) {
|
|
899
|
+
log(chalk.gray(` $ ${rec.command}`));
|
|
900
|
+
}
|
|
901
|
+
if (rec.alternative) {
|
|
902
|
+
log(chalk.gray(` ${rec.alternative}`));
|
|
903
|
+
}
|
|
904
|
+
log('');
|
|
905
|
+
});
|
|
906
|
+
}
|
|
907
|
+
|
|
908
|
+
// HIGH
|
|
909
|
+
if (grouped.high.length > 0) {
|
|
910
|
+
log(chalk.red('\n🟠 HIGH (Fix Soon)\n'));
|
|
911
|
+
grouped.high.slice(0, 5).forEach((rec, index) => {
|
|
912
|
+
log(` ${index + 1}. ${rec.issue}`);
|
|
913
|
+
if (rec.package) {
|
|
914
|
+
log(` Package: ${chalk.yellow(rec.package)}`);
|
|
915
|
+
}
|
|
916
|
+
log(` ${chalk.cyan('Action:')} ${rec.action}`);
|
|
917
|
+
if (rec.command) {
|
|
918
|
+
log(chalk.gray(` $ ${rec.command}`));
|
|
919
|
+
}
|
|
920
|
+
log('');
|
|
921
|
+
});
|
|
922
|
+
|
|
923
|
+
if (grouped.high.length > 5) {
|
|
924
|
+
log(chalk.gray(` ... and ${grouped.high.length - 5} more high-priority items\n`));
|
|
925
|
+
}
|
|
926
|
+
}
|
|
927
|
+
|
|
928
|
+
// MEDIUM
|
|
929
|
+
if (grouped.medium.length > 0) {
|
|
930
|
+
log(chalk.yellow('\n🟡 MEDIUM (Plan to Fix)\n'));
|
|
931
|
+
grouped.medium.slice(0, 3).forEach((rec, index) => {
|
|
932
|
+
log(` ${index + 1}. ${rec.issue}`);
|
|
933
|
+
if (rec.package) {
|
|
934
|
+
log(` Package: ${chalk.gray(rec.package)}`);
|
|
935
|
+
}
|
|
936
|
+
log(` ${chalk.cyan('Action:')} ${rec.action}`);
|
|
937
|
+
log('');
|
|
938
|
+
});
|
|
939
|
+
|
|
940
|
+
if (grouped.medium.length > 3) {
|
|
941
|
+
log(chalk.gray(` ... and ${grouped.medium.length - 3} more medium-priority items\n`));
|
|
942
|
+
}
|
|
943
|
+
}
|
|
944
|
+
|
|
945
|
+
// Expected Impact
|
|
946
|
+
log(chalk.cyan.bold('\n📈 Expected Impact:\n'));
|
|
947
|
+
log(` ${chalk.green('✓')} Current Health Score: ${chalk.yellow(impact.currentScore + '/10')}`);
|
|
948
|
+
log(` ${chalk.green('✓')} Expected Score: ${chalk.green(impact.expectedScore + '/10')}`);
|
|
949
|
+
log(` ${chalk.green('✓')} Improvement: ${chalk.cyan('+' + impact.improvement)} points (${impact.percentageIncrease}% increase)`);
|
|
950
|
+
log(` ${chalk.green('✓')} Issues Resolved: ${impact.critical + impact.high + impact.medium} critical/high/medium`);
|
|
951
|
+
|
|
952
|
+
if (grouped.critical.length > 0) {
|
|
953
|
+
log(` ${chalk.green('✓')} Eliminate ${impact.critical} critical security risks`);
|
|
954
|
+
}
|
|
955
|
+
if (grouped.high.length > 0) {
|
|
956
|
+
log(` ${chalk.green('✓')} Resolve ${impact.high} high-priority issues`);
|
|
957
|
+
}
|
|
958
|
+
|
|
959
|
+
log(chalk.cyan('\n💡 TIP: Run') + chalk.bold(' devcompass fix ') + chalk.cyan('to apply automated fixes!\n'));
|
|
960
|
+
|
|
961
|
+
logDivider();
|
|
514
962
|
}
|
|
515
963
|
|
|
964
|
+
// Original Quick Wins (fallback)
|
|
516
965
|
function displayQuickWins(alerts, unusedDeps, outdatedDeps, score, totalDeps, securityData) {
|
|
517
966
|
const hasCriticalAlerts = alerts.some(a => a.severity === 'critical' || a.severity === 'high');
|
|
518
967
|
const hasCriticalSecurity = securityData.metadata.critical > 0 || securityData.metadata.high > 0;
|