use crate::core::ProjectConfig;
use crate::doctor::Doctor;
use super::DoctorArgs;
use anyhow::Result;
use colored::*;
pub fn run(args: &DoctorArgs) -> Result<()> {
let config = ProjectConfig::from_default_location();
let mut doctor = Doctor::new(&config, args.verbose)?;
eprintln!("{}", "🩺 Forge Guard — Project Doctor".bold());
eprintln!(" Analyzing project health...\n");
let mut all_pass = true;
print_check("Foundry installation");
match doctor.check_foundry_version() {
Ok(version) => println!(" {} Foundry {}\n", "✅".green(), version),
Err(e) => {
println!(" {} {}\n", "❌".red(), e);
all_pass = false;
}
}
print_check("Solidity version");
match doctor.check_solidity_version() {
Ok(version) => println!(" {} Solidity {}\n", "✅".green(), version),
Err(e) => {
println!(" {} {}\n", "❌".red(), e);
all_pass = false;
}
}
print_check("Project structure");
let structure_issues = doctor.check_project_structure()?;
if structure_issues.is_empty() {
println!(" {} Project structure looks good\n", "✅".green());
} else {
for issue in &structure_issues {
println!(" {} {}\n", "⚠️".yellow(), issue);
}
}
print_check("Dependency vulnerabilities");
let dep_issues = doctor.check_dependencies()?;
if dep_issues.is_empty() {
println!(" {} No vulnerable dependencies found\n", "✅".green());
} else {
for issue in &dep_issues {
println!(" {} {}\n", "⚠️".yellow(), issue);
}
}
print_check("Compiler settings");
let compiler_issues = doctor.check_compiler_settings()?;
if compiler_issues.is_empty() {
println!(" {} Compiler settings OK\n", "✅".green());
} else {
for issue in &compiler_issues {
println!(" {} {}\n", "⚠️".yellow(), issue);
}
}
if !config.offline {
print_check("RPC connectivity");
match doctor.check_rpc_connectivity(&config.chain) {
Ok(_) => println!(" {} RPC reachable\n", "✅".green()),
Err(e) => println!(" {} {}\n", "❌".red(), e),
}
}
print_check("Security configuration");
let sec_issues = doctor.check_security_config()?;
if sec_issues.is_empty() {
println!(" {} Security configuration OK\n", "✅".green());
} else {
for issue in &sec_issues {
println!(" {} {}\n", "⚠️".yellow(), issue);
}
}
println!(
"{}",
"═══════════════════════════════════════".bright_blue()
);
if all_pass {
println!("{}", "✅ Project health: GOOD".green().bold());
} else {
println!("{}", "⚠️ Project health: ISSUES FOUND".yellow().bold());
}
println!(
"{}",
"═══════════════════════════════════════".bright_blue()
);
if args.shared.json {
let report = doctor.generate_report(all_pass);
println!("{}", serde_json::to_string_pretty(&report)?);
}
Ok(())
}
fn print_check(label: &str) {
print!(" 🔍 {}... ", label.bold());
std::io::Write::flush(&mut std::io::stdout()).ok();
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_doctor_arg_defaults() {
let args = DoctorArgs {
shared: super::super::SharedFlags {
chain: "ethereum".into(),
project: std::path::PathBuf::from("."),
json: false,
markdown: false,
strict: false,
offline: false,
production: false,
report: false,
parallelism: 4,
},
fix: false,
verbose: false,
check: None,
};
assert!(!args.fix);
assert!(!args.verbose);
assert!(args.check.is_none());
}
#[test]
fn test_doctor_with_fix_and_verbose() {
let args = DoctorArgs {
shared: super::super::SharedFlags::default(),
fix: true,
verbose: true,
check: Some("dependencies".into()),
};
assert!(args.fix);
assert!(args.verbose);
assert_eq!(args.check.as_deref(), Some("dependencies"));
}
#[test]
fn test_doctor_with_json_output() {
let mut shared = super::super::SharedFlags::default();
shared.json = true;
let args = DoctorArgs {
shared,
fix: false,
verbose: false,
check: None,
};
assert!(args.shared.json);
}
}