Skip to content
Merged
Show file tree
Hide file tree
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
44 changes: 44 additions & 0 deletions .github/actions/generate-release-pr-body/action.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
name: 'Generate Release Notes'
description: 'Generate release notes for a given version'

inputs:
version:
description: 'The version being released'
required: true
head_ref:
description: 'The commit SHA or reference of the head of the release branch'
required: true
prior_ref:
description: 'The previous version to compare against'
required: true

outputs:
pr_body_file:
description: 'Path to the generated PR body file'
value: ${{ steps.generate.outputs.pr_body_file }}

runs:
using: 'composite'
steps:
- name: Generate release notes
id: generate
shell: bash
env:
VERSION: ${{ inputs.version }}
HEAD_REF: ${{ inputs.head_ref }}
PRIOR_REF: ${{ inputs.prior_ref }}
TEMPLATE_FILE: "${{ github.action_path }}/pr_body_template.txt"
run: |
git fetch origin --tags

{
sed -e "s/{{VERSION}}/${VERSION}/g" \
-e "s/{{PRIOR_VERSION}}/${PRIOR_REF}/g" \
"$TEMPLATE_FILE"
git log --pretty=format:"- %s (%h)" --reverse ${PRIOR_REF}..${HEAD_REF}
echo ""
echo "---"
echo "*This release PR was generated automatically.*"
} > pr_body.txt

echo "pr_body_file=pr_body.txt" >> $GITHUB_OUTPUT
19 changes: 19 additions & 0 deletions .github/actions/generate-release-pr-body/pr_body_template.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
# Release v{{VERSION}}

## How to Release

Push the release tag to trigger the release:
```bash
git fetch && git tag v{{VERSION}} origin/release/{{VERSION}}
git push origin v{{VERSION}}
```
This PR will auto-merge once the tag is pushed.

## Important Notes

- All commits in this release should have corresponding cherry-picks in `main`
- This PR can be closed if the release is not needed.

## Changes in This Release

**Comparing:** `{{PRIOR_VERSION}}...v{{VERSION}}`
57 changes: 57 additions & 0 deletions .github/workflows/check-release-pr.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
name: Check Release PR

on:
pull_request:
types:
- opened
- synchronize
branches:
- main

jobs:
check-commits:
runs-on: ubuntu-latest
if: startsWith(github.head_ref, 'release/')
steps:
- uses: actions/checkout@08eba0b27e820071cde6df949e0beb9ba4906955 # v4
with:
ref: ${{ github.head_ref }}
fetch-depth: 0

- name: Check all PR commits exist in main
run: |
git fetch origin main

CHERRY_OUTPUT=$(git cherry origin/main HEAD)

if [ -z "$CHERRY_OUTPUT" ]; then
echo "✅ All commits already exist in main"
exit 0
fi

echo "Cherry check results:"
echo "$CHERRY_OUTPUT"

MISSING_COMMITS=$(echo "$CHERRY_OUTPUT" | grep '^+' | cut -d' ' -f2)

if [ -z "$MISSING_COMMITS" ]; then
echo "✅ All commits exist in main"
exit 0
fi

COMMIT_COUNT=$(echo "$MISSING_COMMITS" | wc -l)
FIRST_COMMIT=$(git rev-list --reverse HEAD ^origin/main | head -1)

if [ "$COMMIT_COUNT" -eq 1 ] && echo "$MISSING_COMMITS" | grep -q "$FIRST_COMMIT"; then
echo "✅ Only version bump commit is unique"
git log --oneline -1 "$FIRST_COMMIT"
else
echo "❌ Found commits that should exist in main:"
for commit in $MISSING_COMMITS; do
if [ "$commit" != "$FIRST_COMMIT" ]; then
git log --oneline -1 "$commit"
fi
done
echo "Make sure commits have equivalents in main. If you've since updated main, re-run this job"
exit 1
fi
82 changes: 82 additions & 0 deletions .github/workflows/create-release-pr.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
name: Release Workflow

on:
workflow_call:
inputs:
bump_type:
description: 'Type of version bump (minor or patch)'
required: true
type: string
target_branch:
description: 'Target branch for the pull request'
required: false
type: string
default: 'main'

permissions:
contents: write
pull-requests: write

jobs:
create-release:
runs-on: ubuntu-latest
env:
BUMP_TYPE: ${{ inputs.bump_type }}
TARGET_BRANCH: ${{ inputs.target_branch }}

