xbp 10.15.4

XBP is a zero-config build pack that can also interact with proxies, kafka, sockets, synthetic monitors.
Documentation
//! local redeploy command module
//!
//! finds and executes .xbp/redeploy.sh in the current or parent directory
//! ensures the script is executable and reports stdout stderr via the logging
//! system for troubleshooting legacy redeploy support
use std::path::{Path, PathBuf};
use std::process::Output;
use std::time::{Duration, Instant};
use tokio::process::Command;

use crate::logging::{log_debug, log_error, log_info, log_process_output, log_timed};

/// Execute the `redeploy` command locally.
///
/// Searches up to one parent for `.xbp/redeploy.sh`, makes it executable via
/// `sudo chmod +x`, and runs it.
pub async fn run_redeploy() -> Result<(), String> {
    let current_dir: PathBuf =
        std::env::current_dir().map_err(|e| format!("Failed to get current directory: {}", e))?;
    let mut dir: &Path = current_dir.as_path();
    let mut xbp_path: Option<PathBuf> = None;
    let mut working_dir: Option<PathBuf> = None;

    for _ in 0..2 {
        let potential_path: PathBuf = dir.join(".xbp").join("redeploy.sh");
        log_debug(
            "redeploy",
            &format!("Checking for redeploy.sh at: {}", potential_path.display()),
            None,
        )
        .await
        .ok();
        if potential_path.exists() {
            xbp_path = Some(potential_path);
            // Set working directory to the parent of .xbp (where xbp.json should be)
            working_dir = Some(dir.to_path_buf());
            log_debug(
                "redeploy",
                &format!(
                    "Found redeploy.sh at: {}",
                    xbp_path.as_ref().unwrap().display()
                ),
                None,
            )
            .await
            .ok();
            log_debug(
                "redeploy",
                &format!(
                    "Will execute from working directory: {}",
                    working_dir.as_ref().unwrap().display()
                ),
                None,
            )
            .await
            .ok();
            break;
        }
        if let Some(parent) = dir.parent() {
            dir = parent;
        } else {
            break;
        }
    }

    if let Some(xbp_path) = xbp_path {
        let working_dir = working_dir.expect("working_dir should be set when xbp_path is found");
        log_info(
            "redeploy",
            &format!("Found redeploy.sh at: {}", xbp_path.display()),
            None,
        )
        .await
        .ok();
        let chmod_start: Instant = Instant::now();
        let chmod_output: Output = match Command::new("sudo")
            .arg("chmod")
            .arg("+x")
            .arg(&xbp_path)
            .output()
            .await
        {
            Ok(output) => output,
            Err(e) => {
                let error_msg = format!("Failed to execute chmod command: {}", e);
                log_error("redeploy", &error_msg, None).await.ok();
                return Err(error_msg);
            }
        };
        let chmod_elapsed: Duration = chmod_start.elapsed();
        log_info("redeploy", "Made redeploy.sh executable.", None)
            .await
            .ok();
        log_debug(
            "redeploy",
            &format!("chmod output: {:?}", chmod_output),
            None,
        )
        .await
        .ok();
        log_timed(
            crate::logging::LogLevel::Debug,
            "redeploy",
            "chmod",
            chmod_elapsed.as_millis() as u64,
        )
        .await
        .ok();

        if !chmod_output.status.success() {
            let error_msg = format!(
                "Failed to make redeploy.sh executable: {}",
                String::from_utf8_lossy(&chmod_output.stderr)
            );
            log_error("redeploy", &error_msg, None).await.ok();
            return Err(error_msg);
        }

        let redeploy_start: Instant = Instant::now();
        let redeploy_output: Output = Command::new(&xbp_path)
            .current_dir(&working_dir)
            .output()
            .await
            .map_err(|e| format!("Failed to execute redeploy.sh: {}", e))?;
        let redeploy_elapsed = redeploy_start.elapsed();

        log_debug(
            "redeploy",
            &format!("redeploy.sh output: {:?}", redeploy_output),
            None,
        )
        .await
        .ok();
        log_timed(
            crate::logging::LogLevel::Debug,
            "redeploy",
            "redeploy.sh",
            redeploy_elapsed.as_millis() as u64,
        )
        .await
        .ok();

        let stdout_output = String::from_utf8_lossy(&redeploy_output.stdout);
        let stderr_output = String::from_utf8_lossy(&redeploy_output.stderr);

        if !redeploy_output.status.success() {
            // Check for the specific error about xbp.json not found
            if stderr_output.contains("./xbp.json not found")
                || stdout_output.contains("./xbp.json not found")
            {
                let xbp_json_path = working_dir.join("xbp.json");
                let error_msg = if xbp_json_path.exists() {
                    format!(
                        "Failed to run redeploy.sh: xbp.json not found in working directory.\n\
                        Script was executed from: {}\n\
                        Expected xbp.json at: {}\n\
                        Note: The script looks for ./xbp.json relative to its execution directory.\n\
                        stderr: {}\nstdout: {}",
                        working_dir.display(),
                        xbp_json_path.display(),
                        stderr_output,
                        stdout_output
                    )
                } else {
                    format!(
                        "Failed to run redeploy.sh: xbp.json not found.\n\
                        Script was executed from: {}\n\
                        Expected xbp.json at: {}\n\
                        Please ensure xbp.json exists in the project root.\n\
                        stderr: {}\nstdout: {}",
                        working_dir.display(),
                        xbp_json_path.display(),
                        stderr_output,
                        stdout_output
                    )
                };
                log_error("redeploy", &error_msg, None).await.ok();
                let _ =
                    log_process_output("redeploy", "redeploy.sh", &stdout_output, &stderr_output)
                        .await;
                return Err(error_msg);
            }

            let error_msg = format!(
                "Failed to run redeploy.sh:\nstderr: {}\nstdout: {}",
                stderr_output, stdout_output
            );
            log_error("redeploy", &error_msg, None).await.ok();
            let _ =
                log_process_output("redeploy", "redeploy.sh", &stdout_output, &stderr_output).await;
            return Err(error_msg);
        }

        log_info(
            "redeploy",
            &format!("Redeploy output: {}", stdout_output),
            None,
        )
        .await
        .ok();
        let _ = log_process_output("redeploy", "redeploy.sh", &stdout_output, &stderr_output).await;
        Ok(())
    } else {
        let error_msg = "No .xbp/redeploy.sh found in the directory hierarchy.";
        log_error("redeploy", error_msg, None).await.ok();
        Err(error_msg.to_string())
    }
}