1use crate::error::Result;
10use chrono::Utc;
11use serde::{Deserialize, Serialize};
12use std::collections::HashMap;
13use std::path::{Path, PathBuf};
14
15pub mod dependency_parser;
16pub mod framework_detector;
17pub mod frameworks;
18pub mod language_detector;
19pub mod project_context;
20pub mod vulnerability_checker;
21pub mod security_analyzer;
22pub mod tool_installer;
23pub mod monorepo_detector;
24pub mod docker_analyzer;
25pub mod display;
26
27pub use dependency_parser::{
29 DependencyInfo, DependencyAnalysis, DetailedDependencyMap,
30 Vulnerability, VulnerabilitySeverity
31};
32
33pub use security_analyzer::{
35 SecurityAnalyzer, SecurityReport, SecurityFinding, SecuritySeverity,
36 SecurityCategory, ComplianceStatus, SecurityAnalysisConfig
37};
38
39pub use monorepo_detector::{
41 MonorepoDetectionConfig, analyze_monorepo, analyze_monorepo_with_config
42};
43
44pub use docker_analyzer::{
46 DockerAnalysis, DockerfileInfo, ComposeFileInfo, DockerService,
47 OrchestrationPattern, NetworkingConfig, DockerEnvironment,
48 analyze_docker_infrastructure
49};
50
51#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
53pub struct DetectedLanguage {
54 pub name: String,
55 pub version: Option<String>,
56 pub confidence: f32,
57 pub files: Vec<PathBuf>,
58 pub main_dependencies: Vec<String>,
59 pub dev_dependencies: Vec<String>,
60 pub package_manager: Option<String>,
61}
62
63#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Hash)]
65pub enum TechnologyCategory {
66 MetaFramework,
68 FrontendFramework,
70 BackendFramework,
72 Library(LibraryType),
74 BuildTool,
76 Database,
78 Testing,
80 Runtime,
82 PackageManager,
84}
85
86#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Hash)]
88pub enum LibraryType {
89 UI,
91 StateManagement,
93 DataFetching,
95 Routing,
97 Styling,
99 Utility,
101 HttpClient,
103 Authentication,
105 Other(String),
107}
108
109#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
111pub struct DetectedTechnology {
112 pub name: String,
113 pub version: Option<String>,
114 pub category: TechnologyCategory,
115 pub confidence: f32,
116 pub requires: Vec<String>,
118 pub conflicts_with: Vec<String>,
120 pub is_primary: bool,
122}
123
124#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
126pub struct ServiceAnalysis {
127 pub name: String,
128 pub path: PathBuf,
129 pub languages: Vec<DetectedLanguage>,
130 pub technologies: Vec<DetectedTechnology>,
131 pub entry_points: Vec<EntryPoint>,
132 pub ports: Vec<Port>,
133 pub environment_variables: Vec<EnvVar>,
134 pub build_scripts: Vec<BuildScript>,
135 pub service_type: ProjectType,
136}
137
138#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
140pub struct EntryPoint {
141 pub file: PathBuf,
142 pub function: Option<String>,
143 pub command: Option<String>,
144}
145
146#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Hash)]
148pub struct Port {
149 pub number: u16,
150 pub protocol: Protocol,
151 pub description: Option<String>,
152}
153
154#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Hash)]
155pub enum Protocol {
156 Tcp,
157 Udp,
158 Http,
159 Https,
160}
161
162#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
164pub struct EnvVar {
165 pub name: String,
166 pub default_value: Option<String>,
167 pub required: bool,
168 pub description: Option<String>,
169}
170
171#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
173pub enum ProjectType {
174 WebApplication,
175 ApiService,
176 CliTool,
177 Library,
178 MobileApp,
179 DesktopApp,
180 Microservice,
181 StaticSite,
182 Hybrid, Unknown,
184}
185
186#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
188pub struct BuildScript {
189 pub name: String,
190 pub command: String,
191 pub description: Option<String>,
192 pub is_default: bool,
193}
194
195pub type DependencyMap = HashMap<String, String>;
197
198#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
200pub enum ArchitectureType {
201 Monolithic,
203 Microservices,
205 Hybrid,
207}
208
209pub type DetectedFramework = DetectedTechnology;
211
212#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
214pub struct ProjectAnalysis {
215 pub project_root: PathBuf,
216 pub languages: Vec<DetectedLanguage>,
217 pub technologies: Vec<DetectedTechnology>,
219 #[deprecated(note = "Use technologies field instead")]
221 pub frameworks: Vec<DetectedFramework>,
222 pub dependencies: DependencyMap,
223 pub entry_points: Vec<EntryPoint>,
224 pub ports: Vec<Port>,
225 pub environment_variables: Vec<EnvVar>,
226 pub project_type: ProjectType,
227 pub build_scripts: Vec<BuildScript>,
228 pub services: Vec<ServiceAnalysis>,
230 pub architecture_type: ArchitectureType,
232 pub docker_analysis: Option<DockerAnalysis>,
234 pub analysis_metadata: AnalysisMetadata,
235}
236
237#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
239pub struct AnalysisMetadata {
240 pub timestamp: String,
241 pub analyzer_version: String,
242 pub analysis_duration_ms: u64,
243 pub files_analyzed: usize,
244 pub confidence_score: f32,
245}
246
247#[derive(Debug, Clone)]
249pub struct AnalysisConfig {
250 pub include_dev_dependencies: bool,
251 pub deep_analysis: bool,
252 pub ignore_patterns: Vec<String>,
253 pub max_file_size: usize,
254}
255
256impl Default for AnalysisConfig {
257 fn default() -> Self {
258 Self {
259 include_dev_dependencies: false,
260 deep_analysis: true,
261 ignore_patterns: vec![
262 "node_modules".to_string(),
263 ".git".to_string(),
264 "target".to_string(),
265 "build".to_string(),
266 ".next".to_string(),
267 "dist".to_string(),
268 ],
269 max_file_size: 1024 * 1024, }
271 }
272}
273
274#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
276pub struct ProjectInfo {
277 pub path: PathBuf,
279 pub name: String,
281 pub project_category: ProjectCategory,
283 pub analysis: ProjectAnalysis,
285}
286
287#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
289pub enum ProjectCategory {
290 Frontend,
291 Backend,
292 Api,
293 Service,
294 Library,
295 Tool,
296 Documentation,
297 Infrastructure,
298 Unknown,
299}
300
301#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
303pub struct MonorepoAnalysis {
304 pub root_path: PathBuf,
306 pub is_monorepo: bool,
308 pub projects: Vec<ProjectInfo>,
310 pub metadata: AnalysisMetadata,
312 pub technology_summary: TechnologySummary,
314}
315
316#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
318pub struct TechnologySummary {
319 pub languages: Vec<String>,
320 pub frameworks: Vec<String>,
321 pub databases: Vec<String>,
322 pub total_projects: usize,
323 pub architecture_pattern: ArchitecturePattern,
324}
325
326#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
328pub enum ArchitecturePattern {
329 Monolithic,
331 Fullstack,
333 Microservices,
335 ApiFirst,
337 EventDriven,
339 Mixed,
341}
342
343pub fn analyze_project(path: &Path) -> Result<ProjectAnalysis> {
363 analyze_project_with_config(path, &AnalysisConfig::default())
364}
365
366pub fn analyze_project_with_config(path: &Path, config: &AnalysisConfig) -> Result<ProjectAnalysis> {
368 let start_time = std::time::Instant::now();
369
370 let project_root = crate::common::file_utils::validate_project_path(path)?;
372
373 log::info!("Starting analysis of project: {}", project_root.display());
374
375 let files = crate::common::file_utils::collect_project_files(&project_root, config)?;
377 log::debug!("Found {} files to analyze", files.len());
378
379 let languages = language_detector::detect_languages(&files, config)?;
381 let frameworks = framework_detector::detect_frameworks(&project_root, &languages, config)?;
382 let dependencies = dependency_parser::parse_dependencies(&project_root, &languages, config)?;
383 let context = project_context::analyze_context(&project_root, &languages, &frameworks, config)?;
384
385 let docker_analysis = analyze_docker_infrastructure(&project_root).ok();
387
388 let duration = start_time.elapsed();
389 let confidence = calculate_confidence_score(&languages, &frameworks);
390
391 #[allow(deprecated)]
392 let analysis = ProjectAnalysis {
393 project_root,
394 languages,
395 technologies: frameworks.clone(), frameworks, dependencies,
398 entry_points: context.entry_points,
399 ports: context.ports,
400 environment_variables: context.environment_variables,
401 project_type: context.project_type,
402 build_scripts: context.build_scripts,
403 services: vec![], architecture_type: ArchitectureType::Monolithic, docker_analysis,
406 analysis_metadata: AnalysisMetadata {
407 timestamp: Utc::now().to_rfc3339(),
408 analyzer_version: env!("CARGO_PKG_VERSION").to_string(),
409 analysis_duration_ms: duration.as_millis() as u64,
410 files_analyzed: files.len(),
411 confidence_score: confidence,
412 },
413 };
414
415 log::info!("Analysis completed in {}ms", duration.as_millis());
416 Ok(analysis)
417}
418
419fn calculate_confidence_score(
421 languages: &[DetectedLanguage],
422 frameworks: &[DetectedFramework],
423) -> f32 {
424 if languages.is_empty() {
425 return 0.0;
426 }
427
428 let lang_confidence: f32 = languages.iter().map(|l| l.confidence).sum::<f32>() / languages.len() as f32;
429 let framework_confidence: f32 = if frameworks.is_empty() {
430 0.5 } else {
432 frameworks.iter().map(|f| f.confidence).sum::<f32>() / frameworks.len() as f32
433 };
434
435 (lang_confidence * 0.7 + framework_confidence * 0.3).min(1.0)
436}
437
438#[cfg(test)]
439mod tests {
440 use super::*;
441
442 #[test]
443 fn test_confidence_calculation() {
444 let languages = vec![
445 DetectedLanguage {
446 name: "Rust".to_string(),
447 version: Some("1.70.0".to_string()),
448 confidence: 0.9,
449 files: vec![],
450 main_dependencies: vec!["serde".to_string(), "tokio".to_string()],
451 dev_dependencies: vec!["assert_cmd".to_string()],
452 package_manager: Some("cargo".to_string()),
453 }
454 ];
455
456 let technologies = vec![
457 DetectedTechnology {
458 name: "Actix Web".to_string(),
459 version: Some("4.0".to_string()),
460 category: TechnologyCategory::BackendFramework,
461 confidence: 0.8,
462 requires: vec!["serde".to_string(), "tokio".to_string()],
463 conflicts_with: vec![],
464 is_primary: true,
465 }
466 ];
467
468 let frameworks = technologies.clone(); let score = calculate_confidence_score(&languages, &frameworks);
471 assert!(score > 0.8);
472 assert!(score <= 1.0);
473 }
474
475 #[test]
476 fn test_empty_analysis() {
477 let languages = vec![];
478 let frameworks = vec![];
479 let score = calculate_confidence_score(&languages, &frameworks);
480 assert_eq!(score, 0.0);
481 }
482}