steps:
- uses: actions/checkout@08eba0b27e820071cde6df949e0beb9ba4906955 # v4
with:
ref: ${{ inputs.target_branch }}
fetch-depth: 0 # to generate complete release log

- uses: cashapp/activate-hermit@e49f5cb4dd64ff0b0b659d1d8df499595451155a # v1

- name: Validate input and set old version
run: |
if [[ "$BUMP_TYPE" != "minor" && "$BUMP_TYPE" != "patch" ]]; then
echo "Error: bump_type must be 'minor' or 'patch'"
exit 1
fi

- name: create release branch
run: |
PRIOR_VERSION=$(just get-tag-version)
if [[ "$BUMP_TYPE" == "minor" ]]; then
VERSION=$(just get-next-minor-version)
else
VERSION=$(just get-next-patch-version)
fi

echo "prior_ref=v$PRIOR_VERSION" >> $GITHUB_ENV
echo "version=$VERSION" >> $GITHUB_ENV
echo "Version: $VERSION"

git config --local user.email "41898282+github-actions[bot]@users.noreply.github.com"
git config --local user.name "github-actions[bot]"

just prepare-release $VERSION
BRANCH_NAME=$(git branch --show-current)
echo "branch_name=$BRANCH_NAME" >> $GITHUB_ENV
echo "Branch: $BRANCH_NAME"

- name: push release branch
run: |
git push origin "${{ env.branch_name }}"

- name: Generate release notes
uses: ./.github/actions/generate-release-pr-body
with:
version: ${{ env.version }}
head_ref: ${{ github.event.pull_request.head.sha || github.sha }}
prior_ref: ${{ env.prior_ref }}

- name: Create Pull Request
run: |
gh pr create \
-B "$TARGET_BRANCH" \
-H "${{ env.branch_name }}" \
--title "chore(release): release version ${{ env.version }} ($BUMP_TYPE)" \
--body-file pr_body.txt
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
148 changes: 148 additions & 0 deletions .github/workflows/merge-release-pr-on-tag.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,148 @@
name: Merge release PR on tag push

on:
push:
tags:
- 'v[0-9]+.[0-9]+.[0-9]+'

permissions:
actions: write
contents: write
pull-requests: write
checks: read

jobs:
trigger-patch-release:
runs-on: ubuntu-latest

steps:
- uses: actions/checkout@08eba0b27e820071cde6df949e0beb9ba4906955 # v4

