From 4795754da7393dc82627e2d02e503d254f2d5081 Mon Sep 17 00:00:00 2001 From: Bogdan Ungureanu Date: Fri, 17 Nov 2023 11:00:28 +0200 Subject: [PATCH 1/2] Add support GitHub releases/latest on installation --- features/plugin-install-github-latest.feature | 15 ++++ src/WP_CLI/CommandWithUpgrade.php | 18 +++- src/WP_CLI/ParseGithubUrlInput.php | 88 +++++++++++++++++++ 3 files changed, 120 insertions(+), 1 deletion(-) create mode 100644 features/plugin-install-github-latest.feature create mode 100644 src/WP_CLI/ParseGithubUrlInput.php diff --git a/features/plugin-install-github-latest.feature b/features/plugin-install-github-latest.feature new file mode 100644 index 00000000..35d80886 --- /dev/null +++ b/features/plugin-install-github-latest.feature @@ -0,0 +1,15 @@ +Feature: Install WordPress plugins + + Scenario: Verify that providing a plugin releases/latest GitHub URL will get the latest ZIP + Given a WP install + When I run `wp plugin install https://github.com/danielbachhuber/one-time-login/releases/latest` + Then STDOUT should contain: + """ + Latest release resolved to Version 0.4.0 + Downloading installation package from + """ + And STDOUT should contain: + """ + Plugin installed successfully. + Success: Installed 1 of 1 plugins. + """ diff --git a/src/WP_CLI/CommandWithUpgrade.php b/src/WP_CLI/CommandWithUpgrade.php index 53c45df7..67da0766 100755 --- a/src/WP_CLI/CommandWithUpgrade.php +++ b/src/WP_CLI/CommandWithUpgrade.php @@ -145,7 +145,6 @@ private function show_legend( $items ) { } public function install( $args, $assoc_args ) { - $successes = 0; $errors = 0; foreach ( $args as $slug ) { @@ -159,6 +158,23 @@ public function install( $args, $assoc_args ) { $is_remote = false !== strpos( $slug, '://' ); + $github_parser = new ParseGithubUrlInput(); + + if ( $is_remote && $github_repo = $github_parser->get_github_repo_from_url( $slug ) ) { + $version = $github_parser->get_the_latest_github_version( $github_repo ); + + if ( is_wp_error( $version ) ) { + WP_CLI::error( $version->get_error_message() ); + } + + /** + * Sets the $slug that will trigger the installation based on a zip file. + */ + $slug = $version['url']; + + WP_CLI::log( 'Latest release resolved to ' . $version['name'] ); + } + // Check if a URL to a remote or local zip has been specified. if ( $is_remote || ( pathinfo( $slug, PATHINFO_EXTENSION ) === 'zip' && is_file( $slug ) ) ) { // Install from local or remote zip file. diff --git a/src/WP_CLI/ParseGithubUrlInput.php b/src/WP_CLI/ParseGithubUrlInput.php new file mode 100644 index 00000000..4abb7c71 --- /dev/null +++ b/src/WP_CLI/ParseGithubUrlInput.php @@ -0,0 +1,88 @@ +github_releases_api_endpoint, $repo_slug ); + + $response = \wp_remote_get( $api_url ); + + if ( \is_wp_error( $response ) ) { + return $response; + } + + $body = \wp_remote_retrieve_body( $response ); + $decoded_body = json_decode( $body ); + + if ( wp_remote_retrieve_response_code( $response ) === \WP_Http::FORBIDDEN ) { + return new \WP_Error( \WP_Http::FORBIDDEN, $decoded_body->message . PHP_EOL . $decoded_body->documentation_url ); + } + + if ( null === $decoded_body ) { + return new \WP_Error( 500, 'Empty response received from GitHub.com API' ); + } + + if ( ! isset( $decoded_body[0] ) ) { + return new \WP_Error( '400', 'The given Github repository does not have any releases' ); + } + + $latest_release = $decoded_body[0]; + + return [ 'name' => $latest_release->name, 'url' => $this->get_asset_url_from_release( $latest_release ) ]; + } + + /** + * Get the asset URL from the release array. When the asset is not present, we fallback to the zipball_url (source code) property. + * + * @param array $release + * + * @return string|null + */ + private function get_asset_url_from_release( $release ) { + if ( isset( $release->assets[0]->browser_download_url ) ) { + return $release->assets[0]->browser_download_url; + } + + if ( isset( $release->zipball_url ) ) { + return $release->zipball_url; + } + + return null; + } + + /** + * Get the GitHub repo from the URL. + * + * @param string $url + * + * @return string|null + */ + public function get_github_repo_from_url( $url ) { + preg_match( $this->github_latest_release_url, $url, $matches ); + + return isset( $matches[1] ) ? $matches[1] : null; + } +} From a1e6b9b3aa56bb3fb93f6480955879bd415f6361 Mon Sep 17 00:00:00 2001 From: Bogdan Ungureanu Date: Sat, 18 Nov 2023 15:52:43 +0200 Subject: [PATCH 2/2] Add support for GITHUB_TOKEN environment variable --- src/WP_CLI/CommandWithUpgrade.php | 24 ++++++++++++++---------- src/WP_CLI/ParseGithubUrlInput.php | 26 +++++++++++++++++++++++--- 2 files changed, 37 insertions(+), 13 deletions(-) diff --git a/src/WP_CLI/CommandWithUpgrade.php b/src/WP_CLI/CommandWithUpgrade.php index 67da0766..0cb7c061 100755 --- a/src/WP_CLI/CommandWithUpgrade.php +++ b/src/WP_CLI/CommandWithUpgrade.php @@ -160,19 +160,23 @@ public function install( $args, $assoc_args ) { $github_parser = new ParseGithubUrlInput(); - if ( $is_remote && $github_repo = $github_parser->get_github_repo_from_url( $slug ) ) { - $version = $github_parser->get_the_latest_github_version( $github_repo ); + if ( $is_remote ) { + $github_repo = $github_parser->get_github_repo_from_url( $slug ); - if ( is_wp_error( $version ) ) { - WP_CLI::error( $version->get_error_message() ); - } + if ( $github_repo ) { + $version = $github_parser->get_the_latest_github_version( $github_repo ); + + if ( is_wp_error( $version ) ) { + WP_CLI::error( $version->get_error_message() ); + } - /** - * Sets the $slug that will trigger the installation based on a zip file. - */ - $slug = $version['url']; + /** + * Sets the $slug that will trigger the installation based on a zip file. + */ + $slug = $version['url']; - WP_CLI::log( 'Latest release resolved to ' . $version['name'] ); + WP_CLI::log( 'Latest release resolved to ' . $version['name'] ); + } } // Check if a URL to a remote or local zip has been specified. diff --git a/src/WP_CLI/ParseGithubUrlInput.php b/src/WP_CLI/ParseGithubUrlInput.php index 4abb7c71..14ded787 100644 --- a/src/WP_CLI/ParseGithubUrlInput.php +++ b/src/WP_CLI/ParseGithubUrlInput.php @@ -27,8 +27,11 @@ final class ParseGithubUrlInput { */ public function get_the_latest_github_version( $repo_slug ) { $api_url = sprintf( $this->github_releases_api_endpoint, $repo_slug ); + $token = getenv( 'GITHUB_TOKEN' ); - $response = \wp_remote_get( $api_url ); + $request_arguments = $token ? [ 'headers' => 'Authorization: Bearer ' . getenv( 'GITHUB_TOKEN' ) ] : []; + + $response = \wp_remote_get( $api_url, $request_arguments ); if ( \is_wp_error( $response ) ) { return $response; @@ -38,7 +41,10 @@ public function get_the_latest_github_version( $repo_slug ) { $decoded_body = json_decode( $body ); if ( wp_remote_retrieve_response_code( $response ) === \WP_Http::FORBIDDEN ) { - return new \WP_Error( \WP_Http::FORBIDDEN, $decoded_body->message . PHP_EOL . $decoded_body->documentation_url ); + return new \WP_Error( + \WP_Http::FORBIDDEN, + $this->build_rate_limiting_error_message( $decoded_body ) + ); } if ( null === $decoded_body ) { @@ -51,7 +57,10 @@ public function get_the_latest_github_version( $repo_slug ) { $latest_release = $decoded_body[0]; - return [ 'name' => $latest_release->name, 'url' => $this->get_asset_url_from_release( $latest_release ) ]; + return [ + 'name' => $latest_release->name, + 'url' => $this->get_asset_url_from_release( $latest_release ), + ]; } /** @@ -85,4 +94,15 @@ public function get_github_repo_from_url( $url ) { return isset( $matches[1] ) ? $matches[1] : null; } + + /** + * Build the error message we display in WP-CLI for the API Rate limiting error response. + * + * @param $decoded_body + * + * @return string + */ + private function build_rate_limiting_error_message( $decoded_body ) { + return $decoded_body->message . PHP_EOL . $decoded_body->documentation_url . PHP_EOL . 'In order to pass the token to WP-CLI, you need to use the GITHUB_TOKEN environment variable.'; + } }