use std::path::Path;
use std::time::Duration;
use anyhow::{Context, Result};
const POLL_INTERVAL: Duration = Duration::from_millis(250);
pub(crate) fn step_tether(command: &[String]) -> Result<()> {
let worktree = std::env::current_dir().ok();
let mut cmd = std::process::Command::new(&command[0]);
cmd.args(&command[1..]);
worktrunk::shell_exec::scrub_directive_env_vars(&mut cmd);
set_new_process_group(&mut cmd);
let mut child = cmd
.spawn()
.with_context(|| format!("spawn tethered command: {}", command[0]))?;
let id = child.id();
if let Some(dir) = worktree {
std::thread::spawn(move || {
while !worktree_gone(&dir) {
std::thread::sleep(POLL_INTERVAL);
}
kill_process_tree(id);
});
}
let _ = child.wait();
kill_process_tree(id);
Ok(())
}
fn worktree_gone(worktree: &Path) -> bool {
matches!(
std::fs::symlink_metadata(worktree),
Err(e) if e.kind() == std::io::ErrorKind::NotFound
)
}
#[cfg(unix)]
fn set_new_process_group(cmd: &mut std::process::Command) {
use std::os::unix::process::CommandExt;
cmd.process_group(0);
}
#[cfg(windows)]
fn set_new_process_group(cmd: &mut std::process::Command) {
use std::os::windows::process::CommandExt;
cmd.creation_flags(0x0000_0200);
}
#[cfg(unix)]
fn kill_process_tree(pid: u32) {
worktrunk::shell_exec::forward_signal_with_escalation(pid as i32, signal_hook::consts::SIGTERM);
}
#[cfg(windows)]
fn kill_process_tree(pid: u32) {
let _ = std::process::Command::new("taskkill")
.args(["/T", "/F", "/PID", &pid.to_string()])
.stdout(std::process::Stdio::null())
.stderr(std::process::Stdio::null())
.status();
}