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