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 if std::env::var("SYNCABLE_QUIET").is_ok() {
78 return;
79 }
80 println!("\nš§ Vulnerability Scanning Tools Status:");
81 println!("{}", "=".repeat(50));
82
83 let tool_statuses = self.tool_detector.detect_all_vulnerability_tools(languages);
84
85 for language in languages {
86 match language {
87 Language::Rust => {
88 self.print_single_tool_status("cargo-audit", &tool_statuses, language);
89 }
90 Language::JavaScript | Language::TypeScript => {
91 let js_tools = ["bun", "npm", "yarn", "pnpm"];
92 for tool in &js_tools {
93 if let Some(status) = tool_statuses.get(*tool) {
94 self.print_js_tool_status(tool, status, language);
95 }
96 }
97 }
98 Language::Python => {
99 self.print_single_tool_status("pip-audit", &tool_statuses, language);
100 }
101 Language::Go => {
102 self.print_single_tool_status("govulncheck", &tool_statuses, language);
103 }
104 Language::Java | Language::Kotlin => {
105 self.print_single_tool_status("grype", &tool_statuses, language);
106 }
107 _ => continue,
108 }
109 }
110 println!();
111 }
112
113 fn print_single_tool_status(
114 &self,
115 tool_name: &str,
116 tool_statuses: &HashMap<String, crate::analyzer::tool_management::ToolStatus>,
117 language: &Language,
118 ) {
119 if let Some(status) = tool_statuses.get(tool_name) {
120 let status_icon = if status.available { "ā
" } else { "ā" };
121 print!(
122 " {} {:?}: {} {}",
123 status_icon,
124 language,
125 tool_name,
126 if status.available {
127 "installed"
128 } else {
129 "missing"
130 }
131 );
132
133 if status.available {
134 if let Some(ref version) = status.version {
135 print!(" (v{})", version);
136 }
137 if let Some(ref path) = status.path {
138 print!(" at {}", path.display());
139 }
140 match &status.installation_source {
141 InstallationSource::SystemPath => print!(" [system]"),
142 InstallationSource::UserLocal => print!(" [user]"),
143 InstallationSource::CargoHome => print!(" [cargo]"),
144 InstallationSource::GoHome => print!(" [go]"),
145 InstallationSource::PackageManager(pm) => print!(" [{}]", pm),
146 InstallationSource::Manual => print!(" [manual]"),
147 InstallationSource::NotFound => {}
148 }
149 } else {
150 print!(" - Install with: ");
151 match tool_name {
152 "cargo-audit" => print!("cargo install cargo-audit"),
153 "pip-audit" => print!("pip install pip-audit or pipx install pip-audit"),
154 "govulncheck" => print!("go install golang.org/x/vuln/cmd/govulncheck@latest"),
155 "grype" => print!("brew install grype or download from GitHub"),
156 _ => print!("check documentation"),
157 }
158 }
159 println!();
160 }
161 }
162
163 fn print_js_tool_status(
164 &self,
165 tool_name: &str,
166 status: &crate::analyzer::tool_management::ToolStatus,
167 language: &Language,
168 ) {
169 let status_icon = if status.available { "ā
" } else { "ā" };
170 print!(
171 " {} {:?}: {} {}",
172 status_icon,
173 language,
174 tool_name,
175 if status.available {
176 "installed"
177 } else {
178 "missing"
179 }
180 );
181
182 if status.available {
183 if let Some(ref version) = status.version {
184 print!(" (v{})", version);
185 }
186 if let Some(ref path) = status.path {
187 print!(" at {}", path.display());
188 }
189 match &status.installation_source {
190 InstallationSource::SystemPath => print!(" [system]"),
191 InstallationSource::UserLocal => print!(" [user]"),
192 InstallationSource::CargoHome => print!(" [cargo]"),
193 InstallationSource::GoHome => print!(" [go]"),
194 InstallationSource::PackageManager(pm) => print!(" [{}]", pm),
195 InstallationSource::Manual => print!(" [manual]"),
196 InstallationSource::NotFound => {}
197 }
198 } else {
199 print!(" - Install with: ");
200 match tool_name {
201 "bun" => print!("curl -fsSL https://bun.sh/install | bash"),
202 "npm" => print!("Install Node.js from https://nodejs.org/"),
203 "yarn" => print!("npm install -g yarn"),
204 "pnpm" => print!("npm install -g pnpm"),
205 _ => print!("check documentation"),
206 }
207 }
208 println!();
209 }
210
211 fn ensure_cargo_audit(&mut self) -> Result<()> {
213 use crate::analyzer::tool_management::installers::rust::install_cargo_audit;
214 install_cargo_audit(&mut self.tool_detector, &mut self.installed_tools)
215 }
216
217 fn ensure_npm(&mut self) -> Result<()> {
218 use crate::analyzer::tool_management::installers::javascript::ensure_npm;
219 ensure_npm(&mut self.tool_detector, &mut self.installed_tools)
220 }
221
222 fn ensure_bun(&mut self) -> Result<()> {
223 use crate::analyzer::tool_management::installers::javascript::install_bun;
224 install_bun(&mut self.tool_detector, &mut self.installed_tools)
225 }
226
227 fn ensure_pip_audit(&mut self) -> Result<()> {
228 use crate::analyzer::tool_management::installers::python::install_pip_audit;
229 install_pip_audit(&mut self.tool_detector, &mut self.installed_tools)
230 }
231
232 fn ensure_govulncheck(&mut self) -> Result<()> {
233 use crate::analyzer::tool_management::installers::go::install_govulncheck;
234 install_govulncheck(&mut self.tool_detector, &mut self.installed_tools)
235 }
236
237 fn ensure_grype(&mut self) -> Result<()> {
238 use crate::analyzer::tool_management::installers::java::install_grype;
239 install_grype(&mut self.tool_detector, &mut self.installed_tools)
240 }
241}