homeboy 0.124.11

CLI for multi-component deployment and development workflow automation
Documentation
use crate::component::Component;
use crate::error::{Error, Result};
use crate::extension::ExtensionCapability;
use crate::server::{execute_local_command_passthrough, CommandOutput};
use std::path::Path;

#[derive(Debug, Clone)]
pub struct SelfCheckOutput {
    pub exit_code: i32,
    pub success: bool,
    pub stdout: String,
    pub stderr: String,
}

pub fn run_self_checks(
    component: &Component,
    capability: ExtensionCapability,
    source_path: &Path,
) -> Result<SelfCheckOutput> {
    let commands = component.self_check_commands(capability);
    if commands.is_empty() {
        return Err(Error::validation_invalid_argument(
            "self_checks",
            format!(
                "Component '{}' has no {} self-check commands configured",
                component.id,
                capability.label()
            ),
            None,
            None,
        ));
    }

    let working_dir = source_path.to_string_lossy();
    let mut stdout = String::new();
    let mut stderr = String::new();

    for command in commands {
        crate::log_status!(
            "self-check",
            "running {} self-check for {}: {}",
            capability.label(),
            component.id,
            command
        );
        let output = execute_self_check_command(command, &working_dir);
        stdout.push_str(&output.stdout);
        stderr.push_str(&output.stderr);

        if !output.success {
            return Ok(SelfCheckOutput {
                exit_code: output.exit_code,
                success: false,
                stdout,
                stderr,
            });
        }
    }

    Ok(SelfCheckOutput {
        exit_code: 0,
        success: true,
        stdout,
        stderr,
    })
}

fn execute_self_check_command(command: &str, working_dir: &str) -> CommandOutput {
    execute_local_command_passthrough(command, Some(working_dir), None)
}

#[cfg(test)]
mod tests {
    use super::*;
    use crate::component::{Component, SelfCheckConfig};

    #[test]
    fn test_run_self_checks_requires_configured_commands() {
        let component = Component::new(
            "fixture".to_string(),
            "/tmp/fixture".to_string(),
            "".to_string(),
            None,
        );

        let err = run_self_checks(&component, ExtensionCapability::Test, Path::new("/tmp"))
            .expect_err("missing self-checks should fail");

        assert!(err.to_string().contains("no test self-check commands"));
    }

    #[test]
    fn test_run_self_checks_runs_commands_in_order() {
        let dir = tempfile::tempdir().expect("temp dir");
        std::fs::write(dir.path().join("one.sh"), "printf one >> order.txt\n")
            .expect("first script should be written");
        std::fs::write(dir.path().join("two.sh"), "printf two >> order.txt\n")
            .expect("second script should be written");

        let mut component = Component::new(
            "fixture".to_string(),
            dir.path().to_string_lossy().to_string(),
            "".to_string(),
            None,
        );
        component.self_checks = Some(SelfCheckConfig {
            lint: vec!["sh one.sh".to_string(), "sh two.sh".to_string()],
            test: Vec::new(),
        });

        let output = run_self_checks(&component, ExtensionCapability::Lint, dir.path())
            .expect("self-checks should run");

        assert!(output.success);
        assert_eq!(
            std::fs::read_to_string(dir.path().join("order.txt")).unwrap(),
            "onetwo"
        );
    }
}