depshred 1.0.0 → 1.0.2

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 CHANGED
@@ -1,201 +1,238 @@
1
- <p align="center">
2
- <img src="assets/depshred.webp" width="200" alt="depshred logo">
3
- </p>
4
-
5
- <h1 align="center">depshred</h1>
6
-
7
- <p align="center">
8
- <em>Clean up your node_modules. Stop installing stuff Node already has.</em>
9
- </p>
10
-
11
- <p align="center">
12
- <img src="https://img.shields.io/badge/run_with-npx_depshred-111111?style=flat-square" alt="Run with npx">
13
- <img src="https://img.shields.io/badge/node.js-18%2B_%7C_20%2B_%7C_22%2B-111111?style=flat-square" alt="Node Support">
14
- <img src="https://img.shields.io/badge/version-1.0.0-111111?style=flat-square" alt="version 1.0.0">
15
- <img src="https://img.shields.io/badge/license-MIT-111111?style=flat-square" alt="MIT license">
16
- </p>
17
-
18
- ---
19
-
20
- Ever look at your `node_modules` folder and wonder why it takes up half your hard drive?
21
-
22
- When you follow tutorials or ask AI to write code, it is super easy to end up with 30 different packages installed for things modern Node can already do by itself. Stuff like `dotenv`, `uuid`, or `rimraf` do not need to be in your project anymore. Node already knows how to do all of that.
23
-
24
- `depshred` looks through your project, finds packages you do not actually need, and shows you how to swap them for simple code that comes with Node.
25
-
26
- ---
27
-
28
- ## Quick start
29
-
30
- You do not even need to install anything first. Just open your terminal inside your project folder and run:
31
-
32
- ```bash
33
- npx depshred
34
- ```
35
-
36
- If your project lives in another folder, just tell it where:
37
-
38
- ```bash
39
- npx depshred ./path/to/your-project
40
- ```
41
-
42
- Using pnpm or bun instead of npm? That works too:
43
-
44
- ```bash
45
- pnpm dlx depshred
46
- bunx depshred
47
- ```
48
-
49
- ---
50
-
51
- ## What it looks like
52
-
53
- Here is what you see when you run it in your terminal:
54
-
55
- <p align="center">
56
- <img src="assets/view01.webp" width="100%" alt="depshred terminal interface">
57
- </p>
58
-
59
- You get an easy list you can move through with your arrow keys:
60
-
61
- ```text
62
- +--------------------------------------------------------------------------+
63
- | depshred v1.0.0 (Node v20.12.0) |
64
- | Target: ./my-project |
65
- +--------------------------------------------------------------------------+
66
- | Total Scanned: 24 dependencies |
67
- | |
68
- | [CRITICAL] 2 packages have native Node.js replacements (saves ~182 KB) |
69
- | [UNUSED] 1 package has 0 imports (run: npm uninstall rimraf) |
70
- | [WARN] 1 package is dormant >3y (0 sub-deps, self-contained) |
71
- | [OK] 20 packages are lean and actively maintained |
72
- +--------------------------------------------------------------------------+
73
-
74
- Select a dependency to inspect (Use arrow keys, Enter to inspect):
75
- > [CRITICAL] dotenv Native Node.js built-in available (saves ~72 KB)
76
- [CRITICAL] uuid Native Node.js built-in available (saves ~110 KB)
77
- [UNUSED] rimraf 0 imports found in project source code
78
- [WARN] ms Dormant >3 years | 0 sub-dependencies (self-contained)
79
- [OK] fastify Active, schema-compiled and lean
80
- ```
81
-
82
- ---
83
-
84
- ## What it checks for
85
-
86
- When you run `depshred`, it checks three main things:
87
-
88
- 1. **Stuff Node already has built in**
89
- Do you really need `dotenv` when Node can load `.env` files on its own? Do you need `uuid` when Node has `crypto.randomUUID()`? `depshred` finds every file and exact line number where you use these, and shows you the quick built-in way to replace them.
90
-
91
- 2. **Packages you installed but never use**
92
- Ever ran `npm install` for a test or a guide, forgot about it, and left it sitting in your `package.json`? `depshred` scans your whole project. If you never imported it anywhere, it tells you straight up: `npm uninstall <package>`.
93
-
94
- 3. **Old or abandoned packages**
95
- It checks npm to see how big each package is and whether anyone has touched it in the last 3 years. If a package is small, has zero other packages inside it, and just works, `depshred` lets you know so you do not have to worry.
96
-
97
- ```text
98
- Your package.json
99
- |
100
- +------------------+------------------+
101
- | | |
102
- v v v
103
- 1. Already in Node? 2. Used in code? 3. npm check
104
- (dotenv, uuid, etc.) (0 imports found) (file size, age)
105
- | | |
106
- v v v
107
- Use Node built-ins Safe to delete Good to know
108
- (saves disk space) (npm uninstall) (see how big it is)
109
- ```
110
-
111
- ---
112
-
113
- ## Extra options
114
-
115
- Need to run it inside a script or change how it works? Here are the flags you can use:
116
-
117
- | Flag | What it does |
118
- | :--- | :--- |
119
- | `--check` | Checks your project and stops with an error if it finds bad or useless packages. Great for GitHub Actions. |
120
- | `--strict` | Like `--check`, but also stops on small warnings. |
121
- | `--json` | Prints results as JSON so you can read them with code. |
122
- | `--no-interactive` | Skips the arrow-key menu and just prints a plain table. |
123
- | `--path <folder>` | Checks a different folder instead of where you are right now. |
124
- | `--dormant-years <num>` | How many years without updates before calling a package old (defaults to 3). |
125
- | `--ignore <names>` | Comma-separated names of packages you want to skip. |
126
- | `--init-skill [app]` | Adds instructions to your AI editor so it stops installing packages you do not need. |
127
- | `--help`, `-h` | Shows all available flags. |
128
- | `--version`, `-v` | Shows the version number. |
129
-
130
- ---
131
-
132
- ## Teach your AI editor to stop installing junk
133
-
134
- If you use AI coding tools like Cursor, Claude, Windsurf, or GitHub Copilot, you have probably seen them run `npm install` for every single little thing. Most of the time, Node already has what they are trying to download.
135
-
136
- You can teach your AI helper to check first before installing:
137
-
138
- ```bash
139
- # Pick your editor from a menu:
140
- npx depshred --init-skill
141
-
142
- # Or pick your editor directly:
143
- npx depshred --init-skill cursor # For Cursor (.cursor/rules)
144
- npx depshred --init-skill copilot # For GitHub Copilot (.github)
145
- npx depshred --init-skill windsurf # For Windsurf (.windsurf/rules)
146
- npx depshred --init-skill claude # For Claude Code (CLAUDE.md)
147
- npx depshred --init-skill cline # For Cline (.clinerules)
148
- npx depshred --init-skill antigravity # For Antigravity (.agents/skills)
149
- npx depshred --init-skill agents # For general agents (AGENTS.md)
150
- npx depshred --init-skill all # Sets up all of them at once
151
- ```
152
-
153
- Want to see all the skill files and how they work? Check out [SKILLS.md](SKILLS.md).
154
-
155
- ---
156
-
157
- ## Save your settings
158
-
159
- If there is a package you want `depshred` to leave alone, you can save your preferences right inside your `package.json`:
160
-
161
- ```json
162
- {
163
- "depshred": {
164
- "ignore": ["fast-deep-equal"],
165
- "dormantYears": 4
166
- }
167
- }
168
- ```
169
-
170
- ---
171
-
172
- ## What projects work with it?
173
-
174
- Pretty much any modern Node.js project:
175
-
176
- - **Any framework:** Express, Fastify, Next.js, NestJS, Koa, or plain Node scripts.
177
- - **Any module style:** Both `import` (ES Modules) and `require` (CommonJS).
178
- - **JavaScript and TypeScript:** `.js`, `.ts`, `.jsx`, `.tsx`, `.mjs`, `.cjs`.
179
- - **Projects with multiple folders:** Works with monorepos using the `--path` flag.
180
-
181
- ---
182
-
183
- ## Questions you might have
184
-
185
- **Does it change my code automatically?**
186
- No. `depshred` never touches your code behind your back. It shows you the file, the line number, and the built-in Node replacement. You decide what to change and when.
187
-
188
- **Does it need a database or send my code to some server?**
189
- No. Everything runs right on your computer. It only talks to the public npm website to check package sizes and update dates. It saves that info in your computer's temporary folder for a day so it stays fast.
190
-
191
- **What if an old package works fine and does not need updates?**
192
- Not every old package is broken. Some small tools are simply finished and do not need changes. `depshred` checks if the package has zero sub-packages. If it is small and self-contained, it gives you a soft heads-up, not a red error. You can also add it to your ignore list.
193
-
194
- **Does it work with Python or Go?**
195
- No, this is built only for Node.js and npm projects.
196
-
197
- ---
198
-
199
- ## License
200
-
1
+ <p align="center">
2
+ <img src="https://raw.githubusercontent.com/gozajpeg/depshred/master/assets/depshred.webp" width="200" alt="depshred logo">
3
+ </p>
4
+
5
+ <h1 align="center">depshred</h1>
6
+
7
+ <p align="center">
8
+ <em>Clean up your node_modules. Stop installing stuff Node already has.</em>
9
+ </p>
10
+
11
+ <p align="center">
12
+ <img src="https://img.shields.io/badge/run_with-npx_depshred-111111?style=flat-square" alt="Run with npx">
13
+ <img src="https://img.shields.io/badge/node.js-18%2B_%7C_20%2B_%7C_22%2B-111111?style=flat-square" alt="Node Support">
14
+ <img src="https://img.shields.io/badge/version-1.0.2-111111?style=flat-square" alt="version 1.0.0">
15
+ <img src="https://img.shields.io/badge/license-MIT-111111?style=flat-square" alt="MIT license">
16
+ </p>
17
+
18
+ ---
19
+
20
+ Ever look at your `node_modules` folder and wonder why it takes up half your hard drive?
21
+
22
+ When you follow tutorials or ask AI to write code, it is super easy to end up with 30 different packages installed for things modern Node can already do by itself. Stuff like `dotenv`, `uuid`, or `rimraf` do not need to be in your project anymore. Node already knows how to do all of that.
23
+
24
+ `depshred` looks through your project, finds packages you do not actually need, and shows you how to swap them for simple code that comes with Node.
25
+
26
+ ---
27
+
28
+ ## Quick start
29
+
30
+ You do not even need to install anything first. Just open your terminal inside your project folder and run:
31
+
32
+ ```bash
33
+ npx depshred
34
+ ```
35
+
36
+ If your project lives in another folder, just tell it where:
37
+
38
+ ```bash
39
+ npx depshred ./path/to/your-project
40
+ ```
41
+
42
+ Using pnpm or bun instead of npm? That works too:
43
+
44
+ ```bash
45
+ pnpm dlx depshred
46
+ bunx depshred
47
+ ```
48
+
49
+ ---
50
+
51
+ ## What it looks like
52
+
53
+ Here is what you see when you run it in your terminal:
54
+
55
+ <p align="center">
56
+ <img src="https://raw.githubusercontent.com/gozajpeg/depshred/master/assets/view01.webp" width="100%" alt="depshred terminal interface">
57
+ </p>
58
+
59
+ You get an easy list you can move through with your arrow keys:
60
+
61
+ ```text
62
+ +--------------------------------------------------------------------------+
63
+ | depshred v1.0.2 (Node v20.12.0) |
64
+ | Target: ./my-project |
65
+ +--------------------------------------------------------------------------+
66
+ | Total Scanned: 24 dependencies |
67
+ | |
68
+ | [CRITICAL] 2 packages have native Node.js replacements (saves ~182 KB) |
69
+ | [UNUSED] 1 package has 0 imports (run: npm uninstall rimraf) |
70
+ | [WARN] 1 package is dormant >3y (0 sub-deps, self-contained) |
71
+ | [OK] 20 packages are lean and actively maintained |
72
+ +--------------------------------------------------------------------------+
73
+
74
+ Select a dependency to inspect (Use arrow keys, Enter to inspect):
75
+ > [CRITICAL] dotenv Native Node.js built-in available (saves ~72 KB)
76
+ [CRITICAL] uuid Native Node.js built-in available (saves ~110 KB)
77
+ [UNUSED] rimraf 0 imports found in project source code
78
+ [WARN] ms Dormant >3 years | 0 sub-dependencies (self-contained)
79
+ [OK] fastify Active, schema-compiled and lean
80
+ ```
81
+
82
+ ---
83
+
84
+ ## What it checks for
85
+
86
+ When you run `depshred`, it checks three main things:
87
+
88
+ 1. **Stuff Node already has built in**
89
+ Do you really need `dotenv` when Node can load `.env` files on its own? Do you need `uuid` when Node has `crypto.randomUUID()`? `depshred` finds every file and exact line number where you use these, and shows you the quick built-in way to replace them.
90
+
91
+ 2. **Packages you installed but never use**
92
+ Ever ran `npm install` for a test or a guide, forgot about it, and left it sitting in your `package.json`? `depshred` scans your whole project. If you never imported it anywhere, it tells you straight up: `npm uninstall <package>`.
93
+
94
+ 3. **Old or abandoned packages**
95
+ It checks npm to see how big each package is and whether anyone has touched it in the last 3 years. If a package is small, has zero other packages inside it, and just works, `depshred` lets you know so you do not have to worry.
96
+
97
+ ```text
98
+ Your package.json
99
+ |
100
+ +------------------+------------------+
101
+ | | |
102
+ v v v
103
+ 1. Already in Node? 2. Used in code? 3. npm check
104
+ (dotenv, uuid, etc.) (0 imports found) (file size, age)
105
+ | | |
106
+ v v v
107
+ Use Node built-ins Safe to delete Good to know
108
+ (saves disk space) (npm uninstall) (see how big it is)
109
+ ```
110
+
111
+
112
+ ---
113
+
114
+ ## What packages can depshred replace?
115
+
116
+ To be completely honest: no tool can replace every single package in the world. Node does not have a built-in 3D engine to replace `three.js` or a built-in database to replace `pg`.
117
+
118
+ Instead, `depshred` targets the **most common sources of accidental bloat**: legacy packages that modern Node already includes for free, and heavy monolithic libraries with modern lightweight replacements.
119
+
120
+ ### 1. Modern Node.js built-ins (Zero packages needed)
121
+
122
+ | Legacy Package | Built-in Node Feature | Supported in | What replaces it |
123
+ | :--- | :--- | :--- | :--- |
124
+ | **`dotenv`** | `--env-file` | Node 20.6+ | `node --env-file=.env app.js` |
125
+ | **`uuid`** | `crypto.randomUUID()` | Node 14.17+ / 16.7+ | `crypto.randomUUID()` |
126
+ | **`rimraf`** | `fs.rmSync()` / `fs.rm()` | Node 14.14+ | `fs.rmSync(path, { recursive: true, force: true })` |
127
+ | **`mkdirp`** | `fs.mkdirSync()` / `fs.mkdir()` | Node 10.12+ | `fs.mkdirSync(path, { recursive: true })` |
128
+ | **`nodemon`** | `node --watch` | Node 18.11+ | `node --watch app.js` |
129
+ | **`axios`** | `fetch()` | Node 18+ | Native `fetch` and `AbortController` |
130
+ | **`node-fetch`** | `fetch()` | Node 18+ | Native `fetch` and `AbortController` |
131
+ | **`glob`** | `fs.glob()` | Node 22+ | Native file matching in `node:fs` |
132
+ | **`querystring`** | `URLSearchParams` | Node 10+ | Built-in `URLSearchParams` |
133
+
134
+ ### 2. Lightweight modern alternatives
135
+
136
+ | Heavy Package | Suggested Alternative | What you save |
137
+ | :--- | :--- | :--- |
138
+ | **`zod`** | `valibot` or `arktype` | Saves ~5.5 MB unpacked (~90% bundle size reduction) |
139
+ | **`moment`** | `dayjs` or native `Intl` | Saves ~4 MB unpacked |
140
+ | **`lodash`** | `es-toolkit` or modern JS | Saves ~1.5 MB unpacked |
141
+ | **`chalk`** | `picocolors` | Saves ~100 KB and drops dependency chain |
142
+ | **`classnames`** | `clsx` | Drop-in API, faster, zero dependencies |
143
+ | **`bcrypt`** | `bcryptjs` or native `crypto` | Removes heavy C++ `node-gyp` build tools |
144
+ | **`deep-equal`** | `fast-deep-equal` | Sub-1KB, faster comparisons |
145
+
146
+ > *Everything else in your project is still scanned for 0-import unused packages, heavy sizes (>5MB), and unmaintained/abandoned code (>3 years old).*
147
+
148
+ ---
149
+
150
+ ## Extra options
151
+
152
+ Need to run it inside a script or change how it works? Here are the flags you can use:
153
+
154
+ | Flag | What it does |
155
+ | :--- | :--- |
156
+ | `--check` | Checks your project and stops with an error if it finds bad or useless packages. Great for GitHub Actions. |
157
+ | `--strict` | Like `--check`, but also stops on small warnings. |
158
+ | `--json` | Prints results as JSON so you can read them with code. |
159
+ | `--no-interactive` | Skips the arrow-key menu and just prints a plain table. |
160
+ | `--path <folder>` | Checks a different folder instead of where you are right now. |
161
+ | `--dormant-years <num>` | How many years without updates before calling a package old (defaults to 3). |
162
+ | `--ignore <names>` | Comma-separated names of packages you want to skip. |
163
+ | `--init-skill [app]` | Adds instructions to your AI editor so it stops installing packages you do not need. |
164
+ | `--help`, `-h` | Shows all available flags. |
165
+ | `--version`, `-v` | Shows the version number. |
166
+
167
+ ---
168
+
169
+ ## Teach your AI editor to stop installing junk
170
+
171
+ If you use AI coding tools like Cursor, Claude, Windsurf, or GitHub Copilot, you have probably seen them run `npm install` for every single little thing. Most of the time, Node already has what they are trying to download.
172
+
173
+ You can teach your AI helper to check first before installing:
174
+
175
+ ```bash
176
+ # Pick your editor from a menu:
177
+ npx depshred --init-skill
178
+
179
+ # Or pick your editor directly:
180
+ npx depshred --init-skill cursor # For Cursor (.cursor/rules)
181
+ npx depshred --init-skill copilot # For GitHub Copilot (.github)
182
+ npx depshred --init-skill windsurf # For Windsurf (.windsurf/rules)
183
+ npx depshred --init-skill claude # For Claude Code (CLAUDE.md)
184
+ npx depshred --init-skill cline # For Cline (.clinerules)
185
+ npx depshred --init-skill antigravity # For Antigravity (.agents/skills)
186
+ npx depshred --init-skill agents # For general agents (AGENTS.md)
187
+ npx depshred --init-skill all # Sets up all of them at once
188
+ ```
189
+
190
+ Want to see all the skill files and how they work? Check out [SKILLS.md](SKILLS.md).
191
+
192
+ ---
193
+
194
+ ## Save your settings
195
+
196
+ If there is a package you want `depshred` to leave alone, you can save your preferences right inside your `package.json`:
197
+
198
+ ```json
199
+ {
200
+ "depshred": {
201
+ "ignore": ["fast-deep-equal"],
202
+ "dormantYears": 4
203
+ }
204
+ }
205
+ ```
206
+
207
+ ---
208
+
209
+ ## What projects work with it?
210
+
211
+ Pretty much any modern Node.js project:
212
+
213
+ - **Any framework:** Express, Fastify, Next.js, NestJS, Koa, or plain Node scripts.
214
+ - **Any module style:** Both `import` (ES Modules) and `require` (CommonJS).
215
+ - **JavaScript and TypeScript:** `.js`, `.ts`, `.jsx`, `.tsx`, `.mjs`, `.cjs`.
216
+ - **Projects with multiple folders:** Works with monorepos using the `--path` flag.
217
+
218
+ ---
219
+
220
+ ## Questions you might have
221
+
222
+ **Does it change my code automatically?**
223
+ No. `depshred` never touches your code behind your back. It shows you the file, the line number, and the built-in Node replacement. You decide what to change and when.
224
+
225
+ **Does it need a database or send my code to some server?**
226
+ No. Everything runs right on your computer. It only talks to the public npm website to check package sizes and update dates. It saves that info in your computer's temporary folder for a day so it stays fast.
227
+
228
+ **What if an old package works fine and does not need updates?**
229
+ Not every old package is broken. Some small tools are simply finished and do not need changes. `depshred` checks if the package has zero sub-packages. If it is small and self-contained, it gives you a soft heads-up, not a red error. You can also add it to your ignore list.
230
+
231
+ **Does it work with Python or Go?**
232
+ No, this is built only for Node.js and npm projects.
233
+
234
+ ---
235
+
236
+ ## License
237
+
201
238
  [MIT](LICENSE).
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "depshred",
3
- "version": "1.0.0",
3
+ "version": "1.0.2",
4
4
  "description": "Shred dependency bloat. Embrace modern Node.",
