use std::collections::BTreeSet;
use std::path::Path;
use anyhow::Result;
use crate::git::ChangedFile;
use crate::workspace::Package;
pub enum LockfileImpact {
Modeled(BTreeSet<String>),
Fallback(String),
}
pub trait PackageManager {
fn lockfile_names(&self) -> &'static [&'static str];
fn modeled_detail(&self) -> &'static str {
"changed dependency resolution"
}
fn lockfile_impact(
&self,
root: &Path,
base: &str,
change: &ChangedFile,
packages: &[Package],
) -> Result<LockfileImpact>;
}
const MANAGERS: &[&dyn PackageManager] = &[&crate::pnpm::Pnpm, &Npm, &Yarn, &Bun];
pub fn package_manager_for(file_name: &str) -> Option<&'static dyn PackageManager> {
MANAGERS
.iter()
.copied()
.find(|manager| manager.lockfile_names().contains(&file_name))
}
const UNMODELED_FORMAT: &str = "exact runtime consumers are not modeled for this lockfile format";
struct Npm;
impl PackageManager for Npm {
fn lockfile_names(&self) -> &'static [&'static str] {
&["package-lock.json"]
}
fn lockfile_impact(
&self,
_root: &Path,
_base: &str,
_change: &ChangedFile,
_packages: &[Package],
) -> Result<LockfileImpact> {
Ok(LockfileImpact::Fallback(UNMODELED_FORMAT.to_string()))
}
}
struct Yarn;
impl PackageManager for Yarn {
fn lockfile_names(&self) -> &'static [&'static str] {
&["yarn.lock"]
}
fn lockfile_impact(
&self,
_root: &Path,
_base: &str,
_change: &ChangedFile,
_packages: &[Package],
) -> Result<LockfileImpact> {
Ok(LockfileImpact::Fallback(UNMODELED_FORMAT.to_string()))
}
}
struct Bun;
impl PackageManager for Bun {
fn lockfile_names(&self) -> &'static [&'static str] {
&["bun.lock", "bun.lockb"]
}
fn lockfile_impact(
&self,
_root: &Path,
_base: &str,
_change: &ChangedFile,
_packages: &[Package],
) -> Result<LockfileImpact> {
Ok(LockfileImpact::Fallback(UNMODELED_FORMAT.to_string()))
}
}