From 6f0a549a8702d61af40aeb9f5eca686e7436497e Mon Sep 17 00:00:00 2001 From: Claude Date: Sat, 24 Jan 2026 20:14:02 +0000 Subject: [PATCH] Refactor release workflow to use PR-based flow Split the release process into two workflows: - prepare-release.yml: Creates a PR with version updates and 'release' label - publish-release.yml: Creates tag and GitHub Release when PR is merged This avoids needing to bypass branch protection rules by working with the standard PR workflow instead of pushing directly to main. --- ...create-release.yml => prepare-release.yml} | 66 +++++++---------- .github/workflows/publish-release.yml | 74 +++++++++++++++++++ 2 files changed, 100 insertions(+), 40 deletions(-) rename .github/workflows/{create-release.yml => prepare-release.yml} (75%) create mode 100644 .github/workflows/publish-release.yml diff --git a/.github/workflows/create-release.yml b/.github/workflows/prepare-release.yml similarity index 75% rename from .github/workflows/create-release.yml rename to .github/workflows/prepare-release.yml index fe0cb61e..1fe77970 100644 --- a/.github/workflows/create-release.yml +++ b/.github/workflows/prepare-release.yml @@ -1,4 +1,4 @@ -name: Create Release +name: Prepare Release on: workflow_dispatch: @@ -10,16 +10,15 @@ on: permissions: contents: write + pull-requests: write jobs: - release: + prepare: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 with: fetch-depth: 0 - # Use PAT with bypass permissions for pushing to protected branch - token: ${{ secrets.RELEASE_TOKEN }} - name: Validate version format run: | @@ -33,8 +32,12 @@ jobs: git config user.name "github-actions[bot]" git config user.email "github-actions[bot]@users.noreply.github.com" + - name: Create release branch + run: | + VERSION="${{ inputs.version }}" + git checkout -b "release/${VERSION}" + - name: Update CHANGELOG.md - id: changelog env: GITHUB_REPOSITORY: ${{ github.repository }} run: | @@ -98,28 +101,6 @@ jobs: fi fi - # Extract the changelog content for this version (for the release notes) - # Get everything between the version header and the next version header - RELEASE_NOTES=$(awk -v version="$VERSION" ' - BEGIN { capture = 0; found = 0 } - /^## \[/ { - if (capture) exit - if ($0 ~ "\\[" version "\\]") { - capture = 1 - found = 1 - next - } - } - capture { print } - ' CHANGELOG.md) - - # Save release notes to a file for the release step - echo "$RELEASE_NOTES" > release_notes.md - - # Output for debugging - echo "Release notes extracted:" - cat release_notes.md - - name: Update pyproject.toml version run: | VERSION="${{ inputs.version }}" @@ -141,24 +122,29 @@ jobs: - name: Update lock file run: uv sync - - name: Commit changes + - name: Commit and push run: | VERSION="${{ inputs.version }}" git add CHANGELOG.md pyproject.toml uv.lock git commit -m "Release v${VERSION}" - git push origin main + git push -u origin "release/${VERSION}" - - name: Create and push tag + - name: Create Pull Request + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} run: | VERSION="${{ inputs.version }}" - git tag -a "$VERSION" -m "Release $VERSION" - git push origin "$VERSION" + gh pr create \ + --title "${VERSION}" \ + --body "## Release v${VERSION} - - name: Create GitHub Release - uses: softprops/action-gh-release@v2 - with: - tag_name: ${{ inputs.version }} - name: ${{ inputs.version }} - body_path: release_notes.md - draft: false - prerelease: false + This PR prepares the release of version ${VERSION}. + + ### Changes + - Updated version in pyproject.toml + - Updated CHANGELOG.md + - Updated uv.lock + + ### After Merge + A GitHub Release will be automatically created when this PR is merged." \ + --label "release" diff --git a/.github/workflows/publish-release.yml b/.github/workflows/publish-release.yml new file mode 100644 index 00000000..b57c0e13 --- /dev/null +++ b/.github/workflows/publish-release.yml @@ -0,0 +1,74 @@ +name: Publish Release + +on: + pull_request: + types: [closed] + +permissions: + contents: write + +jobs: + release: + # Only run if PR was merged and has the 'release' label + if: github.event.pull_request.merged == true && contains(github.event.pull_request.labels.*.name, 'release') + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - name: Get version from PR title + id: version + run: | + VERSION="${{ github.event.pull_request.title }}" + # Validate version format + if ! echo "$VERSION" | grep -qE '^[0-9]+\.[0-9]+\.[0-9]+$'; then + echo "Error: PR title must be a version number in format X.Y.Z (e.g., 0.6.0)" + exit 1 + fi + echo "version=$VERSION" >> "$GITHUB_OUTPUT" + + - name: Configure git + run: | + git config user.name "github-actions[bot]" + git config user.email "github-actions[bot]@users.noreply.github.com" + + - name: Create and push tag + run: | + VERSION="${{ steps.version.outputs.version }}" + git tag -a "$VERSION" -m "Release $VERSION" + git push origin "$VERSION" + + - name: Extract release notes from CHANGELOG + id: notes + run: | + VERSION="${{ steps.version.outputs.version }}" + # Extract the changelog content for this version (for the release notes) + # Get everything between the version header and the next version header + RELEASE_NOTES=$(awk -v version="$VERSION" ' + BEGIN { capture = 0 } + /^## \[/ { + if (capture) exit + if ($0 ~ "\\[" version "\\]") { + capture = 1 + next + } + } + capture { print } + ' CHANGELOG.md) + + # Save release notes to a file for the release step + echo "$RELEASE_NOTES" > release_notes.md + + # Output for debugging + echo "Release notes:" + cat release_notes.md + + - name: Create GitHub Release + uses: softprops/action-gh-release@v2 + with: + tag_name: ${{ steps.version.outputs.version }} + name: ${{ steps.version.outputs.version }} + body_path: release_notes.md + draft: false + prerelease: false