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