The Problem — node_modules Is a Disk Space Leak
A fresh npm install on a typical Node.js project pulls in anywhere from 50MB to 500MB of dependencies. That's fine for one project. But most developers have dozens of projects cloned on their machine — side projects, client work, open source contributions, tutorials they started and forgot about.
Those node_modules folders sit there indefinitely. You don't need them unless you're actively working on that project. But they're invisible — you don't see them in Finder or Explorer unless you go looking. Meanwhile they quietly consume gigabytes.
The manual approach is tedious: open each project folder, check if there's a node_modules, estimate the size, decide whether to delete it. Repeat for 40 projects. Nobody does this consistently.
So I built deepclean-cli — a single command that scans an entire directory tree, finds every matching folder, tells you exactly how much space each one is using, and removes them after a confirmation prompt.
Install and Run
Install it globally once and use the deepclean command anywhere:
npm install -g deepclean-cliOr run it without installing via npx:
npx deepclean-cli -d ~/codePoint it at your projects folder and run a dry-run first to see what it finds:
deepclean -d ~/code --dry-runOutput looks like this:
Scanning "/Users/zain/code" up to 3 level(s) deep for "node_modules"... (dry run)
Found: /Users/zain/code/project-alpha/node_modules (312.45 MB)
Found: /Users/zain/code/project-beta/node_modules (87.20 MB)
Found: /Users/zain/code/client/saas-app/node_modules (540.11 MB)
Found: /Users/zain/code/experiments/ai-chat/node_modules (203.78 MB)
4 folder(s) found, totaling 1.14 GB.
Dry run complete. Nothing was deleted.When you're ready to delete, run it without --dry-run. It will show you the same list and ask for confirmation before touching anything:
deepclean -d ~/code
# Output:
# Found: ... (lists all matches with sizes)
# 4 folder(s) found, totaling 1.14 GB.
# Delete 4 folder(s)? [y/N]All the Options
The tool is intentionally simple — five flags cover every use case:
deepclean --help
Options:
-d, --dir <path> Root directory to scan (required)
-t, --target-dir <name> Folder name to find/delete (default: "node_modules")
-l, --level <number> Max depth to scan (default: 3)
--dry-run List matches, delete nothing
-y, --yes Skip the confirmation prompt
-V, --version Print version
-h, --help Print usageClean dist and build folders too — it works on any folder name, not just node_modules:
# Clean up dist output folders across all projects
deepclean -d ~/code -t dist
# Clean up .cache folders, scan deeper
deepclean -d ~/code -t .cache -l 5Skip the confirmation in scripts — the -y flag is useful in automation:
# In a cron job or shell script — no interactive prompt
deepclean -d ~/code -yHow It Works Internally
The core logic is a recursive directory scanner in about 80 lines of plain Node.js — no dependencies beyond commander for CLI argument parsing.
The scanner walks the directory tree depth-first, up to the configured level. When it finds a folder matching the target name, it calculates the size using a recursive byte counter, records the match, and — critically — never descends into it. This is important: if you're scanning for node_modules, you don't want to waste time recursing into a folder you're about to delete anyway.
// The key design decision — stop descending once a match is found
if (entry.name === targetName) {
const size = getDirSize(fullPath);
stats.count++;
stats.bytes += size;
if (options.onMatch) options.onMatch(fullPath, size);
if (!options.dryRun) {
fs.rmSync(fullPath, { recursive: true, force: true });
}
continue; // don't recurse into the matched folder
}
// Only recurse into non-matching directories
scanDir(fullPath, targetName, maxLevel, depth + 1, options, stats);The CLI always runs a dry-run pass first — even when you haven't specified --dry-run. This means you always see the full list and total size before anything gets deleted. Then it asks for confirmation (unless you passed -y), and only then runs the actual deletion pass.
// Two-pass approach in the CLI
// Pass 1: always dry-run — show matches and total size
const preview = run({ dir, targetDir, level, dryRun: true, onMatch, onError });
if (preview.count === 0) { console.log("No matches found."); return; }
if (opts.dryRun) { console.log("Dry run complete."); return; }
// Pass 2: confirm then delete
const ok = opts.yes || await confirm(`Delete ${preview.count} folder(s)? [y/N] `);
if (!ok) { console.log("Aborted."); return; }
run({ dir, targetDir, level, dryRun: false, onMatch, onError });Deletion uses fs.rmSync with recursive: true — folders are permanently removed, not sent to the trash. Always run --dry-runfirst if you're unsure what will be matched.
Why I Kept It Simple
There are fancier tools out there that do similar things — interactive TUIs, progress bars, undo stacks. I deliberately avoided all of that.
- ▸Zero config — one required flag (-d), sensible defaults for everything else
- ▸Single dependency (commander) — installs in under a second, no bloat
- ▸Works on Node.js 14+ — runs on anything you're likely to have
- ▸Composable — the scanner is exported as a module so you can use it programmatically in your own scripts
- ▸Predictable — always shows you what it found before deleting, no surprises
The goal was a tool I'd actually remember to use — one command, one obvious flag, done.
Use It Programmatically
The scanner is also available as a Node.js module if you want to integrate it into your own tooling:
const { run, formatBytes } = require("deepclean-cli/lib/scanner");
const result = run({
dir: "/Users/zain/code",
targetDir: "node_modules",
level: 3,
dryRun: true,
onMatch: (matchPath, size) => {
console.log(`Found: ${matchPath} (${formatBytes(size)})`);
},
});
console.log(`Total: ${formatBytes(result.bytes)} across ${result.count} folders`);
// result.matches is an array of { path, size } objectsGet It
- ▸npm: npm install -g deepclean-cli
- ▸npx (no install): npx deepclean-cli -d ~/code --dry-run
- ▸GitHub: github.com/ZainMustafaaa — PRs and issues welcome
If you've been putting off cleaning up your projects folder because hunting through directories manually is tedious — this is the 30-second fix. Run the dry-run first, see what's sitting there, and reclaim the space.