executesoft 0.2.1

ExecuteSoft repository automation CLI
use clap::{Args, Parser, Subcommand};

#[derive(Parser)]
#[command(
    name = "exe",
    about = "ExecuteSoft repository tooling",
    long_about = "ExecuteSoft repository tooling for service generation, compliance checks, gateway route generation, local development, database helpers, sync, release, and deployment workflows.",
    after_long_help = "Examples:\n  exe setup\n  exe dev\n  exe service check-all\n  exe service create --name recommendation-engine --domain core --template rust\n  exe gateway route entry --service auth --rpc Login\n  exe gateway generate\n  exe dev up\n  exe release-sync\n  exe deploy kubernetes tag=latest"
)]
pub(crate) struct Cli {
    #[command(subcommand)]
    pub(crate) command: Commands,
}

#[derive(Subcommand)]
pub(crate) enum Commands {
    #[command(about = "Create, validate, build, test, and run services")]
    Service {
        #[command(subcommand)]
        command: ServiceCommand,
    },
    #[command(about = "Generate and validate public gateway route artifacts")]
    Gateway {
        #[command(subcommand)]
        command: GatewayCommand,
    },
    #[command(about = "Run service database migration helpers")]
    Db {
        #[command(subcommand)]
        command: DbCommand,
    },
    #[command(
        about = "Prepare a service-only checkout for development",
        long_about = "Prepare the current service checkout: validate service.yaml, run local generators, download language dependencies, and start service-local Docker dependencies when development/compose.dev.yml exists."
    )]
    Setup(KeyValueArgs),
    #[command(
        about = "Run service-local dev mode or an explicit Docker Compose dev stack",
        long_about = "Run plain `exe dev` inside a service checkout to start shared local dependencies and hot-reload that service. Compose subcommands such as `exe dev up` and `exe dev all` use tools/local-dev/compose.yml by default. Pass DEV_COMPOSE_FILE=path/to/compose.yml only for a custom stack."
    )]
    Dev {
        #[command(subcommand)]
        command: Option<DevCommand>,
    },
    #[command(about = "Sync service assets before release or deployment")]
    Sync,
    #[command(about = "Run the release workflow, for example: exe release tag=latest")]
    Release(KeyValueArgs),
    #[command(
        about = "Bump the CLI crate patch version and publish it",
        long_about = "Bump tools/exe/Cargo.toml to the next patch version, then run cargo publish for the executesoft CLI crate. Pass cargo publish flags such as --allow-dirty after the command."
    )]
    Publish(KeyValueArgs),
    #[command(about = "Upload the local devops folder to the production server")]
    ReleaseSync(KeyValueArgs),
    #[command(about = "Run deployment workflows")]
    Deploy {
        #[command(subcommand)]
        command: DeployCommand,
    },
}

#[derive(Subcommand)]
pub(crate) enum ServiceCommand {
    #[command(about = "Validate one service or template skeleton against the compliance contract")]
    Check {
        #[arg(help = "Service directory, for example services/core/auth")]
        path: String,
    },
    #[command(about = "Validate all service roots and certified template skeletons")]
    CheckAll,
    #[command(about = "Create a service from a certified template")]
    Create(CreateServiceArgs),
    #[command(about = "Run the service test workflow")]
    Test(ServiceTargetArgs),
    #[command(about = "Run the service build workflow")]
    Build(ServiceTargetArgs),
    #[command(about = "Run the service generation workflow")]
    Generate(ServiceTargetArgs),
    #[command(about = "Run the service locally")]
    Run(ServiceTargetArgs),
    #[command(about = "Run the Rust clippy workflow for a service")]
    Clippy(ServiceTargetArgs),
    #[command(about = "Run service compliance checks")]
    Compliance(ServiceTargetArgs),
    #[command(about = "Validate a service gateway route source file")]
    GatewayRoutesCheck(ServiceTargetArgs),
}

#[derive(Args)]
pub(crate) struct CreateServiceArgs {
    #[arg(help = "Service key, for example recommendation-engine")]
    #[arg(long, alias = "service")]
    pub(crate) name: String,
    #[arg(
        help = "Service domain folder under services/",
        long,
        default_value = "core"
    )]
    pub(crate) domain: String,
    #[arg(
        help = "Template language: go, rust, python, or typescript",
        long = "template",
        alias = "tpl",
        default_value = "go"
    )]
    pub(crate) template_lang: String,
    #[arg(help = "Template service type", long = "type", default_value = "grpc")]
    pub(crate) service_type: String,
    #[arg(help = "Template framework variant", long, default_value = "native")]
    pub(crate) framework: String,
    #[arg(help = "Owning team metadata")]
    #[arg(long)]
    pub(crate) owner_team: Option<String>,
    #[arg(help = "Output directory; defaults to services/<domain>/<name>")]
    #[arg(long)]
    pub(crate) output: Option<String>,
    #[arg(help = "Override template metadata name")]
    #[arg(long)]
    pub(crate) template_name: Option<String>,
    #[arg(help = "Override generated protobuf package")]
    #[arg(long)]
    pub(crate) proto_package: Option<String>,
    #[arg(help = "Override generated protobuf service class prefix")]
    #[arg(long)]
    pub(crate) service_class: Option<String>,
}

#[derive(Args)]
pub(crate) struct ServiceTargetArgs {
    #[arg(help = "Service name or service path, for example auth or services/core/auth")]
    pub(crate) target: String,
    #[arg(
        help = "Extra Make variables, for example TAG=latest or --image-tag=v1",
        trailing_var_arg = true,
        allow_hyphen_values = true
    )]
    pub(crate) vars: Vec<String>,
}

