Skip to content
Open
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
78 changes: 73 additions & 5 deletions .github/workflows/release-plugin.yml
Original file line number Diff line number Diff line change
@@ -1,25 +1,79 @@
name: Deploy to WordPress.org

# Releases whenever master carries a version that has not been tagged yet, so
# merging a PR that bumps the version ships it. Pushes without a version bump
# find the tag already exists and skip everything after the check.
#
# Tags are created by this workflow, not pushed by hand. Pushing a tag no longer
# triggers a release, and the tag this workflow pushes with GITHUB_TOKEN does
# not start any other workflow run. To retry a failed release, use "Re-run
# failed jobs".
on:
push:
tags:
- "*"
branches:
- master
workflow_dispatch:

# One release run at a time, so two quick merges cannot deploy the same version
# twice. A run that has started is never cancelled part way through a deploy.
concurrency:
group: release-plugin
cancel-in-progress: false

jobs:
check:
name: Check version
runs-on: ubuntu-latest
if: github.ref == 'refs/heads/master'
outputs:
version: ${{ steps.version.outputs.version }}
release: ${{ steps.tag.outputs.release }}
steps:
- uses: actions/checkout@v4

- name: Check version numbers and changelog
id: version
run: echo "version=$(./scripts/check-version.sh)" >> "$GITHUB_OUTPUT"

- name: Check whether this version has been released
id: tag
env:
VERSION: ${{ steps.version.outputs.version }}
run: |
if git ls-remote --exit-code --tags origin "refs/tags/$VERSION" > /dev/null; then
echo "Tag $VERSION already exists, nothing to release."
echo "release=false" >> "$GITHUB_OUTPUT"
else
echo "Tag $VERSION does not exist yet, releasing."
echo "release=true" >> "$GITHUB_OUTPUT"
fi

php-tests:
needs: check
if: needs.check.outputs.release == 'true'
uses: ./.github/workflows/php-tests.yml

jest-tests:
needs: check
if: needs.check.outputs.release == 'true'
uses: ./.github/workflows/jest-tests.yml

e2e:
needs: check
if: needs.check.outputs.release == 'true'
uses: ./.github/workflows/e2e.yml

tag:
release:
name: Build plugin and deploy to WordPress.org
runs-on: ubuntu-latest
needs: [php-tests, jest-tests, e2e]
needs: [check, php-tests, jest-tests, e2e]
if: needs.check.outputs.release == 'true'
permissions:
contents: write
env:
VERSION: ${{ needs.check.outputs.version }}
steps:
- uses: actions/checkout@master
- uses: actions/checkout@v4

- name: Setup PHP
uses: shivammathur/setup-php@v2
Expand All @@ -40,6 +94,8 @@ jobs:
run: |
rm common-knowledge-join-flow.zip

# Skips the SVN commit if this version is already on WordPress.org, so
# re-running after a later step fails is safe.
- name: WordPress Plugin Deploy
uses: 10up/action-wordpress-plugin-deploy@stable
env:
Expand All @@ -48,7 +104,19 @@ jobs:
SLUG: common-knowledge-join-flow
BUILD_DIR: dist/common-knowledge-join-flow

# Tagged only after the deploy succeeds, because the check job treats an
# existing tag as already released.
- name: Tag the release
run: |
if git ls-remote --exit-code --tags origin "refs/tags/$VERSION" > /dev/null; then
echo "Tag $VERSION already exists."
else
git tag "$VERSION" "$GITHUB_SHA"
git push origin "refs/tags/$VERSION"
fi

- name: Create GitHub Release
uses: softprops/action-gh-release@v2
with:
tag_name: ${{ env.VERSION }}
generate_release_notes: true
18 changes: 18 additions & 0 deletions .github/workflows/script-tests.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
name: Script Tests

on:
push:
branches:
- master
pull_request:
workflow_call:

jobs:
script-tests:
name: Script tests
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4

- name: Test version check
run: ./scripts/check-version.test.sh
14 changes: 9 additions & 5 deletions readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -351,23 +351,27 @@ This produces a deployable WordPress plugin in `packages/join-block`.

Releases are automated via GitHub Actions (`.github/workflows/release-plugin.yml`).

