Skip to content

Notify mentioned users and thread followers in Notes #153124

Notify mentioned users and thread followers in Notes

Notify mentioned users and thread followers in Notes #153124

name: Build Gutenberg Plugin Zip
on:
pull_request:
push:
branches:
- trunk
- 'release/**'
- 'wp/**'
workflow_dispatch:
inputs:
version:
description: 'rc or stable?'
required: true
# Cancels previous non-release workflow runs that have not completed.
# Workflow-dispatched release runs must not cancel an in-flight run after it may have
# pushed version bump commits.
concurrency:
# The concurrency group contains a shared release group for dispatches,
# the branch name for pull requests, or the commit hash for any other events.
group: ${{ github.workflow }}-${{ github.event_name == 'workflow_dispatch' && 'release' || ( github.event_name == 'pull_request' && github.head_ref || github.sha ) }}
cancel-in-progress: ${{ github.event_name != 'workflow_dispatch' }}
# Disable permissions for all available scopes by default.
# Any needed permissions should be configured at the job level.
permissions: {}
jobs:
compute-stable-branches:
name: Compute current and next stable release branches
runs-on: 'ubuntu-24.04'
permissions:
contents: read
if: ${{ github.event_name == 'workflow_dispatch' }}
outputs:
current_stable_branch: ${{ steps.get_branches.outputs.current_stable_branch }}
next_stable_branch: ${{ steps.get_branches.outputs.next_stable_branch }}
steps:
- name: Get current and next stable release branches
id: get_branches
run: |
curl \
-H "Accept: application/vnd.github.v3+json" \
-o latest.json \
"https://api.github.com/repos/${GITHUB_REPOSITORY}/releases/latest"
LATEST_STABLE_TAG="$(jq --raw-output '.tag_name' latest.json)"
# shellcheck disable=SC2034
IFS='.' read -r LATEST_STABLE_MAJOR LATEST_STABLE_MINOR LATEST_STABLE_PATCH <<< "${LATEST_STABLE_TAG#v}"
echo "current_stable_branch=release/${LATEST_STABLE_MAJOR}.${LATEST_STABLE_MINOR}" >> "$GITHUB_OUTPUT"
if [[ "${LATEST_STABLE_MINOR}" == "9" ]]; then
echo "next_stable_branch=release/$((LATEST_STABLE_MAJOR + 1)).0" >> "$GITHUB_OUTPUT"
else
echo "next_stable_branch=release/${LATEST_STABLE_MAJOR}.$((LATEST_STABLE_MINOR + 1))" >> "$GITHUB_OUTPUT"
fi
preflight-release-token:
name: Preflight release token
runs-on: 'ubuntu-24.04'
environment: 'GitHub Releases'
permissions: {}
if: ${{ github.event_name == 'workflow_dispatch' }}
steps:
# Request the same elevated permissions as create-release so invalid
# credentials or unavailable requested permissions fail before version
# bump commits are pushed.
- name: Generate application token
uses: actions/create-github-app-token@bcd2ba49218906704ab6c1aa796996da409d3eb1 # v3.2.0
id: app-token
with:
client-id: ${{ vars.APP_CLIENT_ID }}
private-key: ${{ secrets.APP_PRIVATE_KEY }}
permission-contents: write
permission-workflows: write
bump-version:
name: Bump version
runs-on: 'ubuntu-24.04'
environment: 'GitHub Releases'
needs: [compute-stable-branches, preflight-release-token]
if: |
github.event_name == 'workflow_dispatch' && (
(
github.ref == 'refs/heads/trunk' ||
endsWith( github.ref, needs.compute-stable-branches.outputs.next_stable_branch )
) && (
github.event.inputs.version == 'rc' ||
github.event.inputs.version == 'stable'
) || (
startsWith( github.ref, 'refs/heads/release/' ) &&
github.event.inputs.version == 'stable'
)
)
outputs:
old_version: ${{ steps.get_version.outputs.old_version }}
new_version: ${{ steps.get_version.outputs.new_version }}
release_branch: ${{ steps.get_version.outputs.release_branch }}
release_branch_commit: ${{ steps.commit_version_bump_to_release_branch.outputs.version_bump_commit }}
trunk_commit: ${{ steps.commit_version_bump_to_trunk.outputs.version_bump_commit }}
steps:
- name: Generate application token
uses: actions/create-github-app-token@bcd2ba49218906704ab6c1aa796996da409d3eb1 # v3.2.0
id: app-token
with:
client-id: ${{ vars.APP_CLIENT_ID }}
private-key: ${{ secrets.APP_PRIVATE_KEY }}
permission-contents: write
- name: Checkout code
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
with:
token: ${{ steps.app-token.outputs.token }}
show-progress: ${{ runner.debug == '1' && 'true' || 'false' }}
persist-credentials: true
- name: Compute old and new version
id: get_version
env:
VERSION: ${{ github.event.inputs.version }}
run: |
OLD_VERSION="$(jq --raw-output '.version' package.json)"
echo "old_version=${OLD_VERSION}" >> "$GITHUB_OUTPUT"
if [[ "$VERSION" == 'stable' ]]; then
NEW_VERSION="$(npx semver "$OLD_VERSION" -i patch)"
else
if [[ "$OLD_VERSION" == *"rc"* ]]; then
NEW_VERSION="$(npx semver "$OLD_VERSION" -i prerelease)"
else
# WordPress version guidelines: If minor is 9, bump major instead.
IFS='.' read -r -a OLD_VERSION_ARRAY <<< "$OLD_VERSION"
if [[ ${OLD_VERSION_ARRAY[1]} == "9" ]]; then
NEW_VERSION="$(npx semver "$OLD_VERSION" -i major)-rc.1"
else
NEW_VERSION="$(npx semver "$OLD_VERSION" -i minor)-rc.1"
fi
fi
fi
echo "new_version=${NEW_VERSION}" >> "$GITHUB_OUTPUT"
IFS='.' read -r -a NEW_VERSION_ARRAY <<< "$NEW_VERSION"
RELEASE_BRANCH="release/${NEW_VERSION_ARRAY[0]}.${NEW_VERSION_ARRAY[1]}"
echo "release_branch=${RELEASE_BRANCH}" >> "$GITHUB_OUTPUT"
- name: Configure Git user name
run: git config user.name '${{ steps.app-token.outputs.app-slug }}[bot]'
- name: Configure Git user email
env:
APP_USER_ID: ${{ vars.APP_USER_ID }}
run: git config user.email "${APP_USER_ID}+${{ steps.app-token.outputs.app-slug }}[bot]@users.noreply.github.com"
- name: Create and switch to release branch
if: |
github.event.inputs.version == 'rc' &&
! contains( steps.get_version.outputs.old_version, 'rc' )
env:
TARGET_BRANCH: ${{ steps.get_version.outputs.release_branch }}
run: git checkout -b "$TARGET_BRANCH"
- name: Switch to release branch
if: |
github.event.inputs.version == 'stable' ||
contains( steps.get_version.outputs.old_version, 'rc' )
env:
TARGET_BRANCH: ${{ steps.get_version.outputs.release_branch }}
run: |
git fetch --depth=1 origin "$TARGET_BRANCH"
git checkout "$TARGET_BRANCH"
- name: Update plugin version
env:
VERSION: ${{ steps.get_version.outputs.new_version }}
OLD_VERSION: ${{ steps.get_version.outputs.old_version }}
run: |
jq --tab --arg version "${VERSION}" '.version = $version' package.json > package.json.tmp
mv package.json.tmp package.json
jq --tab --arg version "${VERSION}" '.version = $version | .packages[""].version = $version' package-lock.json > package-lock.json.tmp
mv package-lock.json.tmp package-lock.json
sed -i "s/${OLD_VERSION}/${VERSION}/g" gutenberg.php
- name: Commit the version bump to the release branch
id: commit_version_bump_to_release_branch
env:
TARGET_BRANCH: ${{ steps.get_version.outputs.release_branch }}
VERSION: ${{ steps.get_version.outputs.new_version }}
run: |
git add gutenberg.php package.json package-lock.json
git commit -m "Bump plugin version to ${VERSION}"
git push --set-upstream origin "$TARGET_BRANCH"
echo "version_bump_commit=$(git rev-parse --verify --short HEAD)" >> "$GITHUB_OUTPUT"
- name: Fetch trunk
if: ${{ github.ref != 'refs/heads/trunk' }}
run: git fetch --depth=1 origin trunk
- name: Cherry-pick the version bump commit to trunk
id: commit_version_bump_to_trunk
env:
TARGET_BRANCH: ${{ steps.get_version.outputs.release_branch }}
OLD_VERSION: ${{ steps.get_version.outputs.old_version }}
run: |
git checkout trunk
git pull
TRUNK_VERSION="$(jq --raw-output '.version' package.json)"
if [[ "$OLD_VERSION" == "$TRUNK_VERSION" ]]; then
git cherry-pick "$TARGET_BRANCH"
git push
echo "version_bump_commit=$(git rev-parse --verify --short HEAD)" >> "$GITHUB_OUTPUT"
fi
build:
name: ${{ matrix.IS_GUTENBERG_PLUGIN && 'Build Release Artifact' || 'Build assets for wordpress-develop' }}
runs-on: 'ubuntu-24.04'
permissions:
contents: read
needs: bump-version
if: |
always() && (
github.event_name == 'pull_request' ||
(
github.event_name == 'workflow_dispatch' &&
needs.bump-version.result == 'success'
) ||
(
github.event_name != 'workflow_dispatch' &&
github.repository == 'WordPress/gutenberg'
)
)
strategy:
matrix:
IS_GUTENBERG_PLUGIN: [true, false]
steps:
- name: Checkout code
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
with:
ref: ${{ needs.bump-version.outputs.release_branch || github.ref }}
show-progress: ${{ runner.debug == '1' && 'true' || 'false' }}
persist-credentials: false
- name: Use desired version of Node.js
uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0
with:
node-version-file: '.nvmrc'
check-latest: true
- name: Configure build for wordpress-develop
if: ${{ ! matrix.IS_GUTENBERG_PLUGIN }}
run: jq --tab '.wpPlugin.name = "wp"' package.json > package.json.tmp && mv package.json.tmp package.json
- name: Build Gutenberg plugin ZIP file
run: ./bin/build-plugin-zip.sh
env:
NO_CHECKS: 'true'
IS_GUTENBERG_PLUGIN: ${{ matrix.IS_GUTENBERG_PLUGIN }}
IS_WORDPRESS_CORE: ${{ ! matrix.IS_GUTENBERG_PLUGIN }}
- name: Upload artifact
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: gutenberg-${{ ! matrix.IS_GUTENBERG_PLUGIN && 'wordpress-develop' || 'plugin' }}
path: ./gutenberg.zip
- name: Build release notes draft
if: ${{ matrix.IS_GUTENBERG_PLUGIN && needs.bump-version.outputs.new_version }}
env:
VERSION: ${{ needs.bump-version.outputs.new_version }}
run: |
IFS='.' read -r -a VERSION_ARRAY <<< "${VERSION}"
MILESTONE="Gutenberg ${VERSION_ARRAY[0]}.${VERSION_ARRAY[1]}"
RELEASE_NOTES_TMP="$(mktemp)"
trap 'rm -f "$RELEASE_NOTES_TMP"' EXIT
npm run other:changelog -- --milestone="$MILESTONE" --unreleased > "$RELEASE_NOTES_TMP"
sed '1,6d' "$RELEASE_NOTES_TMP" > release-notes.txt
if [[ ! -s release-notes.txt ]]; then
echo "Release notes are empty for milestone \"$MILESTONE\"." >&2
exit 1
fi
if [[ "${VERSION}" != *"rc"* ]]; then
# Include previous RCs' release notes, if any
CHANGELOG_REGEX="=\s[0-9]+\.[0-9]+\.[0-9]+(-rc\.[0-9]+)?\s="
RC_REGEX="=\s${VERSION}(-rc\.[0-9]+)?\s="
awk "/${RC_REGEX}/ {found=1;print;next} /${CHANGELOG_REGEX}/ {found=0} found" changelog.txt >> release-notes.txt
fi
- name: Upload release notes artifact
if: ${{ matrix.IS_GUTENBERG_PLUGIN && needs.bump-version.outputs.new_version }}
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: release-notes
path: ./release-notes.txt
# Publishes the built plugin zip file to the GitHub Container registry.
publish-to-container-registry:
name: Publish to GitHub Container Registry
runs-on: 'ubuntu-24.04'
needs: ['bump-version', 'build']
permissions:
contents: read
packages: write
if: |
always() &&
needs.build.result == 'success' &&
github.repository == 'WordPress/gutenberg' &&
(
github.event_name == 'push' ||
github.event_name == 'workflow_dispatch' ||
(
github.event_name == 'pull_request' &&
github.event.pull_request.head.repo.full_name == github.repository
)
)
steps:
- name: Download build artifact
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
with:
name: gutenberg-wordpress-develop
- name: Unzip zip artifact
run: unzip gutenberg.zip && rm gutenberg.zip
# Noting the hash helps keep track of the commit the zip was built from.
# For pull_request events, github.sha is the ephemeral merge commit; use the PR head SHA instead
# so the recorded value points to a real, branch-reachable commit.
- name: Add a file for tracking the SHA value used.
env:
SOURCE_SHA: ${{ github.event_name == 'pull_request' && github.event.pull_request.head.sha || github.sha }}
run: echo "$SOURCE_SHA" > .gutenberg-hash
- name: Recompress as a .gz file
run: tar -czf ${{ runner.temp }}/gutenberg.tar.gz . && mv ${{ runner.temp }}/gutenberg.tar.gz .
- name: Login to GitHub Container Registry
uses: docker/login-action@af1e73f918a031802d376d3c8bbc3fe56130a9b0 # v4.4.0
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
# OCI Registry As Storage (ORAS) provides a way to push and pull non-image, generic artifacts (.gz files,
# for example) to container registries such as GitHub Container Registry.
- name: Setup ORAS
uses: oras-project/setup-oras@38de303aac69abb66f3e6255b7198bff35f323e3 # v2.0.0
# The owner/repository is hard-coded because capital letters are not allowed when publishing to GitHub
# Container Registry. The organization name is WordPress, so ${{ github.repository }} causes an error.
# See https://github.com/orgs/community/discussions/27086 for more info.
- name: Push built plugin .tar.gz file to GitHub Container Registry
env:
SOURCE_SHA: ${{ github.event_name == 'pull_request' && github.event.pull_request.head.sha || github.sha }}
run: |
oras push "ghcr.io/wordpress/gutenberg/gutenberg-wp-develop-build:${SOURCE_SHA}" \
"gutenberg.tar.gz:application/gzip" \
--annotation "org.opencontainers.image.description=Gutenberg plugin build for commit ${SOURCE_SHA}" \
--annotation "org.opencontainers.image.source=https://github.com/${{ github.repository }}" \
--annotation "org.opencontainers.image.revision=${SOURCE_SHA}"
# Compute the mutable tag from the event. GitHub Actions expressions can't do string replacement,
# so the branch-name-to-tag conversion (release/19.5 -> release-19.5) must happen in bash.
- name: Determine mutable tag
id: mutable_tag
env:
EVENT_NAME: ${{ github.event_name }}
GITHUB_REF: ${{ github.ref }}
PR_NUMBER: ${{ github.event.pull_request.number }}
run: |
if [[ "$EVENT_NAME" == "pull_request" ]]; then
TAG="pr-${PR_NUMBER}"
else
BRANCH="${GITHUB_REF#refs/heads/}"
TAG="${BRANCH//\//-}"
fi
TAG="$(echo "$TAG" | tr '[:upper:]' '[:lower:]')"
echo "value=${TAG}" >> "$GITHUB_OUTPUT"
# Add a mutable tag alongside the SHA tag so developers can pull "the latest build for this stream"
# (e.g. :trunk, :pr-12345, :release-19.5) without needing to update a pinned SHA.
- name: Add mutable tag for the built plugin
env:
SOURCE_SHA: ${{ github.event_name == 'pull_request' && github.event.pull_request.head.sha || github.sha }}
MUTABLE_TAG: ${{ steps.mutable_tag.outputs.value }}
run: |
oras tag "ghcr.io/wordpress/gutenberg/gutenberg-wp-develop-build:${SOURCE_SHA}" "${MUTABLE_TAG}"
revert-version-bump:
name: Revert version bump if build failed
needs: [bump-version, build]
runs-on: 'ubuntu-24.04'
environment: 'GitHub Releases'
if: |
always() &&
needs.build.result == 'failure' &&
needs.bump-version.outputs.release_branch_commit
steps:
- name: Generate application token
uses: actions/create-github-app-token@bcd2ba49218906704ab6c1aa796996da409d3eb1 # v3.2.0
id: app-token
with:
client-id: ${{ vars.APP_CLIENT_ID }}
private-key: ${{ secrets.APP_PRIVATE_KEY }}
permission-contents: write
- name: Checkout code
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
with:
fetch-depth: 2
ref: ${{ needs.bump-version.outputs.release_branch }}
token: ${{ steps.app-token.outputs.token }}
show-progress: ${{ runner.debug == '1' && 'true' || 'false' }}
persist-credentials: true
- name: Configure Git user name
run: git config user.name '${{ steps.app-token.outputs.app-slug }}[bot]'
- name: Configure Git user email
env:
APP_USER_ID: ${{ vars.APP_USER_ID }}
run: git config user.email "${APP_USER_ID}+${{ steps.app-token.outputs.app-slug }}[bot]@users.noreply.github.com"
- name: Revert version bump commit on release branch
if: |
github.event.inputs.version == 'stable' ||
contains( needs.bump-version.outputs.old_version, 'rc' )
env:
RELEAD_BRANCH_COMMIT: ${{ needs.bump-version.outputs.release_branch_commit }}
RELEASE_BRANCH: ${{ needs.bump-version.outputs.release_branch }}
run: |
git revert --no-edit "$RELEAD_BRANCH_COMMIT"
git push --set-upstream origin "$RELEASE_BRANCH"
- name: Delete release branch if it was only just created for the RC
if: |
github.event.inputs.version == 'rc' &&
! contains( needs.bump-version.outputs.old_version, 'rc' )
env:
RELEASE_BRANCH: ${{ needs.bump-version.outputs.release_branch }}
run: |
git push origin :"$RELEASE_BRANCH"
- name: Revert version bump on trunk
if: ${{ needs.bump-version.outputs.trunk_commit }}
env:
TRUNK_COMMIT: ${{ needs.bump-version.outputs.trunk_commit }}
run: |
git fetch --depth=2 origin trunk
git checkout trunk
git revert --no-edit "$TRUNK_COMMIT"
git push --set-upstream origin trunk
create-release:
name: Create Release Draft and Attach Asset
needs: [bump-version, build]
runs-on: 'ubuntu-24.04'
environment: 'GitHub Releases'
if: ${{ needs.bump-version.outputs.new_version }}
steps:
- name: Generate application token
uses: actions/create-github-app-token@bcd2ba49218906704ab6c1aa796996da409d3eb1 # v3.2.0
id: app-token
with:
client-id: ${{ vars.APP_CLIENT_ID }}
private-key: ${{ secrets.APP_PRIVATE_KEY }}
permission-contents: write
permission-workflows: write
- name: Set Release Version
id: get_release_version
env:
VERSION: ${{ needs.bump-version.outputs.new_version }}
run: echo "version=$(echo "$VERSION" | cut -d / -f 3 | sed 's/-rc./ RC/' )" >> "$GITHUB_OUTPUT"
- name: Download Plugin Zip Artifact
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
with:
name: gutenberg-plugin
- name: Download Release Notes Artifact
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
with:
name: release-notes
- name: Create Release Draft and Attach Asset
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
env:
ASSET_CONTENT_TYPE: application/zip
ASSET_NAME: gutenberg.zip
ASSET_PATH: gutenberg.zip
BODY_PATH: release-notes.txt
COMMITISH: ${{ needs.bump-version.outputs.release_branch || github.ref }}
PRERELEASE: ${{ contains(needs.bump-version.outputs.new_version, 'rc') }}
RELEASE_NAME: ${{ steps.get_release_version.outputs.version }}
VERSION: ${{ needs.bump-version.outputs.new_version }}
with:
github-token: ${{ steps.app-token.outputs.token }}
script: |
const fs = require('fs');
const release = await github.rest.repos.createRelease({
owner: context.repo.owner,
repo: context.repo.repo,
tag_name: `v${process.env.VERSION}`,
name: process.env.RELEASE_NAME,
target_commitish: process.env.COMMITISH,
draft: true,
prerelease: process.env.PRERELEASE === 'true',
body: fs.readFileSync(process.env.BODY_PATH, 'utf8')
});
const asset = fs.readFileSync(process.env.ASSET_PATH);
await github.rest.repos.uploadReleaseAsset({
owner: context.repo.owner,
repo: context.repo.repo,
release_id: release.data.id,
name: process.env.ASSET_NAME,
data: asset,
headers: {
'content-type': process.env.ASSET_CONTENT_TYPE,
'content-length': asset.length
}
});
npm-publish:
name: Publish WordPress packages to npm
runs-on: 'ubuntu-24.04'
permissions:
contents: read
environment: WordPress packages
needs: [bump-version, build]
if: ${{ endsWith( needs.bump-version.outputs.new_version, '-rc.1' ) }}
steps:
- name: Checkout (for CLI)
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
with:
path: main
ref: trunk
show-progress: ${{ runner.debug == '1' && 'true' || 'false' }}
persist-credentials: false
- name: Checkout (for publishing)
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
with:
path: publish
# Later, we switch this branch in the script that publishes packages.
ref: trunk
token: ${{ secrets.GUTENBERG_TOKEN }}
show-progress: ${{ runner.debug == '1' && 'true' || 'false' }}
persist-credentials: true
- name: Configure git user name and email (for publishing)
run: |
cd publish
git config user.name "Gutenberg Repository Automation"
git config user.email gutenberg@wordpress.org
- name: Setup Node.js
uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0
with:
node-version-file: 'main/.nvmrc'
registry-url: 'https://registry.npmjs.org'
check-latest: true
- name: Publish packages to npm ("latest" dist-tag)
run: |
cd main
npm ci
npm exec --no release-cli -- npm-latest --semver minor --ci --repository-path ../publish
env:
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}