Repository navigation
Chore: add a workflow to bump Harbor in consumer plugins #201
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from all commits
Commits
Show all changes
7 commits
Select commit
Hold shift + click to select a range
11fc25c
chore: open PRs in consumer plugins after a release
jonwaldstein 4afac93
chore: fix spell check and ignore prerelease tags in update-consumers
jonwaldstein 5701a42
Merge branch 'main' into chore/update-consumers-on-release
jonwaldstein dfb58af
test: cover update-consumers with bats and add --dry-run
jonwaldstein dc2a974
fix: show the staged diff in --dry-run without committing
jonwaldstein ef2d3bc
chore: run update-consumers from its own manual workflow
jonwaldstein acfdaa4
chore: add remaining Harbor consumers to update-consumers
jonwaldstein File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -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: "7.4" | ||
| 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[@]}" |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,155 @@ | ||
| #!/usr/bin/env bash | ||
| # | ||
| # Opens a PR on each consumer plugin bumping its stellarwp/harbor requirement | ||
| # 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 ...] | ||
| # 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 diff 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-<version> 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 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 | ||
| stellarwp/kadence-pro | ||
| stellarwp/kadence-shop-kit | ||
| stellarwp/learndash-core | ||
| stellarwp/memberdash | ||
| stellarwp/restrict-content-pro | ||
| the-events-calendar/tribe-common | ||
| ) | ||
|
|
||
| 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") ;; | ||
| *) | ||
| [ -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/||' | 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 | ||
| 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" ] && [ -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; } | ||
| 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 by Harbor's update-consumers script." | ||
|
|
||
| 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. 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='php+' | ||
|
|
||
| git add composer.json composer.lock | ||
| if git diff --cached --quiet; then | ||
| echo "${repo}: already on ${VERSION}, nothing to do." | ||
| return 0 | ||
| fi | ||
|
|
||
| if [ -n "$DRY_RUN" ]; then | ||
| 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" | ||
|
coderabbitai[bot] marked this conversation as resolved.
|
||
|
|
||
| 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}" | ||
| # 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=$? | ||
| set -e | ||
| [ $status -eq 0 ] || FAILED+=("$repo") | ||
| done | ||
|
|
||
| if [ ${#FAILED[@]} -gt 0 ]; then | ||
| echo "" | ||
| echo "Failed:" | ||
| printf ' • %s\n' "${FAILED[@]}" | ||
| exit 1 | ||
| fi | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,161 @@ | ||
| #!/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" | ||
|
|
||
| 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 | ||
|
|
||
| # 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 "${CONSUMERS[@]}"; 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 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" | ||
| } | ||
|
|
||
| @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"'* ]] | ||
| } |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.