use std::path::{Path, PathBuf};
#[derive(Debug, Clone, PartialEq)]
pub enum CheckStatus {
Ok(String),
Missing,
FileMissing,
WrongValue(String),
Unsupported(String),
}
impl CheckStatus {
#[must_use]
pub fn is_ok(&self) -> bool {
matches!(self, CheckStatus::Ok(_))
}
#[must_use]
pub fn is_unsupported(&self) -> bool {
matches!(self, CheckStatus::Unsupported(_))
}
#[must_use]
pub fn is_error(&self) -> bool {
matches!(
self,
CheckStatus::Missing | CheckStatus::FileMissing | CheckStatus::WrongValue(_)
)
}
}
impl std::fmt::Display for CheckStatus {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
CheckStatus::Ok(_) => write!(f, "OK"),
CheckStatus::Missing => write!(f, "Not set"),
CheckStatus::FileMissing => write!(f, "file missing"),
CheckStatus::WrongValue(v) => write!(f, "Current: {v}"),
CheckStatus::Unsupported(v) => write!(f, "{v}"),
}
}
}
#[derive(Debug, Clone)]
pub struct Recommendation {
pub key: String,
pub description: String,
pub expected: String,
pub status: CheckStatus,
}
impl Recommendation {
#[must_use]
pub fn needs_fix(&self) -> bool {
!self.status.is_ok() && !self.status.is_unsupported()
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum ManagerKind {
Npm,
Pnpm,
PnpmGlobal,
PnpmWorkspace,
Bun,
Uv,
Pip,
Poetry,
Aube,
Yarn,
Renovate,
Dependabot,
}
impl ManagerKind {
pub const USER_LEVEL: &[ManagerKind] = &[
ManagerKind::Npm,
ManagerKind::Pnpm,
ManagerKind::PnpmGlobal,
ManagerKind::Bun,
ManagerKind::Uv,
ManagerKind::Pip,
ManagerKind::Poetry,
ManagerKind::Aube,
ManagerKind::Yarn,
];
pub const ALL: &[ManagerKind] = &[
ManagerKind::Npm,
ManagerKind::Pnpm,
ManagerKind::PnpmGlobal,
ManagerKind::PnpmWorkspace,
ManagerKind::Bun,
ManagerKind::Uv,
ManagerKind::Pip,
ManagerKind::Poetry,
ManagerKind::Aube,
ManagerKind::Yarn,
ManagerKind::Renovate,
ManagerKind::Dependabot,
];
pub fn name(self) -> &'static str {
match self {
ManagerKind::Npm => "npm",
ManagerKind::Pnpm | ManagerKind::PnpmGlobal => "pnpm",
ManagerKind::PnpmWorkspace => "pnpm-workspace",
ManagerKind::Bun => "bun",
ManagerKind::Uv => "uv",
ManagerKind::Pip => "pip",
ManagerKind::Poetry => "poetry",
ManagerKind::Aube => "aube",
ManagerKind::Yarn => "yarn",
ManagerKind::Renovate => "renovate",
ManagerKind::Dependabot => "dependabot",
}
}
pub fn icon(self) -> &'static str {
match self {
ManagerKind::Npm => "๐ฆ",
ManagerKind::Pnpm | ManagerKind::PnpmGlobal | ManagerKind::PnpmWorkspace => "โก",
ManagerKind::Bun => "๐ฅ",
ManagerKind::Uv | ManagerKind::Pip | ManagerKind::Poetry => "๐",
ManagerKind::Aube => "๐
",
ManagerKind::Yarn => "๐งถ",
ManagerKind::Renovate => "๐",
ManagerKind::Dependabot => "๐ค",
}
}
pub fn from_name(name: &str) -> Option<ManagerKind> {
ManagerKind::ALL
.iter()
.find(|k| k.name().eq_ignore_ascii_case(name))
.copied()
}
pub fn valid_names() -> Vec<&'static str> {
let mut names: Vec<&str> = Vec::new();
for k in Self::ALL {
let n = k.name();
if n != "pnpm-workspace" && !names.contains(&n) {
names.push(n);
}
}
names
}
}
#[derive(Debug, Clone)]
pub struct ManagerInfo {
pub kind: ManagerKind,
pub version: String,
pub config_path: PathBuf,
pub recommendations: Vec<Recommendation>,
pub discovered: bool,
}
#[derive(Debug, Clone, Copy, PartialEq)]
pub enum TargetOs {
Linux,
MacOs,
Windows,
}
impl TargetOs {
pub fn current() -> Self {
if cfg!(target_os = "macos") {
TargetOs::MacOs
} else if cfg!(target_os = "windows") {
TargetOs::Windows
} else {
TargetOs::Linux
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum RepoConfigKind {
PnpmWorkspace,
Npmrc,
YarnRc,
Renovate,
Dependabot,
}
pub fn unsupported_if_configured(
mut rec: Recommendation,
manager_name: &str,
min_major: u64,
min_minor: u64,
have_version: &str,
) -> Recommendation {
if rec.status.is_ok() {
rec.status = CheckStatus::Unsupported(format!(
"requires {manager_name} \u{2265} {min_major}.{min_minor} (have {have_version})"
));
}
rec
}
pub fn unsupported_with_message_if_configured(
mut rec: Recommendation,
message: String,
) -> Recommendation {
if rec.status.is_ok() {
rec.status = CheckStatus::Unsupported(message);
}
rec
}
pub fn missing_status_for_path(path: &Path) -> CheckStatus {
if path.exists() {
CheckStatus::Missing
} else {
CheckStatus::FileMissing
}
}