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