Skip to main content

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    /// Stop the command once this much time has passed since it started
104    /// (default `None`: unbounded).
105    ///
106    /// Mirrors the JavaScript `timeout` option. The child is stopped with
107    /// [`kill_signal`](Self::kill_signal), then `SIGKILL` after
108    /// [`kill_grace_ms`](Self::kill_grace_ms); the result carries the
109    /// `128 + signal` code (143 for `SIGTERM`) and
110    /// [`crate::ProcessRunner::timed_out`] reports `true`. The deadline is
111    /// enforced while [`crate::ProcessRunner::run`] is awaited and also bounds
112    /// built-in virtual commands.
113    pub timeout: Option<std::time::Duration>,
114    /// Variables removed from the child's environment, the equivalent of
115    /// `env -u NAME` (default empty).
116    ///
117    /// Removals are applied after [`env`](Self::env), so a name listed here
118    /// is absent even when `env` sets it.
119    pub env_remove: Vec<String>,
120}
121
122impl Default for RunOptions {
123    fn default() -> Self {
124        RunOptions {
125            mirror: true,
126            capture: true,
127            stdin: StdinOption::Inherit,
128            cwd: None,
129            env: None,
130            prefer_local: PreferLocal::Off,
131            interactive: false,
132            shell_operators: true,
133            trace: true,
134            kill_signal: signal::DEFAULT_KILL_SIGNAL.to_string(),
135            kill_grace_ms: signal::DEFAULT_KILL_GRACE_MS,
136            timeout: None,
137            env_remove: Vec::new(),
138        }
139    }
140}
141
142/// Standard input options
143#[derive(Debug, Clone)]
144pub enum StdinOption {
145    /// Inherit from parent process
146    Inherit,
147    /// Pipe (allow writing to stdin)
148    Pipe,
149    /// Provide string content
150    Content(String),
151    /// Null device
152    Null,
153}