grex_cli/cli/verbs/
exec.rs1use crate::cli::args::{ExecArgs, GlobalFlags};
11use anyhow::Result;
12use std::path::PathBuf;
13use std::process::Command;
14use tokio_util::sync::CancellationToken;
15
16pub fn run(args: ExecArgs, global: &GlobalFlags, _cancel: &CancellationToken) -> Result<()> {
17 let json = global.json;
18 let cwd = match resolve_cwd(args.pack) {
19 Some(p) => p,
20 None => {
21 emit_error(json, "usage", "no pack root: cwd lacks .grex/pack.yaml");
22 std::process::exit(2);
23 }
24 };
25
26 let (program, rest) = match args.cmd.split_first() {
27 Some((p, r)) => (p.clone(), r.to_vec()),
28 None => {
29 emit_error(json, "usage", "command is required");
31 std::process::exit(2);
32 }
33 };
34
35 let mut cmd = Command::new(&program);
36 cmd.args(&rest).current_dir(&cwd);
37
38 let exit_code =
39 if json { run_capture(&mut cmd, &cwd, &program) } else { run_inherit(&mut cmd, &program) };
40 std::process::exit(exit_code.min(125));
41}
42
43fn resolve_cwd(explicit: Option<PathBuf>) -> Option<PathBuf> {
44 if let Some(p) = explicit {
45 return Some(p);
46 }
47 let cwd = std::env::current_dir().ok()?;
48 if cwd.join(".grex").join("pack.yaml").is_file() {
49 Some(cwd)
50 } else {
51 None
52 }
53}
54
55fn run_inherit(cmd: &mut Command, program: &str) -> i32 {
56 match cmd.status() {
57 Ok(status) => status.code().unwrap_or(125),
58 Err(err) => {
59 eprintln!("grex exec: spawn `{program}` failed: {err}");
60 127
61 }
62 }
63}
64
65fn run_capture(cmd: &mut Command, cwd: &std::path::Path, program: &str) -> i32 {
66 match cmd.output() {
67 Ok(out) => {
68 let code = out.status.code().unwrap_or(125);
69 let doc = serde_json::json!({
70 "verb": "exec",
71 "cwd": cwd.display().to_string(),
72 "exit_code": code,
73 "stdout": String::from_utf8_lossy(&out.stdout).into_owned(),
74 "stderr": String::from_utf8_lossy(&out.stderr).into_owned(),
75 });
76 println!("{}", serde_json::to_string(&doc).unwrap_or_default());
77 code
78 }
79 Err(err) => {
80 let doc = serde_json::json!({
81 "verb": "exec",
82 "error": { "kind": "spawn_failed", "program": program, "message": err.to_string() },
83 });
84 println!("{}", serde_json::to_string(&doc).unwrap_or_default());
85 127
86 }
87 }
88}
89
90fn emit_error(json: bool, kind: &str, msg: &str) {
91 if json {
92 let doc = serde_json::json!({
93 "verb": "exec",
94 "error": { "kind": kind, "message": msg },
95 });
96 println!("{}", serde_json::to_string(&doc).unwrap_or_default());
97 } else {
98 eprintln!("grex exec: {msg}");
99 }
100}