1use crate::error::ReleaseError;
13
14pub fn run_shell(
18 cmd: &str,
19 stdin_data: Option<&str>,
20 env: &[(&str, &str)],
21) -> Result<(), ReleaseError> {
22 let mut child = {
23 let mut builder = std::process::Command::new("sh");
24 builder.args(["-c", cmd]);
25 for &(k, v) in env {
26 builder.env(k, v);
27 }
28 if stdin_data.is_some() {
29 builder.stdin(std::process::Stdio::piped());
30 } else {
31 builder.stdin(std::process::Stdio::inherit());
32 }
33 builder
34 .spawn()
35 .map_err(|e| ReleaseError::Hook(format!("{cmd}: {e}")))?
36 };
37
38 if let Some(data) = stdin_data
39 && let Some(ref mut stdin) = child.stdin
40 {
41 use std::io::Write;
42 let _ = stdin.write_all(data.as_bytes());
43 }
44
45 let status = child
46 .wait()
47 .map_err(|e| ReleaseError::Hook(format!("{cmd}: {e}")))?;
48
49 if !status.success() {
50 let code = status.code().unwrap_or(1);
51 return Err(ReleaseError::Hook(format!("{cmd} exited with code {code}")));
52 }
53
54 Ok(())
55}
56
57#[cfg(test)]
58mod tests {
59 use super::*;
60
61 #[test]
62 fn run_shell_success() {
63 run_shell("true", None, &[]).unwrap();
64 }
65
66 #[test]
67 fn run_shell_failure() {
68 let result = run_shell("false", None, &[]);
69 assert!(result.is_err());
70 }
71
72 #[test]
73 fn run_shell_with_env() {
74 run_shell("test \"$MY_VAR\" = hello", None, &[("MY_VAR", "hello")]).unwrap();
75 }
76}