Skip to content
30 changes: 30 additions & 0 deletions features/package-install.feature
Original file line number Diff line number Diff line change
Expand Up @@ -1102,3 +1102,33 @@ Feature: Install WP-CLI packages
Error: ZipArchive failed to unzip 'package-dir/zero.zip': Not a zip archive (19).
"""
And STDOUT should be empty

Scenario: Install a package with --no-interaction flag
Given an empty directory
And a composer.json file:
"""
{
"repositories": {
"test" : {
"type": "path",
"url": "./dummy-package/"
},
"wp-cli": {
"type": "composer",
"url": "https://wp-cli.org/package-index/"
}
}
}
"""
And a dummy-package/composer.json file:
"""
{
"name": "wp-cli/restful",
"description": "Test package for no-interaction flag"
}
"""
When I run `WP_CLI_PACKAGES_DIR=. wp package install wp-cli/restful --no-interaction`
Then STDOUT should contain:
"""
Success: Package installed
"""
20 changes: 20 additions & 0 deletions features/package-update.feature
Original file line number Diff line number Diff line change
Expand Up @@ -98,3 +98,23 @@ Feature: Update WP-CLI packages
"""
Success: Packages updated.
"""

Scenario: Update packages with --no-interaction flag
Given an empty directory

When I run `wp package install danielbachhuber/wp-cli-reset-post-date-command`
Then STDOUT should contain:
"""
Success: Package installed.
"""

When I run `wp package update --no-interaction`
Then STDOUT should contain:
"""
Using Composer to update packages...
"""
And STDOUT should contain:
"""
Packages updated.
"""
And STDERR should be empty
19 changes: 19 additions & 0 deletions features/package.feature
Original file line number Diff line number Diff line change
Expand Up @@ -208,3 +208,22 @@ Feature: Manage WP-CLI packages
"""
{NO_SUCH_PACKAGE_COMPOSER_JSON}
"""

Scenario: Uninstall a package with --no-interaction flag
Given an empty directory

When I run `wp package install runcommand/hook`
Then STDERR should be empty

When I run `wp package uninstall runcommand/hook --no-interaction`
Then STDERR should be empty
And STDOUT should contain:
"""
Success: Uninstalled package.
"""

When I run `wp package list`
Then STDOUT should not contain:
"""
runcommand/hook
"""
53 changes: 50 additions & 3 deletions src/Package_Command.php
Original file line number Diff line number Diff line change
Expand Up @@ -199,6 +199,9 @@ public function browse( $_, $assoc_args ) {
* [--insecure]
* : Retry downloads without certificate validation if TLS handshake fails. Note: This makes the request vulnerable to a MITM attack.
*
* [--interaction]
* : Control interactive mode. Use `--no-interaction` to disable prompts (interactive by default). Useful for scripting.
*
* ## EXAMPLES
*
* # Install a package hosted at a git URL.
Expand All @@ -216,7 +219,12 @@ public function browse( $_, $assoc_args ) {
public function install( $args, $assoc_args ) {
list( $package_name ) = $args;

$insecure = (bool) Utils\get_flag_value( $assoc_args, 'insecure', false );
$insecure = (bool) Utils\get_flag_value( $assoc_args, 'insecure', false );
$interaction = Utils\get_flag_value( $assoc_args, 'interaction', true );
Copy link

Copilot AI Dec 19, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The interaction flag should be explicitly cast to boolean like the insecure flag on line 222 for consistency and type safety. While Utils\get_flag_value returns a boolean-ish value, explicit casting makes the intent clear and ensures consistent behavior.

Suggested change
$interaction = Utils\get_flag_value( $assoc_args, 'interaction', true );
$interaction = (bool) Utils\get_flag_value( $assoc_args, 'interaction', true );

Copilot uses AI. Check for mistakes.

if ( ! $interaction ) {
$this->set_non_interactive_mode();
}

$this->set_composer_auth_env_var();
$git_package = false;
Expand Down Expand Up @@ -503,6 +511,11 @@ public function path( $args ) {
/**
* Updates all installed WP-CLI packages to their latest version.
*
* ## OPTIONS
*
* [--interaction]
* : Boolean flag that controls interactive mode (enabled by default). Use `--no-interaction` to disable prompts, which is useful for scripting.
*
* ## EXAMPLES
*
* $ wp package update
Expand All @@ -518,8 +531,17 @@ public function path( $args ) {
* Generating autoload files
* ---
* Success: Packages updated.
*
* @param array $_ Unused positional arguments (none expected).
* @param array $assoc_args Associative array of options.
*/
public function update() {
public function update( $_, $assoc_args = [] ) {
$interaction = Utils\get_flag_value( $assoc_args, 'interaction', true );
Copy link

Copilot AI Dec 19, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The interaction flag should be explicitly cast to boolean for consistency with how the insecure flag is handled elsewhere in this file (lines 222, 602) and for type safety.

Suggested change
$interaction = Utils\get_flag_value( $assoc_args, 'interaction', true );
$interaction = (bool) Utils\get_flag_value( $assoc_args, 'interaction', true );

Copilot uses AI. Check for mistakes.

if ( ! $interaction ) {
$this->set_non_interactive_mode();
}

$this->set_composer_auth_env_var();
$composer = $this->get_composer();

Expand Down Expand Up @@ -562,6 +584,9 @@ public function update() {
* [--insecure]
* : Retry downloads without certificate validation if TLS handshake fails. Note: This makes the request vulnerable to a MITM attack.
*
* [--interaction]
* : Control interactive prompts. Use `--no-interaction` to disable interactive questions (useful for scripting).
*
* ## EXAMPLES
*
* # Uninstall package.
Expand All @@ -574,7 +599,12 @@ public function update() {
public function uninstall( $args, $assoc_args ) {
list( $package_name ) = $args;

$insecure = (bool) Utils\get_flag_value( $assoc_args, 'insecure', false );
$insecure = (bool) Utils\get_flag_value( $assoc_args, 'insecure', false );
$interaction = Utils\get_flag_value( $assoc_args, 'interaction', true );
Copy link

Copilot AI Dec 19, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The interaction flag should be explicitly cast to boolean like the insecure flag on line 602 for consistency and type safety. While Utils\get_flag_value returns a boolean-ish value, explicit casting makes the intent clear and ensures consistent behavior.

Suggested change
$interaction = Utils\get_flag_value( $assoc_args, 'interaction', true );
$interaction = (bool) Utils\get_flag_value( $assoc_args, 'interaction', true );

Copilot uses AI. Check for mistakes.

if ( ! $interaction ) {
$this->set_non_interactive_mode();
}

$this->set_composer_auth_env_var();
$package = $this->get_installed_package_by_name( $package_name );
Expand Down Expand Up @@ -1478,4 +1508,21 @@ private function get_github_default_branch( $package_name, $insecure = false ) {

return $default_branch;
}

/**
* Sets environment variables to enable non-interactive mode.
*
* This prevents Git from prompting for credentials (e.g., SSH passwords),
* which is useful for scripting and automation.
*
* Note: This uses putenv() which affects the entire PHP process, including
* any Git operations spawned by Composer. This is intentional to ensure
* non-interactive behavior propagates to all child processes.
*/
private function set_non_interactive_mode() {
// Prevent Git from prompting for credentials
putenv( 'GIT_TERMINAL_PROMPT=0' );
// Prevent SSH from prompting for passwords
putenv( 'GIT_SSH_COMMAND=ssh -o BatchMode=yes' );
}
}