slave 0.6.0

CLI for slave, a Laravel-inspired Rust framework for MCP-first apps: scaffold an MCP backend with auth, roles and Turso migrations, add Axum or Dioxus web apps, and route them by domain or path
//! Dioxus client apps (`make:app --stack dioxus`): standalone crates in
//! `apps/<name>` for web, desktop and mobile, built with `dx`.
//!
//! They are not part of the backend's Cargo workspace and are not served by
//! `slave serve`; they reach the backend through MCP like any other client.

use std::path::Path;
use std::process::{Command, Stdio};

use anyhow::{Context, Result, bail};

/// How to get `dx`, matching the Dioxus version the template uses.
pub const INSTALL_HINT: &str = "install it with `cargo install dioxus-cli --version ^0.7 --locked`";

/// Whether `apps/<app>` is a Dioxus app.
pub fn is_dioxus_app(app_dir: &Path) -> bool {
    app_dir.join("Dioxus.toml").is_file()
}

/// Names of the project's Dioxus apps, sorted.
pub fn apps(root: &Path) -> Vec<String> {
    let mut names: Vec<String> = std::fs::read_dir(root.join("apps"))
        .into_iter()
        .flatten()
        .flatten()
        .filter(|entry| is_dioxus_app(&entry.path()))
        .filter_map(|entry| entry.file_name().into_string().ok())
        .collect();
    names.sort();
    names
}

pub fn dx_installed() -> bool {
    Command::new("dx")
        .arg("--version")
        .stdout(Stdio::null())
        .stderr(Stdio::null())
        .status()
        .is_ok_and(|status| status.success())
}

/// Builds the app's web bundle (`dx build --platform web`) in `apps/<app>`.
/// Desktop and mobile builds are run with `dx` directly.
pub fn build(root: &Path, app: &str, release: bool) -> Result<()> {
    if !dx_installed() {
        bail!("`{app}` is a Dioxus app, but `dx` was not found; {INSTALL_HINT}");
    }
    let mut command = Command::new("dx");
    command
        .args(["build", "--platform", "web"])
        .current_dir(root.join("apps").join(app));
    if release {
        command.arg("--release");
    }
    let status = command
        .status()
        .with_context(|| format!("failed to run `dx build` for {app}"))?;
    if !status.success() {
        bail!("`dx build` failed for {app}");
    }
    Ok(())
}