5 releases
| 0.1.4 | Apr 24, 2026 |
|---|---|
| 0.1.3 | Apr 16, 2026 |
| 0.1.2 | Apr 15, 2026 |
| 0.1.1 | Apr 13, 2026 |
| 0.1.0 | Apr 13, 2026 |
#10 in #stale
170KB
4K
SLoC
dep-age
Check how old your dependencies are — for Cargo.toml, package.json, pyproject.toml, requirements.txt, go.mod, docker-compose.yml, Gemfile, and composer.json.
See at a glance which packages haven't been updated in months or years. Spot stale and abandoned dependencies before they become a security or compatibility problem.
✓ tokio "1" 3mo aging crates
✓ serde "1" 1mo fresh crates
! toml "0.5" 1.8y stale crates
✗ time "0.1" 3.2y ancient crates
Install
Cargo
cargo install dep-age
Homebrew
brew tap Ayyankhan101/dep-age
brew install dep-age
npm
npm install -g dep-age
CLI usage
# Auto-detect Cargo.toml or package.json in current directory
dep-age
# Check a specific file
dep-age Cargo.toml
dep-age path/to/package.json
dep-age path/to/pyproject.toml
dep-age path/to/requirements.txt
dep-age path/to/go.mod
dep-age path/to/docker-compose.yml
# Skip dev-dependencies
dep-age --no-dev
# Only show specific statuses
dep-age --filter ancient
dep-age --filter stale
dep-age --filter aging
# Output raw JSON (great for CI)
dep-age --json
# Fail only on stale+ancient (default: ancient)
dep-age --fail-on stale
# Use cached registry responses (1h TTL)
dep-age --cache
# Custom age thresholds (days)
dep-age --fresh 60 --aging 180 --stale 540
# Ignore specific packages
dep-age --ignore time --ignore old-crate
# Sort results
dep-age --sort age # by age (default)
dep-age --sort name # alphabetically
dep-age --sort status # grouped by status
# Output formats
dep-age --format csv # CSV output (pipeable to spreadsheets)
dep-age --format json # JSON output (alternative to --json)
dep-age --format ndjson # Newline-delimited JSON (streaming-friendly)
dep-age --format github-checks # GitHub Actions annotations
dep-age --format junit # JUnit XML for CI systems
dep-age --format sarif # SARIF for GitHub Advanced Security
# Config file (create .dep-age.toml in project root)
# [tool.dep-age]
# fresh = 60
# aging = 180
# stale = 540
# no-dev = true
# fail-on = "stale"
# ignore = ["time", "old-crate"]
# Custom config file
dep-age --config path/to/dep-age.toml
# Check mode - quiet, exit code only
dep-age --check
# Diff - show changes since last run
dep-age --diff
# Ignore file: create a `.dep-age-ignore` in your project root
# One package name per line, # comments supported
# Control parallelism (default: 10)
dep-age --concurrency 20
Status thresholds
| Icon | Status | Age |
|---|---|---|
| ✓ | fresh |
< 90 days |
| ~ | aging |
90 days – 1 yr |
| ! | stale |
1 yr – 2 yrs |
| ✗ | ancient |
2+ years |
The CLI exits with code 1 if any ancient packages are found — useful for CI pipelines.
Library usage
Add to your Cargo.toml:
[dependencies]
dep-age = "0.1.4"
tokio = { version = "1", features = ["full"] }
Check a Cargo.toml
use dep_age::{check_cargo_toml, CheckOptions};
#[tokio::main]
async fn main() {
let opts = CheckOptions::default();
let summary = check_cargo_toml("Cargo.toml", &opts).await.unwrap();
println!("Total packages: {}", summary.total);
println!("Ancient: {}", summary.ancient);
println!("Stale: {}", summary.stale);
if let Some(oldest) = &summary.oldest {
println!("Oldest: {} ({} days)", oldest.name, oldest.days_since_publish.unwrap_or(0));
}
for result in &summary.results {
println!("{} → {} ({})", result.name, result.status.as_str(),
result.days_since_publish.map(|d| format!("{}d", d)).unwrap_or_default()
);
}
}
Check a package.json
use dep_age::{check_package_json, CheckOptions};
#[tokio::main]
async fn main() {
let opts = CheckOptions {
include_dev: false, // skip devDependencies
concurrency: 20, // more parallel requests
..CheckOptions::default()
};
let summary = check_package_json("package.json", &opts).await.unwrap();
println!("Stale npm packages: {}", summary.stale);
}
Check a single package
use dep_age::{check_crate, check_npm_package, check_pypi_package, CheckOptions};
#[tokio::main]
async fn main() {
let opts = CheckOptions::default();
let result = check_crate("serde", "1", &opts).await;
println!("{}: {} days old", result.name, result.days_since_publish.unwrap_or(0));
let result = check_npm_package("lodash", "^4.17.21", &opts).await;
println!("{}: {}", result.name, result.status.as_str());
let result = check_pypi_package("requests", ">=2.28", &opts).await;
println!("{}: {}", result.name, result.status.as_str());
}
Check a pyproject.toml or requirements.txt
use dep_age::{check_pyproject_toml, check_requirements_txt, CheckOptions};
#[tokio::main]
async fn main() {
let opts = CheckOptions::default();
let summary = check_pyproject_toml("pyproject.toml", &opts).await.unwrap();
println!("Total Python deps: {}", summary.total);
let summary = check_requirements_txt("requirements.txt", &opts).await.unwrap();
println!("Ancient Python packages: {}", summary.ancient);
}
Check a go.mod
use dep_age::{check_go_mod, CheckOptions};
#[tokio::main]
async fn main() {
let opts = CheckOptions::default();
let summary = check_go_mod("go.mod", &opts).await.unwrap();
println!("Total Go modules: {}", summary.total);
println!("Stale: {}", summary.stale);
}
Check a Docker Compose file
use dep_age::{check_docker_compose, CheckOptions};
#[tokio::main]
async fn main() {
let opts = CheckOptions::default();
let summary = check_docker_compose("docker-compose.yml", &opts).await.unwrap();
println!("Total Docker images: {}", summary.total);
println!("Ancient images: {}", summary.ancient);
}
CheckOptions
| Field | Type | Default | Description |
|---|---|---|---|
include_dev |
bool |
true |
Include dev-dependencies |
concurrency |
usize |
10 |
Parallel registry requests |
threshold_fresh |
i64 |
90 |
Days below which = fresh |
threshold_aging |
i64 |
365 |
Days below which = aging |
threshold_stale |
i64 |
730 |
Days below which = stale |
ignore_list |
Vec<String> |
[] |
Package names to skip |
crates_base_url |
Option<String> |
None |
Override crates.io API URL |
npm_base_url |
Option<String> |
None |
Override npm registry URL |
pypi_base_url |
Option<String> |
None |
Override PyPI API URL |
registry_cache |
Option<RegistryCache> |
None |
Use cached registry responses |
on_progress |
Arc<dyn Fn(DepResult)> |
None |
Callback per-package results |
DepResult
pub struct DepResult {
pub name: String,
pub version_spec: String,
pub latest_version: String,
pub published_at: Option<DateTime<Utc>>,
pub days_since_publish: Option<i64>,
pub status: Status, // Fresh | Aging | Stale | Ancient | Error(String)
pub registry: Registry, // Crates | Npm | Pypi
}
DepAgeSummary
pub struct DepAgeSummary {
pub results: Vec<DepResult>,
pub total: usize,
pub fresh: usize,
pub aging: usize,
pub stale: usize,
pub ancient: usize,
pub errors: usize,
pub oldest: Option<DepResult>,
pub checked_at: DateTime<Utc>,
}
Convenience methods
// Check if all packages are fresh (no aging, stale, ancient, or errors)
if summary.is_all_fresh() {
println!("All dependencies are up to date!");
}
CI usage
GitHub Actions
- name: Check dependency ages
run: dep-age --no-dev --fail-on stale
GitHub Actions with SARIF (Advanced Security)
- name: Check dependency ages
run: dep-age --format sarif --output dep-age.sarif
- name: Upload SARIF
uses: github/codeql-action/upload-sarif@v3
with:
sarif_file: dep-age.sarif
GitHub Actions with GitHub Checks
- name: Check dependency ages
run: dep-age --format github-checks >> $GITHUB_OUTPUT
JSON output + jq
# Fail if any packages are ancient
dep-age --json | jq 'if .ancient > 0 then error("ancient packages found") else . end'
# Or just let dep-age exit 1 automatically
dep-age --no-dev && echo "all good"
# Save a report
dep-age --json > dep-age-report.json
JUnit XML (Jenkins, etc.)
dep-age --format junit > test-results.xml
With cache (faster re-runs)
# Cache registry responses (1h TTL) for faster re-runs
dep-age --cache --no-dev
Diff tracking
# First run - establishes baseline
dep-age --no-dev
# Second run - shows changes
dep-age --no-dev --diff
Contributing
- Fork and clone the repo
- Run
cargo fmt && cargo clippy -- -D warnings && cargo test - Open a PR with a clear description of the change
All PRs require green CI (formatting, clippy, tests) before merge.
Security
To report a security vulnerability, please email the maintainer. Do not open a public issue.
License
MIT
Dependencies
~13–29MB
~356K SLoC