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 crate::core::detection::scanner::{ScanSnapshotStore, Scanner};
use chrono::{TimeZone, Utc};

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

    println!("Scanning {}...", path.display());

    let mut scanner = Scanner::new(path.clone());
    let result = scanner.scan();
    result.print_summary(tag, verbose);

    let snapshot = result.to_snapshot(&path);
    let store = ScanSnapshotStore::new(project_root);
    store.save(&snapshot)?;
    println!(
        "{} Scan snapshot saved: {}",
        crate::utils::terminal::success_prefix(),
        snapshot.id
    );

    match crate::core::fingerprint::ProjectFingerprint::generate(project_root) {
        Ok(fp) => {
            if let Err(e) = fp.save(project_root) {
                eprintln!("Warning: Failed to save project fingerprint: {}", e);
            }
        }
        Err(e) => {
            eprintln!("Warning: Failed to generate project fingerprint: {}", e);
        }
    }

    Ok(())
}

pub fn execute_list(project_root: &Path) -> Result<()> {
    let store = ScanSnapshotStore::new(project_root);
    let list = store.list()?;

    println!();
    println!("{}", crate::utils::terminal::label("Scan Snapshots:"));
    println!("{}", crate::utils::terminal::label("".repeat(50).as_str()));

    if list.is_empty() {
        println!("  No scan snapshots stored yet.");
        return Ok(());
    }

    for snap in list {
        let dt = Utc.timestamp_opt(snap.timestamp as i64, 0).unwrap();
        let datetime = dt.format("%Y-%m-%d %H:%M:%S UTC").to_string();
        println!(
            "  {} {} [files: {} scanned, {} skipped] [{}]",
            crate::utils::terminal::bullet(),
            crate::utils::terminal::label(&snap.id),
            snap.file_counts.scanned,
            snap.file_counts.skipped,
            datetime
        );
        println!("    Path:       {}", snap.target_path.display());
        println!("    Frameworks: {}", snap.detected_frameworks.join(", "));
        println!();
    }

    Ok(())
}

pub fn execute_show(id: &str, project_root: &Path) -> Result<()> {
    let store = ScanSnapshotStore::new(project_root);
    let snapshot = store.load(id)?;

    let Some(snap) = snapshot else {
        anyhow::bail!("Scan snapshot not found: {}", id);
    };

    let dt = Utc.timestamp_opt(snap.timestamp as i64, 0).unwrap();
    let datetime = dt.format("%Y-%m-%d %H:%M:%S UTC").to_string();

    println!();
    println!("{} Scan Snapshot Detail: {}", crate::utils::terminal::success_prefix(), crate::utils::terminal::label(&snap.id));
    println!("  Timestamp:   {}", datetime);
    println!("  Target Path: {}", snap.target_path.display());
    println!();
    println!("  {}", crate::utils::terminal::label("File Metrics:"));
    println!("    Total Files:   {}", snap.file_counts.total);
    println!("    Scanned Files: {}", snap.file_counts.scanned);
    println!("    Skipped Files: {}", snap.file_counts.skipped);
    println!();
    println!("  {}", crate::utils::terminal::label("Risk Assessment:"));
    println!("    Risks Found:   {}", snap.risk_counts.total);
    println!();
    println!("  {}", crate::utils::terminal::label("Detected Frameworks:"));
    if snap.detected_frameworks.is_empty() {
        println!("    None");
    } else {
        for fw in &snap.detected_frameworks {
            println!("    - {}", fw);
        }
    }
    println!();
    println!("  {}", crate::utils::terminal::label("Recommended Recipes:"));
    if snap.recipe_suggestions.is_empty() {
        println!("    None");
    } else {
        for rec in &snap.recipe_suggestions {
            println!("    - {}", rec);
        }
    }

    Ok(())
}