syncable_cli/analyzer/monorepo/
analysis.rs

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