Version 1.0.0 released

Woohoo!

Over the course of 5+ years, hundreds of contributors have worked to bring you WP-CLI v1.0.0, which I’m proud to announce today.

This release represents a level of maturity few open source projects achieve. It also marks a moment of transition. The WP-CLI project will shift its focus to the WP-CLI package ecosystem, where it will enable innovation by building and encouraging new features as standalone packages. We hope this approach will promote faster iteration and more creativity, and more sustainably distribute the maintenance burden. As these community packages find success, we’ll bring their learnings back into WP-CLI, alongside bug fixes and minor enhancements.

Now that the issue backlog is down to zero, I’m personally looking forward to getting more ideas cooking for runcommand, my own WP-CLI innovation studio.

Headed to Philly this week? I’ll be at Post Status Publish and WCUS (although only until mid-afternoon Friday). Say hello – I’m @danielbachhuber on Twitter.

On with the show…

Introducing WP_CLI::runcommand()

WP_CLI::runcommand() (doc) is the new best way to run WP-CLI commands from within your WP-CLI command. It’s as though WP_CLI::run_command() and WP_CLI::launch_self() grew up, married, and had the perfect child.

With WP_CLI::runcommand(), you can:

  • Launch a new child process (default), or reuse the existing process.
  • Optionally prevent the process from exiting on error.
  • Return STDOUT generated by the command, or all command execution details (STDOUT, STDERR, return_code) as an object.
  • Optionally parse captured STDOUT as JSON.

Relevant pull requests include: #3605, #3619, #3621.

Breaking change: Uses return code 1 when batch operation partially fails

Some commands support performing the same operation against multiple resources (e.g. updating two or more plugins with wp plugin update akismet hello). Previously, if one of the operations failed (e.g. a plugin update failed to be downloaded), WP-CLI would display a warning, continue on, and exit with return code 0. Beginning in v1.0.0, WP-CLI uses return code 1 when one or more operations fails.

See this issue for more background and rationale.

Affected commands include:

  • wp media (regenerate|import)
  • wp menu delete
  • wp menu item delete
  • wp plugin (install|activate|update|toggle|deactivate|uninstall|delete)
  • wp super-admin add
  • wp theme (install|update)
  • wp term delete
  • wp widget (delete|deactivate|reset)

Use WP_CLIUtilsreport_batch_operation_results() (doc) in your custom WP-CLI commands to more easily support this behavior.

Relevant pull requests include: #3584, #3583, #3582, #3585, #3586, #3588.

Everything else in 1.0.0

New commands:

  • wp package update – Update all installed WP-CLI packages to their latest version.
  • wp scaffold theme-tests – Scaffold PHPUnit tests for themes.

