syncable_cli/handlers/
tools.rs

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    // Determine which languages to check
23    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    // Determine which languages to install tools for
44    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    // Determine which languages to verify
78    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
140// Helper functions
141
142fn 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            // Check all JavaScript package managers, prioritize bun
216            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    // Use the enhanced tool status display from ToolInstaller
242    installer.print_tool_status(langs_to_check);
243    
244    // Also check universal tools
245    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}