use std::path::PathBuf;
use crate::config::{Result, TestbedError};
pub mod install;
const MISE_MIN_VERSION: &str = "2024.1";
#[derive(Debug)]
pub struct HostPrerequisites {
pub mise: ToolStatus,
pub nushell: ToolStatus,
pub pitchfork: ToolStatus,
}
impl Default for HostPrerequisites {
fn default() -> Self {
Self {
mise: ToolStatus::Missing { error: String::new() },
nushell: ToolStatus::Missing { error: String::new() },
pitchfork: ToolStatus::Missing { error: String::new() },
}
}
}
#[derive(Debug, Clone)]
pub enum ToolStatus {
AlreadyPresent { version: String },
Installed { version: String },
Missing { error: String },
}
impl ToolStatus {
pub fn is_available(&self) -> bool {
!matches!(self, ToolStatus::Missing { .. })
}
pub fn version(&self) -> Option<&str> {
match self {
ToolStatus::AlreadyPresent { version } | ToolStatus::Installed { version } => Some(version),
ToolStatus::Missing { .. } => None,
}
}
}
pub fn ensure_host_prerequisites() -> Result<HostPrerequisites> {
let mut state = HostPrerequisites::default();
state.mise = ensure_mise()?;
state.nushell = ensure_nushell(&state.mise)?;
state.pitchfork = ensure_pitchfork(&state.mise)?;
write_host_mise_config()?;
Ok(state)
}
fn ensure_mise() -> Result<ToolStatus> {
match check_tool_version("mise", MISE_MIN_VERSION) {
Ok(version) => Ok(ToolStatus::AlreadyPresent { version }),
Err(_) => {
eprintln!(" Installing mise...");
install::install_mise()?;
match check_tool_version("mise", MISE_MIN_VERSION) {
Ok(version) => Ok(ToolStatus::Installed { version }),
Err(e) => Ok(ToolStatus::Missing { error: format!("{e}") }),
}
}
}
}
fn ensure_nushell(mise_status: &ToolStatus) -> Result<ToolStatus> {
if let Ok(version) = check_tool_present("nu") { return Ok(ToolStatus::AlreadyPresent { version }) }
if !mise_status.is_available() {
return Ok(ToolStatus::Missing {
error: "mise not available to install nushell".to_string(),
});
}
eprintln!(" Installing nushell via mise...");
install::install_nushell()?;
match check_tool_present("nu") {
Ok(version) => Ok(ToolStatus::Installed { version }),
Err(e) => Ok(ToolStatus::Missing { error: format!("{e}") }),
}
}
fn ensure_pitchfork(mise_status: &ToolStatus) -> Result<ToolStatus> {
if let Ok(version) = check_tool_present("pitchfork") { return Ok(ToolStatus::AlreadyPresent { version }) }
if !mise_status.is_available() {
return Ok(ToolStatus::Missing {
error: "mise not available to install pitchfork".to_string(),
});
}
eprintln!(" Installing pitchfork via mise...");
install::install_pitchfork()?;
match check_tool_present("pitchfork") {
Ok(version) => Ok(ToolStatus::Installed { version }),
Err(e) => Ok(ToolStatus::Missing { error: format!("{e}") }),
}
}
fn check_tool_present(name: &str) -> Result<String> {
which::which(name).map_err(|e| TestbedError::Qcow2Error {
message: format!("{name} not found: {e}"),
})?;
let output = std::process::Command::new(name)
.arg("--version")
.output()
.map_err(|e| TestbedError::Qcow2Error {
message: format!("running {name} --version: {e}"),
})?;
if output.status.success() {
Ok(String::from_utf8_lossy(&output.stdout).trim().to_string())
} else {
Err(TestbedError::Qcow2Error {
message: format!("{name} --version failed"),
})
}
}
fn check_tool_version(name: &str, _min_version: &str) -> Result<String> {
check_tool_present(name)
}
fn write_host_mise_config() -> Result<()> {
let config_path = host_mise_config_path();
let parent = config_path.parent().unwrap();
if !parent.exists() {
std::fs::create_dir_all(parent).map_err(|e| TestbedError::Qcow2Error {
message: format!("creating mise config dir: {e}"),
})?;
}
if !config_path.exists() {
let content = "[tools]\nnu = \"latest\"\n\n[settings]\ncargo_binstall = true\n";
std::fs::write(&config_path, content).map_err(|e| TestbedError::Qcow2Error {
message: format!("writing mise config: {e}"),
})?;
}
Ok(())
}
pub fn host_mise_config_path() -> PathBuf {
if let Some(home) = dirs::home_dir() {
return home.join(".config").join("mise").join("config.toml");
}
PathBuf::from("/home/darkvoid/.config/mise/config.toml")
}
pub fn check_host_prerequisites() -> HostPrerequisites {
let mise = check_tool_present("mise").map_or_else(
|e| ToolStatus::Missing { error: format!("{e}") },
|v| ToolStatus::AlreadyPresent { version: v },
);
let nushell = check_tool_present("nu").map_or_else(
|e| ToolStatus::Missing { error: format!("{e}") },
|v| ToolStatus::AlreadyPresent { version: v },
);
let pitchfork = check_tool_present("pitchfork").map_or_else(
|e| ToolStatus::Missing { error: format!("{e}") },
|v| ToolStatus::AlreadyPresent { version: v },
);
HostPrerequisites {
mise,
nushell,
pitchfork,
}
}