clean-dev-dirs 2.8.0

A fast CLI tool for recursively cleaning development build directories (Rust, Node.js, Python, Go, Java/Kotlin, C/C++, Swift, .NET) to reclaim disk space
Documentation

A fast and efficient CLI tool for recursively cleaning development build directories across 16 language ecosystems to reclaim disk space. Supports Rust, Node.js, Python, Go, Java/Kotlin, C/C++, Swift, .NET/C#, Ruby, Elixir, Deno, PHP, Haskell, Dart/Flutter, Zig, and Scala.

Created and maintained by Tom Planche. The GitHub organization exists solely to host the Homebrew tap alongside the main repository.

Quick Start

# Install from crates.io
cargo install clean-dev-dirs

# Clean all development directories in current directory
clean-dev-dirs

# Clean a specific directory
clean-dev-dirs ~/Projects

# Clean multiple directories at once
clean-dev-dirs ~/Projects ~/work/client

# Preview what would be cleaned (dry run)
clean-dev-dirs --dry-run

# Interactive mode - choose which projects to clean
clean-dev-dirs --interactive

Features

  • Multi-language support: Clean build artifacts across 16 ecosystems — Rust (target/), Node.js (node_modules/), Python (cache dirs), Go (vendor/), Java/Kotlin (target//build/), C/C++ (build/), Swift (.build/), .NET/C# (bin/+obj/), Ruby (.bundle//vendor/bundle/), Elixir (_build/), Deno (vendor//node_modules/), PHP (vendor/), Haskell (.stack-work//dist-newstyle/), Dart/Flutter (.dart_tool//build/), Zig (zig-cache//zig-out/), and Scala (target/)
  • Parallel scanning: Lightning-fast directory traversal using multithreading
  • Smart filtering: Filter by project size, modification time, and project type
  • Flexible sorting: Sort results by size, age, name, or project type with --sort
  • Interactive mode: Choose which projects to clean with an intuitive interface
  • Dry-run mode: Preview what would be cleaned without actually deleting anything
  • Progress indicators: Real-time feedback during scanning and cleaning operations
  • Executable preservation: Keep compiled binaries before cleaning with --keep-executables
  • Safe by default: Moves directories to the system trash for recoverable deletions; use --permanent when you want rm -rf
  • JSON output: Structured --json output for scripting, piping, and dashboard integration
  • Detailed statistics: See total space that can be reclaimed before cleaning
  • Persistent configuration: Set defaults in ~/.config/clean-dev-dirs/config.toml so you don't repeat flags
  • Flexible configuration: Combine multiple filters and options for precise control

Inspiration

This project is inspired by cargo-clean-all, a Rust-specific tool for cleaning cargo projects. I've improved upon the original concept with:

  • Multi-language support: Extended beyond Rust to support Node.js, Python, Go, Java/Kotlin, C/C++, Swift, .NET/C#, Ruby, Elixir, Deno, PHP, Haskell, Dart/Flutter, Zig, and Scala projects
  • Parallel scanning: Significantly faster directory traversal using multithreading
  • Enhanced filtering: More granular control over what gets cleaned
  • Cleaner code architecture: Well-structured, modular codebase for better maintainability

Installation

From crates.io (Recommended)

cargo install clean-dev-dirs

From Source

git clone https://github.com/TomPlanche/clean-dev-dirs.git
cd clean-dev-dirs
cargo install --path .

Requirements

  • Rust 2024 edition or later
  • Cargo package manager

Usage

Basic Usage

# Clean all development directories in the current directory
clean-dev-dirs

# Clean a specific directory
clean-dev-dirs ~/Projects

# Clean multiple directories at once
clean-dev-dirs ~/Projects ~/work/client ~/personal/code

# Preview what would be cleaned (dry run)
clean-dev-dirs --dry-run

# Interactive mode - choose which projects to clean
clean-dev-dirs --interactive

Project Type Filtering

# Clean only Rust projects
clean-dev-dirs --project-type rust
# or use short flag
clean-dev-dirs -p rust

# Clean only Node.js projects
clean-dev-dirs -p node

# Clean only Python projects
clean-dev-dirs -p python

# Clean only Go projects
clean-dev-dirs -p go

# Clean only Java/Kotlin projects
clean-dev-dirs -p java

# Clean only C/C++ projects
clean-dev-dirs -p cpp

# Clean only Swift projects
clean-dev-dirs -p swift

# Clean only .NET/C# projects
clean-dev-dirs -p dotnet

# Clean only Ruby projects
clean-dev-dirs -p ruby

# Clean only Elixir projects
clean-dev-dirs -p elixir

# Clean only Deno projects
clean-dev-dirs -p deno

# Clean only PHP projects
clean-dev-dirs -p php

# Clean only Haskell projects
clean-dev-dirs -p haskell

# Clean only Dart/Flutter projects
clean-dev-dirs -p dart

# Clean only Zig projects
clean-dev-dirs -p zig

# Clean only Scala projects
clean-dev-dirs -p scala

# Clean all project types (default)
clean-dev-dirs -p all

Size and Time Filtering

# Only clean projects with build dirs larger than 100MB
clean-dev-dirs --keep-size 100MB

# Only clean projects not modified in the last 30 days
clean-dev-dirs --keep-days 30

# Combine size and time filters
clean-dev-dirs --keep-size 50MB --keep-days 7

Name Filtering

# Only clean projects whose name matches a glob pattern
clean-dev-dirs --name "my-app*"

# Glob with single-character wildcard
clean-dev-dirs --name "app-?"

# Use a regular expression (prefix with regex:)
clean-dev-dirs --name "regex:^client-.*"

# Combine with other filters
clean-dev-dirs --name "regex:^client-" --keep-size 50MB --dry-run

Sorting

# Sort projects by size (largest first)
clean-dev-dirs --sort size

# Sort projects by age (oldest first)
clean-dev-dirs --sort age

# Sort projects by name (alphabetical)
clean-dev-dirs --sort name

# Sort projects grouped by type (Go, Node, Python, Rust)
clean-dev-dirs --sort type

# Reverse any sort order (e.g. smallest first)
clean-dev-dirs --sort size --reverse

# Combine with other options
clean-dev-dirs ~/Projects --sort size --keep-size 50MB --dry-run

Keeping Executables

# Preserve compiled binaries before cleaning
clean-dev-dirs --keep-executables
# or use short flag
clean-dev-dirs -k

# In interactive mode (-i) without -k, you will be prompted:
#   "Keep compiled executables before cleaning? (y/N)"
clean-dev-dirs --interactive

# Combine with other options
clean-dev-dirs ~/Projects -p rust -k --keep-days 30

When enabled, compiled outputs are copied to <project>/bin/ before the build directory is deleted:

  • Rust: executables from target/release/ and target/debug/ are copied to bin/release/ and bin/debug/
  • Python: .whl files from dist/ and .so/.pyd C extensions from build/ are copied to bin/
  • Node.js / Go / Java / C++ / Swift / .NET / Ruby / Elixir / Deno / PHP / Haskell / Dart / Zig / Scala: no-op (their cleaned directories contain dependencies or build outputs not easily preservable)

Trash Support (Default)

By default, build directories are moved to the system trash (Trash on macOS/Linux, Recycle Bin on Windows) instead of being permanently removed. This means all deletions are recoverable -- just check your trash.

# Default behavior: moves to trash (safe, recoverable)
clean-dev-dirs

# Permanently delete instead (rm -rf style, irreversible)
clean-dev-dirs --permanent

# Combine permanent deletion with other options
clean-dev-dirs --permanent --keep-executables -y

To make permanent deletion the default, set use_trash = false in your config file:

[execution]
use_trash = false

JSON Output

Use --json to get structured output for scripting, piping to jq, or feeding into dashboards:

# List all projects as JSON (dry run)
clean-dev-dirs --json --dry-run

# Clean and get machine-readable results
clean-dev-dirs --json --yes ~/Projects

# Pipe to jq for further processing
clean-dev-dirs --json --dry-run | jq '.projects[] | select(.build_artifacts_size > 1000000000)'

# Get total reclaimable space across Rust projects
clean-dev-dirs --json --dry-run -p rust | jq '.summary.total_size_formatted'

When --json is active, all human-readable output (colors, progress bars) is suppressed and a single JSON document is printed to stdout. --json is incompatible with --interactive and implies --yes behavior (no confirmation prompts).

{
  "mode": "dry_run",
  "projects": [
    {
      "name": "my-rust-app",
      "type": "rust",
      "root_path": "/home/user/projects/rust-app",
      "build_artifacts_paths": ["/home/user/projects/rust-app/target"],
      "build_artifacts_size": 2300000000,
      "build_artifacts_size_formatted": "2.30 GB"
    },
    {
      "name": "web-frontend",
      "type": "node",
      "root_path": "/home/user/projects/web-app",
      "build_artifacts_paths": ["/home/user/projects/web-app/node_modules"],
      "build_artifacts_size": 856000000,
      "build_artifacts_size_formatted": "856.00 MB"
    }
  ],
  "summary": {
    "total_projects": 2,
    "total_size": 3156000000,
    "total_size_formatted": "3.16 GB",
    "by_type": {
      "node": { "count": 1, "size": 856000000, "size_formatted": "856.00 MB" },
      "rust": { "count": 1, "size": 2300000000, "size_formatted": "2.30 GB" }
    }
  }
}
{
  "mode": "cleanup",
  "projects": [ "..." ],
  "summary": { "..." },
  "cleanup": {
    "success_count": 2,
    "failure_count": 0,
    "total_freed": 3156000000,
    "total_freed_formatted": "3.16 GB",
    "errors": []
  }
}

Advanced Options

# Use 8 threads for faster scanning
clean-dev-dirs --threads 8

# Show verbose output including scan errors
clean-dev-dirs --verbose

# Skip specific directories during scanning
clean-dev-dirs --skip node_modules --skip .git

# Limit how deep into the directory tree the scanner goes
clean-dev-dirs --max-depth 3

# Non-interactive mode (auto-confirm)
clean-dev-dirs --yes

# Combine multiple options
clean-dev-dirs ~/Projects -p rust --keep-size 100MB --keep-days 30 --dry-run

Configuration File

You can store default settings in a TOML file so you don't have to repeat the same flags every time. CLI arguments always override config file values.

Location: ~/.config/clean-dev-dirs/config.toml (Linux/macOS) or %APPDATA%\clean-dev-dirs\config.toml (Windows)

Use the built-in config subcommand to manage the file without hunting for the path:

# Find out where the config file lives on your platform
clean-dev-dirs config path

# Write a fully commented-out template (does nothing if the file already exists)
clean-dev-dirs config init

# Print every setting with its current value or default
clean-dev-dirs config show
# Default project type filter
project_type = "rust"

# Default directories to scan — use dirs for multiple roots (~ is expanded)
dirs = ["~/Projects", "~/work/client"]

# Legacy single-directory option (ignored when dirs is set)
# dir = "~/Projects"

[filtering]
keep_size = "50MB"
keep_days = 7
sort = "size"       # "size", "age", "name", or "type"
reverse = false
# name_pattern = "my-*"   # glob or "regex:^prefix-.*"

[scanning]
threads = 4
verbose = true
skip = [".cargo", "vendor"]
ignore = [".git"]
max_depth = 5   # optional; omit for unlimited depth

[execution]
keep_executables = true
interactive = false
dry_run = false
use_trash = true          # default; set to false for permanent deletion

All fields are optional — only set what you need. An absent config file is silently ignored; a malformed one produces an error message.

Layering rules:

Value type Behavior
Scalar (keep_size, threads, project_type, sort, …) CLI wins if provided, otherwise config file, otherwise built-in default
Directory roots (dirs/dir) CLI args win; then config dirs (plural); then config dir (legacy); then current directory
Boolean flag (--dry-run, --verbose, --reverse, …) true if the CLI flag is present or the config file sets it to true
List (skip, ignore) Merged — config file entries first, then CLI entries appended

Examples:

# Uses keep_size = "50MB" from config, overrides project_type on CLI
clean-dev-dirs -p node

# CLI --keep-size wins over the config file value
clean-dev-dirs --keep-size 200MB

# skip dirs from config (.cargo, vendor) + CLI (node_modules) are all active
clean-dev-dirs --skip node_modules

Common Use Cases

1. Clean old Rust projects:

clean-dev-dirs ~/Projects -p rust --keep-days 90

2. Preview large Python cache directories:

clean-dev-dirs ~/workspace -p python --keep-size 50MB --dry-run

3. Interactive cleaning of all Node.js projects:

clean-dev-dirs ~/dev -p node --interactive

4. Quick cleanup with confirmation:

clean-dev-dirs ~/code --keep-size 100MB --keep-days 60

5. Fast scan with multiple threads:

clean-dev-dirs /large/directory --threads 16 --verbose

6. Clean Rust projects but keep the compiled binaries:

clean-dev-dirs ~/Projects -p rust -k

7. Find the biggest space hogs:

clean-dev-dirs ~/Projects --sort size --dry-run

8. Clean the most stale projects first:

clean-dev-dirs ~/code --sort age --interactive

9. Get a JSON report for a CI/CD dashboard:

clean-dev-dirs ~/Projects --json --dry-run | jq '.summary'

10. Permanently delete (skip the trash):

clean-dev-dirs ~/Projects --permanent --yes

11. Scan multiple root directories at once:

clean-dev-dirs ~/Projects ~/work/client ~/personal/code --sort size --dry-run

12. Clean only projects whose name starts with client-:

clean-dev-dirs ~/Projects --name "regex:^client-" --dry-run

13. Set up a config file for your usual workflow:

# Generate a commented-out template at the right platform path
clean-dev-dirs config init

# Open it in your editor, uncomment and set what you need, then just run:
clean-dev-dirs

Command Reference

Config Subcommand

clean-dev-dirs config <COMMAND>
Command Description
config path Print the platform-specific path to the config file
config show Print every setting with its current value or annotated default
config init Write a fully commented-out template if no config file exists yet

Main Arguments

Argument Description
[DIRS]... One or more directories to search for projects (default: current directory). Multiple roots are deduplicated.

Project Type Filter

Option Short Values Description
--project-type -p all, rust, node, python, go, java, cpp, swift, dotnet, ruby, elixir, deno, php, haskell, dart, zig, scala Filter by project type (default: all)

Filtering Options

Option Short Description
--keep-size -s Ignore projects with build dir smaller than specified size
--keep-days -d Ignore projects modified in the last N days
--name Filter by project name using a glob or regex: pattern

Sorting Options

Option Values Description
--sort size, age, name, type Sort projects before display (default: scan order)
--reverse Reverse the sort order

Default sort directions: size largest first, age oldest first, name A-Z, type alphabetical by type name.

Output Options

Option Description
--json Output results as a single JSON object for scripting/piping (incompatible with --interactive)

Execution Options

Option Short Description
--yes -y Don't ask for confirmation; clean all detected projects
--dry-run List cleanable projects without actually cleaning
--interactive -i Use interactive project selection
--keep-executables -k Copy compiled executables to <project>/bin/ before cleaning
--permanent Permanently delete directories instead of moving them to the system trash

Scanning Options

Option Short Description
--threads -t Number of threads for directory scanning (default: CPU cores)
--verbose -v Show access errors during scanning
--skip Directories to skip during scanning (can be specified multiple times)
--max-depth Maximum directory depth to scan (default: unlimited)

Size Formats

The --keep-size option supports various size formats:

Format Example Description
Decimal 100KB, 1.5MB, 2GB Base 1000
Binary 100KiB, 1.5MiB, 2GiB Base 1024
Bytes 1000000 Raw byte count

Examples:

clean-dev-dirs --keep-size 100KB    # 100 kilobytes
clean-dev-dirs --keep-size 1.5MB    # 1.5 megabytes
clean-dev-dirs --keep-size 2GiB     # 2 gibibytes
clean-dev-dirs --keep-size 500000   # 500,000 bytes

Project Detection

The tool automatically detects development projects by looking for characteristic files and directories:

Rust Projects

  • Detection criteria: Both Cargo.toml and target/ directory must exist, and the directory is not a workspace member (workspace members share the root target/ and are skipped)
  • Cleans: target/ directory
  • Name extraction: From [package] name in Cargo.toml

Node.js Projects

  • Detection criteria: Both package.json and node_modules/ directory must exist
  • Cleans: node_modules/ directory
  • Name extraction: From name field in package.json

Python Projects

  • Detection criteria:
    • At least one config file: requirements.txt, setup.py, pyproject.toml, setup.cfg, Pipfile, pipenv.lock, poetry.lock
    • At least one cache/build directory exists
  • Cleans: All present cache/build directories:
    • __pycache__
    • .pytest_cache
    • venv / .venv
    • build / dist
    • .eggs / .tox / .coverage
    • Any *.egg-info directories found in the project root
  • Name extraction: From pyproject.toml (project name or tool.poetry name) or setup.py

Go Projects

  • Detection criteria: Both go.mod and vendor/ directory must exist
  • Cleans: vendor/ directory
  • Name extraction: From module path in go.mod

Java/Kotlin Projects

  • Detection criteria:
    • Maven: pom.xml + target/ directory
    • Gradle: build.gradle or build.gradle.kts + build/ directory
  • Cleans: target/ (Maven) or build/ (Gradle) directory
  • Name extraction: From <artifactId> in pom.xml, or rootProject.name in settings.gradle

C/C++ Projects

  • Detection criteria: CMakeLists.txt or Makefile + build/ directory
  • Cleans: build/ directory
  • Name extraction: From project() in CMakeLists.txt, or falls back to directory name

Swift Projects

  • Detection criteria: Both Package.swift and .build/ directory must exist
  • Cleans: .build/ directory
  • Name extraction: From name: in Package.swift

.NET/C# Projects

  • Detection criteria: At least one .csproj file + bin/ and/or obj/ directories
  • Cleans: Both bin/ and obj/ directories when present
  • Name extraction: From the .csproj filename

Ruby Projects

  • Detection criteria: Both Gemfile and .bundle/ or vendor/bundle/ directory must exist
  • Cleans: Both .bundle/ and vendor/bundle/ directories when present
  • Name extraction: From the name field in a .gemspec file, or falls back to directory name

Elixir Projects

  • Detection criteria: Both mix.exs and _build/ directory must exist
  • Cleans: _build/ directory
  • Name extraction: From app: atom in mix.exs, or falls back to directory name

Deno Projects

  • Detection criteria: deno.json or deno.jsonc + vendor/ directory (primary), or node_modules/ without package.json (secondary)
  • Cleans: vendor/ or node_modules/ directory
  • Name extraction: From name field in deno.json/deno.jsonc, or falls back to directory name

PHP Projects

  • Detection criteria: Both composer.json and vendor/ directory must exist
  • Cleans: vendor/ directory
  • Name extraction: From name field in composer.json (the package component after /), or falls back to directory name

Haskell Projects

  • Detection criteria:
    • Stack: stack.yaml + .stack-work/ directory
    • Cabal: cabal.project or a *.cabal file + dist-newstyle/ directory
  • Cleans: .stack-work/ (Stack) or dist-newstyle/ (Cabal)
  • Name extraction: From name: field in a *.cabal file, then package.yaml (hpack), or falls back to directory name

Dart/Flutter Projects

  • Detection criteria: pubspec.yaml + .dart_tool/ and/or build/ directory must exist
  • Cleans: .dart_tool/ and/or build/ directories when present
  • Name extraction: From name: field in pubspec.yaml, or falls back to directory name

Zig Projects

  • Detection criteria: build.zig + zig-cache/ and/or zig-out/ directory must exist
  • Cleans: zig-cache/ and/or zig-out/ directories when present
  • Name extraction: Falls back to directory name (no standard name field in build.zig)

Scala Projects

  • Detection criteria: Both build.sbt and target/ directory must exist
  • Cleans: target/ directory
  • Name extraction: From name := "..." assignment in build.sbt, or falls back to directory name

Safety Features

  • Trash by default: Directories are moved to the system trash for recoverable cleanups; use --permanent to override
  • Confirmation prompt: A "Clean N projects?" prompt is shown before any deletion; use --yes/-y to skip it
  • Dry-run mode: Preview all operations before execution with --dry-run
  • Interactive selection: Manually choose which projects to clean with --interactive
  • Intelligent filtering: Skip recently modified or small projects with --keep-days and --keep-size
  • Error handling: Graceful handling of permission errors and inaccessible files
  • Read-only scanning: Never modifies files during the scanning phase
  • Clear output: Color-coded, human-readable output with ASCII project-type tags and sizes

Output

The tool provides colored output including:

Tag Project Type
[rs] Rust projects
[js] Node.js projects
[py] Python projects
[go] Go projects
[java] Java/Kotlin projects
[cpp] C/C++ projects
[swift] Swift projects
[net] .NET/C# projects
[rb] Ruby projects
[ex] Elixir projects
[deno] Deno projects
[php] PHP projects
λ Haskell projects
[dart] Dart/Flutter projects
[zig] Zig projects
[scala] Scala projects

Sample Output

Found 15 projects

Found projects:

[rs] my-rust-app (/home/user/projects/rust-app)
   Size: 2.3 GB

[js] web-frontend (/home/user/projects/web-app)
   Size: 856 MB

[py] ml-project (/home/user/projects/python-ml)
   Size: 1.2 GB

Total space that can be reclaimed: 4.4 GB

Contributing

Contributions are welcome! Please feel free to submit a Pull Request.

Adding Language Support

Want to add support for a new programming language? Here's how to extend clean-dev-dirs:

1. Update Project Types

First, add your language to the ProjectType enum in src/project/project.rs:

#[derive(Clone, PartialEq, Debug)]
pub enum ProjectType {
    Rust,
    Node,
    Python,
    Go,
    YourLanguage, // Add your language here
}

Don't forget to update the Display implementation to include an appropriate ASCII tag and name.

2. Add CLI Filter Option

Update src/config/filter.rs to add your language to the ProjectFilter enum:

#[derive(Clone, Copy, PartialEq, Debug, ValueEnum, Default)]
pub enum ProjectFilter {
    All,
    Rust,
    Node,
    Python,
    Go,
    YourLanguage, // Add here
}

3. Implement Project Detection

Add detection logic in src/scanner.rs by implementing:

  • Detection method: detect_your_language_project() - identifies projects by looking for characteristic files
  • Name extraction: extract_your_language_project_name() - parses project configuration files to get the name
  • Integration: Update detect_project() to call your detection method

Example detection criteria:

fn detect_your_language_project(&self, path: &Path, errors: &Arc<Mutex<Vec<String>>>) -> Option<Project> {
    let config_file = path.join("your_config.conf");  // Language-specific config file
    let build_dir = path.join("build");               // Build/cache directory to clean

    if config_file.exists() && build_dir.exists() {
        let name = self.extract_your_language_project_name(&config_file, errors);

        let build_arts = vec![BuildArtifacts {
            path: build_dir,
            size: 0, // Will be calculated later
        }];

        return Some(Project::new(
            ProjectType::YourLanguage,
            path.to_path_buf(),
            build_arts,
            name,
        ));
    }

    None
}

4. Update Directory Exclusions

Add any language-specific directories that should be skipped during scanning to the should_scan_entry() method in src/scanner.rs.

5. Update Documentation

  • Add your language to the "Project Detection" section in this README
  • Update the CLI help text descriptions
  • Add examples in the usage section

6. Testing Considerations

Consider these when testing your implementation:

  • Multiple config files: Some languages have different project file formats
  • Build directory variations: Different build tools may use different directory names
  • Name extraction edge cases: Handle malformed or missing project names gracefully
  • Performance: Ensure detection doesn't significantly slow down scanning

7. Pull Request Guidelines

When submitting your language support:

  1. Test thoroughly: Verify detection works with real projects
  2. Add examples: Include sample project structures in your PR description
  3. Update help text: Ensure all user-facing text is clear and consistent
  4. Follow patterns: Use the same patterns as existing language implementations
  5. Consider edge cases: Handle projects with unusual structures gracefully

License

This project is dual-licensed under either:

You may choose either license at your option.

Acknowledgments

Built with excellent open-source libraries:

  • Clap - Command-line argument parsing with derive macros
  • Rayon - Data parallelism for fast directory scanning
  • Colored - Beautiful colored terminal output
  • Indicatif - Progress bars and spinners
  • Inquire - Interactive prompts and selection
  • WalkDir - Recursive directory iteration
  • Humansize - Human-readable file sizes
  • Serde + serde_json + TOML - Serialization, JSON output, and configuration file parsing
  • dirs - Cross-platform config directory resolution
  • trash - Cross-platform system trash support

Support