dev-prune 1.5.0

Universal, lockfile-safe workspace pruner and background dependency cleaner
Documentation
[package]
name = "dev-prune"
version = "1.5.0"
edition = "2024"
authors = ["VKrishna04"]
description = "Universal, lockfile-safe workspace pruner and background dependency cleaner"
license = "Apache-2.0"
readme = "README.md"
repository = "https://github.com/Life-Experimentalist/dev-prune"
homepage = "https://devprune.vkrishna04.me"
# Not docs.rs. This is a binary crate: docs.rs would build the library surface, which is
# nothing anyone installing `dev-prune` wants to read. Point at the docs that describe
# the commands instead — crates.io links this field directly.
documentation = "https://github.com/Life-Experimentalist/dev-prune/blob/main/docs/README.md"
keywords = ["cli", "workspace", "cleanup", "node_modules", "venv"]
# Five is the crates.io maximum and every one of them is a category page this tool
# genuinely belongs on — those pages are how people browsing crates.io find it at all.
categories = [
    "command-line-utilities",
    "development-tools",
    "development-tools::build-utils",
    "filesystem",
    "caching",
]
rust-version = "1.88"

# An allowlist, not an exclude list, and deliberately so.
#
# Three of these entries are load-bearing: `build.rs` reads `assets/icon.ico`, and the
# binary embeds `schemas/`, `assets/mimetype/` and `.agents/skills/dev-prune/SKILL.md`
# with `include_bytes!`/`include_str!`. A published crate missing any of them does not
# fail at runtime — it fails to compile, for everyone, and the only fix is a new version.
#
# The dotted path has to be spelled out: cargo does not pick up `.agents/` on its own.
# `site/` is omitted on purpose; its `node_modules` is 16k files and belongs nowhere near
# a crate tarball.
include = [
    "/src",
    "/tests",
    "/build.rs",
    "/Cargo.toml",
    "/Cargo.lock",
    "/README.md",
    "/LICENSE.md",
    "/NOTICE",
    "/CHANGELOG.md",
    "/SECURITY.md",
    "/clippy.toml",
    "/rustfmt.toml",
    "/docs",
    "/schemas",
    "/assets/icon.ico",
    "/assets/icon.png",
    "/assets/icon.icns",
    "/assets/mimetype",
    "/assets/README.md",
    "/.agents/skills/dev-prune/SKILL.md",
    "/.agents/rules/dev-prune.rules.md",
]

# `cargo install dev-prune` compiles from source, because crates.io stores source and
# nothing else — there is no binary on the registry for cargo to fetch. `cargo binstall
# dev-prune` reads the table below, downloads the matching GitHub release asset and
# unpacks the executable, so it needs no toolchain and finishes in seconds.
#
# The URLs restate the asset names built by `.github/workflows/release.yml`, which are
# already a contract with `scripts/install.sh` and `scripts/install.ps1`. binstall's own
# `{ target-arch }` / `{ target-family }` placeholders expand to `x86_64` and `unix`, not
# the `x64` and `darwin` this project uses, so every target is spelled out. Renaming an
# asset means editing this table too, and `cargo binstall` failing over to a from-source
# build is the only symptom if it is forgotten.
#
# Both libc flavours of each Linux target are listed: binstall probes the host and asks
# for gnu or musl depending on what it finds, while the published asset is one statically
# linked musl build that serves both.
[package.metadata.binstall]
bin-dir = "{ bin }{ binary-ext }"

[package.metadata.binstall.overrides.x86_64-unknown-linux-gnu]
pkg-url = "{ repo }/releases/download/v{ version }/dev-prune-v{ version }-linux-x64.tar.gz"
pkg-fmt = "tgz"

[package.metadata.binstall.overrides.x86_64-unknown-linux-musl]
pkg-url = "{ repo }/releases/download/v{ version }/dev-prune-v{ version }-linux-x64.tar.gz"
pkg-fmt = "tgz"

[package.metadata.binstall.overrides.aarch64-unknown-linux-gnu]
pkg-url = "{ repo }/releases/download/v{ version }/dev-prune-v{ version }-linux-arm64.tar.gz"
pkg-fmt = "tgz"

[package.metadata.binstall.overrides.aarch64-unknown-linux-musl]
pkg-url = "{ repo }/releases/download/v{ version }/dev-prune-v{ version }-linux-arm64.tar.gz"
pkg-fmt = "tgz"

