microsandbox-cli 0.6.0

CLI binary for managing microsandbox environments.
//! `msb uninstall` command — remove an installed sandbox alias.

use std::fs;

use clap::Args;

use super::install::{alias_candidates, is_generated_alias};
use crate::ui;

//--------------------------------------------------------------------------------------------------
// Types
//--------------------------------------------------------------------------------------------------

/// Remove an installed sandbox command.
#[derive(Debug, Args)]
pub struct UninstallArgs {
    /// Command(s) to remove.
    #[arg(required = true)]
    pub names: Vec<String>,
}

//--------------------------------------------------------------------------------------------------
// Functions
//--------------------------------------------------------------------------------------------------

/// Execute the `msb uninstall` command.
pub async fn run(args: UninstallArgs) -> anyhow::Result<()> {
    let backend = microsandbox::backend::default_backend();
    let home = match backend.as_local() {
        Some(local) => local.config().home(),
        None => microsandbox_utils::resolve_home(),
    };
    let bin_dir = home.join("bin");

    let mut failed = false;

    for name in &args.names {
        if name.contains('/') || name.contains('\\') || name.contains(':') || name.contains("..") {
            ui::error(&format!("invalid alias name '{name}'"));
            failed = true;
            continue;
        }

        let Some(path) = alias_candidates(&bin_dir, name)
            .into_iter()
            .find(|path| path.exists())
        else {
            ui::error(&format!("alias '{name}' not found"));
            failed = true;
            continue;
        };

        // Validate it's an msb-generated script (marker must be on line 2).
        let content = match fs::read_to_string(&path) {
            Ok(c) => c,
            Err(e) => {
                ui::error(&format!("failed to read '{name}': {e}"));
                failed = true;
                continue;
            }
        };
        if !is_generated_alias(&content) {
            ui::error(&format!("'{name}' is not an msb-installed alias"));
            failed = true;
            continue;
        }

        match fs::remove_file(&path) {
            Ok(()) => ui::success("Uninstalled", name),
            Err(e) => {
                ui::error(&format!("failed to remove '{name}': {e}"));
                failed = true;
            }
        }
    }

    if failed {
        anyhow::bail!("some aliases failed to uninstall");
    }

    Ok(())
}