depstein 0.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 +438 -0
- package/dist/core/cache.d.ts +15 -0
- package/dist/core/cache.js +75 -0
- package/dist/core/cache.js.map +1 -0
- package/dist/core/fetcher.d.ts +7 -0
- package/dist/core/fetcher.js +59 -0
- package/dist/core/fetcher.js.map +1 -0
- package/dist/core/replacements.d.ts +32 -0
- package/dist/core/replacements.js +419 -0
- package/dist/core/replacements.js.map +1 -0
- package/dist/core/scanner.d.ts +7 -0
- package/dist/core/scanner.js +298 -0
- package/dist/core/scanner.js.map +1 -0
- package/dist/core/scorer.d.ts +5 -0
- package/dist/core/scorer.js +289 -0
- package/dist/core/scorer.js.map +1 -0
- package/dist/core/types.d.ts +99 -0
- package/dist/core/types.js +2 -0
- package/dist/core/types.js.map +1 -0
- package/dist/core/vulnerabilities.d.ts +5 -0
- package/dist/core/vulnerabilities.js +93 -0
- package/dist/core/vulnerabilities.js.map +1 -0
- package/dist/ecosystems/cargo.d.ts +2 -0
- package/dist/ecosystems/cargo.js +74 -0
- package/dist/ecosystems/cargo.js.map +1 -0
- package/dist/ecosystems/golang.d.ts +2 -0
- package/dist/ecosystems/golang.js +90 -0
- package/dist/ecosystems/golang.js.map +1 -0
- package/dist/ecosystems/npm.d.ts +2 -0
- package/dist/ecosystems/npm.js +92 -0
- package/dist/ecosystems/npm.js.map +1 -0
- package/dist/ecosystems/pypi.d.ts +2 -0
- package/dist/ecosystems/pypi.js +80 -0
- package/dist/ecosystems/pypi.js.map +1 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +303 -0
- package/dist/index.js.map +1 -0
- package/dist/tui/colors.d.ts +17 -0
- package/dist/tui/colors.js +86 -0
- package/dist/tui/colors.js.map +1 -0
- package/dist/tui/dashboard.d.ts +7 -0
- package/dist/tui/dashboard.js +236 -0
- package/dist/tui/dashboard.js.map +1 -0
- package/dist/tui/detail.d.ts +6 -0
- package/dist/tui/detail.js +68 -0
- package/dist/tui/detail.js.map +1 -0
- package/dist/tui/picker.d.ts +7 -0
- package/dist/tui/picker.js +62 -0
- package/dist/tui/picker.js.map +1 -0
- package/dist/tui/summary.d.ts +6 -0
- package/dist/tui/summary.js +13 -0
- package/dist/tui/summary.js.map +1 -0
- package/dist/tui/table.d.ts +9 -0
- package/dist/tui/table.js +50 -0
- package/dist/tui/table.js.map +1 -0
- package/dist/utils/format.d.ts +9 -0
- package/dist/utils/format.js +72 -0
- package/dist/utils/format.js.map +1 -0
- package/dist/utils/http.d.ts +2 -0
- package/dist/utils/http.js +53 -0
- package/dist/utils/http.js.map +1 -0
- package/dist/utils/spinner.d.ts +11 -0
- package/dist/utils/spinner.js +40 -0
- package/dist/utils/spinner.js.map +1 -0
- package/package.json +50 -0
package/README.md
ADDED
|
@@ -0,0 +1,438 @@
|
|
|
1
|
+
<p align="center">
|
|
2
|
+
<img src="https://img.shields.io/npm/v/depstein?color=0ea5e9&style=flat-square" alt="npm version" />
|
|
3
|
+
<img src="https://img.shields.io/npm/l/depstein?color=22c55e&style=flat-square" alt="license" />
|
|
4
|
+
<img src="https://img.shields.io/node/v/depstein?color=f59e0b&style=flat-square" alt="node version" />
|
|
5
|
+
<img src="https://img.shields.io/badge/ecosystems-npm%20%C2%B7%20PyPI%20%C2%B7%20Cargo%20%C2%B7%20Go-a855f7?style=flat-square" alt="ecosystems" />
|
|
6
|
+
</p>
|
|
7
|
+
|
|
8
|
+
```
|
|
9
|
+
██████╗ ███████╗██████╗ ███████╗████████╗███████╗██╗███╗ ██╗
|
|
10
|
+
██╔══██╗██╔════╝██╔══██╗██╔════╝╚══██╔══╝██╔════╝██║████╗ ██║
|
|
11
|
+
██║ ██║█████╗ ██████╔╝███████╗ ██║ █████╗ ██║██╔██╗ ██║
|
|
12
|
+
██║ ██║██╔══╝ ██╔═══╝ ╚════██║ ██║ ██╔══╝ ██║██║╚██╗██║
|
|
13
|
+
██████╔╝███████╗██║ ███████║ ██║ ███████╗██║██║ ╚████║
|
|
14
|
+
╚═════╝ ╚══════╝╚═╝ ╚══════╝ ╚═╝ ╚══════╝╚═╝╚═╝ ╚═══╝
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
<p align="center"><strong>Dependency health scoring for npm · PyPI · Cargo · Go — right in your terminal.</strong></p>
|
|
18
|
+
|
|
19
|
+
<br />
|
|
20
|
+
|
|
21
|
+
depstein audits every dependency in your project and scores it **0–100** across six dimensions — security, maintenance, popularity, bundle size, TypeScript support, and license. Pop it open with a single command and get an interactive TUI to triage your dependencies before they become problems.
|
|
22
|
+
|
|
23
|
+
---
|
|
24
|
+
|
|
25
|
+
## Screenshots
|
|
26
|
+
|
|
27
|
+
### Main Dashboard
|
|
28
|
+
|
|
29
|
+
```
|
|
30
|
+
██████╗ ███████╗██████╗ ...
|
|
31
|
+
v0.1.0 ~/projects/my-app [npm]
|
|
32
|
+
────────────────────────────────────────────────────────────────────────────────
|
|
33
|
+
8 packages avg 80/100 B ██████████████████████░░░░░░░░ 0 critical 1 poor
|
|
34
|
+
|
|
35
|
+
PACKAGE VER SCORE GRADE ISSUES
|
|
36
|
+
────────────────────────────────────────────────────────────────────────────────
|
|
37
|
+
› @types/react 18.2.0 89/100 A
|
|
38
|
+
@types/node 20.10.0 85/100 A huge size
|
|
39
|
+
chalk 5.3.0 84/100 B 1 vuln (LOW)
|
|
40
|
+
commander 11.1.0 84/100 B
|
|
41
|
+
ink 4.4.1 84/100 B
|
|
42
|
+
typescript 5.3.0 75/100 B huge size
|
|
43
|
+
react 18.2.0 74/100 B vulnerable
|
|
44
|
+
tsx 4.6.0 64/100 C no types
|
|
45
|
+
|
|
46
|
+
────────────────────────────────────────────────────────────────────────────────
|
|
47
|
+
[↑↓] navigate [enter] details [f] filter: All [s] sort: Score [e] export [q] quit
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
### Detail Panel (`Enter` on any package)
|
|
51
|
+
|
|
52
|
+
```
|
|
53
|
+
moment 2.29.4 42/100 D [prod]
|
|
54
|
+
Parse, validate, manipulate, and display dates in javascript.
|
|
55
|
+
|
|
56
|
+
license MIT updated 3 years ago downloads 12.1M/wk bundle 329KB gzip (65KB min)
|
|
57
|
+
repo https://github.com/moment/moment
|
|
58
|
+
|
|
59
|
+
Score Breakdown
|
|
60
|
+
Maintenance ██░░░░░░░░░░░░░░░░░░ 2/20 ↓ Updated over 2 years ago
|
|
61
|
+
Popularity ████████████████████ 10/10 87.3M/week
|
|
62
|
+
Bundle Size ░░░░░░░░░░░░░░░░░░░░ 0/15 ↓ Huge bundle (>100KB gzip)
|
|
63
|
+
TypeScript ██████████████░░░░░░ 7/10 @types/ available
|
|
64
|
+
License ████████████████████ 15/15 MIT (permissive)
|
|
65
|
+
Security ███████████████░░░░░ 22/30 1 vuln(s), LOW
|
|
66
|
+
|
|
67
|
+
Suggested Actions
|
|
68
|
+
⚠ Not actively maintained — consider a fork or alternative
|
|
69
|
+
◆ Very large bundle — evaluate if this package is worth the size
|
|
70
|
+
↑ New version available: 2.29.4 → 2.30.1
|
|
71
|
+
|
|
72
|
+
Replace with
|
|
73
|
+
→ dayjs (91/100 A) — saves 327KB gzip lightweight Day.js, same API
|
|
74
|
+
or: date-fns (94/100 A) — saves 281KB gzip modular date utilities
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
### Interactive Path Picker (run `depstein` from any directory)
|
|
78
|
+
|
|
79
|
+
```
|
|
80
|
+
██████╗ ███████╗██████╗ ...
|
|
81
|
+
Dependency health scorer · npm · pypi · cargo · go
|
|
82
|
+
────────────────────────────────────────────────────────────────────────────────
|
|
83
|
+
|
|
84
|
+
Enter the path to a project, or press Enter to scan the current directory.
|
|
85
|
+
|
|
86
|
+
› Project path · ~/projects/my-rust-app█
|
|
87
|
+
|
|
88
|
+
[enter] scan [esc/ctrl-c] quit
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
### CI Output (`--ci`)
|
|
92
|
+
|
|
93
|
+
```
|
|
94
|
+
depstein CI — 32 packages, avg 68/100 (C)
|
|
95
|
+
threshold: 70/100
|
|
96
|
+
|
|
97
|
+
✗ moment 42/100 D outdated, huge bundle
|
|
98
|
+
→ Replace with: dayjs
|
|
99
|
+
✗ left-pad 18/100 F abandoned
|
|
100
|
+
! node-sass 49/100 D vulnerable, outdated
|
|
101
|
+
~ axios 66/100 C no types
|
|
102
|
+
✓ express 94/100 A ok
|
|
103
|
+
✓ chalk 84/100 B ok
|
|
104
|
+
|
|
105
|
+
✗ Average score 68/100 is below threshold 70/100
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
In GitHub Actions, failing packages additionally emit `::error::` annotations visible inline in pull request diffs.
|
|
109
|
+
|
|
110
|
+
---
|
|
111
|
+
|
|
112
|
+
## Installation
|
|
113
|
+
|
|
114
|
+
```bash
|
|
115
|
+
# Install globally
|
|
116
|
+
npm install -g depstein
|
|
117
|
+
|
|
118
|
+
# Or run without installing
|
|
119
|
+
npx depstein
|
|
120
|
+
|
|
121
|
+
# Or with Bun
|
|
122
|
+
bunx depstein
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
**Requirements:** Node.js 18+
|
|
126
|
+
|
|
127
|
+
---
|
|
128
|
+
|
|
129
|
+
## Usage
|
|
130
|
+
|
|
131
|
+
```bash
|
|
132
|
+
# Open the interactive TUI (auto-detects ecosystem)
|
|
133
|
+
depstein
|
|
134
|
+
|
|
135
|
+
# Analyze a specific directory
|
|
136
|
+
depstein ~/projects/my-app
|
|
137
|
+
|
|
138
|
+
# Show only failing/critical dependencies
|
|
139
|
+
depstein --filter critical
|
|
140
|
+
|
|
141
|
+
# Sort by date last updated (oldest first = most stale)
|
|
142
|
+
depstein --sort updated
|
|
143
|
+
|
|
144
|
+
# Show only the 10 worst packages
|
|
145
|
+
depstein --top 10
|
|
146
|
+
|
|
147
|
+
# Export a full JSON report
|
|
148
|
+
depstein --json > report.json
|
|
149
|
+
|
|
150
|
+
# CI mode — plain text + GitHub Actions annotations
|
|
151
|
+
depstein --ci
|
|
152
|
+
depstein --ci --min-score 80
|
|
153
|
+
|
|
154
|
+
# Auto-replace deprecated packages in package.json
|
|
155
|
+
depstein --fix
|
|
156
|
+
|
|
157
|
+
# Force a specific ecosystem
|
|
158
|
+
depstein --ecosystem pypi
|
|
159
|
+
|
|
160
|
+
# Bypass disk cache for fresh API data
|
|
161
|
+
depstein --no-cache
|
|
162
|
+
```
|
|
163
|
+
|
|
164
|
+
---
|
|
165
|
+
|
|
166
|
+
## CLI Reference
|
|
167
|
+
|
|
168
|
+
| Option | Description | Default |
|
|
169
|
+
|--------|-------------|---------|
|
|
170
|
+
| `[path]` | Project directory to analyze | `.` |
|
|
171
|
+
| `--ecosystem` | Force: `npm` \| `pypi` \| `cargo` \| `golang` | auto |
|
|
172
|
+
| `--filter` | `all` \| `critical` \| `poor` \| `dev` \| `prod` | `all` |
|
|
173
|
+
| `--sort` | `score` \| `name` \| `size` \| `updated` | `score` |
|
|
174
|
+
| `--top <n>` | Show only the N worst packages | all |
|
|
175
|
+
| `--json` | Raw JSON output instead of TUI | — |
|
|
176
|
+
| `--ci` | CI mode: plain text + GitHub Actions `::error::` | — |
|
|
177
|
+
| `--min-score <n>` | Exit 1 if avg score < N (default 70 with `--ci`) | — |
|
|
178
|
+
| `--fix` | Rewrite `package.json` with modern replacements | — |
|
|
179
|
+
| `--no-cache` | Skip disk cache, fetch fresh data | — |
|
|
180
|
+
| `-v, --version` | Show version | — |
|
|
181
|
+
|
|
182
|
+
---
|
|
183
|
+
|
|
184
|
+
## Interactive Controls
|
|
185
|
+
|
|
186
|
+
| Key | Action |
|
|
187
|
+
|-----|--------|
|
|
188
|
+
| `↑` / `↓` | Navigate the dependency list |
|
|
189
|
+
| `Enter` | Open detail panel for the selected package |
|
|
190
|
+
| `Esc` | Close the detail panel |
|
|
191
|
+
| `f` | Cycle filters (all → critical → poor → dev → prod) |
|
|
192
|
+
| `s` | Cycle sort (score → name → size → updated) |
|
|
193
|
+
| `e` | Export report to `depstein-report-<timestamp>.json` |
|
|
194
|
+
| `q` | Quit |
|
|
195
|
+
|
|
196
|
+
---
|
|
197
|
+
|
|
198
|
+
## Scoring
|
|
199
|
+
|
|
200
|
+
Each dependency is scored **0–100** by summing six dimensions. The model is penalty-based — packages start clean and lose points when evidence suggests a problem.
|
|
201
|
+
|
|
202
|
+
| Dimension | Max | Measured by |
|
|
203
|
+
|-----------|:---:|-------------|
|
|
204
|
+
| Security | 30 | CVEs from [OSV.dev](https://osv.dev), version-aware |
|
|
205
|
+
| Maintenance | 20 | Days since last release |
|
|
206
|
+
| Bundle Size | 15 | Gzip size via [bundlephobia](https://bundlephobia.com) (npm only) |
|
|
207
|
+
| License | 15 | Legal risk classification |
|
|
208
|
+
| TypeScript | 10 | Built-in types, `@types/`, or none |
|
|
209
|
+
| Popularity | 10 | Weekly / monthly downloads |
|
|
210
|
+
| **Total** | **100** | |
|
|
211
|
+
|
|
212
|
+
### Security (0–30)
|
|
213
|
+
|
|
214
|
+
| Worst CVE affecting installed version | Points |
|
|
215
|
+
|--------------------------------------|--------|
|
|
216
|
+
| None | 30 |
|
|
217
|
+
| LOW | 22 |
|
|
218
|
+
| MEDIUM | 15 |
|
|
219
|
+
| HIGH | 3 |
|
|
220
|
+
| CRITICAL | 0 |
|
|
221
|
+
|
|
222
|
+
Queries are version-aware — only CVEs that affect the _installed_ version count.
|
|
223
|
+
|
|
224
|
+
### Maintenance (0–20)
|
|
225
|
+
|
|
226
|
+
| Time since last release | Points |
|
|
227
|
+
|------------------------|--------|
|
|
228
|
+
| ≤ 30 days | 20 |
|
|
229
|
+
| ≤ 90 days | 18 |
|
|
230
|
+
| ≤ 6 months | 16 |
|
|
231
|
+
| ≤ 1 year | 10 |
|
|
232
|
+
| ≤ 2 years | 2 |
|
|
233
|
+
| > 2 years | 0 |
|
|
234
|
+
|
|
235
|
+
### Bundle Size (0–15) — npm only
|
|
236
|
+
|
|
237
|
+
Uses real gzip sizes from bundlephobia, not tarball unpacked size.
|
|
238
|
+
|
|
239
|
+
| Gzip size | Points |
|
|
240
|
+
|-----------|--------|
|
|
241
|
+
| < 5 KB | 15 |
|
|
242
|
+
| < 25 KB | 12 |
|
|
243
|
+
| < 50 KB | 10 |
|
|
244
|
+
| < 100 KB | 10 (flagged: large) |
|
|
245
|
+
| ≥ 100 KB | 0 (flagged: huge) |
|
|
246
|
+
|
|
247
|
+
Non-npm packages receive 15/15.
|
|
248
|
+
|
|
249
|
+
### TypeScript Support (0–10)
|
|
250
|
+
|
|
251
|
+
| Support | Points |
|
|
252
|
+
|---------|--------|
|
|
253
|
+
| Built-in (`types`/`typings` in `package.json`) | 10 |
|
|
254
|
+
| `@types/` package available | 7 |
|
|
255
|
+
| No types | 0 |
|
|
256
|
+
|
|
257
|
+
### License (0–15)
|
|
258
|
+
|
|
259
|
+
| License | Points |
|
|
260
|
+
|---------|--------|
|
|
261
|
+
| MIT, Apache-2.0, BSD-*, ISC | 15 |
|
|
262
|
+
| Unlicense, CC0 | 12 |
|
|
263
|
+
| MPL-2.0, LGPL | 10 |
|
|
264
|
+
| GPL variants | 5 |
|
|
265
|
+
| Unknown / Proprietary | 0 |
|
|
266
|
+
|
|
267
|
+
### Popularity (0–10)
|
|
268
|
+
|
|
269
|
+
| Weekly downloads (npm) | Points |
|
|
270
|
+
|------------------------|--------|
|
|
271
|
+
| ≥ 1 million | 10 |
|
|
272
|
+
| ≥ 100K | 8 |
|
|
273
|
+
| ≥ 10K | 7 |
|
|
274
|
+
| ≥ 1K | 5 |
|
|
275
|
+
| ≥ 100 | 2 |
|
|
276
|
+
| < 100 | 0 |
|
|
277
|
+
|
|
278
|
+
### Grade Scale
|
|
279
|
+
|
|
280
|
+
| Score | Grade |
|
|
281
|
+
|-------|:-----:|
|
|
282
|
+
| 85–100 | **A** |
|
|
283
|
+
| 70–84 | **B** |
|
|
284
|
+
| 50–69 | **C** |
|
|
285
|
+
| 30–49 | **D** |
|
|
286
|
+
| 0–29 | **F** |
|
|
287
|
+
|
|
288
|
+
---
|
|
289
|
+
|
|
290
|
+
## Supported Ecosystems
|
|
291
|
+
|
|
292
|
+
| Ecosystem | Manifest | Data sources |
|
|
293
|
+
|-----------|----------|-------------|
|
|
294
|
+
| **npm** | `package.json` | npm registry · download API · bundlephobia · OSV |
|
|
295
|
+
| **PyPI** | `requirements.txt` · `pyproject.toml` · `setup.py` | pypi.org · pypistats.org · OSV |
|
|
296
|
+
| **Cargo** | `Cargo.toml` · `Cargo.lock` | crates.io · OSV |
|
|
297
|
+
| **Go** | `go.mod` | proxy.golang.org · GitHub API · OSV |
|
|
298
|
+
|
|
299
|
+
---
|
|
300
|
+
|
|
301
|
+
## `--fix` — Automated Replacements
|
|
302
|
+
|
|
303
|
+
`depstein --fix` rewrites `package.json` replacing 30+ known problematic packages:
|
|
304
|
+
|
|
305
|
+
| Remove | Replace with | Why |
|
|
306
|
+
|--------|-------------|-----|
|
|
307
|
+
| `moment` | `dayjs` | 329KB → ~2KB gzip, near-identical API |
|
|
308
|
+
| `request` | `got` | Officially deprecated since 2020 |
|
|
309
|
+
| `node-sass` | `sass` | Official Dart Sass port |
|
|
310
|
+
| `tslint` | `eslint` | Deprecated, merged into ESLint |
|
|
311
|
+
| `colors` | `chalk` | Supply-chain incident |
|
|
312
|
+
| `lodash` | `es-toolkit` | Native ESM, tree-shakeable, 97% smaller |
|
|
313
|
+
| `uuid` | `nanoid` | Smaller, faster, cryptographically strong |
|
|
314
|
+
| `faker` | `@faker-js/faker` | Community-maintained fork |
|
|
315
|
+
| `cross-fetch` | `native fetch` | Node 18+ has `fetch` built-in |
|
|
316
|
+
| `left-pad` | `String.prototype.padStart` | Abandoned; native in JS |
|
|
317
|
+
| … and 20+ more | | |
|
|
318
|
+
|
|
319
|
+
Run `npm install` after `--fix` to apply lockfile changes.
|
|
320
|
+
|
|
321
|
+
---
|
|
322
|
+
|
|
323
|
+
## CI / CD
|
|
324
|
+
|
|
325
|
+
### GitHub Actions
|
|
326
|
+
|
|
327
|
+
```yaml
|
|
328
|
+
- name: Audit dependencies
|
|
329
|
+
run: npx depstein --ci --min-score 75
|
|
330
|
+
```
|
|
331
|
+
|
|
332
|
+
- Outputs a plain-text table of all packages with scores and issues
|
|
333
|
+
- Emits `::error::` annotations on failing packages when `GITHUB_ACTIONS=true`
|
|
334
|
+
- Exits with code 1 if the average score is below the threshold
|
|
335
|
+
|
|
336
|
+
### Plain shell
|
|
337
|
+
|
|
338
|
+
```bash
|
|
339
|
+
# Fail if average drops below 70
|
|
340
|
+
depstein --min-score 70 --json
|
|
341
|
+
```
|
|
342
|
+
|
|
343
|
+
---
|
|
344
|
+
|
|
345
|
+
## Caching
|
|
346
|
+
|
|
347
|
+
API responses are cached at `~/.depstein/cache.json` with a **1-hour TTL**. Repeated runs are near-instant. Cached packages are labelled `[cached]` in the detail panel. Use `--no-cache` to bypass.
|
|
348
|
+
|
|
349
|
+
---
|
|
350
|
+
|
|
351
|
+
## JSON Output
|
|
352
|
+
|
|
353
|
+
```jsonc
|
|
354
|
+
{
|
|
355
|
+
"projectPath": "/Users/you/my-app",
|
|
356
|
+
"ecosystem": "npm",
|
|
357
|
+
"averageScore": 80.4,
|
|
358
|
+
"grade": "B",
|
|
359
|
+
"totalCount": 8,
|
|
360
|
+
"criticalCount": 0,
|
|
361
|
+
"poorCount": 1,
|
|
362
|
+
"scoredDependencies": [
|
|
363
|
+
{
|
|
364
|
+
"raw": { "name": "chalk", "version": "5.3.0", "isDev": false },
|
|
365
|
+
"totalScore": 84,
|
|
366
|
+
"grade": "B",
|
|
367
|
+
"issues": ["1 vuln (LOW)"],
|
|
368
|
+
"suggestion": null,
|
|
369
|
+
"dimensions": {
|
|
370
|
+
"maintenance": { "score": 18, "maxScore": 20, "reason": "Updated within 6 months" },
|
|
371
|
+
"popularity": { "score": 10, "maxScore": 10, "reason": "430.9M/week" },
|
|
372
|
+
"bundleSize": { "score": 12, "maxScore": 15, "reason": "3.1KB gzip (small)" },
|
|
373
|
+
"typescript": { "score": 10, "maxScore": 10, "reason": "Built-in types" },
|
|
374
|
+
"license": { "score": 15, "maxScore": 15, "reason": "MIT (permissive)" },
|
|
375
|
+
"vulnerability": { "score": 22, "maxScore": 30, "reason": "1 vuln(s), LOW" }
|
|
376
|
+
}
|
|
377
|
+
}
|
|
378
|
+
]
|
|
379
|
+
}
|
|
380
|
+
```
|
|
381
|
+
|
|
382
|
+
---
|
|
383
|
+
|
|
384
|
+
## Architecture
|
|
385
|
+
|
|
386
|
+
```
|
|
387
|
+
src/
|
|
388
|
+
├── index.ts CLI entry (Commander) — routes to TUI / JSON / CI / fix
|
|
389
|
+
├── core/
|
|
390
|
+
│ ├── types.ts Shared TypeScript interfaces
|
|
391
|
+
│ ├── cache.ts Disk cache (~/.depstein/cache.json, 1-hr TTL)
|
|
392
|
+
│ ├── scanner.ts Detects ecosystem, parses manifests + lockfiles
|
|
393
|
+
│ ├── fetcher.ts Parallel batch fetcher (10 concurrent)
|
|
394
|
+
│ ├── scorer.ts Pure scoring engine (6 dimensions → 0–100)
|
|
395
|
+
│ ├── replacements.ts 30+ deprecated-package replacement database
|
|
396
|
+
│ └── vulnerabilities.ts OSV.dev API client (version-aware CVE queries)
|
|
397
|
+
├── ecosystems/
|
|
398
|
+
│ ├── npm.ts npm registry · download API · bundlephobia
|
|
399
|
+
│ ├── pypi.ts PyPI JSON API · pypistats.org
|
|
400
|
+
│ ├── cargo.ts crates.io API
|
|
401
|
+
│ └── golang.ts Go module proxy · GitHub API
|
|
402
|
+
├── tui/
|
|
403
|
+
│ ├── dashboard.tsx Main Ink/React app component
|
|
404
|
+
│ ├── table.tsx Scrollable dependency table with grade colours
|
|
405
|
+
│ ├── summary.tsx Score distribution bar
|
|
406
|
+
│ ├── detail.tsx Package detail panel + replacement suggestions
|
|
407
|
+
│ ├── picker.tsx Interactive path picker (run from any directory)
|
|
408
|
+
│ └── colors.ts Grade/score colour helpers
|
|
409
|
+
└── utils/
|
|
410
|
+
├── http.ts Fetch with retry + rate-limit backoff
|
|
411
|
+
├── format.ts Number formatting, relative dates, progress bars
|
|
412
|
+
└── spinner.ts Stderr spinner for non-TUI modes
|
|
413
|
+
```
|
|
414
|
+
|
|
415
|
+
---
|
|
416
|
+
|
|
417
|
+
## Development
|
|
418
|
+
|
|
419
|
+
```bash
|
|
420
|
+
git clone https://github.com/depstein/depstein
|
|
421
|
+
cd depstein
|
|
422
|
+
npm install
|
|
423
|
+
|
|
424
|
+
# Run in dev mode against any local project
|
|
425
|
+
npm run dev -- ~/projects/my-app
|
|
426
|
+
|
|
427
|
+
# Build
|
|
428
|
+
npm run build
|
|
429
|
+
|
|
430
|
+
# Install as a global command from local build
|
|
431
|
+
npm install -g .
|
|
432
|
+
```
|
|
433
|
+
|
|
434
|
+
---
|
|
435
|
+
|
|
436
|
+
## License
|
|
437
|
+
|
|
438
|
+
MIT
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import type { PackageMetadata } from './types.js';
|
|
2
|
+
declare class DiskCache {
|
|
3
|
+
private store;
|
|
4
|
+
private dirty;
|
|
5
|
+
constructor();
|
|
6
|
+
private load;
|
|
7
|
+
private save;
|
|
8
|
+
get(ecosystem: string, name: string): Omit<PackageMetadata, 'fromCache'> | null;
|
|
9
|
+
set(ecosystem: string, name: string, data: Omit<PackageMetadata, 'fromCache'>): void;
|
|
10
|
+
clear(): void;
|
|
11
|
+
/** Flush dirty writes at process exit */
|
|
12
|
+
flush(): void;
|
|
13
|
+
}
|
|
14
|
+
export declare const cache: DiskCache;
|
|
15
|
+
export {};
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import { readFileSync, writeFileSync, mkdirSync, existsSync } from 'fs';
|
|
2
|
+
import { join } from 'path';
|
|
3
|
+
import { homedir } from 'os';
|
|
4
|
+
const CACHE_DIR = join(homedir(), '.depstein');
|
|
5
|
+
const CACHE_FILE = join(CACHE_DIR, 'cache.json');
|
|
6
|
+
const CACHE_TTL_MS = 60 * 60 * 1000; // 1 hour
|
|
7
|
+
class DiskCache {
|
|
8
|
+
store = {};
|
|
9
|
+
dirty = false;
|
|
10
|
+
constructor() {
|
|
11
|
+
this.load();
|
|
12
|
+
}
|
|
13
|
+
load() {
|
|
14
|
+
try {
|
|
15
|
+
if (existsSync(CACHE_FILE)) {
|
|
16
|
+
const raw = readFileSync(CACHE_FILE, 'utf-8');
|
|
17
|
+
this.store = JSON.parse(raw);
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
catch {
|
|
21
|
+
this.store = {};
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
save() {
|
|
25
|
+
try {
|
|
26
|
+
mkdirSync(CACHE_DIR, { recursive: true });
|
|
27
|
+
writeFileSync(CACHE_FILE, JSON.stringify(this.store, null, 2), 'utf-8');
|
|
28
|
+
this.dirty = false;
|
|
29
|
+
}
|
|
30
|
+
catch {
|
|
31
|
+
// Silently fail — cache writes are best-effort
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
get(ecosystem, name) {
|
|
35
|
+
const key = `${ecosystem}:${name}`;
|
|
36
|
+
const entry = this.store[key];
|
|
37
|
+
if (!entry)
|
|
38
|
+
return null;
|
|
39
|
+
if (Date.now() - entry.timestamp > CACHE_TTL_MS) {
|
|
40
|
+
delete this.store[key];
|
|
41
|
+
this.dirty = true;
|
|
42
|
+
return null;
|
|
43
|
+
}
|
|
44
|
+
const cached = entry.data;
|
|
45
|
+
return {
|
|
46
|
+
...cached,
|
|
47
|
+
publishedAt: cached.publishedAt ? new Date(cached.publishedAt) : null,
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
set(ecosystem, name, data) {
|
|
51
|
+
const key = `${ecosystem}:${name}`;
|
|
52
|
+
this.store[key] = {
|
|
53
|
+
data: {
|
|
54
|
+
...data,
|
|
55
|
+
// Serialize Date to ISO string for JSON storage
|
|
56
|
+
publishedAt: data.publishedAt ? data.publishedAt.toISOString() : null,
|
|
57
|
+
},
|
|
58
|
+
timestamp: Date.now(),
|
|
59
|
+
};
|
|
60
|
+
this.save();
|
|
61
|
+
}
|
|
62
|
+
clear() {
|
|
63
|
+
this.store = {};
|
|
64
|
+
this.save();
|
|
65
|
+
}
|
|
66
|
+
/** Flush dirty writes at process exit */
|
|
67
|
+
flush() {
|
|
68
|
+
if (this.dirty)
|
|
69
|
+
this.save();
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
export const cache = new DiskCache();
|
|
73
|
+
// Ensure final flush on clean exit
|
|
74
|
+
process.on('exit', () => cache.flush());
|
|
75
|
+
//# sourceMappingURL=cache.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cache.js","sourceRoot":"","sources":["../../src/core/cache.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import type { RawDependency, PackageMetadata } from './types.js';
|
|
2
|
+
export type ProgressCallback = (done: number, total: number, current: string) => void;
|
|
3
|
+
/**
|
|
4
|
+
* Fetch metadata for all dependencies in parallel batches.
|
|
5
|
+
* Never throws — individual failures are captured in metadata.fetchError.
|
|
6
|
+
*/
|
|
7
|
+
export declare function fetchAll(deps: RawDependency[], noCache: boolean, onProgress?: ProgressCallback): Promise<PackageMetadata[]>;
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import { fetchNpm } from '../ecosystems/npm.js';
|
|
2
|
+
import { fetchPypi } from '../ecosystems/pypi.js';
|
|
3
|
+
import { fetchCargo } from '../ecosystems/cargo.js';
|
|
4
|
+
import { fetchGolang } from '../ecosystems/golang.js';
|
|
5
|
+
const BATCH_SIZE = 10;
|
|
6
|
+
async function fetchOne(dep, noCache) {
|
|
7
|
+
switch (dep.ecosystem) {
|
|
8
|
+
case 'npm': return fetchNpm(dep.name, dep.version, noCache);
|
|
9
|
+
case 'pypi': return fetchPypi(dep.name, dep.version, noCache);
|
|
10
|
+
case 'cargo': return fetchCargo(dep.name, dep.version, noCache);
|
|
11
|
+
case 'golang': return fetchGolang(dep.name, dep.version, noCache);
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* Fetch metadata for all dependencies in parallel batches.
|
|
16
|
+
* Never throws — individual failures are captured in metadata.fetchError.
|
|
17
|
+
*/
|
|
18
|
+
export async function fetchAll(deps, noCache, onProgress) {
|
|
19
|
+
let done = 0;
|
|
20
|
+
const results = new Array(deps.length);
|
|
21
|
+
for (let i = 0; i < deps.length; i += BATCH_SIZE) {
|
|
22
|
+
const batch = deps.slice(i, i + BATCH_SIZE);
|
|
23
|
+
await Promise.all(batch.map(async (dep, batchIdx) => {
|
|
24
|
+
const globalIdx = i + batchIdx;
|
|
25
|
+
onProgress?.(done, deps.length, dep.name);
|
|
26
|
+
try {
|
|
27
|
+
results[globalIdx] = await fetchOne(dep, noCache);
|
|
28
|
+
}
|
|
29
|
+
catch (err) {
|
|
30
|
+
// Defensive fallback — fetchOne should never throw, but just in case
|
|
31
|
+
results[globalIdx] = {
|
|
32
|
+
name: dep.name,
|
|
33
|
+
ecosystem: dep.ecosystem,
|
|
34
|
+
latestVersion: dep.version,
|
|
35
|
+
requestedVersion: dep.version,
|
|
36
|
+
publishedAt: null,
|
|
37
|
+
description: '',
|
|
38
|
+
license: 'Unknown',
|
|
39
|
+
repositoryUrl: null,
|
|
40
|
+
weeklyDownloads: null,
|
|
41
|
+
monthlyDownloads: null,
|
|
42
|
+
totalDownloads: null,
|
|
43
|
+
unpackedSize: null,
|
|
44
|
+
gzipSize: null,
|
|
45
|
+
minifiedSize: null,
|
|
46
|
+
hasBuiltinTypes: false,
|
|
47
|
+
hasTypesPackage: false,
|
|
48
|
+
vulnerabilities: [],
|
|
49
|
+
fromCache: false,
|
|
50
|
+
fetchError: err.message,
|
|
51
|
+
};
|
|
52
|
+
}
|
|
53
|
+
done++;
|
|
54
|
+
onProgress?.(done, deps.length, dep.name);
|
|
55
|
+
}));
|
|
56
|
+
}
|
|
57
|
+
return results;
|
|
58
|
+
}
|
|
59
|
+
//# sourceMappingURL=fetcher.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"fetcher.js","sourceRoot":"","sources":["../../src/core/fetcher.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Curated replacement suggestions for common problematic npm packages.
|
|
3
|
+
*
|
|
4
|
+
* savesKbGzip: approximate bundle size reduction in KB (gzip) vs the original.
|
|
5
|
+
* null = can't be meaningfully compared (CLI-only tool, Node built-in, etc.)
|
|
6
|
+
* approxScore: rough health score for the alternative (used for display only).
|
|
7
|
+
* replacement: actual npm package name for --fix. null for native JS alternatives.
|
|
8
|
+
*/
|
|
9
|
+
export interface PackageAlternative {
|
|
10
|
+
name: string | null;
|
|
11
|
+
label: string;
|
|
12
|
+
replacement: string | null;
|
|
13
|
+
reason: string;
|
|
14
|
+
savesKbGzip: number | null;
|
|
15
|
+
approxScore: number;
|
|
16
|
+
}
|
|
17
|
+
export interface ReplacementEntry {
|
|
18
|
+
alternatives: PackageAlternative[];
|
|
19
|
+
}
|
|
20
|
+
export declare const REPLACEMENTS: Record<string, ReplacementEntry>;
|
|
21
|
+
/**
|
|
22
|
+
* Returns the replacement info for a package, or null if none is known.
|
|
23
|
+
*/
|
|
24
|
+
export declare function getReplacements(name: string): PackageAlternative[] | null;
|
|
25
|
+
/**
|
|
26
|
+
* Returns the label of the first/best replacement (for summary display).
|
|
27
|
+
*/
|
|
28
|
+
export declare function getSuggestedLabel(name: string): string | null;
|
|
29
|
+
/**
|
|
30
|
+
* Returns the npm package name for the best replacement (for --fix).
|
|
31
|
+
*/
|
|
32
|
+
export declare function getBestReplacementPackage(name: string): string | null;
|