[package.metadata.binstall.overrides.x86_64-apple-darwin]
pkg-url = "{ repo }/releases/download/v{ version }/dev-prune-v{ version }-darwin-x64.tar.gz"
pkg-fmt = "tgz"

[package.metadata.binstall.overrides.aarch64-apple-darwin]
pkg-url = "{ repo }/releases/download/v{ version }/dev-prune-v{ version }-darwin-arm64.tar.gz"
pkg-fmt = "tgz"

[package.metadata.binstall.overrides.x86_64-pc-windows-msvc]
pkg-url = "{ repo }/releases/download/v{ version }/dev-prune-v{ version }-windows-x64.zip"
pkg-fmt = "zip"

[package.metadata.binstall.overrides.aarch64-pc-windows-msvc]
pkg-url = "{ repo }/releases/download/v{ version }/dev-prune-v{ version }-windows-arm64.zip"
pkg-fmt = "zip"

[package.metadata.binstall.overrides.i686-pc-windows-msvc]
pkg-url = "{ repo }/releases/download/v{ version }/dev-prune-v{ version }-windows-x86.zip"
pkg-fmt = "zip"

[[bin]]
name = "dev-prune"
path = "src/main.rs"

[dependencies]
# CLI argument parsing
clap = { version = "4", features = ["derive", "color", "suggestions"] }

# Shell completion scripts for `devp completions`. Generated from the same clap
# definition the binary parses with, so a new flag cannot be missing from them.
clap_complete = "4"
# Man pages for `devp man`. Same clap definition again, same reason.
clap_mangen = "0.3"

# Serialization
serde = { version = "1", features = ["derive"] }
serde_json = "1"

# Error handling
anyhow = "1"

# Timestamps & time
chrono = { version = "0.4", features = ["serde"] }

# Cross-platform paths
dirs = "6"

# Directory walking
walkdir = "2"

# Terminal UI & Spinners
#
# crossterm is held at 0.29 because ratatui 0.30's default `crossterm_0_29` feature
# depends on it. Taking a different crossterm on its own puts two copies of the
# terminal's raw-mode state in one binary, and ratatui's MSRV moves with it (0.30 is
# what pushed `rust-version` to 1.88). Bump all three together, or none.
ratatui = "0.30"
crossterm = "0.29"
indicatif = "0.18"

# Terminal column widths for the plain-text tables.
#
# Not a new dependency: ratatui-core, indicatif and console all already pull this in,
# so it is compiled into the binary either way. Naming it directly costs nothing and
# buys correct alignment for CJK and emoji paths, where one `char` occupies two
# columns and `{:<35}` — which counts `char`s — overshoots by the number of wide
# characters in the name.
unicode-width = "0.2"

# Colored terminal output
colored = "3"

# Human-readable byte sizes
humansize = "2"

# HTTP. Used by exactly one module — `commands::update` — and nothing else in the binary
# opens a socket. rustls with bundled roots rather than the platform verifier, so the
# release check behaves the same on a bare CI container as on a developer laptop.
ureq = { version = "3.4", default-features = false, features = ["rustls", "gzip"] }
# SHA-256, for `devp update --install`: the release publishes an uncompressed binary
# beside each archive, and the sidecar hash is the only thing standing between a
# download and executing it. Verification is the whole point of the direct route, so
# this is not an optional dependency. Nothing else is pulled in to unpack an archive —
# that is why the raw binary is published at all.
sha2 = "0.11"

# Restoring the default SIGPIPE disposition, which Rust ignores at startup. Without it
# `devp status | head` dies in a panic instead of exiting quietly.
[target.'cfg(unix)'.dependencies]
libc = "0.2"

# Hardlink counts. pnpm and bun link store files into `node_modules` instead of copying
# them, and on Windows the link count lives behind an opened handle
# (`GetFileInformationByHandle`) — std only exposes it on unstable. Already in the
# dependency tree transitively, so this pins no new crate.
[target.'cfg(windows)'.dependencies]
windows-sys = { version = "0.61", features = [
    "Win32_Foundation",
    "Win32_Storage_FileSystem",
    # `GetConsoleWindow`, for the child-window policy in `src/spawn.rs`.
    "Win32_System_Console",
] }

[dev-dependencies]
# Mock filesystems for testing
tempfile = "3"
# Assertions
assert_cmd = "2"
predicates = "3"

[target.'cfg(windows)'.build-dependencies]
winres = "0.1"

[profile.release]
lto = true
codegen-units = 1
strip = true
opt-level = 3