Skip to content

Testing

Testing #119

Workflow file for this run

name: Build
on:
push:
branches:
- 'trunk'
- 'prerelease'
# The `**/*/` works around the fact that GitHub considers a leading `**/` as meaning "zero or more path components" where we want "one or more".
- '**/*/branch-**'
pull_request:
concurrency:
# Cancel concurrent jobs on pull_request but not push, by including the run_id in the concurrency group for the latter.
group: build-${{ case( github.event_name == 'push', github.run_id, 'pr' ) }}-${{ github.ref }}
cancel-in-progress: true
permissions:
# actions/checkout, actions/upload-artifact, actions/download-artifact
contents: read
env:
COMPOSER_ROOT_VERSION: "dev-trunk"
# This string is used as a unique identifier of test reminder comments on PRs.
TEST_COMMENT_INDICATOR: "<!-- wpcom-reminder-comment -->"
jobs:
prepare:
name: Prepare build matrix
runs-on: ubuntu-latest
timeout-minutes: 5 # 2026-04-07: Should take about a minute.
permissions:
# actions/checkout
contents: read
# Comment posting
pull-requests: write
outputs:
changed_projects: ${{ steps.changed.outputs.projects }}
build_matrix: ${{ steps.changed.outputs.build-matrix }}
comment_id: ${{ steps.check-test-reminder-comment.outputs.result }}
steps:
- uses: actions/checkout@v6
# For pull requests, list-changed-projects.sh needs the merge base.
# But it doesn't have to be checked out.
- name: Deepen to merge base
if: github.event_name == 'pull_request'
uses: ./.github/actions/deepen-to-merge-base
with:
checkout: false
- name: Setup tools
uses: ./.github/actions/tool-setup
- name: Pnpm install
run: pnpm install
# For trunk merges, we build everything but split into two jobs: 'wpcom' and 'non-wpcom'. The 'wpcom' job pushes certain plugins to mirrors.
# For everything else, we do a single build job.
- name: Detect changed projects
id: changed
run: |
if [[ "$GITHUB_EVENT_NAME" == 'push' && "$GITHUB_REF" == 'refs/heads/trunk' ]]; then
pnpm jetpack dependencies list --extra=build --ignore-root | sort > all.txt
pnpm jetpack dependencies list --extra=build --ignore-root --add-dependencies plugins/jetpack plugins/mu-wpcom-plugin | sort > wpcom.txt
comm -23 all.txt wpcom.txt > non-wpcom.txt
CHANGED=$( jq -ncR 'reduce inputs as $i ({}; .[$i] |= true)' all.txt )
echo "projects=${CHANGED}" >> "$GITHUB_OUTPUT"
WPCOM=$( jq -ncR 'reduce inputs as $i ({}; .[$i] |= true)' wpcom.txt )
NONWPCOM=$( jq -ncR 'reduce inputs as $i ({}; .[$i] |= true)' non-wpcom.txt )
MATRIX=$( jq -nc --arg WPCOM "$WPCOM" --arg NONWPCOM "$NONWPCOM" '[
{
what: "wpcom",
changed: $WPCOM,
},
{
what: "non-wpcom",
changed: $NONWPCOM,
}
]' )
echo "build-matrix=$MATRIX" >> "$GITHUB_OUTPUT"
else
CHANGED="$(EXTRA=build .github/files/list-changed-projects.sh)"
echo "projects=${CHANGED}" >> "$GITHUB_OUTPUT"
MATRIX=$( jq -nc --arg CHANGED "$CHANGED" '[
{
what: "all",
changed: $CHANGED,
}
]' )
echo "build-matrix=$MATRIX" >> "$GITHUB_OUTPUT"
fi
- name: Check if a WordPress.com test reminder comment is needed.
id: check-test-reminder-comment
uses: actions/github-script@v8
if: ${{ github.event_name == 'pull_request' && github.event.pull_request.head.repo.full_name == github.event.pull_request.base.repo.full_name }}
env:
CHANGED: ${{ steps.changed.outputs.projects }}
with:
script: |
const { checkTestPendingComment } = require('.github/files/build-reminder-comment/check-test-reminder-comment.js')
const data = await checkTestPendingComment( github, context, core );
return data;
build:
name: Build ${{ matrix.name }} projects
runs-on: ubuntu-latest
needs: prepare
env:
FILENAME: partial-build-${{ matrix.name }}.tar.xz
CHANGED: ${{ matrix.changed }}
# Hard-code a specific directory to avoid paths in vendor/composer/installed.json changing every build.
BUILD_BASE: /tmp/jetpack-build
permissions:
# .github/actions/turnstile
actions: read
# actions/checkout, actions/upload-artifact
contents: read
# push-to-mirrors uses secrets.API_TOKEN_GITHUB, so no need for permissions
strategy:
fail-fast: false
matrix:
include: ${{ fromJson( needs.prepare.outputs.build-matrix ) }}
# We don't set a job-level timeout because the turnstyle step may take arbitrarily long.
steps:
- uses: actions/checkout@v6
timeout-minutes: 1 # 2026-04-07: Successful runs seem to take a few seconds
- name: Setup tools
uses: ./.github/actions/tool-setup
timeout-minutes: 3 # 2026-04-07: Successful runs seem to take ~30 seconds
- name: Pnpm install
timeout-minutes: 5 # 2025-11-20: The pnpm install may take a few minutes on cache miss.
run: pnpm install
# We need the tree (but not the blob) for packages that will have -alpha version numbers so the timestamp appending works right.
# We also need the tree for js-packages/social-logos/src/svg so the font build will work right.
- name: Deepen tree for packages
timeout-minutes: 1 # 2026-04-07: Successful runs seem to take a few seconds
run: |
mapfile -t PROJECTS < <(jq -r 'to_entries[] | select( .value ) | .key' <<<"$CHANGED")
if [[ ${#PROJECTS[@]} -gt 0 ]]; then
depth=$( git rev-list --count --first-parent HEAD )
[[ "$depth" -lt 1000 ]] && depth=1000
BASE=$PWD
REF=$(git rev-parse HEAD)
for SLUG in $(pnpm jetpack dependencies list --add-dependencies --extra="build" --ignore-root "${PROJECTS[@]}"); do
cd "$BASE/projects/$SLUG/"
if [[ "$SLUG" == packages/* ]]; then
SUBDIR=.
# Only deepen if it'll be a -alpha, i.e. it has changelog entries
CHANGES_DIR="$(jq -r '.extra.changelogger["changes-dir"] // "changelog"' composer.json)"
[[ -d "$CHANGES_DIR" && -n "$(ls -- "$CHANGES_DIR")" ]] || continue
elif [[ "$SLUG" == js-packages/social-logos ]]; then
SUBDIR=./src/svg
else
continue
fi
echo "Checking depth for $SLUG"
while git log --format='%h, %D,' -1 "$SUBDIR" | grep ', grafted,'; do
depth=$((depth * 2))
echo "::group::Deepen to $depth"
echo "/usr/bin/git -c protocol.version=2 fetch --no-tags --prune --progress --no-recurse-submodules --depth=$depth --filter=blob:none origin $REF"
/usr/bin/git -c protocol.version=2 fetch --no-tags --prune --progress --no-recurse-submodules --depth=$depth --filter=blob:none origin "$REF"
echo "::endgroup::"
done
done
fi
- name: Build changed projects
timeout-minutes: 25 # 2026-04-07: Successful runs seem to take ~15 minutes.
run: |
mapfile -t PROJECTS < <(jq -r 'to_entries[] | select( .value ) | .key' <<<"$CHANGED")
if [[ ${#PROJECTS[@]} -eq 0 ]]; then
echo "Nothing to build. Generating empty artifact."
mkdir "$BUILD_BASE"
touch "$BUILD_BASE/mirrors.txt"
else
pnpm jetpack build -v --no-pnpm-install --for-mirrors="$BUILD_BASE" "${PROJECTS[@]}"
fi
- name: Create archive
timeout-minutes: 5 # 2026-04-07: Takes a minute or two.
run: |
tar --owner=0 --group=0 --xz -cvvf "$FILENAME" -C "$BUILD_BASE" --transform 's,^\.,build,' .
# In case the "Push wpcom projects" below runs, it'll want a modified mirrors.txt
printf '%s\n' plugins/jetpack plugins/mu-wpcom-plugin > "$BUILD_BASE/mirrors.txt"
- name: Store build as artifact
uses: actions/upload-artifact@v7
timeout-minutes: 5 # 2026-04-07: Should take just a few seconds.
with:
path: ${{ env.FILENAME }}
retention-days: 1
# Already compressed.
compression-level: 0
archive: false
- name: Wait for prior instances of the workflow to finish
if: github.event_name == 'push' && github.repository == 'Automattic/jetpack' && matrix.name == 'wpcom'
uses: ./.github/actions/turnstile
- name: Push wpcom projects
if: github.event_name == 'push' && github.repository == 'Automattic/jetpack' && matrix.name == 'wpcom'
uses: ./projects/github-actions/push-to-mirrors
with:
source-directory: ${{ github.workspace }}
token: ${{ secrets.API_TOKEN_GITHUB }}
upstream-ref-since: '2024-04-10' # No point in checking 12 years of earlier commits from before we started adding "Upstream-Ref".
username: matticbot
working-directory: ${{ env.BUILD_BASE }}
timeout-minutes: 10 # 2025-11-06: Successful runs seem to take a minute or two.
merge_artifacts:
name: Merge build artifacts
runs-on: ubuntu-latest
timeout-minutes: 10 # 2026-04-07: Should take a minute or two.
needs: [ prepare, build ]
permissions:
# actions/checkout, actions/upload-artifact, actions/download-artifact
contents: read
# Comment posting
pull-requests: write
env:
BUILD_BASE: ${{ github.workspace }}/build
steps:
- uses: actions/checkout@v6
with:
path: monorepo
- name: Download build artifacts
uses: actions/download-artifact@v8
with:
pattern: partial-build-*.tar.xz
- name: Extract and merge build archives
run: |
mkdir merged
touch merged.mirrors.txt
for ARCHIVE in partial-build-*.tar.xz; do
tar --xz -xvvf "$ARCHIVE" build
for MIRROR in $(< build/mirrors.txt ); do
if [[ ! -d "merged/$MIRROR" ]]; then
echo "Copying $MIRROR from $ARCHIVE"
mkdir -p "$( dirname "merged/$MIRROR" )"
mv "build/$MIRROR" "merged/$MIRROR"
echo "$MIRROR" >> merged/mirrors.txt
elif [[ "$MIRROR" == 'Automattic/storybook' || "$MIRROR" == 'Automattic/charts' ]]; then
# Known unstable builds
echo "Ignoring $MIRROR from $ARCHIVE, already copied"
elif diff -ur "build/$MIRROR" "merged/$MIRROR" > diff.txt; then
echo "$MIRROR from $ARCHIVE matches earlier copied build"
else
echo "::warning::Multiple builds for $MIRROR differ!"
echo "::group::Diff for $MIRROR builds"
cat diff.txt
echo "::endgroup::"
fi
done
rm -rf build
done
mv merged build
- name: Filter mirror list for release branch
if: github.ref == 'refs/heads/prerelease' || contains( github.ref, '/branch-' )
working-directory: ./monorepo
run: .github/files/filter-mirrors-for-release-branch.sh
- name: Determine plugins to publish
id: plugins
working-directory: ./monorepo
run: |
jq -r 'if .extra["mirror-repo"] and ( .extra["beta-plugin-slug"] // .extra["wp-plugin-slug"] ) then [ ( input_filename | sub( "/composer\\.json$"; "" ) ), .extra["mirror-repo"], .extra["beta-plugin-slug"] // .extra["wp-plugin-slug"] ] else empty end | @tsv' projects/plugins/*/composer.json | while IFS=$'\t' read -r SRC MIRROR SLUG; do
if [[ -e "$BUILD_BASE/$MIRROR" ]] && grep -q --fixed-strings --line-regexp "$MIRROR" "$BUILD_BASE/mirrors.txt"; then
printf '%s\t%s\t%s\n' "$SRC" "$MIRROR" "$SLUG"
fi
done > "$BUILD_BASE/plugins.tsv"
if [[ ! -s "$BUILD_BASE/plugins.tsv" ]]; then
echo "No plugins were built"
echo "any=false" >> "$GITHUB_OUTPUT"
exit 0
fi
cat "$BUILD_BASE/plugins.tsv"
echo "any=true" >> "$GITHUB_OUTPUT"
- name: Create merged archive
run: |
tar --owner=0 --group=0 --xz -cvvf jetpack-build.tar.xz -C "$BUILD_BASE" --transform 's,^\.,build,' .
# For Store build as artifact (old)
ln jetpack-build.tar.xz build.tar.xz
- name: Prepare plugin zips
if: steps.plugins.outputs.any
id: plugin-zips
env:
SHA: ${{ github.event.pull_request.head.sha || github.sha }}
run: |
mkdir work
mkdir zips
# Current version must compare greather than any previously used current version for this PR.
# Assume GH run IDs are monotonic.
VERSUFFIX="${GITHUB_RUN_ID}-g${SHA:0:8}"
ANY_BUILT=false
while IFS=$'\t' read -r SRC MIRROR SLUG; do
echo "::group::$MIRROR (src=$SRC slug=$SLUG)"
if [[ ! -e "build/$MIRROR" ]]; then
echo "Plugin was not built, skipping."
echo "::endgroup::"
continue
fi
if ! grep -q --fixed-strings --line-regexp "$MIRROR" build/mirrors.txt; then
echo "Plugin is not being mirrored in this build, skipping."
echo "::endgroup::"
continue
fi
# The Jetpack Beta Tester plugin needs the base directory name to be like "${SLUG}-dev", so copy it over.
mv "build/$MIRROR" "work/${SLUG}-dev"
# Copy testing docs that are not included in the mirror.
if [[ -e "$SRC/to-test.md" ]]; then
cp "$SRC/to-test.md" "work/${SLUG}-dev/"
fi
# Extract and update version.
CURRENT_VERSION=$(monorepo/tools/plugin-version.sh "work/${SLUG}-dev/")-$VERSUFFIX
echo "Using version $CURRENT_VERSION"
echo "$CURRENT_VERSION" > "work/${SLUG}-dev/version.txt"
# Don't use plugin-version.sh here, updating JETPACK__VERSION would clutter stats.
sed -i -e 's/Version: .*$/Version: '"$CURRENT_VERSION"'/' "work/${SLUG}-dev"/*.php
# Remove .github directory.
rm -rf "work/${SLUG}-dev/.github"
# Zip the plugin
( cd work && zip -9 -r "../zips/${SLUG}-dev.zip" "${SLUG}-dev" )
ANY_BUILT=true
echo "::endgroup::"
done < build/plugins.tsv
if ! $ANY_BUILT; then
echo "No plugins were built"
fi
echo "any-built=$ANY_BUILT" >> "$GITHUB_OUTPUT"
# 2026-03-02: Temporarily upload the artifact twice, under both old and new names, as we update things using the old name.
- name: Store build as artifact (old)
uses: actions/upload-artifact@v7
with:
name: jetpack-build
path: build.tar.xz
# Retain trunk builds for 7 days so we can manually download for branch comparisons. Branch builds only need one day so the beta builder can slurp it up to distribute.
retention-days: ${{ case( github.ref == 'refs/heads/trunk', 7, 1 ) }}
# Already compressed.
compression-level: 0
- name: Store build as artifact (new)
uses: actions/upload-artifact@v7
with:
path: jetpack-build.tar.xz
# Retain trunk builds for 7 days so we can manually download for branch comparisons. Branch builds only need one day so the beta builder can slurp it up to distribute.
retention-days: ${{ case( github.ref == 'refs/heads/trunk', 7, 1 ) }}
# Already compressed.
compression-level: 0
archive: false
- name: Store plugins.tsv as artifact
if: steps.plugins.outputs.any == 'true'
uses: actions/upload-artifact@v7
with:
path: ${{ env.BUILD_BASE }}/plugins.tsv
# We don't really care about this artifact, its presence is a flag to the post-build job.
retention-days: 1
archive: false
- name: Create plugins artifact
uses: actions/upload-artifact@v7
if: steps.plugin-zips.outputs.any-built == 'true'
with:
name: plugins
path: zips
# Only need to retain for a day since the beta builder slurps it up to distribute.
retention-days: 1
# Already compressed.
compression-level: 0
- name: Update reminder with testing instructions
id: update-reminder-comment
uses: actions/github-script@v8
if: ${{ github.event_name == 'pull_request' && github.event.pull_request.head.repo.full_name == github.event.pull_request.base.repo.full_name && fromJSON(steps.check-test-reminder-comment.outputs.result)['commentId'] != 0 }}
env:
BRANCH_NAME: ${{ github.head_ref }}
DATA: ${{ needs.prepare.outputs.comment_id }}
with:
script: |
const { checkTestReminderComment } = require('./monorepo/.github/files/build-reminder-comment/check-test-reminder-comment.js')
await checkTestReminderComment( github, context, core );
update_mirrors:
name: Push to mirror repos
runs-on: ubuntu-latest
needs: merge_artifacts
permissions:
# .github/actions/turnstile
actions: read
# actions/checkout, actions/download-artifact
contents: read
# push-to-mirrors uses secrets.API_TOKEN_GITHUB, so no need for permissions
if: github.event_name == 'push' && github.repository == 'Automattic/jetpack'
# Not setting a job-level timeout because it would be kind of pointless with the blocking step. Set a step timeout for all other steps instead.
steps:
- uses: actions/checkout@v6
with:
path: monorepo
timeout-minutes: 1 # 2025-11-06: Successful runs seem to take a few seconds
- name: Download build artifact
uses: actions/download-artifact@v8
with:
name: jetpack-build.tar.xz
timeout-minutes: 2 # 2025-11-06: Successful runs normally take a few seconds
- name: Extract build archive
run: tar --xz -xvvf jetpack-build.tar.xz build
timeout-minutes: 1 # 2025-11-06: Successful runs seem to take a few seconds
- name: Wait for prior instances of the workflow to finish
uses: ./monorepo/.github/actions/turnstile
- name: Push changed projects
uses: ./monorepo/projects/github-actions/push-to-mirrors
with:
source-directory: ${{ github.workspace }}/monorepo
token: ${{ secrets.API_TOKEN_GITHUB }}
upstream-ref-since: '2024-04-10' # No point in checking 12 years of earlier commits from before we started adding "Upstream-Ref".
username: matticbot
working-directory: ${{ github.workspace }}/build
timeout-minutes: 10 # 2025-11-06: Successful runs seem to take a minute or two.