use std::path::Path;
use anyhow::Result;
use colored::Colorize;
use crate::core::detection::scanner::Scanner;
use crate::core::planner::{Plan, build_plan};
use crate::core::registry::RecipeRegistry;
use crate::utils::terminal;
pub fn execute(path: &Path, tag: Option<&str>) -> Result<()> {
let path = if path.exists() && path.is_relative() {
std::env::current_dir()?.join(path)
} else {
path.to_path_buf()
};
println!("Planning migrations for {}...", path.display());
let mut scanner = Scanner::new(path);
let scan = scanner.scan();
let mut registry = RecipeRegistry::new();
registry.load_plugins(&scan.root);
let plan = build_plan(&scan, ®istry);
render_plan(&plan, tag, &scan, ®istry);
Ok(())
}
fn render_plan(
plan: &Plan,
tag_filter: Option<&str>,
scan: &crate::core::detection::scanner::ScanResult,
registry: &RecipeRegistry,
) {
if scan.scanned_files.is_empty() {
println!();
println!("{}", "✨ Welcome to Morph CLI! ✨".bold().cyan());
println!("{}", "═".repeat(60).cyan());
println!("It looks like you've initialized morph-cli in an empty project or a directory with no supported files.");
println!();
println!("{}", "💡 Quick Onboarding Guide:".bold().yellow());
println!(" 1. Place some Javascript or TypeScript files in this directory.");
println!(" 2. Run `morph init` to generate a `morph-cli.toml` config file.");
println!(" 3. Run `morph list` to see all available recipes.");
println!();
println!("{}", "🚀 Beginner-Safe Recommendations:".bold().green());
println!(" - To migrate CJS require statements to modern ESM imports:");
println!(" {}", "morph run commonjs-to-esm . --dry-run".bold().cyan());
println!(" - To upgrade JavaScript files to TypeScript:");
println!(" {}", "morph run js-to-ts . --dry-run".bold().cyan());
println!(" - To preview a preset workflow impact:");
println!(" {}", "morph preset run modern-js .".bold().cyan());
println!();
println!("{}", "👉 Next-Step Hints:".bold().magenta());
println!(" Run `morph magic` to start a guided, step-by-step interactive assistant!");
println!("{}", "═".repeat(60).cyan());
println!();
return;
}
println!();
println!("{}", terminal::label("Project tag summary"));
let mut tag_counts = std::collections::BTreeMap::new();
for f in &scan.scanned_files {
for t in &f.tags {
*tag_counts.entry(t.clone()).or_insert(0) += 1;
}
}
for (t, count) in &tag_counts {
println!(" - {}: {} file(s)", t, count);
}
println!();
println!("{}", terminal::label("Recommended recipes"));
if plan.recommendations.is_empty() {
println!(" No automatic migration recommendations generated for this directory.");
println!();
println!("{}", "🚀 Beginner-Safe Recipes to explore manually:".bold().green());
println!(" - To migrate CJS require statements to modern ESM imports:");
println!(" {}", "morph run commonjs-to-esm . --dry-run --review".bold().cyan());
println!(" - To upgrade JavaScript files to TypeScript safely:");
println!(" {}", "morph run js-to-ts . --dry-run --review".bold().cyan());
println!();
println!("{}", "💡 Suggested Commands:".bold().yellow());
println!(" - Run `morph list` to inspect all built-in recipes.");
println!(" - Run `morph doctor` to check your setup and environment health.");
println!();
println!("{}", "👉 Next-Step Hints:".bold().magenta());
println!(" - Run `morph magic` to launch our guided interactive migration assistant!");
return;
}
for recommendation in &plan.recommendations {
let maturity_colored = match recommendation.maturity.to_lowercase().as_str() {
"stable" => recommendation.maturity.green().bold(),
"beta" => recommendation.maturity.yellow().bold(),
_ => recommendation.maturity.red().bold(),
};
let safety_colored = match recommendation.safety_level.as_str() {
"High" => "High 🟢".green().bold(),
"Medium" => "Medium 🟡".yellow().bold(),
_ => "Low 🔴".red().bold(),
};
let usefulness_colored = match recommendation.usefulness.as_str() {
"High" => "High 🔥".green().bold(),
"Medium" => "Medium ⚡".yellow().bold(),
_ => "Low ❄️".red().bold(),
};
println!();
println!(" ┌──────────────────────────────────────────────────────────────────────────────┐");
println!(
" │ 🚀 {:<30} [{:<18}] (confidence: {:>3}%) │",
recommendation.recipe.cyan().bold(),
maturity_colored,
recommendation.confidence
);
println!(" ├──────────────────────────────────────────────────────────────────────────────┤");
println!(" │ {:<15} {:<58} │", "Category:".dimmed(), recommendation.category.magenta().bold());
let max_len = 54;
let mut words = recommendation.reason.split_whitespace();
let mut line = String::new();
let mut is_first = true;
while let Some(word) = words.next() {
if line.len() + word.len() + 1 > max_len {
if is_first {
println!(" │ {:<15} {:<58} │", "Why Suggested:".dimmed(), line);
is_first = false;
} else {
println!(" │ {:<15} {:<58} │", "".dimmed(), line);
}
line = word.to_string();
} else {
if !line.is_empty() {
line.push(' ');
}
line.push_str(word);
}
}
if !line.is_empty() {
if is_first {
println!(" │ {:<15} {:<58} │", "Why Suggested:".dimmed(), line);
} else {
println!(" │ {:<15} {:<58} │", "".dimmed(), line);
}
}
println!(" │ {:<15} {:<58} │", "Safety Level:".dimmed(), safety_colored);
println!(" │ {:<15} {:<58} │", "Usefulness:".dimmed(), usefulness_colored);
let mut meta_tags = Vec::new();
if let Some(r) = registry.find(&recommendation.recipe) {
let meta = r.metadata();
if !meta.tags.is_empty() {
meta_tags = meta.tags.iter().map(|s| s.to_string()).collect();
}
}
if !meta_tags.is_empty() {
println!(" │ {:<15} {:<58} │", "Tags:".dimmed(), meta_tags.join(", ").dimmed());
}
if let Some(ref note) = recommendation.ordering_note {
println!(" │ {:<15} {:<58} │", "Order Hints:".dimmed(), note.dimmed());
}
if let Some(tag) = tag_filter {
let matching_files: Vec<_> = scan.scanned_files.iter()
.filter(|f| f.tags.iter().any(|t| t == tag))
.collect();
println!(" ├──────────────────────────────────────────────────────────────────────────────┤");
println!(" │ Matching files ({}):", tag);
if !matching_files.is_empty() {
for f in matching_files.iter().take(5) {
println!(" │ - {:<72} │", f.path.display().to_string().cyan());
}
if matching_files.len() > 5 {
println!(" │ ... and {} more", matching_files.len() - 5);
}
} else {
println!(" │ No files match the specified tag filter '{}'.", tag);
}
}
println!(" └──────────────────────────────────────────────────────────────────────────────┘");
}
}