Skip to content
New issue

Have a question about this project? # for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “#”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? # to your account

Add support GitHub releases/latest on installation #386

Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 15 additions & 0 deletions features/plugin-install-github-latest.feature
Original file line number Diff line number Diff line change
@@ -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.
"""
Copy link
Member

Choose a reason for hiding this comment

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

Can you include a test case for the non-happy path too?

22 changes: 21 additions & 1 deletion src/WP_CLI/CommandWithUpgrade.php
Original file line number Diff line number Diff line change
Expand Up @@ -145,7 +145,6 @@ private function show_legend( $items ) {
}

public function install( $args, $assoc_args ) {

$successes = 0;
$errors = 0;
foreach ( $args as $slug ) {
Expand All @@ -159,6 +158,27 @@ 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 );

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'];

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.
Expand Down
108 changes: 108 additions & 0 deletions src/WP_CLI/ParseGithubUrlInput.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
<?php

namespace WP_CLI;

Check failure on line 3 in src/WP_CLI/ParseGithubUrlInput.php

View workflow job for this annotation

GitHub Actions / code-quality / PHPCS

Namespaces declared by a theme/plugin should start with the theme/plugin prefix. Found: "WP_CLI".

final class ParseGithubUrlInput {
Copy link
Member

Choose a reason for hiding this comment

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

Can we move the methods on this class to the existing CommandWithUpgrade class? I don't think we want to introduce it as a class others can use, etc.


/**
* The GitHub Releases public api endpoint.
*
* @var string
*/
private $github_releases_api_endpoint = 'https://api.github.com/repos/%s/releases';

/**
* The GitHub latest release url format.
*
* @var string
*/
private $github_latest_release_url = '/^https:\/\/github\.com\/(.*)\/releases\/latest\/?$/';

/**
* Get the latest package version based on a given repo slug.
*
* @param string $repo_slug
*
* @return array{ name: string, url: string }|\WP_Error
*/
public function get_the_latest_github_version( $repo_slug ) {
$api_url = sprintf( $this->github_releases_api_endpoint, $repo_slug );
$token = getenv( 'GITHUB_TOKEN' );

$request_arguments = $token ? [ 'headers' => 'Authorization: Bearer ' . getenv( 'GITHUB_TOKEN' ) ] : [];

$response = \wp_remote_get( $api_url, $request_arguments );

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,
$this->build_rate_limiting_error_message( $decoded_body )
);
}

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 ) {
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
public function get_github_repo_from_url( $url ) {
public function get_github_repo_from_releases_url( $url ) {

Should we include 'releases' in this method name to make it more specific?

preg_match( $this->github_latest_release_url, $url, $matches );

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.';
}
}
Loading