Skip to content

Commit 1aad80d

Browse files
authored
fix(pm): accept positional package names for vp rebuild (#1564)
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<String> 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. <!-- CURSOR_SUMMARY --> --- > [!NOTE] > **Low Risk** > Low risk: limited to `rebuild` CLI parsing/argument forwarding plus tests/docs; no security- or data-sensitive behavior changes. > > **Overview** > `vp rebuild` now accepts positional package names (e.g. `vp rebuild better-sqlite3 sharp`) and forwards them to the underlying npm/pnpm `rebuild` invocation, fixing the previous “Unexpected argument” behavior. > > This threads a new `packages` field through `RebuildCommandOptions`, updates the CLI/handler wiring, and extends unit + snapshot tests and docs to cover the new usage (Yarn/Bun remain warn-and-skip). > > <sup>Reviewed by [Cursor Bugbot](https://cursor.com/bugbot) for commit a92cba8. Configure [here](https://www.cursor.com/dashboard/bugbot).</sup> <!-- /CURSOR_SUMMARY -->
1 parent c61621a commit 1aad80d

8 files changed

Lines changed: 125 additions & 10 deletions

File tree

crates/vite_command/src/lib.rs

Lines changed: 19 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -159,8 +159,17 @@ where
159159
{
160160
let cwd = cwd.as_ref();
161161
let (program, prefix_args) = resolve_program(bin_name, envs, cwd)?;
162+
let args: Vec<OsString> = args.into_iter().map(|s| s.as_ref().to_owned()).collect();
163+
tracing::debug!(
164+
target: "vite_command::spawn",
165+
program = %program.as_path().display(),
166+
prefix_args = ?prefix_args,
167+
args = ?args,
168+
cwd = %cwd.as_path().display(),
169+
"spawn",
170+
);
162171
let mut cmd = build_command(&program, cwd);
163-
cmd.args(&prefix_args).args(args).envs(envs);
172+
cmd.args(&prefix_args).args(&args).envs(envs);
164173
let status = cmd.status().await?;
165174
Ok(status)
166175
}
@@ -188,8 +197,16 @@ where
188197
S: AsRef<OsStr>,
189198
{
190199
let cwd = cwd.as_ref();
200+
let args: Vec<OsString> = args.into_iter().map(|s| s.as_ref().to_owned()).collect();
201+
tracing::debug!(
202+
target: "vite_command::spawn",
203+
bin_name,
204+
args = ?args,
205+
cwd = %cwd.as_path().display(),
206+
"spawn (fspy)",
207+
);
191208
let mut cmd = fspy::Command::new(bin_name);
192-
cmd.args(args)
209+
cmd.args(&args)
193210
// set system environment variables first
194211
.envs(std::env::vars_os())
195212
// then set custom environment variables

crates/vite_install/src/commands/rebuild.rs

Lines changed: 47 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,9 @@ use crate::package_manager::{
1010
};
1111

1212
/// Options for the rebuild command.
13-
#[derive(Debug)]
13+
#[derive(Debug, Default)]
1414
pub struct RebuildCommandOptions<'a> {
15+
pub packages: &'a [String],
1516
pub pass_through_args: Option<&'a [String]>,
1617
}
1718

@@ -69,10 +70,10 @@ impl PackageManager {
6970
}
7071
}
7172

72-
// Add pass-through args
7373
if let Some(pass_through_args) = options.pass_through_args {
7474
args.extend_from_slice(pass_through_args);
7575
}
76+
args.extend_from_slice(options.packages);
7677

7778
Some(ResolveCommandResult { bin_path: bin_name, args, envs })
7879
}
@@ -110,7 +111,7 @@ mod tests {
110111
#[test]
111112
fn test_npm_rebuild() {
112113
let pm = create_mock_package_manager(PackageManagerType::Npm, "11.0.0");
113-
let result = pm.resolve_rebuild_command(&RebuildCommandOptions { pass_through_args: None });
114+
let result = pm.resolve_rebuild_command(&RebuildCommandOptions::default());
114115
assert!(result.is_some());
115116
let result = result.unwrap();
116117
assert_eq!(result.bin_path, "npm");
@@ -120,7 +121,7 @@ mod tests {
120121
#[test]
121122
fn test_pnpm_rebuild() {
122123
let pm = create_mock_package_manager(PackageManagerType::Pnpm, "10.0.0");
123-
let result = pm.resolve_rebuild_command(&RebuildCommandOptions { pass_through_args: None });
124+
let result = pm.resolve_rebuild_command(&RebuildCommandOptions::default());
124125
assert!(result.is_some());
125126
let result = result.unwrap();
126127
assert_eq!(result.bin_path, "pnpm");
@@ -130,14 +131,54 @@ mod tests {
130131
#[test]
131132
fn test_yarn1_rebuild_not_supported() {
132133
let pm = create_mock_package_manager(PackageManagerType::Yarn, "1.22.0");
133-
let result = pm.resolve_rebuild_command(&RebuildCommandOptions { pass_through_args: None });
134+
let result = pm.resolve_rebuild_command(&RebuildCommandOptions::default());
134135
assert!(result.is_none());
135136
}
136137

137138
#[test]
138139
fn test_yarn2_rebuild_not_supported() {
139140
let pm = create_mock_package_manager(PackageManagerType::Yarn, "4.0.0");
140-
let result = pm.resolve_rebuild_command(&RebuildCommandOptions { pass_through_args: None });
141+
let result = pm.resolve_rebuild_command(&RebuildCommandOptions::default());
141142
assert!(result.is_none());
142143
}
144+
145+
#[test]
146+
fn test_npm_rebuild_with_packages() {
147+
let pm = create_mock_package_manager(PackageManagerType::Npm, "11.0.0");
148+
let packages = vec!["better-sqlite3".to_string(), "sharp".to_string()];
149+
let result = pm.resolve_rebuild_command(&RebuildCommandOptions {
150+
packages: &packages,
151+
..Default::default()
152+
});
153+
let result = result.unwrap();
154+
assert_eq!(result.bin_path, "npm");
155+
assert_eq!(result.args, vec!["rebuild", "better-sqlite3", "sharp"]);
156+
}
157+
158+
#[test]
159+
fn test_pnpm_rebuild_with_packages() {
160+
let pm = create_mock_package_manager(PackageManagerType::Pnpm, "10.0.0");
161+
let packages = vec!["better-sqlite3".to_string()];
162+
let result = pm.resolve_rebuild_command(&RebuildCommandOptions {
163+
packages: &packages,
164+
..Default::default()
165+
});
166+
let result = result.unwrap();
167+
assert_eq!(result.bin_path, "pnpm");
168+
assert_eq!(result.args, vec!["rebuild", "better-sqlite3"]);
169+
}
170+
171+
#[test]
172+
fn test_pnpm_rebuild_with_packages_and_pass_through() {
173+
let pm = create_mock_package_manager(PackageManagerType::Pnpm, "11.0.6");
174+
let packages = vec!["better-sqlite3".to_string()];
175+
let pass_through = vec!["--recursive".to_string()];
176+
let result = pm.resolve_rebuild_command(&RebuildCommandOptions {
177+
packages: &packages,
178+
pass_through_args: Some(&pass_through),
179+
});
180+
let result = result.unwrap();
181+
assert_eq!(result.bin_path, "pnpm");
182+
assert_eq!(result.args, vec!["rebuild", "--recursive", "better-sqlite3"]);
183+
}
143184
}

crates/vite_pm_cli/src/cli.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -860,6 +860,9 @@ pub enum PmCommands {
860860
/// Rebuild native modules
861861
#[command(visible_alias = "rb")]
862862
Rebuild {
863+
/// Packages to rebuild (rebuilds all if omitted)
864+
packages: Vec<String>,
865+
863866
/// Additional arguments
864867
#[arg(last = true, allow_hyphen_values = true)]
865868
pass_through_args: Option<Vec<String>>,

crates/vite_pm_cli/src/handlers.rs

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -452,8 +452,11 @@ pub async fn run_pm_subcommand(
452452
Ok(pm.run_search_command(&options, cwd).await?)
453453
}
454454

455-
PmCommands::Rebuild { pass_through_args } => {
456-
let options = RebuildCommandOptions { pass_through_args: pass_through_args.as_deref() };
455+
PmCommands::Rebuild { packages, pass_through_args } => {
456+
let options = RebuildCommandOptions {
457+
packages: &packages,
458+
pass_through_args: pass_through_args.as_deref(),
459+
};
457460
Ok(pm.run_rebuild_command(&options, cwd).await?)
458461
}
459462

docs/guide/install.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -121,15 +121,19 @@ Use these when you need to understand the current state of dependencies.
121121
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.
122122

123123
- `vp rebuild` rebuilds all native modules
124+
- `vp rebuild <package...>` rebuilds the listed packages only
124125
- `vp rebuild -- <args>` passes extra arguments to the underlying package manager
125126

126127
```bash
127128
vp rebuild
129+
vp rebuild better-sqlite3 sharp
128130
vp rebuild -- --update-binary
129131
```
130132

131133
`vp rebuild` is a shorthand for `vp pm rebuild`.
132134

135+
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.
136+
133137
#### Advanced
134138

135139
Use these when you need lower-level package-manager behavior.
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
{
2+
"name": "command-rebuild-pnpm11",
3+
"version": "1.0.0",
4+
"private": true,
5+
"dependencies": {
6+
"testnpm2": "1.0.0"
7+
},
8+
"packageManager": "pnpm@11.0.6"
9+
}
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
> vp rebuild --help # should show help with [PACKAGES]... positional
2+
Usage: vp pm rebuild [PACKAGES]... [-- <PASS_THROUGH_ARGS>...]
3+
4+
Rebuild native modules
5+
6+
Arguments:
7+
[PACKAGES]... Packages to rebuild (rebuilds all if omitted)
8+
[PASS_THROUGH_ARGS]... Additional arguments
9+
10+
Options:
11+
-h, --help Print help
12+
13+
Documentation: https://viteplus.dev/guide/install
14+
15+
16+
> vp install # set up node_modules
17+
Packages: +<variable>
18+
+
19+
Progress: resolved <variable>, reused <variable>, downloaded <variable>, added <variable>, done
20+
21+
dependencies:
22+
+ testnpm2 <semver> (1.0.1 is available)
23+
24+
Done in <variable>ms using pnpm v<semver>
25+
26+
> vp rebuild # bare rebuild, no args
27+
> vp rebuild testnpm2 # should accept positional package name
28+
> vp rebuild testnpm2 -- --recursive # package name + pass-through args
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
{
2+
"ignoredPlatforms": ["win32"],
3+
"commands": [
4+
"vp rebuild --help # should show help with [PACKAGES]... positional",
5+
"vp install # set up node_modules",
6+
"vp rebuild # bare rebuild, no args",
7+
"vp rebuild testnpm2 # should accept positional package name",
8+
"vp rebuild testnpm2 -- --recursive # package name + pass-through args"
9+
]
10+
}

0 commit comments

Comments
 (0)