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
72 changes: 66 additions & 6 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
@@ -1,9 +1,27 @@
name: Release

# Publishes a GitHub Release for the version in bibliography-builder.php when
# no release for it exists yet, then dispatches the WordPress.org deploy. It
# runs when:
# - a tag like v1.11.0 is pushed (the tag must match the version);
# - main changes bibliography-builder.php or this workflow, so merging a
# version bump releases it;
# - it is run by hand (workflow_dispatch).
# The plugin header, readme.txt Stable tag, package.json and block.json must
# all name the same version. The release body is that version's CHANGELOG.md
# section, followed by GitHub's generated pull-request list. An existing
# release is never changed.

on:
push:
tags:
- 'v*'
branches:
- main
paths:
- 'bibliography-builder.php'
- '.github/workflows/release.yml'
workflow_dispatch:

permissions:
contents: write
Expand All @@ -18,38 +36,80 @@ jobs:
- name: Checkout
uses: actions/checkout@v5

- name: Read the plugin version
id: version
env:
GH_TOKEN: ${{ github.token }}
run: |
version="$(sed -n 's/^ \* Version: *\([^ ]*\)$/\1/p' bibliography-builder.php)"
if [ -z "$version" ]; then
echo "Could not read Version from bibliography-builder.php" >&2
exit 1
fi
stable="$(sed -n 's/^Stable tag: *\([^ ]*\)$/\1/p' readme.txt)"
package="$(node -p "require('./package.json').version")"
block="$(node -p "require('./block.json').version")"
for other in "$stable" "$package" "$block"; do
if [ "$other" != "$version" ]; then
echo "Versions disagree: plugin ${version}, readme.txt ${stable}, package.json ${package}, block.json ${block}" >&2
exit 1
fi
done
if [ "$GITHUB_REF_TYPE" = "tag" ] && [ "v${version}" != "$GITHUB_REF_NAME" ]; then
echo "Tag ${GITHUB_REF_NAME} does not match plugin version ${version}" >&2
exit 1
fi
echo "tag=v${version}" >> "$GITHUB_OUTPUT"
if gh release view "v${version}" --repo "$GITHUB_REPOSITORY" >/dev/null 2>&1; then
echo "Release v${version} already exists; nothing to do."
echo "exists=true" >> "$GITHUB_OUTPUT"
fi

- name: Set up Node.js
if: steps.version.outputs.exists != 'true'
uses: actions/setup-node@v5
with:
node-version: 22
cache: npm

- name: Install dependencies
if: steps.version.outputs.exists != 'true'
run: npm ci --legacy-peer-deps

- name: Build
if: steps.version.outputs.exists != 'true'
run: npm run build

- name: Package release zip
if: steps.version.outputs.exists != 'true'
run: npm run package:release

# The release body is this version's CHANGELOG section, followed by
# GitHub's generated pull-request list (the action prepends the body
# to the generated notes). Fails if CHANGELOG.md has no entry for
# the tag, before anything is published.
# Fails if CHANGELOG.md has no entry for the version, before
# anything is published.
- name: Extract release notes from CHANGELOG
run: node scripts/extract-changelog-section.js "$GITHUB_REF_NAME" > output/release/release-notes.md
if: steps.version.outputs.exists != 'true'
env:
RELEASE_TAG: ${{ steps.version.outputs.tag }}
run: node scripts/extract-changelog-section.js "$RELEASE_TAG" > output/release/release-notes.md

# Creates the tag at this commit when it does not exist yet.
- name: Create GitHub Release
if: steps.version.outputs.exists != 'true'
uses: softprops/action-gh-release@v3
with:
tag_name: ${{ steps.version.outputs.tag }}
target_commitish: ${{ github.sha }}
files: output/release/borges-bibliography-builder.zip
body_path: output/release/release-notes.md
generate_release_notes: true
fail_on_unmatched_files: true
overwrite_files: true

# A release published with the workflow token does not trigger
# wp-deploy.yml's release event, so dispatch it on the tag.
- name: Dispatch WordPress.org deploy
if: steps.version.outputs.exists != 'true'
env:
GH_TOKEN: ${{ github.token }}
run: gh workflow run wp-deploy.yml --ref "$GITHUB_REF_NAME"
RELEASE_TAG: ${{ steps.version.outputs.tag }}
run: gh workflow run wp-deploy.yml --ref "$RELEASE_TAG"
4 changes: 2 additions & 2 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -108,13 +108,13 @@ The Playground blueprint at `playground/blueprint.json` configures the GitHub/re

### Release

