WordPress plugin release tools

When developing WordPress plugin for distribution on the wordpress.org directory, we need some tools to automate the release process. It’s convenient to use github (or another source-code control vendor) to hold the day-to-day development source code. And, when we’re ready to release the plugin to the WordPress repository, we need to use WordPress’s Subversion (svn) repository release process. It’s fiddly to do that process manually. So we’ll do some automation.

Repository setup

Start by creating a git repository for a new plugin. At the top level of that repository should be the plugin’s basic set of files:

plugin-name.php      -- the plugin's code. 
readme.txt           -- the plugin's marketing copy
.wordpress-org/     -- a directory containing images and icons mentioned in readme.txt
   banner-772x250.png   -- the banner for the plugin directory page
   icon-128x128.png     -- the icon
   screenshot-1.png     -- first screenshot
   screenshot-2.png     -- second screenshot, etc.
README.md            -- the git repository's README file, not part of the distributed plugin
.gitignore           -- a list of local files and folders to omit from the git repository
.distignore          -- a list of local files and folders to omit from the plugin distribution

Notice this: you should use the .wordpress-org/ directory to hold your images and icons, not the assets/ directory. You may have followed the way WordPress’s repository is laid out, with those files in assets/. But the automated tools work better with this new directory.

The .distignore file in your toplevel directory contains the files and directories to omit from the plugin distribution. This is a good .distignore file to use.

# Directories and files to omit from the wordpress.org svn repo.
.distignore
.gitignore
.wordpress-org
.git
.editorconfig
.vs
.idea
.gitlab-ci.yml
.travis.yml
.DS_Store
Thumbs.db
behat.yml
bin
circle.yml
composer.json
composer.lock
Gruntfile.js
package.json
package-lock.json
phpunit.xml
phpunit.xml.dist
multisite.xml
multisite.xml.dist
phpcs.ruleset.xml
README.md
wp-cli.local.yml
tests
vendor
node_modules
*.sql
*.tar.gz
*.zip
*~

Beware! many online examples suggest using lines starting with /, such as /.git, to mention a file or directory in the top level of your Git repository. This does not work.

Creating the plugin’s .zip file

Plugins are all distributed as .zip archives containing their code. The WordPress Plugin Directory distributes those zip files, and we can also distribute plugin zip files directly. WP-CLI offers a straightforward way to make an appropriate .zip file from a plugin directory hierarchy: the wp dist-archive command-line program.

To use wp dist-archive, start by installing the command into your WP-CLI environment.

 wp package install wp-cli/dist-archive-command

Then set your working directory to the top level of your plugin’s git directory and give the command

 wp dist-archive .

and the command will write plugin’s .zip file into the parent of the current directory, honoring the contents of the .distignore file.

Carefully inspect the .zip file to ensure it contains all the files you want there, and no files you don’t want there. Adjust .distignore until the .zip file’s contents are perfect. It’s important: later steps of the plugin release process also depend on .distignore.

Localization — Making a .pot File

This wpcli command, make-pot, makes a .pot file to help localize a plugin. (It scans internationalized strings.)

wp i18n make-pot . languages/pluginslug.pot --path=/path/to/wordpress/install

Deploying with a Github action

Github offers dev-ops automation in the form of Actions. Actions allow you to specify workflows to run upon various operations on your repository in Github. You can attach workflows to all sorts of events, like pushes, pull-request creation, and many others. For the purpose of deploying a plugin to the WordPress.org directory, we use a workflow attached to publishing a release. (We avoid running the deployment workflow on commits, pushes or other more frequent Github events: the WordPress.org people do not want their repository to be used as day-to-day source control, only for releases.)

The workflow we use is called WordPress Plugin Deploy. It was developed by people at 10up.com and comes from Github’s Actions Marketplace. It’s MIT-licensed and open source.

Creating the workflow file

Workflow files go in our Github repository (but not the WordPress plugin repository) in the .github/workflows/ subdirectory. We can put as many workflows as necessary in that subdirectory; each one has its own .yml file. For plugin deployment we’ll name the file .github/workflows/deploy-to-repo.yml. Here’s the file.

name: Deploy Plugin to WordPress.org
on:
  release:
    types: [published]
jobs:
  tag:
    name: New release
    runs-on: ubuntu-latest
    steps:
      - name: Checkout code
        uses: actions/checkout@v2
      - name: WordPress Plugin Deploy
        id: deploy
        uses: 10up/action-wordpress-plugin-deploy@stable
        with:
          generate-zip: true
        env:
          SVN_USERNAME: ${{ secrets.SVN_USERNAME }}
          SVN_PASSWORD: ${{ secrets.SVN_PASSWORD }}
          ASSETS_DIR: .wordpress-org
          SLUG: my-wonderful-plugin
      - name: Upload release assets
        uses: actions/upload-release-asset@v1
        env:
          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
        with:
          upload_url: ${{ github.event.release.upload_url }}
          asset_path: ${{ steps.deploy.outputs.zip-path }}
          asset_name: ${{ github.event.repository.name }}.zip
          asset_content_type: application/zip

The best way to explain this workflow is line-by-line.

name: Deploy Plugin to WordPress.org
on:
  release:
    types: [published]

These lines give the workflow a name, and declare that we want it to run when we create and publish a new release on Github.

jobs:
  tag:
    name: New release
    runs-on: ubuntu-latest
    steps:

