use anyhow::{Context, Result};
use sha2::{Digest, Sha256};
use std::fs;
use std::path::Path;
use std::process::{Command, Stdio};
#[derive(Debug, Clone, Copy, PartialEq)]
pub enum PackageManager {
Npm,
Yarn,
Pnpm,
}
impl PackageManager {
pub fn lock_file(&self) -> &'static str {
match self {
PackageManager::Npm => "package-lock.json",
PackageManager::Yarn => "yarn.lock",
PackageManager::Pnpm => "pnpm-lock.yaml",
}
}
pub fn install_command(&self) -> &'static str {
match self {
PackageManager::Npm => "npm",
PackageManager::Yarn => "yarn",
PackageManager::Pnpm => "pnpm",
}
}
pub fn install_args(&self) -> &'static [&'static str] {
match self {
PackageManager::Npm => &["install"],
PackageManager::Yarn => &["install"],
PackageManager::Pnpm => &["install"],
}
}
pub fn name(&self) -> &'static str {
match self {
PackageManager::Npm => "npm",
PackageManager::Yarn => "yarn",
PackageManager::Pnpm => "pnpm",
}
}
}
pub fn detect_package_manager(workdir: &Path) -> Option<PackageManager> {
let managers = [
PackageManager::Pnpm,
PackageManager::Yarn,
PackageManager::Npm,
];
managers
.into_iter()
.find(|pm| workdir.join(pm.lock_file()).exists())
}
pub fn get_file_hash(path: &Path) -> Result<String> {
let content =
fs::read(path).with_context(|| format!("Failed to read file: {}", path.display()))?;
let mut hasher = Sha256::new();
hasher.update(&content);
let result = hasher.finalize();
Ok(format!("{:x}", result))
}
pub fn get_lock_file_hash(workdir: &Path) -> Result<Option<(PackageManager, String)>> {
if let Some(pm) = detect_package_manager(workdir) {
let lock_path = workdir.join(pm.lock_file());
let hash = get_file_hash(&lock_path)?;
Ok(Some((pm, hash)))
} else {
Ok(None)
}
}
pub fn run_install(pm: PackageManager, workdir: &Path) -> Result<bool> {
let mut child = Command::new(pm.install_command())
.args(pm.install_args())
.current_dir(workdir)
.stdout(Stdio::inherit())
.stderr(Stdio::inherit())
.spawn()
.with_context(|| format!("Failed to run {} install", pm.name()))?;
let status = child.wait()?;
Ok(status.success())
}
#[cfg(test)]
mod tests {
use super::*;
use std::io::Write;
use tempfile::TempDir;
#[test]
fn test_detect_npm() {
let dir = TempDir::new().unwrap();
fs::write(dir.path().join("package-lock.json"), "{}").unwrap();
assert_eq!(
detect_package_manager(dir.path()),
Some(PackageManager::Npm)
);
}
#[test]
fn test_detect_yarn() {
let dir = TempDir::new().unwrap();
fs::write(dir.path().join("yarn.lock"), "").unwrap();
assert_eq!(
detect_package_manager(dir.path()),
Some(PackageManager::Yarn)
);
}
#[test]
fn test_detect_pnpm() {
let dir = TempDir::new().unwrap();
fs::write(dir.path().join("pnpm-lock.yaml"), "").unwrap();
assert_eq!(
detect_package_manager(dir.path()),
Some(PackageManager::Pnpm)
);
}
#[test]
fn test_no_package_manager() {
let dir = TempDir::new().unwrap();
assert_eq!(detect_package_manager(dir.path()), None);
}
#[test]
fn test_file_hash() {
let dir = TempDir::new().unwrap();
let file = dir.path().join("test.txt");
let mut f = fs::File::create(&file).unwrap();
f.write_all(b"hello world").unwrap();
let hash = get_file_hash(&file).unwrap();
assert_eq!(
hash,
"b94d27b9934d3e08a52e52d7da7dabfac484efe37a5380ee9088f7ace2efcde9"
);
}
}