Skip to content

Plugin Release

Plugin Release #121

# Plugin release automation
#
# Builds the plugin for release candidates and stable releases.
#
# Creates the release branch, the actual release on GitHub, and the correct tag.
#
# For new major releases, the action should be run from the `main` branch.
# For patch releases, the action should be run from the corresponding release branch (e.g. `release/1.2.0`)
name: Plugin Release
on:
workflow_dispatch:
inputs:
version:
description: 'Plugin version (e.g. 1.2.3 or 7.2.0-rc.1)'
required: true
permissions:
contents: read
env:
PLUGIN_VERSION: ${{ github.event.inputs.version }}
TAG_NAME: 'v${{ github.event.inputs.version }}'
IS_RC: ${{ contains(github.event.inputs.version, 'rc') }}
IS_PATCH_RELEASE: ${{ startsWith(github.ref, 'refs/heads/release/') }}
GIT_AUTHOR_EMAIL: 94923726+googleforcreators-bot@users.noreply.github.com
GIT_AUTHOR_NAME: googleforcreators-bot
GIT_COMMITTER_EMAIL: 94923726+googleforcreators-bot@users.noreply.github.com
GIT_COMMITTER_NAME: googleforcreators-bot
GITHUB_REPO_ID: 235435637
jobs:
# Perform some sanity checks at the beginning to avoid surprises.
checks:
name: Checks
runs-on: ubuntu-latest
timeout-minutes: 5
# This step requires additional review
# See https://docs.github.com/en/actions/reference/environments
environment: Production
steps:
- name: Harden Runner
uses: step-security/harden-runner@91182cccc01eb5e619899d80e4e971d6181294a7
with:
egress-policy: audit # TODO: change to 'egress-policy: block' after couple of runs
- name: Checkout
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683
- name: Verify semver compatibility
run: |
if [[ $PLUGIN_VERSION =~ $SEMVER_VERSION_REGEX ]]; then
echo "Given plugin version string is a valid semver version"
else
echo "Given plugin version string is not a valid semver version"
exit 1
fi
env:
SEMVER_VERSION_REGEX: ^([0-9]+)\.([0-9]+)\.([0-9]+)(-([0-9A-Za-z-]+(\.[0-9A-Za-z-]+)*))?(\+[0-9A-Za-z-]+)?$
- name: Verify release does not exist yet
run: |
if git describe --abbrev=0 --tags --match "$TAG_NAME" &>/dev/null; then
echo "The planned plugin version already exists!"
exit 1
fi
# - name: Ensure RC exists for stable release
# if: ${{ ! contains(github.event.inputs.version, 'rc') }}
# run: |
# VERSION_WITHOUT_SUFFIX=${PLUGIN_VERSION/-rc.*/}
# BRANCH=release/$VERSION_WITHOUT_SUFFIX
#
# if [[ -z $(git ls-remote origin $BRANCH) ]]; then
# echo "No release branch exists for this planned stable release"
# exit 1
# fi
#
# git checkout --track origin/$BRANCH
#
# if ! git describe --abbrev=0 --tags --match "$TAG_NAME-rc.*" &>/dev/null; then
# echo "No RC exists for this planned stable release"
# exit 1
# fi
- name: Ensure readme.txt contains changelog
run: |
VERSION_WITHOUT_SUFFIX=${PLUGIN_VERSION/-rc.*/}
CHANGELOG_REGEX="= $VERSION_WITHOUT_SUFFIX ="
if ! grep -q -P "$CHANGELOG_REGEX" readme.txt; then
echo "No changelog found in readme.txt"
exit 1
fi
# Get the current CDN assets version.
# If the static assets on the CDN have changed since the last release,
# bump the assets version accordingly in the GoogleForCreators/wp.stories.google repo.
assets-version:
name: Prepare static assets
runs-on: ubuntu-latest
timeout-minutes: 10
needs: [checks]
steps:
- name: Harden Runner
uses: step-security/harden-runner@91182cccc01eb5e619899d80e4e971d6181294a7
with:
egress-policy: audit # TODO: change to 'egress-policy: block' after couple of runs
# TODO: Define behavior for patch releases.
#
# A patch release is done from a specific release branch instead of `main`
#
# Patch releases must not necessarily copy assets from `main`.
# Given the following assets versions:
# main 1 2 3 4 <- next major release
# ^
# |
# current branch
#
# The patch release should probably get version 3.1 or similar,
# since version 4 is already used by the next major release.
#
# Right now, this is needs to be done manually for patch releases,
# otherwise the assets version is left unchanged here.
# Grab current assets version from `web-stories.php` and pass on to next steps.
# - name: Checkout
# uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683
# with:
# ref:
# - name: Get current assets version
# id: base_assets_version
# run: |
# BASE_ASSETS_VERSION=main
# if [[ $(cat web-stories.php) =~ $ASSETS_VERSION_REGEX ]]; then
# BASE_ASSETS_VERSION=${BASH_REMATCH[1]}
# fi
# echo "BASE_ASSETS_VERSION=$BASE_ASSETS_VERSION" >> $GITHUB_OUTPUT
# env:
# ASSETS_VERSION_REGEX: "https://wp.stories.google/static/([^']+)"
- name: Checkout wp.stories.google
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683
with:
repository: GoogleForCreators/wp.stories.google
lfs: true
# Needed so the below commits will trigger a website deployment.
token: ${{ secrets.GOOGLEFORCREATORS_BOT_TOKEN }}
- name: Authenticate
uses: google-github-actions/auth@6fc4af4b145ae7821d527454aa9bd537d1f2dc5f
with:
credentials_json: ${{ secrets.GCP_SA_KEY }}
- name: Setup Cloud SDK
uses: google-github-actions/setup-gcloud@6189d56e4096ee891640bb02ac264be376592d6a
with:
project_id: ${{ secrets.GCP_PROJECT_ID }}
# For release candidates of new major releases:
#
# 1. Get highest assets version
# 2. Compare with `main`
# 3. If they differ:
# 3.1 Set new_version = version+1
# 3.2 Copy `main` to new_version
# 3.2 Push new directory
# 4. Else, keep currently highest version
- name: Prepare assets for RC
if: ${{ contains(github.event.inputs.version, 'rc') && ! startsWith(github.ref, 'refs/heads/release/') }}
run: |
LATEST_ASSETS_VERSION=$(gsutil ls gs://web-stories-wp-cdn-assets/ | sed 's/gs:\/\/web-stories-wp-cdn-assets\///' | sed 's/\///' | sort -n | tail -1)
NEW_ASSETS_VERSION=$LATEST_ASSETS_VERSION
NUMBER_OF_NEW_ASSETS=$(ls main | wc -l)
if [[ "0" -eq NUMBER_OF_NEW_ASSETS ]]; then
echo "No new assets found. Not uploading anything."
else
echo "New assets found."
NEW_ASSETS_VERSION=$((LATEST_ASSETS_VERSION+1))
echo "Copying existing assets over to new version."
gsutil -m rsync -r gs://web-stories-wp-cdn-assets/$LATEST_ASSETS_VERSION gs://web-stories-wp-cdn-assets/$NEW_ASSETS_VERSION
echo "Uploading new assets to new version."
gsutil -m rsync -r -x '(^|/)\.' main gs://web-stories-wp-cdn-assets/$NEW_ASSETS_VERSION
rm -rf main/*
echo "Updating LATEST_ASSETS_VERSION Firebase env variable."
echo "LATEST_ASSETS_VERSION=$NEW_ASSETS_VERSION" > ../../packages/functions/.env
git add ../../packages/functions/.env
git add .
git status
git commit -m "Preparing assets for plugin release $PLUGIN_VERSION"
git pull --rebase
git push origin main
fi
echo "Assets version for this release: $NEW_ASSETS_VERSION"
mkdir -p assets_version
echo $NEW_ASSETS_VERSION > assets_version/assets_version.txt
echo "ASSETS_VERSION=${NEW_ASSETS_VERSION}" >> $GITHUB_ENV
working-directory: public/static
env:
BASE_ASSETS_VERSION: main
# Uploads an empty file just so we have something to download in the next step
# Essentially a no-op.
- name: Prepare assets for stable release
if: ${{ ! contains(github.event.inputs.version, 'rc') || startsWith(github.ref, 'refs/heads/release/') }}
run: |
mkdir -p assets_version
echo "" > assets_version/assets_version.txt
- name: Upload assets version
uses: actions/upload-artifact@b4b15b8c7c6ac21ea08fcf65892d2ee8f75cf882
with:
name: assets-version
path: public/static/assets_version
- name: Write summary
run: |
echo "Preparing assets for release" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
echo "Assets version: $ASSETS_VERSION" >> $GITHUB_STEP_SUMMARY
env:
ASSETS_VERSION: ${{ env.ASSES_VERSION }}
build:
name: Build new version
runs-on: ubuntu-latest
timeout-minutes: 20
needs: [assets-version]
outputs:
release_branch: ${{ steps.release_branch.outputs.release_branch }}
release_name: ${{ steps.release_branch.outputs.release_name }}
steps:
- name: Harden Runner
uses: step-security/harden-runner@91182cccc01eb5e619899d80e4e971d6181294a7
with:
egress-policy: audit # TODO: change to 'egress-policy: block' after couple of runs
- name: Checkout
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683
with:
fetch-depth: 0 # 0 indicates all history for all branches and tags.
token: ${{ secrets.GOOGLEFORCREATORS_BOT_TOKEN }}
- name: Download assets version
uses: actions/download-artifact@fa0a91b85d4f404e444e00e005971372dc801d16
with:
name: assets-version
continue-on-error: true
- name: Retrieve assets version
id: assets_version
run: |
echo "ASSETS_VERSION=$(cat assets_version.txt)" >> $GITHUB_ENV
rm -rf assets_version.txt
continue-on-error: true
- name: Setup Node
uses: actions/setup-node@39370e3970a6d050c480ffad4ff0ed4d3fdee5af
with:
node-version-file: '.nvmrc'
cache: npm
- name: Setup PHP
uses: shivammathur/setup-php@e6f75134d35752277f093989e72e140eaa222f35
with:
php-version: '8.0'
coverage: none
tools: composer
- name: Install dependencies
run: |
npm ci
env:
PUPPETEER_SKIP_DOWNLOAD: true
- name: Install PHP dependencies
uses: ramsey/composer-install@57532f8be5bda426838819c5ee9afb8af389d51a
with:
composer-options: '--prefer-dist --no-progress --no-interaction'
- name: Setup Bun
uses: oven-sh/setup-bun@4bc047ad259df6fc24a6c9b0f9a0cb08cf17fbe5
with:
bun-version: latest
- name: Create release branch
id: release_branch
run: |
VERSION_WITHOUT_SUFFIX=${PLUGIN_VERSION/-rc.*/}
BRANCH=release/$VERSION_WITHOUT_SUFFIX
# Patch releases are already on the correct branch.
if $IS_PATCH_RELEASE; then
BRANCH=${GITHUB_REF#refs/heads/}
echo "release_branch=${BRANCH}" >> $GITHUB_OUTPUT
exit 0
fi
if [[ -z $(git ls-remote origin $BRANCH) ]]; then
git checkout -b $BRANCH
else
git checkout --track origin/$BRANCH
fi
echo "release_branch=${BRANCH/-rc./ RC}" >> $GITHUB_OUTPUT
RELEASE_NAME=${PLUGIN_VERSION/-rc./ RC}
echo "release_name=${RELEASE_NAME}" >> $GITHUB_OUTPUT
- name: Update assets version
run: bun run workflow:assets-version $ASSETS_VERSION
if: ${{ env.ASSETS_VERSION }}
env:
ASSETS_VERSION: ${{ env.ASSETS_VERSION }}
- name: Commit assets version bump
run: |
git add web-stories.php
git status
git diff --staged --quiet && echo 'No changes to commit; exiting!' && exit 0
git commit -m "Update assets version to $ASSETS_VERSION"
git push -u origin HEAD
if: ${{ env.ASSETS_VERSION }}
env:
ASSETS_VERSION: ${{ env.ASSETS_VERSION }}
- name: Update plugin version
run: bun run workflow:version $PLUGIN_VERSION
# Commit the plugin version bump if it was successful.
# It's also possible that there were no changes, for example if the
# workflow was run a second time and the commit has already happened,
# but the process later failed.
# This allows re-running the workflow again without aborting in this case.
- name: Commit plugin version bump
id: plugin_version_bump
run: |
git add web-stories.php
git status
if git diff --cached --exit-code > /dev/null; then
git commit -m "Prepare release $PLUGIN_VERSION"
git push -u origin HEAD
echo "changes=yes" >> $GITHUB_ENV
else
echo 'No changes to commit; exiting!'
echo "changes=no" >> $GITHUB_ENV
fi
# Only non-patch release version bumps should be cherry picked to main
# This will cherry-pick the last commit from the release branch, as
# we only want the plugin version bump, not the assets version bump.
#
# Cherry-picking is only done if there actually was
# a version bump in the previous step.
- name: Cherry-pick to main
run: |
git checkout main
git cherry-pick $BRANCH
git pull --rebase
git push
git checkout $BRANCH
if: ${{ ! startsWith(github.ref, 'refs/heads/release/') && env.changes == 'yes' }}
env:
BRANCH: ${{ steps.release_branch.outputs.release_branch }}
- name: Build plugin
run: bun run build:js
- name: Bundle regular version
run: bun run workflow:build-plugin -- --zip web-stories.zip
- name: Bundle development version
run: |
rm -rf assets/css/* assets/js/*
npx webpack --env=development
bun run workflow:build-plugin -- --zip web-stories-dev.zip
- name: Prepare release artifacts
run: |
mkdir -p build/release-assets
mv build/*.zip build/release-assets/
- name: Upload artifacts
uses: actions/upload-artifact@b4b15b8c7c6ac21ea08fcf65892d2ee8f75cf882
with:
name: release-assets
path: build/release-assets
create-release:
name: Create Release
runs-on: ubuntu-latest
timeout-minutes: 5
needs: [build]
steps:
- name: Harden Runner
uses: step-security/harden-runner@91182cccc01eb5e619899d80e4e971d6181294a7
with:
egress-policy: audit # TODO: change to 'egress-policy: block' after couple of runs
- name: Checkout
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683
- name: Download release artifacts
uses: actions/download-artifact@fa0a91b85d4f404e444e00e005971372dc801d16
with:
name: release-assets
path: build
- name: Publish Release
id: create_release
uses: softprops/action-gh-release@e7a8f85e1c67a31e6ed99a94b41bd0b71bbee6b8
with:
tag_name: ${{ env.TAG_NAME }}
name: ${{ env.release_name }}
target_commitish: ${{ steps.release_branch.outputs.release_branch || github.ref }}
prerelease: ${{ env.IS_RC }}
generate_release_notes: true
files: |
build/web-stories.zip
build/web-stories-dev.zip
fail_on_unmatched_files: true
token: ${{ secrets.GOOGLEFORCREATORS_BOT_TOKEN }}
# Post-release version bumps for non-patch releases.
post-release:
name: Post-release version bump
needs: [create-release]
runs-on: ubuntu-latest
if: ${{ ! startsWith(github.ref, 'refs/heads/release/') && ! contains(github.event.inputs.version, 'rc') }}
steps:
- name: Harden Runner
uses: step-security/harden-runner@91182cccc01eb5e619899d80e4e971d6181294a7
with:
egress-policy: audit # TODO: change to 'egress-policy: block' after couple of runs
- name: Checkout
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683
with:
ref: main
token: ${{ secrets.GOOGLEFORCREATORS_BOT_TOKEN }}
- name: Setup Node
uses: actions/setup-node@39370e3970a6d050c480ffad4ff0ed4d3fdee5af
with:
node-version-file: '.nvmrc'
cache: npm
- name: Install dependencies
run: npm ci
env:
PUPPETEER_SKIP_DOWNLOAD: true
- name: Setup Bun
uses: oven-sh/setup-bun@4bc047ad259df6fc24a6c9b0f9a0cb08cf17fbe5
with:
bun-version: latest
# If we're releasing 1.6.0, bump version on main to 1.7.0-alpha.0.
- name: Update plugin version
run: npm run workflow:version -- --increment preminor --preid alpha
- name: Commit changes
run: |
git add web-stories.php
git status
git diff --staged --quiet && echo 'No changes to commit; exiting!' && exit 1
git commit -m "Post-release version bump"
git pull --rebase
git push -u origin HEAD
# Stable releases are automatically deployed to WordPress.org.
# TODO: Consider also deploying other types of releases (RC, beta), but without bumping the stable tag.
# This way we could offer users a way to beta test the plugin.
# See http://plugins.svn.wordpress.org/buddypress/tags/ and https://wordpress.org/plugins/bp-beta-tester/ for inspiration.
deploy:
name: Deploy plugin to WordPress.org
runs-on: ubuntu-latest
timeout-minutes: 10
needs: [create-release]
if: ${{ ! contains(github.event.inputs.version, 'rc') }}
env:
PLUGIN_REPO_URL: 'https://plugins.svn.wordpress.org/web-stories'
STABLE_TAG_REGEX: 'Stable tag:\s*(.+)'
SVN_USERNAME: ${{ secrets.SVN_USERNAME }}
SVN_PASSWORD: ${{ secrets.SVN_PASSWORD }}
steps:
- name: Harden Runner
uses: step-security/harden-runner@91182cccc01eb5e619899d80e4e971d6181294a7
with:
egress-policy: audit # TODO: change to 'egress-policy: block' after couple of runs
- name: Download release artifacts
uses: actions/download-artifact@fa0a91b85d4f404e444e00e005971372dc801d16
with:
name: release-assets
path: release-assets
- name: Check out trunk folder
run: svn checkout $PLUGIN_REPO_URL svn --username "$SVN_USERNAME"
- name: Get previous stable tag
id: get_previous_stable_tag
# Returns the whole matching line.
run: echo stable_tag=$(grep -P "$STABLE_TAG_REGEX" ./svn/trunk/readme.txt) >> $GITHUB_ENV
- name: Delete everything in trunk
run: find . -maxdepth 1 -not -name ".svn" -not -name "." -not -name ".." -exec rm -rf {} +
working-directory: ./svn/trunk
- name: Unzip release asset into trunk
run: |
unzip release-assets/web-stories.zip
mv web-stories/* svn/trunk
env:
PLUGIN_URL: ${{ github.event.release.assets[0].browser_download_url }}
- name: Replace stable tag placeholder with pre-existing stable tag
run: |
sed -r -i "s/${STABLE_TAG_REGEX}/${STABLE_TAG}/g" ./readme.txt
working-directory: ./svn/trunk
env:
STABLE_TAG: ${{ env.stable_tag }}
# Note: Creating the tag trigger an email confirmation that needs to be confirmed by someone with commit access.
- name: Commit changes to trunk
run: |
svn st | grep '^?' | awk '{print $2}' | xargs -r svn add
svn st | grep '^!' | awk '{print $2}' | xargs -r svn rm
svn commit -m "Committing version $PLUGIN_VERSION" \
--no-auth-cache --non-interactive --username "$SVN_USERNAME" --password "$SVN_PASSWORD"
working-directory: ./svn
# Copy trunk to the new tag directly on the server.
# Not done in the same commit as the changes to trunk in order to reduce number of file operations
# and to prevent potential timeouts.
# See https://developer.wordpress.org/plugins/wordpress-org/how-to-use-subversion/#create-tags-from-trunk
- name: Create the SVN tag
run: |
svn cp "$PLUGIN_REPO_URL/trunk" "$PLUGIN_REPO_URL/tags/$PLUGIN_VERSION" \
-m "Tagging version $PLUGIN_VERSION" \
--no-auth-cache --non-interactive --username "$SVN_USERNAME" --password "$SVN_PASSWORD"
working-directory: ./svn
# It's recommended to run this only after the tag was successfully created.
# Otherwise, if there were any errors, we risk changing this to a tag that doesn't exist.
# The actual release still needs to be confirmed via email.
- name: Update stable tag
working-directory: ./svn
run: |
sed -r -i "s/${STABLE_TAG_REGEX}/Stable tag: ${PLUGIN_VERSION}/g" ./trunk/readme.txt
svn commit -m "Releasing version $PLUGIN_VERSION" \
--no-auth-cache --non-interactive --username "$SVN_USERNAME" --password "$SVN_PASSWORD"