ferro_cli/commands/
doctor.rs1use crate::doctor::check::{CheckCategory, CheckStatus, Report};
6use crate::doctor::registry::default_checks;
7use crate::project::find_project_root;
8use console::style;
9
10pub fn run(json: bool, deploy_only: bool) {
11 let root = match find_project_root(None) {
12 Ok(r) => r,
13 Err(_) => {
14 eprintln!(
15 "{} Cargo.toml not found (run `ferro doctor` inside a Ferro project)",
16 style("Error:").red().bold()
17 );
18 std::process::exit(1);
19 }
20 };
21
22 let all_checks = default_checks();
23 let checks: Vec<_> = if deploy_only {
24 all_checks
25 .into_iter()
26 .filter(|c| c.category() == CheckCategory::Deploy)
27 .collect()
28 } else {
29 all_checks
30 };
31 let results: Vec<_> = checks.iter().map(|c| c.run(&root)).collect();
32 let report = Report::build(results);
33
34 if json {
35 match serde_json::to_string_pretty(&report) {
36 Ok(s) => println!("{s}"),
37 Err(e) => {
38 eprintln!("{} JSON serialize error: {e}", style("Error:").red().bold());
39 std::process::exit(1);
40 }
41 }
42 } else {
43 print_human(&report);
44 }
45
46 std::process::exit(report.exit_code());
47}
48
49fn print_human(report: &Report) {
50 for c in &report.checks {
51 let icon = match c.status {
52 CheckStatus::Ok => style("✓").green().to_string(),
53 CheckStatus::Warn => style("⚠").yellow().to_string(),
54 CheckStatus::Error => style("✗").red().to_string(),
55 };
56 println!("{icon} {} — {}", style(c.name).bold(), c.message);
57 if let Some(d) = &c.details {
58 println!(" {}", style(d).dim());
59 }
60 }
61 println!();
62 let s = &report.summary;
63 let summary_line = format!("Summary: {} ok, {} warn, {} error", s.ok, s.warn, s.error);
64 let styled = match s.overall {
65 CheckStatus::Ok => style(summary_line).green().bold(),
66 CheckStatus::Warn => style(summary_line).yellow().bold(),
67 CheckStatus::Error => style(summary_line).red().bold(),
68 };
69 println!("{styled}");
70}