#[derive(Subcommand)]
pub(crate) enum GatewayCommand {
    #[command(about = "Regenerate gateway routes, permissions, mappers, and OpenAPI")]
    Generate,
    #[command(about = "Create gateway route source files")]
    Route {
        #[command(subcommand)]
        command: GatewayRouteCommand,
    },
    #[command(about = "Validate gateway route source JSON files")]
    ValidateRoutes {
        #[arg(help = "Route JSON files to validate")]
        files: Vec<String>,
    },
}

#[derive(Subcommand)]
pub(crate) enum GatewayRouteCommand {
    #[command(about = "Create a route source file for a service")]
    Create(GatewayRouteArgs),
    #[command(about = "Create or update route source from protobuf RPCs")]
    FromProto(GatewayRouteArgs),
    #[command(about = "Add one RPC route entry to a service route source")]
    Entry(GatewayRouteEntryArgs),
}

#[derive(Args)]
pub(crate) struct GatewayRouteArgs {
    #[arg(help = "Service key, for example auth", long, alias = "name")]
    pub(crate) service: String,
    #[arg(help = "Overwrite an existing route source when supported", long)]
    pub(crate) force: bool,
    #[arg(
        help = "Append generated routes instead of replacing when supported",
        long
    )]
    pub(crate) append: bool,
}

#[derive(Args)]
pub(crate) struct GatewayRouteEntryArgs {
    #[arg(help = "Service key, for example auth", long, alias = "name")]
    pub(crate) service: String,
    #[arg(help = "RPC method name to add", long)]
    pub(crate) rpc: String,
    #[arg(help = "Overwrite an existing route source when supported", long)]
    pub(crate) force: bool,
    #[arg(
        help = "Append generated routes instead of replacing when supported",
        long
    )]
    pub(crate) append: bool,
}

#[derive(Subcommand)]
pub(crate) enum DbCommand {
    #[command(about = "Run pending database migrations")]
    Migrate(KeyValueArgs),
    #[command(about = "Apply database migrations")]
    Up(KeyValueArgs),
    #[command(about = "Show database migration status")]
    Status(KeyValueArgs),
    #[command(about = "Revert the last database migration")]
    Revert(KeyValueArgs),
    #[command(about = "Rollback database migrations")]
    Down(KeyValueArgs),
    #[command(about = "Reset database state for local development")]
    Reset(KeyValueArgs),
    #[command(about = "Create a new migration file; pass NAME=create_table")]
    New(KeyValueArgs),
}

#[derive(Subcommand)]
pub(crate) enum DevCommand {
    #[command(about = "Start shared local dependencies")]
    Up(KeyValueArgs),
    #[command(about = "Start all services from the local dev compose file")]
    All(KeyValueArgs),
    #[command(about = "Build services from the local dev compose file")]
    Build(KeyValueArgs),
    #[command(about = "Stop shared local dependencies")]
    Down(KeyValueArgs),
    #[command(about = "Tail shared local dependency logs")]
    Logs(KeyValueArgs),
    #[command(about = "Stop and remove shared local dependency volumes")]
    Reset(KeyValueArgs),
    #[command(about = "Run a command with simple polling hot reload")]
    Watch(WatchArgs),
}

#[derive(Args)]
pub(crate) struct WatchArgs {
    #[arg(
        help = "Environment file to load",
        long,
        default_value = "configs/app.env.example"
    )]
    pub(crate) env_file: String,
    #[arg(help = "Directory to watch", long = "watch", default_value = ".")]
    pub(crate) watch_root: String,
    #[arg(help = "File basename to ignore; can be repeated", long)]
    pub(crate) ignore: Vec<String>,
    #[arg(help = "Polling interval in seconds", long, default_value_t = 1.0)]
    pub(crate) poll: f64,
    #[arg(help = "Command to run after --", last = true, required = true)]
    pub(crate) command: Vec<String>,
}

#[derive(Subcommand)]
pub(crate) enum DeployCommand {
    #[command(about = "Sync assets and deploy Kubernetes manifests")]
    Kubernetes(KeyValueArgs),
    #[command(about = "Build or deploy Docker artifacts")]
    Docker(KeyValueArgs),
    #[command(about = "Apply production service database migrations")]
    Migrations(KeyValueArgs),
    #[command(about = "Temporarily enable full observability stack")]
    ObservabilityUp(KeyValueArgs),
    #[command(about = "Return observability stack to balanced mode")]
    ObservabilityDown(KeyValueArgs),
    #[command(about = "Run the Postgres backup job once")]
    BackupNow(KeyValueArgs),
    #[command(about = "Install the daily host cron backup")]
    InstallBackupCron(KeyValueArgs),
    #[command(about = "Upload the local devops folder to the production server")]
    ReleaseSync(KeyValueArgs),
    #[command(about = "Reload Caddy on the production Docker host")]
    ReloadCaddy(KeyValueArgs),
    #[command(about = "Restart a Docker Compose service; pass CONTAINER=<name>")]
    Restart(KeyValueArgs),
    #[command(about = "Force-recreate a Docker Compose service; pass CONTAINER=<name>")]
    Recreate(KeyValueArgs),
}

#[derive(Args, Clone)]
pub(crate) struct KeyValueArgs {
    #[arg(
        help = "Workflow variables as key=value pairs, for example tag=latest",
        trailing_var_arg = true,
        allow_hyphen_values = true
    )]
    pub(crate) vars: Vec<String>,
}