Add plugin/theme download subcommands runnable before WordPress load#527
Draft
Copilot wants to merge 4 commits into
Draft
Add plugin/theme download subcommands runnable before WordPress load#527Copilot wants to merge 4 commits into
plugin/theme download subcommands runnable before WordPress load#527Copilot wants to merge 4 commits into
Conversation
Codecov Report✅ All modified and coverable lines are covered by tests. 📢 Thoughts on this report? Let us know! |
Co-authored-by: swissspidy <841956+swissspidy@users.noreply.github.com>
Copilot
AI
changed the title
[WIP] Implement wp plugin download command without WordPress required
Add May 27, 2026
plugin/theme download subcommands runnable before WordPress load
Contributor
There was a problem hiding this comment.
Pull request overview
Adds two new WP-CLI subcommands — wp plugin download <slug> and wp theme download <slug> — that fetch a package zip from the WordPress.org repository directly via WpOrgApi, registered with when: before_wp_load so they can be used in deploy/automation flows where WordPress is not available. Both commands support --path, --version (including dev), --force, and --insecure.
Changes:
- Two new command classes (
Plugin_Download_Command,Theme_Download_Command) that resolve metadata throughWpOrgApiand write the zip withUtils\http_request(). - Registration of
plugin downloadandtheme download(withbefore_wp_load) inextension-command.php, plus their addition to the command metadata incomposer.json. - A new
features/extension-download.featurewith two happy-path scenarios.
Reviewed changes
Copilot reviewed 5 out of 5 changed files in this pull request and generated 9 comments.
Show a summary per file
| File | Description |
|---|---|
| src/Plugin_Download_Command.php | New global-namespace command class implementing wp plugin download. |
| src/Theme_Download_Command.php | New global-namespace command class implementing wp theme download. |
| extension-command.php | Requires the new class files and registers the two new subcommands with before_wp_load. |
| composer.json | Adds plugin download and theme download entries to the package command metadata. |
| features/extension-download.feature | Adds happy-path Behat scenarios for both new commands. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
Comment on lines
+76
to
+85
| if ( is_string( $requested ) && '' !== $requested && $requested !== $plugin_data['version'] ) { | ||
| $current_zip = basename( (string) Utils\parse_url( $download_url, PHP_URL_PATH ) ); | ||
| if ( 'dev' === $requested ) { | ||
| $download_url = str_replace( $current_zip, $slug . '.zip', $download_url ); | ||
| $version = 'Development Version'; | ||
| } else { | ||
| $download_url = str_replace( $current_zip, $slug . '.' . $requested . '.zip', $download_url ); | ||
| $version = $requested; | ||
| } | ||
| } |
| } else { | ||
| $download_url = str_replace( $current_zip, $slug . '.' . $requested . '.zip', $download_url ); | ||
| $version = $requested; | ||
| } |
Comment on lines
+100
to
+115
| try { | ||
| Utils\http_request( | ||
| 'GET', | ||
| $download_url, | ||
| null, | ||
| [], | ||
| [ | ||
| 'filename' => $download_file, | ||
| 'insecure' => (bool) $insecure, | ||
| ] | ||
| ); | ||
| } catch ( Exception $exception ) { | ||
| WP_CLI::error( $exception->getMessage() ); | ||
| } | ||
|
|
||
| WP_CLI::success( "Downloaded plugin package to {$download_file}" ); |
Comment on lines
+100
to
+115
| try { | ||
| Utils\http_request( | ||
| 'GET', | ||
| $download_url, | ||
| null, | ||
| [], | ||
| [ | ||
| 'filename' => $download_file, | ||
| 'insecure' => (bool) $insecure, | ||
| ] | ||
| ); | ||
| } catch ( Exception $exception ) { | ||
| WP_CLI::error( $exception->getMessage() ); | ||
| } | ||
|
|
||
| WP_CLI::success( "Downloaded theme package to {$download_file}" ); |
Comment on lines
+69
to
+72
| if ( ! is_array( $plugin_data ) || empty( $plugin_data['download_link'] ) || empty( $plugin_data['version'] ) ) { | ||
| WP_CLI::error( "The '{$slug}' plugin could not be found." ); | ||
| } | ||
|
|
Comment on lines
+43
to
+61
| public function __invoke( $args, $assoc_args ) { | ||
| $slug = (string) $args[0]; | ||
| $insecure = Utils\get_flag_value( $assoc_args, 'insecure', false ); | ||
| $force = Utils\get_flag_value( $assoc_args, 'force', false ); | ||
| $requested = Utils\get_flag_value( $assoc_args, 'version', null ); | ||
| $download_dir = Utils\get_flag_value( $assoc_args, 'path', getcwd() ); | ||
| if ( '' === $slug ) { | ||
| WP_CLI::error( 'Please provide a plugin slug.' ); | ||
| } | ||
|
|
||
| if ( ! is_dir( $download_dir ) ) { | ||
| if ( ! @mkdir( $download_dir, 0755, true ) ) { | ||
| WP_CLI::error( "Failed to create directory '{$download_dir}'." ); | ||
| } | ||
| } | ||
|
|
||
| if ( ! is_writable( $download_dir ) ) { | ||
| WP_CLI::error( "'{$download_dir}' is not writable by current user." ); | ||
| } |
Comment on lines
+43
to
+61
| public function __invoke( $args, $assoc_args ) { | ||
| $slug = (string) $args[0]; | ||
| $insecure = Utils\get_flag_value( $assoc_args, 'insecure', false ); | ||
| $force = Utils\get_flag_value( $assoc_args, 'force', false ); | ||
| $requested = Utils\get_flag_value( $assoc_args, 'version', null ); | ||
| $download_dir = Utils\get_flag_value( $assoc_args, 'path', getcwd() ); | ||
| if ( '' === $slug ) { | ||
| WP_CLI::error( 'Please provide a theme slug.' ); | ||
| } | ||
|
|
||
| if ( ! is_dir( $download_dir ) ) { | ||
| if ( ! @mkdir( $download_dir, 0755, true ) ) { | ||
| WP_CLI::error( "Failed to create directory '{$download_dir}'." ); | ||
| } | ||
| } | ||
|
|
||
| if ( ! is_writable( $download_dir ) ) { | ||
| WP_CLI::error( "'{$download_dir}' is not writable by current user." ); | ||
| } |
Comment on lines
+1
to
+33
| Feature: Download WordPress.org extensions without loading WordPress | ||
|
|
||
| Scenario: Downloading a plugin package works before WordPress is loaded | ||
| Given a WP install | ||
|
|
||
| When I run `wp plugin download debug-bar --skip-wordpress` | ||
| Then STDOUT should contain: | ||
| """ | ||
| Downloading debug-bar | ||
| """ | ||
| And STDOUT should contain: | ||
| """ | ||
| Success: Downloaded plugin package to | ||
| """ | ||
| And save STDOUT 'Success: Downloaded plugin package to (.+)' as {DOWNLOADED_PLUGIN} | ||
| And the {DOWNLOADED_PLUGIN} file should exist | ||
| And STDERR should be empty | ||
|
|
||
| Scenario: Downloading a theme package works before WordPress is loaded | ||
| Given a WP install | ||
|
|
||
| When I run `wp theme download twentytwelve --skip-wordpress` | ||
| Then STDOUT should contain: | ||
| """ | ||
| Downloading twentytwelve | ||
| """ | ||
| And STDOUT should contain: | ||
| """ | ||
| Success: Downloaded theme package to | ||
| """ | ||
| And save STDOUT 'Success: Downloaded theme package to (.+)' as {DOWNLOADED_THEME} | ||
| And the {DOWNLOADED_THEME} file should exist | ||
| And STDERR should be empty |
…atus checks, and feature tests - Move empty-slug check to top of __invoke() before any filesystem ops - Add HEAD request validation for specific version URLs before downloading - Check HTTP response status code after download and clean up on failure - Update feature file: use 'Given an empty directory' instead of WP install, add scenarios for --version, --path, --force, and error paths - Add wp-cli's phpstan scan-files.php to resolve Requests_Response type Co-authored-by: swissspidy <841956+swissspidy@users.noreply.github.com>
Co-authored-by: swissspidy <841956+swissspidy@users.noreply.github.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
This PR adds
wp plugin downloadandwp theme downloadfor deploy/automation flows where WordPress is not loaded. The commands resolve package metadata viaWpOrgApiand download the ZIP directly.New before-load subcommands
plugin downloadandtheme downloadwithwhen: before_wp_load.Plugin_Download_CommandTheme_Download_CommandDownload behavior
get_plugin_info/get_theme_info).--path=(target directory)--version=(builds version-specific package URL, validated via HEAD request before download)--force(overwrite existing destination file)--insecure(TLS fallback behavior aligned with existing command patterns)Command metadata + coverage
composer.json.features/extension-download.feature:Given an empty directory(no WordPress needed, faster, and verifies the before-load guarantee)--version,--path,--force(with and without existing file), unknown slug errors, and non-existent version errors.