devcompass 2.3.1 ā 2.5.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 +164 -41
- package/package.json +11 -4
- package/src/alerts/github-tracker.js +813 -0
- package/src/alerts/predictive.js +106 -35
- package/src/commands/analyze.js +84 -11
- package/src/commands/fix.js +13 -7
- package/src/utils/json-formatter.js +10 -1
package/src/alerts/predictive.js
CHANGED
|
@@ -1,54 +1,125 @@
|
|
|
1
1
|
// src/alerts/predictive.js
|
|
2
|
+
const { checkGitHubIssues } = require('./github-tracker');
|
|
2
3
|
|
|
3
4
|
/**
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
* For production, integrate with GitHub Issues API
|
|
5
|
+
* Generate predictive warnings based on GitHub activity
|
|
6
|
+
* OPTIMIZED: Only checks packages that are actually installed
|
|
7
7
|
*/
|
|
8
|
-
async function
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
8
|
+
async function generatePredictiveWarnings(packages) {
|
|
9
|
+
try {
|
|
10
|
+
// Only check packages that are actually installed
|
|
11
|
+
const installedPackages = Object.keys(packages);
|
|
12
|
+
|
|
13
|
+
if (installedPackages.length === 0) {
|
|
14
|
+
return [];
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
// Pass only installed packages to GitHub checker
|
|
18
|
+
const githubData = await checkGitHubIssues(packages);
|
|
19
|
+
|
|
20
|
+
const warnings = [];
|
|
21
|
+
|
|
22
|
+
for (const data of githubData) {
|
|
23
|
+
// High risk: many recent issues
|
|
24
|
+
if (data.riskScore >= 3) {
|
|
25
|
+
warnings.push({
|
|
26
|
+
package: data.package,
|
|
27
|
+
severity: 'high',
|
|
28
|
+
type: 'predictive',
|
|
29
|
+
title: 'High bug activity detected',
|
|
30
|
+
description: `${data.last7Days} new issues in last 7 days`,
|
|
31
|
+
recommendation: 'Consider delaying upgrade or monitoring closely',
|
|
32
|
+
data: {
|
|
33
|
+
totalIssues: data.totalIssues,
|
|
34
|
+
recentIssues: data.last7Days,
|
|
35
|
+
criticalIssues: data.criticalIssues,
|
|
36
|
+
trend: data.trend,
|
|
37
|
+
repoUrl: data.repoUrl
|
|
38
|
+
}
|
|
39
|
+
});
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
// Medium risk: increasing trend
|
|
43
|
+
else if (data.riskScore >= 2 || data.trend === 'increasing') {
|
|
44
|
+
warnings.push({
|
|
45
|
+
package: data.package,
|
|
46
|
+
severity: 'medium',
|
|
47
|
+
type: 'predictive',
|
|
48
|
+
title: 'Increased issue activity',
|
|
49
|
+
description: `${data.last7Days} issues opened recently`,
|
|
50
|
+
recommendation: 'Monitor for stability',
|
|
51
|
+
data: {
|
|
52
|
+
totalIssues: data.totalIssues,
|
|
53
|
+
recentIssues: data.last7Days,
|
|
54
|
+
criticalIssues: data.criticalIssues,
|
|
55
|
+
trend: data.trend,
|
|
56
|
+
repoUrl: data.repoUrl
|
|
57
|
+
}
|
|
58
|
+
});
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
// Low risk: stable but worth noting
|
|
62
|
+
else if (data.riskScore >= 1) {
|
|
63
|
+
warnings.push({
|
|
64
|
+
package: data.package,
|
|
65
|
+
severity: 'low',
|
|
66
|
+
type: 'predictive',
|
|
67
|
+
title: 'Minor issue activity',
|
|
68
|
+
description: `${data.last7Days} issues in last week`,
|
|
69
|
+
recommendation: 'No immediate action needed',
|
|
70
|
+
data: {
|
|
71
|
+
totalIssues: data.totalIssues,
|
|
72
|
+
recentIssues: data.last7Days,
|
|
73
|
+
trend: data.trend,
|
|
74
|
+
repoUrl: data.repoUrl
|
|
75
|
+
}
|
|
76
|
+
});
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
return warnings;
|
|
81
|
+
} catch (error) {
|
|
82
|
+
console.error('Error generating predictive warnings:', error.message);
|
|
83
|
+
return [];
|
|
84
|
+
}
|
|
18
85
|
}
|
|
19
86
|
|
|
20
87
|
/**
|
|
21
|
-
* Calculate risk score
|
|
88
|
+
* Calculate risk score for a package
|
|
22
89
|
*/
|
|
23
|
-
function calculateRiskScore(
|
|
24
|
-
let
|
|
90
|
+
function calculateRiskScore(githubData) {
|
|
91
|
+
let score = 0;
|
|
25
92
|
|
|
26
|
-
// High
|
|
27
|
-
if (
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
93
|
+
// High recent activity
|
|
94
|
+
if (githubData.last7Days > 20) score += 3;
|
|
95
|
+
else if (githubData.last7Days > 10) score += 2;
|
|
96
|
+
else if (githubData.last7Days > 5) score += 1;
|
|
97
|
+
|
|
98
|
+
// Critical issues
|
|
99
|
+
if (githubData.criticalIssues > 5) score += 2;
|
|
100
|
+
else if (githubData.criticalIssues > 2) score += 1;
|
|
34
101
|
|
|
35
|
-
|
|
102
|
+
// Trend
|
|
103
|
+
if (githubData.trend === 'increasing') score += 1;
|
|
104
|
+
|
|
105
|
+
return score;
|
|
36
106
|
}
|
|
37
107
|
|
|
38
108
|
/**
|
|
39
|
-
*
|
|
109
|
+
* Get statistics about predictive warnings
|
|
40
110
|
*/
|
|
41
|
-
function
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
111
|
+
function getPredictiveStats(warnings) {
|
|
112
|
+
return {
|
|
113
|
+
total: warnings.length,
|
|
114
|
+
high: warnings.filter(w => w.severity === 'high').length,
|
|
115
|
+
medium: warnings.filter(w => w.severity === 'medium').length,
|
|
116
|
+
low: warnings.filter(w => w.severity === 'low').length,
|
|
117
|
+
packages: warnings.map(w => w.package)
|
|
118
|
+
};
|
|
48
119
|
}
|
|
49
120
|
|
|
50
121
|
module.exports = {
|
|
51
|
-
|
|
122
|
+
generatePredictiveWarnings,
|
|
52
123
|
calculateRiskScore,
|
|
53
|
-
|
|
124
|
+
getPredictiveStats
|
|
54
125
|
};
|
package/src/commands/analyze.js
CHANGED
|
@@ -161,7 +161,7 @@ async function analyze(options) {
|
|
|
161
161
|
}
|
|
162
162
|
}
|
|
163
163
|
|
|
164
|
-
// Check security vulnerabilities
|
|
164
|
+
// Check security vulnerabilities
|
|
165
165
|
spinner.text = 'Checking security vulnerabilities...';
|
|
166
166
|
let securityData = { vulnerabilities: [], metadata: { total: 0, critical: 0, high: 0, moderate: 0, low: 0 } };
|
|
167
167
|
|
|
@@ -184,7 +184,43 @@ async function analyze(options) {
|
|
|
184
184
|
}
|
|
185
185
|
}
|
|
186
186
|
|
|
187
|
-
//
|
|
187
|
+
// Check for predictive warnings (GitHub Issues) - UPDATED for v2.5.0
|
|
188
|
+
const { getTrackedPackageCount, TRACKED_REPOS } = require('../alerts/github-tracker');
|
|
189
|
+
const totalTracked = getTrackedPackageCount();
|
|
190
|
+
|
|
191
|
+
// Count how many installed packages are tracked
|
|
192
|
+
const installedTrackedCount = Object.keys(dependencies).filter(pkg => TRACKED_REPOS[pkg]).length;
|
|
193
|
+
|
|
194
|
+
if (installedTrackedCount > 0) {
|
|
195
|
+
spinner.text = `Checking GitHub activity (${installedTrackedCount}/${totalTracked} tracked packages)...`;
|
|
196
|
+
} else {
|
|
197
|
+
spinner.text = 'Checking GitHub activity...';
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
let predictiveWarnings = [];
|
|
201
|
+
|
|
202
|
+
if (config.cache) {
|
|
203
|
+
predictiveWarnings = getCached(projectPath, 'predictive');
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
if (!predictiveWarnings) {
|
|
207
|
+
try {
|
|
208
|
+
const { generatePredictiveWarnings } = require('../alerts/predictive');
|
|
209
|
+
predictiveWarnings = await generatePredictiveWarnings(dependencies);
|
|
210
|
+
|
|
211
|
+
if (config.cache && predictiveWarnings.length > 0) {
|
|
212
|
+
setCache(projectPath, 'predictive', predictiveWarnings);
|
|
213
|
+
}
|
|
214
|
+
} catch (error) {
|
|
215
|
+
if (outputMode !== 'silent') {
|
|
216
|
+
console.log(chalk.yellow('\nā ļø Could not check GitHub activity'));
|
|
217
|
+
console.log(chalk.gray(` Error: ${error.message}\n`));
|
|
218
|
+
}
|
|
219
|
+
predictiveWarnings = [];
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
// Analyze bundle sizes
|
|
188
224
|
spinner.text = 'Analyzing bundle sizes...';
|
|
189
225
|
let bundleSizes = [];
|
|
190
226
|
|
|
@@ -204,7 +240,7 @@ async function analyze(options) {
|
|
|
204
240
|
}
|
|
205
241
|
}
|
|
206
242
|
|
|
207
|
-
// Check licenses
|
|
243
|
+
// Check licenses
|
|
208
244
|
spinner.text = 'Checking licenses...';
|
|
209
245
|
let licenses = [];
|
|
210
246
|
|
|
@@ -224,7 +260,7 @@ async function analyze(options) {
|
|
|
224
260
|
}
|
|
225
261
|
}
|
|
226
262
|
|
|
227
|
-
// Calculate score
|
|
263
|
+
// Calculate score
|
|
228
264
|
const alertPenalty = calculateAlertPenalty(alerts);
|
|
229
265
|
const securityPenalty = calculateSecurityPenalty(securityData.metadata);
|
|
230
266
|
|
|
@@ -241,15 +277,15 @@ async function analyze(options) {
|
|
|
241
277
|
|
|
242
278
|
// Handle different output modes
|
|
243
279
|
if (outputMode === 'json') {
|
|
244
|
-
const jsonOutput = formatAsJson(alerts, unusedDeps, outdatedDeps, score, totalDeps, securityData, bundleSizes, licenses);
|
|
280
|
+
const jsonOutput = formatAsJson(alerts, unusedDeps, outdatedDeps, score, totalDeps, securityData, bundleSizes, licenses, predictiveWarnings);
|
|
245
281
|
console.log(jsonOutput);
|
|
246
282
|
} else if (outputMode === 'ci') {
|
|
247
|
-
displayResults(alerts, unusedDeps, outdatedDeps, score, totalDeps, securityData, bundleSizes, licenses);
|
|
283
|
+
displayResults(alerts, unusedDeps, outdatedDeps, score, totalDeps, securityData, bundleSizes, licenses, predictiveWarnings);
|
|
248
284
|
handleCiMode(score, config, alerts, unusedDeps);
|
|
249
285
|
} else if (outputMode === 'silent') {
|
|
250
286
|
// Silent mode - no output
|
|
251
287
|
} else {
|
|
252
|
-
displayResults(alerts, unusedDeps, outdatedDeps, score, totalDeps, securityData, bundleSizes, licenses);
|
|
288
|
+
displayResults(alerts, unusedDeps, outdatedDeps, score, totalDeps, securityData, bundleSizes, licenses, predictiveWarnings);
|
|
253
289
|
}
|
|
254
290
|
|
|
255
291
|
} catch (error) {
|
|
@@ -262,10 +298,10 @@ async function analyze(options) {
|
|
|
262
298
|
}
|
|
263
299
|
}
|
|
264
300
|
|
|
265
|
-
function displayResults(alerts, unusedDeps, outdatedDeps, score, totalDeps, securityData, bundleSizes, licenses) {
|
|
301
|
+
function displayResults(alerts, unusedDeps, outdatedDeps, score, totalDeps, securityData, bundleSizes, licenses, predictiveWarnings) {
|
|
266
302
|
logDivider();
|
|
267
303
|
|
|
268
|
-
// SECURITY VULNERABILITIES
|
|
304
|
+
// SECURITY VULNERABILITIES
|
|
269
305
|
if (securityData.metadata.total > 0) {
|
|
270
306
|
const criticalCount = securityData.metadata.critical;
|
|
271
307
|
const highCount = securityData.metadata.high;
|
|
@@ -335,6 +371,39 @@ function displayResults(alerts, unusedDeps, outdatedDeps, score, totalDeps, secu
|
|
|
335
371
|
|
|
336
372
|
logDivider();
|
|
337
373
|
|
|
374
|
+
// PREDICTIVE WARNINGS (UPDATED for v2.5.0)
|
|
375
|
+
if (predictiveWarnings.length > 0) {
|
|
376
|
+
const { getTrackedPackageCount } = require('../alerts/github-tracker');
|
|
377
|
+
const totalTracked = getTrackedPackageCount();
|
|
378
|
+
|
|
379
|
+
logSection('š® PREDICTIVE WARNINGS', predictiveWarnings.length);
|
|
380
|
+
|
|
381
|
+
log(chalk.gray(` Based on recent GitHub activity (${totalTracked}+ packages monitored):\n`));
|
|
382
|
+
|
|
383
|
+
predictiveWarnings.forEach(warning => {
|
|
384
|
+
const display = getSeverityDisplay(warning.severity);
|
|
385
|
+
|
|
386
|
+
log(`${display.emoji} ${display.color(warning.package)}`);
|
|
387
|
+
log(` ${chalk.yellow(warning.title)}`);
|
|
388
|
+
log(` ${warning.description}`);
|
|
389
|
+
log(` ${chalk.cyan('ā')} ${warning.recommendation}`);
|
|
390
|
+
|
|
391
|
+
if (warning.data && warning.data.repoUrl) {
|
|
392
|
+
log(chalk.gray(` GitHub: ${warning.data.repoUrl}`));
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
log('');
|
|
396
|
+
});
|
|
397
|
+
} else {
|
|
398
|
+
const { getTrackedPackageCount } = require('../alerts/github-tracker');
|
|
399
|
+
const totalTracked = getTrackedPackageCount();
|
|
400
|
+
|
|
401
|
+
logSection('ā
PREDICTIVE ANALYSIS');
|
|
402
|
+
log(chalk.green(` No unusual activity detected (${totalTracked}+ packages monitored)!\n`));
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
logDivider();
|
|
406
|
+
|
|
338
407
|
// UNUSED DEPENDENCIES
|
|
339
408
|
if (unusedDeps.length > 0) {
|
|
340
409
|
logSection('š“ UNUSED DEPENDENCIES', unusedDeps.length);
|
|
@@ -376,7 +445,7 @@ function displayResults(alerts, unusedDeps, outdatedDeps, score, totalDeps, secu
|
|
|
376
445
|
|
|
377
446
|
logDivider();
|
|
378
447
|
|
|
379
|
-
// BUNDLE SIZE
|
|
448
|
+
// BUNDLE SIZE
|
|
380
449
|
const heavyPackages = findHeavyPackages(bundleSizes);
|
|
381
450
|
if (heavyPackages.length > 0) {
|
|
382
451
|
logSection('š¦ HEAVY PACKAGES', heavyPackages.length);
|
|
@@ -396,7 +465,7 @@ function displayResults(alerts, unusedDeps, outdatedDeps, score, totalDeps, secu
|
|
|
396
465
|
logDivider();
|
|
397
466
|
}
|
|
398
467
|
|
|
399
|
-
// LICENSE WARNINGS
|
|
468
|
+
// LICENSE WARNINGS
|
|
400
469
|
const problematicLicenses = findProblematicLicenses(licenses);
|
|
401
470
|
if (problematicLicenses.length > 0) {
|
|
402
471
|
logSection('āļø LICENSE WARNINGS', problematicLicenses.length);
|
|
@@ -431,6 +500,10 @@ function displayResults(alerts, unusedDeps, outdatedDeps, score, totalDeps, secu
|
|
|
431
500
|
log(` Ecosystem Alerts: ${chalk.red(alerts.length)}`);
|
|
432
501
|
}
|
|
433
502
|
|
|
503
|
+
if (predictiveWarnings.length > 0) {
|
|
504
|
+
log(` Predictive Warnings: ${chalk.yellow(predictiveWarnings.length)}`);
|
|
505
|
+
}
|
|
506
|
+
|
|
434
507
|
log(` Unused: ${chalk.red(unusedDeps.length)}`);
|
|
435
508
|
log(` Outdated: ${chalk.yellow(outdatedDeps.length)}\n`);
|
|
436
509
|
|
package/src/commands/fix.js
CHANGED
|
@@ -3,11 +3,13 @@ const chalk = require('chalk');
|
|
|
3
3
|
const ora = require('ora');
|
|
4
4
|
const { execSync } = require('child_process');
|
|
5
5
|
const readline = require('readline');
|
|
6
|
+
const path = require('path');
|
|
6
7
|
|
|
7
8
|
const { findUnusedDeps } = require('../analyzers/unused-deps');
|
|
8
9
|
const { findOutdatedDeps } = require('../analyzers/outdated');
|
|
9
10
|
const { checkEcosystemAlerts } = require('../alerts');
|
|
10
11
|
const { getSeverityDisplay } = require('../alerts/formatter');
|
|
12
|
+
const { clearCache } = require('../cache/manager');
|
|
11
13
|
|
|
12
14
|
async function fix(options) {
|
|
13
15
|
const projectPath = options.path || process.cwd();
|
|
@@ -22,7 +24,6 @@ async function fix(options) {
|
|
|
22
24
|
|
|
23
25
|
try {
|
|
24
26
|
const fs = require('fs');
|
|
25
|
-
const path = require('path');
|
|
26
27
|
const packageJsonPath = path.join(projectPath, 'package.json');
|
|
27
28
|
|
|
28
29
|
if (!fs.existsSync(packageJsonPath)) {
|
|
@@ -52,7 +53,7 @@ async function fix(options) {
|
|
|
52
53
|
spinner.succeed(chalk.green('Analysis complete!\n'));
|
|
53
54
|
|
|
54
55
|
// Show what will be fixed
|
|
55
|
-
await showFixPlan(criticalAlerts, unusedDeps, outdatedDeps, options);
|
|
56
|
+
await showFixPlan(criticalAlerts, unusedDeps, outdatedDeps, options, projectPath);
|
|
56
57
|
|
|
57
58
|
} catch (error) {
|
|
58
59
|
spinner.fail(chalk.red('Analysis failed'));
|
|
@@ -61,7 +62,7 @@ async function fix(options) {
|
|
|
61
62
|
}
|
|
62
63
|
}
|
|
63
64
|
|
|
64
|
-
async function showFixPlan(criticalAlerts, unusedDeps, outdatedDeps, options) {
|
|
65
|
+
async function showFixPlan(criticalAlerts, unusedDeps, outdatedDeps, options, projectPath) {
|
|
65
66
|
const actions = [];
|
|
66
67
|
|
|
67
68
|
// Critical alerts
|
|
@@ -160,19 +161,19 @@ async function showFixPlan(criticalAlerts, unusedDeps, outdatedDeps, options) {
|
|
|
160
161
|
|
|
161
162
|
// Confirm
|
|
162
163
|
if (options.yes) {
|
|
163
|
-
await applyFixes(actions);
|
|
164
|
+
await applyFixes(actions, projectPath);
|
|
164
165
|
} else {
|
|
165
166
|
const confirmed = await askConfirmation('\nā Apply these fixes?');
|
|
166
167
|
|
|
167
168
|
if (confirmed) {
|
|
168
|
-
await applyFixes(actions);
|
|
169
|
+
await applyFixes(actions, projectPath);
|
|
169
170
|
} else {
|
|
170
171
|
console.log(chalk.yellow('\nā ļø Fix cancelled. No changes made.\n'));
|
|
171
172
|
}
|
|
172
173
|
}
|
|
173
174
|
}
|
|
174
175
|
|
|
175
|
-
async function applyFixes(actions) {
|
|
176
|
+
async function applyFixes(actions, projectPath) {
|
|
176
177
|
console.log(chalk.cyan.bold('\nš§ Applying fixes...\n'));
|
|
177
178
|
|
|
178
179
|
const spinner = ora('Processing...').start();
|
|
@@ -222,6 +223,11 @@ async function applyFixes(actions) {
|
|
|
222
223
|
console.log(chalk.green.bold('\n⨠All fixes applied successfully!\n'));
|
|
223
224
|
console.log(chalk.cyan('š” Run') + chalk.bold(' devcompass analyze ') + chalk.cyan('to see the new health score.\n'));
|
|
224
225
|
|
|
226
|
+
// Clear cache after fixes - ADDED
|
|
227
|
+
spinner.text = 'Clearing cache...';
|
|
228
|
+
clearCache(projectPath);
|
|
229
|
+
spinner.succeed(chalk.gray('Cache cleared'));
|
|
230
|
+
|
|
225
231
|
} catch (error) {
|
|
226
232
|
spinner.fail(chalk.red('Fix failed'));
|
|
227
233
|
console.log(chalk.red(`\nā Error: ${error.message}\n`));
|
|
@@ -244,4 +250,4 @@ function askConfirmation(question) {
|
|
|
244
250
|
});
|
|
245
251
|
}
|
|
246
252
|
|
|
247
|
-
module.exports = { fix };
|
|
253
|
+
module.exports = { fix };
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
/**
|
|
4
4
|
* Format analysis results as JSON
|
|
5
5
|
*/
|
|
6
|
-
function formatAsJson(alerts, unusedDeps, outdatedDeps, score, totalDeps, securityData, bundleSizes, licenses) {
|
|
6
|
+
function formatAsJson(alerts, unusedDeps, outdatedDeps, score, totalDeps, securityData, bundleSizes, licenses, predictiveWarnings = []) {
|
|
7
7
|
const problematicLicenses = licenses.filter(l => l.type === 'restrictive' || l.type === 'unknown');
|
|
8
8
|
const heavyPackages = bundleSizes.filter(p => p.size > 1024);
|
|
9
9
|
|
|
@@ -15,6 +15,7 @@ function formatAsJson(alerts, unusedDeps, outdatedDeps, score, totalDeps, securi
|
|
|
15
15
|
totalDependencies: totalDeps,
|
|
16
16
|
securityVulnerabilities: securityData.metadata.total,
|
|
17
17
|
ecosystemAlerts: alerts.length,
|
|
18
|
+
predictiveWarnings: predictiveWarnings.length,
|
|
18
19
|
unusedDependencies: unusedDeps.length,
|
|
19
20
|
outdatedPackages: outdatedDeps.length,
|
|
20
21
|
heavyPackages: heavyPackages.length,
|
|
@@ -44,6 +45,14 @@ function formatAsJson(alerts, unusedDeps, outdatedDeps, score, totalDeps, securi
|
|
|
44
45
|
source: alert.source,
|
|
45
46
|
reported: alert.reported
|
|
46
47
|
})),
|
|
48
|
+
predictiveWarnings: predictiveWarnings.map(warning => ({
|
|
49
|
+
package: warning.package,
|
|
50
|
+
severity: warning.severity,
|
|
51
|
+
title: warning.title,
|
|
52
|
+
description: warning.description,
|
|
53
|
+
recommendation: warning.recommendation,
|
|
54
|
+
githubData: warning.data
|
|
55
|
+
})),
|
|
47
56
|
unusedDependencies: unusedDeps.map(dep => ({
|
|
48
57
|
name: dep.name
|
|
49
58
|
})),
|