use crate::Result;
use serde::{Deserialize, Serialize};
use std::collections::HashMap;
use std::process::Command;
#[derive(Debug, Serialize, Deserialize)]
pub struct DependencyConfig {
pub dependencies: Vec<Dependency>,
}
#[derive(Debug, Serialize, Deserialize)]
pub struct Dependency {
pub name: String,
pub check_running: bool,
#[serde(default)]
pub min_version: Option<u32>,
pub install_instructions: String,
pub description: String,
#[serde(default)]
pub environments: Option<HashMap<String, EnvironmentConfig>>,
}
#[derive(Debug, Serialize, Deserialize)]
pub struct EnvironmentConfig {
#[serde(default)]
pub install_commands: Option<Vec<String>>,
#[serde(default)]
pub verify_command: Option<String>,
#[serde(default)]
pub skip: bool,
#[serde(default)]
pub skip_reason: Option<String>,
}
pub async fn execute() -> Result<()> {
let deps_yaml = include_str!("../../deps.yaml");
let config: DependencyConfig = serde_yaml::from_str(deps_yaml)?;
println!("BioVault Dependency Check");
println!("=========================\n");
let mut all_found = true;
let mut all_running = true;
for dep in &config.dependencies {
print!("Checking {}... ", dep.name);
let exists = which::which(&dep.name).is_ok();
if !exists {
all_found = false;
println!("❌ NOT FOUND");
println!(" Description: {}", dep.description);
println!(" Installation instructions:");
for line in dep.install_instructions.lines() {
if !line.trim().is_empty() {
println!(" {}", line);
}
}
println!();
} else {
if let Some(min_version) = dep.min_version {
let version_ok = check_version(&dep.name, min_version);
if !version_ok {
all_found = false;
println!("❌ Version too old (requires {} or higher)", min_version);
println!(" Description: {}", dep.description);
println!(" Installation instructions:");
for line in dep.install_instructions.lines() {
if !line.trim().is_empty() {
println!(" {}", line);
}
}
println!();
} else {
print!("✓ Found");
}
} else {
print!("✓ Found");
}
if dep.check_running {
let is_running = check_if_running(&dep.name);
if is_running {
println!(" (running)");
} else {
all_running = false;
println!(" (NOT RUNNING)");
println!(
" To start {}, run: {}",
dep.name,
get_start_command(&dep.name)
);
}
} else {
println!();
}
}
}
println!("\n=========================");
if all_found && all_running {
println!("✓ All dependencies satisfied!");
} else if !all_found {
println!(
"⚠️ Some dependencies are missing. Please install them using the instructions above."
);
} else if !all_running {
println!("⚠️ Some services are not running. Please start them using the commands above.");
}
Ok(())
}
fn check_if_running(service: &str) -> bool {
match service {
"docker" => {
Command::new("docker")
.arg("info")
.output()
.map(|output| output.status.success())
.unwrap_or(false)
}
_ => false,
}
}
fn get_start_command(service: &str) -> String {
match service {
"docker" => "Open Docker Desktop or run 'sudo dockerd' (Linux)".to_string(),
_ => format!("Start {}", service),
}
}
fn check_version(tool: &str, min_version: u32) -> bool {
match tool {
"java" => check_java_version(min_version),
_ => true, }
}
fn check_java_version(min_version: u32) -> bool {
let output = Command::new("java").arg("-version").output();
match output {
Ok(output) => {
let version_str = String::from_utf8_lossy(&output.stderr);
if let Some(version) = parse_java_version(&version_str) {
if version >= min_version {
print!(" (version {})", version);
true
} else {
false
}
} else {
false
}
}
Err(_) => false,
}
}
fn parse_java_version(output: &str) -> Option<u32> {
for line in output.lines() {
if line.contains("version") {
if let Some(start) = line.find('"') {
if let Some(end) = line[start + 1..].find('"') {
let version_str = &line[start + 1..start + 1 + end];
if let Some(stripped) = version_str.strip_prefix("1.") {
if let Some(dot_pos) = stripped.find('.') {
if let Ok(version) = stripped[..dot_pos].parse::<u32>() {
return Some(version);
}
}
} else {
let major_part = version_str.split('.').next().unwrap_or(version_str);
if let Ok(version) = major_part.parse::<u32>() {
return Some(version);
}
}
}
}
}
}
None
}