1. Bump the version:
1. In the PR that should ship, bump the version and add changelog entries:

```bash
./scripts/bump-version.sh # auto-increments patch
./scripts/bump-version.sh 1.4.0 # or specify a version
```

2. Commit and tag:
2. Commit the bump to the PR:

```bash
git add -A
git commit -m "Bump version to 1.4.0"
git tag 1.4.0
git push && git push --tags
```

The GitHub Action will build, package, and deploy to the WordPress.org plugin repository automatically. Monitor progress in the Actions tab.
3. Merge the PR.

On every push to `master`, the workflow reads the version from `packages/join-block/join.php`. If no tag exists for it yet, it runs the PHP, Jest and E2E tests, builds the plugin, deploys it to the WordPress.org plugin repository, then creates the tag and the GitHub Release. If the tag already exists, nothing is released. Monitor progress in the Actions tab.

The workflow fails before releasing if the version in `join.php`, the `Stable tag:` in `readme.txt` and the `release:` in `packages/join-flow/src/index.tsx` disagree, or if `readme.txt` has no changelog entry for the version. Run `./scripts/check-version.sh` to check this locally.

Don't push tags by hand: a tag push no longer triggers a release, and a hand-made tag would make the workflow skip that version. If a release fails, use "Re-run failed jobs" on the run. Re-running is safe: the deploy skips a version already on WordPress.org, and the tag is only created after the deploy succeeds.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Don't write this kind of documentation which says a change occurred. Documentation should detail the current status, not some legacy status that people don't need to know about.


### Manual deployment

Expand Down
3 changes: 1 addition & 2 deletions scripts/bump-version.sh
Original file line number Diff line number Diff line change
Expand Up @@ -122,6 +122,5 @@ echo ""
echo "Next steps:"
echo " 1. Review changes: git diff"
echo " 2. Commit: git add -A && git commit -m 'Bump version to $NEW_VERSION'"
echo " 3. Tag: git tag $NEW_VERSION"
echo " 4. Push: git push && git push --tags"
echo " 3. Push and merge the PR. Merging to master releases $NEW_VERSION, so don't tag it by hand."

61 changes: 61 additions & 0 deletions scripts/check-version.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
#!/bin/bash

# Checks that the plugin version is the same in every place it is recorded,
# and that readme.txt has a changelog entry for it. Prints the version.
#
# Usage: ./scripts/check-version.sh [project root]

set -e

print_error() {
echo "ERROR: $1" >&2
}

SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
PROJECT_ROOT="${1:-$(cd "$SCRIPT_DIR/.." && pwd)}"

README_TXT="$PROJECT_ROOT/packages/join-block/readme.txt"
JOIN_PHP="$PROJECT_ROOT/packages/join-block/join.php"
INDEX_TSX="$PROJECT_ROOT/packages/join-flow/src/index.tsx"

for FILE in "$README_TXT" "$JOIN_PHP" "$INDEX_TSX"; do
if [ ! -f "$FILE" ]; then
print_error "$FILE not found"
exit 1
fi
done

PHP_VERSION=$(sed -n 's/^ \* Version: *\([^ ]*\) *$/\1/p' "$JOIN_PHP" | head -n 1)
README_VERSION=$(sed -n 's/^Stable tag: *\([^ ]*\) *$/\1/p' "$README_TXT" | head -n 1)
TSX_VERSION=$(sed -n 's/^ *release: "\([^"]*\)".*$/\1/p' "$INDEX_TSX" | head -n 1)

if ! [[ "$PHP_VERSION" =~ ^[0-9]+\.[0-9]+\.[0-9]+$ ]]; then
print_error "Could not read an X.Y.Z version from the Version header in packages/join-block/join.php (found '$PHP_VERSION')"
exit 1
fi

if [ "$README_VERSION" != "$PHP_VERSION" ] || [ "$TSX_VERSION" != "$PHP_VERSION" ]; then
print_error "Version numbers disagree:"
echo " packages/join-block/join.php Version: $PHP_VERSION" >&2
echo " packages/join-block/readme.txt Stable tag: $README_VERSION" >&2
echo " packages/join-flow/src/index.tsx release: $TSX_VERSION" >&2
echo "Run ./scripts/bump-version.sh to update all three together." >&2
exit 1
fi

