morph-cli 0.1.0

AST-based codebase migration and codemod tool for JavaScript and TypeScript projects.
Documentation
use std::path::Path;

use anyhow::Result;
use colored::Colorize;

use crate::core::detection::package_json::PackageJson;

pub fn execute(path: &Path) -> Result<()> {
    let path = if path.exists() && path.is_relative() {
        std::env::current_dir()?.join(path)
    } else {
        path.to_path_buf()
    };

    let pkg_path = path.join("package.json");
    if !pkg_path.exists() {
        println!("{} No package.json found in {}", "".yellow(), path.display());
        return Ok(());
    }

    println!("Analyzing dependencies in {}...", pkg_path.display());

    let pkg = match PackageJson::load(&pkg_path) {
        Some(p) => p,
        None => {
            println!("{} Failed to parse package.json", "".red());
            return Ok(());
        }
    };

    let all_deps: std::collections::HashMap<&String, &String> = pkg
        .dependencies
        .iter()
        .chain(pkg.dev_dependencies.iter())
        .collect();

    let mut deprecated = Vec::new();
    let mut blockers = Vec::new();
    let mut mismatches = Vec::new();

    let deprecated_list = [
        "tslint",
        "request",
        "@angular/http",
        "moment",
        "node-sass",
        "phantomjs-prebuilt",
        "bower",
        "vue-resource",
        "@babel/polyfill",
    ];

    let blockers_list = [
        "babel-core", 
        "gulp-util",
    ];

    // Detect Deprecated & Blockers
    for (name, version) in &all_deps {
        if deprecated_list.contains(&name.as_str()) {
            deprecated.push(format!("{} ({})", name, version));
        }
        if blockers_list.contains(&name.as_str()) {
            blockers.push(format!("{} ({})", name, version));
        }

        // Custom blockers check based on version
        if name.as_str() == "webpack" && (version.starts_with('1') || version.starts_with('2') || version.starts_with('3') || version.starts_with("^1") || version.starts_with("^2") || version.starts_with("^3") || version.starts_with("~1") || version.starts_with("~2") || version.starts_with("~3")) {
            blockers.push(format!("{} ({}) - too old", name, version));
        }
    }

    // Detect Mismatches
    if let (Some(react_ver), Some(react_dom_ver)) = (all_deps.get(&"react".to_string()), all_deps.get(&"react-dom".to_string())) {
        if react_ver != react_dom_ver {
            mismatches.push(format!("react ({}) vs react-dom ({})", react_ver, react_dom_ver));
        }
    }

    // Angular mismatches
    if let (Some(core_ver), Some(cli_ver)) = (all_deps.get(&"@angular/core".to_string()), all_deps.get(&"@angular/cli".to_string())) {
        let core_major = core_ver.split('.').next().unwrap_or("");
        let cli_major = cli_ver.split('.').next().unwrap_or("");
        if core_major != cli_major {
             mismatches.push(format!("@angular/core ({}) vs @angular/cli ({})", core_ver, cli_ver));
        }
    }

    // Reporting
    println!("\n{}", "Dependency Analysis Summary:".bold());
    println!("  Total dependencies: {}", pkg.dependencies.len());
    println!("  Total devDependencies: {}", pkg.dev_dependencies.len());

    if !deprecated.is_empty() {
        println!("\n{} {}", "".yellow(), "Deprecated Packages:".bold().yellow());
        for item in &deprecated {
            println!("  - {}", item);
        }
    }

    if !blockers.is_empty() {
        println!("\n{} {}", "".red(), "Migration Blockers:".bold().red());
        for item in &blockers {
            println!("  - {}", item);
        }
    }

    if !mismatches.is_empty() {
        println!("\n{} {}", "".red(), "Framework Version Mismatches:".bold().red());
        for item in &mismatches {
            println!("  - {}", item);
        }
    }

    if deprecated.is_empty() && blockers.is_empty() && mismatches.is_empty() {
        println!("\n{} {}", "".green(), "No major dependency issues found.".green());
    }

    Ok(())
}