Command improvements:

  • wp cache type:
    • Supports WP LCache as a cache type [#3504].
  • wp cli aliases:
    • Adds alias to subcommand for easier access [#3512].
  • wp cli update:
    • Verifies release hash when updating [#3515].
    • No longer requires --allow--root flag when running as root [#3576].
  • wp core config:
    • Ensures WordPress Coding Standards are applied to the generated wp-config [#3496].
  • wp core (install|multisite-install)
    • Defaults to a randomly generated password for --admin_password=<password>, which is now optional [#3535, #3573].
  • wp core language (install|update):
    • Caches language pack downloads [#3595].
  • wp core update:
    • Uses global namespace for WP_Error in CoreUpgrader class [#3593].
  • wp core update-db:
    • Sets the WP_INSTALLING constant for the update process [#3503].
  • wp package install:
    • Uses supplied version in package composer.json, instead of “dev-master” [#3519].
    • Adds WP-CLI version to package manager’s composer.json, to gracefully handle WP-CLI version constraints [#3603].
  • wp package list:
    • Indicates when a package has an update available [#3611, #3612].
  • wp post delete:
    • Correctly indicates revisions are deleted immediately in success message [#3524].
  • wp scaffold plugin:
    • Ignores distribution archive files in .gitignore and .distignore [#3520].
    • Ignores circle.yml, .gitlab-ci.yml and behat.yml in .distignore [#3599].
  • wp scaffold plugin-tests:
    • Checks out the data directory in install-wp-tests.sh to prevent notices in WP 4.7 [#3571].

Framework enhancements:

  • Updates Composer-based dependencies to latest [#3498, #3525].
  • Introduces --prompt=<assoc> to prompt for specific associative args, which lets users avoid exposing secure data in bash history [#3531].
  • Adds support for the version of PHP that comes with Cygwin [#3591].

Contributors to this release (pull requests, documentation, and package authors): abea, anttiviljami, cobyan, danielbachhuber, diggy, ernilambar, franz-josef-kaiser, greatislander, itspriddle, miya0001, mmcev106, mopquill, ocean90, pj-dave, pkarjala, richardbuff, sommarnatt, szepeviktor, torounit

You can browse the full list of resolved issues on Github.

Version 0.25.0 released

Happy release day!

Today, I’m excited to bring you WP-CLI v0.25.0. Check out the newly published roadmap for details on upcoming releases and product focus (hint: there’s a future where WP-CLI no longer supports PHP 5.3).

Let’s dive in.

Compatibility with WordPress 4.7

WordPress 4.7 introduces a new WP_Hook implementation for registering and executing actions and filters. Because WP-CLI has its own WP_CLI::add_wp_hook() that was erroneously accessing the $wp_filter global even when the add_filter() function was available, WP-CLI could fatal in certain circumstances. WP-CLI now appropriately calls add_filter() when it’s available.

Importantly, due to the nature of these changes, WP-CLI versions prior to 0.25.0 will be incompatible with WordPress 4.7.

Inspect the change in this pull request.

New packages in the Package Index

The WP-CLI community has been quite active in creating new tools for you to use (and contribute back to):

Install any one of these with wp package install <package-name> (where <package-name> is typically the <user>/<repo>). When you do, go say thanks to the author!

More ways to install WP-CLI packages

Although we’d love to see your package listed in the Package Index, we realize there are reasons you might not be able to do so. wp package install now supports installing an arbitrary Git URL [#3482], .zip file [#3485], or directory path [#3484] as a package.

$ wp package install git@github.com:runcommand/doctor.git
$ wp package install https://github.com/runcommand/doctor.zip
$ wp package install doctor

(doctor is the second premium WP-CLI command from runcommand)

It’s worth noting Composer’s behavior is slightly different for each package type:

  • Git URLs are treated as VCS repositories, and cloned to ~/.wp-cli/packages/vendor.
  • ZIP archives (remote and local) are extracted to ~/.wp-cli/packages/local, and added as path repositories.
  • Local directory paths are added as path repositories, which means Composer creates a symlink to the existing directory path. If the directory you’ve provided is removed, then the installation will break.

Everything else in 0.25.0

New commands:

  • wp db check – Runs mysqlcheck with the default --check option [#3332].
  • wp site option * – CRUD commands for managing WordPress site options [#3386].
  • wp user session * – CRUD commands for managing user sessions [#3307].

Command improvements:

  • wp cli update:
    • Introduces --stable to install or reinstall the latest stable version [#3430].
  • wp core config:
    • Adds comments to generated wp-config.php to better match the one provided by WordPress core [#3312].
  • wp core download:
    • Preserves case for --version argument to properly handle release candidates [#3283].
    • Ensures wp core download --version=latest produces correctly-versioned cache key [#3467].
  • wp core language update:
    • Fixes strict standard error about variable reference [#3380].
    • Permits updating language packs even when en_US is set as locale [#3397].
  • wp core multisite-(install|convert):
    • Warns when multisite constants can’t be inserted into wp-config.php, instead of erroneously inserting at the end [#3272].
    • Includes adequate vertical spacing around inserted constants [#3267].
  • wp core update-db:
    • Ensures wp core update-db --network --dry-run is actually dry [#3347].
  • wp core version:
    • Displays default core language in wp core version --extra [#3221].
  • wp import:
    • Indicates current file in WXR import progress indicator to communicate the total count is of the current file, not all files [#3270].
  • wp media regenerate:
    • Adds a simple progress indicator [#3407].
  • wp option list:
    • Adds --no-transients flag to ignore transients [#3452].
    • Adds --exclude=<exclude> argument to list options excluding a specific pattern [#3455].
  • wp package install
    • Displays package dependency details when installing a package with a dependency [#3418, #3425].
  • wp package uninstall
    • Removes a package’s dependencies when the package is removed [#3343].
    • Properly assigns $composer_backup when uninstalling [#3399].
  • wp plugin install:
    • Removes branch names from directories created for Github-based ZIPs [#3314, #3451].
  • wp scaffold plugin-tests:
    • Uses PHP version specific to Trusty on CircleCI [#3359].
    • Uses correct default user for MySQL on CircleCI [#3457].
    • Uses the latest version of PHPUnit on Travis, depending on PHP version [#3463].
    • Adds WordPress Coding Standards to newly-scaffolded plugins [#3472].
  • wp search-replace:
    • Ensures tables are quoted to support all permitted characters [#3318].
    • Prevents error notice when export_insert_size isn’t defined [#3357].
    • Fails back to PHP if SQL triggers an error for some reason [#3387].
  • wp server:
    • Supports passing a custom .ini file to configure the server [#3330].
  • wp site create:
    • Use get_blog_details() for the site URL when creating a new site to ensure the correct URL is displayed [#3416].
  • wp site empty:
    • Ensures the entire uploads directory is empty [#3400].
  • wp theme install:
    • Correctly installs parent theme when installing a child theme [#3301].
  • wp transient:
    • Consolidates wp transient delete-all and wp transient delete-expired to flags of wp transient delete [#3389].
  • wp user create:
    • Prevents email notifications when users are created because email notifications should only be sent when --send-email is provided [#3331].

Framework enhancements:

  • Updates Composer-based dependencies to latest [#3257, #3429, #3460, #3468].
  • Properly handles registering an instantiated object as a command [#3269].
  • Splits the ProcessRun class out to its own file [#3377, #3422].
  • Permits running test suite with WP_VERSION env variable [#3383, #3392].
  • Prevents error notice when using Utilsget_named_sem_ver() with WP versions [#3404].
  • Fixes fatal error for failed early database connection by handling dead_db() error on nocache_headers filter [#3440].
  • Assigns a default $_SERVER['SERVER_NAME'] to prevent uncaught exception when wp_mail() is used [#3449].
  • Ignores url: in wp-cli.yml when alias is used, because aliases completely override user, url, path, ssh, and http [#3450].
  • Warns when WP_CLI::launch() ends up with return_code=-1, which could be caused by a custom compiled version of PHP that uses the --enable-sigchild option [#3458].
  • Provides more verbosity in wp_die() handler to give the end user more detail when a database connection fails [#3459].
  • Supports passing arguments to WP_CLI::do_hook() [#3470].
  • Logs the current alias when executing an alias group [#3471].
  • Only checks options for a positional argument when a value is present [#3481].
  • Variety of bash completion improvements [#3490, #3491, #3492].

Bug fixes across the board:

  • Defines all requisite dependencies for PHP 7 on Debian-based systems [#3208].
  • Ensures site --site_id= -> site --network_id= backwards compat shim only affects wp site create [#3227].
  • Catches exceptions thrown by RecursiveDirectoryIterator when verifying core checksums [#3266].
  • Passes slashed data in meta commands [#3274].
  • Ensures appropriate WP-CLI package index URL is used in the composer.json [#3276].
  • Corrects reference of WP_CLI to use global namespace in WP_CLIUtilsget_temp_dir() [#3369].

Contributors to this release (pull requests, documentation, and package authors): 2ndkauboy, aaemnnosttv, alessandrotesoro, anhskohbo, balbuf, BeAPI, binarygary, bradp, brightoak, danielbachhuber, danilomaccioni, diggy, getshifter, eriktorsner, ernilambar, fisele, grappler, guillaumemolter, iandunn, johnbillion, jorgeatorres, kouratoras, markri, mattgrshaw, miya0001, mustafauysal, nyordanov, ocean90, petenelson, polevaultweb, pressbooks, rahulsprajapati, runcommand, rxnlabs, shulard, swissspidy, szepeviktor, taianunes, tnorthcutt, trendwerk, trepmal, veganista, welaika

You can browse the full list of resolved issues on Github.

Two more major releases in 2016

Just a heads up there will be two more major releases in 2016:

  • v0.25.0 will be released on Tuesday, October 25th.
  • v1.0.0 will be released on Tuesday, November 29th.

Want to help out? We’ll be focusing on framework refinement; in particular, there are a number of improvements that can be made to the package management experience.

Version 0.24.1 released

Curious as to why you’re getting a warning message when updating an option? Or why --skip-plugins=<plugin-slug> skips all of your network-activated plugins?

These two bugs, regressions from v0.24.0, are fixed in v0.24.1.

You can browse the full list of resolved issues on Github.

Contributors to this release: danielbachhuber

Support policy updates

To help keep the maintenance burden manageable by volunteers, our support policies have changed: Github issues are now reserved for bug reports and enhancements of existing commands; they are no longer a venue for general support questions.

Have a question about something related to WP-CLI? You might find an answer in one of the following resources:

If you can’t find your answer at one of those links, join the #cli channel on the WordPress.org Slack organization to see if a community member might have an answer for you. Professional users may also consider runcommand for premium support.

Using WP-CLI professionally? We encourage you to purchase support from a regular WP-CLI contributor. Libre != gratis; the open source license grants you the freedom to use and modify, but not commitments of other people’s time. Participating responsibly in the economic ecosystem around an open source tool you depend upon helps to ensure the long-term health of the project.

Version 0.24.0 released

As I mentioned in my WordCamp Europe talk:

Just like WordPress has plugins, the future of WP-CLI is packages of commands. For this future, I’m trying to proactively solve the problems WordPress has with plugins:

  • Where WordPress plugins are considered second-class to what’s included in core, I’d like WP-CLI packages to be considered first-class citizens amongst the commands in WP-CLI.
  • All too often, WordPress plugins have just one author. I’d like for each WP-CLI package to have two or three active maintainers.

In this model, WP-CLI becomes the common interface, and supporting application layer, to a rich ecosystem of features. Doing so opens more frontiers for innovation, which leads to a greater selection of ideas to choose from. And because more people are involved in authoring packages, WP-CLI benefits from a larger contributor pool.

With this model, my focus shifts towards designing a world-class experience for WP-CLI package authorship. Read through the commands cookbook for a thorough introduction to creating a WP-CLI command. Check out wp scaffold package [repo] for the easiest way to generate the boilerplate for your new WP-CLI package. Weigh in with your thoughts on how we should evolve the WP-CLI package index. And follow @runcommand as I explore commercializing WP-CLI products and services — I hope that runcommand is just the first of several WP-CLI-based businesses.

One last ask: if you care about the WP-CLI release cycle, or dependencies and backwards compatibility, please let me know how often you think WP-CLI should be released.

Let’s get on with the show. Use wp cli update to install v0.24.0, representing 449 resolved issues and pull requests. Here’s what’s new.

Forked wp-settings.php no more

Every application has a bootstrap file which loads all of the requisite utilities needed to serve a request. In WordPress, this is called wp-settings.php.

Since v0.8.0 [#261], WP-CLI has used a forked version of this bootstrap file, called wp-settings-cli.php, to give it more control over the load process, providing features like --skip-plugins. But, because WordPress can require new files from wp-settings.php, maintaining a forked version has the unfortunate side effect of WP-CLI regularly breaking when a new version of WordPress is released.

Thanks to coordinated changes in the WordPress project, WP-CLI v0.24.0 returns to loading wp-settings.php for WordPress 4.6 and higher [#2278]. Doing so should make WP-CLI more future proof against new versions of WordPress.

More documentation in more languages

Thanks to tireless efforts by a solid group of contributors, WP-CLI now has more documentation in more languages.

Want to get involved with WP-CLI’s documentation? Check out the Github issues labeled “scope:documentation”.

Effortlessly use WP-CLI against any WordPress install

WP-CLI aliases are shortcuts you register in your wp-cli.yml or config.yml to effortlessly run commands against any WordPress install.

For instance, if I’m working locally on the runcommand theme, have registered a new rewrite rule, and need to flush rewrites inside my Vagrant-based virtual machine, I can run:

$ wp @dev rewrite flush
Success: Rewrite rules flushed.

Then, once the code goes to production, I can run:

$ wp @prod rewrite flush
Success: Rewrite rules flushed.

Look ma! No more SSH’ing into machines, changing directories, and generally spending a full minute to get to a given WordPress install.

Additionally, alias groups let you register groups of aliases. If I want to run a command against both runcommand WordPress instances, I can use @both:

$ wp @both core check-update
Success: WordPress is at the latest version.
Success: WordPress is at the latest version.

Aliases can be registered in your project’s wp-cli.yml file, or your user’s global ~/.wp-cli/config.yml file:

@prod:
  ssh: runcommand@runcommand.io~/webapps/production
@dev:
  ssh: vagrant@192.168.50.10/srv/www/runcommand.dev
@both:
  - @prod
  - @dev

But wait, what’s the ‘ssh’ in there?

WP-CLI now natively supports a --ssh=<host> global parameter for running a command against a remote WordPress install. Many thanks to XWP and their community for paving the way with WP-CLI SSH.

Under the hood, WP-CLI proxies commands to the ssh executable, which then passes them to WP-CLI installed on the remote machine. Your syntax for -ssh=<host> can be any of the following:

  • Just the host (e.g. wp --ssh=runcommand.io), which means the user will be inferred from your current system user, and the path will be the SSH user’s home directory.
  • The user and the host (e.g. wp --ssh=runcommand@runcommand.io).
  • The user, the host, and the path to the WordPress install (e.g. wp --ssh=runcommand@runcommand.io~/webapps/production). The path comes immediately after the TLD of the host.

Or, if you use a ~/.ssh/config, <host> can be any host alias stored in the SSH config (e.g. wp --ssh=rc for me).

Note you do need a copy of WP-CLI on the remote server, accessible as wp. Futhermore, --ssh=<host> won’t load your .bash_profile if you have a shell alias defined, or are extending the $PATH environment variable. If this affects you, here’s a more thorough explanation of how you can make wp accessible.

Relevant pull requests for aliases and SSH support include: #2755, #2974, #3012, #3013, #3014, #3016, #3026, #3040, #3070, #3093, #3100, #3117, #3134, #3135, #3145, #3161, #3180.

Everything else in 0.24.0

Command improvements:

  • Adds a newline when using the wp shell interactive prompt [#2601, #2659].
  • Improves formatting of scaffolded plugins [#2588, #2598].
  • Introduces --format=ids to wp (*) generate commands for easier chaining [2622].
  • Adds term recount command for trigger a recount of taxonomy terms assigned to posts [#2625, #2628].
  • Normalizes plugin / theme version numbers and header formatting when scaffolding [#2644].
  • Introduces --due-now to run all cron events due or overdue [#2658].
  • Permits wp cron (event|schedule) list and wp option list to output a single field [#2657, #3033].
  • Adds field filtering in cron event list command [#2674].
  • Includes a .distignore file when scaffolding a new plugin, to define files and folders excluded from distributions [#2697, #2756, #3042, #3088].
  • Displays a summary success message when using --dry-run with wp search-replace [#2740].
  • Scaffolds plugin based on supported WordPress version [#2751].
  • Applies extended insert format to search-replace SQL export, for a substantial performance boost [#2745].
  • Warns with wp core verify-checksums when extra files exist in wp-admin or wp-includes [#2638].
  • Supports --format=<format> argument for wp cap list, wp user list-caps, and wp super-admin list [#2851, #2961, and #2949].
  • Accepts multiple term IDs with wp term url [#2865].
  • Supports PHP 5.5 Memcache extension when checking cache type [#2945].
  • Uses WP_CLI::warning() when a theme is already active, to make behavior more consistent with plugin activation [#3015].
  • Adds --porcelain flag to wp db export [#3032].
  • Allow the author field to be selected in wp theme list --fields=<field> [#3043].
  • Introduces wp widget reset <sidebar>, for removing all widgets from a sidebar and placing them in the inactive sidebar [#3077].
  • Supports ‘trunk’ and ‘nightly’ version arguments for wp core download [#3127].
  • Adds verbosity to wp role reset [#3132, #3141].
  • Adds --include-columns=<columns> argument to wp search-replace [#3142].
  • Adds --ci=<provider> argument for wp plugin test scaffold, which supports ‘travis’, ‘circle’, or ‘gitlab’ [#3144, #3163].

Framework enhancements:

  • Uses is_callable() in WP_CLI::add_command(), instead of custom logic [#2595].
  • Introduces CompositeCommand->remove_subcommand(), and modifies the bootstrap process to always register core commands [#2629].
  • Runs before_invoke and after_invoke callbacks on subcommands, such that you can hook into immediately before and after subcommand execution [#2647, #2686].
  • Introduces --debug=<group> to limit debug output to a particular group of debug calls [#2648].
  • Interacts with the Package Index over SSL [#2720].
  • Supports CSV with spaces when using --fields=<fields> [#2750].
  • Disables WP cron when ALTERNATE_WP_CRON is defined [#3118].
  • Supports positional arguments defined in wp-cli.yml [#3120].
  • Introduces WP_CLI_STRICT_ARGS_MODE for dealing with arg ambiguity [#3128].
  • Registers --http=<url> global parameter for use with RESTful WP-CLI [#3130].
  • Introduces WP_CLI::add_wp_hook(), for adding actions and filters when you don’t yet have access to actions and filters [#3195].
  • Increases minimum supported PHP version to 5.3.29 [#2672].

Bug fixes across the board:

  • Mitigates a DateTime fatal when instantiating the Composer object [#2607].
  • Squashes wp export notice about skip_comments [#2620].
  • Avoids regex to fix greedy parsing of parameter arguments [#2587, #2717].
  • Ensures default and options are used when supplied as arg args [#2741].
  • Considers image sizes missing when using wp media regenerate and sizes doesn’t have registered sizes [#2645].
  • Catches WP_Error from translations_api() [#2671, #3179].
  • Doesn’t erroneously try to (de)activate plugins with --all flag [#2692].
  • Defines DOING_CRON before WordPress is loaded when running wp cron event run [#2691].
  • Only attempts to use add_user_to_blog() on multisite when importing users from CSV [#2690].
  • Fixes listing user meta associated with a given username [#2700].
  • Differentiates output when moving comments to trash from output when deleting comments [#2701].
  • Prevents runaway memory usage from wp export by clearing object cache after each file [#2716].
  • Ignores ambigious empty plugin and theme slugs when installing [#2715].
  • Takes all digits when running commands that use the comment id [#2714, #2901].
  • Only displays packages directory path when it exists [#2773].
  • Bails early in theme commands if theme is broken or has error [#2798].
  • Displays error if theme directory exists but is erred; permits force install [#2821].
  • Fixes PHP notice when installing a child theme, and running wp theme status [#2976, #3047].
  • Ensures YAML formatter handles objects and --fields=<fields> arg [#3060].
  • Fixes exception in wp menu list if --format=ids [#3075].
  • Populates recently active plugins list when deactivating a plugin [#3068].
  • Respects wp-cli.yml default values when applying argument defaults [#3111].
  • Calls wp_slash() on data passed to post, comment, term, and user commands [#3156, #3157, #3158, #3159, #3167, #3173].
  • Appropriately lists duplicated cron events [#3175].

Contributors to this release: andyexeter, bordoni, danielbachhuber, diggy, enrico-sorcinelli, ernilambar, geo4orce, gedex, gilbitron, hideokamoto, apertureless, JRGould, johnbillion, kkoppenhaver, kouratoras, markjaquith, miya0001, mustafauysal, NateWr, Nikschavan, ocean90, petenelson, phh, rachelbaker, PatelUtkarsh, PeterDaveHello, robhenley rodrigoprimo, roelveldhuizen, ShinichiNishikawa, shulard, stephenharris, stevenkword, swissspidy, taianunes, villevuor, voldemortensen, wesm87, 8bitodyssey

You can browse the full list of resolved issues on Github.

Back to work!

RESTful WP-CLI – The final update?

Last November, I published a Kickstarter, and was completely blown away by the support. This month, the funding ran out, so I thought I’d post one last RESTful WP-CLI update.

Actually, the story doesn’t end here. I’m writing a massive retrospective post about using Kickstarter to fund open source, so keep an eye out for that. Also, WP-CLI v0.24.0 is due out a week from now, July 27th, and it’s looking to be the largest release ever. When you do a Kickstarter, it’s really just the beginning of something bigger.

Enough with the superlatives, let’s dive into some new features. Remember: RESTful WP-CLI features require under the hood changes to WP-CLI. You’ll want to wp cli update --nightly to play with this new functionality locally. Once you’ve done so, you can wp package install wp-cli/restful to install the latest.

Effortlessly use WP-CLI against any WordPress install

WP-CLI aliases are shortcuts you register in your wp-cli.yml or config.yml to effortlessly run commands against any WordPress install.

For instance, if I’m working locally on the runcommand theme, have registered a new rewrite rule, and need to flush rewrites inside my Vagrant-based virtual machine, I can run:

$ wp @dev rewrite flush
Success: Rewrite rules flushed.

Then, once the code goes to production, I can run:

$ wp @prod rewrite flush
Success: Rewrite rules flushed.

Look ma! No more SSH’ing into machines, changing directories, and generally spending a full minute to get to a given WordPress install.

Additionally, alias groups let you register groups of aliases. If I want to run a command against both runcommand WordPress instances, I can use @both:

$ wp @both core check-update
Success: WordPress is at the latest version.
Success: WordPress is at the latest version.

Aliases can be registered in your project’s wp-cli.yml file, or your user’s global ~/.wp-cli/config.yml file:

@prod:
  ssh: runcommand@runcommand.io~/webapps/production
@dev:
  ssh: vagrant@192.168.50.10/srv/www/runcommand.dev
@both:
  - @prod
  - @dev

But wait, what’s the ‘ssh’ in there?

WP-CLI now natively supports a --ssh=<host> global parameter for running a command against a remote WordPress install. Many thanks to XWP and their community for paving the way with WP-CLI SSH.

Under the hood, WP-CLI proxies commands to the ssh executable, which then passes them to WP-CLI installed on the remote machine. Your syntax for -ssh=<host> can be any of the following:

  • Just the host (e.g. wp --ssh=runcommand.io), which means the user will be inferred from your current system user, and the path will be the SSH user’s home directory.
  • The user and the host (e.g. wp --ssh=runcommand@runcommand.io).
  • The user, the host, and the path to the WordPress install (e.g. wp --ssh=runcommand@runcommand.io~/webapps/production). The path comes immediately after the TLD of the host.

Or, if you use a ~/.ssh/config, <host> can be any host alias stored in the SSH config (e.g. wp --ssh=rc for me).

Note you do need a copy of WP-CLI on the remote server, accessible as wp. Futhermore, --ssh=<host> won’t load your .bash_profile if you have a shell alias defined, or are extending the $PATH environment variable. If this affects you, here’s a more thorough explanation of how you can make wp accessible.

RESTful WP-CLI v0.2.0 and beyond

Today marks the release of RESTful WP-CLI v0.2.0. Among 43 closed issues and pull requests, I’d like to highlight two new features.

First, use wp rest (post|user|comment|*) generate to create an arbitrary number of any resource:

$ wp @wpdev rest post generate --count=50 --title="Test Post"
Generating items  100% [==============================================] 0:01 / 0:02

When working on a site locally, you often need dummy content to work with. There are a myriad of ways custom post types can store data in the database though, so generating dummy content can be a painstaking process. Because the WP REST API represents a layer of abstraction between the client (e.g. WP-CLI in this case) and the database, it’s much easier to produce a general purpose content generation command.

In the future, I’d love to see dummy data generated for each field based on the resource schema.

Second, use wp rest (post|user|comment|*) diff to compare resources between two enviroments:

# "command" isn't a typo in this example; "command" is a content type expressed through the WP REST API on runcommand.io
$ wp @dev rest command diff @prod find-unused-themes --fields=title
(-) http://runcommand.dev/api/ (+) https://runcommand.io/api/
  command:
  + title: find-unused-themes

When working with multiple WordPress environments, you may want to know how these environments differ. Because the WP REST API represents a higher-level abstraction on top of WordPress, computing the difference between two environments becomes a matter of fetching the data and producing a comparison.

There are a number of ways the diff command could be improved, so consider this implementation to be the prototype.

What’s next?

More immediately, I’d like to start looking at how well RESTful WP-CLI works with plugins and themes. If you’ve written custom endpoints for the WP REST API, please weigh in on this Github issue so I can check it out.

Ultimately, the goal is for wp rest post to replace wp post, but there are many months between here and there. In this future where WP-CLI packages are first-class citizens amongst the commands in WP-CLI core, RESTful WP-CLI gets to serve as a testbed for figuring out how that actually works. We shall see, we shall see.

As always, thanks for your support!

The future of WP-CLI

As you might be aware, WP-CLI is an indispensable tool for many individuals and companies. We very much appreciate our community of users (although we have no idea how many of you there actually are), and want to ensure our relationship is strong for the years to come.

Just over a month ago, I started a Github issue with this question:

how do I reduce WP-CLI’s bus factor, and more generally lay a foundation for WP-CLI’s long-term organizational stability?

Although things seem to be going reasonably well now, I want to make sure we’re taking steps towards ensuring the long-term continuity of the project. Today, Andreas (WP-CLI’s founder) and I sat down for breakfast to discuss our options and how we want to proceed.

Based on our conversation, we think the most important task is to reduce WP-CLI’s bus factor and onboard one or more additional maintainers to focus on a few non-code roles:

  • Documentation – Help ensure WP-CLI’s documentation is world-class.
  • Marketing / community management – Help spread the word about WP-CLI.
  • Support – Help WP-CLI users where they’re asking questions, let it be Github, WordPress.org, Stack Exchange, or elsewhere.

Contributions in these areas will always be welcome. Maintainership is a longer-term commitment to the project, focused on ongoing improvements to the specific area. WP-CLI expects its maintainers to commit a few hours per week to the project.

At this time, we don’t have plans to establish a legal WP-CLI organization. We’ve decided the operational complexities exceed the benefits. While many companies and individuals want to donate to WP-CLI, introducing money always has the possibility of causing more harm than good. Instead, we’ll use Kickstarter to fund larger endeavors (e.g. a logo and website redesign) on an as-needed basis.

If you would like to become a maintainer of WP-CLI in a non-code role, please email info@wp-cli.org. Expect to participate in a trial period of 2-3 months.

We have a Mailchimp mailing list now too. Sign up for email notifications of new versions and occasional announcements about the project.

Thanks,

Daniel Bachhuber, maintainer
Andreas Creten, founder

Version 0.23.1 released

WP-CLI v0.23.1 is a compatibility release for those who’ve installed WP-CLI with Composer and require a minimum-stability. See #2664 for related conversation.

If you’re using the Phar distribution of WP-CLI, you should wp cli update, but don’t need to prioritize it, as the release doesn’t change WP-CLI’s behavior in any significant way.

Contributors to this release: danielbachhuber

You can browse the full list of resolved issues on Github.

RESTful WP-CLI – What I’ve been hacking on

Let me just say — Thursday, February 4th was pretty darn demoralizing. I spent a huge amount of time in January towards the WP REST API in preparation for what I wanted to do on the command line, and a lot of momentum / inspiration / general good feelings were destroyed in that meeting. As such, I spent much of February and March working on WP-CLI features unrelated to the WP REST API (e.g. package management).

But, I’m back in the saddle. Because I’m 2/3 of the way through one of those fancy WP REST API + React WordPress applications, I’m running into dozens of ways I want to be able to make WordPress more efficiently. And of course, this means doing it on the command line.

Before we proceed: most of the, if not all, RESTful WP-CLI features have required under the hood changes to WP-CLI. You’ll want to wp cli update --nightly to play with this new functionality locally. Once you’ve done so, you can wp package install danielbachhuber/wp-rest-cli to install the latest.

Use --debug and --debug=rest to profile your REST endpoints

REST APIs are all about speed. Milliseconds matter, and every one you manage to shave off will have a real world impact on user experience.

To make it much, much easier to understand how many queries your endpoint is performing, and how long they take, I’ve added some lightweight profiling to RESTful WP-CLI.

Use --debug to get a summary of your queries for any command.

$ wp rest post list --debug
Debug (rest): REST command executed 7 queries in 0.001954 seconds. Use --debug=rest to see all queries. (1.446s)
+----+-----------------------------+
| id | title                       |
+----+-----------------------------+
| 1  | {"rendered":"Hello world!"} |
+----+-----------------------------+

Use --debug=rest to get the full list of queries executed.

$ wp rest post list --fields=id,title --debug=rest
Debug: REST command executed 7 queries in 0.001696 seconds. Ordered by slowness, the queries are:
1:
  - 0.000291 seconds
  - WP_REST_Posts_Controller->get_items, WP_Query->query, WP_Query->get_posts
  - SELECT SQL_CALC_FOUND_ROWS  wp_posts.ID FROM wp_posts  WHERE 1=1  AND wp_posts.post_type = 'post' AND (wp_posts.post_status = 'publish')  ORDER BY wp_posts.post_date DESC LIMIT 0, 10
2:
  - 0.000257 seconds
  - WP_REST_Posts_Controller->get_items, WP_Query->query, WP_Query->get_posts, WP_Query->set_found_posts
  - SELECT FOUND_ROWS()
3:
  - 0.000256 seconds
  - WP_REST_Posts_Controller->get_items, WP_REST_Posts_Controller->prepare_item_for_response, setup_postdata, WP_Query->setup_postdata, get_userdata, get_user_by, WP_User::get_data_by
  - SELECT * FROM wp_users WHERE ID = '1'
4:
  - 0.000244 seconds
  - WP_REST_Posts_Controller->get_items, WP_REST_Posts_Controller->prepare_item_for_response, setup_postdata, WP_Query->setup_postdata, get_userdata, get_user_by, WP_User->init, WP_User->for_blog, WP_User->_init_caps, get_user_meta, get_metadata, update_meta_cache
  - SELECT user_id, meta_key, meta_value FROM wp_usermeta WHERE user_id IN (1) ORDER BY umeta_id ASC
5:
  - 0.000233 seconds
  - WP_REST_Posts_Controller->get_items, WP_Query->query, WP_Query->get_posts, _prime_post_caches
  - SELECT wp_posts.* FROM wp_posts WHERE ID IN (1)
6:
  - 0.000209 seconds
  - WP_REST_Posts_Controller->get_items, WP_Query->query, WP_Query->get_posts, _prime_post_caches, update_post_caches, update_object_term_cache, wp_get_object_terms
  - SELECT t.*, tt.*, tr.object_id FROM wp_terms AS t INNER JOIN wp_term_taxonomy AS tt ON tt.term_id = t.term_id INNER JOIN wp_term_relationships AS tr ON tr.term_taxonomy_id = tt.term_taxonomy_id  WHERE tt.taxonomy IN ('category', 'post_tag', 'post_format') AND tr.object_id IN (1) ORDER BY t.name ASC
7:
  - 0.000206 seconds
  - WP_REST_Posts_Controller->get_items, WP_Query->query, WP_Query->get_posts, _prime_post_caches, update_post_caches, update_postmeta_cache, update_meta_cache
  - SELECT post_id, meta_key, meta_value FROM wp_postmeta WHERE post_id IN (1) ORDER BY meta_id ASC
 (1.598s)
+----+-----------------------------+
| id | title                       |
+----+-----------------------------+
| 1  | {"rendered":"Hello world!"} |
+----+-----------------------------+

Profiling works for any CRUD operation.

$ wp rest post create --title="Test post" --user=daniel --debug
Debug (rest): REST command executed 28 queries in 0.023962 seconds. Use --debug=rest to see all queries. (1.777s)
Success: Created post.
$ wp rest post update 3 --content="Foo bar" --user=daniel --debug
Debug (rest): REST command executed 31 queries in 0.023309 seconds. Use --debug=rest to see all queries. (1.634s)
Success: Updated post.

Hopefully this feature becomes an invaluable part of your REST endpoint development process, as it has mine. Hit me with feedback on its Github issue.

Use wp rest * edit to edit a resource in your system editor

Most people probably don’t know this, but you can use wp post edit <id> to edit post content in your system editor (e.g. vim). Now, with wp rest * edit, you can edit any REST resource in your system editor.

$ wp rest post edit 3 --user=daniel

When you run wp rest * edit, RESTful WP-CLI fetches the resource, transforms it into a YAML document, and puts it in your system editor:

---
date: 2016-04-14T14:02:57
date_gmt: null
password:
slug:
status: draft
title:
  raw: Test post
  rendered: Test post
content:
  raw: Foo bar
  rendered: |
    |
        Foo bar
excerpt:
  raw:
  rendered: |
    |
        Foo bar
author: 1
featured_media: 0
comment_status: open
ping_status: open
sticky: false
format: standard
categories:
  - 1
tags: [ ]

If you make changes to any of the fields, then the command sends it back to WordPress (through the WP REST API) to update.

On WordPress installs that support Basic Auth, editing also works over HTTP:

$ wp --http=http://daniel:daniel@wordpress-develop.dev rest post edit 1

Et, voila.

Get involved!

I’d love your input on the dozens of ideas I have for a more RESTful WP-CLI:

  • Render the help docs in formats like API Blueprint and Swagger [#36]
  • Introduce wp rest * generate to generate mock data in the format your application expects [#55].
  • Introduce wp rest * diff to be able to diff the state of two different WordPresses, a la Dictator [#56].
  • Figure out an elegant aliases implementation, so --http=http://daniel:daniel@wordpress-develop.dev becomes @wpdev [#2039]

And I want to hear your ideas too! As well as any feedback, questions, or violent dissent. Let’s chat on Github.