Skip to content
Open
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
1 change: 1 addition & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions crates/vite_cli_snapshots/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ cp_r = { workspace = true }
ctrlc = { workspace = true }
pty_terminal_test_client = { workspace = true, features = ["testing"] }
serde_json = { workspace = true }
vite_shared = { workspace = true }

[dev-dependencies]
cow-utils = { workspace = true }
Expand Down
2 changes: 1 addition & 1 deletion crates/vite_cli_snapshots/src/bin/vpt/pipe_stdin.rs
Original file line number Diff line number Diff line change
Expand Up @@ -31,5 +31,5 @@ pub fn run(args: &[String]) -> Result<(), Box<dyn std::error::Error>> {
}

let status = child.wait()?;
std::process::exit(status.code().unwrap_or(1));
std::process::exit(vite_shared::exit_code_from_status(status));
}
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
const { spawn } = require('child_process');
const { constants } = require('os');

const child = spawn('vp', ['install', '-g', './long-time-install-package'], {
stdio: 'inherit',
Expand All @@ -10,6 +11,7 @@ setTimeout(() => {
}
}, 100);

child.on('close', (code) => {
process.exit(code);
child.on('close', (code, signal) => {
const signalNumber = signal && constants.signals[signal];
process.exit(code ?? (signalNumber ? 128 + signalNumber : 1));
});
5 changes: 4 additions & 1 deletion crates/vite_cli_snapshots/tests/cli_snapshots/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -768,7 +768,10 @@ fn run_step_piped(
let mut output = stdout_thread.join().unwrap();
output.push_str(&stderr_thread.join().unwrap());
match status {
Some(status) => (TerminationState::Exited(i64::from(status.code().unwrap_or(-1))), output),
Some(status) => (
TerminationState::Exited(i64::from(vite_shared::exit_code_from_status(status))),
output,
),
None => (TerminationState::TimedOut, output),
}
}
Expand Down
4 changes: 2 additions & 2 deletions crates/vite_global_cli/src/commands/vpr.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
//! vite-plus CLI to execute tasks.

use vite_path::AbsolutePath;
use vite_shared::output;
use vite_shared::{exit_code_from_status, output};

/// Main entry point for vpr execution.
///
Expand All @@ -16,7 +16,7 @@ pub async fn execute_vpr(args: &[String], cwd: &AbsolutePath) -> i32 {

let cwd_buf = cwd.to_absolute_path_buf();
match super::delegate::execute(cwd_buf, "run", args).await {
Ok(status) => status.code().unwrap_or(1),
Ok(status) => exit_code_from_status(status),
Err(e) => {
output::error(&e.to_string());
1
Expand Down
2 changes: 1 addition & 1 deletion crates/vite_global_cli/src/commands/vpx.rs
Original file line number Diff line number Diff line change
Expand Up @@ -120,7 +120,7 @@ pub async fn execute_vpx(args: &[String], cwd: &AbsolutePath) -> i32 {
args: positional,
};
match vite_pm_cli::dispatch(cwd, dlx).await {
Ok(status) => status.code().unwrap_or(1),
Ok(status) => vite_shared::exit_code_from_status(status),
Err(e) => {
output::error(&format!("vpx: {e}"));
1
Expand Down
4 changes: 2 additions & 2 deletions crates/vite_global_cli/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ use std::{
use clap::error::{ContextKind, ContextValue};
use clap_complete::env::CompleteEnv;
use owo_colors::OwoColorize;
use vite_shared::output;
use vite_shared::{exit_code_from_status, output};

pub use crate::cli::try_parse_args_from;
use crate::cli::{
Expand Down Expand Up @@ -213,7 +213,7 @@ fn exit_status_to_exit_code(exit_status: ExitStatus) -> ExitCode {
ExitCode::SUCCESS
} else {
#[allow(clippy::cast_sign_loss, clippy::cast_possible_truncation)]
exit_status.code().map_or(ExitCode::FAILURE, |c| ExitCode::from(c as u8))
ExitCode::from(exit_code_from_status(exit_status) as u8)
}
}

Expand Down
15 changes: 1 addition & 14 deletions crates/vite_global_cli/src/shim/exec.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,20 +4,7 @@
//! On Windows, spawns the process and waits for completion.

use vite_path::AbsolutePath;
use vite_shared::output;

/// Convert a process ExitStatus to an exit code.
/// On Unix, if the process was killed by a signal, returns 128 + signal_number.
fn exit_code_from_status(status: std::process::ExitStatus) -> i32 {
#[cfg(unix)]
{
use std::os::unix::process::ExitStatusExt;
if let Some(signal) = status.signal() {
return 128 + signal;
}
}
status.code().unwrap_or(1)
}
use vite_shared::{exit_code_from_status, output};

/// Spawn a tool as a child process and wait for completion.
///
Expand Down
10 changes: 5 additions & 5 deletions crates/vite_setup/src/install.rs
Original file line number Diff line number Diff line change
Expand Up @@ -293,7 +293,7 @@ pub async fn install_production_deps(
if !release_age_blocked {
return Err(Error::Setup(
format_install_failure_message(
output.status.code().unwrap_or(-1),
vite_shared::exit_code_from_status(output.status),
log_path.as_ref(),
false,
)
Expand All @@ -305,7 +305,7 @@ pub async fn install_production_deps(
{
return Err(Error::Setup(
format_install_failure_message(
output.status.code().unwrap_or(-1),
vite_shared::exit_code_from_status(output.status),
log_path.as_ref(),
true,
)
Expand All @@ -323,7 +323,7 @@ pub async fn install_production_deps(
write_upgrade_log(version_dir, &retry_output.stdout, &retry_output.stderr).await;
return Err(Error::Setup(
format_install_failure_message(
retry_output.status.code().unwrap_or(-1),
vite_shared::exit_code_from_status(retry_output.status),
retry_log_path.as_ref(),
false,
)
Expand Down Expand Up @@ -469,7 +469,7 @@ pub async fn refresh_shims(install_dir: &AbsolutePath) -> Result<(), Error> {
let stderr = String::from_utf8_lossy(&output.stderr);
tracing::warn!(
"Shim refresh exited with code {}, continuing anyway\n{}",
output.status.code().unwrap_or(-1),
vite_shared::exit_code_from_status(output.status),
stderr.trim()
);
}
Expand Down Expand Up @@ -612,7 +612,7 @@ pub async fn create_env_files(install_dir: &AbsolutePath) -> Result<(), Error> {
let stderr = String::from_utf8_lossy(&output.stderr);
tracing::warn!(
"env setup --env-only exited with code {}, continuing anyway\n{}",
output.status.code().unwrap_or(-1),
vite_shared::exit_code_from_status(output.status),
stderr.trim()
);
}
Expand Down
2 changes: 2 additions & 0 deletions crates/vite_shared/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ mod json_edit;
pub mod output;
mod package_json;
mod path_env;
mod process;
pub mod string_similarity;
mod tls;
mod tracing;
Expand All @@ -33,5 +34,6 @@ pub use path_env::{
PrependOptions, PrependResult, format_path_prepended, format_path_with_prepend,
prepend_to_path_env,
};
pub use process::exit_code_from_status;
pub use tls::ensure_tls_provider;
pub use tracing::init_tracing;
27 changes: 27 additions & 0 deletions crates/vite_shared/src/process.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
use std::process::ExitStatus;

/// Convert a process status to the shell-compatible exit code used by CLI callers.
pub fn exit_code_from_status(status: ExitStatus) -> i32 {
#[cfg(unix)]
{
use std::os::unix::process::ExitStatusExt;
if let Some(signal) = status.signal() {
return 128 + signal;
}
}
status.code().unwrap_or(1)
}

#[cfg(test)]
#[cfg(unix)]
mod tests {
use super::exit_code_from_status;

/// Regression test for https://github.com/voidzero-dev/vite-plus/issues/2041.
#[test]
fn preserves_signal_exit_code() {
let status =
std::process::Command::new("/bin/sh").arg("-c").arg("kill -ILL $$").status().unwrap();
assert_eq!(exit_code_from_status(status), 132);
}
}
27 changes: 25 additions & 2 deletions crates/vite_trampoline/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,21 @@

use std::{
env,
process::{self, Command},
process::{self, Command, ExitStatus},
};

/// Preserve Unix signal termination using the shell's `128 + signal` convention.
fn exit_code_from_status(status: ExitStatus) -> i32 {
#[cfg(unix)]
{
use std::os::unix::process::ExitStatusExt;
if let Some(signal) = status.signal() {
return 128 + signal;
}
}
status.code().unwrap_or(1)
}

fn main() {
// 1. Determine tool name from our own executable filename
let exe_path = env::current_exe().unwrap_or_else(|_| process::exit(1));
Expand Down Expand Up @@ -58,7 +70,7 @@ fn main() {
// 5. Execute and propagate exit code.
// Use write_all instead of eprintln!/format! to avoid pulling in core::fmt (~100KB).
match cmd.status() {
Ok(s) => process::exit(s.code().unwrap_or(1)),
Ok(status) => process::exit(exit_code_from_status(status)),
Err(_) => {
use std::io::Write;
let stderr = std::io::stderr();
Expand All @@ -71,6 +83,17 @@ fn main() {
}
}

#[cfg(all(test, unix))]
mod tests {
use super::*;

#[test]
fn preserves_signal_exit_code() {
let status = Command::new("/bin/sh").arg("-c").arg("kill -ILL $$").status().unwrap();
assert_eq!(exit_code_from_status(status), 132);
}
}

/// Install a console control handler that ignores Ctrl+C, Ctrl+Break, etc.
///
/// When Ctrl+C is pressed, Windows sends the event to all processes in the
Expand Down
8 changes: 4 additions & 4 deletions packages/cli/binding/src/cli/execution.rs
Original file line number Diff line number Diff line change
Expand Up @@ -66,11 +66,11 @@ pub(super) async fn resolve_and_execute(
// For interactive commands (dev, preview), use terminal guard to restore terminal state on exit
if is_interactive {
let status = vite_command::execute_with_terminal_guard(cmd).await?;
Ok(ExitStatus(status.code().unwrap_or(1) as u8))
Ok(ExitStatus(vite_shared::exit_code_from_status(status) as u8))
} else {
let mut child = cmd.spawn().map_err(|e| Error::Anyhow(e.into()))?;
let status = child.wait().await.map_err(|e| Error::Anyhow(e.into()))?;
Ok(ExitStatus(status.code().unwrap_or(1) as u8))
Ok(ExitStatus(vite_shared::exit_code_from_status(status) as u8))
}
}

Expand Down Expand Up @@ -112,7 +112,7 @@ pub(super) async fn resolve_and_execute_with_filter(
}
}

Ok(ExitStatus(output.status.code().unwrap_or(1) as u8))
Ok(ExitStatus(vite_shared::exit_code_from_status(output.status) as u8))
}

pub(crate) async fn resolve_and_capture_output(
Expand All @@ -135,7 +135,7 @@ pub(crate) async fn resolve_and_capture_output(
let output = child.wait_with_output().await.map_err(|e| Error::Anyhow(e.into()))?;

Ok(CapturedCommandOutput {
status: ExitStatus(output.status.code().unwrap_or(1) as u8),
status: ExitStatus(vite_shared::exit_code_from_status(output.status) as u8),
stdout: String::from_utf8_lossy(&output.stdout).into_owned(),
stderr: String::from_utf8_lossy(&output.stderr).into_owned(),
})
Expand Down
2 changes: 1 addition & 1 deletion packages/cli/binding/src/cli/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -310,7 +310,7 @@ async fn execute_pm_command(
}
Err(e) => return Err(Error::Anyhow(anyhow::Error::new(e))),
};
Ok(ExitStatus(status.code().unwrap_or(1) as u8))
Ok(ExitStatus(vite_shared::exit_code_from_status(status) as u8))
}

#[cfg(test)]
Expand Down
4 changes: 2 additions & 2 deletions packages/cli/binding/src/exec/workspace.rs
Original file line number Diff line number Diff line change
Expand Up @@ -187,7 +187,7 @@ pub(super) async fn execute_exec_workspace(
use std::io::Write;
let _ = std::io::stdout().write_all(&output.stdout);
let _ = std::io::stderr().write_all(&output.stderr);
let code = output.status.code().unwrap_or(1) as u8;
let code = vite_shared::exit_code_from_status(output.status) as u8;
if code > worst_exit {
worst_exit = code;
}
Expand Down Expand Up @@ -250,7 +250,7 @@ pub(super) async fn execute_exec_workspace(
let mut child = cmd.spawn().map_err(|e| Error::Anyhow(e.into()))?;
let status = child.wait().await.map_err(|e| Error::Anyhow(e.into()))?;
let duration = start.elapsed();
let code = status.code().unwrap_or(1) as u8;
let code = vite_shared::exit_code_from_status(status) as u8;

if args.report_summary {
let pkg_status = if code == 0 { "passed" } else { "failed" };
Expand Down
3 changes: 1 addition & 2 deletions packages/cli/binding/src/utils.rs
Original file line number Diff line number Diff line change
Expand Up @@ -113,8 +113,7 @@ pub async fn run_command(options: RunCommandOptions) -> Result<RunCommandResult>
);
}

// Get the exit code
let exit_code = result.status.code().unwrap_or(1);
let exit_code = vite_shared::exit_code_from_status(result.status);

Ok(RunCommandResult { exit_code, path_accesses })
}
30 changes: 28 additions & 2 deletions packages/cli/src/utils/__tests__/command.spec.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import { describe, expect, it } from 'vitest';

import { runCommandSilently } from '../command.ts';
import { runCommand, runCommandSilently } from '../command.ts';

describe('runCommandSilently', () => {
describe('command runners', () => {
it('resolves with the child output when no timeout is set', async () => {
const result = await runCommandSilently({
command: process.execPath,
Expand Down Expand Up @@ -37,4 +37,30 @@ describe('runCommandSilently', () => {
});
expect(result.exitCode).toBe(3);
});

it.skipIf(process.platform === 'win32')(
'preserves the signal exit code for captured commands',
async () => {
const result = await runCommandSilently({
command: process.execPath,
args: ['-e', 'process.kill(process.pid, "SIGILL")'],
cwd: process.cwd(),
envs: process.env,
});
expect(result.exitCode).toBe(132);
},
);

it.skipIf(process.platform === 'win32')(
'preserves the signal exit code for inherited commands',
async () => {
const result = await runCommand({
command: process.execPath,
args: ['-e', 'process.kill(process.pid, "SIGILL")'],
cwd: process.cwd(),
envs: process.env,
});
expect(result.exitCode).toBe(132);
},
);
});
Loading
Loading