Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
42 changes: 40 additions & 2 deletions .github/workflows/wordpress-release.yaml
Original file line number Diff line number Diff line change
@@ -1,12 +1,49 @@
name: WordPress release

# Run by hand once the SVN tag is committed: WordPress.org releases happen outside GitHub.
# A merge to main that changes the plugin publishes it to WordPress.org SVN, then records the GitHub release and
# announces it on Discord once WordPress.org serves it. Safe to re-run: an unchanged plugin publishes nothing, and a
# version already released on GitHub is not announced twice. Release steps: wordpress/README.md
on:
push:
branches:
- main
paths:
- wordpress/svn/**
workflow_dispatch:

concurrency:
group: wordpress-release
cancel-in-progress: false

jobs:
publish:
name: Publish to WordPress.org SVN
runs-on: ubuntu-latest
# SVN_USERNAME and SVN_PASSWORD live in this environment, deployable from main only: whoever holds them ships
# code to every site that auto-updates the plugin.
environment: wordpress-org
permissions:
contents: read
outputs:
version: ${{ steps.publish.outputs.version }}

steps:
- name: Checkout code
uses: actions/checkout@v4

- name: Install Subversion
run: sudo apt-get update -qq && sudo apt-get install -y -qq subversion

- name: Publish
id: publish
run: scripts/publish-wordpress-svn.sh
env:
SVN_USERNAME: ${{ secrets.SVN_USERNAME }}
SVN_PASSWORD: ${{ secrets.SVN_PASSWORD }}

announce:
name: Record the plugin release on GitHub and announce it on Discord
name: Record the release on GitHub and announce it on Discord
needs: publish
runs-on: ubuntu-latest
permissions:
contents: write
Expand All @@ -23,5 +60,6 @@ jobs:
- name: Announce the release
run: node scripts/announce-wordpress-release.mjs
env:
WAIT_FOR_LIVE_MINUTES: "45"
DISCORD_RELEASES_WEBHOOK_URL: ${{ secrets.DISCORD_RELEASES_WEBHOOK_URL }}
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
15 changes: 15 additions & 0 deletions .github/workflows/wordpress.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ on:
- main
paths:
- wordpress/**
- scripts/publish-wordpress-svn.sh
- .github/workflows/wordpress.yaml

jobs:
Expand Down Expand Up @@ -53,3 +54,17 @@ jobs:
run: |
pinned_version=$(node -p "require('./package.json').dependencies['@simplepdf/web-embed-pdf']")
grep -q "define('SIMPLEPDF_WEB_EMBED_VERSION', '${pinned_version}');" svn/trunk/simplepdf-embed.php

publish-preview:
name: WordPress.org publish preview (dry run)
runs-on: ubuntu-latest

steps:
- name: Checkout code
uses: actions/checkout@v4

- name: Install Subversion
run: sudo apt-get update -qq && sudo apt-get install -y -qq subversion

- name: What merging this sends to WordPress.org
run: DRY_RUN=1 scripts/publish-wordpress-svn.sh
16 changes: 15 additions & 1 deletion scripts/announce-wordpress-release.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
// run by hand after the SVN commit. It refuses to run until WordPress.org serves the Stable tag, and the GitHub release
// (`wordpress@<version>`) is the record that the version was announced, so a second run posts nothing.
// Usage: DISCORD_RELEASES_WEBHOOK_URL=… GITHUB_TOKEN=… GITHUB_REPOSITORY=… GITHUB_SHA=… node scripts/announce-wordpress-release.mjs
// WAIT_FOR_LIVE_MINUTES=… polls WordPress.org once a minute until it serves the Stable tag (its import lags the SVN commit).
// Without DISCORD_RELEASES_WEBHOOK_URL, or with DRY_RUN=1, it prints the Discord message and the GitHub release instead.
import { readFile } from 'node:fs/promises'
import path from 'node:path'
Expand Down Expand Up @@ -110,6 +111,19 @@ const createGitHubRelease = async ({ token, repository, release }) => {
return response.ok ? null : `${response.status} ${await response.text()}`
}

const LIVE_VERSION_POLL_MS = 60_000

const waitForLiveVersion = async ({ version, minutes }) => {
const deadline = Date.now() + minutes * 60_000
const liveVersion = await fetchLiveVersion()
if (liveVersion === version || Date.now() >= deadline) {
return liveVersion
}
console.log(`WordPress.org serves ${liveVersion ?? 'nothing yet'}, waiting for ${version}…`)
await new Promise((resolve) => setTimeout(resolve, LIVE_VERSION_POLL_MS))
return waitForLiveVersion({ version, minutes: (deadline - Date.now()) / 60_000 })
}

const fetchLiveVersion = async () => {
try {
const response = await fetch(PLUGIN_INFO_URL)
Expand Down Expand Up @@ -141,7 +155,7 @@ const main = async () => {
return EXIT_CODES.success
}

const liveVersion = await fetchLiveVersion()
const liveVersion = await waitForLiveVersion({ version: release.version, minutes: Number(process.env.WAIT_FOR_LIVE_MINUTES ?? 0) })
if (liveVersion === null) {
console.error(`Could not read the live version from ${PLUGIN_INFO_URL}`)
return EXIT_CODES.wordpress_org_unreachable
Expand Down
76 changes: 76 additions & 0 deletions scripts/publish-wordpress-svn.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
#!/usr/bin/env bash
# Publishes wordpress/svn (git main is the source) to the WordPress.org SVN repository.
# - A new Stable tag: trunk/ and assets/ are synced, trunk is copied to tags/<version>, one commit.
# - A Stable tag already published: only assets/ and the readme (trunk/ and tags/<version>/) are synced, since
# WordPress.org reads the description from the tag; the code of a published tag never changes.
# Usage (the WordPress release workflow): SVN_USERNAME=… SVN_PASSWORD=… scripts/publish-wordpress-svn.sh
# DRY_RUN=1 stops before the commit and prints what would be sent; it needs no credentials.
# Writes `version=<version>` to $GITHUB_OUTPUT when set.
set -euo pipefail

readonly EXIT_SUCCESS=0
readonly EXIT_VERSION_MISMATCH=2
readonly EXIT_MISSING_CREDENTIALS=3

readonly SVN_URL="https://plugins.svn.wordpress.org/simplepdf-embed"
readonly SOURCE_DIR="$(cd "$(dirname "$0")/../wordpress/svn" && pwd)"
readonly WORKING_COPY="$(mktemp -d)"
trap 'rm -rf "$WORKING_COPY"' EXIT

stable_tag="$(sed -n 's/^Stable tag:[[:space:]]*//p' "$SOURCE_DIR/trunk/README.txt" | tr -d '[:space:]')"
header_version="$(sed -n 's/^Version:[[:space:]]*//p' "$SOURCE_DIR/trunk/simplepdf-embed.php" | tr -d '[:space:]')"
constant_version="$(sed -n "s/^define('SIMPLEPDF_PLUGIN_VERSION', '\(.*\)');/\1/p" "$SOURCE_DIR/trunk/simplepdf-embed.php")"
if [ -z "$stable_tag" ] || [ "$stable_tag" != "$header_version" ] || [ "$stable_tag" != "$constant_version" ]; then
echo "Version mismatch: Stable tag '$stable_tag', plugin header '$header_version', SIMPLEPDF_PLUGIN_VERSION '$constant_version'" >&2
exit "$EXIT_VERSION_MISMATCH"
fi
if [ -n "${GITHUB_OUTPUT:-}" ]; then
echo "version=$stable_tag" >> "$GITHUB_OUTPUT"
fi

