Skip to content
Merged
Changes from 3 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
68 changes: 68 additions & 0 deletions .github/workflows/release.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -20,12 +20,20 @@ on:
jobs:
release:
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

defaults:
run:
working-directory: ./mcp-server

permissions:
contents: write
pull-requests: write

steps:
- uses: actions/checkout@v4
Expand All @@ -48,3 +56,63 @@ 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.
- name: Open sync PR to main
# Also runs when the release step 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 }}
Comment thread
agoldis marked this conversation as resolved.
Outdated
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.
if ! printf '%s' "$RELEASE_BRANCH" | grep -Eq '^release/[A-Za-z0-9._-]+$'; then
echo "Not a release branch, skipping sync PR: $RELEASE_BRANCH"
exit 0
fi
Comment thread
agoldis marked this conversation as resolved.
Outdated

VERSION=$(node -p "require('./package.json').version")

# Nothing to sync unless release-it actually made the release commit.
if [ "$(git log -1 --pretty=%s)" != "chore: release v$VERSION" ]; then
echo "No release commit on HEAD, skipping sync PR."
exit 0
fi
Comment thread
agoldis marked this conversation as resolved.
Outdated

# release-it commits before it pushes. If the push failed, the commit
# exists only on the runner and a PR would not contain it.
REMOTE_HEAD=$(git ls-remote origin "refs/heads/$RELEASE_BRANCH" | cut -f1 || true)
if [ "$REMOTE_HEAD" != "$(git rev-parse HEAD)" ]; then
echo "Release commit is not on origin/$RELEASE_BRANCH, skipping sync PR."
exit 0
fi
Comment thread
agoldis marked this conversation as resolved.
Outdated

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