5
5
  "type": "module",
6
6
  "main": "./src/cli.js",
package/src/cli.js CHANGED
@@ -33,7 +33,7 @@ ${pc.bold('EXAMPLES:')}
33
33
  npx depshred
34
34
  npx depshred --check
35
35
  npx depshred --init-skill cursor
36
- npx depshred ./packages/api --json
36
+ npx depshred ./path/to/your-project --json
37
37
  `;
38
38
 
39
39
  export async function run(rawArgs = []) {
@@ -77,7 +77,7 @@ export async function run(rawArgs = []) {
77
77
  }
78
78
 
79
79
  if (values.version) {
80
- console.log('depshred v1.0.0');
80
+ console.log('depshred v1.0.2');
81
81
  return;
82
82
  }
83
83
 
@@ -29,7 +29,7 @@ export async function runInteractive(auditData) {
29
29
  process.stdout.write('\x1b[2J\x1b[3J\x1b[H');
30
30
 
31
31
  console.log(pc.bold(pc.white('+--------------------------------------------------------------------------+')));
32
- console.log(pc.bold(pc.white(`| depshred v1.0.0 (Target Node: ${targetNode})`)));
32
+ console.log(pc.bold(pc.white(`| depshred v1.0.2 (Target Node: ${targetNode})`)));
33
33
  console.log(pc.bold(pc.white(`| Project: ${projectName} (${totalDependencies} dependencies scanned)`)));
34
34
  console.log(pc.bold(pc.white('+--------------------------------------------------------------------------+')));
35
35
  console.log(pc.red(`| [CRITICAL] ${summary.critical} native replacements (saves ~${formatBytes(totalSaveableBytes)})`));
@@ -41,27 +41,70 @@ export async function runInteractive(auditData) {
41
41
 
42
42
  if (inspecting) {
43
43
  const item = results[selectedIndex];
44
- console.log(pc.bold(`=== INSPECTING: ${item.name} (${item.tier}) ===`));
45
- console.log(`Details: ${item.details}`);
46
- console.log(`Unpacked Size: ${formatBytes(item.size)}`);
47
- console.log(`Sub-dependencies: ${item.dependenciesCount}`);
44
+ console.log(pc.bold(pc.white('+--------------------------------------------------------------------------+')));
45
+ console.log(pc.bold(pc.white(`| INSPECTING: ${item.name} (${item.tier})`)));
46
+ console.log(pc.bold(pc.white('+--------------------------------------------------------------------------+')));
47
+ console.log(`Summary: ${item.details}`);
48
+ console.log(`Unpacked Size: ${formatBytes(item.size)} | Sub-dependencies: ${item.dependenciesCount}`);
49
+ console.log(`Type: ${item.isDev ? 'Development dependency (devDependencies)' : 'Production dependency (dependencies)'}`);
48
50
  console.log('');
49
51
 
52
+ // Critical items: show Before/After diff and uninstall command
53
+ if (item.tier === 'CRITICAL') {
54
+ if (item.diff) {
55
+ console.log(pc.bold('Replacement Solution (Modern Node.js Built-in):'));
56
+ console.log(pc.green(item.diff));
57
+ console.log('');
58
+ }
59
+ console.log(pc.bold('Action:'));
60
+ console.log(pc.cyan(` npm uninstall ${item.name}`));
61
+ console.log(pc.dim(` Saves ~${formatBytes(item.size)} unpacked disk space.`));
62
+ console.log('');
63
+ }
50
64
 
65
+ // Unused items: show clear uninstall command and ignore instructions
66
+ if (item.tier === 'UNUSED') {
67
+ console.log(pc.bold('Recommended Action:'));
68
+ console.log(pc.cyan(` npm uninstall ${item.name}`));
69
+ console.log(pc.dim(` Zero imports or config references found in project.`));
70
+ console.log(pc.dim(` To permanently ignore, add to package.json: "depshred": { "ignore": ["${item.name}"] }`));
71
+ console.log('');
72
+ }
51
73
 
52
- if (item.imports && item.imports.length > 0) {
53
- console.log(pc.bold(`Imports found (${item.imports.length}):`));
54
- for (const imp of item.imports.slice(0, 5)) {
55
- console.log(pc.dim(` - ${imp.file}:${imp.line} -> ${imp.snippet}`));
74
+ // Warning items: show lighter alternatives or dormancy details
75
+ if (item.tier === 'WARN') {
76
+ if (item.altRule) {
77
+ console.log(pc.bold('Recommended Lighter Alternative:'));
78
+ console.log(` Suggested: ${pc.cyan(item.altRule.lighter)}`);
79
+ console.log(` Savings: ${pc.green(item.altRule.savings)}`);
80
+ console.log(` Details: ${item.altRule.description}`);
81
+ console.log('');
82
+ } else if (item.size > 5 * 1024 * 1024) {
83
+ console.log(pc.bold('Size Optimization Note:'));
84
+ console.log(pc.yellow(` Heavy package (${formatBytes(item.size)}). Check if dynamic import() or tree-shaking can reduce bundle weight.`));
85
+ console.log('');
56
86
  }
57
- if (item.imports.length > 5) {
58
- console.log(pc.dim(` ... and ${item.imports.length - 5} more`));
87
+
88
+ if (item.yearsDormant && item.yearsDormant >= 3) {
89
+ console.log(pc.bold('Maintenance Assessment:'));
90
+ if (item.selfContained) {
91
+ console.log(pc.dim(` Last update >${item.yearsDormant} years ago, but 0 sub-dependencies (self-contained and stable).`));
92
+ } else {
93
+ console.log(pc.yellow(` Last update >${item.yearsDormant} years ago with ${item.dependenciesCount} sub-dependencies.`));
94
+ }
95
+ console.log('');
59
96
  }
60
- console.log('');
61
97
  }
62
98
 
63
- if (item.tier === 'UNUSED') {
64
- console.log(pc.cyan(`Run: npm uninstall ${item.name}`));
99
+ // Show where imports or references were found
100
+ if (item.imports && item.imports.length > 0) {
101
+ console.log(pc.bold(`References in project (${item.imports.length}):`));
102
+ for (const imp of item.imports.slice(0, 8)) {
103
+ console.log(pc.dim(` - ${imp.file}:${imp.line} -> ${imp.snippet}`));
104
+ }
105
+ if (item.imports.length > 8) {
106
+ console.log(pc.dim(` ... and ${item.imports.length - 8} more references`));
107
+ }
65
108
  console.log('');
66
109
  }
67
110
 
@@ -136,4 +179,4 @@ export async function runInteractive(auditData) {
136
179
  process.stdin.resume();
137
180
  render();
138
181
  });
139
- }
182
+ }
package/src/locator.js CHANGED
@@ -1,7 +1,7 @@
1
1
  import { readdir, readFile, stat } from 'node:fs/promises';
2
- import { join, relative, extname } from 'node:path';
2
+ import { join, relative, extname, basename } from 'node:path';
3
3
 
4
- const SUPPORTED_EXTENSIONS = new Set(['.js', '.mjs', '.cjs', '.ts', '.tsx', '.jsx']);
4
+ const SUPPORTED_EXTENSIONS = new Set(['.js', '.mjs', '.cjs', '.ts', '.tsx', '.jsx', '.vue', '.svelte']);
5
5
  const IGNORED_DIRECTORIES = new Set([
6
6
  'node_modules',
7
7
  '.git',
@@ -17,6 +17,25 @@ const IGNORED_DIRECTORIES = new Set([
17
17
  '.windsurf'
18
18
  ]);
19
19
 
20
+ const CONFIG_FILE_PATTERNS = [
21
+ /^\.babelrc(\.json|\.js|\.cjs|\.mjs)?$/,
22
+ /^babel\.config(\.json|\.js|\.cjs|\.mjs)?$/,
23
+ /^\.eslintrc(\.json|\.js|\.cjs|\.yaml|\.yml)?$/,
24
+ /^eslint\.config(\.js|\.mjs|\.cjs)?$/,
25
+ /^\.prettierrc(\.json|\.js|\.cjs|\.yaml|\.yml)?$/,
26
+ /^prettier\.config(\.js|\.mjs|\.cjs)?$/,
27
+ /^tsconfig(\..+)?\.json$/,
28
+ /^jsconfig(\..+)?\.json$/,
29
+ /^postcss\.config(\.js|\.cjs|\.mjs|\.json)?$/,
30
+ /^tailwind\.config(\.js|\.cjs|\.mjs|\.ts)?$/,
31
+ /^vite\.config(\.js|\.ts|\.mjs)?$/,
32
+ /^webpack\.config(\.js|\.cjs|\.mjs|\.ts)?$/,
33
+ /^rollup\.config(\.js|\.mjs)?$/,
34
+ /^next\.config(\.js|\.mjs|\.ts)?$/,
35
+ /^jest\.config(\.js|\.ts|\.json)?$/,
36
+ /^vitest\.config(\.js|\.ts)?$/
37
+ ];
38
+
20
39
  /**
21
40
  * Normalizes an import specifier to its root npm package name.
22
41
  * e.g. "lodash/map" -> "lodash", "@scoped/pkg/deep" -> "@scoped/pkg"
@@ -80,10 +99,34 @@ async function getSourceFiles(dir) {
80
99
  }
81
100
 
82
101
  /**
83
- * Scan project source files to locate package imports and detect unused dependencies.
102
+ * Find root-level config files in a directory.
103
+ */
104
+ async function getRootConfigFiles(dir) {
105
+ const configFiles = [];
106
+ let entries;
107
+ try {
108
+ entries = await readdir(dir, { withFileTypes: true });
109
+ } catch {
110
+ return configFiles;
111
+ }
112
+
113
+ for (const entry of entries) {
114
+ if (entry.isFile()) {
115
+ const name = entry.name.toLowerCase();
116
+ if (CONFIG_FILE_PATTERNS.some((pat) => pat.test(name))) {
117
+ configFiles.push(entry.name);
118
+ }
119
+ }
120
+ }
121
+ return configFiles;
122
+ }
123
+
124
+ /**
125
+ * Scan project source files and config files to locate package imports and detect unused dependencies.
84
126
  *
85
127
  * @param {string} rootDir - Root directory of the project
86
128
  * @param {string[]} declaredDependencies - List of package names from package.json
129
+ * @param {object} scripts - package.json scripts object
87
130
  * @returns {Promise<{ importsMap: Map<string, Array<{file: string, line: number, snippet: string}>>, unusedPackages: string[], scannedFilesCount: number }>}
88
131
  */
89
132
  export async function locateImports(rootDir, declaredDependencies = [], scripts = {}) {
@@ -91,7 +134,142 @@ export async function locateImports(rootDir, declaredDependencies = [], scripts
91
134
  const importsMap = new Map();
92
135
  const importedSet = new Set();
93
136
 
94
- // Scan package.json scripts for CLI tool references (e.g. nodemon, rimraf)
137
+ // Rule 1: Always ignore depshred itself (never flag depshred as unused within a project)
138
+ if (declaredDependencies.includes('depshred')) {
139
+ importedSet.add('depshred');
140
+ importsMap.set('depshred', [{
141
+ file: 'package.json',
142
+ line: 1,
143
+ snippet: 'depshred CLI dependency auditor'
144
+ }]);
145
+ }
146
+
147
+ // Rule 2: Ambient TypeScript definitions (@types/*) are used by compiler, never imported in source code
148
+ for (const pkg of declaredDependencies) {
149
+ if (pkg.startsWith('@types/')) {
150
+ importedSet.add(pkg);
151
+ if (!importsMap.has(pkg)) importsMap.set(pkg, []);
152
+ importsMap.get(pkg).push({
153
+ file: 'tsconfig.json',
154
+ line: 1,
155
+ snippet: 'TypeScript ambient type definition'
156
+ });
157
+ }
158
+ }
159
+
160
+ // Rule 3: Bundler, linter, and build configuration plugins
161
+ for (const pkg of declaredDependencies) {
162
+ if (
163
+ pkg.startsWith('@vitejs/plugin-') ||
164
+ pkg.startsWith('vite-plugin-') ||
165
+ pkg.startsWith('@rollup/plugin-') ||
166
+ pkg.startsWith('rollup-plugin-') ||
167
+ pkg.startsWith('@rolldown/plugin-') ||
168
+ pkg.endsWith('-webpack-plugin') ||
169
+ pkg.startsWith('eslint-config-') ||
170
+ pkg.startsWith('eslint-plugin-') ||
171
+ pkg.startsWith('@eslint/') ||
172
+ pkg.startsWith('prettier-plugin-')
173
+ ) {
174
+ importedSet.add(pkg);
175
+ if (!importsMap.has(pkg)) importsMap.set(pkg, []);
176
+ importsMap.get(pkg).push({
177
+ file: 'package.json',
178
+ line: 1,
179
+ snippet: 'Bundler or linter build plugin'
180
+ });
181
+ }
182
+ }
183
+
184
+ // Rule 4: Compiler engines and toolchain infrastructure
185
+ // Babel toolchain detection: @babel/core, plugins, presets, react-compiler
186
+ const hasBabelTool = declaredDependencies.some((p) =>
187
+ p.includes('babel') || p === '@vitejs/plugin-react' || p.includes('react-compiler')
188
+ );
189
+ if (hasBabelTool) {
190
+ for (const pkg of declaredDependencies) {
191
+ if (
192
+ pkg === '@babel/core' ||
193
+ pkg === '@babel/runtime' ||
194
+ pkg.startsWith('babel-plugin-') ||
195
+ pkg.startsWith('@babel/plugin-') ||
196
+ pkg.startsWith('babel-preset-') ||
197
+ pkg.startsWith('@babel/preset-') ||
198
+ pkg.includes('plugin-babel')
199
+ ) {
200
+ importedSet.add(pkg);
201
+ if (!importsMap.has(pkg)) importsMap.set(pkg, []);
202
+ importsMap.get(pkg).push({
203
+ file: 'package.json',
204
+ line: 1,
205
+ snippet: 'Babel compiler infrastructure / plugin'
206
+ });
207
+ }
208
+ }
209
+ }
210
+
211
+ // PostCSS toolchain detection: postcss, autoprefixer, tailwindcss
212
+ const hasPostcssTool = declaredDependencies.some((p) =>
213
+ p.includes('postcss') || p === 'tailwindcss' || p === 'autoprefixer'
214
+ );
215
+ if (hasPostcssTool) {
216
+ for (const pkg of declaredDependencies) {
217
+ if (pkg === 'postcss' || pkg.startsWith('postcss-')) {
218
+ importedSet.add(pkg);
219
+ if (!importsMap.has(pkg)) importsMap.set(pkg, []);
220
+ importsMap.get(pkg).push({
221
+ file: 'package.json',
222
+ line: 1,
223
+ snippet: 'PostCSS compiler infrastructure / plugin'
224
+ });
225
+ }
226
+ }
227
+ }
228
+
229
+ // Rule 5: Scan root configuration files for package names and plugin/preset references
230
+ const rootConfigFiles = await getRootConfigFiles(rootDir);
231
+ for (const configName of rootConfigFiles) {
232
+ let content;
233
+ try {
234
+ content = await readFile(join(rootDir, configName), 'utf8');
235
+ } catch {
236
+ continue;
237
+ }
238
+
239
+ for (const pkg of declaredDependencies) {
240
+ if (importedSet.has(pkg)) continue;
241
+
242
+ // Exact package match (quoted or word boundary)
243
+ const escaped = pkg.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
244
+ const directRegex = new RegExp(`['"]${escaped}['"]|\\b${escaped}\\b`);
245
+
246
+ // Shorthand matches for common config tools
247
+ let shorthandMatch = false;
248
+ if (configName.includes('eslint')) {
249
+ const eslintPlugin = pkg.replace(/^eslint-plugin-/, '');
250
+ const eslintConfig = pkg.replace(/^eslint-config-/, '');
251
+ if (eslintPlugin !== pkg && new RegExp(`['"]${eslintPlugin}['"]`).test(content)) shorthandMatch = true;
252
+ if (eslintConfig !== pkg && new RegExp(`['"]${eslintConfig}['"]`).test(content)) shorthandMatch = true;
253
+ } else if (configName.includes('babel')) {
254
+ const babelPreset = pkg.replace(/^(@babel\/preset-|babel-preset-)/, '');
255
+ const babelPlugin = pkg.replace(/^(@babel\/plugin-|babel-plugin-)/, '');
256
+ if (babelPreset !== pkg && new RegExp(`['"]${babelPreset}['"]`).test(content)) shorthandMatch = true;
257
+ if (babelPlugin !== pkg && new RegExp(`['"]${babelPlugin}['"]`).test(content)) shorthandMatch = true;
258
+ }
259
+
260
+ if (directRegex.test(content) || shorthandMatch) {
261
+ importedSet.add(pkg);
262
+ if (!importsMap.has(pkg)) importsMap.set(pkg, []);
263
+ importsMap.get(pkg).push({
264
+ file: configName,
265
+ line: 1,
266
+ snippet: `Config reference in ${configName}`
267
+ });
268
+ }
269
+ }
270
+ }
271
+
272
+ // Rule 6: Scan package.json scripts for CLI tool references (e.g. nodemon, rimraf, tsc, prettier)
95
273
  for (const [scriptName, scriptCmd] of Object.entries(scripts)) {
96
274
  if (typeof scriptCmd !== 'string') continue;
97
275
  for (const pkg of declaredDependencies) {
@@ -111,6 +289,7 @@ export async function locateImports(rootDir, declaredDependencies = [], scripts
111
289
  }
112
290
  }
113
291
 
292
+ // Rule 7: Scan project source files for import / require / export from
114
293
  for (const filePath of sourceFiles) {
115
294
  let content;
116
295
  try {
@@ -124,7 +303,6 @@ export async function locateImports(rootDir, declaredDependencies = [], scripts
124
303
 
125
304
  for (let i = 0; i < lines.length; i++) {
126
305
  const line = lines[i];
127
- // Skip empty or comment-only lines
128
306
  const trimmed = line.trim();
129
307
  if (!trimmed || trimmed.startsWith('//') || trimmed.startsWith('/*') || trimmed.startsWith('*')) {
130
308
  continue;
@@ -152,7 +330,7 @@ export async function locateImports(rootDir, declaredDependencies = [], scripts
152
330
  }
153
331
  }
154
332
 
155
- // Detect unused packages: declared in package.json but never imported in source code
333
+ // Detect unused packages: declared in package.json but never referenced anywhere
156
334
  const unusedPackages = declaredDependencies.filter((pkg) => !importedSet.has(pkg));
157
335
 
158
336
  return {
@@ -160,4 +338,4 @@ export async function locateImports(rootDir, declaredDependencies = [], scripts
160
338
  unusedPackages,
161
339
  scannedFilesCount: sourceFiles.length
162
340
  };
163
- }
341
+ }
package/src/registry.js CHANGED
@@ -1,4 +1,4 @@
1
- import { readFile, writeFile } from 'node:fs/promises';
1
+ import { readFile, writeFile } from 'node:fs/promises';
2
2
  import { tmpdir } from 'node:os';
3
3
  import { join } from 'node:path';
4
4
 
@@ -66,7 +66,7 @@ export async function fetchPackageMetadata(packageName, timeoutMs = 4000) {
66
66
  signal: controller.signal,
67
67
  headers: {
68
68
  Accept: 'application/json',
69
- 'User-Agent': 'depshred-audit/1.0.0'
69
+ 'User-Agent': 'depshred-audit/1.0.2'
70
70
  }
71
71
  });
72
72
 
package/src/reporter.js CHANGED
@@ -9,7 +9,7 @@ export function printScorecard(auditData, options = {}) {
9
9
 
10
10
  console.log('');
11
11
  console.log(pc.bold(pc.white('+--------------------------------------------------------------------------+')));
12
- console.log(pc.bold(pc.white(`| depshred v1.0.0 (Target Node: ${targetNode})`)));
12
+ console.log(pc.bold(pc.white(`| depshred v1.0.2 (Target Node: ${targetNode})`)));
13
13
  console.log(pc.bold(pc.white(`| Project: ${projectName} (${totalDependencies} dependencies scanned)`)));
14
14
  console.log(pc.bold(pc.white('+--------------------------------------------------------------------------+')));
15
15
 
@@ -1,8 +1,14 @@
1
- /**
1
+ /**
2
2
  * Modern Lightweight Package Alternatives Catalog.
3
3
  * Objective replacements for heavy or legacy packages.
4
4
  */
5
5
  export const ALT_RULES = [
6
+ {
7
+ name: 'zod',
8
+ lighter: 'valibot',
9
+ description: 'Zod is ~5.9MB unpacked (~14KB gzipped). Replace with valibot (modular, sub-1KB per schema) or arktype for 10x smaller bundles.',
10
+ savings: '~5.5MB unpacked (~90% bundle reduction)'
11
+ },
6
12
  {
7
13
  name: 'moment',
8
14
  lighter: 'dayjs',
@@ -21,10 +27,22 @@ export const ALT_RULES = [
21
27
  description: 'Large dependency chain in v4/v5. Replace with picocolors (sub-7KB, 0 sub-dependencies).',
22
28
  savings: '~100KB'
23
29
  },
30
+ {
31
+ name: 'classnames',
32
+ lighter: 'clsx',
33
+ description: 'Legacy classnames utility. Replace with clsx (drop-in API, faster, zero dependencies).',
34
+ savings: '~50KB'
35
+ },
24
36
  {
25
37
  name: 'bcrypt',
26
38
  lighter: 'bcryptjs',
27
39
  description: 'Requires node-gyp C++ compilation toolchain. Replace with pure JS bcryptjs or native crypto.scrypt.',
28
40
  savings: 'Eliminates native build toolchain dependency'
41
+ },
42
+ {
43
+ name: 'deep-equal',
44
+ lighter: 'fast-deep-equal',
45
+ description: 'Legacy recursive comparison. Replace with fast-deep-equal (sub-1KB, faster) or native assert.deepStrictEqual.',
46
+ savings: '~80% faster and 0 sub-dependencies'
29
47
  }
30
- ];
48
+ ];