svn checkout --quiet --non-interactive --depth immediates "$SVN_URL" "$WORKING_COPY"
svn update --quiet --non-interactive --set-depth infinity "$WORKING_COPY/trunk" "$WORKING_COPY/assets"

if svn ls --non-interactive "$SVN_URL/tags/$stable_tag" > /dev/null 2>&1; then
echo "$stable_tag is already on WordPress.org: syncing the readme and the listing assets only"
svn update --quiet --non-interactive --set-depth infinity "$WORKING_COPY/tags/$stable_tag"
rsync --archive --delete --exclude .svn "$SOURCE_DIR/assets/" "$WORKING_COPY/assets/"
cp "$SOURCE_DIR/trunk/README.txt" "$WORKING_COPY/trunk/README.txt"
cp "$SOURCE_DIR/trunk/README.txt" "$WORKING_COPY/tags/$stable_tag/README.txt"
if ! diff -rq --exclude .svn --exclude README.txt "$SOURCE_DIR/trunk" "$WORKING_COPY/tags/$stable_tag" > /dev/null; then
echo "::warning::The plugin code on main differs from the published $stable_tag: bump the version to publish it"
fi
commit_message="Listing: readme and assets for $stable_tag"
else
echo "Publishing $stable_tag"
rsync --archive --delete --exclude .svn "$SOURCE_DIR/trunk/" "$WORKING_COPY/trunk/"
rsync --archive --delete --exclude .svn "$SOURCE_DIR/assets/" "$WORKING_COPY/assets/"
commit_message="Release $stable_tag"
fi

