Categories
CLI Coding WordPress

Ergonomically Running WP-CLI in wp-env

Something that has always annoyed me is how verbose it is to run WP-CLI commands in wp-env projects. For example, to list out all posts:

npm run wp-env run cli wp post listCode language: Bash (bash)

The “npm run wp-env run cli” part I have to copy for each command, and I often forget the right order or miss a component. It gets worse when needing to pass command line options to WP-CLI, because the options could inadvertently get consumed either by npm or by wp-env. For example, neither of these work as expected:

npm run wp-env run cli wp post list --helpCode language: Bash (bash)
Output
Run arbitrary package scripts

Usage:
npm run-script <command> [-- <args>]

Options:
[-w|--workspace <workspace-name> [-w|--workspace <workspace-name> ...]]
[-ws|--workspaces] [--include-workspace-root] [--if-present] [--ignore-scripts]
[--foreground-scripts] [--script-shell <script-shell>]

aliases: run, rum, urn

Run "npm help run-script" for more infoCode language: Shell Session (shell)
npm run -- wp-env run cli wp post list --helpCode language: Bash (bash)
Output
> wp-env
> wp-env run cli wp post list --help

wp-env run <container> [command...]

Runs an arbitrary command in one of the underlying Docker containers. A double
dash can be used to pass arguments to the container without parsing them. This
is necessary if you are using an option that is defined below. You can use
`bash` to open a shell session and both `composer` and `phpunit` are available
in all WordPress and CLI containers. WP-CLI is also available in the CLI
containers.

Positionals:
  container  The underlying Docker service to run the command on.
              [string] [required] [choices: "mysql", "tests-mysql", "wordpress",
                                          "tests-wordpress", "cli", "tests-cli"]
  command    The command to run.                           [array] [default: []]

Options:
  --help     Show help                                                 [boolean]
  --debug    Enable debug output.                     [boolean] [default: false]
  --version  Show version number                                       [boolean]
  --env-cwd  The command's working directory inside of the container. Paths
             without a leading slash are relative to the WordPress root.
                                                         [string] [default: "."]
  --silent   Whether to omit the command tip and spinner text.
                                                      [boolean] [default: false]Code language: Shell Session (shell)

To get the help output for the wp post list command, you need to add two instances of “--” in the command:

npm run -- wp-env run cli -- wp post list --helpCode language: Bash (bash)

The verbosity gets even worse when needing to nest WP-CLI commands in subshells, for example to delete all posts of a given post type:

npm run -- wp-env run cli -- wp post delete --force $(
   npm run --silent -- wp-env run cli -- 
       wp post list --post_type=foo --format=ids 2>/dev/null)Code language: Bash (bash)

Here the --silent and 2>/dev/null are both needed to remove extra output from being printed to prevent passing them into the wp post delete command (with the first two lines coming from npm run and suppressed by --silent, and the remaining lines coming from wp-env run:

> wp-env
> wp-env run cli -- wp post list --post_type=post --format=ids

ℹ Starting 'wp post list --post_type=post --format=ids' on the cli container. 
...
✔ Ran `wp post list --post_type=post --format=ids` in 'cli'. (in 0s 683ms)Code language: Shell Session (shell)

It would be nice if the wp-env run command had an option to omit this verbosity (which I’ve proposed in #65015).

The Solution

To make this more ergonomic, I added a wp wrapper script on my machine:

#!/bin/bash
npm run --silent -- wp-env run cli -- wp "$@" 2>/dev/nullCode language: Bash (bash)

I located this at /usr/local/bin/wp and gave it the execute bit (chmod +x /usr/local/bin/wp). Now when I want to delete all posts of a given post type, I can just do:

wp post delete --force $(wp post list --post_type=foo --format=ids)Code language: Bash (bash)

Much nicer! It’s as if I’m not using wp-env at all. But then my teammate Felix Arntz raised what would happen if there was a global wp command already for WP-CLI to manage sites not using wp-env. It would be ideal if it could detect whether you’re currently in a project using wp-env and route to the regular WP-CLI wp global command when you aren’t. With some help from Gemini, I found this is doable by getting all instances of wp on the $PATH (via which -a), omitting the current wp, and then using the first remaining. Here’s the full script I’m using now:

#!/bin/bash
# WP-CLI `wp` command wrapper for wp-env
#
# Falls back to the global wp next on the PATH when not in 
# a directory tree containing .wp-env.json.
# Save this as a file named `wp` in a directory first on 
# the $PATH environment variable, with a secondary $PATH
# directory containing the global WP-CLI command.

is_wp_env() {
    local current_dir="$PWD"
    while [ "$current_dir" != '/' ]; do
        if [ -f "$current_dir/.wp-env.json" ]; then
            return 0
        fi
        current_dir="$(dirname "$current_dir")"
    done
    return 1
}

if is_wp_env; then
    exec npm run --silent -- wp-env run cli -- wp "$@" 2>/dev/null
else
    other_wp=$(which -a "$(basename "$0")" | grep -v "$0" | head -n 1)
    if [ -z "$other_wp" ]; then
        echo "Error: Not located inside a wp-env directory and no global wp command available." >&2
        exit 1
    else
        exec "$other_wp" "$@"
    fi
fiCode language: Bash (bash)

Bonus: This also works with WP-CLI tab completion! However, given the additional latency of wp-env it’s not a particularly great experience.

Enjoy!

Leave a Reply

Your email address will not be published. Required fields are marked *