Skip to content

Add plugin/theme download subcommands runnable before WordPress load#527

Draft
Copilot wants to merge 4 commits into
mainfrom
copilot/add-wp-plugin-download-command
Draft

Add plugin/theme download subcommands runnable before WordPress load#527
Copilot wants to merge 4 commits into
mainfrom
copilot/add-wp-plugin-download-command

Conversation

Copy link
Copy Markdown
Contributor

Copilot AI commented May 27, 2026

This PR adds wp plugin download and wp theme download for deploy/automation flows where WordPress is not loaded. The commands resolve package metadata via WpOrgApi and download the ZIP directly.

  • New before-load subcommands

    • Registered plugin download and theme download with when: before_wp_load.
    • Added dedicated command classes:
      • Plugin_Download_Command
      • Theme_Download_Command
  • Download behavior

    • Fetches extension info from WordPress.org API (get_plugin_info / get_theme_info).
    • Downloads ZIP to a local path (default: current directory).
    • Supports:
      • --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)
    • Validates slug before any filesystem operations.
    • Checks HTTP response status after download and cleans up partial files on failure.
  • Command metadata + coverage

    • Added new command entries to package command metadata in composer.json.
    • Added focused Behat feature coverage for both new commands in features/extension-download.feature:
      • Uses Given an empty directory (no WordPress needed, faster, and verifies the before-load guarantee)
      • Covers happy-path downloads, --version, --path, --force (with and without existing file), unknown slug errors, and non-existent version errors.
wp plugin download debug-bar
wp theme download twentytwelve

Copilot AI linked an issue May 27, 2026 that may be closed by this pull request
@github-actions github-actions Bot added command:plugin Related to 'plugin' command scope:distribution Related to distribution scope:testing Related to testing labels May 27, 2026
@codecov
Copy link
Copy Markdown

codecov Bot commented May 27, 2026

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 plugin/theme download subcommands runnable before WordPress load May 27, 2026
Copilot AI requested a review from swissspidy May 27, 2026 10:46
@swissspidy swissspidy requested a review from Copilot May 28, 2026 10:42
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

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 through WpOrgApi and write the zip with Utils\http_request().
  • Registration of plugin download and theme download (with before_wp_load) in extension-command.php, plus their addition to the command metadata in composer.json.
  • A new features/extension-download.feature with 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
Comment thread composer.json
Copilot AI and others added 2 commits May 28, 2026 11:04
…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>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

command:plugin Related to 'plugin' command scope:distribution Related to distribution scope:testing Related to testing

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Add wp plugin download command

3 participants