Skip to content

Commit 901771a

Browse files
committed
fix(pm): accept positional package names for vp rebuild
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.
1 parent c61621a commit 901771a

7 files changed

Lines changed: 107 additions & 7 deletions

File tree

crates/vite_install/src/commands/rebuild.rs

Lines changed: 48 additions & 5 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

@@ -47,10 +48,12 @@ impl PackageManager {
4748
PackageManagerType::Npm => {
4849
bin_name = "npm".into();
4950
args.push("rebuild".into());
51+
args.extend_from_slice(options.packages);
5052
}
5153
PackageManagerType::Pnpm => {
5254
bin_name = "pnpm".into();
5355
args.push("rebuild".into());
56+
args.extend_from_slice(options.packages);
5457
}
5558
PackageManagerType::Yarn => {
5659
let is_yarn1 = self.version.starts_with("1.");
@@ -110,7 +113,7 @@ mod tests {
110113
#[test]
111114
fn test_npm_rebuild() {
112115
let pm = create_mock_package_manager(PackageManagerType::Npm, "11.0.0");
113-
let result = pm.resolve_rebuild_command(&RebuildCommandOptions { pass_through_args: None });
116+
let result = pm.resolve_rebuild_command(&RebuildCommandOptions::default());
114117
assert!(result.is_some());
115118
let result = result.unwrap();
116119
assert_eq!(result.bin_path, "npm");
@@ -120,7 +123,7 @@ mod tests {
120123
#[test]
121124
fn test_pnpm_rebuild() {
122125
let pm = create_mock_package_manager(PackageManagerType::Pnpm, "10.0.0");
123-
let result = pm.resolve_rebuild_command(&RebuildCommandOptions { pass_through_args: None });
126+
let result = pm.resolve_rebuild_command(&RebuildCommandOptions::default());
124127
assert!(result.is_some());
125128
let result = result.unwrap();
126129
assert_eq!(result.bin_path, "pnpm");
@@ -130,14 +133,54 @@ mod tests {
130133
#[test]
131134
fn test_yarn1_rebuild_not_supported() {
132135
let pm = create_mock_package_manager(PackageManagerType::Yarn, "1.22.0");
133-
let result = pm.resolve_rebuild_command(&RebuildCommandOptions { pass_through_args: None });
136+
let result = pm.resolve_rebuild_command(&RebuildCommandOptions::default());
134137
assert!(result.is_none());
135138
}
136139

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

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; with pnpm v10+, bare rebuild only acts on packages whose build scripts are approved)
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; with pnpm v10+, bare rebuild only acts on packages whose build scripts are approved)
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)