use std::collections::BTreeSet;
use std::fs;
use std::path::Path;
use anyhow::{Context, Result};
use serde_json::Value;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum PackageManager {
Npm,
Pnpm,
Yarn,
Bun,
}
impl PackageManager {
pub const fn command(self) -> &'static str {
match self {
Self::Npm => "npm",
Self::Pnpm => "pnpm",
Self::Yarn => "yarn",
Self::Bun => "bun",
}
}
}
#[derive(Debug, Clone, Default)]
pub struct ProjectFacts {
pub package_manager: Option<PackageManager>,
pub package_scripts: BTreeSet<String>,
pub has_python: bool,
pub has_rust: bool,
pub has_go: bool,
}
impl ProjectFacts {
pub fn inspect(root: &Path) -> Result<Self> {
let mut facts = Self {
package_manager: detect_package_manager(root),
has_python: ["pyproject.toml", "requirements.txt", "setup.py", "Pipfile"]
.iter()
.any(|name| root.join(name).exists()),
has_rust: root.join("Cargo.toml").is_file(),
has_go: root.join("go.mod").is_file(),
..Self::default()
};
let package_json = root.join("package.json");
if package_json.is_file() {
let text = fs::read_to_string(&package_json).context("failed to read package.json")?;
if let Ok(value) = serde_json::from_str::<Value>(&text) {
if facts.package_manager.is_none() {
facts.package_manager = value
.get("packageManager")
.and_then(Value::as_str)
.and_then(parse_package_manager);
}
if let Some(scripts) = value.get("scripts").and_then(Value::as_object) {
facts.package_scripts.extend(scripts.keys().cloned());
}
}
}
Ok(facts)
}
}
fn detect_package_manager(root: &Path) -> Option<PackageManager> {
[
("pnpm-lock.yaml", PackageManager::Pnpm),
("yarn.lock", PackageManager::Yarn),
("bun.lock", PackageManager::Bun),
("bun.lockb", PackageManager::Bun),
("package-lock.json", PackageManager::Npm),
]
.into_iter()
.find_map(|(name, manager)| root.join(name).is_file().then_some(manager))
}
fn parse_package_manager(value: &str) -> Option<PackageManager> {
match value.split('@').next()? {
"npm" => Some(PackageManager::Npm),
"pnpm" => Some(PackageManager::Pnpm),
"yarn" => Some(PackageManager::Yarn),
"bun" => Some(PackageManager::Bun),
_ => None,
}
}