Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
96 changes: 96 additions & 0 deletions .github/workflows/release.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ on:
jobs:
release:
runs-on: ubuntu-latest

defaults:
run:
working-directory: ./mcp-server
Expand Down Expand Up @@ -48,3 +49,98 @@ jobs:
run: npx release-it ${{ inputs.increment != 'auto' && inputs.increment || '' }} --ci ${{ inputs.dry_run && '--dry-run' || '' }}
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

# The release commit (version bump + changelog) is made on the release
# branch. Until it reaches main, main's newest reachable tag is the previous
# release, and the next release computes a version that already exists.
#
# Separate job on purpose: `pull-requests: write` must not be in scope while
# release-it runs the repository's own test suite through its before:init
# hook. Nothing here executes repository code.
sync-pr:
needs: release
# Also runs when the release job failed: release-it pushes the commit and
# tag before it creates the GitHub release, so a late failure still leaves
# a release commit that main needs.
if: ${{ !cancelled() && !inputs.dry_run }}
runs-on: ubuntu-latest

# The sync PR is opened with a list-then-create pair, which is not atomic.
# Serializing runs per ref keeps two of them from opening duplicates.
concurrency:
group: release-sync-${{ github.ref }}
cancel-in-progress: false

permissions:
contents: read
pull-requests: write

steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0

- name: Open sync PR to main
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
RELEASE_BRANCH: ${{ github.ref_name }}
RELEASE_REF_TYPE: ${{ github.ref_type }}
run: |
set -euo pipefail

# A tag can be named release/x.y.z and would pass the branch check.
if [ "$RELEASE_REF_TYPE" != "branch" ]; then
echo "Dispatched from a $RELEASE_REF_TYPE, skipping sync PR."
exit 0
fi

# Read the ref as data, never as shell source: a branch name is
# attacker-controlled by anyone with write access.
# `+` is legal in a semver build version and release-it accepts the
# branch, so the allowlist has to allow it too.
if ! printf '%s' "$RELEASE_BRANCH" | grep -Eq '^release/[A-Za-z0-9.+_-]+$'; then
Comment thread
agoldis marked this conversation as resolved.
echo "Not a release branch, skipping sync PR: $RELEASE_BRANCH"
exit 0
fi

# Decide from the remote branch rather than the runner's checkout: a
# re-run of this workflow checks out the dispatch sha, which predates
# the release commit, and the PR is opened from the remote branch
# anyway.
REMOTE_TIP=$(git ls-remote origin "refs/heads/$RELEASE_BRANCH" | cut -f1 || true)
if [ -z "$REMOTE_TIP" ]; then
echo "No origin/$RELEASE_BRANCH, skipping sync PR."
exit 0
fi

git fetch --quiet --tags origin "$RELEASE_BRANCH"
VERSION=$(git show "$REMOTE_TIP:mcp-server/package.json" | jq -r '.version // ""')

# jq yields "null" for a missing or non-string version, which would
# otherwise reach the tag ref and the PR body.
if ! printf '%s' "$VERSION" | grep -Eq '^[0-9]+\.[0-9]+\.[0-9]+([-+][0-9A-Za-z.-]+)*$'; then
echo "Unexpected version at the branch tip, skipping sync PR."
exit 0
fi

# The release is only real once its tag is pushed and contained in
# the branch. Ancestry, not equality: the branch may have moved on.
if ! git merge-base --is-ancestor "refs/tags/v$VERSION" "$REMOTE_TIP" 2>/dev/null; then
echo "v$VERSION is not on origin/$RELEASE_BRANCH, skipping sync PR."
exit 0
fi

EXISTING=$(gh pr list --head "$RELEASE_BRANCH" --base main --state open --json number --jq '.[0].number // empty')
if [ -n "$EXISTING" ]; then
echo "Sync PR already open for $RELEASE_BRANCH: #$EXISTING"
exit 0
fi

gh pr create --base main --head "$RELEASE_BRANCH" \
Comment thread
coderabbitai[bot] marked this conversation as resolved.
Comment thread
agoldis marked this conversation as resolved.
--title "chore: sync release $VERSION back to main" \
--body "$(printf '%s\n' \
"Merges the \`chore: release v$VERSION\` commit (version bump + changelog) back into \`main\`." \
"" \
"Opened automatically by the Create Release workflow. Without it, \`main\` keeps the previous version and the next release branch computes a version that already exists." \
"" \
"No code changes - release bookkeeping only.")"
Loading