devcompass 1.0.5 โ 2.1.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 +262 -25
- package/bin/devcompass.js +9 -1
- package/data/issues-db.json +60 -0
- package/package.json +9 -4
- package/src/alerts/formatter.js +68 -0
- package/src/alerts/index.js +31 -0
- package/src/alerts/matcher.js +50 -0
- package/src/alerts/resolver.js +45 -0
- package/src/analyzers/scoring.js +13 -3
- package/src/commands/analyze.js +119 -20
- package/src/commands/fix.js +246 -0
- package/src/config/loader.js +29 -0
- package/src/index.js +0 -0
package/README.md
CHANGED
|
@@ -1,19 +1,25 @@
|
|
|
1
1
|
# ๐งญ DevCompass
|
|
2
2
|
|
|
3
|
-
**Dependency health checker for JavaScript/TypeScript projects**
|
|
3
|
+
**Dependency health checker with ecosystem intelligence for JavaScript/TypeScript projects**
|
|
4
4
|
|
|
5
5
|
[](https://www.npmjs.com/package/devcompass)
|
|
6
6
|
[](https://www.npmjs.com/package/devcompass)
|
|
7
7
|
[](https://opensource.org/licenses/MIT)
|
|
8
8
|
|
|
9
|
-
Analyze your JavaScript projects to find unused dependencies, outdated packages, and
|
|
9
|
+
Analyze your JavaScript projects to find unused dependencies, outdated packages, **detect known security issues**, and **automatically fix them** with a single command.
|
|
10
|
+
|
|
11
|
+
> **NEW in v2.1:** Auto-fix command! ๐ง Fix critical issues automatically!
|
|
12
|
+
> **NEW in v2.0:** Real-time ecosystem alerts for known issues! ๐จ
|
|
10
13
|
|
|
11
14
|
## โจ Features
|
|
12
15
|
|
|
16
|
+
- ๐ง **Auto-Fix Command** (NEW in v2.1!) - Fix issues automatically with one command
|
|
17
|
+
- ๐จ **Ecosystem Intelligence** - Detect known issues before they break production
|
|
13
18
|
- ๐ **Detect unused dependencies** - Find packages you're not actually using
|
|
14
19
|
- ๐ฆ **Check for outdated packages** - See what needs updating
|
|
20
|
+
- ๐ **Security alerts** - Critical vulnerabilities and deprecated packages
|
|
15
21
|
- ๐ **Project health score** - Get a 0-10 rating for your dependencies
|
|
16
|
-
- ๐จ **Beautiful terminal UI** - Colored output with
|
|
22
|
+
- ๐จ **Beautiful terminal UI** - Colored output with severity indicators
|
|
17
23
|
- โก **Fast analysis** - Scans projects in seconds
|
|
18
24
|
- ๐ง **Framework-aware** - Handles React, Next.js, Angular, NestJS, PostCSS, Tailwind
|
|
19
25
|
|
|
@@ -36,48 +42,219 @@ npx devcompass analyze
|
|
|
36
42
|
|
|
37
43
|
## ๐ Usage
|
|
38
44
|
|
|
45
|
+
### Analyze Your Project
|
|
39
46
|
Navigate to your project directory and run:
|
|
40
47
|
```bash
|
|
41
48
|
devcompass analyze
|
|
42
49
|
```
|
|
43
50
|
|
|
51
|
+
### Auto-Fix Issues (NEW in v2.1!)
|
|
52
|
+
Automatically fix detected issues:
|
|
53
|
+
```bash
|
|
54
|
+
devcompass fix
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
## ๐ง Auto-Fix Command (NEW in v2.1!)
|
|
58
|
+
|
|
59
|
+
DevCompass can now **automatically fix issues** in your project!
|
|
60
|
+
|
|
61
|
+
### What it does:
|
|
62
|
+
- ๐ด **Fixes critical security issues** - Upgrades packages with known vulnerabilities
|
|
63
|
+
- ๐งน **Removes unused dependencies** - Cleans up packages you're not using
|
|
64
|
+
- โฌ๏ธ **Safe updates** - Applies patch and minor updates automatically
|
|
65
|
+
- โ ๏ธ **Skips breaking changes** - Major updates require manual review
|
|
66
|
+
|
|
67
|
+
### Usage
|
|
68
|
+
```bash
|
|
69
|
+
# Interactive mode (asks for confirmation)
|
|
70
|
+
devcompass fix
|
|
71
|
+
|
|
72
|
+
# Auto-apply without confirmation
|
|
73
|
+
devcompass fix --yes
|
|
74
|
+
devcompass fix -y
|
|
75
|
+
|
|
76
|
+
# Fix specific directory
|
|
77
|
+
devcompass fix --path /path/to/project
|
|
78
|
+
```
|
|
79
|
+
|
|
44
80
|
### Example Output
|
|
45
81
|
```
|
|
46
|
-
|
|
82
|
+
๐ง DevCompass Fix - Analyzing and fixing your project...
|
|
83
|
+
|
|
84
|
+
๐ด CRITICAL ISSUES TO FIX:
|
|
85
|
+
|
|
86
|
+
๐ด lodash@4.17.19
|
|
87
|
+
Issue: Prototype pollution vulnerability
|
|
88
|
+
Fix: Upgrade to 4.17.21
|
|
89
|
+
|
|
90
|
+
๐ axios@1.6.0
|
|
91
|
+
Issue: Memory leak in request interceptors
|
|
92
|
+
Fix: Upgrade to 1.6.2
|
|
93
|
+
|
|
94
|
+
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
|
|
95
|
+
|
|
96
|
+
๐งน UNUSED DEPENDENCIES TO REMOVE:
|
|
97
|
+
|
|
98
|
+
โ moment
|
|
99
|
+
โ express
|
|
100
|
+
|
|
101
|
+
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
|
|
102
|
+
|
|
103
|
+
โฌ๏ธ SAFE UPDATES (patch/minor):
|
|
104
|
+
|
|
105
|
+
react-dom: 18.2.0 โ 18.2.1 (patch update)
|
|
106
|
+
|
|
107
|
+
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
|
|
108
|
+
|
|
109
|
+
โ ๏ธ MAJOR UPDATES (skipped - may have breaking changes):
|
|
110
|
+
|
|
111
|
+
express: 4.18.0 โ 5.2.1
|
|
112
|
+
|
|
113
|
+
Run these manually after reviewing changelog:
|
|
114
|
+
npm install express@5.2.1
|
|
115
|
+
|
|
116
|
+
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
|
|
117
|
+
|
|
118
|
+
๐ FIX SUMMARY:
|
|
119
|
+
|
|
120
|
+
Critical fixes: 2
|
|
121
|
+
Remove unused: 2
|
|
122
|
+
Safe updates: 1
|
|
123
|
+
Skipped major: 1
|
|
124
|
+
|
|
125
|
+
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
|
|
126
|
+
|
|
127
|
+
โ Apply these fixes? (y/N): y
|
|
128
|
+
|
|
129
|
+
๐ง Applying fixes...
|
|
130
|
+
|
|
131
|
+
โ โ
Removed 2 unused packages
|
|
132
|
+
โ โ
Fixed lodash@4.17.21
|
|
133
|
+
โ โ
Fixed axios@1.6.2
|
|
134
|
+
โ โ
Updated 1 packages
|
|
135
|
+
|
|
136
|
+
โจ All fixes applied successfully!
|
|
137
|
+
|
|
138
|
+
๐ก Run devcompass analyze to see the new health score.
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
### Safety Features
|
|
142
|
+
- โ
Shows what will be changed before applying
|
|
143
|
+
- โ
Requires confirmation (unless `--yes` flag used)
|
|
144
|
+
- โ
Skips major updates (may have breaking changes)
|
|
145
|
+
- โ
Groups actions by priority (critical โ cleanup โ updates)
|
|
146
|
+
- โ
Provides clear summary of changes
|
|
147
|
+
|
|
148
|
+
### Workflow Example
|
|
149
|
+
```bash
|
|
150
|
+
# 1. Analyze your project
|
|
151
|
+
devcompass analyze
|
|
152
|
+
|
|
153
|
+
# 2. If issues found, auto-fix them
|
|
154
|
+
devcompass fix
|
|
155
|
+
|
|
156
|
+
# 3. Verify the improvements
|
|
157
|
+
devcompass analyze
|
|
158
|
+
```
|
|
159
|
+
|
|
160
|
+
## ๐ Analyze Command
|
|
161
|
+
|
|
162
|
+
### Example Output (v2.1)
|
|
163
|
+
```
|
|
164
|
+
๐ DevCompass v2.1.0 - Analyzing your project...
|
|
47
165
|
โ Scanned 15 dependencies in project
|
|
166
|
+
|
|
167
|
+
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
|
|
168
|
+
|
|
169
|
+
๐จ ECOSYSTEM ALERTS (2)
|
|
170
|
+
|
|
171
|
+
๐ด CRITICAL
|
|
172
|
+
lodash@4.17.19
|
|
173
|
+
Issue: Prototype pollution vulnerability
|
|
174
|
+
Affected: <4.17.21
|
|
175
|
+
Fix: 4.17.21
|
|
176
|
+
Source: npm advisory 1523
|
|
177
|
+
|
|
178
|
+
๐ HIGH
|
|
179
|
+
axios@1.6.0
|
|
180
|
+
Issue: Memory leak in request interceptors
|
|
181
|
+
Affected: >=1.5.0 <1.6.2
|
|
182
|
+
Fix: 1.6.2
|
|
183
|
+
Source: GitHub Issue #5456
|
|
184
|
+
|
|
48
185
|
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
|
|
49
186
|
|
|
50
187
|
๐ด UNUSED DEPENDENCIES (2)
|
|
51
|
-
โ lodash
|
|
52
188
|
โ moment
|
|
189
|
+
โ request
|
|
53
190
|
|
|
54
191
|
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
|
|
55
192
|
|
|
56
193
|
๐ก OUTDATED PACKAGES (3)
|
|
57
194
|
react 18.2.0 โ ^19.0.0 (major update)
|
|
58
|
-
axios 1.4.0 โ ^1.6.0 (minor update)
|
|
59
195
|
express 4.18.0 โ ^4.19.0 (patch update)
|
|
60
196
|
|
|
61
197
|
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
|
|
62
198
|
|
|
63
199
|
๐ PROJECT HEALTH
|
|
64
|
-
Overall Score:
|
|
200
|
+
Overall Score: 5.5/10
|
|
65
201
|
Total Dependencies: 15
|
|
202
|
+
Ecosystem Alerts: 2
|
|
66
203
|
Unused: 2
|
|
67
204
|
Outdated: 3
|
|
68
205
|
|
|
69
206
|
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
|
|
70
207
|
|
|
71
|
-
๐ก QUICK
|
|
72
|
-
|
|
73
|
-
|
|
208
|
+
๐ก QUICK WINS
|
|
209
|
+
๐ด Fix critical issues:
|
|
210
|
+
|
|
211
|
+
npm install lodash@4.17.21
|
|
212
|
+
npm install axios@1.6.2
|
|
213
|
+
|
|
214
|
+
๐งน Clean up unused dependencies:
|
|
215
|
+
|
|
216
|
+
npm uninstall moment request
|
|
74
217
|
|
|
75
218
|
Expected impact:
|
|
219
|
+
โ Resolve critical security/stability issues
|
|
76
220
|
โ Remove 2 unused packages
|
|
77
221
|
โ Reduce node_modules size
|
|
78
|
-
โ Improve health score โ
|
|
222
|
+
โ Improve health score โ 8.5/10
|
|
223
|
+
|
|
224
|
+
๐ก TIP: Run 'devcompass fix' to apply these fixes automatically!
|
|
79
225
|
```
|
|
80
226
|
|
|
227
|
+
## ๐จ Ecosystem Intelligence
|
|
228
|
+
|
|
229
|
+
DevCompass tracks **real-world issues** in popular packages and warns you before they break production!
|
|
230
|
+
|
|
231
|
+
### What Gets Detected:
|
|
232
|
+
- ๐ด **Critical security vulnerabilities** - Zero-day exploits, prototype pollution
|
|
233
|
+
- ๐ **High-severity bugs** - Memory leaks, data corruption, breaking changes
|
|
234
|
+
- ๐ก **Deprecated packages** - Unmaintained dependencies
|
|
235
|
+
- โช **Low-priority issues** - Minor bugs, cosmetic problems
|
|
236
|
+
|
|
237
|
+
### Severity Levels:
|
|
238
|
+
- **CRITICAL** - Immediate security risk or data loss (โ2.0 points per issue)
|
|
239
|
+
- **HIGH** - Production stability issues (โ1.5 points per issue)
|
|
240
|
+
- **MEDIUM** - Maintenance concerns, deprecations (โ0.5 points per issue)
|
|
241
|
+
- **LOW** - Minor issues (โ0.2 points per issue)
|
|
242
|
+
|
|
243
|
+
### Currently Tracked Packages:
|
|
244
|
+
- **axios** - Memory leaks, breaking changes
|
|
245
|
+
- **lodash** - Security vulnerabilities (prototype pollution)
|
|
246
|
+
- **moment** - Deprecation notice
|
|
247
|
+
- **express** - Security issues in dependencies
|
|
248
|
+
- **request** - Package deprecated
|
|
249
|
+
|
|
250
|
+
> More packages being added regularly! [Suggest a package](https://github.com/AjayBThorat-20/devcompass/issues)
|
|
251
|
+
|
|
252
|
+
### How It Works:
|
|
253
|
+
1. Reads your actual installed versions from `node_modules`
|
|
254
|
+
2. Matches against curated issues database
|
|
255
|
+
3. Uses semantic versioning for precise detection
|
|
256
|
+
4. Shows actionable fix commands
|
|
257
|
+
|
|
81
258
|
## ๐ฏ What It Detects
|
|
82
259
|
|
|
83
260
|
### Unused Dependencies
|
|
@@ -100,25 +277,41 @@ DevCompass won't flag these as unused (they're typically used in config files):
|
|
|
100
277
|
- Shows current vs latest versions
|
|
101
278
|
- Indicates update type (major/minor/patch)
|
|
102
279
|
|
|
103
|
-
### Health Score
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
280
|
+
### Health Score (Enhanced in v2.0)
|
|
281
|
+
Calculated from 0-10 based on:
|
|
282
|
+
- Percentage of unused dependencies (โ4 points per 100%)
|
|
283
|
+
- Percentage of outdated packages (โ3 points per 100%)
|
|
284
|
+
- Ecosystem alerts by severity (โ0.2 to โ2.0 per issue)
|
|
107
285
|
- Higher score = healthier project
|
|
108
286
|
|
|
109
|
-
## โ๏ธ Options
|
|
287
|
+
## โ๏ธ Commands & Options
|
|
288
|
+
|
|
289
|
+
### Commands
|
|
110
290
|
```bash
|
|
111
|
-
# Analyze
|
|
291
|
+
# Analyze project dependencies
|
|
112
292
|
devcompass analyze
|
|
113
293
|
|
|
114
|
-
#
|
|
115
|
-
devcompass
|
|
294
|
+
# Auto-fix issues
|
|
295
|
+
devcompass fix
|
|
116
296
|
|
|
117
297
|
# Show version
|
|
118
298
|
devcompass --version
|
|
299
|
+
devcompass -v
|
|
119
300
|
|
|
120
301
|
# Show help
|
|
121
302
|
devcompass --help
|
|
303
|
+
devcompass -h
|
|
304
|
+
```
|
|
305
|
+
|
|
306
|
+
### Options
|
|
307
|
+
```bash
|
|
308
|
+
# Analyze/fix specific directory
|
|
309
|
+
devcompass analyze --path /path/to/project
|
|
310
|
+
devcompass fix --path /path/to/project
|
|
311
|
+
|
|
312
|
+
# Auto-fix without confirmation
|
|
313
|
+
devcompass fix --yes
|
|
314
|
+
devcompass fix -y
|
|
122
315
|
```
|
|
123
316
|
|
|
124
317
|
## โ ๏ธ Known Issues & Best Practices
|
|
@@ -147,9 +340,10 @@ If you encounter a false positive, please [report it](https://github.com/AjayBTh
|
|
|
147
340
|
## ๐ก Tips
|
|
148
341
|
|
|
149
342
|
1. **Run regularly** - Add to your CI/CD pipeline or git hooks
|
|
150
|
-
2. **
|
|
151
|
-
3. **
|
|
152
|
-
4. **
|
|
343
|
+
2. **Use fix command** - Let DevCompass handle routine maintenance
|
|
344
|
+
3. **Fix critical alerts first** - Prioritize security and stability
|
|
345
|
+
4. **Review major updates** - Always check changelogs before major version bumps
|
|
346
|
+
5. **Verify before uninstalling** - DevCompass helps identify candidates, but always verify
|
|
153
347
|
|
|
154
348
|
## ๐ค Contributing
|
|
155
349
|
|
|
@@ -161,6 +355,27 @@ Contributions are welcome! Feel free to:
|
|
|
161
355
|
4. Push to the branch (`git push origin feature/amazing-feature`)
|
|
162
356
|
5. Open a Pull Request
|
|
163
357
|
|
|
358
|
+
### Adding Issues to Database
|
|
359
|
+
Want to add known issues for a package?
|
|
360
|
+
|
|
361
|
+
1. Edit `data/issues-db.json`
|
|
362
|
+
2. Follow the existing format:
|
|
363
|
+
```json
|
|
364
|
+
{
|
|
365
|
+
"package-name": [
|
|
366
|
+
{
|
|
367
|
+
"title": "Brief issue description",
|
|
368
|
+
"severity": "critical|high|medium|low",
|
|
369
|
+
"affected": "semver range (e.g., >=1.0.0 <2.0.0)",
|
|
370
|
+
"fix": "Fixed version or migration advice",
|
|
371
|
+
"source": "GitHub Issue #123 or npm advisory",
|
|
372
|
+
"reported": "YYYY-MM-DD"
|
|
373
|
+
}
|
|
374
|
+
]
|
|
375
|
+
}
|
|
376
|
+
```
|
|
377
|
+
3. Submit a PR with your additions!
|
|
378
|
+
|
|
164
379
|
### Development
|
|
165
380
|
```bash
|
|
166
381
|
# Clone the repo
|
|
@@ -172,10 +387,15 @@ npm install
|
|
|
172
387
|
|
|
173
388
|
# Test locally
|
|
174
389
|
node bin/devcompass.js analyze
|
|
390
|
+
node bin/devcompass.js fix
|
|
175
391
|
|
|
176
392
|
# Run on test projects
|
|
177
|
-
cd
|
|
178
|
-
|
|
393
|
+
cd /tmp
|
|
394
|
+
mkdir test-project && cd test-project
|
|
395
|
+
npm init -y
|
|
396
|
+
npm install axios@1.6.0 lodash@4.17.19
|
|
397
|
+
node ~/devcompass/bin/devcompass.js analyze
|
|
398
|
+
node ~/devcompass/bin/devcompass.js fix
|
|
179
399
|
```
|
|
180
400
|
|
|
181
401
|
## ๐ License
|
|
@@ -197,6 +417,7 @@ Built with:
|
|
|
197
417
|
- [chalk](https://github.com/chalk/chalk) - Terminal colors
|
|
198
418
|
- [ora](https://github.com/sindresorhus/ora) - Spinners
|
|
199
419
|
- [commander](https://github.com/tj/commander.js) - CLI framework
|
|
420
|
+
- [semver](https://github.com/npm/node-semver) - Semantic versioning
|
|
200
421
|
|
|
201
422
|
## ๐ Stats
|
|
202
423
|
|
|
@@ -204,8 +425,24 @@ Check out DevCompass stats:
|
|
|
204
425
|
- [npm trends](https://npmtrends.com/devcompass)
|
|
205
426
|
- [npm-stat](https://npm-stat.com/charts.html?package=devcompass)
|
|
206
427
|
|
|
428
|
+
## ๐ What's Next?
|
|
429
|
+
|
|
430
|
+
### Roadmap (v2.2+)
|
|
431
|
+
- [x] ~~Automatic fix command~~ โ
**Added in v2.1!**
|
|
432
|
+
- [ ] Integration with `npm audit` for automated security scanning
|
|
433
|
+
- [ ] CI/CD integration with `--json` output
|
|
434
|
+
- [ ] GitHub Issues API for real-time issue tracking
|
|
435
|
+
- [ ] Web dashboard for team health monitoring
|
|
436
|
+
- [ ] More tracked packages (React, Next.js, Vue, Angular)
|
|
437
|
+
- [ ] Custom ignore rules via config file
|
|
438
|
+
- [ ] Bundle size analysis
|
|
439
|
+
|
|
440
|
+
Want to contribute? Pick an item and open an issue! ๐
|
|
441
|
+
|
|
207
442
|
---
|
|
208
443
|
|
|
209
444
|
**Made with โค๏ธ by [Ajay Thorat](https://github.com/AjayBThorat-20)**
|
|
210
445
|
|
|
211
|
-
*DevCompass - Keep your dependencies healthy!* ๐งญ
|
|
446
|
+
*DevCompass - Keep your dependencies healthy!* ๐งญ
|
|
447
|
+
|
|
448
|
+
**Like Lighthouse for your dependencies** โก
|
package/bin/devcompass.js
CHANGED
|
@@ -4,6 +4,7 @@ const { Command } = require('commander');
|
|
|
4
4
|
const chalk = require('chalk');
|
|
5
5
|
const path = require('path');
|
|
6
6
|
const { analyze } = require('../src/commands/analyze');
|
|
7
|
+
const { fix } = require('../src/commands/fix');
|
|
7
8
|
const packageJson = require('../package.json');
|
|
8
9
|
|
|
9
10
|
// Check if running from local node_modules
|
|
@@ -31,4 +32,11 @@ program
|
|
|
31
32
|
.option('-p, --path <path>', 'Project path', process.cwd())
|
|
32
33
|
.action(analyze);
|
|
33
34
|
|
|
34
|
-
program
|
|
35
|
+
program
|
|
36
|
+
.command('fix')
|
|
37
|
+
.description('Fix issues automatically (remove unused, update safe packages)')
|
|
38
|
+
.option('-p, --path <path>', 'Project path', process.cwd())
|
|
39
|
+
.option('-y, --yes', 'Skip confirmation prompt', false)
|
|
40
|
+
.action(fix);
|
|
41
|
+
|
|
42
|
+
program.parse();
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
{
|
|
2
|
+
"axios": [
|
|
3
|
+
{
|
|
4
|
+
"title": "Memory leak in request interceptors",
|
|
5
|
+
"severity": "high",
|
|
6
|
+
"affected": ">=1.5.0 <1.6.2",
|
|
7
|
+
"fix": "1.6.2",
|
|
8
|
+
"source": "GitHub Issue #5456",
|
|
9
|
+
"reported": "2024-01-15"
|
|
10
|
+
},
|
|
11
|
+
{
|
|
12
|
+
"title": "Breaking change in error handling",
|
|
13
|
+
"severity": "medium",
|
|
14
|
+
"affected": ">=1.4.0 <1.5.0",
|
|
15
|
+
"fix": "1.5.0",
|
|
16
|
+
"source": "GitHub Release Notes",
|
|
17
|
+
"reported": "2023-11-20"
|
|
18
|
+
}
|
|
19
|
+
],
|
|
20
|
+
"lodash": [
|
|
21
|
+
{
|
|
22
|
+
"title": "Prototype pollution vulnerability",
|
|
23
|
+
"severity": "critical",
|
|
24
|
+
"affected": "<4.17.21",
|
|
25
|
+
"fix": "4.17.21",
|
|
26
|
+
"source": "npm advisory 1523",
|
|
27
|
+
"reported": "2021-02-15"
|
|
28
|
+
}
|
|
29
|
+
],
|
|
30
|
+
"moment": [
|
|
31
|
+
{
|
|
32
|
+
"title": "Package is deprecated - no longer maintained",
|
|
33
|
+
"severity": "medium",
|
|
34
|
+
"affected": "*",
|
|
35
|
+
"fix": "Use dayjs or date-fns instead",
|
|
36
|
+
"source": "npm deprecation notice",
|
|
37
|
+
"reported": "2023-09-01"
|
|
38
|
+
}
|
|
39
|
+
],
|
|
40
|
+
"request": [
|
|
41
|
+
{
|
|
42
|
+
"title": "Package deprecated - use node-fetch or axios",
|
|
43
|
+
"severity": "high",
|
|
44
|
+
"affected": "*",
|
|
45
|
+
"fix": "Migrate to axios or node-fetch",
|
|
46
|
+
"source": "npm deprecation notice",
|
|
47
|
+
"reported": "2020-02-11"
|
|
48
|
+
}
|
|
49
|
+
],
|
|
50
|
+
"express": [
|
|
51
|
+
{
|
|
52
|
+
"title": "Security vulnerability in qs dependency",
|
|
53
|
+
"severity": "medium",
|
|
54
|
+
"affected": ">=4.0.0 <4.18.2",
|
|
55
|
+
"fix": "4.18.2",
|
|
56
|
+
"source": "npm advisory 1867",
|
|
57
|
+
"reported": "2022-11-26"
|
|
58
|
+
}
|
|
59
|
+
]
|
|
60
|
+
}
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "devcompass",
|
|
3
|
-
"version": "1.0
|
|
4
|
-
"description": "
|
|
3
|
+
"version": "2.1.0",
|
|
4
|
+
"description": "Dependency health checker with ecosystem intelligence for JavaScript/TypeScript projects",
|
|
5
5
|
"main": "src/index.js",
|
|
6
6
|
"bin": {
|
|
7
7
|
"devcompass": "./bin/devcompass.js"
|
|
@@ -9,6 +9,7 @@
|
|
|
9
9
|
"files": [
|
|
10
10
|
"bin/",
|
|
11
11
|
"src/",
|
|
12
|
+
"data/",
|
|
12
13
|
"README.md",
|
|
13
14
|
"LICENSE"
|
|
14
15
|
],
|
|
@@ -25,7 +26,10 @@
|
|
|
25
26
|
"cli",
|
|
26
27
|
"devtools",
|
|
27
28
|
"package-manager",
|
|
28
|
-
"dependency-analysis"
|
|
29
|
+
"dependency-analysis",
|
|
30
|
+
"security",
|
|
31
|
+
"ecosystem",
|
|
32
|
+
"alerts"
|
|
29
33
|
],
|
|
30
34
|
"author": "Ajay Thorat <ajaythorat988@gmail.com>",
|
|
31
35
|
"license": "MIT",
|
|
@@ -34,7 +38,8 @@
|
|
|
34
38
|
"commander": "^11.1.0",
|
|
35
39
|
"depcheck": "^1.4.7",
|
|
36
40
|
"npm-check-updates": "^16.14.12",
|
|
37
|
-
"ora": "^5.4.1"
|
|
41
|
+
"ora": "^5.4.1",
|
|
42
|
+
"semver": "^7.6.0"
|
|
38
43
|
},
|
|
39
44
|
"engines": {
|
|
40
45
|
"node": ">=14.0.0"
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
const chalk = require('chalk');
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Format alerts for terminal output
|
|
5
|
+
*/
|
|
6
|
+
function formatAlerts(alerts) {
|
|
7
|
+
if (alerts.length === 0) {
|
|
8
|
+
return null;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
// Group alerts by package
|
|
12
|
+
const grouped = {};
|
|
13
|
+
|
|
14
|
+
alerts.forEach(alert => {
|
|
15
|
+
if (!grouped[alert.package]) {
|
|
16
|
+
grouped[alert.package] = [];
|
|
17
|
+
}
|
|
18
|
+
grouped[alert.package].push(alert);
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
return grouped;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Get severity emoji and color
|
|
26
|
+
*/
|
|
27
|
+
function getSeverityDisplay(severity) {
|
|
28
|
+
const displays = {
|
|
29
|
+
critical: { emoji: '๐ด', color: chalk.red.bold, label: 'CRITICAL' },
|
|
30
|
+
high: { emoji: '๐ ', color: chalk.red, label: 'HIGH' },
|
|
31
|
+
medium: { emoji: '๐ก', color: chalk.yellow, label: 'MEDIUM' },
|
|
32
|
+
low: { emoji: 'โช', color: chalk.gray, label: 'LOW' }
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
return displays[severity] || displays.medium;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Calculate alert impact on health score
|
|
40
|
+
*/
|
|
41
|
+
function calculateAlertPenalty(alerts) {
|
|
42
|
+
let penalty = 0;
|
|
43
|
+
|
|
44
|
+
alerts.forEach(alert => {
|
|
45
|
+
switch (alert.severity) {
|
|
46
|
+
case 'critical':
|
|
47
|
+
penalty += 2.0;
|
|
48
|
+
break;
|
|
49
|
+
case 'high':
|
|
50
|
+
penalty += 1.5;
|
|
51
|
+
break;
|
|
52
|
+
case 'medium':
|
|
53
|
+
penalty += 0.5;
|
|
54
|
+
break;
|
|
55
|
+
case 'low':
|
|
56
|
+
penalty += 0.2;
|
|
57
|
+
break;
|
|
58
|
+
}
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
return Math.min(penalty, 5.0); // Cap at 5 points max
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
module.exports = {
|
|
65
|
+
formatAlerts,
|
|
66
|
+
getSeverityDisplay,
|
|
67
|
+
calculateAlertPenalty
|
|
68
|
+
};
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
const { matchIssues } = require('./matcher');
|
|
2
|
+
const { formatAlerts } = require('./formatter');
|
|
3
|
+
const { resolveInstalledVersions } = require('./resolver');
|
|
4
|
+
const path = require('path');
|
|
5
|
+
const fs = require('fs');
|
|
6
|
+
|
|
7
|
+
async function checkEcosystemAlerts(projectPath, dependencies) {
|
|
8
|
+
try {
|
|
9
|
+
// Load issues database
|
|
10
|
+
const issuesDbPath = path.join(__dirname, '../../data/issues-db.json');
|
|
11
|
+
|
|
12
|
+
if (!fs.existsSync(issuesDbPath)) {
|
|
13
|
+
return []; // No alerts if database doesn't exist
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
const issuesDb = JSON.parse(fs.readFileSync(issuesDbPath, 'utf8'));
|
|
17
|
+
|
|
18
|
+
// Resolve installed versions from node_modules
|
|
19
|
+
const installedVersions = await resolveInstalledVersions(projectPath, dependencies);
|
|
20
|
+
|
|
21
|
+
// Match issues against installed versions
|
|
22
|
+
const alerts = matchIssues(installedVersions, issuesDb);
|
|
23
|
+
|
|
24
|
+
return alerts;
|
|
25
|
+
} catch (error) {
|
|
26
|
+
console.error('Error in checkEcosystemAlerts:', error.message);
|
|
27
|
+
return []; // Return empty array on error, don't break analysis
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
module.exports = { checkEcosystemAlerts };
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
const semver = require('semver');
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Match installed packages against known issues database
|
|
5
|
+
*/
|
|
6
|
+
function matchIssues(installedVersions, issuesDb) {
|
|
7
|
+
const alerts = [];
|
|
8
|
+
|
|
9
|
+
for (const [packageName, versionInfo] of Object.entries(installedVersions)) {
|
|
10
|
+
// Check if this package has known issues
|
|
11
|
+
if (!issuesDb[packageName]) {
|
|
12
|
+
continue;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
const packageIssues = issuesDb[packageName];
|
|
16
|
+
const installedVersion = versionInfo.version;
|
|
17
|
+
|
|
18
|
+
// Check each issue for this package
|
|
19
|
+
for (const issue of packageIssues) {
|
|
20
|
+
try {
|
|
21
|
+
// Use semver to check if installed version is affected
|
|
22
|
+
if (semver.satisfies(installedVersion, issue.affected)) {
|
|
23
|
+
alerts.push({
|
|
24
|
+
package: packageName,
|
|
25
|
+
version: installedVersion,
|
|
26
|
+
severity: issue.severity,
|
|
27
|
+
title: issue.title,
|
|
28
|
+
affected: issue.affected,
|
|
29
|
+
fix: issue.fix || null,
|
|
30
|
+
source: issue.source || null,
|
|
31
|
+
reported: issue.reported || null
|
|
32
|
+
});
|
|
33
|
+
}
|
|
34
|
+
} catch (error) {
|
|
35
|
+
// Skip if semver parsing fails
|
|
36
|
+
continue;
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
// Sort by severity: critical > high > medium > low
|
|
42
|
+
const severityOrder = { critical: 0, high: 1, medium: 2, low: 3 };
|
|
43
|
+
alerts.sort((a, b) => {
|
|
44
|
+
return severityOrder[a.severity] - severityOrder[b.severity];
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
return alerts;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
module.exports = { matchIssues };
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
const path = require('path');
|
|
2
|
+
const fs = require('fs');
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Resolve actual installed versions from node_modules
|
|
6
|
+
* This is CRITICAL - we need installed version, not package.json version
|
|
7
|
+
*/
|
|
8
|
+
async function resolveInstalledVersions(projectPath, dependencies) {
|
|
9
|
+
const installedVersions = {};
|
|
10
|
+
|
|
11
|
+
for (const [packageName, declaredVersion] of Object.entries(dependencies)) {
|
|
12
|
+
try {
|
|
13
|
+
const packageJsonPath = path.join(
|
|
14
|
+
projectPath,
|
|
15
|
+
'node_modules',
|
|
16
|
+
packageName,
|
|
17
|
+
'package.json'
|
|
18
|
+
);
|
|
19
|
+
|
|
20
|
+
if (fs.existsSync(packageJsonPath)) {
|
|
21
|
+
const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, 'utf8'));
|
|
22
|
+
installedVersions[packageName] = {
|
|
23
|
+
name: packageName,
|
|
24
|
+
version: packageJson.version, // Clean version like "1.6.0"
|
|
25
|
+
declaredVersion: declaredVersion // What's in package.json like "^1.6.0"
|
|
26
|
+
};
|
|
27
|
+
} else {
|
|
28
|
+
// Fallback: use declared version (strip prefixes)
|
|
29
|
+
const cleanVersion = declaredVersion.replace(/^[\^~>=<]/, '');
|
|
30
|
+
installedVersions[packageName] = {
|
|
31
|
+
name: packageName,
|
|
32
|
+
version: cleanVersion,
|
|
33
|
+
declaredVersion: declaredVersion
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
} catch (error) {
|
|
37
|
+
// Skip packages that can't be resolved
|
|
38
|
+
continue;
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
return installedVersions;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
module.exports = { resolveInstalledVersions };
|
package/src/analyzers/scoring.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
function calculateScore(totalDeps, unusedCount, outdatedCount) {
|
|
1
|
+
function calculateScore(totalDeps, unusedCount, outdatedCount, alertsCount = 0, alertPenalty = 0) {
|
|
2
2
|
let score = 10;
|
|
3
3
|
|
|
4
4
|
if (totalDeps === 0) {
|
|
@@ -7,20 +7,28 @@ function calculateScore(totalDeps, unusedCount, outdatedCount) {
|
|
|
7
7
|
breakdown: {
|
|
8
8
|
unused: 0,
|
|
9
9
|
outdated: 0,
|
|
10
|
+
alerts: 0,
|
|
10
11
|
unusedPenalty: 0,
|
|
11
|
-
outdatedPenalty: 0
|
|
12
|
+
outdatedPenalty: 0,
|
|
13
|
+
alertsPenalty: 0
|
|
12
14
|
}
|
|
13
15
|
};
|
|
14
16
|
}
|
|
15
17
|
|
|
18
|
+
// Unused dependencies penalty
|
|
16
19
|
const unusedRatio = unusedCount / totalDeps;
|
|
17
20
|
const unusedPenalty = unusedRatio * 4;
|
|
18
21
|
score -= unusedPenalty;
|
|
19
22
|
|
|
23
|
+
// Outdated packages penalty
|
|
20
24
|
const outdatedRatio = outdatedCount / totalDeps;
|
|
21
25
|
const outdatedPenalty = outdatedRatio * 3;
|
|
22
26
|
score -= outdatedPenalty;
|
|
23
27
|
|
|
28
|
+
// Ecosystem alerts penalty (from formatter.calculateAlertPenalty)
|
|
29
|
+
score -= alertPenalty;
|
|
30
|
+
|
|
31
|
+
// Ensure score is between 0 and 10
|
|
24
32
|
score = Math.max(0, Math.min(10, score));
|
|
25
33
|
|
|
26
34
|
return {
|
|
@@ -28,8 +36,10 @@ function calculateScore(totalDeps, unusedCount, outdatedCount) {
|
|
|
28
36
|
breakdown: {
|
|
29
37
|
unused: unusedCount,
|
|
30
38
|
outdated: outdatedCount,
|
|
39
|
+
alerts: alertsCount,
|
|
31
40
|
unusedPenalty: parseFloat(unusedPenalty.toFixed(1)),
|
|
32
|
-
outdatedPenalty: parseFloat(outdatedPenalty.toFixed(1))
|
|
41
|
+
outdatedPenalty: parseFloat(outdatedPenalty.toFixed(1)),
|
|
42
|
+
alertsPenalty: parseFloat(alertPenalty.toFixed(1))
|
|
33
43
|
}
|
|
34
44
|
};
|
|
35
45
|
}
|
package/src/commands/analyze.js
CHANGED
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
// src/commands/analyze.js
|
|
2
1
|
const chalk = require('chalk');
|
|
3
2
|
const ora = require('ora');
|
|
4
3
|
const path = require('path');
|
|
@@ -7,6 +6,12 @@ const fs = require('fs');
|
|
|
7
6
|
const { findUnusedDeps } = require('../analyzers/unused-deps');
|
|
8
7
|
const { findOutdatedDeps } = require('../analyzers/outdated');
|
|
9
8
|
const { calculateScore } = require('../analyzers/scoring');
|
|
9
|
+
const { checkEcosystemAlerts } = require('../alerts');
|
|
10
|
+
const {
|
|
11
|
+
formatAlerts,
|
|
12
|
+
getSeverityDisplay,
|
|
13
|
+
calculateAlertPenalty
|
|
14
|
+
} = require('../alerts/formatter');
|
|
10
15
|
const {
|
|
11
16
|
log,
|
|
12
17
|
logSection,
|
|
@@ -58,8 +63,17 @@ async function analyze(options) {
|
|
|
58
63
|
process.exit(0);
|
|
59
64
|
}
|
|
60
65
|
|
|
61
|
-
|
|
66
|
+
// Check for ecosystem alerts
|
|
67
|
+
spinner.text = 'Checking ecosystem alerts...';
|
|
68
|
+
let alerts = [];
|
|
69
|
+
try {
|
|
70
|
+
alerts = await checkEcosystemAlerts(projectPath, dependencies);
|
|
71
|
+
} catch (error) {
|
|
72
|
+
console.log(chalk.yellow('\nโ ๏ธ Could not check ecosystem alerts'));
|
|
73
|
+
console.log(chalk.gray(` Error: ${error.message}\n`));
|
|
74
|
+
}
|
|
62
75
|
|
|
76
|
+
spinner.text = 'Detecting unused dependencies...';
|
|
63
77
|
let unusedDeps = [];
|
|
64
78
|
try {
|
|
65
79
|
unusedDeps = await findUnusedDeps(projectPath, dependencies);
|
|
@@ -69,7 +83,6 @@ async function analyze(options) {
|
|
|
69
83
|
}
|
|
70
84
|
|
|
71
85
|
spinner.text = 'Checking for outdated packages...';
|
|
72
|
-
|
|
73
86
|
let outdatedDeps = [];
|
|
74
87
|
try {
|
|
75
88
|
outdatedDeps = await findOutdatedDeps(projectPath, dependencies);
|
|
@@ -78,15 +91,18 @@ async function analyze(options) {
|
|
|
78
91
|
console.log(chalk.gray(` Error: ${error.message}\n`));
|
|
79
92
|
}
|
|
80
93
|
|
|
94
|
+
const alertPenalty = calculateAlertPenalty(alerts);
|
|
81
95
|
const score = calculateScore(
|
|
82
96
|
totalDeps,
|
|
83
97
|
unusedDeps.length,
|
|
84
|
-
outdatedDeps.length
|
|
98
|
+
outdatedDeps.length,
|
|
99
|
+
alerts.length,
|
|
100
|
+
alertPenalty
|
|
85
101
|
);
|
|
86
102
|
|
|
87
103
|
spinner.succeed(chalk.green(`Scanned ${totalDeps} dependencies in project`));
|
|
88
104
|
|
|
89
|
-
displayResults(unusedDeps, outdatedDeps, score, totalDeps);
|
|
105
|
+
displayResults(alerts, unusedDeps, outdatedDeps, score, totalDeps);
|
|
90
106
|
|
|
91
107
|
} catch (error) {
|
|
92
108
|
spinner.fail(chalk.red('Analysis failed'));
|
|
@@ -98,13 +114,53 @@ async function analyze(options) {
|
|
|
98
114
|
}
|
|
99
115
|
}
|
|
100
116
|
|
|
101
|
-
function displayResults(unusedDeps, outdatedDeps, score, totalDeps) {
|
|
117
|
+
function displayResults(alerts, unusedDeps, outdatedDeps, score, totalDeps) {
|
|
102
118
|
logDivider();
|
|
103
119
|
|
|
120
|
+
// ECOSYSTEM ALERTS (NEW SECTION)
|
|
121
|
+
if (alerts.length > 0) {
|
|
122
|
+
logSection('๐จ ECOSYSTEM ALERTS', alerts.length);
|
|
123
|
+
|
|
124
|
+
const grouped = formatAlerts(alerts);
|
|
125
|
+
|
|
126
|
+
Object.entries(grouped).forEach(([packageName, packageAlerts]) => {
|
|
127
|
+
const firstAlert = packageAlerts[0];
|
|
128
|
+
const display = getSeverityDisplay(firstAlert.severity);
|
|
129
|
+
|
|
130
|
+
log(`\n${display.emoji} ${display.color(display.label)}`);
|
|
131
|
+
log(` ${chalk.bold(packageName)}${chalk.gray('@' + firstAlert.version)}`);
|
|
132
|
+
|
|
133
|
+
packageAlerts.forEach((alert, index) => {
|
|
134
|
+
if (index > 0) {
|
|
135
|
+
const alertDisplay = getSeverityDisplay(alert.severity);
|
|
136
|
+
log(`\n ${alertDisplay.emoji} ${alertDisplay.color(alertDisplay.label)}`);
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
log(` ${chalk.yellow('Issue:')} ${alert.title}`);
|
|
140
|
+
log(` ${chalk.gray('Affected:')} ${alert.affected}`);
|
|
141
|
+
|
|
142
|
+
if (alert.fix) {
|
|
143
|
+
log(` ${chalk.green('Fix:')} ${alert.fix}`);
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
if (alert.source) {
|
|
147
|
+
log(` ${chalk.gray('Source:')} ${alert.source}`);
|
|
148
|
+
}
|
|
149
|
+
});
|
|
150
|
+
});
|
|
151
|
+
|
|
152
|
+
log('');
|
|
153
|
+
} else {
|
|
154
|
+
logSection('โ
ECOSYSTEM ALERTS');
|
|
155
|
+
log(chalk.green(' No known issues detected!\n'));
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
logDivider();
|
|
159
|
+
|
|
160
|
+
// UNUSED DEPENDENCIES
|
|
104
161
|
if (unusedDeps.length > 0) {
|
|
105
162
|
logSection('๐ด UNUSED DEPENDENCIES', unusedDeps.length);
|
|
106
163
|
|
|
107
|
-
// Show ALL unused deps
|
|
108
164
|
unusedDeps.forEach(dep => {
|
|
109
165
|
log(` ${chalk.red('โ')} ${dep.name}`);
|
|
110
166
|
});
|
|
@@ -120,10 +176,10 @@ function displayResults(unusedDeps, outdatedDeps, score, totalDeps) {
|
|
|
120
176
|
|
|
121
177
|
logDivider();
|
|
122
178
|
|
|
179
|
+
// OUTDATED PACKAGES
|
|
123
180
|
if (outdatedDeps.length > 0) {
|
|
124
181
|
logSection('๐ก OUTDATED PACKAGES', outdatedDeps.length);
|
|
125
182
|
|
|
126
|
-
// Show ALL outdated packages
|
|
127
183
|
outdatedDeps.forEach(dep => {
|
|
128
184
|
const nameCol = dep.name.padEnd(20);
|
|
129
185
|
const currentVer = chalk.yellow(dep.current);
|
|
@@ -142,36 +198,79 @@ function displayResults(unusedDeps, outdatedDeps, score, totalDeps) {
|
|
|
142
198
|
|
|
143
199
|
logDivider();
|
|
144
200
|
|
|
201
|
+
// PROJECT HEALTH
|
|
145
202
|
logSection('๐ PROJECT HEALTH');
|
|
146
203
|
|
|
147
204
|
const scoreColor = getScoreColor(score.total);
|
|
148
205
|
log(` Overall Score: ${scoreColor(score.total + '/10')}`);
|
|
149
206
|
log(` Total Dependencies: ${chalk.cyan(totalDeps)}`);
|
|
207
|
+
|
|
208
|
+
if (alerts.length > 0) {
|
|
209
|
+
log(` Ecosystem Alerts: ${chalk.red(alerts.length)}`);
|
|
210
|
+
}
|
|
211
|
+
|
|
150
212
|
log(` Unused: ${chalk.red(unusedDeps.length)}`);
|
|
151
213
|
log(` Outdated: ${chalk.yellow(outdatedDeps.length)}\n`);
|
|
152
214
|
|
|
153
215
|
logDivider();
|
|
154
216
|
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
217
|
+
// QUICK WINS
|
|
218
|
+
displayQuickWins(alerts, unusedDeps, outdatedDeps, score, totalDeps);
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
function displayQuickWins(alerts, unusedDeps, outdatedDeps, score, totalDeps) {
|
|
222
|
+
const hasCriticalAlerts = alerts.some(a => a.severity === 'critical' || a.severity === 'high');
|
|
223
|
+
|
|
224
|
+
if (hasCriticalAlerts || unusedDeps.length > 0) {
|
|
225
|
+
logSection('๐ก QUICK WINS');
|
|
162
226
|
|
|
163
|
-
|
|
227
|
+
// Fix critical alerts first
|
|
228
|
+
if (hasCriticalAlerts) {
|
|
229
|
+
const criticalAlerts = alerts.filter(a => a.severity === 'critical' || a.severity === 'high');
|
|
230
|
+
|
|
231
|
+
log(' ๐ด Fix critical issues:\n');
|
|
232
|
+
|
|
233
|
+
criticalAlerts.forEach(alert => {
|
|
234
|
+
if (alert.fix && alert.fix.includes('.')) {
|
|
235
|
+
// It's a version number
|
|
236
|
+
log(chalk.cyan(` npm install ${alert.package}@${alert.fix}`));
|
|
237
|
+
} else {
|
|
238
|
+
log(chalk.gray(` ${alert.package}: ${alert.fix}`));
|
|
239
|
+
}
|
|
240
|
+
});
|
|
241
|
+
|
|
242
|
+
log('');
|
|
243
|
+
}
|
|
164
244
|
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
245
|
+
// Remove unused deps
|
|
246
|
+
if (unusedDeps.length > 0) {
|
|
247
|
+
log(' ๐งน Clean up unused dependencies:\n');
|
|
248
|
+
|
|
249
|
+
const packagesToRemove = unusedDeps.map(d => d.name).join(' ');
|
|
250
|
+
log(chalk.cyan(` npm uninstall ${packagesToRemove}\n`));
|
|
251
|
+
}
|
|
168
252
|
|
|
253
|
+
// Show expected impact
|
|
254
|
+
const alertPenalty = calculateAlertPenalty(alerts.filter(a => a.severity !== 'critical' && a.severity !== 'high'));
|
|
169
255
|
const improvedScore = calculateScore(
|
|
170
256
|
totalDeps - unusedDeps.length,
|
|
171
257
|
0,
|
|
172
|
-
outdatedDeps.length
|
|
258
|
+
outdatedDeps.length,
|
|
259
|
+
alerts.length - alerts.filter(a => a.severity === 'critical' || a.severity === 'high').length,
|
|
260
|
+
alertPenalty
|
|
173
261
|
);
|
|
174
262
|
|
|
263
|
+
log(' Expected impact:');
|
|
264
|
+
|
|
265
|
+
if (hasCriticalAlerts) {
|
|
266
|
+
log(` ${chalk.green('โ')} Resolve critical security/stability issues`);
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
if (unusedDeps.length > 0) {
|
|
270
|
+
log(` ${chalk.green('โ')} Remove ${unusedDeps.length} unused package${unusedDeps.length > 1 ? 's' : ''}`);
|
|
271
|
+
log(` ${chalk.green('โ')} Reduce node_modules size`);
|
|
272
|
+
}
|
|
273
|
+
|
|
175
274
|
const improvedScoreColor = getScoreColor(improvedScore.total);
|
|
176
275
|
log(` ${chalk.green('โ')} Improve health score โ ${improvedScoreColor(improvedScore.total + '/10')}\n`);
|
|
177
276
|
|
|
@@ -0,0 +1,246 @@
|
|
|
1
|
+
const chalk = require('chalk');
|
|
2
|
+
const ora = require('ora');
|
|
3
|
+
const { execSync } = require('child_process');
|
|
4
|
+
const readline = require('readline');
|
|
5
|
+
|
|
6
|
+
const { findUnusedDeps } = require('../analyzers/unused-deps');
|
|
7
|
+
const { findOutdatedDeps } = require('../analyzers/outdated');
|
|
8
|
+
const { checkEcosystemAlerts } = require('../alerts');
|
|
9
|
+
const { getSeverityDisplay } = require('../alerts/formatter');
|
|
10
|
+
|
|
11
|
+
async function fix(options) {
|
|
12
|
+
const projectPath = options.path || process.cwd();
|
|
13
|
+
|
|
14
|
+
console.log('\n');
|
|
15
|
+
console.log(chalk.cyan.bold('๐ง DevCompass Fix') + ' - Analyzing and fixing your project...\n');
|
|
16
|
+
|
|
17
|
+
const spinner = ora({
|
|
18
|
+
text: 'Analyzing project...',
|
|
19
|
+
color: 'cyan'
|
|
20
|
+
}).start();
|
|
21
|
+
|
|
22
|
+
try {
|
|
23
|
+
const fs = require('fs');
|
|
24
|
+
const path = require('path');
|
|
25
|
+
const packageJsonPath = path.join(projectPath, 'package.json');
|
|
26
|
+
|
|
27
|
+
if (!fs.existsSync(packageJsonPath)) {
|
|
28
|
+
spinner.fail(chalk.red('No package.json found'));
|
|
29
|
+
process.exit(1);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
const projectPackageJson = JSON.parse(fs.readFileSync(packageJsonPath, 'utf8'));
|
|
33
|
+
const dependencies = {
|
|
34
|
+
...(projectPackageJson.dependencies || {}),
|
|
35
|
+
...(projectPackageJson.devDependencies || {})
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
// Check for critical alerts first
|
|
39
|
+
spinner.text = 'Checking for critical issues...';
|
|
40
|
+
const alerts = await checkEcosystemAlerts(projectPath, dependencies);
|
|
41
|
+
const criticalAlerts = alerts.filter(a => a.severity === 'critical' || a.severity === 'high');
|
|
42
|
+
|
|
43
|
+
// Find unused dependencies
|
|
44
|
+
spinner.text = 'Finding unused dependencies...';
|
|
45
|
+
const unusedDeps = await findUnusedDeps(projectPath, dependencies);
|
|
46
|
+
|
|
47
|
+
// Find outdated packages
|
|
48
|
+
spinner.text = 'Checking for updates...';
|
|
49
|
+
const outdatedDeps = await findOutdatedDeps(projectPath, dependencies);
|
|
50
|
+
|
|
51
|
+
spinner.succeed(chalk.green('Analysis complete!\n'));
|
|
52
|
+
|
|
53
|
+
// Show what will be fixed
|
|
54
|
+
await showFixPlan(criticalAlerts, unusedDeps, outdatedDeps, options);
|
|
55
|
+
|
|
56
|
+
} catch (error) {
|
|
57
|
+
spinner.fail(chalk.red('Analysis failed'));
|
|
58
|
+
console.log(chalk.red(`\nโ Error: ${error.message}\n`));
|
|
59
|
+
process.exit(1);
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
async function showFixPlan(criticalAlerts, unusedDeps, outdatedDeps, options) {
|
|
64
|
+
const actions = [];
|
|
65
|
+
|
|
66
|
+
// Critical alerts
|
|
67
|
+
if (criticalAlerts.length > 0) {
|
|
68
|
+
console.log(chalk.red.bold('๐ด CRITICAL ISSUES TO FIX:\n'));
|
|
69
|
+
|
|
70
|
+
criticalAlerts.forEach(alert => {
|
|
71
|
+
const display = getSeverityDisplay(alert.severity);
|
|
72
|
+
console.log(`${display.emoji} ${chalk.bold(alert.package)}@${alert.version}`);
|
|
73
|
+
console.log(` ${chalk.gray('Issue:')} ${alert.title}`);
|
|
74
|
+
|
|
75
|
+
if (alert.fix && /^\d+\.\d+/.test(alert.fix)) {
|
|
76
|
+
console.log(` ${chalk.green('Fix:')} Upgrade to ${alert.fix}\n`);
|
|
77
|
+
actions.push({
|
|
78
|
+
type: 'upgrade',
|
|
79
|
+
package: alert.package,
|
|
80
|
+
version: alert.fix,
|
|
81
|
+
reason: 'Critical security/stability issue'
|
|
82
|
+
});
|
|
83
|
+
} else {
|
|
84
|
+
console.log(` ${chalk.yellow('Fix:')} ${alert.fix}\n`);
|
|
85
|
+
}
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
console.log('โ'.repeat(70) + '\n');
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
// Unused dependencies
|
|
92
|
+
if (unusedDeps.length > 0) {
|
|
93
|
+
console.log(chalk.yellow.bold('๐งน UNUSED DEPENDENCIES TO REMOVE:\n'));
|
|
94
|
+
|
|
95
|
+
unusedDeps.forEach(dep => {
|
|
96
|
+
console.log(` ${chalk.red('โ')} ${dep.name}`);
|
|
97
|
+
actions.push({
|
|
98
|
+
type: 'uninstall',
|
|
99
|
+
package: dep.name,
|
|
100
|
+
reason: 'Not used in project'
|
|
101
|
+
});
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
console.log('\n' + 'โ'.repeat(70) + '\n');
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
// Safe updates (patch/minor only)
|
|
108
|
+
const safeUpdates = outdatedDeps.filter(dep =>
|
|
109
|
+
dep.versionsBehind === 'patch update' || dep.versionsBehind === 'minor update'
|
|
110
|
+
);
|
|
111
|
+
|
|
112
|
+
if (safeUpdates.length > 0) {
|
|
113
|
+
console.log(chalk.cyan.bold('โฌ๏ธ SAFE UPDATES (patch/minor):\n'));
|
|
114
|
+
|
|
115
|
+
safeUpdates.forEach(dep => {
|
|
116
|
+
console.log(` ${dep.name}: ${chalk.yellow(dep.current)} โ ${chalk.green(dep.latest)} ${chalk.gray(`(${dep.versionsBehind})`)}`);
|
|
117
|
+
actions.push({
|
|
118
|
+
type: 'update',
|
|
119
|
+
package: dep.name,
|
|
120
|
+
version: dep.latest,
|
|
121
|
+
reason: dep.versionsBehind
|
|
122
|
+
});
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
console.log('\n' + 'โ'.repeat(70) + '\n');
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
// Major updates (show but don't auto-apply)
|
|
129
|
+
const majorUpdates = outdatedDeps.filter(dep => dep.versionsBehind === 'major update');
|
|
130
|
+
|
|
131
|
+
if (majorUpdates.length > 0) {
|
|
132
|
+
console.log(chalk.gray.bold('โ ๏ธ MAJOR UPDATES (skipped - may have breaking changes):\n'));
|
|
133
|
+
|
|
134
|
+
majorUpdates.forEach(dep => {
|
|
135
|
+
console.log(` ${chalk.gray(dep.name)}: ${dep.current} โ ${dep.latest}`);
|
|
136
|
+
});
|
|
137
|
+
|
|
138
|
+
console.log(chalk.gray('\n Run these manually after reviewing changelog:\n'));
|
|
139
|
+
majorUpdates.forEach(dep => {
|
|
140
|
+
console.log(chalk.gray(` npm install ${dep.name}@${dep.latest}`));
|
|
141
|
+
});
|
|
142
|
+
|
|
143
|
+
console.log('\n' + 'โ'.repeat(70) + '\n');
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
if (actions.length === 0) {
|
|
147
|
+
console.log(chalk.green('โจ Everything looks good! No fixes needed.\n'));
|
|
148
|
+
return;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
// Summary
|
|
152
|
+
console.log(chalk.bold('๐ FIX SUMMARY:\n'));
|
|
153
|
+
console.log(` Critical fixes: ${criticalAlerts.length}`);
|
|
154
|
+
console.log(` Remove unused: ${unusedDeps.length}`);
|
|
155
|
+
console.log(` Safe updates: ${safeUpdates.length}`);
|
|
156
|
+
console.log(` Skipped major: ${majorUpdates.length}\n`);
|
|
157
|
+
|
|
158
|
+
console.log('โ'.repeat(70) + '\n');
|
|
159
|
+
|
|
160
|
+
// Confirm
|
|
161
|
+
if (options.yes) {
|
|
162
|
+
await applyFixes(actions);
|
|
163
|
+
} else {
|
|
164
|
+
const confirmed = await askConfirmation('\nโ Apply these fixes?');
|
|
165
|
+
|
|
166
|
+
if (confirmed) {
|
|
167
|
+
await applyFixes(actions);
|
|
168
|
+
} else {
|
|
169
|
+
console.log(chalk.yellow('\nโ ๏ธ Fix cancelled. No changes made.\n'));
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
async function applyFixes(actions) {
|
|
175
|
+
console.log(chalk.cyan.bold('\n๐ง Applying fixes...\n'));
|
|
176
|
+
|
|
177
|
+
const spinner = ora('Processing...').start();
|
|
178
|
+
|
|
179
|
+
try {
|
|
180
|
+
// Group by type
|
|
181
|
+
const toUninstall = actions.filter(a => a.type === 'uninstall').map(a => a.package);
|
|
182
|
+
const toUpgrade = actions.filter(a => a.type === 'upgrade');
|
|
183
|
+
const toUpdate = actions.filter(a => a.type === 'update');
|
|
184
|
+
|
|
185
|
+
// Uninstall unused
|
|
186
|
+
if (toUninstall.length > 0) {
|
|
187
|
+
spinner.text = `Removing ${toUninstall.length} unused packages...`;
|
|
188
|
+
|
|
189
|
+
const cmd = `npm uninstall ${toUninstall.join(' ')}`;
|
|
190
|
+
execSync(cmd, { stdio: 'pipe' });
|
|
191
|
+
|
|
192
|
+
spinner.succeed(chalk.green(`โ
Removed ${toUninstall.length} unused packages`));
|
|
193
|
+
spinner.start();
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
// Upgrade critical packages
|
|
197
|
+
for (const action of toUpgrade) {
|
|
198
|
+
spinner.text = `Fixing ${action.package}@${action.version}...`;
|
|
199
|
+
|
|
200
|
+
const cmd = `npm install ${action.package}@${action.version}`;
|
|
201
|
+
execSync(cmd, { stdio: 'pipe' });
|
|
202
|
+
|
|
203
|
+
spinner.succeed(chalk.green(`โ
Fixed ${action.package}@${action.version}`));
|
|
204
|
+
spinner.start();
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
// Update safe packages
|
|
208
|
+
if (toUpdate.length > 0) {
|
|
209
|
+
spinner.text = `Updating ${toUpdate.length} packages...`;
|
|
210
|
+
|
|
211
|
+
for (const action of toUpdate) {
|
|
212
|
+
const cmd = `npm install ${action.package}@${action.version}`;
|
|
213
|
+
execSync(cmd, { stdio: 'pipe' });
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
spinner.succeed(chalk.green(`โ
Updated ${toUpdate.length} packages`));
|
|
217
|
+
} else {
|
|
218
|
+
spinner.stop();
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
console.log(chalk.green.bold('\nโจ All fixes applied successfully!\n'));
|
|
222
|
+
console.log(chalk.cyan('๐ก Run') + chalk.bold(' devcompass analyze ') + chalk.cyan('to see the new health score.\n'));
|
|
223
|
+
|
|
224
|
+
} catch (error) {
|
|
225
|
+
spinner.fail(chalk.red('Fix failed'));
|
|
226
|
+
console.log(chalk.red(`\nโ Error: ${error.message}\n`));
|
|
227
|
+
console.log(chalk.yellow('๐ก You may need to fix this manually.\n'));
|
|
228
|
+
process.exit(1);
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
function askConfirmation(question) {
|
|
233
|
+
const rl = readline.createInterface({
|
|
234
|
+
input: process.stdin,
|
|
235
|
+
output: process.stdout
|
|
236
|
+
});
|
|
237
|
+
|
|
238
|
+
return new Promise(resolve => {
|
|
239
|
+
rl.question(chalk.cyan(question) + chalk.gray(' (y/N): '), answer => {
|
|
240
|
+
rl.close();
|
|
241
|
+
resolve(answer.toLowerCase() === 'y' || answer.toLowerCase() === 'yes');
|
|
242
|
+
});
|
|
243
|
+
});
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
module.exports = { fix };
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
const fs = require('fs');
|
|
2
|
+
const path = require('path');
|
|
3
|
+
|
|
4
|
+
function loadConfig(projectPath) {
|
|
5
|
+
const configPath = path.join(projectPath, 'devcompass.config.json');
|
|
6
|
+
|
|
7
|
+
if (!fs.existsSync(configPath)) {
|
|
8
|
+
return getDefaultConfig();
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
try {
|
|
12
|
+
const config = JSON.parse(fs.readFileSync(configPath, 'utf8'));
|
|
13
|
+
return { ...getDefaultConfig(), ...config };
|
|
14
|
+
} catch (error) {
|
|
15
|
+
console.warn('Warning: Could not parse devcompass.config.json, using defaults');
|
|
16
|
+
return getDefaultConfig();
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
function getDefaultConfig() {
|
|
21
|
+
return {
|
|
22
|
+
ignore: [],
|
|
23
|
+
ignoreSeverity: [],
|
|
24
|
+
minScore: 0,
|
|
25
|
+
cache: true
|
|
26
|
+
};
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
module.exports = { loadConfig };
|
package/src/index.js
DELETED
|
File without changes
|