1use crate::{
2 analyzer::{tool_management::ToolInstaller, dependency_parser::Language},
3 cli::{ToolsCommand, OutputFormat},
4};
5use std::collections::HashMap;
6use termcolor::{ColorChoice, StandardStream, WriteColor, ColorSpec, Color};
7
8pub async fn handle_tools(command: ToolsCommand) -> crate::Result<()> {
9 match command {
10 ToolsCommand::Status { format, languages } => handle_tools_status(format, languages),
11 ToolsCommand::Install { languages, include_owasp, dry_run, yes } => {
12 handle_tools_install(languages, include_owasp, dry_run, yes)
13 }
14 ToolsCommand::Verify { languages, detailed } => handle_tools_verify(languages, detailed),
15 ToolsCommand::Guide { languages, platform } => handle_tools_guide(languages, platform),
16 }
17}
18
19fn handle_tools_status(format: OutputFormat, languages: Option<Vec<String>>) -> crate::Result<()> {
20 let mut installer = ToolInstaller::new();
21
22 let langs_to_check = get_languages_to_check(languages);
24
25 println!("š§ Checking vulnerability scanning tools status...\n");
26
27 match format {
28 OutputFormat::Table => display_status_table(&mut installer, &langs_to_check)?,
29 OutputFormat::Json => display_status_json(&mut installer, &langs_to_check),
30 }
31
32 Ok(())
33}
34
35fn handle_tools_install(
36 languages: Option<Vec<String>>,
37 include_owasp: bool,
38 dry_run: bool,
39 yes: bool,
40) -> crate::Result<()> {
41 let mut installer = ToolInstaller::new();
42
43 let langs_to_install = get_languages_to_install(languages);
45
46 if dry_run {
47 return handle_dry_run(&mut installer, &langs_to_install, include_owasp);
48 }
49
50 if !yes && !confirm_installation()? {
51 println!("Installation cancelled.");
52 return Ok(());
53 }
54
55 println!("š ļø Installing vulnerability scanning tools...");
56
57 match installer.ensure_tools_for_languages(&langs_to_install) {
58 Ok(()) => {
59 println!("ā
Tool installation completed!");
60 installer.print_tool_status(&langs_to_install);
61 print_setup_instructions();
62 }
63 Err(e) => {
64 eprintln!("ā Tool installation failed: {}", e);
65 eprintln!("\nš§ Manual installation may be required for some tools.");
66 eprintln!(" Run 'sync-ctl tools guide' for manual installation instructions.");
67 return Err(e);
68 }
69 }
70
71 Ok(())
72}
73
74fn handle_tools_verify(languages: Option<Vec<String>>, detailed: bool) -> crate::Result<()> {
75 let mut installer = ToolInstaller::new();
76
77 let langs_to_verify = get_languages_to_verify(languages);
79
80 println!("š Verifying vulnerability scanning tools...\n");
81
82 let mut all_working = true;
83
84 for language in &langs_to_verify {
85 let (tool_name, is_working) = get_tool_for_language(&mut installer, language);
86
87 print!(" {} {:?}: {}",
88 if is_working { "ā
" } else { "ā" },
89 language,
90 tool_name);
91
92 if is_working {
93 println!(" - working correctly");
94
95 if detailed {
96 print_version_info(tool_name);
97 }
98 } else {
99 println!(" - not working or missing");
100 all_working = false;
101 }
102 }
103
104 if all_working {
105 println!("\nā
All tools are working correctly!");
106 } else {
107 println!("\nā Some tools are missing or not working.");
108 println!(" Run 'sync-ctl tools install' to install missing tools.");
109 }
110
111 Ok(())
112}
113
114fn handle_tools_guide(languages: Option<Vec<String>>, platform: Option<String>) -> crate::Result<()> {
115 let target_platform = platform.unwrap_or_else(|| {
116 match std::env::consts::OS {
117 "macos" => "macOS".to_string(),
118 "linux" => "Linux".to_string(),
119 "windows" => "Windows".to_string(),
120 other => other.to_string(),
121 }
122 });
123
124 println!("š Vulnerability Scanning Tools Installation Guide");
125 println!("Platform: {}", target_platform);
126 println!("{}", "=".repeat(60));
127
128 let langs_to_show = get_languages_to_show(languages);
129
130 for language in &langs_to_show {
131 print_language_guide(language, &target_platform);
132 }
133
134 print_universal_scanners_info();
135 print_general_tips();
136
137 Ok(())
138}
139
140fn get_languages_to_check(languages: Option<Vec<String>>) -> Vec<Language> {
143 if let Some(lang_names) = languages {
144 lang_names.iter()
145 .filter_map(|name| Language::from_string(name))
146 .collect()
147 } else {
148 vec![
149 Language::Rust,
150 Language::JavaScript,
151 Language::TypeScript,
152 Language::Python,
153 Language::Go,
154 Language::Java,
155 Language::Kotlin,
156 ]
157 }
158}
159
160fn get_languages_to_install(languages: Option<Vec<String>>) -> Vec<Language> {
161 if let Some(lang_names) = languages {
162 lang_names.iter()
163 .filter_map(|name| Language::from_string(name))
164 .collect()
165 } else {
166 vec![
167 Language::Rust,
168 Language::JavaScript,
169 Language::TypeScript,
170 Language::Python,
171 Language::Go,
172 Language::Java,
173 ]
174 }
175}
176
177fn get_languages_to_verify(languages: Option<Vec<String>>) -> Vec<Language> {
178 if let Some(lang_names) = languages {
179 lang_names.iter()
180 .filter_map(|name| Language::from_string(name))
181 .collect()
182 } else {
183 vec![
184 Language::Rust,
185 Language::JavaScript,
186 Language::TypeScript,
187 Language::Python,
188 Language::Go,
189 Language::Java,
190 ]
191 }
192}
193
194fn get_languages_to_show(languages: Option<Vec<String>>) -> Vec<Language> {
195 if let Some(lang_names) = languages {
196 lang_names.iter()
197 .filter_map(|name| Language::from_string(name))
198 .collect()
199 } else {
200 vec![
201 Language::Rust,
202 Language::JavaScript,
203 Language::TypeScript,
204 Language::Python,
205 Language::Go,
206 Language::Java,
207 ]
208 }
209}
210
211fn get_tool_for_language<'a>(installer: &mut ToolInstaller, language: &Language) -> (&'a str, bool) {
212 match language {
213 Language::Rust => ("cargo-audit", installer.test_tool_availability("cargo-audit")),
214 Language::JavaScript | Language::TypeScript => {
215 if installer.test_tool_availability("bun") {
217 ("bun", true)
218 } else if installer.test_tool_availability("npm") {
219 ("npm", true)
220 } else if installer.test_tool_availability("yarn") {
221 ("yarn", true)
222 } else if installer.test_tool_availability("pnpm") {
223 ("pnpm", true)
224 } else {
225 ("npm", false)
226 }
227 },
228 Language::Python => ("pip-audit", installer.test_tool_availability("pip-audit")),
229 Language::Go => ("govulncheck", installer.test_tool_availability("govulncheck")),
230 Language::Java | Language::Kotlin => ("grype", installer.test_tool_availability("grype")),
231 _ => ("unknown", false),
232 }
233}
234
235fn display_status_table(installer: &mut ToolInstaller, langs_to_check: &[Language]) -> crate::Result<()> {
236 let mut stdout = StandardStream::stdout(ColorChoice::Always);
237
238 println!("š Vulnerability Scanning Tools Status");
239 println!("{}", "=".repeat(50));
240
241 installer.print_tool_status(langs_to_check);
243
244 println!("š Universal Scanners:");
246 let grype_available = installer.test_tool_availability("grype");
247 print!(" {} Grype: ", if grype_available { "ā
" } else { "ā" });
248 if grype_available {
249 stdout.set_color(ColorSpec::new().set_fg(Some(Color::Green)))?;
250 println!("installed");
251 } else {
252 stdout.set_color(ColorSpec::new().set_fg(Some(Color::Red)))?;
253 println!("missing - Install with: brew install grype or download from GitHub");
254 }
255 stdout.reset()?;
256
257 Ok(())
258}
259
260fn display_status_json(installer: &mut ToolInstaller, langs_to_check: &[Language]) {
261 let mut status = HashMap::new();
262
263 for language in langs_to_check {
264 let (tool_name, is_available) = get_tool_for_language(installer, language);
265
266 if tool_name == "unknown" {
267 continue;
268 }
269
270 status.insert(format!("{:?}", language), serde_json::json!({
271 "tool": tool_name,
272 "available": is_available
273 }));
274 }
275
276 println!("{}", serde_json::to_string_pretty(&status).unwrap());
277}
278
279fn handle_dry_run(installer: &mut ToolInstaller, langs_to_install: &[Language], include_owasp: bool) -> crate::Result<()> {
280 println!("š Dry run: Tools that would be installed:");
281 println!("{}", "=".repeat(50));
282
283 for language in langs_to_install {
284 let (tool_name, is_available) = get_tool_for_language(installer, language);
285
286 if tool_name == "unknown" {
287 continue;
288 }
289
290 if !is_available {
291 println!(" š¦ Would install {} for {:?}", tool_name, language);
292 } else {
293 println!(" ā
{} already installed for {:?}", tool_name, language);
294 }
295 }
296
297 if include_owasp && !installer.test_tool_availability("dependency-check") {
298 println!(" š¦ Would install OWASP Dependency Check (large download)");
299 }
300
301 Ok(())
302}
303
304fn confirm_installation() -> crate::Result<bool> {
305 use std::io::{self, Write};
306 print!("š§ Install missing vulnerability scanning tools? [y/N]: ");
307 io::stdout().flush()?;
308
309 let mut input = String::new();
310 io::stdin().read_line(&mut input)?;
311
312 Ok(input.trim().to_lowercase().starts_with('y'))
313}
314
315fn print_setup_instructions() {
316 println!("\nš” Setup Instructions:");
317 println!(" ⢠Add ~/.local/bin to your PATH for manually installed tools");
318 println!(" ⢠Add ~/go/bin to your PATH for Go tools");
319 println!(" ⢠Add to your shell profile (~/.bashrc, ~/.zshrc, etc.):");
320 println!(" export PATH=\"$HOME/.local/bin:$HOME/go/bin:$PATH\"");
321}
322
323fn print_version_info(tool_name: &str) {
324 use std::process::Command;
325 let version_result = match tool_name {
326 "cargo-audit" => Command::new("cargo").args(&["audit", "--version"]).output(),
327 "npm" => Command::new("npm").arg("--version").output(),
328 "bun" => Command::new("bun").arg("--version").output(),
329 "yarn" => Command::new("yarn").arg("--version").output(),
330 "pnpm" => Command::new("pnpm").arg("--version").output(),
331 "pip-audit" => Command::new("pip-audit").arg("--version").output(),
332 "govulncheck" => Command::new("govulncheck").arg("-version").output(),
333 "grype" => Command::new("grype").arg("version").output(),
334 _ => return,
335 };
336
337 if let Ok(output) = version_result {
338 if output.status.success() {
339 let version = String::from_utf8_lossy(&output.stdout);
340 println!(" Version: {}", version.trim());
341 }
342 }
343}
344
345fn print_language_guide(language: &Language, target_platform: &str) {
346 match language {
347 Language::Rust => {
348 println!("\nš¦ Rust - cargo-audit");
349 println!(" Install: cargo install cargo-audit");
350 println!(" Usage: cargo audit");
351 }
352 Language::JavaScript | Language::TypeScript => {
353 println!("\nš JavaScript/TypeScript - Multiple package managers");
354 println!(" Bun (recommended for speed):");
355 println!(" Install: curl -fsSL https://bun.sh/install | bash");
356 match target_platform {
357 "Windows" => println!(" Windows: irm bun.sh/install.ps1 | iex"),
358 _ => {}
359 }
360 println!(" Usage: bun audit");
361 println!(" npm (traditional):");
362 println!(" Install: Download Node.js from https://nodejs.org/");
363 match target_platform {
364 "macOS" => println!(" Package manager: brew install node"),
365 "Linux" => println!(" Package manager: sudo apt install nodejs npm (Ubuntu/Debian)"),
366 _ => {}
367 }
368 println!(" Usage: npm audit");
369 println!(" yarn:");
370 println!(" Install: npm install -g yarn");
371 println!(" Usage: yarn audit");
372 println!(" pnpm:");
373 println!(" Install: npm install -g pnpm");
374 println!(" Usage: pnpm audit");
375 }
376 Language::Python => {
377 println!("\nš Python - pip-audit");
378 println!(" Install: pipx install pip-audit (recommended)");
379 println!(" Alternative: pip3 install --user pip-audit");
380 println!(" Also available: safety (pip install safety)");
381 println!(" Usage: pip-audit");
382 }
383 Language::Go => {
384 println!("\nš¹ Go - govulncheck");
385 println!(" Install: go install golang.org/x/vuln/cmd/govulncheck@latest");
386 println!(" Note: Make sure ~/go/bin is in your PATH");
387 println!(" Usage: govulncheck ./...");
388 }
389 Language::Java => {
390 println!("\nā Java - Multiple options");
391 println!(" Grype (recommended):");
392 match target_platform {
393 "macOS" => println!(" Install: brew install anchore/grype/grype"),
394 "Linux" => println!(" Install: Download from https://github.com/anchore/grype/releases"),
395 _ => println!(" Install: Download from https://github.com/anchore/grype/releases"),
396 }
397 println!(" Usage: grype .");
398 println!(" OWASP Dependency Check:");
399 match target_platform {
400 "macOS" => println!(" Install: brew install dependency-check"),
401 _ => println!(" Install: Download from https://github.com/jeremylong/DependencyCheck/releases"),
402 }
403 println!(" Usage: dependency-check --project myproject --scan .");
404 }
405 _ => {}
406 }
407}
408
409fn print_universal_scanners_info() {
410 println!("\nš Universal Scanners:");
411 println!(" Grype: Works with multiple ecosystems");
412 println!(" Trivy: Container and filesystem scanning");
413 println!(" Snyk: Commercial solution with free tier");
414}
415
416fn print_general_tips() {
417 println!("\nš” Tips:");
418 println!(" ⢠Run 'sync-ctl tools status' to check current installation");
419 println!(" ⢠Run 'sync-ctl tools install' for automatic installation");
420 println!(" ⢠Add tool directories to your PATH for easier access");
421}