cd "$WORKING_COPY"
svn status | awk '$1 == "?" { print $2 }' | while read -r added_path; do svn add --quiet --parents "$added_path"; done
svn status | awk '$1 == "!" { print $2 }' | while read -r removed_path; do svn rm --quiet "$removed_path"; done
if ! svn ls --non-interactive "$SVN_URL/tags/$stable_tag" > /dev/null 2>&1; then
svn cp --quiet trunk "tags/$stable_tag"
fi

pending_changes="$(svn status)"
if [ -z "$pending_changes" ]; then
echo "Nothing to publish: WordPress.org already matches main"
exit "$EXIT_SUCCESS"
fi
echo "$pending_changes" | grep -v "^A *+\{0,1\} *tags/$stable_tag/" || true

if [ "${DRY_RUN:-}" = "1" ]; then
echo "DRY_RUN: stopping before the commit ('$commit_message')"
exit "$EXIT_SUCCESS"
fi
if [ -z "${SVN_USERNAME:-}" ] || [ -z "${SVN_PASSWORD:-}" ]; then
echo "SVN_USERNAME and SVN_PASSWORD are required to publish" >&2
exit "$EXIT_MISSING_CREDENTIALS"
fi

svn commit --quiet --non-interactive --no-auth-cache --username "$SVN_USERNAME" --password "$SVN_PASSWORD" \
-m "$commit_message (${GITHUB_SHA:-local})"
echo "Committed '$commit_message' to WordPress.org"
50 changes: 26 additions & 24 deletions wordpress/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,38 +7,40 @@ The plugin is published to the Wordpress SVN registry https://plugins.svn.wordpr

## How to publish

`svn/` is both the git folder and the WordPress.org SVN working copy (its `.svn` is gitignored). Git `main` is the source of truth: publish from `main`, and SVN only carries it to WordPress.org.
Merging to `main` publishes. The **WordPress release** workflow (`.github/workflows/wordpress-release.yaml`) runs on every merge that changes `wordpress/svn/`:

1. `scripts/publish-wordpress-svn.sh` checks that the plugin header `Version`, `SIMPLEPDF_PLUGIN_VERSION` and the readme `Stable tag` agree, then:
- a new Stable tag: syncs `trunk/` and `assets/` to WordPress.org SVN and copies `trunk` to `tags/<version>`
- a Stable tag already published: syncs `assets/` and the readme only (in `trunk/` and `tags/<version>/`); the code of a published tag never changes
2. Once WordPress.org serves the version (its import lags the commit by minutes), it creates the `wordpress@<version>` GitHub release and posts the `README.txt` changelog to Discord. A version already released on GitHub is never announced twice.

Every pull request touching `wordpress/` runs the same script as a dry run (the "publish preview" job) and lists what merging would send.

To release a new version:

1. Set `SIMPLEPDF_WEB_EMBED_VERSION` in [simplepdf-embed.php](./svn/trunk/simplepdf-embed.php) to the `@simplepdf/web-embed-pdf` version pinned in [package.json](./package.json) (CI fails on a mismatch), and run `npm run package-plugin`
2. Set the new version in the [simplepdf-embed.php](./svn/trunk/simplepdf-embed.php) header and `SIMPLEPDF_PLUGIN_VERSION`, the `Stable tag` of [README.txt](./svn/trunk/README.txt) and [blueprint.json](./svn/assets/blueprints/blueprint.json)
3. Add the changelog entry in [README.txt](./svn/trunk/README.txt): it is posted to Discord as written
4. Merge. Then commit the new `svn/tags/<version>/` folder to git (it mirrors WordPress.org)

WordPress.org strips images from `README.txt`: show the plugin through `svn/assets/screenshot-N.png`, captioned under `== Screenshots ==`.

The SVN credentials (`SVN_USERNAME`, and the SVN password from WordPress.org > Profile > Account & Security as `SVN_PASSWORD`) live in the repository's `wordpress-org` environment, deployable from `main` only.

### Manual fallback

`svn/` is also the SVN working copy (its `.svn` is gitignored): git `main` stays the source of truth.

_Pre-requisites (once per clone)_
```bash
brew install svn
cd wordpress
# --force: svn/ already holds the git files; they stay as local changes. Older tags are not fetched.
svn checkout --force --depth immediates https://plugins.svn.wordpress.org/simplepdf-embed svn
cd svn && svn update --force --set-depth infinity trunk assets
git checkout -- . # SVN may have written its own copies over the git files: git wins
```
Never run `svn revert`: it replaces the git files with the last published version. To discard a change, restore from git.

1. Set `SIMPLEPDF_WEB_EMBED_VERSION` in [simplepdf-embed.php](./svn/trunk/simplepdf-embed.php) to the `@simplepdf/web-embed-pdf` version pinned in [package.json](./package.json) (CI fails on a mismatch)
2. Update the TAG / version in [simplepdf-embed.php](./svn/trunk/simplepdf-embed.php)
3. Update the TAG / version in [README.txt](./svn/trunk/README.txt)
4. Update the TAG / version in [blueprint.json](./svn/assets/blueprints/blueprint.json)
5. Update changelog in [README.txt](./svn/trunk/README.txt)
WordPress.org strips images from `README.txt`: show the plugin through `svn/assets/screenshot-N.png`, captioned under `== Screenshots ==`. It reads the description from the Stable tag's folder, so a readme fix after release goes in both `trunk/` and `tags/<TAG>/`.
6. Merge to `main`, check it out, then run the following

```bash
npm run package-plugin
cd svn
svn update
svn status # "?" = new file, "!" = removed file
svn add <each new file in trunk/ or assets/>
svn rm <each removed file>
svn status # "?" = new file (svn add), "!" = removed file (svn rm)
svn cp trunk tags/<TAG>
svn commit -m 'Release <TAG>' --username bendersej # the SVN password from WordPress.org > Profile > Account & Security
svn commit -m 'Release <TAG>' --username bendersej
```
Leave the older `tags/*` folders git tracks out of `svn add`: they already exist on WordPress.org.

7. Once https://wordpress.org/plugins/simplepdf-embed/ shows the new version, run the **WordPress release** workflow (`gh workflow run wordpress-release.yaml --repo SimplePDF/simplepdf-embed`). It creates the `wordpress@<TAG>` GitHub release and posts the `README.txt` changelog for the Stable tag to Discord. It refuses to run until WordPress.org serves that version, and does nothing when the GitHub release already exists.
8. Commit the new `svn/tags/<TAG>/` to git.
Never run `svn revert`: it replaces the git files with the last published version. Then run the **WordPress release** workflow by hand (`gh workflow run wordpress-release.yaml --repo SimplePDF/simplepdf-embed`) for the GitHub release and the Discord post.
Loading