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.
Files changed (65) hide show
  1. package/README.md +438 -0
  2. package/dist/core/cache.d.ts +15 -0
  3. package/dist/core/cache.js +75 -0
  4. package/dist/core/cache.js.map +1 -0
  5. package/dist/core/fetcher.d.ts +7 -0
  6. package/dist/core/fetcher.js +59 -0
  7. package/dist/core/fetcher.js.map +1 -0
  8. package/dist/core/replacements.d.ts +32 -0
  9. package/dist/core/replacements.js +419 -0
  10. package/dist/core/replacements.js.map +1 -0
  11. package/dist/core/scanner.d.ts +7 -0
  12. package/dist/core/scanner.js +298 -0
  13. package/dist/core/scanner.js.map +1 -0
  14. package/dist/core/scorer.d.ts +5 -0
  15. package/dist/core/scorer.js +289 -0
  16. package/dist/core/scorer.js.map +1 -0
  17. package/dist/core/types.d.ts +99 -0
  18. package/dist/core/types.js +2 -0
  19. package/dist/core/types.js.map +1 -0
  20. package/dist/core/vulnerabilities.d.ts +5 -0
  21. package/dist/core/vulnerabilities.js +93 -0
  22. package/dist/core/vulnerabilities.js.map +1 -0
  23. package/dist/ecosystems/cargo.d.ts +2 -0
  24. package/dist/ecosystems/cargo.js +74 -0
  25. package/dist/ecosystems/cargo.js.map +1 -0
  26. package/dist/ecosystems/golang.d.ts +2 -0
  27. package/dist/ecosystems/golang.js +90 -0
  28. package/dist/ecosystems/golang.js.map +1 -0
  29. package/dist/ecosystems/npm.d.ts +2 -0
  30. package/dist/ecosystems/npm.js +92 -0
  31. package/dist/ecosystems/npm.js.map +1 -0
  32. package/dist/ecosystems/pypi.d.ts +2 -0
  33. package/dist/ecosystems/pypi.js +80 -0
  34. package/dist/ecosystems/pypi.js.map +1 -0
  35. package/dist/index.d.ts +2 -0
  36. package/dist/index.js +303 -0
  37. package/dist/index.js.map +1 -0
  38. package/dist/tui/colors.d.ts +17 -0
  39. package/dist/tui/colors.js +86 -0
  40. package/dist/tui/colors.js.map +1 -0
  41. package/dist/tui/dashboard.d.ts +7 -0
  42. package/dist/tui/dashboard.js +236 -0
  43. package/dist/tui/dashboard.js.map +1 -0
  44. package/dist/tui/detail.d.ts +6 -0
  45. package/dist/tui/detail.js +68 -0
  46. package/dist/tui/detail.js.map +1 -0
  47. package/dist/tui/picker.d.ts +7 -0
  48. package/dist/tui/picker.js +62 -0
  49. package/dist/tui/picker.js.map +1 -0
  50. package/dist/tui/summary.d.ts +6 -0
  51. package/dist/tui/summary.js +13 -0
  52. package/dist/tui/summary.js.map +1 -0
  53. package/dist/tui/table.d.ts +9 -0
  54. package/dist/tui/table.js +50 -0
  55. package/dist/tui/table.js.map +1 -0
  56. package/dist/utils/format.d.ts +9 -0
  57. package/dist/utils/format.js +72 -0
  58. package/dist/utils/format.js.map +1 -0
  59. package/dist/utils/http.d.ts +2 -0
  60. package/dist/utils/http.js +53 -0
  61. package/dist/utils/http.js.map +1 -0
  62. package/dist/utils/spinner.d.ts +11 -0
  63. package/dist/utils/spinner.js +40 -0
  64. package/dist/utils/spinner.js.map +1 -0
  65. 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;