kegani-cli 0.1.4

CLI tool for Kegani framework
Documentation
//! `kegani migrate` command - Run database migrations

use crate::types::MigrateAction;
use anyhow::{Context, Result};
use console::{style, Emoji};
use std::process::Command;

/// Run database migrations
pub fn run_migration(action: MigrateAction) -> Result<()> {
    // Pin project dir before any subprocess changes cwd
    let project_dir = std::env::current_dir().context("Failed to get current directory")?;

    println!();
    println!("{}", style("🗄️ Database Migration").bold());
    println!("{}", style("━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━").dim());

    match action {
        MigrateAction::Up => run_migrations_up(&project_dir)?,
        MigrateAction::Down => run_migrations_down(&project_dir)?,
        MigrateAction::Status => show_migration_status(&project_dir)?,
        MigrateAction::Reset => reset_migrations(&project_dir)?,
    }

    Ok(())
}

fn run_migrations_up(project_dir: &std::path::Path) -> Result<()> {
    println!("  {} Running migrations...", style("").cyan());

    // Check if sqlx-cli is installed
    if !is_sqlx_installed() {
        println!();
        println!("{} {}", style("⚠️").yellow(), style("sqlx-cli not found. Installing...").dim());
        install_sqlx()?;
    }

    // Run migrations
    let status = Command::new("sqlx")
        .args(["migrate", "run"])
        .current_dir(project_dir)
        .status()
        .context("Failed to run migrations")?;

    if status.success() {
        println!();
        println!("{} {}", Emoji("", ""), style("Migrations completed successfully!").green());
    } else {
        anyhow::bail!("Migration failed with exit code: {:?}", status.code());
    }

    Ok(())
}

fn run_migrations_down(project_dir: &std::path::Path) -> Result<()> {
    println!("  {} Rolling back last migration...", style("").cyan());

    let status = Command::new("sqlx")
        .args(["migrate", "revert"])
        .current_dir(project_dir)
        .status()
        .context("Failed to rollback migration")?;

    if status.success() {
        println!();
        println!("{} {}", Emoji("", ""), style("Rollback completed successfully!").green());
    } else {
        anyhow::bail!("Rollback failed with exit code: {:?}", status.code());
    }

    Ok(())
}

fn show_migration_status(project_dir: &std::path::Path) -> Result<()> {
    println!("  {} Checking migration status...", style("📋").cyan());

    let status = Command::new("sqlx")
        .args(["migrate", "status"])
        .current_dir(project_dir)
        .status()
        .context("Failed to check migration status")?;

    if !status.success() {
        anyhow::bail!("Migration status check failed");
    }

    Ok(())
}

fn reset_migrations(project_dir: &std::path::Path) -> Result<()> {
    println!("  {} {} This will drop all tables!", style("⚠️").yellow(), style("WARNING").red().bold());
    println!();

    let status = Command::new("sqlx")
        .args(["migrate", "reset"])
        .current_dir(project_dir)
        .status()
        .context("Failed to reset migrations")?;

    if status.success() {
        println!();
        println!("{} {}", Emoji("", ""), style("Reset completed successfully!").green());
    } else {
        anyhow::bail!("Reset failed with exit code: {:?}", status.code());
    }

    Ok(())
}

fn is_sqlx_installed() -> bool {
    Command::new("sqlx")
        .arg("--version")
        .output()
        .map(|output| output.status.success())
        .unwrap_or(false)
}

fn install_sqlx() -> Result<()> {
    let status = Command::new("cargo")
        .args(["install", "sqlx-cli", "--no-default-features", "--features", "postgres"])
        .status()
        .context("Failed to install sqlx-cli")?;

    if !status.success() {
        anyhow::bail!("Failed to install sqlx-cli");
    }

    Ok(())
}