Skip to main content

cargo_ferris_wheel/executors/
check.rs

1//! Check command executor
2
3use console::style;
4use miette::{IntoDiagnostic, Result, WrapErr};
5
6use crate::analyzer::WorkspaceAnalyzer;
7use crate::cli::OutputFormat;
8use crate::config::CheckCyclesConfig;
9use crate::detector::CycleDetector;
10use crate::executors::CommandExecutor;
11use crate::graph::DependencyGraphBuilder;
12use crate::progress::ProgressReporter;
13use crate::reports::{
14    GitHubReportGenerator, HumanReportGenerator, JsonReportGenerator, JunitReportGenerator,
15    ReportGenerator,
16};
17
18pub struct CheckExecutor;
19
20impl CommandExecutor for CheckExecutor {
21    type Config = CheckCyclesConfig;
22
23    fn execute(config: Self::Config) -> Result<()> {
24        if config.intra_workspace {
25            eprintln!(
26                "{} Checking for intra-workspace dependency cycles...\n",
27                style("🎡").cyan()
28            );
29        } else {
30            eprintln!(
31                "{} Checking for inter-workspace dependency cycles...\n",
32                style("🎡").cyan()
33            );
34        }
35
36        // Create progress reporter if we're in an interactive terminal
37        let mut progress = if console::Term::stderr().is_term() {
38            Some(ProgressReporter::new())
39        } else {
40            None
41        };
42
43        // Discover and analyze workspaces
44        let mut analyzer = WorkspaceAnalyzer::new();
45        analyzer
46            .discover_workspaces(&config.paths, progress.as_mut())
47            .wrap_err("Failed to discover and analyze workspaces")?;
48
49        if analyzer.workspaces().is_empty() {
50            eprintln!("{} No workspaces found to analyze", style("ℹ").blue());
51            return Ok(());
52        }
53
54        // Build dependency graph
55        eprintln!("\n{} Building dependency graph...", style("🔨").blue());
56        eprintln!(
57            "  {} Exclude dev dependencies: {}",
58            style("→").dim(),
59            if config.exclude_dev {
60                style("yes").red()
61            } else {
62                style("no").green()
63            }
64        );
65        eprintln!(
66            "  {} Exclude build dependencies: {}",
67            style("→").dim(),
68            if config.exclude_build {
69                style("yes").red()
70            } else {
71                style("no").green()
72            }
73        );
74        eprintln!(
75            "  {} Exclude target dependencies: {}",
76            style("→").dim(),
77            if config.exclude_target {
78                style("yes").red()
79            } else {
80                style("no").green()
81            }
82        );
83
84        let mut graph_builder = DependencyGraphBuilder::new(
85            config.exclude_dev,
86            config.exclude_build,
87            config.exclude_target,
88        );
89
90        if config.intra_workspace {
91            graph_builder
92                .build_intra_workspace_graph(analyzer.workspaces(), progress.as_ref())
93                .wrap_err("Failed to build intra-workspace dependency graph")?;
94        } else {
95            graph_builder
96                .build_cross_workspace_graph(
97                    analyzer.workspaces(),
98                    analyzer.crate_to_workspace(),
99                    analyzer.crate_path_to_workspace(),
100                    analyzer.crate_to_paths(),
101                    progress.as_ref(),
102                )
103                .wrap_err("Failed to build cross-workspace dependency graph")?;
104        }
105
106        // Detect cycles
107        if let Some(p) = progress.as_mut() {
108            p.start_cycle_detection();
109        }
110
111        let mut detector = CycleDetector::new();
112        detector
113            .detect_cycles(graph_builder.graph())
114            .wrap_err("Failed to detect dependency cycles")?;
115
116        if let Some(p) = progress.as_ref() {
117            p.finish_cycle_detection(detector.cycle_count());
118        }
119
120        // Generate report based on format
121        let report_result = match config.format {
122            OutputFormat::Human => {
123                let generator = HumanReportGenerator::new(config.max_cycles);
124                generator.generate_report(&detector)
125            }
126            OutputFormat::Json => {
127                let generator = JsonReportGenerator::new();
128                generator.generate_report(&detector)
129            }
130            OutputFormat::Junit => {
131                let generator = JunitReportGenerator::new();
132                generator.generate_report(&detector)
133            }
134            OutputFormat::GitHub => {
135                let generator = GitHubReportGenerator::new();
136                generator.generate_report(&detector)
137            }
138        };
139
140        match report_result {
141            Ok(report) => print!("{report}"),
142            Err(e) => {
143                return Err(e)
144                    .into_diagnostic()
145                    .wrap_err("Failed to generate report");
146            }
147        }
148
149        // Exit with error code if cycles found and requested
150        if config.error_on_cycles && detector.has_cycles() {
151            std::process::exit(1);
152        }
153
154        Ok(())
155    }
156}