vv-agent 0.4.1

VectorVein agent runtime, SDK, CLI, tools, and workspace backends
Documentation
use std::collections::BTreeMap;
use std::io::Write;
use std::path::Path;
use std::process::{Child, Command, ExitStatus, Stdio};
use std::time::{Duration, Instant};

use super::output::{next_output_path, open_output_file, remove_captured_output};
use super::platform::configure_process_group;

#[derive(Debug)]
pub struct CapturedProcess {
    pub child: Child,
    pub output_path: std::path::PathBuf,
}

pub fn start_captured_process(
    command: &[String],
    cwd: &Path,
    stdin_text: Option<&str>,
) -> std::io::Result<CapturedProcess> {
    start_captured_process_with_env(command, cwd, stdin_text, None)
}

pub fn start_captured_process_with_env(
    command: &[String],
    cwd: &Path,
    stdin_text: Option<&str>,
    env: Option<&BTreeMap<String, String>>,
) -> std::io::Result<CapturedProcess> {
    let Some(program) = command.first() else {
        return Err(std::io::Error::new(
            std::io::ErrorKind::InvalidInput,
            "empty command",
        ));
    };
    let output_path = next_output_path();
    let stdout_file = open_output_file(&output_path)?;
    let stderr_file = stdout_file.try_clone()?;

    let mut child_command = Command::new(program);
    child_command
        .args(&command[1..])
        .current_dir(cwd)
        .stdin(if stdin_text.is_some() {
            Stdio::piped()
        } else {
            Stdio::null()
        })
        .stdout(Stdio::from(stdout_file))
        .stderr(Stdio::from(stderr_file));
    if let Some(env) = env {
        child_command.envs(env);
    }

    configure_process_group(&mut child_command);

    let mut child = match child_command.spawn() {
        Ok(child) => child,
        Err(error) => {
            remove_captured_output(&output_path);
            return Err(error);
        }
    };

    if let Some(stdin_text) = stdin_text {
        if let Some(mut stdin) = child.stdin.take() {
            stdin.write_all(stdin_text.as_bytes())?;
        }
    }

    Ok(CapturedProcess { child, output_path })
}

pub fn wait_for_child(child: &mut Child, timeout: Duration) -> std::io::Result<Option<ExitStatus>> {
    let started_at = Instant::now();
    loop {
        if let Some(status) = child.try_wait()? {
            return Ok(Some(status));
        }
        if started_at.elapsed() >= timeout {
            return Ok(None);
        }
        std::thread::sleep(Duration::from_millis(25));
    }
}