1use crate::error::Result;
10use chrono::Utc;
11use serde::{Deserialize, Serialize};
12use std::collections::HashMap;
13use std::path::{Path, PathBuf};
14
15pub mod context;
16pub mod dclint;
17pub mod dependency_parser;
18pub mod display;
19pub mod docker_analyzer;
20pub mod framework_detector;
21pub mod frameworks;
22pub mod hadolint;
23pub mod language_detector;
24pub mod monorepo;
25pub mod runtime;
26pub mod security;
27pub mod security_analyzer;
28pub mod tool_management;
29pub mod vulnerability;
30
31pub use dependency_parser::{DependencyAnalysis, DependencyInfo, DetailedDependencyMap};
33
34pub use security_analyzer::{
36 ComplianceStatus, SecurityAnalysisConfig, SecurityAnalyzer, SecurityCategory, SecurityFinding,
37 SecurityReport, SecuritySeverity,
38};
39
40pub use security::SecretPatternManager;
42pub use security::config::SecurityConfigPreset;
43
44pub use tool_management::{InstallationSource, ToolDetector, ToolInstaller, ToolStatus};
46
47pub use runtime::{
49 DetectionConfidence, JavaScriptRuntime, PackageManager, RuntimeDetectionResult, RuntimeDetector,
50};
51
52pub use vulnerability::types::VulnerabilitySeverity as VulnSeverity;
54pub use vulnerability::{
55 VulnerabilityChecker, VulnerabilityInfo, VulnerabilityReport, VulnerableDependency,
56};
57
58pub use monorepo::{MonorepoDetectionConfig, analyze_monorepo, analyze_monorepo_with_config};
60
61pub use docker_analyzer::{
63 ComposeFileInfo, DockerAnalysis, DockerEnvironment, DockerService, DockerfileInfo,
64 NetworkingConfig, OrchestrationPattern, analyze_docker_infrastructure,
65};
66
67#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
69pub struct DetectedLanguage {
70 pub name: String,
71 pub version: Option<String>,
72 pub confidence: f32,
73 pub files: Vec<PathBuf>,
74 pub main_dependencies: Vec<String>,
75 pub dev_dependencies: Vec<String>,
76 pub package_manager: Option<String>,
77}
78
79#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Hash)]
81pub enum TechnologyCategory {
82 MetaFramework,
84 FrontendFramework,
86 BackendFramework,
88 Library(LibraryType),
90 BuildTool,
92 Database,
94 Testing,
96 Runtime,
98 PackageManager,
100}
101
102#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Hash)]
104pub enum LibraryType {
105 UI,
107 StateManagement,
109 DataFetching,
111 Routing,
113 Styling,
115 Utility,
117 HttpClient,
119 Authentication,
121 CLI,
123 Other(String),
125}
126
127#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
129pub struct DetectedTechnology {
130 pub name: String,
131 pub version: Option<String>,
132 pub category: TechnologyCategory,
133 pub confidence: f32,
134 pub requires: Vec<String>,
136 pub conflicts_with: Vec<String>,
138 pub is_primary: bool,
140 pub file_indicators: Vec<String>,
142}
143
144#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
146pub struct ServiceAnalysis {
147 pub name: String,
148 pub path: PathBuf,
149 pub languages: Vec<DetectedLanguage>,
150 pub technologies: Vec<DetectedTechnology>,
151 pub entry_points: Vec<EntryPoint>,
152 pub ports: Vec<Port>,
153 pub environment_variables: Vec<EnvVar>,
154 pub build_scripts: Vec<BuildScript>,
155 pub service_type: ProjectType,
156}
157
158#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
160pub struct EntryPoint {
161 pub file: PathBuf,
162 pub function: Option<String>,
163 pub command: Option<String>,
164}
165
166#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Hash)]
168pub struct Port {
169 pub number: u16,
170 pub protocol: Protocol,
171 pub description: Option<String>,
172}
173
174#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Hash)]
175pub enum Protocol {
176 Tcp,
177 Udp,
178 Http,
179 Https,
180}
181
182#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
184pub struct EnvVar {
185 pub name: String,
186 pub default_value: Option<String>,
187 pub required: bool,
188 pub description: Option<String>,
189}
190
191#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
193pub enum ProjectType {
194 WebApplication,
195 ApiService,
196 CliTool,
197 Library,
198 MobileApp,
199 DesktopApp,
200 Microservice,
201 StaticSite,
202 Hybrid, Unknown,
204}
205
206#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
208pub struct BuildScript {
209 pub name: String,
210 pub command: String,
211 pub description: Option<String>,
212 pub is_default: bool,
213}
214
215pub type DependencyMap = HashMap<String, String>;
217
218#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
220pub enum ArchitectureType {
221 Monolithic,
223 Microservices,
225 Hybrid,
227}
228
229pub type DetectedFramework = DetectedTechnology;
231
232#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
234pub struct ProjectAnalysis {
235 pub project_root: PathBuf,
236 pub languages: Vec<DetectedLanguage>,
237 pub technologies: Vec<DetectedTechnology>,
239 #[deprecated(note = "Use technologies field instead")]
241 pub frameworks: Vec<DetectedFramework>,
242 pub dependencies: DependencyMap,
243 pub entry_points: Vec<EntryPoint>,
244 pub ports: Vec<Port>,
245 pub environment_variables: Vec<EnvVar>,
246 pub project_type: ProjectType,
247 pub build_scripts: Vec<BuildScript>,
248 pub services: Vec<ServiceAnalysis>,
250 pub architecture_type: ArchitectureType,
252 pub docker_analysis: Option<DockerAnalysis>,
254 pub analysis_metadata: AnalysisMetadata,
255}
256
257#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
259pub struct AnalysisMetadata {
260 pub timestamp: String,
261 pub analyzer_version: String,
262 pub analysis_duration_ms: u64,
263 pub files_analyzed: usize,
264 pub confidence_score: f32,
265}
266
267#[derive(Debug, Clone)]
269pub struct AnalysisConfig {
270 pub include_dev_dependencies: bool,
271 pub deep_analysis: bool,
272 pub ignore_patterns: Vec<String>,
273 pub max_file_size: usize,
274}
275
276impl Default for AnalysisConfig {
277 fn default() -> Self {
278 Self {
279 include_dev_dependencies: false,
280 deep_analysis: true,
281 ignore_patterns: vec![
282 "node_modules".to_string(),
283 ".git".to_string(),
284 "target".to_string(),
285 "build".to_string(),
286 ".next".to_string(),
287 "dist".to_string(),
288 ],
289 max_file_size: 1024 * 1024, }
291 }
292}
293
294#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
296pub struct ProjectInfo {
297 pub path: PathBuf,
299 pub name: String,
301 pub project_category: ProjectCategory,
303 pub analysis: ProjectAnalysis,
305}
306
307#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
309pub enum ProjectCategory {
310 Frontend,
311 Backend,
312 Api,
313 Service,
314 Library,
315 Tool,
316 Documentation,
317 Infrastructure,
318 Unknown,
319}
320
321#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
323pub struct MonorepoAnalysis {
324 pub root_path: PathBuf,
326 pub is_monorepo: bool,
328 pub projects: Vec<ProjectInfo>,
330 pub metadata: AnalysisMetadata,
332 pub technology_summary: TechnologySummary,
334}
335
336#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
338pub struct TechnologySummary {
339 pub languages: Vec<String>,
340 pub frameworks: Vec<String>,
341 pub databases: Vec<String>,
342 pub total_projects: usize,
343 pub architecture_pattern: ArchitecturePattern,
344}
345
346#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
348pub enum ArchitecturePattern {
349 Monolithic,
351 Fullstack,
353 Microservices,
355 ApiFirst,
357 EventDriven,
359 Mixed,
361}
362
363pub fn analyze_project(path: &Path) -> Result<ProjectAnalysis> {
383 analyze_project_with_config(path, &AnalysisConfig::default())
384}
385
386pub fn analyze_project_with_config(
388 path: &Path,
389 config: &AnalysisConfig,
390) -> Result<ProjectAnalysis> {
391 let start_time = std::time::Instant::now();
392
393 let project_root = crate::common::file_utils::validate_project_path(path)?;
395
396 log::info!("Starting analysis of project: {}", project_root.display());
397
398 let files = crate::common::file_utils::collect_project_files(&project_root, config)?;
400 log::debug!("Found {} files to analyze", files.len());
401
402 let languages = language_detector::detect_languages(&files, config)?;
404 let frameworks = framework_detector::detect_frameworks(&project_root, &languages, config)?;
405 let dependencies = dependency_parser::parse_dependencies(&project_root, &languages, config)?;
406 let context = context::analyze_context(&project_root, &languages, &frameworks, config)?;
407
408 let docker_analysis = analyze_docker_infrastructure(&project_root).ok();
410
411 let duration = start_time.elapsed();
412 let confidence = calculate_confidence_score(&languages, &frameworks);
413
414 #[allow(deprecated)]
415 let analysis = ProjectAnalysis {
416 project_root,
417 languages,
418 technologies: frameworks.clone(), frameworks, dependencies,
421 entry_points: context.entry_points,
422 ports: context.ports,
423 environment_variables: context.environment_variables,
424 project_type: context.project_type,
425 build_scripts: context.build_scripts,
426 services: vec![], architecture_type: ArchitectureType::Monolithic, docker_analysis,
429 analysis_metadata: AnalysisMetadata {
430 timestamp: Utc::now().to_rfc3339(),
431 analyzer_version: env!("CARGO_PKG_VERSION").to_string(),
432 analysis_duration_ms: duration.as_millis() as u64,
433 files_analyzed: files.len(),
434 confidence_score: confidence,
435 },
436 };
437
438 log::info!("Analysis completed in {}ms", duration.as_millis());
439 Ok(analysis)
440}
441
442fn calculate_confidence_score(
444 languages: &[DetectedLanguage],
445 frameworks: &[DetectedFramework],
446) -> f32 {
447 if languages.is_empty() {
448 return 0.0;
449 }
450
451 let lang_confidence: f32 =
452 languages.iter().map(|l| l.confidence).sum::<f32>() / languages.len() as f32;
453 let framework_confidence: f32 = if frameworks.is_empty() {
454 0.5 } else {
456 frameworks.iter().map(|f| f.confidence).sum::<f32>() / frameworks.len() as f32
457 };
458
459 (lang_confidence * 0.7 + framework_confidence * 0.3).min(1.0)
460}
461
462#[cfg(test)]
463mod tests {
464 use super::*;
465
466 #[test]
467 fn test_confidence_calculation() {
468 let languages = vec![DetectedLanguage {
469 name: "Rust".to_string(),
470 version: Some("1.70.0".to_string()),
471 confidence: 0.9,
472 files: vec![],
473 main_dependencies: vec!["serde".to_string(), "tokio".to_string()],
474 dev_dependencies: vec!["assert_cmd".to_string()],
475 package_manager: Some("cargo".to_string()),
476 }];
477
478 let technologies = vec![DetectedTechnology {
479 name: "Actix Web".to_string(),
480 version: Some("4.0".to_string()),
481 category: TechnologyCategory::BackendFramework,
482 confidence: 0.8,
483 requires: vec!["serde".to_string(), "tokio".to_string()],
484 conflicts_with: vec![],
485 is_primary: true,
486 file_indicators: vec![],
487 }];
488
489 let frameworks = technologies.clone(); let score = calculate_confidence_score(&languages, &frameworks);
492 assert!(score > 0.8);
493 assert!(score <= 1.0);
494 }
495
496 #[test]
497 fn test_empty_analysis() {
498 let languages = vec![];
499 let frameworks = vec![];
500 let score = calculate_confidence_score(&languages, &frameworks);
501 assert_eq!(score, 0.0);
502 }
503}