These lines declare that we want a job (within the workflow) named “New release.” We want Github to spin up an Ubuntu Linux virtual machine to run the job. Next we’ll take a look at the steps of that job.

      - name: Checkout code
        uses: actions/checkout@v2

This first step of the job checks out the code from our Github repository using actions/checkout@v2, version 2 of the built-in checkout action.

      - name: WordPress Plugin Deploy
        id: deploy
        uses: 10up/action-wordpress-plugin-deploy@stable
        with:
          generate-zip: true
        env:
          SVN_USERNAME: ${{ secrets.SVN_USERNAME }}
          SVN_PASSWORD: ${{ secrets.SVN_PASSWORD }}
          ASSETS_DIR: .wordpress-org
          SLUG: my-wonderful-plugin

This second step does the work of checking out the plugin from WordPress.org’s Subversion repository, updating the contents to put in the latest changes from our Git repository, and committing the changes back to Subversion. Lines 12-14 invoke the WordPress Plugin Deploy action. Lines 15-16 declare that we want the action to create a zip file for the plugin as well as sending it to the repository.

The environment variables declared in lines 17-20 are critical to the correct functioning of the Action. When the workflow runs, it retrieves the first two variables (SVN_USERNAME and SVN_PASSWORD) from Github’s secrets storage. These are the username and password we use for logging in to https://wordpress.org/. We can find more about Github secrets here. Instructions for setting them are here.

ASSETS_DIR gives the name of the directory containing images and icons mentioned in the plugin’s readme.txt file. The Action expects to find it at the top level of our Git repository, and uses it to populate the /assets/ directory in the plugin repository. (That’s where the wordpress.org web site looks when it builds our plugin’s page in the Directory.)

SLUG gives the slug name of our plugin. That’s the name used in the WordPress.org plugin directory. For example, my-wonderful-plugin would be found at https://wordpress.org/plugins/my-wonderful-plugin/.

The WordPress Plugin Deploy action does its work by running a shell script on the Linux virtual machine handling the workflow. That shell script’s source code is here.

      - name: Upload release assets
        uses: actions/upload-release-asset@v1
        env:
          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
        with:
          upload_url: ${{ github.event.release.upload_url }}
          asset_path: ${{ steps.deploy.outputs.zip-path }}
          asset_name: ${{ github.event.repository.name }}.zip
          asset_content_type: application/zip

The last step of our workflow creates the plugin’s zip file. It uses actions/upload-release-asset@v1, version 1 of the built-in Action for uploading our released asset (our zip file) back to the Releases section of our Github repository.

Using the workflow by tagging the Github repository

Github runs the workflow for us whenever we create and publish a new Release. In Github, Releases are created from repository tags. And, importantly, the WordPress Deploy Action uses the Github repository tag as the tag for wordpress.org’s Subversion repository. So, we have to be careful with Github tags: they control deployment to the WordPress.org repository.

Let’s say we want to release version 10.11.12 of our plugin to our users. Here’s what we must do.

  1. Edit the plugin’s readme.txt file so the Stable tag line near the top says Stable tag: 10.11.12
  2. Edit the rest of the plugin’s source code to update the version number appropriately wherever it appears.
  3. Commit everything to the Git repository.
  4. Put the 10.11.12 tag on the Git repository. You can do this with your IDE or with the command git tag 10.11.12
  5. Push the Git repository, including the tags, to Github. You can do this with your IDE or with the command git push --tags
  6. On Github, create and publish a release from that same tag.

Creating and publishing the release triggers the release workflow, and we’re done.

Testing

This workflow seems a little perilous to test, because testing it involves pushing a version of the plugin to wordpress.org’s Subversion repository. We don’t want our plugin’s users to get plugin updates from our test version, after all. How can we test this without releasing a new version of the plugin?

  1. Don’t update the Stable tag line in readme.txt. A new version is not pushed to users unless it’s mentioned in that line.
  2. Don’t mess up the contents of readme.txt or the icon, banner, or screenshots (in .wordpress-org/) too badly: users will see those contents.
  3. Use a test tag name like 10.11.12.rc1 or something similar.
  4. Create the release on Github.

That will activate the workflow to push a version of our code to Subversion that’s tagged with the test tag name.

Then, we can check out the code from Subversion with a shell command like

svn co https://plugins.svn.wordpress.org/my-wonderful-plugin testdirectory
cd testdirectory

Inspecting the checked-out repository should show our correct source code in /trunk/ and in, for our example /tags/10.11.12.rc1/. And it should show the correct icon, banner, and screenshots in /assets/.

Finally, we can get rid of the testing tag from the Subversion repository with commands like these

svn delete /tags/10.11.12.rc1
svn commit -m "remove test tag"

Checking readme.txt

Our readme.txt file for the plugin is important to get right. The WordPress.org plugin repository (the “repo”) uses it to provide the text to the plugin listing. That’s how people find and learn to use our work.

Version numbers: These descriptive lines should appear in both readme.txt and the plugin’s main php file. The Description name should appear there too. And, if our plugin provides mu-plugin or dropin code, that code’s header should have these lines too.

Plugin Name: WP Framis Zumbinator
Description: Zumbinates your site's framises, individually or in batches. 
Version:           1.4.17
 Requires at least: 4.2
 Tested up to:      6.5
 Requires PHP:      5.6

WordPress.org has an online readme.txt file validator here. If we paste the readme.txt contents into it, it does some basic syntax checks and helps avoid errors.

https://wordpress.org/plugins/developers/readme-validator/

Leave a Comment