syncable_cli/analyzer/tool_management/
installer.rs

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