diff --git a/crates/vite_cli_snapshots/tests/cli_snapshots/fixtures/vite_task_bunx_wrapper/package.json b/crates/vite_cli_snapshots/tests/cli_snapshots/fixtures/vite_task_bunx_wrapper/package.json new file mode 100644 index 0000000000..cee3a4f1ee --- /dev/null +++ b/crates/vite_cli_snapshots/tests/cli_snapshots/fixtures/vite_task_bunx_wrapper/package.json @@ -0,0 +1,19 @@ +{ + "name": "vite-task-bunx-wrapper", + "version": "1.0.0", + "private": true, + "scripts": { + "gate:test": "bun probe", + "probe": "bunx --bun probe" + }, + "dependencies": { + "probe-bin": "file:./probe-bin" + }, + "devEngines": { + "packageManager": { + "name": "bun", + "version": "1.3.14", + "onFail": "download" + } + } +} diff --git a/crates/vite_cli_snapshots/tests/cli_snapshots/fixtures/vite_task_bunx_wrapper/probe-bin/bin.js b/crates/vite_cli_snapshots/tests/cli_snapshots/fixtures/vite_task_bunx_wrapper/probe-bin/bin.js new file mode 100755 index 0000000000..a96152fb6a --- /dev/null +++ b/crates/vite_cli_snapshots/tests/cli_snapshots/fixtures/vite_task_bunx_wrapper/probe-bin/bin.js @@ -0,0 +1,3 @@ +#!/usr/bin/env node + +console.log('probe binary ran'); diff --git a/crates/vite_cli_snapshots/tests/cli_snapshots/fixtures/vite_task_bunx_wrapper/probe-bin/package.json b/crates/vite_cli_snapshots/tests/cli_snapshots/fixtures/vite_task_bunx_wrapper/probe-bin/package.json new file mode 100644 index 0000000000..f567c86ea7 --- /dev/null +++ b/crates/vite_cli_snapshots/tests/cli_snapshots/fixtures/vite_task_bunx_wrapper/probe-bin/package.json @@ -0,0 +1,7 @@ +{ + "name": "probe-bin", + "version": "1.0.0", + "bin": { + "probe": "bin.js" + } +} diff --git a/crates/vite_cli_snapshots/tests/cli_snapshots/fixtures/vite_task_bunx_wrapper/snapshots.toml b/crates/vite_cli_snapshots/tests/cli_snapshots/fixtures/vite_task_bunx_wrapper/snapshots.toml new file mode 100644 index 0000000000..24c39e911b --- /dev/null +++ b/crates/vite_cli_snapshots/tests/cli_snapshots/fixtures/vite_task_bunx_wrapper/snapshots.toml @@ -0,0 +1,7 @@ +[[case]] +name = "vite_task_bunx_wrapper" +vp = "local" +steps = [ + { argv = ["vp", "install"], snapshot = false }, + { argv = ["vp", "run", "gate:test"], comment = "managed bunx should execute the package binary without recursively invoking the matching package script" }, +] diff --git a/crates/vite_cli_snapshots/tests/cli_snapshots/fixtures/vite_task_bunx_wrapper/snapshots/vite_task_bunx_wrapper.md b/crates/vite_cli_snapshots/tests/cli_snapshots/fixtures/vite_task_bunx_wrapper/snapshots/vite_task_bunx_wrapper.md new file mode 100644 index 0000000000..dc13fa811e --- /dev/null +++ b/crates/vite_cli_snapshots/tests/cli_snapshots/fixtures/vite_task_bunx_wrapper/snapshots/vite_task_bunx_wrapper.md @@ -0,0 +1,14 @@ +# vite_task_bunx_wrapper + +## `vp install` + + +## `vp run gate:test` + +managed bunx should execute the package binary without recursively invoking the matching package script + +``` +$ bun probe ⊘ cache disabled +$ bunx --bun probe +probe binary ran +``` diff --git a/crates/vite_pm_cli/src/package_manager.rs b/crates/vite_pm_cli/src/package_manager.rs index 6398e70de6..e87bbe6d3c 100644 --- a/crates/vite_pm_cli/src/package_manager.rs +++ b/crates/vite_pm_cli/src/package_manager.rs @@ -1384,9 +1384,9 @@ async fn create_bun_shim_files(bin_prefix: &AbsolutePath) -> Result<(), Error> { let bun_shim = bin_prefix.join("bun"); shim::write_native_shims(&native_bin, &bun_shim).await?; - // Create bunx shim -> bun.native (bunx is just bun with different argv[0]) + // Native wrappers cannot preserve bunx as argv[0], so select its equivalent x subcommand. let bunx_shim = bin_prefix.join("bunx"); - shim::write_native_shims(&native_bin, &bunx_shim).await?; + shim::write_native_shims_with_args(&native_bin, &bunx_shim, &["x"]).await?; Ok(()) } diff --git a/crates/vite_pm_cli/src/shim.rs b/crates/vite_pm_cli/src/shim.rs index 903a64bf5f..901c00728d 100644 --- a/crates/vite_pm_cli/src/shim.rs +++ b/crates/vite_pm_cli/src/shim.rs @@ -217,6 +217,8 @@ pub(crate) fn pwsh_shim(relative_file: &str) -> String { #[cfg(test)] #[cfg(not(windows))] // FIXME mod tests { + use std::{os::unix::fs::PermissionsExt, process::Command}; + use tempfile::TempDir; use tokio::fs::read_to_string; @@ -226,6 +228,29 @@ mod tests { shim.replace(' ', "·") } + #[tokio::test] + async fn test_native_shim_forwards_subcommand() { + let temp_dir = TempDir::new().unwrap(); + let source = temp_dir.path().join("bin").join("bun.native"); + let target = temp_dir.path().join("bin").join("bunx"); + + tokio::fs::create_dir_all(source.parent().unwrap()).await.unwrap(); + tokio::fs::write(&source, "#!/bin/sh\nprintf '%s\\n' \"$@\"\n").await.unwrap(); + tokio::fs::set_permissions(&source, std::fs::Permissions::from_mode(0o755)).await.unwrap(); + + write_native_shims_with_args(&source, &target, &["x"]).await.unwrap(); + + let output = Command::new(&target).args(["--bun", "vitest"]).output().unwrap(); + assert!(output.status.success()); + assert_eq!(String::from_utf8(output.stdout).unwrap(), "x\n--bun\nvitest\n"); + + let cmd = read_to_string(target.with_extension("cmd")).await.unwrap(); + assert!(cmd.contains("@\"%~dp0\\bun.native\" x %*")); + + let pwsh = read_to_string(target.with_extension("ps1")).await.unwrap(); + assert!(pwsh.contains("& \"$basedir/bun.native\" x $args")); + } + #[test] fn test_native_shims_without_args() { let sh = native_sh_shim("bun.native", &[]);