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 helmlint;
24pub mod k8s_optimize;
25pub mod kubelint;
26pub mod language_detector;
27pub mod monorepo;
28pub mod runtime;
29pub mod security;
30pub mod security_analyzer;
31pub mod tool_management;
32pub mod vulnerability;
33
34pub use dependency_parser::{DependencyAnalysis, DependencyInfo, DetailedDependencyMap};
36
37pub use security_analyzer::{
39 ComplianceStatus, SecurityAnalysisConfig, SecurityAnalyzer, SecurityCategory, SecurityFinding,
40 SecurityReport, SecuritySeverity,
41};
42
43pub use security::SecretPatternManager;
45pub use security::config::SecurityConfigPreset;
46
47pub use tool_management::{InstallationSource, ToolDetector, ToolInstaller, ToolStatus};
49
50pub use runtime::{
52 DetectionConfidence, JavaScriptRuntime, PackageManager, RuntimeDetectionResult, RuntimeDetector,
53};
54
55pub use vulnerability::types::VulnerabilitySeverity as VulnSeverity;
57pub use vulnerability::{
58 VulnerabilityChecker, VulnerabilityInfo, VulnerabilityReport, VulnerableDependency,
59};
60
61pub use monorepo::{MonorepoDetectionConfig, analyze_monorepo, analyze_monorepo_with_config};
63
64pub use docker_analyzer::{
66 ComposeFileInfo, DiscoveredDockerfile, DockerAnalysis, DockerEnvironment, DockerService,
67 DockerfileInfo, NetworkingConfig, OrchestrationPattern, analyze_docker_infrastructure,
68 discover_dockerfiles_for_deployment,
69};
70
71#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
73pub struct DetectedLanguage {
74 pub name: String,
75 pub version: Option<String>,
76 pub confidence: f32,
77 pub files: Vec<PathBuf>,
78 pub main_dependencies: Vec<String>,
79 pub dev_dependencies: Vec<String>,
80 pub package_manager: Option<String>,
81}
82
83#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Hash)]
85pub enum TechnologyCategory {
86 MetaFramework,
88 FrontendFramework,
90 BackendFramework,
92 Library(LibraryType),
94 BuildTool,
96 Database,
98 Testing,
100 Runtime,
102 PackageManager,
104}
105
106#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Hash)]
108pub enum LibraryType {
109 UI,
111 StateManagement,
113 DataFetching,
115 Routing,
117 Styling,
119 Utility,
121 HttpClient,
123 Authentication,
125 CLI,
127 Other(String),
129}
130
131#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
133pub struct DetectedTechnology {
134 pub name: String,
135 pub version: Option<String>,
136 pub category: TechnologyCategory,
137 pub confidence: f32,
138 pub requires: Vec<String>,
140 pub conflicts_with: Vec<String>,
142 pub is_primary: bool,
144 pub file_indicators: Vec<String>,
146}
147
148#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
150pub struct ServiceAnalysis {
151 pub name: String,
152 pub path: PathBuf,
153 pub languages: Vec<DetectedLanguage>,
154 pub technologies: Vec<DetectedTechnology>,
155 pub entry_points: Vec<EntryPoint>,
156 pub ports: Vec<Port>,
157 pub environment_variables: Vec<EnvVar>,
158 pub build_scripts: Vec<BuildScript>,
159 pub service_type: ProjectType,
160}
161
162#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
164pub struct EntryPoint {
165 pub file: PathBuf,
166 pub function: Option<String>,
167 pub command: Option<String>,
168}
169
170#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Hash)]
172pub enum PortSource {
173 Dockerfile,
175 DockerCompose,
177 PackageJson,
179 FrameworkDefault,
181 EnvVar,
183 SourceCode,
185 ConfigFile,
187}
188
189impl PortSource {
190 pub fn description(&self) -> &'static str {
192 match self {
193 PortSource::Dockerfile => "Dockerfile EXPOSE",
194 PortSource::DockerCompose => "docker-compose.yml",
195 PortSource::PackageJson => "package.json scripts",
196 PortSource::FrameworkDefault => "framework default",
197 PortSource::EnvVar => "environment variable",
198 PortSource::SourceCode => "source code",
199 PortSource::ConfigFile => "configuration file",
200 }
201 }
202}
203
204#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Hash)]
206pub struct Port {
207 pub number: u16,
208 pub protocol: Protocol,
209 pub description: Option<String>,
210 #[serde(skip_serializing_if = "Option::is_none")]
212 pub source: Option<PortSource>,
213}
214
215impl Port {
216 pub fn with_source(number: u16, protocol: Protocol, source: PortSource) -> Self {
218 Self {
219 number,
220 protocol,
221 description: None,
222 source: Some(source),
223 }
224 }
225
226 pub fn with_source_and_description(
228 number: u16,
229 protocol: Protocol,
230 source: PortSource,
231 description: impl Into<String>,
232 ) -> Self {
233 Self {
234 number,
235 protocol,
236 description: Some(description.into()),
237 source: Some(source),
238 }
239 }
240}
241
242#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Hash)]
243pub enum Protocol {
244 Tcp,
245 Udp,
246 Http,
247 Https,
248}
249
250#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
252pub enum HealthEndpointSource {
253 CodePattern,
255 FrameworkDefault,
257 ConfigFile,
259}
260
261impl HealthEndpointSource {
262 pub fn description(&self) -> &'static str {
264 match self {
265 HealthEndpointSource::CodePattern => "source code analysis",
266 HealthEndpointSource::FrameworkDefault => "framework convention",
267 HealthEndpointSource::ConfigFile => "configuration file",
268 }
269 }
270}
271
272#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
274pub struct HealthEndpoint {
275 pub path: String,
277 pub confidence: f32,
279 pub source: HealthEndpointSource,
281 pub description: Option<String>,
283}
284
285impl HealthEndpoint {
286 pub fn from_code(path: impl Into<String>, confidence: f32) -> Self {
288 Self {
289 path: path.into(),
290 confidence,
291 source: HealthEndpointSource::CodePattern,
292 description: None,
293 }
294 }
295
296 pub fn from_framework(path: impl Into<String>, framework: &str) -> Self {
298 Self {
299 path: path.into(),
300 confidence: 0.7, source: HealthEndpointSource::FrameworkDefault,
302 description: Some(format!("{} default health endpoint", framework)),
303 }
304 }
305}
306
307#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
309pub struct EnvVar {
310 pub name: String,
311 pub default_value: Option<String>,
312 pub required: bool,
313 pub description: Option<String>,
314}
315
316#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
318pub enum ProjectType {
319 WebApplication,
320 ApiService,
321 CliTool,
322 Library,
323 MobileApp,
324 DesktopApp,
325 Microservice,
326 StaticSite,
327 Hybrid, Unknown,
329}
330
331#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
333pub struct BuildScript {
334 pub name: String,
335 pub command: String,
336 pub description: Option<String>,
337 pub is_default: bool,
338}
339
340#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Default)]
342pub struct InfrastructurePresence {
343 pub has_kubernetes: bool,
345 pub kubernetes_paths: Vec<PathBuf>,
347 pub has_helm: bool,
349 pub helm_chart_paths: Vec<PathBuf>,
351 pub has_docker_compose: bool,
353 pub has_terraform: bool,
355 pub terraform_paths: Vec<PathBuf>,
357 pub has_deployment_config: bool,
359 pub summary: Option<String>,
361}
362
363impl InfrastructurePresence {
364 pub fn has_any(&self) -> bool {
366 self.has_kubernetes || self.has_helm || self.has_docker_compose || self.has_terraform || self.has_deployment_config
367 }
368
369 pub fn detected_types(&self) -> Vec<&'static str> {
371 let mut types = Vec::new();
372 if self.has_kubernetes { types.push("Kubernetes"); }
373 if self.has_helm { types.push("Helm"); }
374 if self.has_docker_compose { types.push("Docker Compose"); }
375 if self.has_terraform { types.push("Terraform"); }
376 if self.has_deployment_config { types.push("Syncable Config"); }
377 types
378 }
379}
380
381pub type DependencyMap = HashMap<String, String>;
383
384#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
386pub enum ArchitectureType {
387 Monolithic,
389 Microservices,
391 Hybrid,
393}
394
395pub type DetectedFramework = DetectedTechnology;
397
398#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
400pub struct ProjectAnalysis {
401 pub project_root: PathBuf,
402 pub languages: Vec<DetectedLanguage>,
403 pub technologies: Vec<DetectedTechnology>,
405 #[deprecated(note = "Use technologies field instead")]
407 pub frameworks: Vec<DetectedFramework>,
408 pub dependencies: DependencyMap,
409 pub entry_points: Vec<EntryPoint>,
410 pub ports: Vec<Port>,
411 #[serde(default)]
413 pub health_endpoints: Vec<HealthEndpoint>,
414 pub environment_variables: Vec<EnvVar>,
415 pub project_type: ProjectType,
416 pub build_scripts: Vec<BuildScript>,
417 pub services: Vec<ServiceAnalysis>,
419 pub architecture_type: ArchitectureType,
421 pub docker_analysis: Option<DockerAnalysis>,
423 #[serde(default)]
425 pub infrastructure: Option<InfrastructurePresence>,
426 pub analysis_metadata: AnalysisMetadata,
427}
428
429#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
431pub struct AnalysisMetadata {
432 pub timestamp: String,
433 pub analyzer_version: String,
434 pub analysis_duration_ms: u64,
435 pub files_analyzed: usize,
436 pub confidence_score: f32,
437}
438
439#[derive(Debug, Clone)]
441pub struct AnalysisConfig {
442 pub include_dev_dependencies: bool,
443 pub deep_analysis: bool,
444 pub ignore_patterns: Vec<String>,
445 pub max_file_size: usize,
446}
447
448impl Default for AnalysisConfig {
449 fn default() -> Self {
450 Self {
451 include_dev_dependencies: false,
452 deep_analysis: true,
453 ignore_patterns: vec![
454 "node_modules".to_string(),
455 ".git".to_string(),
456 "target".to_string(),
457 "build".to_string(),
458 ".next".to_string(),
459 "dist".to_string(),
460 ],
461 max_file_size: 1024 * 1024, }
463 }
464}
465
466#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
468pub struct ProjectInfo {
469 pub path: PathBuf,
471 pub name: String,
473 pub project_category: ProjectCategory,
475 pub analysis: ProjectAnalysis,
477}
478
479#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
481pub enum ProjectCategory {
482 Frontend,
483 Backend,
484 Api,
485 Service,
486 Library,
487 Tool,
488 Documentation,
489 Infrastructure,
490 Unknown,
491}
492
493#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
495pub struct MonorepoAnalysis {
496 pub root_path: PathBuf,
498 pub is_monorepo: bool,
500 pub projects: Vec<ProjectInfo>,
502 pub metadata: AnalysisMetadata,
504 pub technology_summary: TechnologySummary,
506}
507
508#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
510pub struct TechnologySummary {
511 pub languages: Vec<String>,
512 pub frameworks: Vec<String>,
513 pub databases: Vec<String>,
514 pub total_projects: usize,
515 pub architecture_pattern: ArchitecturePattern,
516}
517
518#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
520pub enum ArchitecturePattern {
521 Monolithic,
523 Fullstack,
525 Microservices,
527 ApiFirst,
529 EventDriven,
531 Mixed,
533}
534
535pub fn analyze_project(path: &Path) -> Result<ProjectAnalysis> {
555 analyze_project_with_config(path, &AnalysisConfig::default())
556}
557
558pub fn analyze_project_with_config(
560 path: &Path,
561 config: &AnalysisConfig,
562) -> Result<ProjectAnalysis> {
563 let start_time = std::time::Instant::now();
564
565 let project_root = crate::common::file_utils::validate_project_path(path)?;
567
568 log::info!("Starting analysis of project: {}", project_root.display());
569
570 let files = crate::common::file_utils::collect_project_files(&project_root, config)?;
572 log::debug!("Found {} files to analyze", files.len());
573
574 let languages = language_detector::detect_languages(&files, config)?;
576 let frameworks = framework_detector::detect_frameworks(&project_root, &languages, config)?;
577 let dependencies = dependency_parser::parse_dependencies(&project_root, &languages, config)?;
578 let context = context::analyze_context(&project_root, &languages, &frameworks, config)?;
579
580 let health_endpoints = context::detect_health_endpoints(&project_root, &frameworks, config.max_file_size);
582
583 let infrastructure = context::detect_infrastructure(&project_root);
585
586 let docker_analysis = analyze_docker_infrastructure(&project_root).ok();
588
589 let duration = start_time.elapsed();
590 let confidence = calculate_confidence_score(&languages, &frameworks);
591
592 #[allow(deprecated)]
593 let analysis = ProjectAnalysis {
594 project_root,
595 languages,
596 technologies: frameworks.clone(), frameworks, dependencies,
599 entry_points: context.entry_points,
600 ports: context.ports,
601 health_endpoints,
602 environment_variables: context.environment_variables,
603 project_type: context.project_type,
604 build_scripts: context.build_scripts,
605 services: vec![], architecture_type: ArchitectureType::Monolithic, docker_analysis,
608 infrastructure: Some(infrastructure),
609 analysis_metadata: AnalysisMetadata {
610 timestamp: Utc::now().to_rfc3339(),
611 analyzer_version: env!("CARGO_PKG_VERSION").to_string(),
612 analysis_duration_ms: duration.as_millis() as u64,
613 files_analyzed: files.len(),
614 confidence_score: confidence,
615 },
616 };
617
618 log::info!("Analysis completed in {}ms", duration.as_millis());
619 Ok(analysis)
620}
621
622fn calculate_confidence_score(
624 languages: &[DetectedLanguage],
625 frameworks: &[DetectedFramework],
626) -> f32 {
627 if languages.is_empty() {
628 return 0.0;
629 }
630
631 let lang_confidence: f32 =
632 languages.iter().map(|l| l.confidence).sum::<f32>() / languages.len() as f32;
633 let framework_confidence: f32 = if frameworks.is_empty() {
634 0.5 } else {
636 frameworks.iter().map(|f| f.confidence).sum::<f32>() / frameworks.len() as f32
637 };
638
639 (lang_confidence * 0.7 + framework_confidence * 0.3).min(1.0)
640}
641
642#[cfg(test)]
643mod tests {
644 use super::*;
645
646 #[test]
647 fn test_confidence_calculation() {
648 let languages = vec![DetectedLanguage {
649 name: "Rust".to_string(),
650 version: Some("1.70.0".to_string()),
651 confidence: 0.9,
652 files: vec![],
653 main_dependencies: vec!["serde".to_string(), "tokio".to_string()],
654 dev_dependencies: vec!["assert_cmd".to_string()],
655 package_manager: Some("cargo".to_string()),
656 }];
657
658 let technologies = vec![DetectedTechnology {
659 name: "Actix Web".to_string(),
660 version: Some("4.0".to_string()),
661 category: TechnologyCategory::BackendFramework,
662 confidence: 0.8,
663 requires: vec!["serde".to_string(), "tokio".to_string()],
664 conflicts_with: vec![],
665 is_primary: true,
666 file_indicators: vec![],
667 }];
668
669 let frameworks = technologies.clone(); let score = calculate_confidence_score(&languages, &frameworks);
672 assert!(score > 0.8);
673 assert!(score <= 1.0);
674 }
675
676 #[test]
677 fn test_empty_analysis() {
678 let languages = vec![];
679 let frameworks = vec![];
680 let score = calculate_confidence_score(&languages, &frameworks);
681 assert_eq!(score, 0.0);
682 }
683}