clankers-cli 0.1.5

Command-line interface for clankeRS
Documentation
use std::path::Path;

use anyhow::{Context, Result};

/// Run the current project's node with MCAP recording force-enabled.
///
/// Recording happens inside the node process (the sim pub/sub bus is
/// per-process), so this sets `CLANKERS_RECORD_MCAP`/`CLANKERS_RECORD_OUTPUT`
/// and runs the node; the `#[clankers::node]` entry point picks them up and
/// finalizes the file on exit or Ctrl-C.
pub async fn execute(output: &str) -> Result<()> {
    if !Path::new("clankeRS.toml").exists() {
        anyhow::bail!(
            "no clankeRS.toml in the current directory — run `clankers record` inside a \
             clankeRS project. (You can also set [logging] record_mcap = true in \
             clankeRS.toml and use `clankers run`.)"
        );
    }

    println!("clankeRS record → {output} (Ctrl-C to stop)");
    let mut child = tokio::process::Command::new("cargo")
        .args(["run", "--release"])
        .env("CLANKERS_RECORD_MCAP", "1")
        .env("CLANKERS_RECORD_OUTPUT", output)
        .spawn()
        .context("failed to run cargo run")?;

    // On Ctrl-C the node receives its own SIGINT from the terminal and
    // finalizes the MCAP file; swallow the signal here and wait for it.
    let status = tokio::select! {
        s = child.wait() => Some(s.context("wait for node")?),
        _ = tokio::signal::ctrl_c() => {
            let _ = child.wait().await;
            None
        }
    };

    if let Some(status) = status {
        if !status.success() {
            anyhow::bail!("node exited with {status}");
        }
    }
    println!("clankeRS record finished → {output}");
    Ok(())
}