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
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
59 .insert(tool.to_string(), status.available);
60 status.available
61 }
62
63 pub fn test_tool_availability(&mut self, tool: &str) -> bool {
65 self.is_tool_installed(tool)
66 }
67
68 pub fn get_tool_status(&self) -> HashMap<String, bool> {
70 self.installed_tools.clone()
71 }
72
73 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 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}