`npm run package:release` builds the production zip (strips dev dependencies). Pushing a `v*` tag triggers `release.yml`, which builds the ZIP, publishes the GitHub Release (its body is the tag's `CHANGELOG.md` section, extracted by `scripts/extract-changelog-section.js`, followed by GitHub's generated PR list; the job fails if that section is missing), and dispatches `wp-deploy.yml`. `wp-deploy.yml` also runs on published GitHub releases and can be run manually.
`npm run package:release` builds the production zip (strips dev dependencies). `release.yml` runs when a push to `main` changes `bibliography-builder.php` (so merging a version bump releases it), when a `v*` tag is pushed, or by hand. It reads the version from the plugin header, fails unless `readme.txt`, `package.json` and `block.json` agree, and does nothing if that version's release exists. Otherwise it builds the ZIP, creates the tag, publishes the GitHub Release (its body is the tag's `CHANGELOG.md` section, extracted by `scripts/extract-changelog-section.js`, followed by GitHub's generated PR list; the job fails if that section is missing), and dispatches `wp-deploy.yml`. `wp-deploy.yml` also runs on published GitHub releases and can be run manually.

## CI overview

- **ci.yml** — lint, Jest, PHPUnit, Psalm, Playwright Playground E2E, coverage upload, package artifact
- **runtime-matrix.yml** — smoke tests across PHP 7.4–8.4, WP 6.4+, Apache/Nginx, MySQL via Docker. They run against the packaged release ZIP contents (`WP_BIBLIO_PLUGIN_DIR`), not the checkout. Beyond activation and the read routes, `scripts/runtime-matrix/smoke.sh` checks formatter parity with `tests/fixtures/csl-styles/` goldens on each PHP version, times a 200-entry `/format` request over HTTP with `scripts/time-format-route.sh` (also usable by hand against any site), and exercises the write routes over HTTP with an application password.
- **release.yml** — tag-triggered GitHub Release and ZIP publication
- **release.yml** — GitHub Release and ZIP publication on a version bump merged to `main`, a `v*` tag, or by hand
- **wp-deploy.yml** — deploys to WordPress.org SVN on published GitHub release, release dispatch, or manual run
- **codeql.yml** — security analysis
- **demo-links.yml** — scheduled/manual check that Playground demo blueprint install URLs stay reachable and never regress to the browser-broken `git:directory` resource
14 changes: 9 additions & 5 deletions docs/release-readiness-checklist.md
Original file line number Diff line number Diff line change
Expand Up @@ -114,10 +114,14 @@ miss — nothing in CI checks any of them.

## Publish and verify

Merging the release PR does not release anything. `wp-deploy.yml` runs on
`release: published` (or manual dispatch), so wordpress.org keeps serving the
previous tag — and keeps showing its `Tested up to` — until a release exists.

- [ ] Tag created and GitHub release published
Merging the release PR releases it. When a push to `main` changes
`bibliography-builder.php`, `release.yml` reads the version, checks that the
plugin header, `readme.txt`, `package.json` and `block.json` agree, and, if no
GitHub release exists for it yet, creates the tag and the release and
dispatches `wp-deploy.yml`. So run this checklist before merging the version
bump, not after. Pushing a matching `v*` tag, or running `release.yml` by
hand, does the same; an existing release is never changed.

- [ ] `release.yml` run completed: tag created and GitHub release published
- [ ] `wp-deploy` run completed
- [ ] wordpress.org listing shows the new version and `Tested up to`
25 changes: 22 additions & 3 deletions src/release-workflows.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,15 +8,34 @@ const {
const rootDir = path.resolve(__dirname, '..');

describe('release workflows', () => {
test('tag release workflow dispatches the WordPress.org deploy workflow', () => {
test('release workflow dispatches the WordPress.org deploy workflow on the release tag', () => {
const releaseWorkflow = fs.readFileSync(
path.join(rootDir, '.github/workflows/release.yml'),
'utf8'
);

expect(releaseWorkflow).toMatch(/actions:\s+write/u);
expect(releaseWorkflow).toContain(
'gh workflow run wp-deploy.yml --ref "$GITHUB_REF_NAME"'
'gh workflow run wp-deploy.yml --ref "$RELEASE_TAG"'
);
});

test('release workflow runs on a tag, a version bump on main, or by hand', () => {
const releaseWorkflow = fs.readFileSync(
path.join(rootDir, '.github/workflows/release.yml'),
'utf8'
);

expect(releaseWorkflow).toMatch(/tags:\s+- 'v\*'/u);
expect(releaseWorkflow).toMatch(
/branches:\s+- main\s+paths:\s+- 'bibliography-builder\.php'/u
);
expect(releaseWorkflow).toContain('workflow_dispatch:');
// An existing release is left alone, so re-runs and unrelated edits
// to the main file are no-ops.
expect(releaseWorkflow).toContain('gh release view "v${version}"');
expect(releaseWorkflow).toContain(
'tag_name: ${{ steps.version.outputs.tag }}'
);
});

Expand All @@ -27,7 +46,7 @@ describe('release workflows', () => {
);

expect(releaseWorkflow).toContain(
'node scripts/extract-changelog-section.js "$GITHUB_REF_NAME" > output/release/release-notes.md'
'node scripts/extract-changelog-section.js "$RELEASE_TAG" > output/release/release-notes.md'
);
expect(releaseWorkflow).toContain(
'body_path: output/release/release-notes.md'
Expand Down
Loading