From 6a8aa7d2c5bb7514996115f920890418416ae61f Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sun, 1 Feb 2026 16:20:05 +0000 Subject: [PATCH 1/5] Initial plan From d554d5e98448a2fcae0890d23a52730abef099c8 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sun, 1 Feb 2026 16:23:28 +0000 Subject: [PATCH 2/5] Change default package version from dev-main to @stable Co-authored-by: swissspidy <841956+swissspidy@users.noreply.github.com> --- src/Package_Command.php | 19 ++++++++++++------- 1 file changed, 12 insertions(+), 7 deletions(-) diff --git a/src/Package_Command.php b/src/Package_Command.php index b5a4af8f..0067d82e 100644 --- a/src/Package_Command.php +++ b/src/Package_Command.php @@ -41,9 +41,9 @@ * | wp-cli/server-command | Daniel Bachhuber | dev-main | available | 2.x-dev | * +-----------------------+------------------+----------+-----------+----------------+ * - * # Install the latest development version of the package. + * # Install the latest stable version of the package. * $ wp package install wp-cli/server-command - * Installing package wp-cli/server-command (dev-main) + * Installing package wp-cli/server-command (^2.0) * Updating /home/person/.wp-cli/packages/composer.json to require the package... * Using Composer to install the package... * --- @@ -201,10 +201,10 @@ public function browse( $_, $assoc_args ) { * * ## EXAMPLES * - * # Install a package hosted at a git URL. + * # Install the latest stable version of a package. * $ wp package install runcommand/hook * - * # Install the latest stable version. + * # Install the latest stable version (explicitly specified). * $ wp package install wp-cli/server-command:@stable * * # Install a package hosted at a GitLab.com URL. @@ -224,12 +224,17 @@ public function install( $args, $assoc_args ) { $version = ''; if ( $this->is_git_repository( $package_name ) ) { if ( '' === $version ) { - $version = "dev-{$this->get_github_default_branch( $package_name, $insecure )}"; + $version = '@stable'; } $git_package = $package_name; $matches = []; if ( preg_match( '#([^:\/]+\/[^\/]+)\.git#', $package_name, $matches ) ) { - $package_name = $this->check_git_package_name( $matches[1], $package_name, $version, $insecure ); + $extracted_package_name = $matches[1]; + if ( '@stable' === $version ) { + $tag = $this->get_github_latest_release_tag( $extracted_package_name, $insecure ); + $version = $this->guess_version_constraint_from_tag( $tag ); + } + $package_name = $this->check_git_package_name( $extracted_package_name, $package_name, $version, $insecure ); } else { WP_CLI::error( "Couldn't parse package name from expected path '/'." ); } @@ -301,7 +306,7 @@ public function install( $args, $assoc_args ) { $git_package = $package; if ( '' === $version ) { - $version = "dev-{$this->get_github_default_branch( $package_name, $insecure )}"; + $version = '@stable'; } if ( '@stable' === $version ) { From e197b859be9d186a765a9daa9ad1c384e4bd68c4 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sun, 1 Feb 2026 16:25:17 +0000 Subject: [PATCH 3/5] Update get_github_latest_release_tag to fallback to default branch Co-authored-by: swissspidy <841956+swissspidy@users.noreply.github.com> --- src/Package_Command.php | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/src/Package_Command.php b/src/Package_Command.php index 0067d82e..4625deda 100644 --- a/src/Package_Command.php +++ b/src/Package_Command.php @@ -1357,23 +1357,28 @@ private function get_raw_git_version( $version ) { /** * Gets the release tag for the latest stable release of a GitHub repository. * + * If there is no release, falls back to the default branch prefixed with 'dev-'. + * * @param string $package_name Name of the repository. + * @param bool $insecure Whether to retry downloads without certificate validation if TLS handshake fails. * - * @return string Release tag. + * @return string Release tag or 'dev-{default_branch}' if no release exists. */ private function get_github_latest_release_tag( $package_name, $insecure ) { $url = "https://api.github.com/repos/{$package_name}/releases/latest"; $options = [ 'insecure' => $insecure ]; $response = Utils\http_request( 'GET', $url, null, [], $options ); if ( 20 !== (int) substr( $response->status_code, 0, 2 ) ) { - WP_CLI::warning( 'Could not guess stable version from GitHub repository, falling back to master branch' ); - return 'master'; + $default_branch = $this->get_github_default_branch( $package_name, $insecure ); + WP_CLI::warning( "Could not guess stable version from GitHub repository, falling back to {$default_branch} branch" ); + return "dev-{$default_branch}"; } $package_data = json_decode( $response->body ); if ( JSON_ERROR_NONE !== json_last_error() ) { - WP_CLI::warning( 'Could not guess stable version from GitHub repository, falling back to master branch' ); - return 'master'; + $default_branch = $this->get_github_default_branch( $package_name, $insecure ); + WP_CLI::warning( "Could not guess stable version from GitHub repository, falling back to {$default_branch} branch" ); + return "dev-{$default_branch}"; } $tag = $package_data->tag_name; From 2a4d65f7787ba2c5bcf00a07a5870669f6c960c3 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 3 Feb 2026 15:06:37 +0000 Subject: [PATCH 4/5] Refactor to eliminate code duplication in @stable resolution Co-authored-by: swissspidy <841956+swissspidy@users.noreply.github.com> --- src/Package_Command.php | 33 ++++++++++++++++++++++----------- 1 file changed, 22 insertions(+), 11 deletions(-) diff --git a/src/Package_Command.php b/src/Package_Command.php index 4625deda..df8c9fd6 100644 --- a/src/Package_Command.php +++ b/src/Package_Command.php @@ -231,8 +231,7 @@ public function install( $args, $assoc_args ) { if ( preg_match( '#([^:\/]+\/[^\/]+)\.git#', $package_name, $matches ) ) { $extracted_package_name = $matches[1]; if ( '@stable' === $version ) { - $tag = $this->get_github_latest_release_tag( $extracted_package_name, $insecure ); - $version = $this->guess_version_constraint_from_tag( $tag ); + $version = $this->resolve_stable_version( $extracted_package_name, $insecure ); } $package_name = $this->check_git_package_name( $extracted_package_name, $package_name, $version, $insecure ); } else { @@ -310,8 +309,7 @@ public function install( $args, $assoc_args ) { } if ( '@stable' === $version ) { - $tag = $this->get_github_latest_release_tag( $package_name, $insecure ); - $version = $this->guess_version_constraint_from_tag( $tag ); + $version = $this->resolve_stable_version( $package_name, $insecure ); } $package_name = $this->check_github_package_name( $package_name, $version, $insecure ); } @@ -1354,6 +1352,19 @@ private function get_raw_git_version( $version ) { return str_replace( [ '^', '~' ], '', $version ); } + /** + * Resolves '@stable' version to an actual version constraint. + * + * @param string $package_name Name of the repository. + * @param bool $insecure Whether to retry downloads without certificate validation if TLS handshake fails. + * + * @return string Version constraint. + */ + private function resolve_stable_version( $package_name, $insecure ) { + $tag = $this->get_github_latest_release_tag( $package_name, $insecure ); + return $this->guess_version_constraint_from_tag( $tag ); + } + /** * Gets the release tag for the latest stable release of a GitHub repository. * @@ -1368,14 +1379,14 @@ private function get_github_latest_release_tag( $package_name, $insecure ) { $url = "https://api.github.com/repos/{$package_name}/releases/latest"; $options = [ 'insecure' => $insecure ]; $response = Utils\http_request( 'GET', $url, null, [], $options ); - if ( 20 !== (int) substr( $response->status_code, 0, 2 ) ) { - $default_branch = $this->get_github_default_branch( $package_name, $insecure ); - WP_CLI::warning( "Could not guess stable version from GitHub repository, falling back to {$default_branch} branch" ); - return "dev-{$default_branch}"; - } - $package_data = json_decode( $response->body ); - if ( JSON_ERROR_NONE !== json_last_error() ) { + // Check for successful response and valid JSON + $package_data = isset( $response->body ) ? json_decode( $response->body ) : null; + + if ( 20 !== (int) substr( $response->status_code, 0, 2 ) + || null === $package_data + || ! isset( $package_data->tag_name ) ) { + $default_branch = $this->get_github_default_branch( $package_name, $insecure ); WP_CLI::warning( "Could not guess stable version from GitHub repository, falling back to {$default_branch} branch" ); return "dev-{$default_branch}"; From be5aaa46b2af3fdd99759982583b16388a3d25c9 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 3 Feb 2026 15:07:38 +0000 Subject: [PATCH 5/5] Add explicit JSON error check for better error handling Co-authored-by: swissspidy <841956+swissspidy@users.noreply.github.com> --- src/Package_Command.php | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/Package_Command.php b/src/Package_Command.php index df8c9fd6..db78e6ac 100644 --- a/src/Package_Command.php +++ b/src/Package_Command.php @@ -1381,9 +1381,10 @@ private function get_github_latest_release_tag( $package_name, $insecure ) { $response = Utils\http_request( 'GET', $url, null, [], $options ); // Check for successful response and valid JSON - $package_data = isset( $response->body ) ? json_decode( $response->body ) : null; + $package_data = json_decode( $response->body ?? '' ); if ( 20 !== (int) substr( $response->status_code, 0, 2 ) + || JSON_ERROR_NONE !== json_last_error() || null === $package_data || ! isset( $package_data->tag_name ) ) {