- name: Extract version from tag
id: version
env:
TAG: ${{ github.ref_name }}
run: |
VERSION=${TAG#v}
BRANCH="release/${VERSION}"

echo "tag=${TAG}" >> $GITHUB_OUTPUT
echo "version=${VERSION}" >> $GITHUB_OUTPUT
echo "branch=${BRANCH}" >> $GITHUB_OUTPUT

echo "Tag: ${TAG}"
echo "Version: ${VERSION}"
echo "Expected branch: ${BRANCH}"

- name: Find matching PR
id: find_pr
env:
GH_TOKEN: ${{ github.token }}
BRANCH: ${{ steps.version.outputs.branch }}
run: |
PR_NUMBER=$(gh pr list --head "$BRANCH" --state open --json number --jq '.[0].number // empty')

if [ -z "$PR_NUMBER" ]; then
echo "❌ No open PR found for branch: $BRANCH"
echo "pr_found=false" >> $GITHUB_OUTPUT
exit 1
else
echo "✅ Found PR #$PR_NUMBER for branch: $BRANCH"
echo "pr_found=true" >> $GITHUB_OUTPUT
echo "pr_number=$PR_NUMBER" >> $GITHUB_OUTPUT
fi

- name: Get PR details and check status
if: steps.find_pr.outputs.pr_found == 'true'
id: pr_status
env:
GH_TOKEN: ${{ github.token }}
PR_NUMBER: ${{ steps.find_pr.outputs.pr_number }}
run: |
PR_DATA=$(gh pr view $PR_NUMBER --json title,headRefName,baseRefName,mergeable,statusCheckRollup)

TITLE=$(echo "$PR_DATA" | jq -r '.title')
HEAD_BRANCH=$(echo "$PR_DATA" | jq -r '.headRefName')
BASE_BRANCH=$(echo "$PR_DATA" | jq -r '.baseRefName')
MERGEABLE=$(echo "$PR_DATA" | jq -r '.mergeable')

echo "PR Title: $TITLE"
echo "Head Branch: $HEAD_BRANCH"
echo "Base Branch: $BASE_BRANCH"
echo "Mergeable: $MERGEABLE"

if [ "$MERGEABLE" != "MERGEABLE" ]; then
echo "❌ PR is not in a mergeable state: $MERGEABLE"
echo "can_merge=false" >> $GITHUB_OUTPUT
echo "merge_reason=PR is not mergeable (state: $MERGEABLE)" >> $GITHUB_OUTPUT
exit 0
fi

STATUS_CHECKS=$(echo "$PR_DATA" | jq -r '.statusCheckRollup[]? | select(.conclusion != null) | "\(.context): \(.conclusion)"')
FAILED_CHECKS=$(echo "$PR_DATA" | jq -r '.statusCheckRollup[]? | select(.conclusion == "FAILURE" or .conclusion == "CANCELLED" or .conclusion == "TIMED_OUT") | .context')
PENDING_CHECKS=$(echo "$PR_DATA" | jq -r '.statusCheckRollup[]? | select(.conclusion == null) | .context')

echo "Status checks:"
if [ -n "$STATUS_CHECKS" ]; then
echo "$STATUS_CHECKS"
else
echo "No status checks found"
fi

if [ -n "$FAILED_CHECKS" ]; then
echo "❌ Failed checks found:"
echo "$FAILED_CHECKS"
echo "can_merge=false" >> $GITHUB_OUTPUT
echo "merge_reason=Some checks are failing" >> $GITHUB_OUTPUT
exit 0
fi

if [ -n "$PENDING_CHECKS" ]; then
echo "⏳ Pending checks found:"
echo "$PENDING_CHECKS"
echo "can_merge=false" >> $GITHUB_OUTPUT
echo "merge_reason=Some checks are still pending" >> $GITHUB_OUTPUT
exit 0
fi

echo "✅ All checks are passing and PR is ready to merge"
echo "can_merge=true" >> $GITHUB_OUTPUT

- name: Get branch SHA before merge
if: steps.find_pr.outputs.pr_found == 'true' && steps.pr_status.outputs.can_merge == 'true'
id: branch_info
env:
BRANCH: ${{ steps.version.outputs.branch }}
run: |
git fetch origin

BRANCH_SHA=$(git rev-parse "origin/$BRANCH")
echo "branch_sha=$BRANCH_SHA" >> $GITHUB_OUTPUT
echo "Branch SHA: $BRANCH_SHA"

- name: Merge PR
if: steps.find_pr.outputs.pr_found == 'true' && steps.pr_status.outputs.can_merge == 'true'
env:
GH_TOKEN: ${{ github.token }}
PR_NUMBER: ${{ steps.find_pr.outputs.pr_number }}
VERSION: ${{ steps.version.outputs.version }}
TAG: ${{ steps.version.outputs.tag }}
run: |
gh pr merge $PR_NUMBER --squash --delete-branch --subject "Release $VERSION" --body "Auto-merged release PR after tag $TAG was pushed"

- name: Restore branch
if: steps.find_pr.outputs.pr_found == 'true' && steps.pr_status.outputs.can_merge == 'true'
env:
BRANCH: ${{ steps.version.outputs.branch }}
BRANCH_SHA: ${{ steps.branch_info.outputs.branch_sha }}
run: |
git checkout -b "$BRANCH" "$BRANCH_SHA"
git push origin "$BRANCH"

- name: Trigger patch release
env:
BRANCH: ${{ steps.version.outputs.branch }}
GH_TOKEN: ${{ github.token }}
run: |
gh workflow run patch-release.yaml \
--field target_branch=$BRANCH
16 changes: 16 additions & 0 deletions .github/workflows/minor-release.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
name: Create Minor Release PR

permissions:
contents: write
pull-requests: write

on:
schedule:
- cron: '0 0 * * 2'
workflow_dispatch:

jobs:
release:
uses: ./.github/workflows/create-release-pr.yaml
with:
bump_type: "minor"
20 changes: 20 additions & 0 deletions .github/workflows/patch-release.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
name: Create Patch Release PR

permissions:
contents: write
pull-requests: write

on:
workflow_dispatch:
inputs:
target_branch:
description: 'Target branch for hotfix'
required: true
type: string

jobs:
hotfix:
uses: ./.github/workflows/create-release-pr.yaml
with:
bump_type: "patch"
target_branch: ${{ inputs.target_branch }}
Loading