From 11fc25cd82828b0126cf21f95eaf2e8c5f084116 Mon Sep 17 00:00:00 2001 From: Jon Waldstein Date: Mon, 5 Oct 2026 12:59:23 -0400 Subject: [PATCH 1/6] chore: open PRs in consumer plugins after a release Co-Authored-By: Claude Opus 5.5 --- .github/workflows/release.yml | 27 +++++++ README.md | 1 + composer.json | 9 ++- dev_scripts/update-consumers.sh | 139 ++++++++++++++++++++++++++++++++ 4 files changed, 174 insertions(+), 2 deletions(-) create mode 100755 dev_scripts/update-consumers.sh diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index cef11f78..4eaeb880 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -43,3 +43,30 @@ jobs: git tag -f "$TAG" git push origin --force "refs/tags/$TAG" + + update-consumers: + name: Open PRs in consumer plugins + # After sync-version, which may move the tag. + needs: sync-version + if: ${{ !github.event.release.prerelease }} + runs-on: blacksmith-4vcpu-ubuntu-2404 + steps: + - uses: actions/checkout@v4 + + - uses: shivammathur/setup-php@v2 + with: + php-version: "8.0" + coverage: none + + # GH_BOT_TOKEN needs push and pull request access to every repo in + # CONSUMERS (dev_scripts/update-consumers.sh), plus read access to their + # private composer dependencies. + - name: Open PRs + env: + GH_TOKEN: ${{ secrets.GH_BOT_TOKEN }} + TAG: ${{ github.event.release.tag_name }} + run: | + gh auth setup-git + git config --global user.name "github-actions[bot]" + git config --global user.email "github-actions[bot]@users.noreply.github.com" + dev_scripts/update-consumers.sh "$TAG" -y diff --git a/README.md b/README.md index 0ebd24fa..631ab10b 100644 --- a/README.md +++ b/README.md @@ -111,6 +111,7 @@ bunx @stellarwp/changelogger write --overwrite-version 1. Run the **Release Prep** workflow (`Actions → Release Prep → Run workflow`). Supply the target branch, version (e.g. `1.2.0`), and the release date (e.g. `2026-04-29`). The workflow bumps the `VERSION` constant, compiles the changelog, and opens a PR automatically. 2. Review and merge the PR. 3. Create a GitHub Release with a new tag in the format `vX.X.X` targeting the merge commit. +4. The **Release** workflow opens a PR in each consumer plugin (GiveWP, Kadence Blocks, tribe-common for The Events Calendar, and LearnDash) that bumps `stellarwp/harbor` to the new version. To rerun it by hand, or to target other repos, run `composer release:update-consumers -- [version] [owner/repo ...]`. The consumer list lives in `dev_scripts/update-consumers.sh`. ## Documentation diff --git a/composer.json b/composer.json index d327fe4e..1d07c912 100644 --- a/composer.json +++ b/composer.json @@ -87,7 +87,11 @@ "phpcs:fix": "phpcbf", "phpcs:ci": "dev_scripts/partial-phpcs.sh", "release:prep": "dev_scripts/release-prep.sh", - "release:set-version": "dev_scripts/set-numeric-version.sh" + "release:set-version": "dev_scripts/set-numeric-version.sh", + "release:update-consumers": [ + "Composer\\Config::disableProcessTimeout", + "dev_scripts/update-consumers.sh" + ] }, "scripts-descriptions": { "test:analysis": "Run static code analysis.", @@ -103,6 +107,7 @@ "compatibility:php-8.4": "Run PHPCompatibilityWP for PHP 8.4.", "compatibility": "Run PHPCompatibilityWP for all supported PHP versions.", "release:prep": "Prepare a release: set version, replace TBD tags, and write changelog. Usage: composer release:prep -- [date]", - "release:set-version": "Replace TBD tags and update Harbor::VERSION. Usage: composer release:set-version -- " + "release:set-version": "Replace TBD tags and update Harbor::VERSION. Usage: composer release:set-version -- ", + "release:update-consumers": "Open PRs bumping Harbor in consumer plugins. Usage: composer release:update-consumers -- [version] [-y] [owner/repo ...]" } } diff --git a/dev_scripts/update-consumers.sh b/dev_scripts/update-consumers.sh new file mode 100755 index 00000000..3a8ce919 --- /dev/null +++ b/dev_scripts/update-consumers.sh @@ -0,0 +1,139 @@ +#!/usr/bin/env bash +# +# Opens a PR on each consumer plugin bumping its stellarwp/harbor requirement +# to a released version. Runs automatically after a release is published +# (.github/workflows/release.yml) and can be run by hand. +# +# Usage: +# composer release:update-consumers -- [version] [-y] [owner/repo ...] +# dev_scripts/update-consumers.sh [version] [-y] [owner/repo ...] +# version Release version, with or without a leading "v". Defaults to +# the newest tag on origin. +# -y, --yes Skip the confirmation prompt (required when there is no TTY). +# owner/repo Consumer repos to update. Defaults to CONSUMERS below. +# +# Each repo gets a chore/bump-harbor- branch cut from its default +# branch with composer.json and composer.lock updated, and a PR against the +# default branch. Re-running refreshes the branch and keeps the open PR. +# +# Requirements: gh (authenticated with push access to every consumer repo), +# composer, git. + +set -euo pipefail + +# The Events Calendar consumes Harbor through tribe-common (its `common` +# submodule), so the PR goes there. +CONSUMERS=( + impress-org/givewp + stellarwp/kadence-blocks + the-events-calendar/tribe-common + stellarwp/learndash-core +) + +PKG="stellarwp/harbor" + +VERSION="" +ASSUME_YES="" +REPOS=() +for arg in "$@"; do + case "$arg" in + -y|--yes) ASSUME_YES=1 ;; + -*) echo "ERROR: unknown flag '$arg'"; exit 1 ;; + */*) REPOS+=("$arg") ;; + *) + [ -z "$VERSION" ] || { echo "ERROR: unexpected argument '$arg'"; exit 1; } + VERSION="$arg" + ;; + esac +done +[ ${#REPOS[@]} -gt 0 ] || REPOS=("${CONSUMERS[@]}") + +command -v gh >/dev/null || { echo "ERROR: gh CLI is required."; exit 1; } +command -v composer >/dev/null || { echo "ERROR: composer is required."; exit 1; } + +if [ -z "$VERSION" ]; then + VERSION="$(git ls-remote --tags --refs origin 'v*' | sed 's|.*refs/tags/||' | sort -V | tail -1)" +fi +VERSION="${VERSION#v}" +if ! [[ "$VERSION" =~ ^[0-9]+\.[0-9]+\.[0-9]+$ ]]; then + echo "ERROR: '$VERSION' is not an x.y.z version." + exit 1 +fi + +BRANCH="chore/bump-harbor-${VERSION}" + +echo "Bump ${PKG} to ^${VERSION} on branch ${BRANCH} in:" +printf ' • %s\n' "${REPOS[@]}" + +if [ -z "$ASSUME_YES" ]; then + [ -t 0 ] || { echo "ERROR: no TTY for confirmation. Pass -y to proceed."; exit 1; } + read -rp "Proceed? [y/N] " REPLY + [[ "$REPLY" == [Yy]* ]] || { echo "Aborted."; exit 0; } +fi + +# Lets composer read private VCS repositories (e.g. Kadence's prophecy-* +# packages) with the same auth gh uses. +COMPOSER_AUTH="{\"github-oauth\":{\"github.com\":\"$(gh auth token)\"}}" +export COMPOSER_AUTH + +TMP="$(mktemp -d)" +trap 'rm -rf "$TMP"' EXIT + +BODY="Updates \`${PKG}\` to [v${VERSION}](https://github.com/stellarwp/harbor/releases/tag/v${VERSION}). + +Only \`composer.json\` and \`composer.lock\` change. Run \`composer install\` to pull in the new version. + +Opened automatically by Harbor's release workflow." + +update_repo() { + local repo="$1" dir="$TMP/${1//\//-}" + + gh repo clone "$repo" "$dir" -- --depth 1 --quiet + cd "$dir" + git checkout --quiet -B "$BRANCH" + + # -w lets Harbor's own dependencies move with it. Extensions are ignored + # because the runner's PHP may not match the plugin's; each plugin pins + # config.platform.php, so the PHP version is still checked. + composer require "${PKG}:^${VERSION}" -w \ + --no-install --no-scripts --no-audit --no-interaction \ + --ignore-platform-req='ext-*' + + git add composer.json composer.lock + if git diff --cached --quiet; then + echo "${repo}: already on ${VERSION}, nothing to do." + return 0 + fi + + git commit --quiet -m "chore: bump ${PKG} to ${VERSION}" + git push --quiet --force -u origin "$BRANCH" + + local pr + pr="$(gh pr list -R "$repo" --head "$BRANCH" --state open --json url -q '.[0].url')" + if [ -n "$pr" ]; then + echo "${repo}: refreshed ${pr}" + else + gh pr create -R "$repo" --head "$BRANCH" \ + --title "Bump ${PKG} to ${VERSION}" --body "$BODY" + fi +} + +FAILED=() +for repo in "${REPOS[@]}"; do + echo "" + echo "== ${repo}" + # Subshell so one failing repo doesn't stop the rest. Not `( … ) || …`: + # that form silently disables errexit inside the subshell. + set +e + ( set -e; update_repo "$repo" ) + status=$? + set -e + [ $status -eq 0 ] || FAILED+=("$repo") +done + +if [ ${#FAILED[@]} -gt 0 ]; then + echo "" + echo "Failed:" + printf ' • %s\n' "${FAILED[@]}" + exit 1 +fi From 4afac9391780835be74f0e5a55c64ccac580874d Mon Sep 17 00:00:00 2001 From: Jon Waldstein Date: Mon, 5 Oct 2026 13:12:40 -0400 Subject: [PATCH 2/6] chore: fix spell check and ignore prerelease tags in update-consumers Co-Authored-By: Claude Opus 5.5 --- .github/workflows/release.yml | 1 + dev_scripts/update-consumers.sh | 6 +++--- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 4eaeb880..553ee570 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -1,3 +1,4 @@ +# cspell:ignore shivammathur name: Release on: diff --git a/dev_scripts/update-consumers.sh b/dev_scripts/update-consumers.sh index 3a8ce919..6a0ed47e 100755 --- a/dev_scripts/update-consumers.sh +++ b/dev_scripts/update-consumers.sh @@ -52,7 +52,7 @@ command -v gh >/dev/null || { echo "ERROR: gh CLI is required."; exit 1; } command -v composer >/dev/null || { echo "ERROR: composer is required."; exit 1; } if [ -z "$VERSION" ]; then - VERSION="$(git ls-remote --tags --refs origin 'v*' | sed 's|.*refs/tags/||' | sort -V | tail -1)" + VERSION="$(git ls-remote --tags --refs origin 'v*' | sed 's|.*refs/tags/||' | grep -E '^v[0-9]+\.[0-9]+\.[0-9]+$' | sort -V | tail -1)" fi VERSION="${VERSION#v}" if ! [[ "$VERSION" =~ ^[0-9]+\.[0-9]+\.[0-9]+$ ]]; then @@ -122,8 +122,8 @@ FAILED=() for repo in "${REPOS[@]}"; do echo "" echo "== ${repo}" - # Subshell so one failing repo doesn't stop the rest. Not `( … ) || …`: - # that form silently disables errexit inside the subshell. + # Child shell so one failing repo does not stop the rest. Not `( … ) || …`: + # that form silently disables errexit inside the child shell. set +e ( set -e; update_repo "$repo" ) status=$? From dfb58affbdf025f34dc9f67bc692c5306ee42090 Mon Sep 17 00:00:00 2001 From: Jon Waldstein Date: Mon, 5 Oct 2026 14:02:58 -0400 Subject: [PATCH 3/6] test: cover update-consumers with bats and add --dry-run Co-Authored-By: Claude Opus 5.5 --- README.md | 2 +- composer.json | 2 +- dev_scripts/update-consumers.sh | 17 +++- tests/shell/update-consumers.bats | 150 ++++++++++++++++++++++++++++++ 4 files changed, 166 insertions(+), 5 deletions(-) create mode 100644 tests/shell/update-consumers.bats diff --git a/README.md b/README.md index dce0d0cb..6b63acf0 100644 --- a/README.md +++ b/README.md @@ -160,7 +160,7 @@ bunx @stellarwp/changelogger write --overwrite-version 1. Run the **Release Prep** workflow (`Actions → Release Prep → Run workflow`). Supply the target branch, version (e.g. `1.2.0`), and the release date (e.g. `2026-04-29`). The workflow bumps the `VERSION` constant, compiles the changelog, and opens a PR automatically. 2. Review and merge the PR. 3. Create a GitHub Release with a new tag in the format `vX.X.X` targeting the merge commit. -4. The **Release** workflow opens a PR in each consumer plugin (GiveWP, Kadence Blocks, tribe-common for The Events Calendar, and LearnDash) that bumps `stellarwp/harbor` to the new version. To rerun it by hand, or to target other repos, run `composer release:update-consumers -- [version] [owner/repo ...]`. The consumer list lives in `dev_scripts/update-consumers.sh`. +4. The **Release** workflow opens a PR in each consumer plugin (GiveWP, Kadence Blocks, tribe-common for The Events Calendar, and LearnDash) that bumps `stellarwp/harbor` to the new version. To rerun it by hand, or to target other repos, run `composer release:update-consumers -- [version] [owner/repo ...]`. Add `--dry-run` to see the change without pushing anything. The consumer list lives in `dev_scripts/update-consumers.sh`. ## Documentation diff --git a/composer.json b/composer.json index 1d07c912..544a83f7 100644 --- a/composer.json +++ b/composer.json @@ -108,6 +108,6 @@ "compatibility": "Run PHPCompatibilityWP for all supported PHP versions.", "release:prep": "Prepare a release: set version, replace TBD tags, and write changelog. Usage: composer release:prep -- [date]", "release:set-version": "Replace TBD tags and update Harbor::VERSION. Usage: composer release:set-version -- ", - "release:update-consumers": "Open PRs bumping Harbor in consumer plugins. Usage: composer release:update-consumers -- [version] [-y] [owner/repo ...]" + "release:update-consumers": "Open PRs bumping Harbor in consumer plugins. Usage: composer release:update-consumers -- [version] [-y] [--dry-run] [owner/repo ...]" } } diff --git a/dev_scripts/update-consumers.sh b/dev_scripts/update-consumers.sh index 6a0ed47e..cf6de1fc 100755 --- a/dev_scripts/update-consumers.sh +++ b/dev_scripts/update-consumers.sh @@ -5,11 +5,13 @@ # (.github/workflows/release.yml) and can be run by hand. # # Usage: -# composer release:update-consumers -- [version] [-y] [owner/repo ...] -# dev_scripts/update-consumers.sh [version] [-y] [owner/repo ...] +# composer release:update-consumers -- [version] [-y] [--dry-run] [owner/repo ...] +# dev_scripts/update-consumers.sh [version] [-y] [--dry-run] [owner/repo ...] # version Release version, with or without a leading "v". Defaults to # the newest tag on origin. # -y, --yes Skip the confirmation prompt (required when there is no TTY). +# --dry-run Clone and run composer, then show the change instead of +# pushing it or opening a PR. Needs no confirmation. # owner/repo Consumer repos to update. Defaults to CONSUMERS below. # # Each repo gets a chore/bump-harbor- branch cut from its default @@ -34,10 +36,12 @@ PKG="stellarwp/harbor" VERSION="" ASSUME_YES="" +DRY_RUN="" REPOS=() for arg in "$@"; do case "$arg" in -y|--yes) ASSUME_YES=1 ;; + --dry-run) DRY_RUN=1 ;; -*) echo "ERROR: unknown flag '$arg'"; exit 1 ;; */*) REPOS+=("$arg") ;; *) @@ -65,7 +69,7 @@ BRANCH="chore/bump-harbor-${VERSION}" echo "Bump ${PKG} to ^${VERSION} on branch ${BRANCH} in:" printf ' • %s\n' "${REPOS[@]}" -if [ -z "$ASSUME_YES" ]; then +if [ -z "$ASSUME_YES" ] && [ -z "$DRY_RUN" ]; then [ -t 0 ] || { echo "ERROR: no TTY for confirmation. Pass -y to proceed."; exit 1; } read -rp "Proceed? [y/N] " REPLY [[ "$REPLY" == [Yy]* ]] || { echo "Aborted."; exit 0; } @@ -106,6 +110,13 @@ update_repo() { fi git commit --quiet -m "chore: bump ${PKG} to ${VERSION}" + + if [ -n "$DRY_RUN" ]; then + git show --stat --format= HEAD + echo "${repo}: dry run, not pushing or opening a PR." + return 0 + fi + git push --quiet --force -u origin "$BRANCH" local pr diff --git a/tests/shell/update-consumers.bats b/tests/shell/update-consumers.bats new file mode 100644 index 00000000..f40d3c39 --- /dev/null +++ b/tests/shell/update-consumers.bats @@ -0,0 +1,150 @@ +#!/usr/bin/env bats +# cspell:ignore NOSYSTEM + +bats_require_minimum_version 1.5.0 + +SCRIPT="$BATS_TEST_DIRNAME/../../dev_scripts/update-consumers.sh" +BRANCH="chore/bump-harbor-1.7.0" + +# Creates a bare repo standing in for a consumer on GitHub, with composer.json +# and composer.lock requiring the given Harbor constraint. +make_remote() { + local name="${1//\//-}" constraint="$2" work="$TEST_DIR/work/${1//\//-}" + rm -rf "$work" "$REMOTES/$name.git" + git init --quiet -b main "$work" + printf '{"require":{"stellarwp/harbor":"%s"}}\n' "$constraint" > "$work/composer.json" + printf 'locked %s\n' "${constraint#^}" > "$work/composer.lock" + git -C "$work" add . + git -C "$work" commit --quiet -m init + git clone --quiet --bare "$work" "$REMOTES/$name.git" +} + +setup() { + TEST_DIR="$(mktemp -d)" + REMOTES="$TEST_DIR/remotes" + export REMOTES + export STUB_LOG="$TEST_DIR/stub.log" + touch "$STUB_LOG" + + # Keep the developer's git config (signing, hooks) out of the test. + export GIT_CONFIG_GLOBAL=/dev/null GIT_CONFIG_NOSYSTEM=1 + export GIT_AUTHOR_NAME=test GIT_AUTHOR_EMAIL=test@example.com + export GIT_COMMITTER_NAME=test GIT_COMMITTER_EMAIL=test@example.com + + mkdir -p "$TEST_DIR/bin" "$REMOTES" + + # gh: clones come from the local bare repos, PR calls are logged. + cat > "$TEST_DIR/bin/gh" <<'SH' +#!/usr/bin/env bash +echo "gh $*" >> "$STUB_LOG" +case "$1 $2" in + "auth token") echo fake-token ;; + "repo clone") git clone --quiet "$REMOTES/${3//\//-}.git" "$4" ;; + "pr list") echo "${GH_OPEN_PR:-}" ;; + "pr create") echo "https://github.com/$4/pull/1" ;; +esac +SH + + # composer: writes the requested constraint the way `composer require` would. + cat > "$TEST_DIR/bin/composer" <<'SH' +#!/usr/bin/env bash +echo "composer $*" >> "$STUB_LOG" +[ "$(basename "$PWD")" != "${COMPOSER_FAIL_REPO:-}" ] || { echo "composer failed" >&2; exit 1; } +constraint="${2#*:}" +printf '{"require":{"stellarwp/harbor":"%s"}}\n' "$constraint" > composer.json +printf 'locked %s\n' "${constraint#^}" > composer.lock +SH + chmod +x "$TEST_DIR/bin/gh" "$TEST_DIR/bin/composer" + export PATH="$TEST_DIR/bin:$PATH" + + for repo in impress-org/givewp stellarwp/kadence-blocks the-events-calendar/tribe-common stellarwp/learndash-core; do + make_remote "$repo" "^1.6" + done + + # A Harbor checkout whose origin has tags, for the default version lookup. + git init --quiet -b main "$TEST_DIR/harbor-work" + git -C "$TEST_DIR/harbor-work" commit --quiet --allow-empty -m init + for tag in v1.6.1 v1.7.0 v2.0.0-rc.1; do + git -C "$TEST_DIR/harbor-work" tag "$tag" + done + git clone --quiet --bare "$TEST_DIR/harbor-work" "$TEST_DIR/harbor.git" + git clone --quiet "$TEST_DIR/harbor.git" "$TEST_DIR/harbor" + cd "$TEST_DIR/harbor" +} + +teardown() { + rm -rf "$TEST_DIR" +} + +remote_file() { + git -C "$REMOTES/${1//\//-}.git" show "$BRANCH:$2" +} + +@test "rejects a version that is not x.y.z" { + run "$SCRIPT" 1.7 -y impress-org/givewp + [ "$status" -eq 1 ] + [[ "$output" == *"ERROR: '1.7' is not an x.y.z version."* ]] +} + +@test "requires -y when there is no TTY" { + run "$SCRIPT" 1.7.0 impress-org/givewp < /dev/null + [ "$status" -eq 1 ] + [[ "$output" == *"Pass -y to proceed."* ]] +} + +@test "defaults to the newest stable tag on origin" { + run "$SCRIPT" --dry-run impress-org/givewp + [ "$status" -eq 0 ] + [[ "$output" == *"Bump stellarwp/harbor to ^1.7.0"* ]] +} + +@test "defaults to every consumer repo" { + run "$SCRIPT" 1.7.0 --dry-run + [ "$status" -eq 0 ] + for repo in impress-org/givewp stellarwp/kadence-blocks the-events-calendar/tribe-common stellarwp/learndash-core; do + [[ "$output" == *"== $repo"* ]] + done +} + +@test "pushes the bump and opens a PR" { + run "$SCRIPT" v1.7.0 -y impress-org/givewp + [ "$status" -eq 0 ] + [[ "$(remote_file impress-org/givewp composer.json)" == *'"^1.7.0"'* ]] + [[ "$(remote_file impress-org/givewp composer.lock)" == "locked 1.7.0" ]] + grep -q "gh pr create -R impress-org/givewp --head $BRANCH" "$STUB_LOG" +} + +@test "refreshes the branch and keeps an open PR" { + export GH_OPEN_PR="https://github.com/impress-org/givewp/pull/9" + run "$SCRIPT" 1.7.0 -y impress-org/givewp + [ "$status" -eq 0 ] + [[ "$output" == *"refreshed https://github.com/impress-org/givewp/pull/9"* ]] + [[ "$(remote_file impress-org/givewp composer.json)" == *'"^1.7.0"'* ]] + run ! grep -q "gh pr create" "$STUB_LOG" +} + +@test "does nothing when the repo is already on the version" { + make_remote impress-org/givewp "^1.7.0" + run "$SCRIPT" 1.7.0 -y impress-org/givewp + [ "$status" -eq 0 ] + [[ "$output" == *"already on 1.7.0, nothing to do."* ]] + run ! git -C "$REMOTES/impress-org-givewp.git" rev-parse --verify --quiet "$BRANCH" + run ! grep -q "gh pr" "$STUB_LOG" +} + +@test "dry run neither pushes nor opens a PR" { + run "$SCRIPT" 1.7.0 --dry-run impress-org/givewp + [ "$status" -eq 0 ] + [[ "$output" == *"impress-org/givewp: dry run"* ]] + run ! git -C "$REMOTES/impress-org-givewp.git" rev-parse --verify --quiet "$BRANCH" + run ! grep -q "gh pr" "$STUB_LOG" +} + +@test "keeps going when one repo fails and exits non-zero" { + export COMPOSER_FAIL_REPO=impress-org-givewp + run "$SCRIPT" 1.7.0 -y impress-org/givewp stellarwp/kadence-blocks + [ "$status" -eq 1 ] + [[ "$output" == *"Failed:"*"impress-org/givewp"* ]] + [[ "$output" != *"Failed:"*"kadence-blocks"* ]] + [[ "$(remote_file stellarwp/kadence-blocks composer.json)" == *'"^1.7.0"'* ]] +} From dc2a9747e9a70c08a3993b6a6800a5c3f3566900 Mon Sep 17 00:00:00 2001 From: Jon Waldstein Date: Mon, 5 Oct 2026 14:12:51 -0400 Subject: [PATCH 4/6] fix: show the staged diff in --dry-run without committing Co-Authored-By: Claude Opus 5.5 --- dev_scripts/update-consumers.sh | 7 +++---- tests/shell/update-consumers.bats | 9 ++++++++- 2 files changed, 11 insertions(+), 5 deletions(-) diff --git a/dev_scripts/update-consumers.sh b/dev_scripts/update-consumers.sh index cf6de1fc..777db4f9 100755 --- a/dev_scripts/update-consumers.sh +++ b/dev_scripts/update-consumers.sh @@ -10,7 +10,7 @@ # version Release version, with or without a leading "v". Defaults to # the newest tag on origin. # -y, --yes Skip the confirmation prompt (required when there is no TTY). -# --dry-run Clone and run composer, then show the change instead of +# --dry-run Clone and run composer, then show the diff instead of # pushing it or opening a PR. Needs no confirmation. # owner/repo Consumer repos to update. Defaults to CONSUMERS below. # @@ -109,14 +109,13 @@ update_repo() { return 0 fi - git commit --quiet -m "chore: bump ${PKG} to ${VERSION}" - if [ -n "$DRY_RUN" ]; then - git show --stat --format= HEAD + git --no-pager diff --cached -- composer.json composer.lock echo "${repo}: dry run, not pushing or opening a PR." return 0 fi + git commit --quiet -m "chore: bump ${PKG} to ${VERSION}" git push --quiet --force -u origin "$BRANCH" local pr diff --git a/tests/shell/update-consumers.bats b/tests/shell/update-consumers.bats index f40d3c39..37b2aa27 100644 --- a/tests/shell/update-consumers.bats +++ b/tests/shell/update-consumers.bats @@ -132,9 +132,16 @@ remote_file() { run ! grep -q "gh pr" "$STUB_LOG" } -@test "dry run neither pushes nor opens a PR" { +@test "dry run shows the diff without committing, pushing or opening a PR" { + # A failing pre-commit hook proves the dry run never commits. + mkdir -p "$TEST_DIR/hooks" + printf '#!/bin/sh\nexit 1\n' > "$TEST_DIR/hooks/pre-commit" + chmod +x "$TEST_DIR/hooks/pre-commit" + export GIT_CONFIG_COUNT=1 GIT_CONFIG_KEY_0=core.hooksPath GIT_CONFIG_VALUE_0="$TEST_DIR/hooks" + run "$SCRIPT" 1.7.0 --dry-run impress-org/givewp [ "$status" -eq 0 ] + [[ "$output" == *'+{"require":{"stellarwp/harbor":"^1.7.0"}}'* ]] [[ "$output" == *"impress-org/givewp: dry run"* ]] run ! git -C "$REMOTES/impress-org-givewp.git" rev-parse --verify --quiet "$BRANCH" run ! grep -q "gh pr" "$STUB_LOG" From ef2d3bcb924d5b7508cd935b80982863adcff9d2 Mon Sep 17 00:00:00 2001 From: Jon Waldstein Date: Mon, 5 Oct 2026 16:29:30 -0400 Subject: [PATCH 5/6] chore: run update-consumers from its own manual workflow Co-Authored-By: Claude Opus 5.5 --- .github/workflows/release.yml | 28 ---------------- .github/workflows/update-consumers.yml | 46 ++++++++++++++++++++++++++ README.md | 2 +- dev_scripts/update-consumers.sh | 6 ++-- 4 files changed, 50 insertions(+), 32 deletions(-) create mode 100644 .github/workflows/update-consumers.yml diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 553ee570..cef11f78 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -1,4 +1,3 @@ -# cspell:ignore shivammathur name: Release on: @@ -44,30 +43,3 @@ jobs: git tag -f "$TAG" git push origin --force "refs/tags/$TAG" - - update-consumers: - name: Open PRs in consumer plugins - # After sync-version, which may move the tag. - needs: sync-version - if: ${{ !github.event.release.prerelease }} - runs-on: blacksmith-4vcpu-ubuntu-2404 - steps: - - uses: actions/checkout@v4 - - - uses: shivammathur/setup-php@v2 - with: - php-version: "8.0" - coverage: none - - # GH_BOT_TOKEN needs push and pull request access to every repo in - # CONSUMERS (dev_scripts/update-consumers.sh), plus read access to their - # private composer dependencies. - - name: Open PRs - env: - GH_TOKEN: ${{ secrets.GH_BOT_TOKEN }} - TAG: ${{ github.event.release.tag_name }} - run: | - gh auth setup-git - git config --global user.name "github-actions[bot]" - git config --global user.email "github-actions[bot]@users.noreply.github.com" - dev_scripts/update-consumers.sh "$TAG" -y diff --git a/.github/workflows/update-consumers.yml b/.github/workflows/update-consumers.yml new file mode 100644 index 00000000..a027f1a2 --- /dev/null +++ b/.github/workflows/update-consumers.yml @@ -0,0 +1,46 @@ +# cspell:ignore shivammathur +# Opens a PR in each consumer plugin bumping stellarwp/harbor to a released +# version. Run it by hand once a release is out and checked. +name: Update Consumers + +on: + workflow_dispatch: + inputs: + version: + description: "Harbor version (e.g. 1.7.0). Defaults to the newest stable tag." + required: false + type: string + dry_run: + description: "Show the changes without pushing or opening PRs" + required: false + type: boolean + default: false + +jobs: + update-consumers: + name: Open PRs in consumer plugins + runs-on: blacksmith-4vcpu-ubuntu-2404 + steps: + - uses: actions/checkout@v4 + + - uses: shivammathur/setup-php@v2 + with: + php-version: "8.0" + coverage: none + + # GH_BOT_TOKEN needs push and pull request access to every repo in + # CONSUMERS (dev_scripts/update-consumers.sh), plus read access to their + # private composer dependencies. + - name: Open PRs + env: + GH_TOKEN: ${{ secrets.GH_BOT_TOKEN }} + VERSION: ${{ inputs.version }} + DRY_RUN: ${{ inputs.dry_run }} + run: | + gh auth setup-git + git config --global user.name "github-actions[bot]" + git config --global user.email "github-actions[bot]@users.noreply.github.com" + args=(-y) + [ -z "$VERSION" ] || args+=("$VERSION") + [ "$DRY_RUN" != "true" ] || args+=(--dry-run) + dev_scripts/update-consumers.sh "${args[@]}" diff --git a/README.md b/README.md index 6b63acf0..a576b282 100644 --- a/README.md +++ b/README.md @@ -160,7 +160,7 @@ bunx @stellarwp/changelogger write --overwrite-version 1. Run the **Release Prep** workflow (`Actions → Release Prep → Run workflow`). Supply the target branch, version (e.g. `1.2.0`), and the release date (e.g. `2026-04-29`). The workflow bumps the `VERSION` constant, compiles the changelog, and opens a PR automatically. 2. Review and merge the PR. 3. Create a GitHub Release with a new tag in the format `vX.X.X` targeting the merge commit. -4. The **Release** workflow opens a PR in each consumer plugin (GiveWP, Kadence Blocks, tribe-common for The Events Calendar, and LearnDash) that bumps `stellarwp/harbor` to the new version. To rerun it by hand, or to target other repos, run `composer release:update-consumers -- [version] [owner/repo ...]`. Add `--dry-run` to see the change without pushing anything. The consumer list lives in `dev_scripts/update-consumers.sh`. +4. Once the release is checked, run the **Update Consumers** workflow (`Actions → Update Consumers → Run workflow`). It opens a PR in each consumer plugin (GiveWP, Kadence Blocks, tribe-common for The Events Calendar, and LearnDash) that bumps `stellarwp/harbor` to the new version. To run it locally, or to target other repos, run `composer release:update-consumers -- [version] [owner/repo ...]`. Add `--dry-run` to see the change without pushing anything. The consumer list lives in `dev_scripts/update-consumers.sh`. ## Documentation diff --git a/dev_scripts/update-consumers.sh b/dev_scripts/update-consumers.sh index 777db4f9..beb52045 100755 --- a/dev_scripts/update-consumers.sh +++ b/dev_scripts/update-consumers.sh @@ -1,8 +1,8 @@ #!/usr/bin/env bash # # Opens a PR on each consumer plugin bumping its stellarwp/harbor requirement -# to a released version. Runs automatically after a release is published -# (.github/workflows/release.yml) and can be run by hand. +# to a released version. Run it from the Update Consumers workflow +# (.github/workflows/update-consumers.yml) or locally. # # Usage: # composer release:update-consumers -- [version] [-y] [--dry-run] [owner/repo ...] @@ -87,7 +87,7 @@ BODY="Updates \`${PKG}\` to [v${VERSION}](https://github.com/stellarwp/harbor/re Only \`composer.json\` and \`composer.lock\` change. Run \`composer install\` to pull in the new version. -Opened automatically by Harbor's release workflow." +Opened by Harbor's update-consumers script." update_repo() { local repo="$1" dir="$TMP/${1//\//-}" From acfdaa4f959b48f2f11a667b5a80597171d23aa1 Mon Sep 17 00:00:00 2001 From: Jon Waldstein Date: Mon, 5 Oct 2026 16:38:58 -0400 Subject: [PATCH 6/6] chore: add remaining Harbor consumers to update-consumers Co-Authored-By: Claude Opus 5.5 --- .github/workflows/update-consumers.yml | 2 +- README.md | 2 +- dev_scripts/update-consumers.sh | 20 +++++++++++++------- tests/shell/update-consumers.bats | 8 ++++++-- 4 files changed, 21 insertions(+), 11 deletions(-) diff --git a/.github/workflows/update-consumers.yml b/.github/workflows/update-consumers.yml index a027f1a2..4243d5ac 100644 --- a/.github/workflows/update-consumers.yml +++ b/.github/workflows/update-consumers.yml @@ -25,7 +25,7 @@ jobs: - uses: shivammathur/setup-php@v2 with: - php-version: "8.0" + php-version: "7.4" coverage: none # GH_BOT_TOKEN needs push and pull request access to every repo in diff --git a/README.md b/README.md index a576b282..c0db830f 100644 --- a/README.md +++ b/README.md @@ -160,7 +160,7 @@ bunx @stellarwp/changelogger write --overwrite-version 1. Run the **Release Prep** workflow (`Actions → Release Prep → Run workflow`). Supply the target branch, version (e.g. `1.2.0`), and the release date (e.g. `2026-04-29`). The workflow bumps the `VERSION` constant, compiles the changelog, and opens a PR automatically. 2. Review and merge the PR. 3. Create a GitHub Release with a new tag in the format `vX.X.X` targeting the merge commit. -4. Once the release is checked, run the **Update Consumers** workflow (`Actions → Update Consumers → Run workflow`). It opens a PR in each consumer plugin (GiveWP, Kadence Blocks, tribe-common for The Events Calendar, and LearnDash) that bumps `stellarwp/harbor` to the new version. To run it locally, or to target other repos, run `composer release:update-consumers -- [version] [owner/repo ...]`. Add `--dry-run` to see the change without pushing anything. The consumer list lives in `dev_scripts/update-consumers.sh`. +4. Optionally, once the release is checked, run the **Update Consumers** workflow (`Actions → Update Consumers → Run workflow`) instead of bumping each plugin by hand. It opens a PR in every plugin in [Plugins with Harbor](#plugins-with-harbor) that bumps `stellarwp/harbor` to the new version (The Events Calendar and Event Tickets get theirs through tribe-common). To run it locally, or to target specific repos, run `composer release:update-consumers -- [version] [owner/repo ...]`. Add `--dry-run` to see the change without pushing anything. The consumer list lives in `dev_scripts/update-consumers.sh`. ## Documentation diff --git a/dev_scripts/update-consumers.sh b/dev_scripts/update-consumers.sh index beb52045..c7530f39 100755 --- a/dev_scripts/update-consumers.sh +++ b/dev_scripts/update-consumers.sh @@ -23,13 +23,18 @@ set -euo pipefail -# The Events Calendar consumes Harbor through tribe-common (its `common` -# submodule), so the PR goes there. +# The Events Calendar and Event Tickets consume Harbor through tribe-common +# (their `common` submodule), so the PR goes there. Keep this in sync with +# the "Plugins with Harbor" table in README.md. CONSUMERS=( impress-org/givewp stellarwp/kadence-blocks - the-events-calendar/tribe-common + stellarwp/kadence-pro + stellarwp/kadence-shop-kit stellarwp/learndash-core + stellarwp/memberdash + stellarwp/restrict-content-pro + the-events-calendar/tribe-common ) PKG="stellarwp/harbor" @@ -96,12 +101,13 @@ update_repo() { cd "$dir" git checkout --quiet -B "$BRANCH" - # -w lets Harbor's own dependencies move with it. Extensions are ignored - # because the runner's PHP may not match the plugin's; each plugin pins - # config.platform.php, so the PHP version is still checked. + # -w lets Harbor's own dependencies move with it. The runner's PHP may not + # match the plugin's, so missing extensions and PHP upper bounds (old dev + # tools like phpunit 7) are ignored. PHP minimums are still checked, against + # config.platform.php where the plugin sets it. composer require "${PKG}:^${VERSION}" -w \ --no-install --no-scripts --no-audit --no-interaction \ - --ignore-platform-req='ext-*' + --ignore-platform-req='ext-*' --ignore-platform-req='php+' git add composer.json composer.lock if git diff --cached --quiet; then diff --git a/tests/shell/update-consumers.bats b/tests/shell/update-consumers.bats index 37b2aa27..3a35d580 100644 --- a/tests/shell/update-consumers.bats +++ b/tests/shell/update-consumers.bats @@ -57,7 +57,11 @@ SH chmod +x "$TEST_DIR/bin/gh" "$TEST_DIR/bin/composer" export PATH="$TEST_DIR/bin:$PATH" - for repo in impress-org/givewp stellarwp/kadence-blocks the-events-calendar/tribe-common stellarwp/learndash-core; do + CONSUMERS=( + impress-org/givewp stellarwp/kadence-blocks stellarwp/kadence-pro stellarwp/kadence-shop-kit + stellarwp/learndash-core stellarwp/memberdash stellarwp/restrict-content-pro the-events-calendar/tribe-common + ) + for repo in "${CONSUMERS[@]}"; do make_remote "$repo" "^1.6" done @@ -101,7 +105,7 @@ remote_file() { @test "defaults to every consumer repo" { run "$SCRIPT" 1.7.0 --dry-run [ "$status" -eq 0 ] - for repo in impress-org/givewp stellarwp/kadence-blocks the-events-calendar/tribe-common stellarwp/learndash-core; do + for repo in "${CONSUMERS[@]}"; do [[ "$output" == *"== $repo"* ]] done }