remnem
remove node_modules — find every nested node_modules in a project (root + all workspaces + any nested ones) and delete them all, as fast as possible.
Written in Rust (napi-rs) with a parallel directory walker and parallel deletion. Uses the same workspace resolution as bun and pnpm to describe the workspace layout.
$ remnem
root: /Users/you/dev/my-monorepo
package.json workspace (12 packages)
found 13 node_modules totalling 2.4 GB:
318 MB node_modules
1.1 GB apps/web/node_modules
...
permanently delete these 13 directories? [y/N] y
deleted: 13/13 node_modules (2.4 GB) in 412ms
Delete vs. Trash
By default remnem permanently deletes each node_modules in parallel —
space is reclaimed immediately.
Pass -t / --trash to move them to the OS trash instead (Finder Trash on
macOS, the freedesktop trash on Linux, the Recycle Bin on Windows). On the same
volume that is a directory rename — O(1), effectively instant no matter how
many files the tree holds — and recoverable from the trash. The disk space is
reclaimed when you empty it. A node_modules on a different volume (rare)
can't be renamed instantly, so those fall back to a direct delete.
Install
npm install -g remnem
# or: bun install -g remnem
The right prebuilt native binary is pulled in automatically for your platform
via optionalDependencies. Supported: macOS (arm64, x64), Linux (arm64
& x64, glibc & musl), Windows (arm64, x64).
Then from any repo root:
remnem
From source
bun install
bun run build # builds the native addon for your host → remnem.<platform>.node
bun link # makes `remnem` available on your PATH
What it clears
Every node_modules directory under the given root — the root's own, every
workspace package's, and any stray nested ones — leaving all your source and
package.json files untouched. The walker never descends into a
node_modules (the whole subtree is going to be removed anyway), so it stays
fast even on trees with hundreds of thousands of files.
Workspace resolution (reading package.json#workspaces for bun/npm/yarn, or
pnpm-workspace.yaml#packages for pnpm) is used to report the workspace
layout; clearing always targets every nested node_modules, not only workspace
packages.
Usage
remnem [path] [options]
Arguments:
path Project root to clean (default: current directory)
Options:
-t, --trash Move to the Trash instead of deleting (instant, recoverable)
-l, --list List what would be cleared; touch nothing
--no-measure Skip sizing each node_modules (faster; sizes show as 0)
--json Print the raw result as JSON
-y, --yes Skip the confirmation prompt
-h, --help Show this help
By default remnem permanently deletes each node_modules, after printing what it
found and asking for confirmation (skipped with -y, or when stdout isn't a TTY,
e.g. in CI). Use -t to move them to the Trash instead (space reclaimed when you
empty it), or -l to list what would be cleared without touching anything.
Workspace resolution
remnem mirrors how bun and pnpm resolve workspace packages:
| Source | Field | Example |
|---|---|---|
| bun / npm / yarn | package.json → workspaces |
["packages/*", "!packages/excluded"] |
| bun / npm / yarn | package.json → workspaces.packages |
{ "packages": ["libs/*"] } |
| pnpm | pnpm-workspace.yaml → packages |
- 'packages/*'- '!**/test/**' |
Glob semantics match picomatch (the matcher bun/npm/yarn use):
*matches exactly one path segment (packages/*→packages/a, notpackages/a/b)**matches any number of segments, and a trailing/**is optional (components/**matchescomponentsitself and everything beneath it)!patternexcludes previously-matched directories (!**/test/**drops a directory namedtestand its contents)
A directory only counts as a workspace package when it contains its own
package.json.
API
The napi-rs core is also usable directly from JavaScript:
const { clean, resolveWorkspace } = require("remnem");
// Clear every nested node_modules under a root.
// trash: false (default) → permanent parallel delete; true → move to Trash.
const result = clean({ root: "/path/to/repo", dryRun: false, measure: true, trash: false });
// → { root, workspaceKind, workspacePackages, cleaned: [{ path, bytes, deleted, trashed, error }], totalBytes, count, failed }
// Just inspect how bun/pnpm would see the workspace (no deletion).
const ws = resolveWorkspace("/path/to/repo");
// → { workspaceKind: "pnpm" | "package.json" | "none", workspacePackages: [...] }
See index.d.ts for the full typed surface.
Development
cargo test # Rust unit tests (workspace resolution + glob semantics)
bun run build:debug # debug build
bun run build # release build (LTO)
License
MIT