xbp 10.40.1

XBP is a zero-config build pack that can also interact with proxies, kafka, sockets, synthetic monitors.
Documentation
use std::path::Path;

use colored::Colorize;

use crate::commands::discord_notify::{
    notify_discord_for_project, oci_notification, DiscordStatus,
};
use crate::strategies::{ServiceConfig, XbpConfig};

use super::plan::DeployPlan;
use super::providers::execute_step;
use super::types::{DeployExecutionReport, DeployMode};

pub async fn execute_plan(
    project_root: &Path,
    config: &XbpConfig,
    plan: &DeployPlan,
    mode: DeployMode,
    yes: bool,
    debug: bool,
) -> Result<DeployExecutionReport, String> {
    let mut lines = Vec::new();
    let mut failed = Vec::new();

    lines.push(format!(
        "{} {} services mode={mode:?}",
        "Executing".bright_cyan().bold(),
        plan.steps.len()
    ));

    for step in &plan.steps {
        println!(
            "{} {} ({})",
            "".bright_blue().bold(),
            step.service.bright_white().bold(),
            step.provider.bright_yellow()
        );
        let service_cfg: Option<ServiceConfig> = config
            .services
            .as_ref()
            .and_then(|services| services.iter().find(|s| s.name == step.service).cloned());
        match execute_step(project_root, config, step, mode, yes, debug).await {
            Ok(step_lines) => {
                for line in step_lines {
                    println!("  {line}");
                    lines.push(line);
                }
                if let Some(image_ref) = step.image_ref.as_deref() {
                    if matches!(mode, DeployMode::Run | DeployMode::Verify) {
                        notify_discord_for_project(
                            project_root,
                            Some(config),
                            service_cfg.as_ref(),
                            oci_notification(
                                &step.service,
                                image_ref,
                                DiscordStatus::Success,
                                &format!(
                                    "OCI image verified/deployed (provider={}, mode={:?})",
                                    step.provider, mode
                                ),
                            ),
                        )
                        .await;
                    }
                }
            }
            Err(error) => {
                let msg = format!("[{}] ERROR: {error}", step.service);
                println!("  {}", msg.bright_red());
                lines.push(msg);
                failed.push(step.service.clone());
                if let Some(image_ref) = step.image_ref.as_deref() {
                    notify_discord_for_project(
                        project_root,
                        Some(config),
                        service_cfg.as_ref(),
                        oci_notification(
                            &step.service,
                            image_ref,
                            DiscordStatus::Failure,
                            &error,
                        ),
                    )
                    .await;
                }
                // Continue remaining services so a group gets partial progress visibility
            }
        }
    }

    Ok(DeployExecutionReport {
        ok: failed.is_empty(),
        lines,
        failed_services: failed,
    })
}