syncable_cli/analyzer/tool_management/
installer.rs1use 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
22pub 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 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 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 pub fn test_tool_availability(&mut self, tool: &str) -> bool {
64 self.is_tool_installed(tool)
65 }
66
67 pub fn get_tool_status(&self) -> HashMap<String, bool> {
69 self.installed_tools.clone()
70 }
71
72 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 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}