syncable_cli/analyzer/tool_management/
installer.rs

1use crate::analyzer::dependency_parser::Language;
2use crate::analyzer::tool_management::{InstallationSource, ToolDetector};
3use crate::error::Result;
4use std::collections::HashMap;
5use thiserror::Error;
6
7#[derive(Debug, Error)]
8pub enum ToolInstallationError {
9    #[error("Installation failed: {0}")]
10    InstallationFailed(String),
11
12    #[error("Tool not supported on this platform: {0}")]
13    UnsupportedPlatform(String),
14
15    #[error("Command execution failed: {0}")]
16    CommandFailed(String),
17
18    #[error("IO error: {0}")]
19    Io(#[from] std::io::Error),
20}
21
22/// Tool installer for vulnerability scanning dependencies
23pub struct ToolInstaller {
24    installed_tools: HashMap<String, bool>,
25    tool_detector: ToolDetector,
26}
27
28impl ToolInstaller {
29    pub fn new() -> Self {
30        Self {
31            installed_tools: HashMap::new(),
32            tool_detector: ToolDetector::new(),
33        }
34    }
35
36    /// Ensure all required tools for vulnerability scanning are available
37    pub fn ensure_tools_for_languages(&mut self, languages: &[Language]) -> Result<()> {
38        for language in languages {
39            match language {
40                Language::Rust => self.ensure_cargo_audit()?,
41                Language::JavaScript | Language::TypeScript => {
42                    if self.ensure_bun().is_err() {
43                        self.ensure_npm()?;
44                    }
45                }
46                Language::Python => self.ensure_pip_audit()?,
47                Language::Go => self.ensure_govulncheck()?,
48                Language::Java | Language::Kotlin => self.ensure_grype()?,
49                _ => {}
50            }
51        }
52        Ok(())
53    }
54
55    /// Check if a tool is installed and available
56    fn is_tool_installed(&mut self, tool: &str) -> bool {
57        let status = self.tool_detector.detect_tool(tool);
58        self.installed_tools
59            .insert(tool.to_string(), status.available);
60        status.available
61    }
62
63    /// Test if a tool is available by running version command
64    pub fn test_tool_availability(&mut self, tool: &str) -> bool {
65        self.is_tool_installed(tool)
66    }
67
68    /// Get installation status summary
69    pub fn get_tool_status(&self) -> HashMap<String, bool> {
70        self.installed_tools.clone()
71    }
72
73    /// Print tool installation status with detailed information
74    pub fn print_tool_status(&mut self, languages: &[Language]) {
75        println!("\nšŸ”§ Vulnerability Scanning Tools Status:");
76        println!("{}", "=".repeat(50));
77
78        let tool_statuses = self.tool_detector.detect_all_vulnerability_tools(languages);
79
80        for language in languages {
81            match language {
82                Language::Rust => {
83                    self.print_single_tool_status("cargo-audit", &tool_statuses, language);
84                }
85                Language::JavaScript | Language::TypeScript => {
86                    let js_tools = ["bun", "npm", "yarn", "pnpm"];
87                    for tool in &js_tools {
88                        if let Some(status) = tool_statuses.get(*tool) {
89                            self.print_js_tool_status(tool, status, language);
90                        }
91                    }
92                }
93                Language::Python => {
94                    self.print_single_tool_status("pip-audit", &tool_statuses, language);
95                }
96                Language::Go => {
97                    self.print_single_tool_status("govulncheck", &tool_statuses, language);
98                }
99                Language::Java | Language::Kotlin => {
100                    self.print_single_tool_status("grype", &tool_statuses, language);
101                }
102                _ => continue,
103            }
104        }
105        println!();
106    }
107
108    fn print_single_tool_status(
109        &self,
110        tool_name: &str,
111        tool_statuses: &HashMap<String, crate::analyzer::tool_management::ToolStatus>,
112        language: &Language,
113    ) {
114        if let Some(status) = tool_statuses.get(tool_name) {
115            let status_icon = if status.available { "āœ…" } else { "āŒ" };
116            print!(
117                "  {} {:?}: {} {}",
118                status_icon,
119                language,
120                tool_name,
121                if status.available {
122                    "installed"
123                } else {
124                    "missing"
125                }
126            );
127
128            if status.available {
129                if let Some(ref version) = status.version {
130                    print!(" (v{})", version);
131                }
132                if let Some(ref path) = status.path {
133                    print!(" at {}", path.display());
134                }
135                match &status.installation_source {
136                    InstallationSource::SystemPath => print!(" [system]"),
137                    InstallationSource::UserLocal => print!(" [user]"),
138                    InstallationSource::CargoHome => print!(" [cargo]"),
139                    InstallationSource::GoHome => print!(" [go]"),
140                    InstallationSource::PackageManager(pm) => print!(" [{}]", pm),
141                    InstallationSource::Manual => print!(" [manual]"),
142                    InstallationSource::NotFound => {}
143                }
144            } else {
145                print!(" - Install with: ");
146                match tool_name {
147                    "cargo-audit" => print!("cargo install cargo-audit"),
148                    "pip-audit" => print!("pip install pip-audit or pipx install pip-audit"),
149                    "govulncheck" => print!("go install golang.org/x/vuln/cmd/govulncheck@latest"),
150                    "grype" => print!("brew install grype or download from GitHub"),
151                    _ => print!("check documentation"),
152                }
153            }
154            println!();
155        }
156    }
157
158    fn print_js_tool_status(
159        &self,
160        tool_name: &str,
161        status: &crate::analyzer::tool_management::ToolStatus,
162        language: &Language,
163    ) {
164        let status_icon = if status.available { "āœ…" } else { "āŒ" };
165        print!(
166            "  {} {:?}: {} {}",
167            status_icon,
168            language,
169            tool_name,
170            if status.available {
171                "installed"
172            } else {
173                "missing"
174            }
175        );
176
177        if status.available {
178            if let Some(ref version) = status.version {
179                print!(" (v{})", version);
180            }
181            if let Some(ref path) = status.path {
182                print!(" at {}", path.display());
183            }
184            match &status.installation_source {
185                InstallationSource::SystemPath => print!(" [system]"),
186                InstallationSource::UserLocal => print!(" [user]"),
187                InstallationSource::CargoHome => print!(" [cargo]"),
188                InstallationSource::GoHome => print!(" [go]"),
189                InstallationSource::PackageManager(pm) => print!(" [{}]", pm),
190                InstallationSource::Manual => print!(" [manual]"),
191                InstallationSource::NotFound => {}
192            }
193        } else {
194            print!(" - Install with: ");
195            match tool_name {
196                "bun" => print!("curl -fsSL https://bun.sh/install | bash"),
197                "npm" => print!("Install Node.js from https://nodejs.org/"),
198                "yarn" => print!("npm install -g yarn"),
199                "pnpm" => print!("npm install -g pnpm"),
200                _ => print!("check documentation"),
201            }
202        }
203        println!();
204    }
205
206    // Installation methods - these will be moved to installers/ modules
207    fn ensure_cargo_audit(&mut self) -> Result<()> {
208        use crate::analyzer::tool_management::installers::rust::install_cargo_audit;
209        install_cargo_audit(&mut self.tool_detector, &mut self.installed_tools)
210    }
211
212    fn ensure_npm(&mut self) -> Result<()> {
213        use crate::analyzer::tool_management::installers::javascript::ensure_npm;
214        ensure_npm(&mut self.tool_detector, &mut self.installed_tools)
215    }
216
217    fn ensure_bun(&mut self) -> Result<()> {
218        use crate::analyzer::tool_management::installers::javascript::install_bun;
219        install_bun(&mut self.tool_detector, &mut self.installed_tools)
220    }
221
222    fn ensure_pip_audit(&mut self) -> Result<()> {
223        use crate::analyzer::tool_management::installers::python::install_pip_audit;
224        install_pip_audit(&mut self.tool_detector, &mut self.installed_tools)
225    }
226
227    fn ensure_govulncheck(&mut self) -> Result<()> {
228        use crate::analyzer::tool_management::installers::go::install_govulncheck;
229        install_govulncheck(&mut self.tool_detector, &mut self.installed_tools)
230    }
231
232    fn ensure_grype(&mut self) -> Result<()> {
233        use crate::analyzer::tool_management::installers::java::install_grype;
234        install_grype(&mut self.tool_detector, &mut self.installed_tools)
235    }
236}