From d0e131bb5cbca3fe5ca6f792c8ebe4f1a8f64737 Mon Sep 17 00:00:00 2001 From: ou23h4rfuiyh3e5rg <168471719+ou23h4rfuiyh3e5rg@users.noreply.github.com> Date: Tue, 3 Feb 2026 02:52:35 -0800 Subject: [PATCH] fix(shell): use NUL device on Windows instead of /dev/null (#7) Co-authored-by: ou23h4rfuiyh3e5rg --- crates/pi-natives/src/shell.rs | 53 +++++++++++++++++++++------------- 1 file changed, 33 insertions(+), 20 deletions(-) diff --git a/crates/pi-natives/src/shell.rs b/crates/pi-natives/src/shell.rs index bca8f21f9..bbad9bcd0 100644 --- a/crates/pi-natives/src/shell.rs +++ b/crates/pi-natives/src/shell.rs @@ -46,13 +46,13 @@ use windows::configure_windows_path; use crate::task; struct ShellSessionCore { - shell: BrushShell, + shell: BrushShell, current_abort: Option, } #[derive(Clone)] struct ShellConfig { - session_env: Option>, + session_env: Option>, snapshot_path: Option, } @@ -60,7 +60,7 @@ struct ShellConfig { #[napi(object)] pub struct ShellOptions { /// Environment variables to apply once per session. - pub session_env: Option>, + pub session_env: Option>, /// Optional snapshot file to source on session creation. pub snapshot_path: Option, } @@ -70,25 +70,25 @@ struct ShellRunConfig { /// Command string to execute in the shell. command: String, /// Working directory for the command. - cwd: Option, + cwd: Option, /// Environment variables to apply for this command only. - env: Option>, + env: Option>, } /// Options for running a shell command. #[napi(object)] pub struct ShellRunOptions<'env> { /// Command string to execute in the shell. - pub command: String, + pub command: String, /// Working directory for the command. - pub cwd: Option, + pub cwd: Option, /// Environment variables to apply for this command only. - pub env: Option>, + pub env: Option>, /// Timeout in milliseconds before cancelling the command. #[napi(js_name = "timeoutMs")] pub timeout_ms: Option, /// Abort signal for cancelling the operation. - pub signal: Option>, + pub signal: Option>, } /// Result of running a shell command. @@ -106,7 +106,7 @@ pub struct ShellRunResult { #[napi] pub struct Shell { session: Arc>>, - config: ShellConfig, + config: ShellConfig, } #[napi] @@ -215,21 +215,21 @@ async fn run_shell_session( #[napi(object)] pub struct ShellExecuteOptions<'env> { /// Command string to execute in the shell. - pub command: String, + pub command: String, /// Working directory for the command. - pub cwd: Option, + pub cwd: Option, /// Environment variables to apply for this command only. - pub env: Option>, + pub env: Option>, /// Environment variables to apply once per session. - pub session_env: Option>, + pub session_env: Option>, /// Timeout in milliseconds before cancelling the command. #[napi(js_name = "timeoutMs")] - pub timeout_ms: Option, + pub timeout_ms: Option, /// Optional snapshot file to source on session creation. #[napi(js_name = "snapshotPath")] pub snapshot_path: Option, /// Abort signal for cancelling the operation. - pub signal: Option>, + pub signal: Option>, } /// Result of executing a shell command via brush-core. @@ -258,8 +258,7 @@ pub fn execute_shell<'env>( ) -> Result> { let config = ShellConfig { session_env: options.session_env, snapshot_path: options.snapshot_path }; - let run_config = - ShellRunConfig { command: options.command, cwd: options.cwd, env: options.env }; + let run_config = ShellRunConfig { command: options.command, cwd: options.cwd, env: options.env }; let ct = task::CancelToken::new(options.timeout_ms, options.signal); task::future(env, "shell.execute", async move { @@ -303,7 +302,21 @@ async fn run_shell_oneshot( } fn null_file() -> Result { - openfiles::null().map_err(|err| Error::from_reason(format!("Failed to create null file: {err}"))) + #[cfg(windows)] + { + use std::fs::OpenOptions; + let file = OpenOptions::new() + .read(true) + .write(true) + .open("NUL") + .map_err(|err| Error::from_reason(format!("Failed to open NUL: {err}")))?; + Ok(OpenFile::from(file)) + } + #[cfg(not(windows))] + { + openfiles::null() + .map_err(|err| Error::from_reason(format!("Failed to create null file: {err}"))) + } } const fn exit_code(result: &ExecutionResult) -> i32 { @@ -685,7 +698,7 @@ struct TimeoutCommand { #[arg(required = true)] duration: String, #[arg(required = true, num_args = 1.., trailing_var_arg = true)] - command: Vec, + command: Vec, } impl builtins::Command for TimeoutCommand {