Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion crates/vite_command/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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 }
Expand Down
24 changes: 21 additions & 3 deletions crates/vite_command/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -98,7 +98,7 @@ pub async fn execute_with_terminal_guard(mut cmd: Command) -> Result<ExitStatus,
{
use nix::libc::STDIN_FILENO;

// Save terminal state before spawning child
// Save terminal state and ignore sigint signal for current process before spawning child
let _guard = TerminalStateGuard::save(STDIN_FILENO);

// Spawn and wait for child - guard will restore terminal state on drop
Expand Down Expand Up @@ -308,6 +308,7 @@ pub fn fix_stdio_streams() {
struct TerminalStateGuard {
fd: RawFd,
original: nix::sys::termios::Termios,
original_sigint: Option<nix::sys::signal::SigAction>,
}

#[cfg(unix)]
Expand All @@ -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<nix::sys::signal::SigAction> {
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) };
}
}
}

Expand Down
8 changes: 3 additions & 5 deletions crates/vite_global_cli/src/js_executor.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -352,9 +351,8 @@ impl JsExecutor {
bin_prefix: &AbsolutePath,
args: &[String],
) -> Result<ExitStatus, Error> {
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`.
Expand Down
Loading