syncable_cli/analyzer/monorepo/
analysis.rs1use 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
15pub fn analyze_monorepo(path: &Path) -> Result<MonorepoAnalysis> {
17 analyze_monorepo_with_config(
18 path,
19 &MonorepoDetectionConfig::default(),
20 &AnalysisConfig::default(),
21 )
22}
23
24pub 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 let potential_projects = detect_potential_projects(&root_path, monorepo_config)?;
37
38 log::debug!("Found {} potential projects", potential_projects.len());
39
40 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 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 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 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 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
105fn 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}