stackenv 0.1.3

Native, zero-container multi-project dev environment manager. No Docker, no VMs.
use crate::core::orchestrate;
use clap::{Parser, Subcommand};
use std::path::PathBuf;

#[derive(Parser)]
#[command(name = "stack", version, about = "Native, zero-container multi-project dev environment manager. No Docker, no VMs.")]
struct Cli {
    #[command(subcommand)]
    command: Command,
}

#[derive(Subcommand)]
enum Command {
    New { target: String },
    Add {
        kind: String,
        name: String,
        version: Option<String>,
        #[arg(long)]
        schema: Option<String>,
        #[arg(long)]
        port: Option<u16>,
        #[arg(long)]
        command: Option<String>,
        #[arg(long)]
        path: Option<String>,
        #[arg(long)]
        manager: Option<String>,
        #[arg(long)]
        plugin: Option<String>,
        #[arg(long)]
        binary: Option<String>,
    },
    Remove { kind: String, name: String },
    Register {
        kind: String,
        name: String,
        version: String,
        path: Option<String>,
        #[arg(long)]
        external: bool,
        #[arg(long)]
        port: Option<u16>,
    },
    List,
    Prune {
        #[arg(long)]
        yes: bool,
        #[arg(long)]
        purge_data: bool,
    },
    LoadEnv { path: Option<String> },
    Up {
        #[arg(default_value = ".")]
        dir: PathBuf,
        #[arg(long)]
        prompt: bool,
    },
    Down {
        project: Option<String>,
        #[arg(long)]
        all: bool,
    },
    #[command(alias = "ps")]
    Status,
    Stats {
        #[arg(long)]
        no_stream: bool,
    },
    Logs {
        name: Option<String>,
        #[arg(short = 'f', long)]
        follow: bool,
        #[arg(long)]
        tail: Option<usize>,
    },
    Doctor {
        #[arg(long)]
        fix: bool,
    },
    Hook { shell: String },
    Activate { shell: String },
    Setup {
        #[arg(long, default_value = "pwsh")]
        shell: String,
    },
}

pub fn run() {
    let cli = Cli::parse();

    let result: anyhow::Result<()> = match cli.command {
        Command::New { target } => orchestrate::new_project(&target),
        Command::Add { kind, name, version, schema, port, command, path, manager, plugin, binary } => {
            orchestrate::add(&kind, &name, version, schema, port, command, path, manager, plugin, binary)
        }
        Command::Remove { kind, name } => orchestrate::remove(&kind, &name),
        Command::Register { kind, name, version, path, external, port } => orchestrate::register(&kind, &name, &version, path.as_deref(), external, port),
        Command::List => {
            orchestrate::list();
            Ok(())
        }
        Command::Prune { yes, purge_data } => orchestrate::prune(yes, purge_data),
        Command::LoadEnv { path } => orchestrate::load_env(path),
        Command::Up { dir, prompt } => orchestrate::up(&dir, prompt),
        Command::Down { project, all } => orchestrate::down(project, all),
        Command::Status => {
            orchestrate::status();
            Ok(())
        }
        Command::Stats { no_stream } => {
            orchestrate::stats(no_stream);
            Ok(())
        }
        Command::Logs { name, follow, tail } => orchestrate::logs(name, follow, tail),
        Command::Doctor { fix } => orchestrate::doctor(fix),
        Command::Hook { shell } => orchestrate::hook(&shell),
        Command::Activate { shell } => {
            orchestrate::activate(&shell);
            Ok(())
        }
        Command::Setup { shell } => {
            orchestrate::setup(&shell);
            Ok(())
        }
    };

    if let Err(e) = result {
        eprintln!("stack: {e:#}");
        std::process::exit(1);
    }
}