devcompass 2.6.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 +389 -96
- package/data/known-malicious.json +57 -0
- package/package.json +11 -3
- 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 +447 -18
- 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
|
|
|
@@ -199,6 +210,7 @@ async function analyze(options) {
|
|
|
199
210
|
|
|
200
211
|
let predictiveWarnings = [];
|
|
201
212
|
let githubCheckTime = 0;
|
|
213
|
+
let githubData = [];
|
|
202
214
|
|
|
203
215
|
if (config.cache) {
|
|
204
216
|
predictiveWarnings = getCached(projectPath, 'predictive');
|
|
@@ -207,11 +219,22 @@ async function analyze(options) {
|
|
|
207
219
|
if (!predictiveWarnings) {
|
|
208
220
|
try {
|
|
209
221
|
const { generatePredictiveWarnings } = require('../alerts/predictive');
|
|
222
|
+
const { checkGitHubIssues } = require('../alerts/github-tracker');
|
|
210
223
|
|
|
211
|
-
// Track performance
|
|
224
|
+
// Track performance
|
|
212
225
|
const startTime = Date.now();
|
|
213
226
|
|
|
214
|
-
//
|
|
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
|
|
215
238
|
predictiveWarnings = await generatePredictiveWarnings(dependencies, {
|
|
216
239
|
onProgress: (current, total, packageName) => {
|
|
217
240
|
if (outputMode === 'normal') {
|
|
@@ -274,6 +297,75 @@ async function analyze(options) {
|
|
|
274
297
|
}
|
|
275
298
|
}
|
|
276
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
|
+
|
|
277
369
|
// Calculate score
|
|
278
370
|
const alertPenalty = calculateAlertPenalty(alerts);
|
|
279
371
|
const securityPenalty = calculateSecurityPenalty(securityData.metadata);
|
|
@@ -289,23 +381,76 @@ async function analyze(options) {
|
|
|
289
381
|
|
|
290
382
|
spinner.succeed(chalk.green(`Scanned ${totalDeps} dependencies in project`));
|
|
291
383
|
|
|
292
|
-
// Show performance info if GitHub check was performed
|
|
384
|
+
// Show performance info if GitHub check was performed
|
|
293
385
|
if (githubCheckTime > 0 && outputMode === 'normal' && installedTrackedCount > 0) {
|
|
294
386
|
const timeInSeconds = (githubCheckTime / 1000).toFixed(2);
|
|
295
387
|
console.log(chalk.gray(`⚡ GitHub check completed in ${timeInSeconds}s (parallel processing)`));
|
|
296
388
|
}
|
|
297
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
|
+
|
|
298
401
|
// Handle different output modes
|
|
299
402
|
if (outputMode === 'json') {
|
|
300
|
-
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
|
+
);
|
|
301
418
|
console.log(jsonOutput);
|
|
302
419
|
} else if (outputMode === 'ci') {
|
|
303
|
-
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
|
+
);
|
|
304
435
|
handleCiMode(score, config, alerts, unusedDeps);
|
|
305
436
|
} else if (outputMode === 'silent') {
|
|
306
437
|
// Silent mode - no output
|
|
307
438
|
} else {
|
|
308
|
-
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
|
+
);
|
|
309
454
|
}
|
|
310
455
|
|
|
311
456
|
} catch (error) {
|
|
@@ -318,7 +463,21 @@ async function analyze(options) {
|
|
|
318
463
|
}
|
|
319
464
|
}
|
|
320
465
|
|
|
321
|
-
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
|
+
) {
|
|
322
481
|
logDivider();
|
|
323
482
|
|
|
324
483
|
// SECURITY VULNERABILITIES
|
|
@@ -351,6 +510,59 @@ function displayResults(alerts, unusedDeps, outdatedDeps, score, totalDeps, secu
|
|
|
351
510
|
|
|
352
511
|
logDivider();
|
|
353
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
|
+
|
|
354
566
|
// ECOSYSTEM ALERTS
|
|
355
567
|
if (alerts.length > 0) {
|
|
356
568
|
logSection('🚨 ECOSYSTEM ALERTS', alerts.length);
|
|
@@ -391,7 +603,7 @@ function displayResults(alerts, unusedDeps, outdatedDeps, score, totalDeps, secu
|
|
|
391
603
|
|
|
392
604
|
logDivider();
|
|
393
605
|
|
|
394
|
-
// PREDICTIVE WARNINGS
|
|
606
|
+
// PREDICTIVE WARNINGS
|
|
395
607
|
if (predictiveWarnings.length > 0) {
|
|
396
608
|
const { getTrackedPackageCount } = require('../alerts/github-tracker');
|
|
397
609
|
const totalTracked = getTrackedPackageCount();
|
|
@@ -424,6 +636,124 @@ function displayResults(alerts, unusedDeps, outdatedDeps, score, totalDeps, secu
|
|
|
424
636
|
|
|
425
637
|
logDivider();
|
|
426
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
|
+
|
|
427
757
|
// UNUSED DEPENDENCIES
|
|
428
758
|
if (unusedDeps.length > 0) {
|
|
429
759
|
logSection('🔴 UNUSED DEPENDENCIES', unusedDeps.length);
|
|
@@ -485,9 +815,9 @@ function displayResults(alerts, unusedDeps, outdatedDeps, score, totalDeps, secu
|
|
|
485
815
|
logDivider();
|
|
486
816
|
}
|
|
487
817
|
|
|
488
|
-
// LICENSE WARNINGS
|
|
818
|
+
// LICENSE WARNINGS (legacy - for packages not caught by license risk)
|
|
489
819
|
const problematicLicenses = findProblematicLicenses(licenses);
|
|
490
|
-
if (problematicLicenses.length > 0) {
|
|
820
|
+
if (problematicLicenses.length > 0 && (!licenseRiskData.warnings || licenseRiskData.warnings.length === 0)) {
|
|
491
821
|
logSection('⚖️ LICENSE WARNINGS', problematicLicenses.length);
|
|
492
822
|
|
|
493
823
|
problematicLicenses.forEach(pkg => {
|
|
@@ -498,13 +828,9 @@ function displayResults(alerts, unusedDeps, outdatedDeps, score, totalDeps, secu
|
|
|
498
828
|
});
|
|
499
829
|
|
|
500
830
|
log(chalk.gray('\n Note: Restrictive licenses may require legal review\n'));
|
|
501
|
-
|
|
502
|
-
logSection('✅ LICENSE COMPLIANCE');
|
|
503
|
-
log(chalk.green(' All licenses are permissive!\n'));
|
|
831
|
+
logDivider();
|
|
504
832
|
}
|
|
505
833
|
|
|
506
|
-
logDivider();
|
|
507
|
-
|
|
508
834
|
// PROJECT HEALTH
|
|
509
835
|
logSection('📊 PROJECT HEALTH');
|
|
510
836
|
|
|
@@ -516,6 +842,10 @@ function displayResults(alerts, unusedDeps, outdatedDeps, score, totalDeps, secu
|
|
|
516
842
|
log(` Security Vulnerabilities: ${chalk.red(securityData.metadata.total)}`);
|
|
517
843
|
}
|
|
518
844
|
|
|
845
|
+
if (supplyChainWarnings.length > 0) {
|
|
846
|
+
log(` Supply Chain Warnings: ${chalk.red(supplyChainWarnings.length)}`);
|
|
847
|
+
}
|
|
848
|
+
|
|
519
849
|
if (alerts.length > 0) {
|
|
520
850
|
log(` Ecosystem Alerts: ${chalk.red(alerts.length)}`);
|
|
521
851
|
}
|
|
@@ -524,15 +854,114 @@ function displayResults(alerts, unusedDeps, outdatedDeps, score, totalDeps, secu
|
|
|
524
854
|
log(` Predictive Warnings: ${chalk.yellow(predictiveWarnings.length)}`);
|
|
525
855
|
}
|
|
526
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
|
+
|
|
527
869
|
log(` Unused: ${chalk.red(unusedDeps.length)}`);
|
|
528
870
|
log(` Outdated: ${chalk.yellow(outdatedDeps.length)}\n`);
|
|
529
871
|
|
|
530
872
|
logDivider();
|
|
531
873
|
|
|
532
|
-
//
|
|
533
|
-
|
|
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();
|
|
534
962
|
}
|
|
535
963
|
|
|
964
|
+
// Original Quick Wins (fallback)
|
|
536
965
|
function displayQuickWins(alerts, unusedDeps, outdatedDeps, score, totalDeps, securityData) {
|
|
537
966
|
const hasCriticalAlerts = alerts.some(a => a.severity === 'critical' || a.severity === 'high');
|
|
538
967
|
const hasCriticalSecurity = securityData.metadata.critical > 0 || securityData.metadata.high > 0;
|