slave 0.9.1

CLI for slave, a Laravel-inspired Rust framework for MCP-first apps: scaffold an MCP backend with auth, roles and Toasty models and migrations, add server-rendered web apps, and route them by domain or path
//! `slave make:migration`, `slave migrate` and `slave migrate:fresh`.
//!
//! Migrations are Toasty's: SQL generated from the models and recorded in
//! `database/` (see `Toasty.toml`). The models are compiled into the
//! application, so these commands build and run its `migrate` binary
//! (`src/bin/migrate.rs`, built on `toasty-cli`) from the project root.

use anyhow::{Result, bail};

use crate::{cargo, naming, project};

#[derive(Debug, Clone, Copy)]
pub enum Action<'a> {
    /// Diff the models against the last snapshot and write a migration.
    Generate { name: &'a str },
    /// Apply every pending migration.
    Apply,
    /// Drop every table, then apply all migrations (Toasty asks to confirm).
    Reset,
}

impl Action<'_> {
    fn args(self) -> Vec<&'static str> {
        match self {
            Self::Generate { .. } => vec!["migration", "generate", "--name"],
            Self::Apply => vec!["migration", "apply"],
            Self::Reset => vec!["migration", "reset"],
        }
    }

    fn command_name(self) -> &'static str {
        match self {
            Self::Generate { .. } => "slave make:migration",
            Self::Apply => "slave migrate",
            Self::Reset => "slave migrate:fresh",
        }
    }
}

pub fn run(action: Action) -> Result<()> {
    if let Action::Generate { name } = action {
        naming::validate_migration_name(name)?;
    }
    let root = project::find_root()?;
    if !root.join("src/bin/migrate.rs").is_file() || !root.join("Toasty.toml").is_file() {
        bail!(
            "src/bin/migrate.rs or Toasty.toml not found; migrations need the layout created by `slave new`"
        );
    }

    let mut args = vec!["run", "--quiet", "--bin", "migrate", "--"];
    args.extend(action.args());
    if let Action::Generate { name } = action {
        args.push(name);
    }
    let status = cargo::run(&root, &args, &[])?;
    if !status.success() {
        bail!("`{}` did not complete successfully", action.command_name());
    }
    Ok(())
}