# Releasing the WordPress plugin

## Source of truth

The version lives in **three** places, all kept in sync by `bin/bump-version.sh`:

1. `CHANGELOG.md` — the canonical source. Latest `## [X.Y.Z] - DATE` heading
   wins, all other files are checked against it at build time.
2. `rankwiz-ai.php` — `Version:` header + `RANKWIZ_VERSION` constant.
3. `readme.txt` — `Stable tag:` line. The `== Changelog ==` block is
   regenerated from `CHANGELOG.md` at build time, so do not edit it directly.

`bin/build.sh` refuses to run if any two of these disagree.

## First-time developer setup

`composer.phar` is **not** committed to the repository. Fetch it once via the
official installer before running `composer install` or `bin/build.sh`:

```bash
cd wp-plugin/rankwiz-ai

# Download the Composer installer, verify its checksum, then move the phar
# into place. The phar is gitignored — do not commit it.
php -r "copy('https://getcomposer.org/installer', 'composer-setup.php');"
EXPECTED_HASH="$(php -r "echo file_get_contents('https://composer.github.io/installer.sig');")"
ACTUAL_HASH="$(php -r "echo hash_file('sha384', 'composer-setup.php');")"
if [ "$EXPECTED_HASH" != "$ACTUAL_HASH" ]; then echo "Installer corrupt"; exit 1; fi
php composer-setup.php
rm composer-setup.php
# Optional: make it globally available
# mv composer.phar /usr/local/bin/composer

# Install dev dependencies (PHPUnit, etc.)
php composer.phar install
```

Alternatively, if `composer` is already on your `PATH`, just run
`composer install` directly — `bin/build.sh` and CI both call `composer`
or install it from the official installer automatically.

## Manual release (local)

```bash
cd wp-plugin/rankwiz-ai

# 1. Bump version + insert stanza placeholder.
./bin/bump-version.sh 1.1.0 --notes "$(cat <<'NOTES'
- feat: added foo
- fix: corrected bar
NOTES
)"

# 2. (Optional) edit CHANGELOG.md to expand the placeholder into real notes.

# 3. Build the zip + .sha256 + manifest sidecar.
./bin/build.sh

# 4. Commit, tag, push.
git add CHANGELOG.md rankwiz-ai.php readme.txt
git commit -m "chore(wp-plugin): release v1.1.0"
git tag wp-plugin-v1.1.0
git push origin main --follow-tags
```

The deploy pipeline picks up the new `wp-plugin-v*` tag and serves the new
zip from `/downloads/wp-plugin`.

### `--force` and dirty trees

`bump-version.sh` refuses to run if `CHANGELOG.md`, `rankwiz-ai.php`, or
`readme.txt` already have uncommitted edits — pass `--force` to override.
This guard prevents version bumps from getting tangled in unrelated edits.

## Automated release (CI)

The release workflow lives at
[`scripts/wp-plugin-release-workflow.yml.template`](../../scripts/wp-plugin-release-workflow.yml.template)
and is **not installed by default** — direct CI/CD edits are blocked by a
repo safety hook. To enable auto-release:

```bash
cp scripts/wp-plugin-release-workflow.yml.template \
   .github/workflows/wp-plugin-release.yml
git add .github/workflows/wp-plugin-release.yml
git commit -m "ci: wp-plugin auto-release workflow"
git push
```

Once installed, every push to `main` that touches `wp-plugin/rankwiz-ai/**`:

1. Reads conventional-commits subjects since the last `wp-plugin-v*` tag.
2. Classifies each into the highest applicable bump:
   - `feat!:` / `fix!:` / `BREAKING CHANGE:` footer → **major**
   - `feat:` → **minor**
   - `fix:` / `perf:` / `refactor:` / `revert:` → **patch**
   - `chore:` / `docs:` / `test:` / `style:` / `ci:` → **no bump** (notes only)
3. Runs `bin/bump-version.sh` with the computed version + auto-generated notes.
4. Runs `bin/build.sh`.
5. Verifies the build manifest's `git_commit` matches `HEAD`. Refuses to
   release if dirty or mismatched.
6. Commits the bump, tags `wp-plugin-vX.Y.Z`, pushes both.
7. Creates a GitHub Release with the zip, `.sha256`, and `.manifest.json`
   attached as assets.

The workflow is loop-safe: it skips when the HEAD commit message starts with
`chore(wp-plugin): release` (its own output).

## Build artifacts

Every build produces three files, all named after the version:

| File | Purpose |
|------|---------|
| `rankwiz-ai-X.Y.Z.zip` | The plugin archive itself. Upload via WP Admin → Plugins → Add New → Upload Plugin. |
| `rankwiz-ai-X.Y.Z.zip.sha256` | SHA-256 sidecar. `shasum -a 256 -c rankwiz-ai-X.Y.Z.zip.sha256` to verify. |
| `rankwiz-ai-X.Y.Z.zip.manifest.json` | Build provenance: version, git commit, git_dirty, build timestamp, sha256, filename. |

The manifest is what makes drift detection possible: `deploy.sh` compares
the manifest's `git_commit` against the deploy's HEAD and refuses to serve
a zip built from a different commit, so users never download a plugin
that was built from code other than what's running.

## Drift detection in deploy.sh

After running `bin/build.sh --copy-to storage/app/downloads`, deploy.sh:

1. Reads `git_commit` from the staged zip's manifest.
2. Compares to `git rev-parse HEAD`.
3. If they differ (or version drift between zip filename and
   `rankwiz-ai.php` Version: header), all staged zips are removed so
   `/downloads/wp-plugin` returns 404 instead of misattributed bytes.

This is the safety net for the cases where the build step succeeded
nominally but produced a stale or mismatched artifact.
