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