syncable_cli/analyzer/monorepo/
analysis.rs1use 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
15pub fn analyze_monorepo(path: &Path) -> Result<MonorepoAnalysis> {
17 analyze_monorepo_with_config(path, &MonorepoDetectionConfig::default(), &AnalysisConfig::default())
18}
19
20pub 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 let potential_projects = detect_potential_projects(&root_path, monorepo_config)?;
33
34 log::debug!("Found {} potential projects", potential_projects.len());
35
36 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 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 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 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 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
93fn 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}