pub mod discovery;
mod pm_bun;
mod pm_npm;
mod pm_pnpm;
mod pm_yarn;
pub mod resolver;
pub mod updater;
use std::collections::{HashMap, HashSet};
use std::path::Path;
use anyhow::Result;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum PackageManager {
Npm,
Pnpm,
Yarn,
Bun,
}
impl PackageManager {
pub fn command(self) -> &'static str {
match self {
Self::Npm => "npm",
Self::Pnpm => "pnpm",
Self::Yarn => "yarn",
Self::Bun => "bun",
}
}
}
impl std::fmt::Display for PackageManager {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.write_str(self.command())
}
}
#[derive(Debug, Clone)]
pub struct OutdatedEntry {
pub current: String,
pub latest: String,
}
pub trait PackageManagerResolver {
async fn list_packages(&self, dir: &Path) -> Result<Vec<(String, String, bool)>>;
async fn outdated_packages(&self, dir: &Path) -> Result<HashMap<String, OutdatedEntry>>;
async fn update_packages(&self, dir: &Path) -> Result<String>;
}
pub(super) fn read_dev_dependency_names(dir: &Path) -> HashSet<String> {
let Ok(content) = std::fs::read_to_string(dir.join("package.json")) else {
return HashSet::new();
};
let Ok(pkg) = serde_json::from_str::<serde_json::Value>(&content) else {
return HashSet::new();
};
pkg.get("devDependencies")
.and_then(|v| v.as_object())
.map(|obj| obj.keys().cloned().collect())
.unwrap_or_default()
}
#[cfg(test)]
mod tests {
use super::*;
use std::fs;
use tempfile::TempDir;
#[test]
fn read_dev_dependency_names_with_dev_deps() {
let tmp = TempDir::new().unwrap();
fs::write(
tmp.path().join("package.json"),
r#"{
"dependencies": {"react": "^18.0.0"},
"devDependencies": {"vitest": "^1.0.0", "eslint": "^8.0.0"}
}"#,
)
.unwrap();
let names = read_dev_dependency_names(tmp.path());
assert_eq!(names.len(), 2);
assert!(names.contains("vitest"));
assert!(names.contains("eslint"));
}
#[test]
fn read_dev_dependency_names_without_dev_deps() {
let tmp = TempDir::new().unwrap();
fs::write(
tmp.path().join("package.json"),
r#"{"dependencies": {"react": "^18.0.0"}}"#,
)
.unwrap();
let names = read_dev_dependency_names(tmp.path());
assert!(names.is_empty());
}
#[test]
fn read_dev_dependency_names_malformed_json() {
let tmp = TempDir::new().unwrap();
fs::write(tmp.path().join("package.json"), "not valid json {{{").unwrap();
let names = read_dev_dependency_names(tmp.path());
assert!(names.is_empty());
}
#[test]
fn read_dev_dependency_names_nonexistent_dir() {
let names = read_dev_dependency_names(Path::new("/nonexistent/path/that/does/not/exist"));
assert!(names.is_empty());
}
}