# Build, check and ship a WordPress plugin release with wp-release

Releasing a plugin usually means a list of manual steps: install production dependencies, copy the right files, zip them, run Plugin Check, then copy everything into the wordpress.org SVN trunk and tag it. Miss one step and the zip contains a test folder, or the readme's Stable tag no longer matches the plugin header.

[wp-release](https://github.com/mammothwp/wp-release) puts those steps behind one PHP command, `mmwp-release`, which behaves the same on your machine and in CI.

## Install

```bash
composer require --dev mammothwp/wp-release
vendor/bin/mmwp-release --help
```

It is a development dependency and requires PHP 8.1 or newer, plus these tools on `PATH`: `composer`, `rsync`, `zip` and `unzip` for `build`; `wp` (WP-CLI) and `curl` for `check`; and `svn` for `deploy-wporg`. With the default SQLite database, `check` also needs the `pdo_sqlite` PHP extension.

## Prepare the plugin

wp-release expects the main file to be `<slug>.php` in the plugin root. The slug is detected from the single root PHP file with a `Plugin Name:` header, or set with `--slug`, `MMWP_SLUG` or `mmwp-release.json`.

A `.distignore` file (rsync exclude syntax) is required in the plugin root:

```
/.gitignore
/tests
/docs
/node_modules
```

List every tracked hidden file, such as `.gitignore`, because the build rejects hidden files in the zip. Anchor entries with a leading `/`. An unanchored entry such as `tests` also strips those folders inside `vendor/` packages, and `build` warns about it.

An optional `mmwp-release.json` sets defaults:

```json
{
  "slug": "my-plugin",
  "php_min": "7.4",
  "wp_version": "6.8",
  "strict": false,
  "allow": [],
  "excludes": ["*.psd", "docs/*"],
  "github_repo": "my-plugin"
}
```

## Build

```bash
vendor/bin/mmwp-release build
```

`build` runs a fresh `composer install --no-dev --optimize-autoloader` in a temporary copy, so your working `vendor/` is never touched. In a git work tree it ships only tracked files, applies `.distignore`, writes `dist/<slug>/` and `<slug>.zip`, and prints the zip size and file count.

It also validates the result. The plugin header needs `Text Domain` equal to the slug, plus `Requires PHP`, `Requires at least`, a GPL `License` and `Version`. `readme.txt` needs `Stable tag` and `Tested up to`, and `Version` must equal `Stable tag`. The zip may not contain files such as `.git`, `node_modules`, `tests`, `.env*` or `*.log`, any hidden file or `*.phar`. If your plugin requires runtime packages, the zip must contain their `vendor/` directories.

## Check

```bash
vendor/bin/mmwp-release check
```

`check` creates a throwaway WordPress site in a temporary directory, installs Plugin Check, unzips the built zip, activates the plugin and runs `wp plugin check`. It writes `plugin-check.json` and `plugin-check.txt`, then removes the site. It fails on any Plugin Check error, and on warnings too with `--strict`. Other options include `--self-test` (also runs `wp mmwp self-test <slug>`), `--keep` (keeps the temporary site), `--deps=<slugs>` for plugins that need other plugins active first, and `--db=mysql`.

With `--strict`, you can let specific, known warnings through with `--allow=<regex>`. Errors can never be allowed.

## Status and deploy

```bash
vendor/bin/mmwp-release status
vendor/bin/mmwp-release deploy-wporg --dry-run
```

`status` prints the slug, the header version, the readme Stable tag, whether that version already exists on wordpress.org and GitHub, and what each deploy command would do.

`deploy-wporg` needs a passing check. If `tags/<version>` already exists on `plugins.svn.wordpress.org`, or the plugin has no SVN repository yet, it skips and exits 0. Otherwise it syncs trunk, copies `.wordpress-org/` into assets, runs `svn cp trunk tags/<version>` and commits. With `--dry-run`, it prints the `svn status` diff and stops before the commit. SVN credentials come from `WPORG_USERNAME` and `WPORG_PASSWORD`.

`release-github` creates a GitHub release `v<version>` with the matching `readme.txt` changelog section as notes and uploads the zip. The owner defaults to `mammothwp`, so set `MMWP_GITHUB_OWNER` (and `MMWP_GITHUB_REPO` if needed) for your own repositories. It needs `GITHUB_TOKEN`.

`all` runs `build`, `check`, `deploy-wporg` and `release-github` in that order and stops at the first failure.

## Release model

A version bump is the release. Every deploy command compares the plugin's `Version` header with what is already published and does nothing when that version is already out, so the release job can run on every push to your main branch. Exit codes are specific: for example 11 for a validation failure, 20 for Plugin Check errors and 30 when `deploy-wporg` has no passing check.

## When to use it

- You publish a plugin on wordpress.org and want build, Plugin Check and SVN deploy as one repeatable process.
- You want the same commands locally and in CI.
- Your plugin ships Composer packages in `vendor/` and you want the zip validated before it goes out.

## When not to

- Your plugin is not distributed through wordpress.org or GitHub releases.
- Your release process uses git tags as the trigger. wp-release uses the version header instead.
- Your build environment cannot run PHP 8.1, rsync, WP-CLI or svn.

## Links

- GitHub: https://github.com/mammothwp/wp-release
- Packagist: https://packagist.org/packages/mammothwp/wp-release
- License: GPLv2 or later
