devcompass 2.1.0 â 2.3.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 +412 -135
- package/bin/devcompass.js +4 -0
- package/package.json +9 -2
- package/src/alerts/formatter.js +1 -0
- package/src/alerts/index.js +1 -0
- package/src/alerts/matcher.js +1 -0
- package/src/alerts/predictive.js +54 -0
- package/src/alerts/resolver.js +1 -0
- package/src/analyzers/bundle-size.js +85 -0
- package/src/analyzers/licenses.js +107 -0
- package/src/analyzers/outdated.js +2 -0
- package/src/analyzers/scoring.js +19 -4
- package/src/analyzers/security.js +111 -0
- package/src/analyzers/unused-deps.js +1 -0
- package/src/cache/manager.js +90 -0
- package/src/commands/analyze.js +268 -32
- package/src/commands/fix.js +1 -0
- package/src/config/loader.js +46 -3
- package/src/utils/ci-handler.js +33 -0
- package/src/utils/json-formatter.js +78 -0
- package/src/utils/logger.js +1 -0
package/src/commands/analyze.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
// src/commands/analyze.js
|
|
1
2
|
const chalk = require('chalk');
|
|
2
3
|
const ora = require('ora');
|
|
3
4
|
const path = require('path');
|
|
@@ -7,6 +8,9 @@ const { findUnusedDeps } = require('../analyzers/unused-deps');
|
|
|
7
8
|
const { findOutdatedDeps } = require('../analyzers/outdated');
|
|
8
9
|
const { calculateScore } = require('../analyzers/scoring');
|
|
9
10
|
const { checkEcosystemAlerts } = require('../alerts');
|
|
11
|
+
const { checkSecurity, calculateSecurityPenalty } = require('../analyzers/security');
|
|
12
|
+
const { analyzeBundleSizes, findHeavyPackages } = require('../analyzers/bundle-size');
|
|
13
|
+
const { checkLicenses, findProblematicLicenses } = require('../analyzers/licenses');
|
|
10
14
|
const {
|
|
11
15
|
formatAlerts,
|
|
12
16
|
getSeverityDisplay,
|
|
@@ -18,18 +22,40 @@ const {
|
|
|
18
22
|
logDivider,
|
|
19
23
|
getScoreColor
|
|
20
24
|
} = require('../utils/logger');
|
|
25
|
+
const { loadConfig, filterAlerts } = require('../config/loader');
|
|
26
|
+
const { getCached, setCache } = require('../cache/manager');
|
|
27
|
+
const { formatAsJson } = require('../utils/json-formatter');
|
|
28
|
+
const { handleCiMode } = require('../utils/ci-handler');
|
|
21
29
|
const packageJson = require('../../package.json');
|
|
22
30
|
|
|
23
31
|
async function analyze(options) {
|
|
24
32
|
const projectPath = options.path || process.cwd();
|
|
25
33
|
|
|
26
|
-
|
|
27
|
-
|
|
34
|
+
// Load config
|
|
35
|
+
const config = loadConfig(projectPath);
|
|
28
36
|
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
37
|
+
// Handle output modes
|
|
38
|
+
const outputMode = options.json ? 'json' : (options.ci ? 'ci' : (options.silent ? 'silent' : 'normal'));
|
|
39
|
+
|
|
40
|
+
// Only show header for normal and CI modes
|
|
41
|
+
if (outputMode !== 'silent' && outputMode !== 'json') {
|
|
42
|
+
console.log('\n');
|
|
43
|
+
log(chalk.cyan.bold(`đ DevCompass v${packageJson.version}`) + ' - Analyzing your project...\n');
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
// Create spinner (disabled for json/silent modes to prevent EPIPE errors)
|
|
47
|
+
const spinner = (outputMode === 'json' || outputMode === 'silent')
|
|
48
|
+
? {
|
|
49
|
+
start: function() { return this; },
|
|
50
|
+
succeed: function() {},
|
|
51
|
+
fail: function(msg) { if (msg) console.error(msg); },
|
|
52
|
+
text: '',
|
|
53
|
+
set text(val) {}
|
|
54
|
+
}
|
|
55
|
+
: ora({
|
|
56
|
+
text: 'Loading project...',
|
|
57
|
+
color: 'cyan'
|
|
58
|
+
}).start();
|
|
33
59
|
|
|
34
60
|
try {
|
|
35
61
|
const packageJsonPath = path.join(projectPath, 'package.json');
|
|
@@ -63,46 +89,168 @@ async function analyze(options) {
|
|
|
63
89
|
process.exit(0);
|
|
64
90
|
}
|
|
65
91
|
|
|
66
|
-
// Check for ecosystem alerts
|
|
92
|
+
// Check for ecosystem alerts (with cache)
|
|
67
93
|
spinner.text = 'Checking ecosystem alerts...';
|
|
68
94
|
let alerts = [];
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
console.log(chalk.yellow('\nâ ď¸ Could not check ecosystem alerts'));
|
|
73
|
-
console.log(chalk.gray(` Error: ${error.message}\n`));
|
|
95
|
+
|
|
96
|
+
if (config.cache) {
|
|
97
|
+
alerts = getCached(projectPath, 'alerts');
|
|
74
98
|
}
|
|
75
99
|
|
|
100
|
+
if (!alerts) {
|
|
101
|
+
try {
|
|
102
|
+
alerts = await checkEcosystemAlerts(projectPath, dependencies);
|
|
103
|
+
if (config.cache) {
|
|
104
|
+
setCache(projectPath, 'alerts', alerts);
|
|
105
|
+
}
|
|
106
|
+
} catch (error) {
|
|
107
|
+
if (outputMode !== 'silent') {
|
|
108
|
+
console.log(chalk.yellow('\nâ ď¸ Could not check ecosystem alerts'));
|
|
109
|
+
console.log(chalk.gray(` Error: ${error.message}\n`));
|
|
110
|
+
}
|
|
111
|
+
alerts = [];
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
// Filter alerts based on config
|
|
116
|
+
alerts = filterAlerts(alerts, config);
|
|
117
|
+
|
|
118
|
+
// Unused dependencies
|
|
76
119
|
spinner.text = 'Detecting unused dependencies...';
|
|
77
120
|
let unusedDeps = [];
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
121
|
+
|
|
122
|
+
if (config.cache) {
|
|
123
|
+
unusedDeps = getCached(projectPath, 'unused');
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
if (!unusedDeps) {
|
|
127
|
+
try {
|
|
128
|
+
unusedDeps = await findUnusedDeps(projectPath, dependencies);
|
|
129
|
+
if (config.cache) {
|
|
130
|
+
setCache(projectPath, 'unused', unusedDeps);
|
|
131
|
+
}
|
|
132
|
+
} catch (error) {
|
|
133
|
+
if (outputMode !== 'silent') {
|
|
134
|
+
console.log(chalk.yellow('\nâ ď¸ Could not detect unused dependencies'));
|
|
135
|
+
console.log(chalk.gray(` Error: ${error.message}\n`));
|
|
136
|
+
}
|
|
137
|
+
unusedDeps = [];
|
|
138
|
+
}
|
|
83
139
|
}
|
|
84
140
|
|
|
141
|
+
// Outdated packages
|
|
85
142
|
spinner.text = 'Checking for outdated packages...';
|
|
86
143
|
let outdatedDeps = [];
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
144
|
+
|
|
145
|
+
if (config.cache) {
|
|
146
|
+
outdatedDeps = getCached(projectPath, 'outdated');
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
if (!outdatedDeps) {
|
|
150
|
+
try {
|
|
151
|
+
outdatedDeps = await findOutdatedDeps(projectPath, dependencies);
|
|
152
|
+
if (config.cache) {
|
|
153
|
+
setCache(projectPath, 'outdated', outdatedDeps);
|
|
154
|
+
}
|
|
155
|
+
} catch (error) {
|
|
156
|
+
if (outputMode !== 'silent') {
|
|
157
|
+
console.log(chalk.yellow('\nâ ď¸ Could not check for outdated packages'));
|
|
158
|
+
console.log(chalk.gray(` Error: ${error.message}\n`));
|
|
159
|
+
}
|
|
160
|
+
outdatedDeps = [];
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
// Check security vulnerabilities (NEW)
|
|
165
|
+
spinner.text = 'Checking security vulnerabilities...';
|
|
166
|
+
let securityData = { vulnerabilities: [], metadata: { total: 0, critical: 0, high: 0, moderate: 0, low: 0 } };
|
|
167
|
+
|
|
168
|
+
if (config.cache) {
|
|
169
|
+
const cached = getCached(projectPath, 'security');
|
|
170
|
+
if (cached) securityData = cached;
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
if (securityData.metadata.total === 0) {
|
|
174
|
+
try {
|
|
175
|
+
securityData = await checkSecurity(projectPath);
|
|
176
|
+
if (config.cache) {
|
|
177
|
+
setCache(projectPath, 'security', securityData);
|
|
178
|
+
}
|
|
179
|
+
} catch (error) {
|
|
180
|
+
if (outputMode !== 'silent') {
|
|
181
|
+
console.log(chalk.yellow('\nâ ď¸ Could not check security vulnerabilities'));
|
|
182
|
+
console.log(chalk.gray(` Error: ${error.message}\n`));
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
// Analyze bundle sizes (NEW)
|
|
188
|
+
spinner.text = 'Analyzing bundle sizes...';
|
|
189
|
+
let bundleSizes = [];
|
|
190
|
+
|
|
191
|
+
if (config.cache) {
|
|
192
|
+
const cached = getCached(projectPath, 'bundleSizes');
|
|
193
|
+
if (cached) bundleSizes = cached;
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
if (bundleSizes.length === 0) {
|
|
197
|
+
try {
|
|
198
|
+
bundleSizes = await analyzeBundleSizes(projectPath, dependencies);
|
|
199
|
+
if (config.cache && bundleSizes.length > 0) {
|
|
200
|
+
setCache(projectPath, 'bundleSizes', bundleSizes);
|
|
201
|
+
}
|
|
202
|
+
} catch (error) {
|
|
203
|
+
// Bundle size analysis is optional
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
// Check licenses (NEW)
|
|
208
|
+
spinner.text = 'Checking licenses...';
|
|
209
|
+
let licenses = [];
|
|
210
|
+
|
|
211
|
+
if (config.cache) {
|
|
212
|
+
const cached = getCached(projectPath, 'licenses');
|
|
213
|
+
if (cached) licenses = cached;
|
|
92
214
|
}
|
|
93
215
|
|
|
216
|
+
if (licenses.length === 0) {
|
|
217
|
+
try {
|
|
218
|
+
licenses = await checkLicenses(projectPath, dependencies);
|
|
219
|
+
if (config.cache && licenses.length > 0) {
|
|
220
|
+
setCache(projectPath, 'licenses', licenses);
|
|
221
|
+
}
|
|
222
|
+
} catch (error) {
|
|
223
|
+
// License checking is optional
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
// Calculate score (UPDATED)
|
|
94
228
|
const alertPenalty = calculateAlertPenalty(alerts);
|
|
229
|
+
const securityPenalty = calculateSecurityPenalty(securityData.metadata);
|
|
230
|
+
|
|
95
231
|
const score = calculateScore(
|
|
96
232
|
totalDeps,
|
|
97
233
|
unusedDeps.length,
|
|
98
234
|
outdatedDeps.length,
|
|
99
235
|
alerts.length,
|
|
100
|
-
alertPenalty
|
|
236
|
+
alertPenalty,
|
|
237
|
+
securityPenalty
|
|
101
238
|
);
|
|
102
239
|
|
|
103
240
|
spinner.succeed(chalk.green(`Scanned ${totalDeps} dependencies in project`));
|
|
104
241
|
|
|
105
|
-
|
|
242
|
+
// Handle different output modes
|
|
243
|
+
if (outputMode === 'json') {
|
|
244
|
+
const jsonOutput = formatAsJson(alerts, unusedDeps, outdatedDeps, score, totalDeps, securityData, bundleSizes, licenses);
|
|
245
|
+
console.log(jsonOutput);
|
|
246
|
+
} else if (outputMode === 'ci') {
|
|
247
|
+
displayResults(alerts, unusedDeps, outdatedDeps, score, totalDeps, securityData, bundleSizes, licenses);
|
|
248
|
+
handleCiMode(score, config, alerts, unusedDeps);
|
|
249
|
+
} else if (outputMode === 'silent') {
|
|
250
|
+
// Silent mode - no output
|
|
251
|
+
} else {
|
|
252
|
+
displayResults(alerts, unusedDeps, outdatedDeps, score, totalDeps, securityData, bundleSizes, licenses);
|
|
253
|
+
}
|
|
106
254
|
|
|
107
255
|
} catch (error) {
|
|
108
256
|
spinner.fail(chalk.red('Analysis failed'));
|
|
@@ -114,10 +262,40 @@ async function analyze(options) {
|
|
|
114
262
|
}
|
|
115
263
|
}
|
|
116
264
|
|
|
117
|
-
function displayResults(alerts, unusedDeps, outdatedDeps, score, totalDeps) {
|
|
265
|
+
function displayResults(alerts, unusedDeps, outdatedDeps, score, totalDeps, securityData, bundleSizes, licenses) {
|
|
118
266
|
logDivider();
|
|
119
267
|
|
|
120
|
-
//
|
|
268
|
+
// SECURITY VULNERABILITIES (NEW SECTION)
|
|
269
|
+
if (securityData.metadata.total > 0) {
|
|
270
|
+
const criticalCount = securityData.metadata.critical;
|
|
271
|
+
const highCount = securityData.metadata.high;
|
|
272
|
+
const moderateCount = securityData.metadata.moderate;
|
|
273
|
+
const lowCount = securityData.metadata.low;
|
|
274
|
+
|
|
275
|
+
logSection('đ SECURITY VULNERABILITIES', securityData.metadata.total);
|
|
276
|
+
|
|
277
|
+
if (criticalCount > 0) {
|
|
278
|
+
log(chalk.red.bold(`\n đ´ CRITICAL: ${criticalCount}`));
|
|
279
|
+
}
|
|
280
|
+
if (highCount > 0) {
|
|
281
|
+
log(chalk.red(` đ HIGH: ${highCount}`));
|
|
282
|
+
}
|
|
283
|
+
if (moderateCount > 0) {
|
|
284
|
+
log(chalk.yellow(` đĄ MODERATE: ${moderateCount}`));
|
|
285
|
+
}
|
|
286
|
+
if (lowCount > 0) {
|
|
287
|
+
log(chalk.gray(` ⪠LOW: ${lowCount}`));
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
log(chalk.cyan('\n Run') + chalk.bold(' npm audit fix ') + chalk.cyan('to fix vulnerabilities\n'));
|
|
291
|
+
} else {
|
|
292
|
+
logSection('â
SECURITY VULNERABILITIES');
|
|
293
|
+
log(chalk.green(' No vulnerabilities detected!\n'));
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
logDivider();
|
|
297
|
+
|
|
298
|
+
// ECOSYSTEM ALERTS
|
|
121
299
|
if (alerts.length > 0) {
|
|
122
300
|
logSection('đ¨ ECOSYSTEM ALERTS', alerts.length);
|
|
123
301
|
|
|
@@ -198,6 +376,46 @@ function displayResults(alerts, unusedDeps, outdatedDeps, score, totalDeps) {
|
|
|
198
376
|
|
|
199
377
|
logDivider();
|
|
200
378
|
|
|
379
|
+
// BUNDLE SIZE (NEW SECTION)
|
|
380
|
+
const heavyPackages = findHeavyPackages(bundleSizes);
|
|
381
|
+
if (heavyPackages.length > 0) {
|
|
382
|
+
logSection('đŚ HEAVY PACKAGES', heavyPackages.length);
|
|
383
|
+
|
|
384
|
+
log(chalk.gray(' Packages larger than 1MB:\n'));
|
|
385
|
+
|
|
386
|
+
heavyPackages.slice(0, 10).forEach(pkg => {
|
|
387
|
+
const nameCol = pkg.name.padEnd(25);
|
|
388
|
+
const size = pkg.size > 5120
|
|
389
|
+
? chalk.red(pkg.sizeFormatted)
|
|
390
|
+
: chalk.yellow(pkg.sizeFormatted);
|
|
391
|
+
|
|
392
|
+
log(` ${nameCol} ${size}`);
|
|
393
|
+
});
|
|
394
|
+
|
|
395
|
+
log('');
|
|
396
|
+
logDivider();
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
// LICENSE WARNINGS (NEW SECTION - ALWAYS SHOW)
|
|
400
|
+
const problematicLicenses = findProblematicLicenses(licenses);
|
|
401
|
+
if (problematicLicenses.length > 0) {
|
|
402
|
+
logSection('âď¸ LICENSE WARNINGS', problematicLicenses.length);
|
|
403
|
+
|
|
404
|
+
problematicLicenses.forEach(pkg => {
|
|
405
|
+
const type = pkg.type === 'restrictive'
|
|
406
|
+
? chalk.red('Restrictive')
|
|
407
|
+
: chalk.yellow('Unknown');
|
|
408
|
+
log(` ${chalk.bold(pkg.package)} - ${type} (${pkg.license})`);
|
|
409
|
+
});
|
|
410
|
+
|
|
411
|
+
log(chalk.gray('\n Note: Restrictive licenses may require legal review\n'));
|
|
412
|
+
} else {
|
|
413
|
+
logSection('â
LICENSE COMPLIANCE');
|
|
414
|
+
log(chalk.green(' All licenses are permissive!\n'));
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
logDivider();
|
|
418
|
+
|
|
201
419
|
// PROJECT HEALTH
|
|
202
420
|
logSection('đ PROJECT HEALTH');
|
|
203
421
|
|
|
@@ -205,6 +423,10 @@ function displayResults(alerts, unusedDeps, outdatedDeps, score, totalDeps) {
|
|
|
205
423
|
log(` Overall Score: ${scoreColor(score.total + '/10')}`);
|
|
206
424
|
log(` Total Dependencies: ${chalk.cyan(totalDeps)}`);
|
|
207
425
|
|
|
426
|
+
if (securityData.metadata.total > 0) {
|
|
427
|
+
log(` Security Vulnerabilities: ${chalk.red(securityData.metadata.total)}`);
|
|
428
|
+
}
|
|
429
|
+
|
|
208
430
|
if (alerts.length > 0) {
|
|
209
431
|
log(` Ecosystem Alerts: ${chalk.red(alerts.length)}`);
|
|
210
432
|
}
|
|
@@ -215,16 +437,23 @@ function displayResults(alerts, unusedDeps, outdatedDeps, score, totalDeps) {
|
|
|
215
437
|
logDivider();
|
|
216
438
|
|
|
217
439
|
// QUICK WINS
|
|
218
|
-
displayQuickWins(alerts, unusedDeps, outdatedDeps, score, totalDeps);
|
|
440
|
+
displayQuickWins(alerts, unusedDeps, outdatedDeps, score, totalDeps, securityData);
|
|
219
441
|
}
|
|
220
442
|
|
|
221
|
-
function displayQuickWins(alerts, unusedDeps, outdatedDeps, score, totalDeps) {
|
|
443
|
+
function displayQuickWins(alerts, unusedDeps, outdatedDeps, score, totalDeps, securityData) {
|
|
222
444
|
const hasCriticalAlerts = alerts.some(a => a.severity === 'critical' || a.severity === 'high');
|
|
445
|
+
const hasCriticalSecurity = securityData.metadata.critical > 0 || securityData.metadata.high > 0;
|
|
223
446
|
|
|
224
|
-
if (hasCriticalAlerts || unusedDeps.length > 0) {
|
|
447
|
+
if (hasCriticalSecurity || hasCriticalAlerts || unusedDeps.length > 0) {
|
|
225
448
|
logSection('đĄ QUICK WINS');
|
|
226
449
|
|
|
227
|
-
// Fix
|
|
450
|
+
// Fix security vulnerabilities first
|
|
451
|
+
if (hasCriticalSecurity) {
|
|
452
|
+
log(' đ Fix security vulnerabilities:\n');
|
|
453
|
+
log(chalk.cyan(` npm audit fix\n`));
|
|
454
|
+
}
|
|
455
|
+
|
|
456
|
+
// Fix critical alerts
|
|
228
457
|
if (hasCriticalAlerts) {
|
|
229
458
|
const criticalAlerts = alerts.filter(a => a.severity === 'critical' || a.severity === 'high');
|
|
230
459
|
|
|
@@ -257,13 +486,18 @@ function displayQuickWins(alerts, unusedDeps, outdatedDeps, score, totalDeps) {
|
|
|
257
486
|
0,
|
|
258
487
|
outdatedDeps.length,
|
|
259
488
|
alerts.length - alerts.filter(a => a.severity === 'critical' || a.severity === 'high').length,
|
|
260
|
-
alertPenalty
|
|
489
|
+
alertPenalty,
|
|
490
|
+
0 // Assume security issues fixed
|
|
261
491
|
);
|
|
262
492
|
|
|
263
493
|
log(' Expected impact:');
|
|
264
494
|
|
|
495
|
+
if (hasCriticalSecurity) {
|
|
496
|
+
log(` ${chalk.green('â')} Resolve security vulnerabilities`);
|
|
497
|
+
}
|
|
498
|
+
|
|
265
499
|
if (hasCriticalAlerts) {
|
|
266
|
-
log(` ${chalk.green('â')} Resolve critical
|
|
500
|
+
log(` ${chalk.green('â')} Resolve critical stability issues`);
|
|
267
501
|
}
|
|
268
502
|
|
|
269
503
|
if (unusedDeps.length > 0) {
|
|
@@ -274,6 +508,8 @@ function displayQuickWins(alerts, unusedDeps, outdatedDeps, score, totalDeps) {
|
|
|
274
508
|
const improvedScoreColor = getScoreColor(improvedScore.total);
|
|
275
509
|
log(` ${chalk.green('â')} Improve health score â ${improvedScoreColor(improvedScore.total + '/10')}\n`);
|
|
276
510
|
|
|
511
|
+
log(chalk.cyan('đĄ TIP: Run') + chalk.bold(' devcompass fix ') + chalk.cyan('to apply these fixes automatically!\n'));
|
|
512
|
+
|
|
277
513
|
logDivider();
|
|
278
514
|
}
|
|
279
515
|
}
|
package/src/commands/fix.js
CHANGED
package/src/config/loader.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
// src/config/loader.js
|
|
1
2
|
const fs = require('fs');
|
|
2
3
|
const path = require('path');
|
|
3
4
|
|
|
@@ -20,10 +21,52 @@ function loadConfig(projectPath) {
|
|
|
20
21
|
function getDefaultConfig() {
|
|
21
22
|
return {
|
|
22
23
|
ignore: [],
|
|
23
|
-
ignoreSeverity: [],
|
|
24
|
+
ignoreSeverity: [], // e.g., ["low", "medium"]
|
|
25
|
+
minSeverity: null, // e.g., "medium" - only show medium+ alerts
|
|
24
26
|
minScore: 0,
|
|
25
|
-
cache: true
|
|
27
|
+
cache: true,
|
|
28
|
+
outputMode: 'normal' // normal, json, silent, ci
|
|
26
29
|
};
|
|
27
30
|
}
|
|
28
31
|
|
|
29
|
-
|
|
32
|
+
/**
|
|
33
|
+
* Check if alert should be ignored based on config
|
|
34
|
+
*/
|
|
35
|
+
function shouldIgnoreAlert(alert, config) {
|
|
36
|
+
// Check if package is in ignore list
|
|
37
|
+
if (config.ignore.includes(alert.package)) {
|
|
38
|
+
return true;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
// Check if severity is ignored
|
|
42
|
+
if (config.ignoreSeverity.includes(alert.severity)) {
|
|
43
|
+
return true;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
// Check minimum severity level
|
|
47
|
+
if (config.minSeverity) {
|
|
48
|
+
const severityOrder = { critical: 0, high: 1, medium: 2, low: 3 };
|
|
49
|
+
const minLevel = severityOrder[config.minSeverity];
|
|
50
|
+
const alertLevel = severityOrder[alert.severity];
|
|
51
|
+
|
|
52
|
+
if (alertLevel > minLevel) {
|
|
53
|
+
return true; // Alert is below minimum severity
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
return false;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Filter alerts based on config
|
|
62
|
+
*/
|
|
63
|
+
function filterAlerts(alerts, config) {
|
|
64
|
+
return alerts.filter(alert => !shouldIgnoreAlert(alert, config));
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
module.exports = {
|
|
68
|
+
loadConfig,
|
|
69
|
+
getDefaultConfig,
|
|
70
|
+
shouldIgnoreAlert,
|
|
71
|
+
filterAlerts
|
|
72
|
+
};
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
// src/utils/ci-handler.js
|
|
2
|
+
const chalk = require('chalk');
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Handle CI mode - exit with error code if score below threshold
|
|
6
|
+
*/
|
|
7
|
+
function handleCiMode(score, config, alerts, unusedDeps) {
|
|
8
|
+
const minScore = config.minScore || 7;
|
|
9
|
+
|
|
10
|
+
if (score.total < minScore) {
|
|
11
|
+
console.log(chalk.red(`\nâ CI CHECK FAILED`));
|
|
12
|
+
console.log(chalk.red(`Health score ${score.total}/10 is below minimum ${minScore}/10\n`));
|
|
13
|
+
|
|
14
|
+
// Show critical issues
|
|
15
|
+
const criticalAlerts = alerts.filter(a => a.severity === 'critical' || a.severity === 'high');
|
|
16
|
+
|
|
17
|
+
if (criticalAlerts.length > 0) {
|
|
18
|
+
console.log(chalk.red(`Critical issues: ${criticalAlerts.length}`));
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
if (unusedDeps.length > 0) {
|
|
22
|
+
console.log(chalk.yellow(`Unused dependencies: ${unusedDeps.length}`));
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
process.exit(1); // Fail CI
|
|
26
|
+
} else {
|
|
27
|
+
console.log(chalk.green(`\nâ
CI CHECK PASSED`));
|
|
28
|
+
console.log(chalk.green(`Health score ${score.total}/10 meets minimum ${minScore}/10\n`));
|
|
29
|
+
process.exit(0);
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
module.exports = { handleCiMode };
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
// src/utils/json-formatter.js
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Format analysis results as JSON
|
|
5
|
+
*/
|
|
6
|
+
function formatAsJson(alerts, unusedDeps, outdatedDeps, score, totalDeps, securityData, bundleSizes, licenses) {
|
|
7
|
+
const problematicLicenses = licenses.filter(l => l.type === 'restrictive' || l.type === 'unknown');
|
|
8
|
+
const heavyPackages = bundleSizes.filter(p => p.size > 1024);
|
|
9
|
+
|
|
10
|
+
return JSON.stringify({
|
|
11
|
+
version: require('../../package.json').version,
|
|
12
|
+
timestamp: new Date().toISOString(),
|
|
13
|
+
summary: {
|
|
14
|
+
healthScore: score.total,
|
|
15
|
+
totalDependencies: totalDeps,
|
|
16
|
+
securityVulnerabilities: securityData.metadata.total,
|
|
17
|
+
ecosystemAlerts: alerts.length,
|
|
18
|
+
unusedDependencies: unusedDeps.length,
|
|
19
|
+
outdatedPackages: outdatedDeps.length,
|
|
20
|
+
heavyPackages: heavyPackages.length,
|
|
21
|
+
licenseWarnings: problematicLicenses.length
|
|
22
|
+
},
|
|
23
|
+
security: {
|
|
24
|
+
total: securityData.metadata.total,
|
|
25
|
+
critical: securityData.metadata.critical,
|
|
26
|
+
high: securityData.metadata.high,
|
|
27
|
+
moderate: securityData.metadata.moderate,
|
|
28
|
+
low: securityData.metadata.low,
|
|
29
|
+
vulnerabilities: securityData.vulnerabilities.map(v => ({
|
|
30
|
+
package: v.package,
|
|
31
|
+
severity: v.severity,
|
|
32
|
+
title: v.title,
|
|
33
|
+
cve: v.cve,
|
|
34
|
+
fixAvailable: v.fixAvailable
|
|
35
|
+
}))
|
|
36
|
+
},
|
|
37
|
+
ecosystemAlerts: alerts.map(alert => ({
|
|
38
|
+
package: alert.package,
|
|
39
|
+
version: alert.version,
|
|
40
|
+
severity: alert.severity,
|
|
41
|
+
title: alert.title,
|
|
42
|
+
affected: alert.affected,
|
|
43
|
+
fix: alert.fix,
|
|
44
|
+
source: alert.source,
|
|
45
|
+
reported: alert.reported
|
|
46
|
+
})),
|
|
47
|
+
unusedDependencies: unusedDeps.map(dep => ({
|
|
48
|
+
name: dep.name
|
|
49
|
+
})),
|
|
50
|
+
outdatedPackages: outdatedDeps.map(dep => ({
|
|
51
|
+
name: dep.name,
|
|
52
|
+
current: dep.current,
|
|
53
|
+
latest: dep.latest,
|
|
54
|
+
updateType: dep.versionsBehind
|
|
55
|
+
})),
|
|
56
|
+
bundleAnalysis: {
|
|
57
|
+
heavyPackages: heavyPackages.map(pkg => ({
|
|
58
|
+
name: pkg.name,
|
|
59
|
+
size: pkg.sizeFormatted
|
|
60
|
+
}))
|
|
61
|
+
},
|
|
62
|
+
licenses: {
|
|
63
|
+
warnings: problematicLicenses.map(pkg => ({
|
|
64
|
+
package: pkg.package,
|
|
65
|
+
license: pkg.license,
|
|
66
|
+
type: pkg.type
|
|
67
|
+
}))
|
|
68
|
+
},
|
|
69
|
+
scoreBreakdown: {
|
|
70
|
+
unusedPenalty: score.breakdown.unusedPenalty,
|
|
71
|
+
outdatedPenalty: score.breakdown.outdatedPenalty,
|
|
72
|
+
alertsPenalty: score.breakdown.alertsPenalty,
|
|
73
|
+
securityPenalty: score.breakdown.securityPenalty
|
|
74
|
+
}
|
|
75
|
+
}, null, 2);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
module.exports = { formatAsJson };
|
package/src/utils/logger.js
CHANGED