diff --git a/crates/vite_command/Cargo.toml b/crates/vite_command/Cargo.toml index b2bc76aead..0254bcda39 100644 --- a/crates/vite_command/Cargo.toml +++ b/crates/vite_command/Cargo.toml @@ -19,7 +19,7 @@ vite_shared = { workspace = true } which = { workspace = true, features = ["tracing"] } [target.'cfg(not(target_os = "windows"))'.dependencies] -nix = { workspace = true, features = ["term"] } +nix = { workspace = true, features = ["signal", "term"] } [dev-dependencies] tempfile = { workspace = true } diff --git a/crates/vite_command/src/lib.rs b/crates/vite_command/src/lib.rs index 61f6f041a4..c59cb3b114 100644 --- a/crates/vite_command/src/lib.rs +++ b/crates/vite_command/src/lib.rs @@ -98,7 +98,7 @@ pub async fn execute_with_terminal_guard(mut cmd: Command) -> Result, } #[cfg(unix)] @@ -326,22 +327,39 @@ impl TerminalStateGuard { } match tcgetattr(borrowed_fd) { - Ok(original) => Some(Self { fd, original }), + Ok(original) => Some(Self { fd, original, original_sigint: Self::ignore_sigint() }), Err(_) => None, } } + + fn ignore_sigint() -> Option { + use nix::sys::signal::{SaFlags, SigAction, SigHandler, SigSet, Signal, sigaction}; + + let ignore = SigAction::new(SigHandler::SigIgn, SaFlags::empty(), SigSet::empty()); + // SAFETY: installs a process signal disposition for SIGINT and returns + // the previous disposition, which this guard restores on drop. + unsafe { sigaction(Signal::SIGINT, &ignore).ok() } + } } #[cfg(unix)] impl Drop for TerminalStateGuard { fn drop(&mut self) { - use nix::sys::termios::{SetArg, tcsetattr}; + use nix::sys::{ + signal::{Signal, sigaction}, + termios::{SetArg, tcsetattr}, + }; // SAFETY: fd comes from stdin/stdout/stderr and the guard does not outlive the process let borrowed_fd = unsafe { BorrowedFd::borrow_raw(self.fd) }; // Best effort: ignore errors during cleanup let _ = tcsetattr(borrowed_fd, SetArg::TCSANOW, &self.original); + + // SAFETY: restores the signal disposition captured by save(). + if let Some(original_sigint) = self.original_sigint.take() { + let _ = unsafe { sigaction(Signal::SIGINT, &original_sigint) }; + } } } diff --git a/crates/vite_global_cli/src/js_executor.rs b/crates/vite_global_cli/src/js_executor.rs index fa5404d883..a4e6eee9d0 100644 --- a/crates/vite_global_cli/src/js_executor.rs +++ b/crates/vite_global_cli/src/js_executor.rs @@ -295,8 +295,7 @@ impl JsExecutor { let mut cmd = Self::create_js_command(&node_binary, &bin_prefix); cmd.arg(entry_point.as_path()).args(args).current_dir(project_path.as_path()); - let status = cmd.status().await?; - Ok(status) + Ok(vite_command::execute_with_terminal_guard(cmd).await?) } /// Delegate to local or global vite-plus CLI using the CLI's own runtime. @@ -352,9 +351,8 @@ impl JsExecutor { bin_prefix: &AbsolutePath, args: &[String], ) -> Result { - let mut cmd = self.prepare_js_entry(project_path, node_binary, bin_prefix, args)?; - let status = cmd.status().await?; - Ok(status) + let cmd = self.prepare_js_entry(project_path, node_binary, bin_prefix, args)?; + Ok(vite_command::execute_with_terminal_guard(cmd).await?) } /// Like [`run_js_entry`], but returns `Output`.