cargo-governor 1.2.0

Machine-First, LLM-Ready, CI/CD-Native release automation tool for Rust crates
Documentation
//! CLI module for cargo-governor

use crate::error::{CommandExitCode, Result};
use clap::{Parser, Subcommand};

pub mod owners;
pub mod release;

pub use owners::OwnersSubCommands;
pub use release::{
    AnalyzeOpts, BumpOpts, CheckOpts, PlanOpts, PublishOpts, ReleaseSubCommands, ResumeOpts,
    SimulateOpts, StatusOpts,
};

/// Output format
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum OutputFormat {
    Json,
    Yaml,
    Human,
}

impl OutputFormat {
    pub fn from_str(s: &str) -> Option<Self> {
        match s.to_lowercase().as_str() {
            "json" => Some(Self::Json),
            "yaml" => Some(Self::Yaml),
            "human" => Some(Self::Human),
            _ => None,
        }
    }
}

/// cargo-governor: Machine-First, LLM-Ready, CI/CD-Native release automation tool
#[derive(Parser, Debug)]
#[command(name = "cargo-governor")]
#[command(bin_name = "cargo-governor")]
#[command(about, long_about = None)]
#[command(version)]
#[command(propagate_version = true)]
pub struct Cli {
    /// When invoked as 'cargo governor', cargo passes 'governor' as first arg
    #[arg(hide = true)]
    pub governor: Option<String>,

    /// Path to workspace root (default: current directory)
    #[arg(short = 'w', long, global = true)]
    pub workspace: Option<String>,

    /// Config file path (default: .cargo-governor.toml)
    #[arg(short = 'c', long, global = true)]
    pub config: Option<String>,

    /// Output format: json (default), yaml, human
    #[arg(short = 'f', long, global = true)]
    pub format: Option<String>,

    /// Write output to file
    #[arg(short = 'o', long, global = true)]
    pub output: Option<String>,

    /// Verbose logging
    #[arg(short = 'v', long, global = true)]
    pub verbose: bool,

    /// Suppress all output except result
    #[arg(short = 'q', long, global = true)]
    pub quiet: bool,

    /// Simulate without making changes
    #[arg(long, global = true)]
    pub dry_run: bool,

    #[command(subcommand)]
    pub command: Commands,
}

#[derive(Subcommand, Debug)]
pub enum Commands {
    /// Manage crate owners on crates.io
    #[command(name = "owners")]
    Owners {
        #[command(subcommand)]
        subcmd: OwnersSubCommands,
    },

    /// Release automation - analyze, plan, bump, publish
    #[command(name = "release")]
    Release {
        #[command(subcommand)]
        subcmd: ReleaseSubCommands,
    },
}

/// Main entry point for the CLI
pub async fn run() -> Result<CommandExitCode> {
    use crate::cli::{Cli, Commands, OutputFormat};

    let args: Vec<String> = std::env::args().collect();

    // Check if we're being called as 'cargo governor'
    let cli = if args.len() > 1 && args[1] == "governor" {
        let args_without_governor: Vec<String> = std::iter::once(args[0].clone())
            .chain(args.into_iter().skip(2))
            .collect();
        Cli::parse_from(args_without_governor)
    } else {
        Cli::parse()
    };

    // Get output format (default: JSON for machine-first)
    let format = cli
        .format
        .and_then(|f| OutputFormat::from_str(&f))
        .unwrap_or(OutputFormat::Json);

    let workspace_path = cli.workspace.unwrap_or_else(|| ".".to_string());

    match cli.command {
        Commands::Owners { subcmd } => handle_owners(subcmd, &workspace_path).await,
        Commands::Release { subcmd } => handle_release(subcmd, &workspace_path, format).await,
    }
}

/// Handle owners subcommands
async fn handle_owners(
    subcmd: OwnersSubCommands,
    _workspace_path: &str,
) -> Result<CommandExitCode> {
    let config = crate::meta::CargoConfig::from_current_workspace()?;

    match subcmd {
        OwnersSubCommands::Show { .. } => Ok(crate::commands::owners::show_cmd(&config, &subcmd)),
        OwnersSubCommands::Check { .. } => {
            crate::commands::owners::check_cmd(&config, &subcmd).await
        }
        OwnersSubCommands::Sync { .. } => crate::commands::owners::sync_cmd(&config, &subcmd).await,
    }
}

/// Handle release subcommands
async fn handle_release(
    subcmd: ReleaseSubCommands,
    workspace_path: &str,
    format: OutputFormat,
) -> Result<CommandExitCode> {
    match subcmd {
        ReleaseSubCommands::Analyze(opts) => {
            crate::commands::release::analyze::execute(workspace_path, opts, format).await
        }
        ReleaseSubCommands::Plan(opts) => {
            crate::commands::release::plan::execute(workspace_path, opts, format).await
        }
        ReleaseSubCommands::Bump(opts) => {
            crate::commands::release::bump::execute(workspace_path, opts, format).await
        }
        ReleaseSubCommands::Publish(opts) => {
            crate::commands::release::publish::execute(workspace_path, opts, format).await
        }
        ReleaseSubCommands::Simulate(opts) => {
            crate::commands::release::simulate::execute(workspace_path, opts, format).await
        }
        ReleaseSubCommands::Check(opts) => {
            crate::commands::release::check::execute(workspace_path, opts, format)
        }
        ReleaseSubCommands::Status(opts) => {
            crate::commands::release::status::execute(workspace_path, opts, format).await
        }
        ReleaseSubCommands::Resume(opts) => {
            crate::commands::release::resume::execute(workspace_path, opts, format).await
        }
    }
}