syncable_cli/analyzer/monorepo/
analysis.rs

1use crate::analyzer::{
2    analyze_project_with_config, AnalysisConfig, AnalysisMetadata, MonorepoAnalysis, ProjectInfo,
3};
4use crate::common::file_utils;
5use crate::error::Result;
6use chrono::Utc;
7use std::path::{Path, PathBuf};
8
9use super::config::MonorepoDetectionConfig;
10use super::detection::{detect_potential_projects, determine_if_monorepo};
11use super::helpers::calculate_overall_confidence;
12use super::project_info::{determine_project_category, extract_project_name};
13use super::summary::generate_technology_summary;
14
15/// Detects if a path contains a monorepo and analyzes all projects within it
16pub fn analyze_monorepo(path: &Path) -> Result<MonorepoAnalysis> {
17    analyze_monorepo_with_config(path, &MonorepoDetectionConfig::default(), &AnalysisConfig::default())
18}
19
20/// Analyzes a monorepo with custom configuration
21pub fn analyze_monorepo_with_config(
22    path: &Path,
23    monorepo_config: &MonorepoDetectionConfig,
24    analysis_config: &AnalysisConfig,
25) -> Result<MonorepoAnalysis> {
26    let start_time = std::time::Instant::now();
27    let root_path = file_utils::validate_project_path(path)?;
28
29    log::info!("Starting monorepo analysis of: {}", root_path.display());
30
31    // Detect potential projects within the path
32    let potential_projects = detect_potential_projects(&root_path, monorepo_config)?;
33
34    log::debug!("Found {} potential projects", potential_projects.len());
35
36    // Determine if this is actually a monorepo or just a single project
37    let is_monorepo = determine_if_monorepo(&root_path, &potential_projects, monorepo_config)?;
38
39    let mut projects = Vec::new();
40
41    if is_monorepo && potential_projects.len() > 1 {
42        // Analyze each project separately
43        for project_path in potential_projects {
44            if let Ok(project_info) = analyze_individual_project(&root_path, &project_path, analysis_config) {
45                projects.push(project_info);
46            }
47        }
48
49        // If we didn't find multiple valid projects, treat as single project
50        if projects.len() <= 1 {
51            log::info!("Detected potential monorepo but only found {} valid project(s), treating as single project", projects.len());
52            projects.clear();
53            let single_analysis = analyze_project_with_config(&root_path, analysis_config)?;
54            projects.push(ProjectInfo {
55                path: PathBuf::from("."),
56                name: extract_project_name(&root_path, &single_analysis),
57                project_category: determine_project_category(&single_analysis, &root_path),
58                analysis: single_analysis,
59            });
60        }
61    } else {
62        // Single project analysis
63        let single_analysis = analyze_project_with_config(&root_path, analysis_config)?;
64        projects.push(ProjectInfo {
65            path: PathBuf::from("."),
66            name: extract_project_name(&root_path, &single_analysis),
67            project_category: determine_project_category(&single_analysis, &root_path),
68            analysis: single_analysis,
69        });
70    }
71
72    // Generate technology summary
73    let technology_summary = generate_technology_summary(&projects);
74
75    let duration = start_time.elapsed();
76    let metadata = AnalysisMetadata {
77        timestamp: Utc::now().to_rfc3339(),
78        analyzer_version: env!("CARGO_PKG_VERSION").to_string(),
79        analysis_duration_ms: duration.as_millis() as u64,
80        files_analyzed: projects.iter().map(|p| p.analysis.analysis_metadata.files_analyzed).sum(),
81        confidence_score: calculate_overall_confidence(&projects),
82    };
83
84    Ok(MonorepoAnalysis {
85        root_path,
86        is_monorepo: projects.len() > 1,
87        projects,
88        metadata,
89        technology_summary,
90    })
91}
92
93/// Analyzes an individual project within a monorepo
94fn analyze_individual_project(
95    root_path: &Path,
96    project_path: &Path,
97    config: &AnalysisConfig,
98) -> Result<ProjectInfo> {
99    log::debug!("Analyzing individual project: {}", project_path.display());
100
101    let analysis = analyze_project_with_config(project_path, config)?;
102    let relative_path = project_path.strip_prefix(root_path)
103        .unwrap_or(project_path)
104        .to_path_buf();
105
106    let name = extract_project_name(project_path, &analysis);
107    let category = determine_project_category(&analysis, project_path);
108
109    Ok(ProjectInfo {
110        path: relative_path,
111        name,
112        project_category: category,
113        analysis,
114    })
115}