diff --git a/features/package-update.feature b/features/package-update.feature index c5981277..afeec92c 100644 --- a/features/package-update.feature +++ b/features/package-update.feature @@ -98,3 +98,99 @@ Feature: Update WP-CLI packages """ Success: Packages updated. """ + + Scenario: Update a specific package by name + Given an empty directory + + When I run `wp package install wp-cli-test/updateable-package:v1.0.0` + Then STDOUT should contain: + """ + Success: Package installed. + """ + + When I run `wp package install danielbachhuber/wp-cli-reset-post-date-command` + Then STDOUT should contain: + """ + Success: Package installed. + """ + + When I run `sed -i.bak s/v1.0.0/\>=1.0.0/g {PACKAGE_PATH}/composer.json` + Then the return code should be 0 + + When I run `wp package update wp-cli-test/updateable-package` + Then STDOUT should contain: + """ + Using Composer to update packages... + """ + And STDOUT should contain: + """ + Success: Package updated successfully. + """ + + When I run `wp package list --fields=name,update` + Then STDOUT should be a table containing rows: + | name | update | + | wp-cli-test/updateable-package | none | + + Scenario: Update multiple specific packages by name + Given an empty directory + + When I run `wp package install wp-cli-test/updateable-package:v1.0.0` + Then STDOUT should contain: + """ + Success: Package installed. + """ + + When I run `wp package install danielbachhuber/wp-cli-reset-post-date-command` + Then STDOUT should contain: + """ + Success: Package installed. + """ + + When I run `sed -i.bak s/v1.0.0/\>=1.0.0/g {PACKAGE_PATH}/composer.json` + Then the return code should be 0 + + When I run `wp package update wp-cli-test/updateable-package danielbachhuber/wp-cli-reset-post-date-command` + Then STDOUT should contain: + """ + Using Composer to update packages... + """ + And STDOUT should contain: + """ + Success: Updated 1 of 2 packages. + """ + + Scenario: Update package that is already up to date + 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 danielbachhuber/wp-cli-reset-post-date-command` + Then STDOUT should contain: + """ + Using Composer to update packages... + """ + And STDOUT should contain: + """ + Success: Package already at latest version. + """ + + Scenario: Error when trying to update a non-existent package + 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 try `wp package update non-existent/package` + Then STDERR should contain: + """ + Error: Package 'non-existent/package' is not installed. + """ + And the return code should be 1 diff --git a/src/Package_Command.php b/src/Package_Command.php index 9e13c0d6..ebb4cc87 100644 --- a/src/Package_Command.php +++ b/src/Package_Command.php @@ -501,10 +501,16 @@ public function path( $args ) { } /** - * Updates all installed WP-CLI packages to their latest version. + * Updates installed WP-CLI packages to their latest version. + * + * ## OPTIONS + * + * [...] + * : One or more package names to update. If not specified, all packages will be updated. * * ## EXAMPLES * + * # Update all packages. * $ wp package update * Using Composer to update packages... * --- @@ -518,19 +524,63 @@ public function path( $args ) { * Generating autoload files * --- * Success: Packages updated. + * + * # Update a specific package. + * $ wp package update wp-cli/server-command + * Using Composer to update packages... + * --- + * Loading composer repositories with package information + * Updating dependencies + * Writing lock file + * Generating autoload files + * --- + * Success: Package updated successfully. */ - public function update() { + public function update( $args = [] ) { $this->set_composer_auth_env_var(); + + // Validate package names if provided + $packages_to_update = []; + if ( ! empty( $args ) ) { + foreach ( $args as $package_name ) { + $package = $this->get_installed_package_by_name( $package_name ); + if ( false === $package ) { + WP_CLI::error( sprintf( "Package '%s' is not installed.", $package_name ) ); + } + // Use the package's pretty name (case-sensitive) from composer + $packages_to_update[] = $package->getPrettyName(); + } + } + $composer = $this->get_composer(); - // Set up the EventSubscriber + // Set up the EventSubscriber with tracking for updates + $updated_packages = []; $event_subscriber = new PackageManagerEventSubscriber(); $composer->getEventDispatcher()->addSubscriber( $event_subscriber ); + // Add a listener to track actual package updates + $composer->getEventDispatcher()->addListener( + 'post-package-update', + function ( $event ) use ( &$updated_packages ) { + $operation = $event->getOperation(); + if ( method_exists( $operation, 'getTargetPackage' ) ) { + $package = $operation->getTargetPackage(); + $updated_packages[] = $package->getPrettyName(); + } + } + ); + // Set up the installer $install = Installer::create( new ComposerIO(), $composer ); $install->setUpdate( true ); // Installer class will only override composer.lock with this flag $install->setPreferSource( true ); // Use VCS when VCS for easier contributions. + + // If specific packages are provided, use the allow list + if ( ! empty( $packages_to_update ) ) { + $install->setUpdateAllowList( $packages_to_update ); + } + WP_CLI::log( 'Using Composer to update packages...' ); WP_CLI::log( '---' ); $res = false; @@ -544,7 +594,28 @@ public function update() { // TODO: The --insecure (to be added here) flag should cause another Composer run with verify disabled. if ( 0 === $res ) { - WP_CLI::success( 'Packages updated.' ); + $num_packages = count( $packages_to_update ); + if ( $num_packages > 0 ) { + // When specific packages were requested, report on actual updates + $num_updated = count( $updated_packages ); + if ( 0 === $num_updated ) { + if ( 1 === $num_packages ) { + WP_CLI::success( 'Package already at latest version.' ); + } else { + WP_CLI::success( 'Packages already at latest versions.' ); + } + } elseif ( $num_updated === $num_packages ) { + if ( 1 === $num_packages ) { + WP_CLI::success( 'Package updated successfully.' ); + } else { + WP_CLI::success( sprintf( 'All %d packages updated successfully.', $num_packages ) ); + } + } else { + WP_CLI::success( sprintf( 'Updated %d of %d packages.', $num_updated, $num_packages ) ); + } + } else { + WP_CLI::success( 'Packages updated.' ); + } } else { $res_msg = $res ? " (Composer return code {$res})" : ''; // $res may be null apparently. WP_CLI::error( "Failed to update packages{$res_msg}." );