use clap::Args;
use serde::Serialize;
use homeboy::deploy::{self, ComponentDeployResult, DeployConfig, DeploySummary};
use super::CmdResult;
#[derive(Args)]
pub struct DeployArgs {
pub project_id: Option<String>,
#[arg(short = 'p', long)]
pub project: Option<String>,
#[arg(long)]
pub json: Option<String>,
pub component_ids: Vec<String>,
#[arg(short = 'c', long = "component")]
pub component_flags: Vec<String>,
#[arg(long)]
pub all: bool,
#[arg(long)]
pub outdated: bool,
#[arg(long)]
pub dry_run: bool,
#[arg(long)]
pub check: bool,
}
#[derive(Serialize)]
pub struct DeployOutput {
pub command: String,
pub project_id: String,
pub all: bool,
pub outdated: bool,
pub dry_run: bool,
pub check: bool,
pub results: Vec<ComponentDeployResult>,
pub summary: DeploySummary,
}
pub fn run(mut args: DeployArgs, _global: &crate::commands::GlobalArgs) -> CmdResult<DeployOutput> {
let project_id = resolve_project_id(&args)?;
if args.project.is_some() {
if let Some(first_pos) = args.project_id.take() {
args.component_ids.insert(0, first_pos);
}
}
if args.project.is_none() {
let subcommand_hints = ["status", "list", "show", "help"];
if subcommand_hints.contains(&project_id.as_str()) {
return Err(homeboy::Error::validation_invalid_argument(
"project_id",
format!(
"'{}' looks like a subcommand, but 'deploy' doesn't have subcommands. \
Usage: homeboy deploy <projectId> [componentIds...] [--all]",
project_id
),
None,
None,
));
}
}
if let Some(ref spec) = args.json {
args.component_ids = deploy::parse_bulk_component_ids(spec)?;
}
let mut all_component_ids = args.component_ids.clone();
all_component_ids.extend(args.component_flags.iter().cloned());
let config = DeployConfig {
component_ids: all_component_ids,
all: args.all,
outdated: args.outdated,
dry_run: args.dry_run,
check: args.check,
};
let result = deploy::run(&project_id, &config).map_err(|e| {
if e.message.contains("No components configured for project") {
e.with_hint(format!(
"Run 'homeboy project components add {} <component-id>' to add components",
project_id
))
.with_hint("Run 'homeboy init' to see project context and available components")
} else {
e
}
})?;
let exit_code = if result.summary.failed > 0 { 1 } else { 0 };
Ok((
DeployOutput {
command: "deploy.run".to_string(),
project_id,
all: args.all,
outdated: args.outdated,
dry_run: args.dry_run,
check: args.check,
results: result.results,
summary: result.summary,
},
exit_code,
))
}
fn resolve_project_id(args: &DeployArgs) -> Result<String, homeboy::Error> {
if let Some(ref project_flag) = args.project {
let available_projects = homeboy::project::list_ids().unwrap_or_default();
if !available_projects.contains(project_flag) {
return Err(homeboy::Error::validation_invalid_argument(
"project",
format!("Project '{}' not found", project_flag),
None,
Some(vec![format!(
"Available projects: {}",
available_projects.join(", ")
)]),
));
}
return Ok(project_flag.clone());
}
match &args.project_id {
Some(id) => {
let available_components = homeboy::component::list_ids().unwrap_or_default();
if available_components.contains(id) {
return Err(homeboy::Error::validation_invalid_argument(
"project_id",
format!(
"'{}' is a component, not a project. \
Did you mean: homeboy deploy <project> {}",
id, id
),
None,
Some(vec![
"Argument order: homeboy deploy <project_id> [component_ids...]".to_string(),
format!("Alternative: homeboy deploy {} --project <project_id>", id),
]),
));
}
Ok(id.clone())
}
None => Err(homeboy::Error::validation_missing_argument(vec![
"project_id".to_string(),
])
.with_hint("homeboy deploy <project_id> [component_ids]")
.with_hint("homeboy deploy <component_id> --project <project_id>")),
}
}