1use crate::{
2 analyzer::{self, analyze_monorepo, vulnerability_checker::VulnerabilitySeverity},
3 cli::OutputFormat,
4};
5use crate::handlers::utils::format_project_category;
6use std::process;
7use std::collections::HashMap;
8
9pub async fn handle_dependencies(
10 path: std::path::PathBuf,
11 licenses: bool,
12 vulnerabilities: bool,
13 _prod_only: bool,
14 _dev_only: bool,
15 format: OutputFormat,
16) -> crate::Result<()> {
17 let project_path = path.canonicalize()
18 .unwrap_or_else(|_| path.clone());
19
20 println!("š Analyzing dependencies: {}", project_path.display());
21
22 let monorepo_analysis = analyze_monorepo(&project_path)?;
24
25 let mut all_languages = Vec::new();
27 for project in &monorepo_analysis.projects {
28 all_languages.extend(project.analysis.languages.clone());
29 }
30
31 let dep_analysis = analyzer::dependency_parser::parse_detailed_dependencies(
33 &project_path,
34 &all_languages,
35 &analyzer::AnalysisConfig::default(),
36 ).await?;
37
38 if format == OutputFormat::Table {
39 display_dependencies_table(&dep_analysis, &monorepo_analysis, licenses, vulnerabilities, &all_languages, &project_path).await?;
40 } else if format == OutputFormat::Json {
41 let output = serde_json::json!({
43 "dependencies": dep_analysis.dependencies,
44 "total": dep_analysis.dependencies.len(),
45 });
46 println!("{}", serde_json::to_string_pretty(&output)?);
47 }
48
49 Ok(())
50}
51
52async fn display_dependencies_table(
53 dep_analysis: &analyzer::dependency_parser::DependencyAnalysis,
54 monorepo_analysis: &analyzer::MonorepoAnalysis,
55 licenses: bool,
56 vulnerabilities: bool,
57 all_languages: &[analyzer::DetectedLanguage],
58 project_path: &std::path::Path,
59) -> crate::Result<()> {
60 use termcolor::{ColorChoice, StandardStream, WriteColor, ColorSpec, Color};
61
62 let mut stdout = StandardStream::stdout(ColorChoice::Always);
63
64 println!("\nš¦ Dependency Analysis Report");
66 println!("{}", "=".repeat(80));
67
68 let total_deps: usize = dep_analysis.dependencies.len();
69 println!("Total dependencies: {}", total_deps);
70
71 if monorepo_analysis.is_monorepo {
72 println!("Projects analyzed: {}", monorepo_analysis.projects.len());
73 for project in &monorepo_analysis.projects {
74 println!(" ⢠{} ({})", project.name, format_project_category(&project.project_category));
75 }
76 }
77
78 for (name, info) in &dep_analysis.dependencies {
79 print!(" {} v{}", name, info.version);
80
81 stdout.set_color(ColorSpec::new().set_fg(Some(
83 if info.is_dev { Color::Yellow } else { Color::Green }
84 )))?;
85
86 print!(" [{}]", if info.is_dev { "dev" } else { "prod" });
87
88 stdout.reset()?;
89
90 if licenses && info.license.is_some() {
91 print!(" - License: {}", info.license.as_ref().unwrap_or(&"Unknown".to_string()));
92 }
93
94 println!();
95 }
96
97 if licenses {
98 display_license_summary(&dep_analysis.dependencies);
99 }
100
101 if vulnerabilities {
102 check_and_display_vulnerabilities(dep_analysis, all_languages, project_path).await?;
103 }
104
105 Ok(())
106}
107
108fn display_license_summary(dependencies: &analyzer::dependency_parser::DetailedDependencyMap) {
109 println!("\nš License Summary");
110 println!("{}", "-".repeat(80));
111
112 let mut license_counts: HashMap<String, usize> = HashMap::new();
113
114 for (_name, info) in dependencies {
115 if let Some(license) = &info.license {
116 *license_counts.entry(license.clone()).or_insert(0) += 1;
117 }
118 }
119
120 let mut licenses: Vec<_> = license_counts.into_iter().collect();
121 licenses.sort_by(|a, b| b.1.cmp(&a.1));
122
123 for (license, count) in licenses {
124 println!(" {}: {} packages", license, count);
125 }
126}
127
128async fn check_and_display_vulnerabilities(
129 dep_analysis: &analyzer::dependency_parser::DependencyAnalysis,
130 all_languages: &[analyzer::DetectedLanguage],
131 project_path: &std::path::Path,
132) -> crate::Result<()> {
133 use termcolor::{ColorChoice, StandardStream, WriteColor, ColorSpec, Color};
134
135 println!("\nš Checking for vulnerabilities...");
136
137 let mut deps_by_language: HashMap<analyzer::dependency_parser::Language, Vec<analyzer::dependency_parser::DependencyInfo>> = HashMap::new();
139
140 for language in all_languages {
142 let mut lang_deps = Vec::new();
143
144 for (name, info) in &dep_analysis.dependencies {
146 let matches_language = match language.name.as_str() {
148 "Rust" => info.source == "crates.io",
149 "JavaScript" | "TypeScript" => info.source == "npm",
150 "Python" => info.source == "pypi",
151 "Go" => info.source == "go modules",
152 "Java" | "Kotlin" => info.source == "maven" || info.source == "gradle",
153 _ => false,
154 };
155
156 if matches_language {
157 lang_deps.push(analyzer::dependency_parser::DependencyInfo {
159 name: name.clone(),
160 version: info.version.clone(),
161 dep_type: if info.is_dev {
162 analyzer::dependency_parser::DependencyType::Dev
163 } else {
164 analyzer::dependency_parser::DependencyType::Production
165 },
166 license: info.license.clone().unwrap_or_default(),
167 source: Some(info.source.clone()),
168 language: match language.name.as_str() {
169 "Rust" => analyzer::dependency_parser::Language::Rust,
170 "JavaScript" => analyzer::dependency_parser::Language::JavaScript,
171 "TypeScript" => analyzer::dependency_parser::Language::TypeScript,
172 "Python" => analyzer::dependency_parser::Language::Python,
173 "Go" => analyzer::dependency_parser::Language::Go,
174 "Java" => analyzer::dependency_parser::Language::Java,
175 "Kotlin" => analyzer::dependency_parser::Language::Kotlin,
176 _ => analyzer::dependency_parser::Language::Unknown,
177 },
178 });
179 }
180 }
181
182 if !lang_deps.is_empty() {
183 let lang_enum = match language.name.as_str() {
184 "Rust" => analyzer::dependency_parser::Language::Rust,
185 "JavaScript" => analyzer::dependency_parser::Language::JavaScript,
186 "TypeScript" => analyzer::dependency_parser::Language::TypeScript,
187 "Python" => analyzer::dependency_parser::Language::Python,
188 "Go" => analyzer::dependency_parser::Language::Go,
189 "Java" => analyzer::dependency_parser::Language::Java,
190 "Kotlin" => analyzer::dependency_parser::Language::Kotlin,
191 _ => analyzer::dependency_parser::Language::Unknown,
192 };
193 deps_by_language.insert(lang_enum, lang_deps);
194 }
195 }
196
197 let checker = analyzer::vulnerability_checker::VulnerabilityChecker::new();
198 match checker.check_all_dependencies(&deps_by_language, project_path).await {
199 Ok(report) => {
200 let mut stdout = StandardStream::stdout(ColorChoice::Always);
201
202 println!("\nš”ļø Vulnerability Report");
203 println!("{}", "-".repeat(80));
204 println!("Checked at: {}", report.checked_at.format("%Y-%m-%d %H:%M:%S UTC"));
205 println!("Total vulnerabilities: {}", report.total_vulnerabilities);
206
207 if report.total_vulnerabilities > 0 {
208 display_vulnerability_breakdown(&report, &mut stdout)?;
209 display_vulnerable_dependencies(&report, &mut stdout)?;
210 } else {
211 stdout.set_color(ColorSpec::new().set_fg(Some(Color::Green)))?;
212 println!("\nā
No known vulnerabilities found!");
213 stdout.reset()?;
214 }
215 }
216 Err(e) => {
217 eprintln!("Error checking vulnerabilities: {}", e);
218 process::exit(1);
219 }
220 }
221
222 Ok(())
223}
224
225fn display_vulnerability_breakdown(
226 report: &analyzer::vulnerability_checker::VulnerabilityReport,
227 stdout: &mut termcolor::StandardStream,
228) -> crate::Result<()> {
229 use termcolor::{WriteColor, ColorSpec, Color};
230
231 println!("\nSeverity Breakdown:");
232 if report.critical_count > 0 {
233 stdout.set_color(ColorSpec::new().set_fg(Some(Color::Red)).set_bold(true))?;
234 println!(" CRITICAL: {}", report.critical_count);
235 stdout.reset()?;
236 }
237 if report.high_count > 0 {
238 stdout.set_color(ColorSpec::new().set_fg(Some(Color::Red)))?;
239 println!(" HIGH: {}", report.high_count);
240 stdout.reset()?;
241 }
242 if report.medium_count > 0 {
243 stdout.set_color(ColorSpec::new().set_fg(Some(Color::Yellow)))?;
244 println!(" MEDIUM: {}", report.medium_count);
245 stdout.reset()?;
246 }
247 if report.low_count > 0 {
248 stdout.set_color(ColorSpec::new().set_fg(Some(Color::Blue)))?;
249 println!(" LOW: {}", report.low_count);
250 stdout.reset()?;
251 }
252
253 Ok(())
254}
255
256fn display_vulnerable_dependencies(
257 report: &analyzer::vulnerability_checker::VulnerabilityReport,
258 stdout: &mut termcolor::StandardStream,
259) -> crate::Result<()> {
260 use termcolor::{WriteColor, ColorSpec, Color};
261
262 println!("\nVulnerable Dependencies:");
263 for vuln_dep in &report.vulnerable_dependencies {
264 println!("\n š¦ {} v{} ({})",
265 vuln_dep.name,
266 vuln_dep.version,
267 vuln_dep.language.as_str()
268 );
269
270 for vuln in &vuln_dep.vulnerabilities {
271 print!(" ā ļø {} ", vuln.id);
272
273 stdout.set_color(ColorSpec::new().set_fg(Some(
275 match vuln.severity {
276 VulnerabilitySeverity::Critical => Color::Red,
277 VulnerabilitySeverity::High => Color::Red,
278 VulnerabilitySeverity::Medium => Color::Yellow,
279 VulnerabilitySeverity::Low => Color::Blue,
280 VulnerabilitySeverity::Info => Color::Cyan,
281 }
282 )).set_bold(vuln.severity == VulnerabilitySeverity::Critical))?;
283
284 print!("[{}]", match vuln.severity {
285 VulnerabilitySeverity::Critical => "CRITICAL",
286 VulnerabilitySeverity::High => "HIGH",
287 VulnerabilitySeverity::Medium => "MEDIUM",
288 VulnerabilitySeverity::Low => "LOW",
289 VulnerabilitySeverity::Info => "INFO",
290 });
291
292 stdout.reset()?;
293
294 println!(" - {}", vuln.title);
295
296 if let Some(ref cve) = vuln.cve {
297 println!(" CVE: {}", cve);
298 }
299 if let Some(ref patched) = vuln.patched_versions {
300 stdout.set_color(ColorSpec::new().set_fg(Some(Color::Green)))?;
301 println!(" Fix: Upgrade to {}", patched);
302 stdout.reset()?;
303 }
304 }
305 }
306
307 Ok(())
308}