Skip to content
Draft
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_install/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ flate2 = { workspace = true }
futures-util = { workspace = true }
hex = { workspace = true }
indoc = { workspace = true }
node-semver = { workspace = true }
pathdiff = { workspace = true }
semver = { workspace = true }
serde = { workspace = true, features = ["derive"] }
Expand Down
620 changes: 620 additions & 0 deletions crates/vite_install/src/commands/approve_builds.rs

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions crates/vite_install/src/commands/mod.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
pub mod add;
pub mod approve_builds;
pub mod audit;
pub mod cache;
pub mod config;
Expand Down
69 changes: 69 additions & 0 deletions crates/vite_pm_cli/src/cli.rs
Original file line number Diff line number Diff line change
Expand Up @@ -554,6 +554,23 @@ impl PackageManagerCommand {
/// Package manager subcommands (`vp pm <subcommand>`).
#[derive(Subcommand, Debug, Clone)]
pub enum PmCommands {
/// Approve dependency lifecycle scripts (install/postinstall) to run
#[command(name = "approve-builds")]
ApproveBuilds {
/// Packages to approve. Prefix with `!` to deny (pnpm >= 11.0.0 only).
/// Omit to launch interactive mode (pnpm only).
packages: Vec<String>,

/// Approve every package currently pending approval (pnpm >= 10.32.0).
/// Mutually exclusive with positional packages.
#[arg(long, conflicts_with = "packages")]
all: bool,

/// Additional arguments to pass through to the package manager
#[arg(last = true, allow_hyphen_values = true)]
pass_through_args: Option<Vec<String>>,
},

/// Remove unnecessary packages
Prune {
/// Remove devDependencies
Expand Down Expand Up @@ -1182,4 +1199,56 @@ mod tests {

assert_eq!(error.kind(), clap::error::ErrorKind::ValueValidation);
}

#[test]
fn approve_builds_all_conflicts_with_packages() {
let error = parse_pm_command(&["vp", "pm", "approve-builds", "--all", "esbuild"])
.expect_err("expected --all + positional to fail");

assert_eq!(error.kind(), clap::error::ErrorKind::ArgumentConflict);
}

#[test]
fn approve_builds_all_conflicts_with_deny_packages() {
let error = parse_pm_command(&["vp", "pm", "approve-builds", "--all", "!core-js"])
.expect_err("expected --all + !pkg to fail");

assert_eq!(error.kind(), clap::error::ErrorKind::ArgumentConflict);
}

#[test]
fn approve_builds_all_alone_parses() {
let command = parse_pm_command(&["vp", "pm", "approve-builds", "--all"])
.expect("--all alone should parse");

assert!(matches!(
command,
PackageManagerCommand::Pm(PmCommands::ApproveBuilds { all: true, .. })
));
}

#[test]
fn approve_builds_packages_alone_parses() {
let command = parse_pm_command(&["vp", "pm", "approve-builds", "esbuild", "fsevents"])
.expect("positional packages should parse");

assert!(matches!(
command,
PackageManagerCommand::Pm(PmCommands::ApproveBuilds { all: false, .. })
));
}

#[test]
fn approve_builds_pass_through_args_capture() {
let command =
parse_pm_command(&["vp", "pm", "approve-builds", "esbuild", "--", "--workspace-root"])
.expect("pass-through args should parse");

let PackageManagerCommand::Pm(PmCommands::ApproveBuilds { pass_through_args, .. }) =
command
else {
panic!("expected ApproveBuilds variant");
};
assert_eq!(pass_through_args, Some(vec!["--workspace-root".to_string()]));
}
}
19 changes: 18 additions & 1 deletion crates/vite_pm_cli/src/handlers.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ use vite_install::{
PackageManager,
commands::{
add::AddCommandOptions,
approve_builds::ApproveBuildsCommandOptions,
audit::AuditCommandOptions,
cache::CacheCommandOptions,
config::ConfigCommandOptions,
Expand Down Expand Up @@ -178,7 +179,8 @@ pub async fn run_pm_subcommand(
) -> Result<ExitStatus, Error> {
let needs_project = matches!(
command,
PmCommands::Prune { .. }
PmCommands::ApproveBuilds { .. }
| PmCommands::Prune { .. }
Comment thread
fengmk2 marked this conversation as resolved.
| PmCommands::Pack { .. }
| PmCommands::List { .. }
| PmCommands::Publish { .. }
Expand All @@ -194,6 +196,21 @@ pub async fn run_pm_subcommand(
};

match command {
PmCommands::ApproveBuilds { packages, all, pass_through_args } => {
let options = ApproveBuildsCommandOptions {
packages: &packages,
all,
pass_through_args: pass_through_args.as_deref(),
};
// Map `Error::InvalidArgument` from the resolver to `UserMessage`
// so the version-gate failure renders without the harsh `error:` prefix.
match pm.run_approve_builds_command(&options, cwd).await {
Ok(status) => Ok(status),
Err(vite_error::Error::InvalidArgument(msg)) => Err(Error::UserMessage(msg)),
Err(other) => Err(Error::Install(other)),
}
}

PmCommands::Prune { prod, no_optional, pass_through_args } => {
let options = PruneCommandOptions {
prod,
Expand Down
14 changes: 11 additions & 3 deletions packages/cli/binding/src/cli/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -208,9 +208,17 @@ async fn execute_pm_command(
"Global package operations (`-g`/`--global`) are only supported by the globally-installed `vp` CLI. See https://viteplus.dev/guide/ to install it, then run the same command via the global `vp` binary.",
)));
}
let status = vite_pm_cli::dispatch(cwd, command)
.await
.map_err(|e| Error::Anyhow(anyhow::Error::new(e)))?;
let status = match vite_pm_cli::dispatch(cwd, command).await {
Ok(status) => status,
// Render `UserMessage` cleanly (no `error:` prefix) and exit non-zero —
// matches the global CLI's `is_user_message()` branch in main.rs so the
// friendly version-gate / usage errors look the same on both surfaces.
Err(vite_pm_cli::Error::UserMessage(msg)) => {
vite_shared::output::raw_stderr(&msg);
return Ok(ExitStatus(1));
}
Err(e) => return Err(Error::Anyhow(anyhow::Error::new(e))),
};
Ok(ExitStatus(status.code().unwrap_or(1) as u8))
}

Expand Down
39 changes: 20 additions & 19 deletions packages/cli/snap-tests-global/cli-helper-message/snap.txt
Original file line number Diff line number Diff line change
Expand Up @@ -313,25 +313,26 @@ Usage: vp pm <COMMAND>
Forward a command to the package manager

Commands:
prune Remove unnecessary packages
pack Create a tarball of the package
list List installed packages [aliases: ls]
view View package information from the registry [aliases: info, show]
publish Publish package to registry
owner Manage package owners [aliases: author]
cache Manage package cache
config Manage package manager configuration [aliases: c]
login Log in to a registry [aliases: adduser]
logout Log out from a registry
whoami Show the current logged-in user
token Manage authentication tokens
audit Run a security audit
dist-tag Manage distribution tags
deprecate Deprecate a package version
search Search for packages in the registry
rebuild Rebuild native modules [aliases: rb]
fund Show funding information for installed packages
ping Ping the registry
approve-builds Approve dependency lifecycle scripts (install/postinstall) to run
prune Remove unnecessary packages
pack Create a tarball of the package
list List installed packages [aliases: ls]
view View package information from the registry [aliases: info, show]
publish Publish package to registry
owner Manage package owners [aliases: author]
cache Manage package cache
config Manage package manager configuration [aliases: c]
login Log in to a registry [aliases: adduser]
logout Log out from a registry
whoami Show the current logged-in user
token Manage authentication tokens
audit Run a security audit
dist-tag Manage distribution tags
deprecate Deprecate a package version
search Search for packages in the registry
rebuild Rebuild native modules [aliases: rb]
fund Show funding information for installed packages
ping Ping the registry

Options:
-h, --help Print help
Expand Down
8 changes: 4 additions & 4 deletions packages/cli/snap-tests-global/command-add-bun/snap.txt
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ Usage: vp add <PACKAGES>... [-- <PASS_THROUGH_ARGS>...]
For more information, try '--help'.

> vp add testnpm2 -D && cat package.json # should add package as dev dependencies
bun add v<semver> (af24e281)
bun add v<semver> (<hash>)

installed testnpm2@<semver>

Expand All @@ -51,7 +51,7 @@ installed testnpm2@<semver>
}

> vp add testnpm2 test-vite-plus-install && cat package.json # should add packages to dependencies
bun add v<semver> (af24e281)
bun add v<semver> (<hash>)

installed testnpm2@<semver>
installed test-vite-plus-install@<semver>
Expand All @@ -70,7 +70,7 @@ installed test-vite-plus-install@<semver>
}

> vp install test-vite-plus-package@1.0.0 --save-peer && cat package.json # should install package alias for add
bun add v<semver> (af24e281)
bun add v<semver> (<hash>)

installed test-vite-plus-package@<semver>

Expand All @@ -91,7 +91,7 @@ installed test-vite-plus-package@<semver>
}

> vp add test-vite-plus-package-optional -O && cat package.json # should add package as optional dependencies
bun add v<semver> (af24e281)
bun add v<semver> (<hash>)

installed test-vite-plus-package-optional@<semver>

Expand Down
2 changes: 1 addition & 1 deletion packages/cli/snap-tests-global/command-list-bun/snap.txt
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
> vp install # should install packages first
bun install v<semver> (af24e281)
bun install v<semver> (<hash>)

+ test-vite-plus-package@<semver>
+ test-vite-plus-package-optional@<semver>
Expand Down
6 changes: 3 additions & 3 deletions packages/cli/snap-tests-global/command-outdated-bun/snap.txt
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ Documentation: https://viteplus.dev/guide/install


> vp install # should install packages first
bun install v<semver> (af24e281)
bun install v<semver> (<hash>)

+ test-vite-plus-top-package@<semver>
+ test-vite-plus-other-optional@<semver>
Expand All @@ -34,15 +34,15 @@ bun install v<semver> (af24e281)
4 packages installed [<variable>ms]

> vp outdated testnpm2 # should show outdated package
bun outdated v<semver> (af24e281)
bun outdated v<semver> (<hash>)
|--------------------------------------|
| Package | Current | Update | Latest |
|----------|---------|--------|--------|
| testnpm2 | <semver> | <semver> | <semver> |
|--------------------------------------|

> vp outdated -r # should support recursive output
bun outdated v<semver> (af24e281)
bun outdated v<semver> (<hash>)
|---------------------------------------------------------------------------------------------|
| Package | Current | Update | Latest | Workspace |
|------------------------------------------|---------|--------|--------|----------------------|
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
{
"name": "command-pm-approve-builds-bun",
"version": "1.0.0",
"packageManager": "bun@1.3.11"
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
> vp pm approve-builds --help # should show help with --all caveat
Usage: vp pm approve-builds [OPTIONS] [PACKAGES]... [-- <PASS_THROUGH_ARGS>...]

Approve dependency lifecycle scripts (install/postinstall) to run

Arguments:
[PACKAGES]... Packages to approve. Prefix with `!` to deny (pnpm >= <semver> only). Omit to launch interactive mode (pnpm only)
[PASS_THROUGH_ARGS]... Additional arguments to pass through to the package manager

Options:
--all Approve every package currently pending approval (pnpm >= <semver>). Mutually exclusive with positional packages
-h, --help Print help

Documentation: https://viteplus.dev/guide/install


> vp pm approve-builds # bun no-args: prints contextual note, exit 0
note: bun pm trust requires package names. Run `bun pm untrusted` to see which packages are pending, then pass them explicitly: `vp pm approve-builds <pkg> [<pkg>...]` or `vp pm approve-builds --all`.

> vp pm approve-builds '!core-js' # deny-only: prints deny warn, no redundant note
warn: bun does not support denylisting build scripts. Packages outside `trustedDependencies` in package.json are already denied by default. Skipping: core-js

[1]> vp pm approve-builds --all # forwards bun pm trust --all (errors on empty project — no lockfile)
bun pm trust v<semver> (<hash>)
error: Lockfile not found
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
{
"ignoredPlatforms": ["win32"],
"commands": [
"vp pm approve-builds --help # should show help with --all caveat",
"vp pm approve-builds # bun no-args: prints contextual note, exit 0",
"vp pm approve-builds '!core-js' # deny-only: prints deny warn, no redundant note",
"vp pm approve-builds --all # forwards bun pm trust --all (errors on empty project — no lockfile)"
]
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
{
"name": "command-pm-approve-builds-pnpm10-old",
"version": "1.0.0",
"packageManager": "pnpm@10.31.0"
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
[1]> vp pm approve-builds --all # pnpm 10.31.0 < 10.32.0 → rejected with friendly UserMessage (no `error:` prefix)
`--all` requires pnpm >= <semver>. Upgrade pnpm or pass package names explicitly.

[1]> vp pm approve-builds esbuild '!core-js' # pnpm 10.31.0 < 11.0.0 → !pkg deny syntax rejected
`!<pkg>` deny syntax requires pnpm >= <semver>. Upgrade pnpm or omit the `!` entries.

> vp pm approve-builds esbuild # plain positional still works on old pnpm
There are no packages awaiting approval
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
"commands": [
"vp pm approve-builds --all # pnpm 10.31.0 < 10.32.0 → rejected with friendly UserMessage (no `error:` prefix)",
"vp pm approve-builds esbuild '!core-js' # pnpm 10.31.0 < 11.0.0 → !pkg deny syntax rejected",
"vp pm approve-builds esbuild # plain positional still works on old pnpm"
]
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
{
"name": "command-pm-approve-builds-pnpm11",
"version": "1.0.0",
"private": true,
"packageManager": "pnpm@11.0.0"
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
> vp pm approve-builds --all # forwards pnpm approve-builds --all (nothing to approve)
There are no packages awaiting approval

> vp pm approve-builds esbuild '!core-js' # pnpm 11.x supports !pkg deny syntax — forwarded as positional args
There are no packages awaiting approval
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
{
"commands": [
"vp pm approve-builds --all # forwards pnpm approve-builds --all (nothing to approve)",
"vp pm approve-builds esbuild '!core-js' # pnpm 11.x supports !pkg deny syntax — forwarded as positional args"
]
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
{
"name": "command-pm-approve-builds-yarn4",
"version": "1.0.0",
"private": true,
"packageManager": "yarn@4.10.3"
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
> vp pm approve-builds # yarn Berry warn — points at dependenciesMeta["<pkg>"].built
warn: yarn does not run third-party build scripts by default. To allow a package, set `dependenciesMeta["<package>"].built: true` in package.json.

> vp pm approve-builds esbuild # same warn (no native command on yarn)
warn: yarn does not run third-party build scripts by default. To allow a package, set `dependenciesMeta["<package>"].built: true` in package.json.

> vp pm approve-builds esbuild -- --silent # extras trigger the dropped-pass-through warn
warn: yarn does not run third-party build scripts by default. To allow a package, set `dependenciesMeta["<package>"].built: true` in package.json.
warn: Ignoring pass-through args (--silent): this package manager has no native approve-builds command to forward them to.
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
{
"ignoredPlatforms": ["win32"],
"commands": [
"vp pm approve-builds # yarn Berry warn — points at dependenciesMeta[\"<pkg>\"].built",
"vp pm approve-builds esbuild # same warn (no native command on yarn)",
"vp pm approve-builds esbuild -- --silent # extras trigger the dropped-pass-through warn"
]
}
Loading
Loading