slave 0.13.1

CLI for slave, a Laravel-inspired Rust framework for MCP-first apps: scaffold an MCP backend with auth, roles, teams and SurrealDB migrations, add server-rendered web apps, and route them by domain or path
//! `slave make:migration`, `slave migrate`, `slave migrate:status` and
//! `slave migrate:fresh`.
//!
//! Migrations are SurrealQL files in `database/migrations/`, named
//! `<YYYYMMDD_HHMMSS>_<name>.surql` and applied in name order. Creating one
//! only writes a file; the others build and run the app's `migrate` binary
//! (`src/bin/migrate.rs`, built on `slave_framework::database::migrations`)
//! from the project root, with the app's own `SURREAL_*` settings.

use anyhow::{Result, bail};

use crate::{cargo, generator, naming, project, templates, timestamp};

/// Where migrations live, relative to the project root.
pub const DIR: &str = "database/migrations";

#[derive(Debug, Clone, Copy)]
pub enum Action<'a> {
    /// Write an empty migration file.
    Generate { name: &'a str },
    /// Apply every pending migration.
    Apply,
    /// List migrations and whether each is applied.
    Status,
    /// Drop every table, then apply all migrations.
    Reset { force: bool },
}

pub fn run(action: Action) -> Result<()> {
    let root = project::find_root()?;
    if let Action::Generate { name } = action {
        naming::validate_migration_name(name)?;
        let file = format!(
            "{DIR}/{}_{name}.surql",
            timestamp::next_migration_prefix(&root.join(DIR))?
        );
        let contents = templates::render(templates::MIGRATION, &[("name", name)]);
        generator::write_file(&root.join(&file), &contents, false)?;
        println!("  created {file}");
        println!("\nWrite the SurrealQL, then apply it with `slave migrate`.");
        return Ok(());
    }

    if !root.join("src/bin/migrate.rs").is_file() {
        bail!("src/bin/migrate.rs not found; migrations need the layout created by `slave new`");
    }
    let mut args = vec!["run", "--quiet", "--bin", "migrate", "--"];
    let command_name = match action {
        Action::Apply => {
            args.push("apply");
            "slave migrate"
        }
        Action::Status => {
            args.push("status");
            "slave migrate:status"
        }
        Action::Reset { force } => {
            args.push("fresh");
            if force {
                args.push("--force");
            }
            "slave migrate:fresh"
        }
        Action::Generate { .. } => unreachable!("handled above"),
    };
    let status = cargo::run(&root, &args, &[])?;
    if !status.success() {
        bail!("`{command_name}` did not complete successfully");
    }
    Ok(())
}