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}