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}
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}