chore(release): prepare public source release

This commit is contained in:
MercuryToolbox Release
2026-07-18 15:41:59 +08:00
commit e365e5df4d
508 changed files with 163373 additions and 0 deletions
+752
View File
@@ -0,0 +1,752 @@
//! The `runprobe` command executes one command and reports runtime outcomes.
use std::ffi::OsString;
use std::fmt::Write as _;
use std::fs::{self, OpenOptions};
use std::io::Write as IoWrite;
use std::path::{Path, PathBuf};
use std::time::{Duration, SystemTime, UNIX_EPOCH};
use common::{
CliError, CommonArgs, ExitCode, RenderMode, parse_color_choice, parse_format_choice,
print_json, print_quick_help_error, print_structured, print_text,
};
use lexopt::prelude::{Long, Short, Value as ArgValue};
use runtimekit::{
ShellMode, collect_command_values, parse_duration_flag, parse_shell_mode, parse_usize_flag,
parser_value_string, render_command, run_command_capture_with_shell, tail_bytes_to_string,
};
use serde::Serialize;
const HELP: &str = "\
Probe one command execution with stable JSON and bounded output tails.
Capture success is separate from child success: when `runprobe` launches, waits, and captures
output successfully, it exits 0 and reports child state in `ok`, `exit_code`, and `timed_out`.
Use `--fail-on-child-error` when calling scripts want `runprobe` itself to exit nonzero on child
failure or timeout.
Usage:
runprobe [OPTIONS] -- <COMMAND...>
Options:
--format <FORMAT> Structured output format: text, json, toon
--json Shortcut for --format json
--toon Shortcut for --format toon
--color <WHEN> Control ANSI color output: auto, never
--quiet Suppress non-essential status output
--fail-on-child-error Exit nonzero when the child fails or times out
--shell <MODE> Launch mode: raw, pwsh, cmd (default: raw)
--timeout <DURATION> Kill the command if it exceeds this duration
--cwd <PATH> Working directory used when spawning the command
--tail-bytes <COUNT> Bytes of stdout/stderr to keep in the tail fields (default: 4096)
--log-dir <PATH> Write a full stdout/stderr log file into this directory
-h, --help Show this help text
-V, --version Show the command version
Examples:
runprobe --shell raw -- cmd /d /s /c \"exit 0\"
runprobe --shell pwsh -- Write-Output done
runprobe --shell pwsh -- '& { Write-Output done }'
runprobe --json --shell pwsh -- '& { Write-Error boom; exit 9 }' | ConvertFrom-Json
runprobe --fail-on-child-error --shell pwsh -- '& { Write-Error boom; exit 9 }'
runprobe --json --shell cmd -- echo hello | ConvertFrom-Json
";
#[derive(Debug, Clone)]
struct Cli {
common: CommonArgs,
fail_on_child_error: bool,
shell: ShellMode,
timeout: Option<Duration>,
cwd: Option<PathBuf>,
tail_bytes: usize,
log_dir: Option<PathBuf>,
command: Vec<OsString>,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
enum ParseOutcome {
Help,
Version,
Run,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
struct ProbeReport {
exit_code: Option<i32>,
duration_ms: u128,
timed_out: bool,
termination_reason: String,
stdout_tail: String,
stderr_tail: String,
log_path: Option<String>,
command: String,
argv: Vec<String>,
cwd: Option<String>,
ok: bool,
next_hint: Option<String>,
}
/// Parses CLI arguments and returns a process exit code.
#[must_use]
pub fn main_entry() -> i32 {
match parse_cli_from(std::env::args_os()) {
Ok((ParseOutcome::Help, _)) => {
print!("{HELP}");
ExitCode::Success.as_i32()
}
Ok((ParseOutcome::Version, _)) => {
println!("runprobe {}", env!("CARGO_PKG_VERSION"));
ExitCode::Success.as_i32()
}
Ok((ParseOutcome::Run, cli)) => match run(&cli) {
Ok(code) => code.as_i32(),
Err(error) => {
print_quick_help_error(&error, HELP);
error.exit_code().as_i32()
}
},
Err(error) => {
print_quick_help_error(&error, HELP);
error.exit_code().as_i32()
}
}
}
fn parse_cli_from<I, T>(args: I) -> Result<(ParseOutcome, Cli), CliError>
where
I: IntoIterator<Item = T>,
T: Into<OsString>,
{
let mut parser = lexopt::Parser::from_iter(args);
let mut cli = Cli {
common: CommonArgs::default(),
fail_on_child_error: false,
shell: ShellMode::Raw,
timeout: None,
cwd: None,
tail_bytes: 4096,
log_dir: None,
command: Vec::new(),
};
while let Some(argument) = parser
.next()
.map_err(|error| CliError::usage(error.to_string()))?
{
match argument {
Long("help") | Short('h') => return Ok((ParseOutcome::Help, cli)),
Long("version") | Short('V') => return Ok((ParseOutcome::Version, cli)),
Long("json") => cli.common.set_render_mode(RenderMode::Json),
Long("toon") => cli.common.set_render_mode(RenderMode::Toon),
Long("format") => {
let value = parser_value_string(&mut parser, "--format")?;
cli.common.set_render_mode(parse_format_choice(&value)?);
}
Long("quiet") => cli.common.quiet = true,
Long("fail-on-child-error") => cli.fail_on_child_error = true,
Long("color") => {
cli.common.color =
parse_color_choice(&parser_value_string(&mut parser, "--color")?)?;
}
Long("shell") => {
cli.shell =
parse_shell_mode("--shell", &parser_value_string(&mut parser, "--shell")?)?;
}
Long("timeout") => {
cli.timeout = Some(parse_duration_flag(
"--timeout",
&parser_value_string(&mut parser, "--timeout")?,
)?);
}
Long("cwd") => {
let value = parser
.value()
.map_err(|error| CliError::usage(error.to_string()))?;
cli.cwd = Some(PathBuf::from(value));
}
Long("tail-bytes") => {
cli.tail_bytes = parse_usize_flag(
"--tail-bytes",
&parser_value_string(&mut parser, "--tail-bytes")?,
)?;
}
Long("log-dir") => {
let value = parser
.value()
.map_err(|error| CliError::usage(error.to_string()))?;
cli.log_dir = Some(PathBuf::from(value));
}
ArgValue(value) => {
cli.command = collect_command_values(&mut parser, value)?;
break;
}
_ => {
return Err(CliError::usage(
"unsupported argument; use --help to see available options",
));
}
}
}
if cli.command.is_empty() {
return Err(CliError::usage("runprobe requires a command after --"));
}
Ok((ParseOutcome::Run, cli))
}
fn run(cli: &Cli) -> Result<ExitCode, CliError> {
let captured =
run_command_capture_with_shell(&cli.command, cli.shell, cli.cwd.as_deref(), cli.timeout)?;
let stdout_tail = tail_bytes_to_string(&captured.stdout, cli.tail_bytes);
let stderr_tail = tail_bytes_to_string(&captured.stderr, cli.tail_bytes);
let log_path = cli
.log_dir
.as_deref()
.map(|path| write_log_file(path, cli, &captured))
.transpose()?;
let report = ProbeReport {
exit_code: captured.exit_code,
duration_ms: captured.duration.as_millis(),
timed_out: captured.timed_out,
termination_reason: termination_reason(captured.timed_out, captured.exit_code),
stdout_tail,
stderr_tail,
log_path,
command: render_command(&cli.command),
argv: cli
.command
.iter()
.map(|value| value.to_string_lossy().to_string())
.collect(),
cwd: cli.cwd.as_ref().map(|path| path.display().to_string()),
ok: !captured.timed_out && captured.exit_code == Some(0),
next_hint: child_failure_hint(!captured.timed_out && captured.exit_code == Some(0), cli),
};
match cli.common.render_mode() {
RenderMode::Json => print_json(&report)?,
RenderMode::Toon => print_structured(&report, RenderMode::Toon)?,
RenderMode::Text => print_text(render_report_text(&report))?,
}
if cli.fail_on_child_error && !report.ok {
Ok(ExitCode::RuntimeError)
} else {
Ok(ExitCode::Success)
}
}
fn write_log_file(
log_dir: &Path,
cli: &Cli,
captured: &runtimekit::CapturedCommand,
) -> Result<String, CliError> {
fs::create_dir_all(log_dir).map_err(|error| {
CliError::runtime(format!("failed to create {}: {error}", log_dir.display()))
})?;
let log_path = log_dir.join(format!("runprobe-{}.log", unique_suffix()));
let mut body = String::new();
writeln!(body, "command={}", render_command(&cli.command))
.expect("writing to a String cannot fail");
writeln!(
body,
"cwd={}",
cli.cwd
.as_ref()
.map_or_else(String::new, |path| path.display().to_string())
)
.expect("writing to a String cannot fail");
writeln!(
body,
"exit_code={}",
format_optional_i32(captured.exit_code)
)
.expect("writing to a String cannot fail");
writeln!(body, "timed_out={}", captured.timed_out).expect("writing to a String cannot fail");
writeln!(body, "duration_ms={}", captured.duration.as_millis())
.expect("writing to a String cannot fail");
body.push('\n');
body.push_str("[stdout]\n");
body.push_str(&String::from_utf8_lossy(&captured.stdout));
if !body.ends_with('\n') {
body.push('\n');
}
body.push_str("\n[stderr]\n");
body.push_str(&String::from_utf8_lossy(&captured.stderr));
if !body.ends_with('\n') {
body.push('\n');
}
write_log_file_exclusive(&log_path, &body)?;
Ok(log_path.display().to_string())
}
fn write_log_file_exclusive(path: &Path, body: &str) -> Result<(), CliError> {
let mut file = OpenOptions::new()
.create_new(true)
.write(true)
.open(path)
.map_err(|error| {
CliError::runtime(format!(
"refusing to replace existing runprobe log {}: {error}",
path.display()
))
})?;
file.write_all(body.as_bytes()).map_err(|error| {
CliError::runtime(format!("failed to write {}: {error}", path.display()))
})?;
file.flush()
.map_err(|error| CliError::runtime(format!("failed to flush {}: {error}", path.display())))
}
fn unique_suffix() -> String {
let nanos = SystemTime::now()
.duration_since(UNIX_EPOCH)
.map_or(0, |value| value.as_nanos());
format!("{}-{nanos}", std::process::id())
}
fn render_report_text(report: &ProbeReport) -> String {
let mut text = format!(
"{} exit_code={} duration_ms={} timed_out={} command={}",
status_word(report),
format_optional_i32(report.exit_code),
report.duration_ms,
report.timed_out,
report.command
);
write!(text, " reason={}", report.termination_reason).expect("writing to a String cannot fail");
if let Some(cwd) = &report.cwd {
write!(text, " cwd={cwd}").expect("writing to a String cannot fail");
}
if let Some(log_path) = &report.log_path {
write!(text, " log_path={log_path}").expect("writing to a String cannot fail");
}
if report.ok {
return text;
}
if !report.stderr_tail.is_empty() {
text.push_str("\nstderr_tail:\n");
text.push_str(&report.stderr_tail);
}
if !report.stdout_tail.is_empty() {
if !text.ends_with('\n') {
text.push('\n');
}
text.push_str("stdout_tail:\n");
text.push_str(&report.stdout_tail);
}
if let Some(next_hint) = &report.next_hint {
if !text.ends_with('\n') {
text.push('\n');
}
text.push_str("next_hint:\n");
text.push_str(next_hint);
}
text
}
const fn status_word(report: &ProbeReport) -> &'static str {
if report.ok {
"ok"
} else if report.timed_out {
"timeout"
} else {
"fail"
}
}
fn format_optional_i32(value: Option<i32>) -> String {
value.map_or_else(|| "none".to_string(), |item| item.to_string())
}
fn termination_reason(timed_out: bool, exit_code: Option<i32>) -> String {
if timed_out {
"timeout".to_string()
} else if exit_code.is_some() {
"exit".to_string()
} else {
"terminated".to_string()
}
}
fn child_failure_hint(ok: bool, cli: &Cli) -> Option<String> {
if ok || cli.fail_on_child_error {
return None;
}
Some(
"rerun with --fail-on-child-error if the caller should treat child failure or timeout as a nonzero tool exit"
.to_string(),
)
}
#[cfg(test)]
mod tests {
use std::time::Duration;
use super::*;
use common::CommonArgs;
use tempfile::TempDir;
fn command(items: &[&str]) -> Vec<OsString> {
items.iter().map(OsString::from).collect()
}
#[test]
fn parse_cli_accepts_v3_flags() {
let (_, cli) = parse_cli_from([
"runprobe",
"--json",
"--shell",
"pwsh",
"--timeout",
"250ms",
"--cwd",
".",
"--tail-bytes",
"99",
"--log-dir",
"logs",
"--",
"Write-Output",
"done",
])
.expect("cli");
assert!(cli.common.json);
assert_eq!(cli.shell, ShellMode::Pwsh);
assert_eq!(cli.timeout, Some(Duration::from_millis(250)));
assert_eq!(cli.tail_bytes, 99);
assert!(cli.cwd.is_some());
assert!(cli.log_dir.is_some());
assert_eq!(cli.command.len(), 2);
}
#[test]
fn parse_cli_rejects_missing_command() {
let error = parse_cli_from(["runprobe", "--timeout", "1s"]).expect_err("missing command");
assert!(matches!(
error,
CliError::Usage(message) if message.contains("requires a command")
));
}
#[test]
fn report_text_stays_compact_on_success() {
let report = ProbeReport {
exit_code: Some(0),
duration_ms: 12,
timed_out: false,
termination_reason: "exit".into(),
stdout_tail: "done\n".into(),
stderr_tail: String::new(),
log_path: None,
command: "Write-Output done".into(),
argv: vec!["Write-Output".into(), "done".into()],
cwd: Some(".".into()),
ok: true,
next_hint: None,
};
let text = render_report_text(&report);
assert!(text.contains("ok exit_code=0"));
assert!(text.contains("duration_ms=12"));
assert!(!text.contains("stdout_tail:"));
}
#[test]
fn report_text_includes_tails_on_failure() {
let report = ProbeReport {
exit_code: Some(9),
duration_ms: 12,
timed_out: false,
termination_reason: "exit".into(),
stdout_tail: "out".into(),
stderr_tail: "boom".into(),
log_path: Some("log.txt".into()),
command: render_command(&command(&["Write-Output", "done"])),
argv: vec!["Write-Output".into(), "done".into()],
cwd: None,
ok: false,
next_hint: Some("rerun with --fail-on-child-error".into()),
};
let text = render_report_text(&report);
assert!(text.contains("fail exit_code=9"));
assert!(text.contains("stderr_tail:"));
assert!(text.contains("stdout_tail:"));
assert!(text.contains("log_path=log.txt"));
assert!(text.contains("next_hint:"));
}
#[test]
fn write_log_file_records_metadata_and_optional_exit_codes() {
let temp = TempDir::new().expect("temp dir");
let cwd = temp.path().join("work");
fs::create_dir_all(&cwd).expect("cwd");
let cli = Cli {
common: CommonArgs::default(),
fail_on_child_error: false,
shell: ShellMode::Raw,
timeout: Some(Duration::from_millis(50)),
cwd: Some(cwd.clone()),
tail_bytes: 32,
log_dir: Some(temp.path().join("logs")),
command: command(&["tool.exe", "--flag"]),
};
let captured = runtimekit::CapturedCommand {
exit_code: None,
timed_out: true,
duration: Duration::from_millis(75),
stdout: b"out".to_vec(),
stderr: b"boom".to_vec(),
};
let log_path = write_log_file(cli.log_dir.as_deref().expect("log dir"), &cli, &captured)
.expect("log path");
let body = fs::read_to_string(&log_path).expect("log body");
assert!(body.contains("command=tool.exe --flag"));
assert!(body.contains(&format!("cwd={}", cwd.display())));
assert!(body.contains("exit_code=none"));
assert!(body.contains("timed_out=true"));
assert!(body.contains("[stdout]\nout"));
assert!(body.contains("[stderr]\nboom"));
assert_eq!(format_optional_i32(Some(-9)), "-9");
assert_eq!(format_optional_i32(None), "none");
}
#[test]
fn log_file_writer_refuses_preexisting_paths() {
let temp = TempDir::new().expect("temp dir");
let path = temp.path().join("runprobe-existing.log");
fs::write(&path, "original").expect("preexisting log");
let error =
write_log_file_exclusive(&path, "replacement").expect_err("preexisting path refused");
assert!(matches!(
error,
CliError::Runtime(message)
if message.contains("refusing to replace existing runprobe log")
));
assert_eq!(
fs::read_to_string(&path).expect("preserved content"),
"original"
);
}
#[test]
fn status_word_and_report_text_cover_timeout_and_optional_fields() {
let timeout_report = ProbeReport {
exit_code: None,
duration_ms: 50,
timed_out: true,
termination_reason: "timeout".into(),
stdout_tail: String::new(),
stderr_tail: "still running".into(),
log_path: Some("logs\\probe.log".into()),
command: "pwsh -NoProfile".into(),
argv: vec!["pwsh".into(), "-NoProfile".into()],
cwd: Some("work".into()),
ok: false,
next_hint: Some("rerun with --fail-on-child-error".into()),
};
assert_eq!(status_word(&timeout_report), "timeout");
let timeout_text = render_report_text(&timeout_report);
assert!(timeout_text.contains("timeout exit_code=none"));
assert!(timeout_text.contains("reason=timeout"));
assert!(timeout_text.contains("cwd=work"));
assert!(timeout_text.contains("log_path=logs\\probe.log"));
assert!(timeout_text.contains("stderr_tail:"));
assert!(!timeout_text.contains("stdout_tail:"));
let ok_report = ProbeReport {
ok: true,
timed_out: false,
exit_code: Some(0),
duration_ms: 1,
termination_reason: "exit".into(),
stdout_tail: String::new(),
stderr_tail: String::new(),
log_path: None,
command: "tool".into(),
argv: vec!["tool".into()],
cwd: None,
next_hint: None,
};
assert_eq!(status_word(&ok_report), "ok");
let fail_report = ProbeReport {
ok: false,
timed_out: false,
exit_code: Some(9),
duration_ms: 1,
termination_reason: "exit".into(),
stdout_tail: String::new(),
stderr_tail: String::new(),
log_path: None,
command: "tool".into(),
argv: vec!["tool".into()],
cwd: None,
next_hint: Some("rerun with --fail-on-child-error".into()),
};
assert_eq!(status_word(&fail_report), "fail");
}
#[test]
fn child_failure_hint_is_only_emitted_when_tool_exit_stays_zero() {
let ok_cli = Cli {
common: CommonArgs::default(),
fail_on_child_error: false,
shell: ShellMode::Raw,
timeout: None,
cwd: None,
tail_bytes: 64,
log_dir: None,
command: command(&["tool"]),
};
assert!(child_failure_hint(true, &ok_cli).is_none());
let mut strict_cli = ok_cli.clone();
strict_cli.fail_on_child_error = true;
assert!(child_failure_hint(false, &strict_cli).is_none());
assert!(
child_failure_hint(false, &ok_cli)
.expect("hint")
.contains("--fail-on-child-error")
);
}
#[test]
fn write_log_file_surfaces_log_dir_creation_failures() {
let temp = TempDir::new().expect("temp dir");
let not_a_dir = temp.path().join("occupied");
fs::write(&not_a_dir, "file").expect("occupied file");
let cli = Cli {
common: CommonArgs::default(),
fail_on_child_error: false,
shell: ShellMode::Raw,
timeout: None,
cwd: None,
tail_bytes: 16,
log_dir: Some(not_a_dir.clone()),
command: command(&["tool.exe"]),
};
let captured = runtimekit::CapturedCommand {
exit_code: Some(0),
timed_out: false,
duration: Duration::from_millis(1),
stdout: Vec::new(),
stderr: Vec::new(),
};
assert!(matches!(
write_log_file(&not_a_dir, &cli, &captured),
Err(CliError::Runtime(message)) if message.contains("failed to create")
));
}
#[cfg(windows)]
#[test]
fn run_maps_success_failure_and_timeout_exit_codes() {
let success_cli = Cli {
common: CommonArgs::default(),
fail_on_child_error: false,
shell: ShellMode::Raw,
timeout: Some(Duration::from_secs(1)),
cwd: None,
tail_bytes: 32,
log_dir: None,
command: command(&["pwsh", "-NoProfile", "-Command", "Write-Output ok; exit 0"]),
};
assert_eq!(run(&success_cli).expect("success"), ExitCode::Success);
let failure_cli = Cli {
common: CommonArgs {
json: true,
format: None,
..CommonArgs::default()
},
fail_on_child_error: false,
shell: ShellMode::Raw,
timeout: Some(Duration::from_secs(1)),
cwd: None,
tail_bytes: 32,
log_dir: None,
command: command(&[
"pwsh",
"-NoProfile",
"-Command",
"Write-Error boom; Write-Output out; exit 9",
]),
};
assert_eq!(run(&failure_cli).expect("failure"), ExitCode::Success);
let temp = TempDir::new().expect("temp dir");
let log_dir = temp.path().join("logs");
let timeout_cli = Cli {
common: CommonArgs::default(),
fail_on_child_error: false,
shell: ShellMode::Raw,
timeout: Some(Duration::from_millis(50)),
cwd: None,
tail_bytes: 32,
log_dir: Some(log_dir.clone()),
command: command(&[
"pwsh",
"-NoProfile",
"-Command",
"Start-Sleep -Milliseconds 200",
]),
};
assert_eq!(run(&timeout_cli).expect("timeout"), ExitCode::Success);
let entries = fs::read_dir(&log_dir).expect("log dir");
assert!(entries.count() >= 1);
}
#[cfg(windows)]
#[test]
fn run_accepts_single_string_pwsh_commands() {
let cli = Cli {
common: CommonArgs {
json: true,
format: None,
..CommonArgs::default()
},
fail_on_child_error: false,
shell: ShellMode::Pwsh,
timeout: Some(Duration::from_secs(1)),
cwd: None,
tail_bytes: 32,
log_dir: None,
command: command(&["Write-Output done"]),
};
assert_eq!(run(&cli).expect("pwsh inline command"), ExitCode::Success);
}
#[cfg(windows)]
#[test]
fn fail_on_child_error_switches_exit_code_for_failures_and_timeouts() {
let failure_cli = Cli {
common: CommonArgs::default(),
fail_on_child_error: true,
shell: ShellMode::Raw,
timeout: Some(Duration::from_secs(1)),
cwd: None,
tail_bytes: 32,
log_dir: None,
command: command(&["pwsh", "-NoProfile", "-Command", "Write-Error boom; exit 7"]),
};
assert_eq!(run(&failure_cli).expect("failure"), ExitCode::RuntimeError);
let timeout_cli = Cli {
command: command(&[
"pwsh",
"-NoProfile",
"-Command",
"Start-Sleep -Milliseconds 200",
]),
timeout: Some(Duration::from_millis(50)),
..failure_cli
};
assert_eq!(run(&timeout_cli).expect("timeout"), ExitCode::RuntimeError);
}
}