From 901771a637ed632cfda2f48fb0bc66b7538e5fbe Mon Sep 17 00:00:00 2001 From: MK Date: Wed, 13 May 2026 16:11:01 +0800 Subject: [PATCH 1/3] fix(pm): accept positional package names for vp rebuild MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit vp rebuild previously only accepted -- pass-through args, so running vp rebuild better-sqlite3 errored with "Unexpected argument". Combined with pnpm v10+ only rebuilding packages whose build scripts are approved, bare vp rebuild was a no-op for native modules like better-sqlite3 (71ms vs 11.7s for pnpm rebuild better-sqlite3) and the user had no discoverable way to name a package. Add a packages: Vec positional to the Rebuild clap variant, matching what the RFC (rfcs/pm-command-group.md §17, lines 660-680 and mapping table at 1283-1304) already specifies. Forward the names to pnpm/npm rebuild before the pass-through args. Yarn/Bun paths are unchanged (warn-and-skip). Snap test packages/cli/snap-tests-global/command-rebuild-pnpm11 captures both the help output and the previously-broken positional form. --- crates/vite_install/src/commands/rebuild.rs | 53 +++++++++++++++++-- crates/vite_pm_cli/src/cli.rs | 3 ++ crates/vite_pm_cli/src/handlers.rs | 7 ++- docs/guide/install.md | 4 ++ .../command-rebuild-pnpm11/package.json | 9 ++++ .../command-rebuild-pnpm11/snap.txt | 28 ++++++++++ .../command-rebuild-pnpm11/steps.json | 10 ++++ 7 files changed, 107 insertions(+), 7 deletions(-) create mode 100644 packages/cli/snap-tests-global/command-rebuild-pnpm11/package.json create mode 100644 packages/cli/snap-tests-global/command-rebuild-pnpm11/snap.txt create mode 100644 packages/cli/snap-tests-global/command-rebuild-pnpm11/steps.json diff --git a/crates/vite_install/src/commands/rebuild.rs b/crates/vite_install/src/commands/rebuild.rs index b85725ef3b..16d792b5ce 100644 --- a/crates/vite_install/src/commands/rebuild.rs +++ b/crates/vite_install/src/commands/rebuild.rs @@ -10,8 +10,9 @@ use crate::package_manager::{ }; /// Options for the rebuild command. -#[derive(Debug)] +#[derive(Debug, Default)] pub struct RebuildCommandOptions<'a> { + pub packages: &'a [String], pub pass_through_args: Option<&'a [String]>, } @@ -47,10 +48,12 @@ impl PackageManager { PackageManagerType::Npm => { bin_name = "npm".into(); args.push("rebuild".into()); + args.extend_from_slice(options.packages); } PackageManagerType::Pnpm => { bin_name = "pnpm".into(); args.push("rebuild".into()); + args.extend_from_slice(options.packages); } PackageManagerType::Yarn => { let is_yarn1 = self.version.starts_with("1."); @@ -110,7 +113,7 @@ mod tests { #[test] fn test_npm_rebuild() { let pm = create_mock_package_manager(PackageManagerType::Npm, "11.0.0"); - let result = pm.resolve_rebuild_command(&RebuildCommandOptions { pass_through_args: None }); + let result = pm.resolve_rebuild_command(&RebuildCommandOptions::default()); assert!(result.is_some()); let result = result.unwrap(); assert_eq!(result.bin_path, "npm"); @@ -120,7 +123,7 @@ mod tests { #[test] fn test_pnpm_rebuild() { let pm = create_mock_package_manager(PackageManagerType::Pnpm, "10.0.0"); - let result = pm.resolve_rebuild_command(&RebuildCommandOptions { pass_through_args: None }); + let result = pm.resolve_rebuild_command(&RebuildCommandOptions::default()); assert!(result.is_some()); let result = result.unwrap(); assert_eq!(result.bin_path, "pnpm"); @@ -130,14 +133,54 @@ mod tests { #[test] fn test_yarn1_rebuild_not_supported() { let pm = create_mock_package_manager(PackageManagerType::Yarn, "1.22.0"); - let result = pm.resolve_rebuild_command(&RebuildCommandOptions { pass_through_args: None }); + let result = pm.resolve_rebuild_command(&RebuildCommandOptions::default()); assert!(result.is_none()); } #[test] fn test_yarn2_rebuild_not_supported() { let pm = create_mock_package_manager(PackageManagerType::Yarn, "4.0.0"); - let result = pm.resolve_rebuild_command(&RebuildCommandOptions { pass_through_args: None }); + let result = pm.resolve_rebuild_command(&RebuildCommandOptions::default()); assert!(result.is_none()); } + + #[test] + fn test_npm_rebuild_with_packages() { + let pm = create_mock_package_manager(PackageManagerType::Npm, "11.0.0"); + let packages = vec!["better-sqlite3".to_string(), "sharp".to_string()]; + let result = pm.resolve_rebuild_command(&RebuildCommandOptions { + packages: &packages, + ..Default::default() + }); + let result = result.unwrap(); + assert_eq!(result.bin_path, "npm"); + assert_eq!(result.args, vec!["rebuild", "better-sqlite3", "sharp"]); + } + + #[test] + fn test_pnpm_rebuild_with_packages() { + let pm = create_mock_package_manager(PackageManagerType::Pnpm, "10.0.0"); + let packages = vec!["better-sqlite3".to_string()]; + let result = pm.resolve_rebuild_command(&RebuildCommandOptions { + packages: &packages, + ..Default::default() + }); + let result = result.unwrap(); + assert_eq!(result.bin_path, "pnpm"); + assert_eq!(result.args, vec!["rebuild", "better-sqlite3"]); + } + + #[test] + fn test_pnpm_rebuild_with_packages_and_pass_through() { + let pm = create_mock_package_manager(PackageManagerType::Pnpm, "11.0.6"); + let packages = vec!["better-sqlite3".to_string()]; + let pass_through = vec!["--recursive".to_string()]; + let result = pm.resolve_rebuild_command(&RebuildCommandOptions { + packages: &packages, + pass_through_args: Some(&pass_through), + }); + let result = result.unwrap(); + assert_eq!(result.bin_path, "pnpm"); + assert_eq!(result.args, vec!["rebuild", "better-sqlite3", "--recursive"]); + } } diff --git a/crates/vite_pm_cli/src/cli.rs b/crates/vite_pm_cli/src/cli.rs index f0a6bd6573..f2160b10a4 100644 --- a/crates/vite_pm_cli/src/cli.rs +++ b/crates/vite_pm_cli/src/cli.rs @@ -860,6 +860,9 @@ pub enum PmCommands { /// Rebuild native modules #[command(visible_alias = "rb")] Rebuild { + /// Packages to rebuild (rebuilds all if omitted; with pnpm v10+, bare rebuild only acts on packages whose build scripts are approved) + packages: Vec, + /// Additional arguments #[arg(last = true, allow_hyphen_values = true)] pass_through_args: Option>, diff --git a/crates/vite_pm_cli/src/handlers.rs b/crates/vite_pm_cli/src/handlers.rs index 72536460cf..0108e6aa51 100644 --- a/crates/vite_pm_cli/src/handlers.rs +++ b/crates/vite_pm_cli/src/handlers.rs @@ -452,8 +452,11 @@ pub async fn run_pm_subcommand( Ok(pm.run_search_command(&options, cwd).await?) } - PmCommands::Rebuild { pass_through_args } => { - let options = RebuildCommandOptions { pass_through_args: pass_through_args.as_deref() }; + PmCommands::Rebuild { packages, pass_through_args } => { + let options = RebuildCommandOptions { + packages: &packages, + pass_through_args: pass_through_args.as_deref(), + }; Ok(pm.run_rebuild_command(&options, cwd).await?) } diff --git a/docs/guide/install.md b/docs/guide/install.md index 87c782ca62..066f0a60a4 100644 --- a/docs/guide/install.md +++ b/docs/guide/install.md @@ -121,15 +121,19 @@ Use these when you need to understand the current state of dependencies. Use `vp rebuild` when native modules need to be recompiled, for example after switching Node.js versions or when a C/C++ addon fails to load. - `vp rebuild` rebuilds all native modules +- `vp rebuild ` rebuilds the listed packages only - `vp rebuild -- ` passes extra arguments to the underlying package manager ```bash vp rebuild +vp rebuild better-sqlite3 sharp vp rebuild -- --update-binary ``` `vp rebuild` is a shorthand for `vp pm rebuild`. +With pnpm v10+, bare `vp rebuild` only rebuilds packages whose build scripts are listed in `onlyBuiltDependencies` (or approved via `pnpm approve-builds`); name the package explicitly to force a rebuild that bypasses the approval gate. + #### Advanced Use these when you need lower-level package-manager behavior. diff --git a/packages/cli/snap-tests-global/command-rebuild-pnpm11/package.json b/packages/cli/snap-tests-global/command-rebuild-pnpm11/package.json new file mode 100644 index 0000000000..a22e22e3aa --- /dev/null +++ b/packages/cli/snap-tests-global/command-rebuild-pnpm11/package.json @@ -0,0 +1,9 @@ +{ + "name": "command-rebuild-pnpm11", + "version": "1.0.0", + "private": true, + "dependencies": { + "testnpm2": "1.0.0" + }, + "packageManager": "pnpm@11.0.6" +} diff --git a/packages/cli/snap-tests-global/command-rebuild-pnpm11/snap.txt b/packages/cli/snap-tests-global/command-rebuild-pnpm11/snap.txt new file mode 100644 index 0000000000..3d33e69432 --- /dev/null +++ b/packages/cli/snap-tests-global/command-rebuild-pnpm11/snap.txt @@ -0,0 +1,28 @@ +> vp rebuild --help # should show help with [PACKAGES]... positional +Usage: vp pm rebuild [PACKAGES]... [-- ...] + +Rebuild native modules + +Arguments: + [PACKAGES]... Packages to rebuild (rebuilds all if omitted; with pnpm v10+, bare rebuild only acts on packages whose build scripts are approved) + [PASS_THROUGH_ARGS]... Additional arguments + +Options: + -h, --help Print help + +Documentation: https://viteplus.dev/guide/install + + +> vp install # set up node_modules +Packages: + ++ +Progress: resolved , reused , downloaded , added , done + +dependencies: ++ testnpm2 (1.0.1 is available) + +Done in ms using pnpm v + +> vp rebuild # bare rebuild, no args +> vp rebuild testnpm2 # should accept positional package name +> vp rebuild testnpm2 -- --recursive # package name + pass-through args \ No newline at end of file diff --git a/packages/cli/snap-tests-global/command-rebuild-pnpm11/steps.json b/packages/cli/snap-tests-global/command-rebuild-pnpm11/steps.json new file mode 100644 index 0000000000..b4040831b7 --- /dev/null +++ b/packages/cli/snap-tests-global/command-rebuild-pnpm11/steps.json @@ -0,0 +1,10 @@ +{ + "ignoredPlatforms": ["win32"], + "commands": [ + "vp rebuild --help # should show help with [PACKAGES]... positional", + "vp install # set up node_modules", + "vp rebuild # bare rebuild, no args", + "vp rebuild testnpm2 # should accept positional package name", + "vp rebuild testnpm2 -- --recursive # package name + pass-through args" + ] +} From a92cba89dadd9c876205375c562ee7ffe1c7bf70 Mon Sep 17 00:00:00 2001 From: MK Date: Wed, 13 May 2026 16:16:17 +0800 Subject: [PATCH 2/3] refactor(pm): align vp rebuild arg order with sibling commands MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Move packages slice to append after pass_through_args in resolve_rebuild_command, matching update.rs:194-197, remove.rs:147-150, and add.rs:242-245. Resulting command becomes pnpm rebuild --recursive better-sqlite3 instead of pnpm rebuild better-sqlite3 --recursive — same semantics for pnpm/npm, but consistent with the rest of the codebase. Also trim the packages doc comment in cli.rs; the pnpm v10+ approved-builds caveat already lives in docs/guide/install.md and shouldn't bloat --help. --- crates/vite_install/src/commands/rebuild.rs | 6 ++---- crates/vite_pm_cli/src/cli.rs | 2 +- .../cli/snap-tests-global/command-rebuild-pnpm11/snap.txt | 2 +- 3 files changed, 4 insertions(+), 6 deletions(-) diff --git a/crates/vite_install/src/commands/rebuild.rs b/crates/vite_install/src/commands/rebuild.rs index 16d792b5ce..7cdd3e91d9 100644 --- a/crates/vite_install/src/commands/rebuild.rs +++ b/crates/vite_install/src/commands/rebuild.rs @@ -48,12 +48,10 @@ impl PackageManager { PackageManagerType::Npm => { bin_name = "npm".into(); args.push("rebuild".into()); - args.extend_from_slice(options.packages); } PackageManagerType::Pnpm => { bin_name = "pnpm".into(); args.push("rebuild".into()); - args.extend_from_slice(options.packages); } PackageManagerType::Yarn => { let is_yarn1 = self.version.starts_with("1."); @@ -72,10 +70,10 @@ impl PackageManager { } } - // Add pass-through args if let Some(pass_through_args) = options.pass_through_args { args.extend_from_slice(pass_through_args); } + args.extend_from_slice(options.packages); Some(ResolveCommandResult { bin_path: bin_name, args, envs }) } @@ -181,6 +179,6 @@ mod tests { }); let result = result.unwrap(); assert_eq!(result.bin_path, "pnpm"); - assert_eq!(result.args, vec!["rebuild", "better-sqlite3", "--recursive"]); + assert_eq!(result.args, vec!["rebuild", "--recursive", "better-sqlite3"]); } } diff --git a/crates/vite_pm_cli/src/cli.rs b/crates/vite_pm_cli/src/cli.rs index f2160b10a4..80ee9a8402 100644 --- a/crates/vite_pm_cli/src/cli.rs +++ b/crates/vite_pm_cli/src/cli.rs @@ -860,7 +860,7 @@ pub enum PmCommands { /// Rebuild native modules #[command(visible_alias = "rb")] Rebuild { - /// Packages to rebuild (rebuilds all if omitted; with pnpm v10+, bare rebuild only acts on packages whose build scripts are approved) + /// Packages to rebuild (rebuilds all if omitted) packages: Vec, /// Additional arguments diff --git a/packages/cli/snap-tests-global/command-rebuild-pnpm11/snap.txt b/packages/cli/snap-tests-global/command-rebuild-pnpm11/snap.txt index 3d33e69432..68ba728d8c 100644 --- a/packages/cli/snap-tests-global/command-rebuild-pnpm11/snap.txt +++ b/packages/cli/snap-tests-global/command-rebuild-pnpm11/snap.txt @@ -4,7 +4,7 @@ Usage: vp pm rebuild [PACKAGES]... [-- ...] Rebuild native modules Arguments: - [PACKAGES]... Packages to rebuild (rebuilds all if omitted; with pnpm v10+, bare rebuild only acts on packages whose build scripts are approved) + [PACKAGES]... Packages to rebuild (rebuilds all if omitted) [PASS_THROUGH_ARGS]... Additional arguments Options: From a596c4893056d710532c3c185b4ee64e0188ee94 Mon Sep 17 00:00:00 2001 From: MK Date: Wed, 13 May 2026 16:27:42 +0800 Subject: [PATCH 3/3] chore(command): trace spawned process args under VITE_LOG MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add a tracing::debug! span at the spawn site of both run_command and run_command_with_fspy logging the resolved program path, args, and cwd. Gated on the existing VITE_LOG env var, so it's free at runtime when unset. Useful for diagnosing how vite-plus pm subcommands forward arguments to the underlying package manager — e.g. confirming that VITE_LOG=vite_command=debug vp rebuild better-sqlite3 actually spawns pnpm with ["rebuild", "better-sqlite3"]. --- crates/vite_command/src/lib.rs | 21 +++++++++++++++++++-- 1 file changed, 19 insertions(+), 2 deletions(-) diff --git a/crates/vite_command/src/lib.rs b/crates/vite_command/src/lib.rs index 64da37182f..855b46fa84 100644 --- a/crates/vite_command/src/lib.rs +++ b/crates/vite_command/src/lib.rs @@ -159,8 +159,17 @@ where { let cwd = cwd.as_ref(); let (program, prefix_args) = resolve_program(bin_name, envs, cwd)?; + let args: Vec = args.into_iter().map(|s| s.as_ref().to_owned()).collect(); + tracing::debug!( + target: "vite_command::spawn", + program = %program.as_path().display(), + prefix_args = ?prefix_args, + args = ?args, + cwd = %cwd.as_path().display(), + "spawn", + ); let mut cmd = build_command(&program, cwd); - cmd.args(&prefix_args).args(args).envs(envs); + cmd.args(&prefix_args).args(&args).envs(envs); let status = cmd.status().await?; Ok(status) } @@ -188,8 +197,16 @@ where S: AsRef, { let cwd = cwd.as_ref(); + let args: Vec = args.into_iter().map(|s| s.as_ref().to_owned()).collect(); + tracing::debug!( + target: "vite_command::spawn", + bin_name, + args = ?args, + cwd = %cwd.as_path().display(), + "spawn (fspy)", + ); let mut cmd = fspy::Command::new(bin_name); - cmd.args(args) + cmd.args(&args) // set system environment variables first .envs(std::env::vars_os()) // then set custom environment variables