use crate::error::Result;
use chrono::Utc;
use serde::{Deserialize, Serialize};
use std::collections::HashMap;
use std::path::{Path, PathBuf};
pub mod context;
pub mod dclint;
pub mod dependency_parser;
pub mod display;
pub mod docker_analyzer;
pub mod framework_detector;
pub mod frameworks;
pub mod hadolint;
pub mod helmlint;
pub mod k8s_optimize;
pub mod kubelint;
pub mod language_detector;
pub mod monorepo;
pub mod runtime;
pub mod security;
pub mod security_analyzer;
pub mod tool_management;
pub mod vulnerability;
pub use dependency_parser::{DependencyAnalysis, DependencyInfo, DetailedDependencyMap};
pub use security_analyzer::{
ComplianceStatus, SecurityAnalysisConfig, SecurityAnalyzer, SecurityCategory, SecurityFinding,
SecurityReport, SecuritySeverity,
};
pub use security::SecretPatternManager;
pub use security::config::SecurityConfigPreset;
pub use tool_management::{InstallationSource, ToolDetector, ToolInstaller, ToolStatus};
pub use runtime::{
DetectionConfidence, JavaScriptRuntime, PackageManager, RuntimeDetectionResult, RuntimeDetector,
};
pub use vulnerability::types::VulnerabilitySeverity as VulnSeverity;
pub use vulnerability::{
VulnerabilityChecker, VulnerabilityInfo, VulnerabilityReport, VulnerableDependency,
};
pub use monorepo::{MonorepoDetectionConfig, analyze_monorepo, analyze_monorepo_with_config};
pub use docker_analyzer::{
ComposeFileInfo, DiscoveredDockerfile, DockerAnalysis, DockerEnvironment, DockerService,
DockerfileInfo, NetworkingConfig, OrchestrationPattern, analyze_docker_infrastructure,
discover_dockerfiles_for_deployment,
};
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub struct DetectedLanguage {
pub name: String,
pub version: Option<String>,
pub confidence: f32,
pub files: Vec<PathBuf>,
pub main_dependencies: Vec<String>,
pub dev_dependencies: Vec<String>,
pub package_manager: Option<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Hash)]
pub enum TechnologyCategory {
MetaFramework,
FrontendFramework,
BackendFramework,
Library(LibraryType),
BuildTool,
Database,
Testing,
Runtime,
PackageManager,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Hash)]
pub enum LibraryType {
UI,
StateManagement,
DataFetching,
Routing,
Styling,
Utility,
HttpClient,
Authentication,
CLI,
Other(String),
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub struct DetectedTechnology {
pub name: String,
pub version: Option<String>,
pub category: TechnologyCategory,
pub confidence: f32,
pub requires: Vec<String>,
pub conflicts_with: Vec<String>,
pub is_primary: bool,
pub file_indicators: Vec<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub struct ServiceAnalysis {
pub name: String,
pub path: PathBuf,
pub languages: Vec<DetectedLanguage>,
pub technologies: Vec<DetectedTechnology>,
pub entry_points: Vec<EntryPoint>,
pub ports: Vec<Port>,
pub environment_variables: Vec<EnvVar>,
pub build_scripts: Vec<BuildScript>,
pub service_type: ProjectType,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub struct EntryPoint {
pub file: PathBuf,
pub function: Option<String>,
pub command: Option<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Hash)]
pub enum PortSource {
Dockerfile,
DockerCompose,
PackageJson,
FrameworkDefault,
EnvVar,
SourceCode,
ConfigFile,
}
impl PortSource {
pub fn description(&self) -> &'static str {
match self {
PortSource::Dockerfile => "Dockerfile EXPOSE",
PortSource::DockerCompose => "docker-compose.yml",
PortSource::PackageJson => "package.json scripts",
PortSource::FrameworkDefault => "framework default",
PortSource::EnvVar => "environment variable",
PortSource::SourceCode => "source code",
PortSource::ConfigFile => "configuration file",
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Hash)]
pub struct Port {
pub number: u16,
pub protocol: Protocol,
pub description: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub source: Option<PortSource>,
}
impl Port {
pub fn with_source(number: u16, protocol: Protocol, source: PortSource) -> Self {
Self {
number,
protocol,
description: None,
source: Some(source),
}
}
pub fn with_source_and_description(
number: u16,
protocol: Protocol,
source: PortSource,
description: impl Into<String>,
) -> Self {
Self {
number,
protocol,
description: Some(description.into()),
source: Some(source),
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Hash)]
pub enum Protocol {
Tcp,
Udp,
Http,
Https,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub enum HealthEndpointSource {
CodePattern,
FrameworkDefault,
ConfigFile,
}
impl HealthEndpointSource {
pub fn description(&self) -> &'static str {
match self {
HealthEndpointSource::CodePattern => "source code analysis",
HealthEndpointSource::FrameworkDefault => "framework convention",
HealthEndpointSource::ConfigFile => "configuration file",
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub struct HealthEndpoint {
pub path: String,
pub confidence: f32,
pub source: HealthEndpointSource,
pub description: Option<String>,
}
impl HealthEndpoint {
pub fn from_code(path: impl Into<String>, confidence: f32) -> Self {
Self {
path: path.into(),
confidence,
source: HealthEndpointSource::CodePattern,
description: None,
}
}
pub fn from_framework(path: impl Into<String>, framework: &str) -> Self {
Self {
path: path.into(),
confidence: 0.7, source: HealthEndpointSource::FrameworkDefault,
description: Some(format!("{} default health endpoint", framework)),
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub struct EnvVar {
pub name: String,
pub default_value: Option<String>,
pub required: bool,
pub description: Option<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub enum ProjectType {
WebApplication,
ApiService,
CliTool,
Library,
MobileApp,
DesktopApp,
Microservice,
StaticSite,
Hybrid, Unknown,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub struct BuildScript {
pub name: String,
pub command: String,
pub description: Option<String>,
pub is_default: bool,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Default)]
pub struct InfrastructurePresence {
pub has_kubernetes: bool,
pub kubernetes_paths: Vec<PathBuf>,
pub has_helm: bool,
pub helm_chart_paths: Vec<PathBuf>,
pub has_docker_compose: bool,
pub has_terraform: bool,
pub terraform_paths: Vec<PathBuf>,
pub has_deployment_config: bool,
pub summary: Option<String>,
}
impl InfrastructurePresence {
pub fn has_any(&self) -> bool {
self.has_kubernetes
|| self.has_helm
|| self.has_docker_compose
|| self.has_terraform
|| self.has_deployment_config
}
pub fn detected_types(&self) -> Vec<&'static str> {
let mut types = Vec::new();
if self.has_kubernetes {
types.push("Kubernetes");
}
if self.has_helm {
types.push("Helm");
}
if self.has_docker_compose {
types.push("Docker Compose");
}
if self.has_terraform {
types.push("Terraform");
}
if self.has_deployment_config {
types.push("Syncable Config");
}
types
}
}
pub type DependencyMap = HashMap<String, String>;
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub enum ArchitectureType {
Monolithic,
Microservices,
Hybrid,
}
pub type DetectedFramework = DetectedTechnology;
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub struct ProjectAnalysis {
pub project_root: PathBuf,
pub languages: Vec<DetectedLanguage>,
pub technologies: Vec<DetectedTechnology>,
#[deprecated(note = "Use technologies field instead")]
pub frameworks: Vec<DetectedFramework>,
pub dependencies: DependencyMap,
pub entry_points: Vec<EntryPoint>,
pub ports: Vec<Port>,
#[serde(default)]
pub health_endpoints: Vec<HealthEndpoint>,
pub environment_variables: Vec<EnvVar>,
pub project_type: ProjectType,
pub build_scripts: Vec<BuildScript>,
pub services: Vec<ServiceAnalysis>,
pub architecture_type: ArchitectureType,
pub docker_analysis: Option<DockerAnalysis>,
#[serde(default)]
pub infrastructure: Option<InfrastructurePresence>,
pub analysis_metadata: AnalysisMetadata,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub struct AnalysisMetadata {
pub timestamp: String,
pub analyzer_version: String,
pub analysis_duration_ms: u64,
pub files_analyzed: usize,
pub confidence_score: f32,
}
#[derive(Debug, Clone)]
pub struct AnalysisConfig {
pub include_dev_dependencies: bool,
pub deep_analysis: bool,
pub ignore_patterns: Vec<String>,
pub max_file_size: usize,
}
impl Default for AnalysisConfig {
fn default() -> Self {
Self {
include_dev_dependencies: false,
deep_analysis: true,
ignore_patterns: vec![
"node_modules".to_string(),
".git".to_string(),
"target".to_string(),
"build".to_string(),
".next".to_string(),
"dist".to_string(),
],
max_file_size: 1024 * 1024, }
}
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub struct ProjectInfo {
pub path: PathBuf,
pub name: String,
pub project_category: ProjectCategory,
pub analysis: ProjectAnalysis,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub enum ProjectCategory {
Frontend,
Backend,
Api,
Service,
Library,
Tool,
Documentation,
Infrastructure,
Unknown,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub struct MonorepoAnalysis {
pub root_path: PathBuf,
pub is_monorepo: bool,
pub projects: Vec<ProjectInfo>,
pub metadata: AnalysisMetadata,
pub technology_summary: TechnologySummary,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub struct TechnologySummary {
pub languages: Vec<String>,
pub frameworks: Vec<String>,
pub databases: Vec<String>,
pub total_projects: usize,
pub architecture_pattern: ArchitecturePattern,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub enum ArchitecturePattern {
Monolithic,
Fullstack,
Microservices,
ApiFirst,
EventDriven,
Mixed,
}
pub fn analyze_project(path: &Path) -> Result<ProjectAnalysis> {
analyze_project_with_config(path, &AnalysisConfig::default())
}
pub fn analyze_project_with_config(
path: &Path,
config: &AnalysisConfig,
) -> Result<ProjectAnalysis> {
let start_time = std::time::Instant::now();
let project_root = crate::common::file_utils::validate_project_path(path)?;
log::info!("Starting analysis of project: {}", project_root.display());
let files = crate::common::file_utils::collect_project_files(&project_root, config)?;
log::debug!("Found {} files to analyze", files.len());
let languages = language_detector::detect_languages(&files, config)?;
let frameworks = framework_detector::detect_frameworks(&project_root, &languages, config)?;
let dependencies = dependency_parser::parse_dependencies(&project_root, &languages, config)?;
let context = context::analyze_context(&project_root, &languages, &frameworks, config)?;
let health_endpoints =
context::detect_health_endpoints(&project_root, &frameworks, config.max_file_size);
let infrastructure = context::detect_infrastructure(&project_root);
let docker_analysis = analyze_docker_infrastructure(&project_root).ok();
let duration = start_time.elapsed();
let confidence = calculate_confidence_score(&languages, &frameworks);
#[allow(deprecated)]
let analysis = ProjectAnalysis {
project_root,
languages,
technologies: frameworks.clone(), frameworks, dependencies,
entry_points: context.entry_points,
ports: context.ports,
health_endpoints,
environment_variables: context.environment_variables,
project_type: context.project_type,
build_scripts: context.build_scripts,
services: vec![], architecture_type: ArchitectureType::Monolithic, docker_analysis,
infrastructure: Some(infrastructure),
analysis_metadata: AnalysisMetadata {
timestamp: Utc::now().to_rfc3339(),
analyzer_version: env!("CARGO_PKG_VERSION").to_string(),
analysis_duration_ms: duration.as_millis() as u64,
files_analyzed: files.len(),
confidence_score: confidence,
},
};
log::info!("Analysis completed in {}ms", duration.as_millis());
Ok(analysis)
}
fn calculate_confidence_score(
languages: &[DetectedLanguage],
frameworks: &[DetectedFramework],
) -> f32 {
if languages.is_empty() {
return 0.0;
}
let lang_confidence: f32 =
languages.iter().map(|l| l.confidence).sum::<f32>() / languages.len() as f32;
let framework_confidence: f32 = if frameworks.is_empty() {
0.5 } else {
frameworks.iter().map(|f| f.confidence).sum::<f32>() / frameworks.len() as f32
};
(lang_confidence * 0.7 + framework_confidence * 0.3).min(1.0)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_confidence_calculation() {
let languages = vec![DetectedLanguage {
name: "Rust".to_string(),
version: Some("1.70.0".to_string()),
confidence: 0.9,
files: vec![],
main_dependencies: vec!["serde".to_string(), "tokio".to_string()],
dev_dependencies: vec!["assert_cmd".to_string()],
package_manager: Some("cargo".to_string()),
}];
let technologies = vec![DetectedTechnology {
name: "Actix Web".to_string(),
version: Some("4.0".to_string()),
category: TechnologyCategory::BackendFramework,
confidence: 0.8,
requires: vec!["serde".to_string(), "tokio".to_string()],
conflicts_with: vec![],
is_primary: true,
file_indicators: vec![],
}];
let frameworks = technologies.clone();
let score = calculate_confidence_score(&languages, &frameworks);
assert!(score > 0.8);
assert!(score <= 1.0);
}
#[test]
fn test_empty_analysis() {
let languages = vec![];
let frameworks = vec![];
let score = calculate_confidence_score(&languages, &frameworks);
assert_eq!(score, 0.0);
}
}