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
//! Locating the root of a slave application.

use std::fs;
use std::path::{Path, PathBuf};

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

/// Marker table written into every generated `Cargo.toml`.
pub const MARKER: &str = "[package.metadata.slave]";

/// Walks up from the current directory to find the enclosing slave application.
pub fn find_root() -> Result<PathBuf> {
    let cwd = std::env::current_dir().context("failed to read the current directory")?;
    find_root_from(&cwd)
}

/// The `[package] name` of the application at `root`.
pub fn package_name(root: &Path) -> Result<String> {
    let manifest = root.join("Cargo.toml");
    let contents = fs::read_to_string(&manifest)
        .with_context(|| format!("failed to read {}", manifest.display()))?;
    contents
        .lines()
        .skip_while(|line| line.trim() != "[package]")
        .find_map(|line| {
            let value = line.trim().strip_prefix("name")?.trim().strip_prefix('=')?;
            Some(value.trim().trim_matches('"').to_owned())
        })
        .with_context(|| format!("no package name in {}", manifest.display()))
}

fn find_root_from(start: &Path) -> Result<PathBuf> {
    for dir in start.ancestors() {
        let manifest = dir.join("Cargo.toml");
        if let Ok(contents) = fs::read_to_string(&manifest) {
            if contents.lines().any(|line| line.trim() == MARKER) {
                return Ok(dir.to_path_buf());
            }
        }
    }
    bail!(
        "not inside a slave application (no Cargo.toml containing `{MARKER}` was found).\n\
         Create one with `slave new <name>` and run this command from its directory."
    )
}