diff --git a/features/package-install.feature b/features/package-install.feature index fe76b4c5..2c1f10c1 100644 --- a/features/package-install.feature +++ b/features/package-install.feature @@ -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 + """ diff --git a/features/package-update.feature b/features/package-update.feature index c5981277..42d064ee 100644 --- a/features/package-update.feature +++ b/features/package-update.feature @@ -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 diff --git a/features/package.feature b/features/package.feature index a45218b4..290997c0 100644 --- a/features/package.feature +++ b/features/package.feature @@ -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 + """ diff --git a/src/Package_Command.php b/src/Package_Command.php index 9e13c0d6..769f902a 100644 --- a/src/Package_Command.php +++ b/src/Package_Command.php @@ -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. @@ -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 ); + + if ( ! $interaction ) { + $this->set_non_interactive_mode(); + } $this->set_composer_auth_env_var(); $git_package = false; @@ -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 @@ -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 ); + + if ( ! $interaction ) { + $this->set_non_interactive_mode(); + } + $this->set_composer_auth_env_var(); $composer = $this->get_composer(); @@ -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. @@ -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 ); + + if ( ! $interaction ) { + $this->set_non_interactive_mode(); + } $this->set_composer_auth_env_var(); $package = $this->get_installed_package_by_name( $package_name ); @@ -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' ); + } }