From c0e9ad0aa7c15c1bebd2a348c4ba7026353f0794 Mon Sep 17 00:00:00 2001 From: Sam Morrow Date: Wed, 25 Jun 2025 12:07:17 +0200 Subject: [PATCH 1/3] add a new release workflow --- .github/workflows/docker-publish.yml | 4 +- .github/workflows/pr-base-check.yml | 55 ++++++++ .github/workflows/release.yml | 191 +++++++++++++++++++++++++++ CONTRIBUTING.md | 4 +- 4 files changed, 251 insertions(+), 3 deletions(-) create mode 100644 .github/workflows/pr-base-check.yml create mode 100644 .github/workflows/release.yml diff --git a/.github/workflows/docker-publish.yml b/.github/workflows/docker-publish.yml index 35ffc47dbb..cd2d923cbb 100644 --- a/.github/workflows/docker-publish.yml +++ b/.github/workflows/docker-publish.yml @@ -9,11 +9,11 @@ on: schedule: - cron: "27 0 * * *" push: - branches: ["main"] + branches: ["main", "next"] # Publish semver tags as releases. tags: ["v*.*.*"] pull_request: - branches: ["main"] + branches: ["main", "next"] env: # Use docker.io for Docker Hub if empty diff --git a/.github/workflows/pr-base-check.yml b/.github/workflows/pr-base-check.yml new file mode 100644 index 0000000000..6f48205caa --- /dev/null +++ b/.github/workflows/pr-base-check.yml @@ -0,0 +1,55 @@ +name: PR Base Branch Check + +on: + pull_request: + types: [opened, edited, synchronize] + branches: + - main + +permissions: + pull-requests: write + contents: read + +jobs: + check-base-branch: + runs-on: ubuntu-latest + if: github.event.pull_request.base.ref == 'main' + + steps: + - name: Comment on PR + uses: actions/github-script@v7 + with: + script: | + const message = `👋 Hi there! + + It looks like this PR is targeting the \`main\` branch. To help maintain our development workflow, please change the base reference to \`next\` instead. + + __If this is a bug fix that requires a patch release __ (e.g., a critical bug that needs to be fixed before the next release)__, please leave the base branch as \`main\`.__ + + You can change this by: + 1. Clicking the "Edit" button next to the PR title + 2. Changing the base branch from \`main\` to \`next\` + 3. Clicking "Update pull request" + + Thanks for your contribution! 🚀`; + + // Check if we've already commented + const comments = await github.rest.issues.listComments({ + owner: context.repo.owner, + repo: context.repo.repo, + issue_number: context.issue.number, + }); + + const botComment = comments.data.find(comment => + comment.user.type === 'Bot' && + comment.body.includes('please change the base reference to') + ); + + if (!botComment) { + await github.rest.issues.createComment({ + owner: context.repo.owner, + repo: context.repo.repo, + issue_number: context.issue.number, + body: message + }); + } diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 0000000000..0c909dcfdf --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,191 @@ +name: Release + +on: + workflow_dispatch: + inputs: + tag: + description: 'Release tag (e.g., v0.0.0)' + required: true + default: 'v0.0.0' + type: string + confirm: + description: 'Type "CONFIRM" to proceed with the release' + required: true + type: string + +permissions: + contents: write + pull-requests: write + +jobs: + validate: + runs-on: ubuntu-latest + steps: + - name: Validate confirmation + if: ${{ github.event.inputs.confirm != 'CONFIRM' }} + run: | + echo "::error::You must type 'CONFIRM' to proceed with the release" + exit 1 + + - name: Validate tag format + run: | + TAG="${{ github.event.inputs.tag }}" + if [[ ! $TAG =~ ^v[0-9]+\.[0-9]+\.[0-9]+(-.*)?$ ]]; then + echo "::error::Tag must be in format vX.Y.Z or vX.Y.Z-suffix (e.g., v1.0.0 or v1.0.0-rc1)" + exit 1 + fi + + release: + needs: validate + runs-on: ubuntu-latest + outputs: + pr-number: ${{ steps.create-pr.outputs.pr-number }} + pr-url: ${{ steps.create-pr.outputs.pr-url }} + + steps: + - name: Checkout repository + uses: actions/checkout@v4 + with: + fetch-depth: 0 + token: ${{ secrets.GITHUB_TOKEN }} + + - name: Configure Git + run: | + git config user.name "github-actions[bot]" + git config user.email "github-actions[bot]@users.noreply.github.com" + + - name: Switch to next branch + run: | + git checkout next + git pull origin next + + - name: Rebase next with main + id: rebase + run: | + echo "Attempting to rebase next with main..." + if git rebase origin/main; then + echo "✅ Rebase successful" + echo "rebase-success=true" >> $GITHUB_OUTPUT + else + echo "::error::❌ Rebase failed due to conflicts. Please resolve conflicts manually and try again." + echo "Conflicts detected in the following files:" + git status --porcelain | grep "^UU\|^AA\|^DD" || true + echo "rebase-success=false" >> $GITHUB_OUTPUT + exit 1 + fi + + - name: Check if tag already exists + run: | + TAG="${{ github.event.inputs.tag }}" + if git tag -l | grep -q "^${TAG}$"; then + echo "::error::Tag ${TAG} already exists" + exit 1 + fi + if git ls-remote --tags origin | grep -q "refs/tags/${TAG}$"; then + echo "::error::Tag ${TAG} already exists on remote" + exit 1 + fi + + - name: Tag the release + run: | + TAG="${{ github.event.inputs.tag }}" + git tag -a "${TAG}" -m "Release ${TAG}" + echo "✅ Created tag ${TAG}" + + - name: Create Pull Request + id: create-pr + run: | + TAG="${{ github.event.inputs.tag }}" + + # Create PR from next to main + PR_RESPONSE=$(gh pr create \ + --base main \ + --head next \ + --title "Release ${TAG}" \ + --body "This PR contains the changes for release ${TAG}. + + **Release checklist:** + - [ ] Review the changes + - [ ] Ensure all tests pass + - [ ] Verify the release notes in the draft release + - [ ] Merge this PR after the release is published + + Created by the automated release workflow." \ + --json number,url) + + PR_NUMBER=$(echo "$PR_RESPONSE" | jq -r '.number') + PR_URL=$(echo "$PR_RESPONSE" | jq -r '.url') + + echo "pr-number=${PR_NUMBER}" >> $GITHUB_OUTPUT + echo "pr-url=${PR_URL}" >> $GITHUB_OUTPUT + echo "✅ Created PR #${PR_NUMBER}: ${PR_URL}" + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + + - name: Push tag + run: | + TAG="${{ github.event.inputs.tag }}" + git push origin "${TAG}" + echo "✅ Pushed tag ${TAG}" + + - name: Wait for release to be created + run: | + TAG="${{ github.event.inputs.tag }}" + echo "Waiting for GitHub to create the draft release..." + + # Wait up to 2 minutes for the release to appear + for i in {1..24}; do + if gh release view "${TAG}" >/dev/null 2>&1; then + echo "✅ Draft release created" + break + fi + echo "Waiting for release to be created... (${i}/24)" + sleep 5 + done + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + + summary: + needs: [validate, release] + runs-on: ubuntu-latest + if: always() && needs.release.result == 'success' + + steps: + - name: Release Summary + run: | + TAG="${{ github.event.inputs.tag }}" + PR_URL="${{ needs.release.outputs.pr-url }}" + + echo "## 🎉 Release $TAG has been initiated!" + echo "" + echo "### Next steps:" + echo "1. 📋 Check https://github.com/${{ github.repository }}/releases for the draft release to show up" + echo "2. ✏️ Edit the new release, delete the existing notes and click the auto-generate button GitHub provides" + echo "3. ✨ Add a section at the top calling out the main features" + echo "4. 🚀 Publish the release" + echo "5. 🔀 Merge the pull request into main: ${PR_URL}" + echo "6. Post message in #gh-mcp-releases channel in Slack and then share to the other mcp channels" + echo "" + echo "### Resources:" + echo "- 📦 Draft Release: https://github.com/${{ github.repository }}/releases/tag/$TAG" + echo "- 🔄 Pull Request: ${PR_URL}" + echo "" + echo "The release process is now ready for your review and completion!" + + # Also output as job summary + cat << EOF >> $GITHUB_STEP_SUMMARY + ## 🎉 Release $TAG has been initiated! + + ### Next steps: + 1. 📋 Check [releases page](https://github.com/${{ github.repository }}/releases) for the draft release to show up + 2. ✏️ Edit the new release, delete the existing notes and click the auto-generate button GitHub provides + 3. ✨ Add a section at the top calling out the main features + 4. 🚀 Publish the release + 5. 🔀 Merge the pull request into main: [PR #${{ needs.release.outputs.pr-number }}](${PR_URL}) + + ### Resources: + - 📦 [Draft Release](https://github.com/${{ github.repository }}/releases/tag/$TAG) + - 🔄 [Pull Request](${PR_URL}) + + The release process is now ready for your review and completion! + EOF diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 11d63a389a..6fa9c2ebe6 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -19,12 +19,14 @@ These are one time installations required to be able to test your changes locall ## Submitting a pull request +> **Important**: Please open your pull request against the `next` branch, not `main`. The `next` branch is where we integrate new features and changes before they are merged to `main`. + 1. [Fork][fork] and clone the repository 1. Make sure the tests pass on your machine: `go test -v ./...` 1. Make sure linter passes on your machine: `golangci-lint run` 1. Create a new branch: `git checkout -b my-branch-name` 1. Make your change, add tests, and make sure the tests and linter still pass -1. Push to your fork and [submit a pull request][pr] +1. Push to your fork and [submit a pull request][pr] targeting the `next` branch 1. Pat yourself on the back and wait for your pull request to be reviewed and merged. Here are a few things you can do that will increase the likelihood of your pull request being accepted: From d94992cd30695069ca281b913bfb7b58cd418178 Mon Sep 17 00:00:00 2001 From: Sam Morrow Date: Thu, 26 Jun 2025 16:45:20 +0200 Subject: [PATCH 2/3] improve release.yml to ensure that the ref is up-to-date --- .github/workflows/release.yml | 35 ++++++++++++++++++++++++----------- 1 file changed, 24 insertions(+), 11 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 0c909dcfdf..72a9407eb6 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -59,20 +59,33 @@ jobs: git checkout next git pull origin next - - name: Rebase next with main - id: rebase + - name: Check next branch is up-to-date with main + id: branch-check run: | - echo "Attempting to rebase next with main..." - if git rebase origin/main; then - echo "✅ Rebase successful" - echo "rebase-success=true" >> $GITHUB_OUTPUT - else - echo "::error::❌ Rebase failed due to conflicts. Please resolve conflicts manually and try again." - echo "Conflicts detected in the following files:" - git status --porcelain | grep "^UU\|^AA\|^DD" || true - echo "rebase-success=false" >> $GITHUB_OUTPUT + echo "Checking if next branch is up-to-date with main..." + + # Fetch latest main branch + git fetch origin main + + # Check if next is behind main + BEHIND_COUNT=$(git rev-list --count next..origin/main) + AHEAD_COUNT=$(git rev-list --count origin/main..next) + + echo "Next branch is ${AHEAD_COUNT} commits ahead of main" + echo "Next branch is ${BEHIND_COUNT} commits behind main" + + if [ "$BEHIND_COUNT" -gt 0 ]; then + echo "::error::❌ Next branch is ${BEHIND_COUNT} commits behind main. Please update next branch with the latest changes from main before creating a release." + echo "To fix this, run: git checkout next && git merge main" exit 1 fi + + if [ "$AHEAD_COUNT" -eq 0 ]; then + echo "::warning::⚠️ Next branch has no new commits compared to main. Are you sure you want to create a release?" + fi + + echo "✅ Next branch is up-to-date with main (${AHEAD_COUNT} commits ahead)" + echo "branch-check-success=true" >> $GITHUB_OUTPUT - name: Check if tag already exists run: | From cc2f6477ef604549ac173fd76c42ee19422fc930 Mon Sep 17 00:00:00 2001 From: Sam Morrow Date: Thu, 26 Jun 2025 17:00:45 +0200 Subject: [PATCH 3/3] add sync workflow --- .github/workflows/sync-next-branch.yml | 166 +++++++++++++++++++++++++ 1 file changed, 166 insertions(+) create mode 100644 .github/workflows/sync-next-branch.yml diff --git a/.github/workflows/sync-next-branch.yml b/.github/workflows/sync-next-branch.yml new file mode 100644 index 0000000000..8636130212 --- /dev/null +++ b/.github/workflows/sync-next-branch.yml @@ -0,0 +1,166 @@ +name: Sync Next Branch + +on: + schedule: + # Run daily at 9:00 AM UTC (6:00 AM EST, 3:00 AM PST) + - cron: '0 9 * * *' + workflow_dispatch: + # Allow manual triggering + +permissions: + contents: write + pull-requests: write + +jobs: + check-and-sync: + runs-on: ubuntu-latest + + steps: + - name: Checkout repository + uses: actions/checkout@v4 + with: + fetch-depth: 0 + token: ${{ secrets.GITHUB_TOKEN }} + + - name: Configure Git + run: | + git config user.name "github-actions[bot]" + git config user.email "github-actions[bot]@users.noreply.github.com" + + - name: Check branch status + id: branch-status + run: | + echo "Checking if next branch is up-to-date with main..." + + # Fetch latest branches + git fetch origin main + git fetch origin next + + # Check if next is behind main + BEHIND_COUNT=$(git rev-list --count origin/next..origin/main) + AHEAD_COUNT=$(git rev-list --count origin/main..origin/next) + + echo "Next branch is ${AHEAD_COUNT} commits ahead of main" + echo "Next branch is ${BEHIND_COUNT} commits behind main" + + echo "behind-count=${BEHIND_COUNT}" >> $GITHUB_OUTPUT + echo "ahead-count=${AHEAD_COUNT}" >> $GITHUB_OUTPUT + + if [ "$BEHIND_COUNT" -gt 0 ]; then + echo "needs-sync=true" >> $GITHUB_OUTPUT + echo "🔄 Next branch needs to be synced (${BEHIND_COUNT} commits behind)" + else + echo "needs-sync=false" >> $GITHUB_OUTPUT + echo "✅ Next branch is up-to-date with main" + fi + + - name: Check for existing sync PR + id: existing-pr + if: steps.branch-status.outputs.needs-sync == 'true' + run: | + # Check if there's already an open PR from main to next for syncing + EXISTING_PR=$(gh pr list \ + --base next \ + --head main \ + --state open \ + --json number,title \ + --jq '.[] | select(.title | test("^(Sync|Update) next branch")) | .number') + + if [ -n "$EXISTING_PR" ]; then + echo "existing-pr=${EXISTING_PR}" >> $GITHUB_OUTPUT + echo "⚠️ Sync PR already exists: #${EXISTING_PR}" + echo "has-existing-pr=true" >> $GITHUB_OUTPUT + else + echo "has-existing-pr=false" >> $GITHUB_OUTPUT + echo "No existing sync PR found" + fi + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + + - name: Create sync PR + id: create-sync-pr + if: steps.branch-status.outputs.needs-sync == 'true' && steps.existing-pr.outputs.has-existing-pr == 'false' + run: | + BEHIND_COUNT="${{ steps.branch-status.outputs.behind-count }}" + AHEAD_COUNT="${{ steps.branch-status.outputs.ahead-count }}" + + # Create PR from main to next + PR_RESPONSE=$(gh pr create \ + --base next \ + --head main \ + --title "Sync next branch with main" \ + --body "## 🔄 Automated Branch Sync + + This PR syncs the \`next\` branch with the latest changes from \`main\`. + + ### Status: + - **Behind main**: ${BEHIND_COUNT} commits + - **Ahead of main**: ${AHEAD_COUNT} commits + + ### What to do: + 1. 🔍 Review the changes in this PR + 2. ✅ Ensure all checks pass + 3. 🔀 Merge this PR to sync the \`next\` branch + 4. 🗑️ The \`next\` branch will then be ready for new development + + > **Note**: This PR was automatically created by the daily branch sync workflow. + > If you have any concerns about these changes, please review them carefully before merging." \ + --label "automated" \ + --label "sync" \ + --json number,url) + + PR_NUMBER=$(echo "$PR_RESPONSE" | jq -r '.number') + PR_URL=$(echo "$PR_RESPONSE" | jq -r '.url') + + echo "pr-number=${PR_NUMBER}" >> $GITHUB_OUTPUT + echo "pr-url=${PR_URL}" >> $GITHUB_OUTPUT + + echo "✅ Created sync PR #${PR_NUMBER}: ${PR_URL}" + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + + - name: Job Summary + if: always() + run: | + BEHIND_COUNT="${{ steps.branch-status.outputs.behind-count }}" + AHEAD_COUNT="${{ steps.branch-status.outputs.ahead-count }}" + NEEDS_SYNC="${{ steps.branch-status.outputs.needs-sync }}" + HAS_EXISTING_PR="${{ steps.existing-pr.outputs.has-existing-pr }}" + EXISTING_PR="${{ steps.existing-pr.outputs.existing-pr }}" + NEW_PR_URL="${{ steps.create-sync-pr.outputs.pr-url }}" + NEW_PR_NUMBER="${{ steps.create-sync-pr.outputs.pr-number }}" + + cat << EOF >> $GITHUB_STEP_SUMMARY + # 🔄 Branch Sync Status + + ## Current Status: + - **Next branch**: ${AHEAD_COUNT} commits ahead, ${BEHIND_COUNT} commits behind main + - **Needs sync**: ${NEEDS_SYNC} + + EOF + + if [ "$NEEDS_SYNC" = "true" ]; then + if [ "$HAS_EXISTING_PR" = "true" ]; then + cat << EOF >> $GITHUB_STEP_SUMMARY + ## ⚠️ Action Required: + There is already an existing sync PR: [#${EXISTING_PR}](https://github.com/${{ github.repository }}/pull/${EXISTING_PR}) + + Please review and merge the existing PR to sync the next branch. + EOF + elif [ -n "$NEW_PR_NUMBER" ]; then + cat << EOF >> $GITHUB_STEP_SUMMARY + ## ✅ Action Taken: + Created a new sync PR: [#${NEW_PR_NUMBER}](${NEW_PR_URL}) + + **Next steps:** + 1. Review the changes in the PR + 2. Ensure all checks pass + 3. Merge the PR to sync the next branch + EOF + fi + else + cat << EOF >> $GITHUB_STEP_SUMMARY + ## ✅ All Good! + The next branch is up-to-date with main. No action needed. + EOF + fi