ZUA.
Home/Blog/deepclean-cli
Developer ToolsJuly 15, 2026 · 7 min read

I Built a CLI Tool to Reclaim Disk Space from node_modules — Here's How It Works

Every developer who works across multiple projects eventually runs into the same problem — disk space quietly disappearing into dozens of node_modules folders scattered across their machine. I got tired of hunting them down manually, so I built a CLI tool to do it. Here's what it does, how I built it, and how to use it.

Node.jsCLIOpen SourceDeveloper Toolsnpm

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-cli

Or run it without installing via npx:

npx deepclean-cli -d ~/code

Point it at your projects folder and run a dry-run first to see what it finds:

deepclean -d ~/code --dry-run

Output 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 usage

Clean 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 5

Skip the confirmation in scripts — the -y flag is useful in automation:

# In a cron job or shell script — no interactive prompt
deepclean -d ~/code -y

How 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 });
SAFETY NOTE

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 } objects

Get 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.

Have a developer tooling problem that needs solving?

I build and ship production systems — happy to discuss your requirements.

Book a Call