command_stream/run_options.rs
1//! Public execution options and errors.
2
3use crate::{signal, PreferLocal};
4use std::collections::HashMap;
5use std::path::PathBuf;
6
7/// Error type for command-stream operations
8#[derive(Debug, thiserror::Error)]
9pub enum Error {
10 #[error("IO error: {0}")]
11 Io(#[from] std::io::Error),
12
13 #[error("Command failed with exit code {code}: {message}")]
14 CommandFailed { code: i32, message: String },
15
16 #[error("Command not found: {0}")]
17 CommandNotFound(String),
18
19 #[error("Parse error: {0}")]
20 ParseError(String),
21
22 #[error("Cancelled")]
23 Cancelled,
24}
25
26impl Error {
27 /// Build a [`Error::CommandFailed`] for a command that exited with `code`.
28 pub fn command_failed(code: i32, message: impl Into<String>) -> Self {
29 Error::CommandFailed {
30 code,
31 message: message.into(),
32 }
33 }
34
35 /// Exit status carried by the error, when the failure has one.
36 ///
37 /// Mirrors the `error.code` property of the JavaScript implementation
38 /// (issue #38). Failures that never reached a child process, such as parse
39 /// errors, report `None`.
40 pub fn code(&self) -> Option<i32> {
41 match self {
42 Error::CommandFailed { code, .. } => Some(*code),
43 // `command not found` is 127 in POSIX shells, which is also what
44 // the JavaScript implementation reports for a missing executable.
45 Error::CommandNotFound(_) => Some(127),
46 Error::Io(error) => match error.kind() {
47 std::io::ErrorKind::NotFound => Some(127),
48 std::io::ErrorKind::PermissionDenied => Some(126),
49 _ => None,
50 },
51 // A cancelled command is terminated with SIGINT (128 + 2).
52 Error::Cancelled => Some(130),
53 Error::ParseError(_) => None,
54 }
55 }
56
57 /// Alias for [`code`](Self::code).
58 ///
59 /// Node.js `child_process` names this property `code`, while execa, zx,
60 /// nano-spawn, and Bun Shell name it `exitCode`. command-stream exposes
61 /// both spellings in every language (issue #38).
62 pub fn exit_code(&self) -> Option<i32> {
63 self.code()
64 }
65}
66
67/// Result type for command-stream operations
68pub type Result<T> = std::result::Result<T, Error>;
69
70/// Options for command execution
71#[derive(Debug, Clone)]
72pub struct RunOptions {
73 /// Mirror output to parent stdout/stderr
74 pub mirror: bool,
75 /// Capture output in result
76 pub capture: bool,
77 /// Standard input handling
78 pub stdin: StdinOption,
79 /// Working directory
80 pub cwd: Option<PathBuf>,
81 /// Environment variables
82 pub env: Option<HashMap<String, String>>,
83 /// Prefer executables from the working directory or explicit local directories.
84 pub prefer_local: PreferLocal,
85 /// Interactive mode (TTY forwarding)
86 pub interactive: bool,
87 /// Enable shell operator parsing
88 pub shell_operators: bool,
89 /// Enable tracing for this command
90 pub trace: bool,
91 /// Signal used to stop the process when it is killed without an explicit
92 /// signal, i.e. [`crate::ProcessRunner::kill`] (default `SIGTERM`).
93 ///
94 /// Mirrors the JavaScript `killSignal` option. An explicit
95 /// [`crate::ProcessRunner::kill_with`] argument always overrides it.
96 pub kill_signal: String,
97 /// Milliseconds the child is given to handle the kill signal before
98 /// `SIGKILL` is sent (default 100).
99 ///
100 /// Mirrors the JavaScript `killGrace` option. This is the window in which a
101 /// child running its own signal handler can shut down on its own terms.
102 pub kill_grace_ms: u64,
103}
104
105impl Default for RunOptions {
106 fn default() -> Self {
107 RunOptions {
108 mirror: true,
109 capture: true,
110 stdin: StdinOption::Inherit,
111 cwd: None,
112 env: None,
113 prefer_local: PreferLocal::Off,
114 interactive: false,
115 shell_operators: true,
116 trace: true,
117 kill_signal: signal::DEFAULT_KILL_SIGNAL.to_string(),
118 kill_grace_ms: signal::DEFAULT_KILL_GRACE_MS,
119 }
120 }
121}
122
123/// Standard input options
124#[derive(Debug, Clone)]
125pub enum StdinOption {
126 /// Inherit from parent process
127 Inherit,
128 /// Pipe (allow writing to stdin)
129 Pipe,
130 /// Provide string content
131 Content(String),
132 /// Null device
133 Null,
134}