use std::path::Path;
use anyhow::Result;
use crate::core::detection::scanner::Scanner;
use crate::core::detection::ModuleSystem;
use crate::utils::terminal;
pub fn execute(path: &Path, format: &str) -> Result<()> {
let mut scanner = Scanner::new(path.to_path_buf());
let result = scanner.scan();
let score_data = calculate_score(&result);
if format == "json" {
println!("{}", serde_json::to_string_pretty(&score_data)?);
} else {
print_score_report(&score_data);
}
Ok(())
}
#[derive(serde::Serialize)]
struct ScoreData {
overall_score: u8,
readiness: String,
metrics: ScoreMetrics,
suggestions: Vec<String>,
}
#[derive(serde::Serialize)]
struct ScoreMetrics {
typescript_adoption: String,
module_system: String,
risky_patterns: usize,
deprecated_syntax: usize,
migration_opportunities: usize,
}
fn calculate_score(result: &crate::core::detection::scanner::ScanResult) -> ScoreData {
let mut score: i16 = 70;
let mut ts_adoption = "None";
let has_ts = result.detection.frameworks.iter().any(|f| f.name == "TypeScript");
if has_ts {
score += 20;
ts_adoption = "Full";
}
let module_system = match result.detection.module_system {
ModuleSystem::ESM => {
score += 10;
"ESM"
}
ModuleSystem::CommonJS => {
score -= 20;
"CommonJS"
}
ModuleSystem::Mixed => {
score -= 10;
"Mixed"
}
};
let risky_penalty = (result.detection.risky_areas.len() as i16 * 5).min(20);
score -= risky_penalty;
let opp_penalty = (result.detection.migration_opportunities.len() as i16 * 5).min(20);
score -= opp_penalty;
let final_score = score.clamp(0, 100) as u8;
let readiness = if final_score > 80 {
"Modern"
} else if final_score > 50 {
"Modernizing"
} else if final_score > 20 {
"Transitioning"
} else {
"Legacy"
};
let mut suggestions = Vec::new();
for opp in &result.detection.migration_opportunities {
for recipe in &opp.recipes {
if !suggestions.contains(recipe) {
suggestions.push(recipe.clone());
}
}
}
ScoreData {
overall_score: final_score,
readiness: readiness.to_string(),
metrics: ScoreMetrics {
typescript_adoption: ts_adoption.to_string(),
module_system: module_system.to_string(),
risky_patterns: result.detection.risky_areas.len(),
deprecated_syntax: result.detection.migration_opportunities.len(), migration_opportunities: result.detection.migration_opportunities.len(),
},
suggestions,
}
}
fn print_score_report(data: &ScoreData) {
println!();
println!("{}", terminal::label("═".repeat(50).as_str()));
println!(" Modernization Score: {}", terminal::label(&format!("{} / 100", data.overall_score)));
println!(" Readiness Level: {}", terminal::label(&data.readiness));
println!("{}", terminal::label("─".repeat(50).as_str()));
println!();
println!(" Metrics:");
println!(" TypeScript: {}", data.metrics.typescript_adoption);
println!(" Module System: {}", data.metrics.module_system);
println!(" Risky Areas: {}", data.metrics.risky_patterns);
println!(" Legacy Syntax: {}", data.metrics.deprecated_syntax);
println!(" Opportunities: {}", data.metrics.migration_opportunities);
println!();
if !data.suggestions.is_empty() {
println!(" Suggested Recipes:");
for recipe in &data.suggestions {
println!(" - {}", recipe);
}
} else {
println!(" Project is up to date!");
}
println!();
}