CHANGELOG_ENTRIES=$(awk -v heading="= $PHP_VERSION =" '
/^== Changelog ==/ { in_changelog = 1; next }
in_changelog && /^== / { exit }
in_changelog && $0 == heading { in_entry = 1; next }
in_entry && /^= / { exit }
in_entry && /^\* / { count++ }
END { print count + 0 }
' "$README_TXT")

if [ "$CHANGELOG_ENTRIES" -eq 0 ]; then
print_error "packages/join-block/readme.txt has no changelog entry for $PHP_VERSION"
echo "Add a '= $PHP_VERSION =' heading with at least one '* ' line under '== Changelog =='." >&2
exit 1
fi

echo "$PHP_VERSION"
99 changes: 99 additions & 0 deletions scripts/check-version.test.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
#!/bin/bash

# Tests for check-version.sh. Run with: ./scripts/check-version.test.sh

set -u

SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
CHECK="$SCRIPT_DIR/check-version.sh"
FIXTURES=$(mktemp -d)
trap 'rm -rf "$FIXTURES"' EXIT
FAILURES=0

# make_fixture <join.php version> <readme.txt stable tag> <index.tsx release> <changelog heading> [changelog line]
make_fixture() {
local root
root=$(mktemp -d "$FIXTURES/project.XXXXXX")
mkdir -p "$root/packages/join-block" "$root/packages/join-flow/src"
cat > "$root/packages/join-block/join.php" <<EOF
<?php

/**
* Plugin Name: Common Knowledge Join Flow
* Version: $1
*/
EOF
cat > "$root/packages/join-block/readme.txt" <<EOF
=== Common Knowledge Join Flow ===
Stable tag: $2

== Changelog ==

= $4 =
${5-* Something changed}

= 1.0.0 =
* First release

== Upgrade Notice ==

= $1 =
* An upgrade notice, not a changelog entry
EOF
cat > "$root/packages/join-flow/src/index.tsx" <<EOF
init({
release: "$3"
});
EOF
echo "$root"
}

expect_pass() {
local name="$1" root="$2" expected="$3" output
if output=$("$CHECK" "$root" 2>&1) && [ "$output" = "$expected" ]; then
echo "ok - $name"
else
echo "not ok - $name: expected success printing '$expected', got: $output"
FAILURES=$((FAILURES + 1))
fi
}

expect_fail() {
local name="$1" root="$2" message="$3" output
if output=$("$CHECK" "$root" 2>&1); then
echo "not ok - $name: expected failure, got: $output"
FAILURES=$((FAILURES + 1))
elif [[ "$output" != *"$message"* ]]; then
echo "not ok - $name: expected message containing '$message', got: $output"
FAILURES=$((FAILURES + 1))
else
echo "ok - $name"
fi
}

expect_pass "matching versions with a changelog entry" \
"$(make_fixture 1.2.3 1.2.3 1.2.3 1.2.3)" "1.2.3"

expect_fail "readme.txt stable tag differs" \
"$(make_fixture 1.2.3 1.2.2 1.2.3 1.2.3)" "Version numbers disagree"

expect_fail "index.tsx release differs" \
"$(make_fixture 1.2.3 1.2.3 1.2.2 1.2.3)" "Version numbers disagree"

expect_fail "no changelog entry for the version" \
"$(make_fixture 1.2.3 1.2.3 1.2.3 1.2.2)" "no changelog entry for 1.2.3"

expect_fail "changelog heading with no entries" \
"$(make_fixture 1.2.3 1.2.3 1.2.3 1.2.3 "")" "no changelog entry for 1.2.3"

expect_fail "version header is not X.Y.Z" \
"$(make_fixture 1.2 1.2 1.2 1.2)" "Could not read an X.Y.Z version"

expect_pass "real project files" "$SCRIPT_DIR/.." \
"$(sed -n 's/^Stable tag: *//p' "$SCRIPT_DIR/../packages/join-block/readme.txt")"

if [ "$FAILURES" -gt 0 ]; then
echo "$FAILURES test(s) failed"
exit 1
fi
echo "All tests passed"
Loading