diff --git a/.github/actions/generate-release-pr-body/pr_body_template.txt b/.github/actions/generate-release-pr-body/pr_body_template.txt index e63e7c2041..7d57c86a18 100644 --- a/.github/actions/generate-release-pr-body/pr_body_template.txt +++ b/.github/actions/generate-release-pr-body/pr_body_template.txt @@ -7,7 +7,11 @@ Push the release tag to trigger the release: git fetch && git tag v{{VERSION}} origin/release/{{VERSION}} git push origin v{{VERSION}} ``` -This PR will auto-merge once the tag is pushed. +The tag push will trigger the release build. This PR will be automatically closed. + +## Cherry-Picks + +If you need to include additional fixes, cherry-pick them into the `release/{{VERSION}}` branch before tagging. ## Important Notes diff --git a/.github/workflows/check-release-pr.yaml b/.github/workflows/check-release-pr.yaml index 711ad7ab07..148b1bfbd5 100644 --- a/.github/workflows/check-release-pr.yaml +++ b/.github/workflows/check-release-pr.yaml @@ -18,7 +18,7 @@ jobs: ref: ${{ github.head_ref }} fetch-depth: 0 - - name: Check all PR commits exist in main + - name: Check all PR commits are empty or cherry-picked from main run: | git fetch origin main @@ -39,19 +39,25 @@ jobs: exit 0 fi - COMMIT_COUNT=$(echo "$MISSING_COMMITS" | wc -l) - FIRST_COMMIT=$(git rev-list --reverse HEAD ^origin/main | head -1) + ALL_EMPTY=true + for commit in $MISSING_COMMITS; do + if [ -n "$(git diff-tree --no-commit-id --name-only -r "$commit")" ]; then + ALL_EMPTY=false + break + fi + done - 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:" + if [ "$ALL_EMPTY" = true ]; then + echo "✅ Only empty commits (release branch markers) are unique" for commit in $MISSING_COMMITS; do - if [ "$commit" != "$FIRST_COMMIT" ]; then - git log --oneline -1 "$commit" - fi + git log --oneline -1 "$commit" done - echo "Make sure commits have equivalents in main. If you've since updated main, re-run this job" - exit 1 + exit 0 fi + + echo "❌ Found commits with changes that don't exist in main:" + for commit in $MISSING_COMMITS; do + git log --oneline -1 "$commit" + done + echo "Cherry-pick these commits into main first, or re-run this job after updating main." + exit 1 diff --git a/.github/workflows/close-release-pr-on-tag.yaml b/.github/workflows/close-release-pr-on-tag.yaml new file mode 100644 index 0000000000..37c5305def --- /dev/null +++ b/.github/workflows/close-release-pr-on-tag.yaml @@ -0,0 +1,59 @@ +name: Close release PR on tag push + +on: + push: + tags: + - 'v[0-9]+.[0-9]+.[0-9]+' + +permissions: + actions: write + contents: write + pull-requests: write + +jobs: + close-release-pr: + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 + + - 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 and close matching PR + env: + GH_TOKEN: ${{ github.token }} + BRANCH: ${{ steps.version.outputs.branch }} + TAG: ${{ steps.version.outputs.tag }} + VERSION: ${{ steps.version.outputs.version }} + 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 (may already be closed)" + else + echo "✅ Found PR #$PR_NUMBER for branch: $BRANCH" + gh pr close "$PR_NUMBER" --comment "Release $TAG has been tagged and pushed. Closing this PR (version bump was already merged to main)." + echo "✅ Closed PR #$PR_NUMBER" + fi + + - 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 diff --git a/.github/workflows/create-release-branch.yaml b/.github/workflows/create-release-branch.yaml new file mode 100644 index 0000000000..4296fad167 --- /dev/null +++ b/.github/workflows/create-release-branch.yaml @@ -0,0 +1,88 @@ +name: Create Release Branch + +on: + pull_request: + types: + - closed + branches: + - main + +permissions: + contents: write + pull-requests: write + +jobs: + create-release-branch: + runs-on: ubuntu-latest + if: > + github.event.pull_request.merged == true && + startsWith(github.event.pull_request.head.ref, 'version-bump/') + steps: + - uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 + with: + ref: ${{ github.event.pull_request.merge_commit_sha }} + fetch-depth: 0 + + - uses: cashapp/activate-hermit@e49f5cb4dd64ff0b0b659d1d8df499595451155a # v1 + - uses: astral-sh/setup-uv@61cb8a9741eeb8a550a1b8544337180c0fc8476b # v7.2.0 + + - name: Extract version and base branch + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: | + VERSION=$(just get-tag-version) + BASE_BRANCH="${{ github.event.pull_request.base.ref }}" + echo "version=$VERSION" >> $GITHUB_ENV + echo "base_branch=$BASE_BRANCH" >> $GITHUB_ENV + echo "Version: $VERSION" + echo "Base branch: $BASE_BRANCH" + + PRIOR_TAG=$(just get-prior-version "$VERSION") + if [[ -z "$PRIOR_TAG" ]]; then + echo "No prior version (first release), using first commit" + PRIOR_TAG=$(git rev-list --max-parents=0 HEAD) + fi + echo "prior_ref=$PRIOR_TAG" >> $GITHUB_ENV + echo "Prior ref: $PRIOR_TAG" + + - name: Create release branch + run: | + git config --local user.email "41898282+github-actions[bot]@users.noreply.github.com" + git config --local user.name "github-actions[bot]" + + BRANCH_NAME="release/${{ env.version }}" + git switch -c "$BRANCH_NAME" + + # Add an empty commit so GitHub can represent this as a PR + git commit --allow-empty --message "chore(release): open release branch for ${{ env.version }}" + + git push origin "$BRANCH_NAME" + + HEAD_REF=$(git rev-parse HEAD) + echo "branch_name=$BRANCH_NAME" >> $GITHUB_ENV + echo "head_ref=$HEAD_REF" >> $GITHUB_ENV + + - name: Generate release notes + uses: ./.github/actions/generate-release-pr-body + with: + version: ${{ env.version }} + head_ref: ${{ env.head_ref }} + prior_ref: ${{ env.prior_ref }} + + - name: Create Release PR + run: | + PR_URL=$(gh pr create \ + -B "${{ env.base_branch }}" \ + -H "${{ env.branch_name }}" \ + --title "chore(release): release version ${{ env.version }}" \ + --body-file pr_body.txt) + echo "pr_url=$PR_URL" >> $GITHUB_ENV + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + + - name: Post release checklist comment + run: | + sed 's/{{VERSION}}/${{ env.version }}/g' RELEASE_CHECKLIST.md > checklist_comment.md + gh pr comment "${{ env.pr_url }}" --body-file checklist_comment.md + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/create-release-pr.yaml b/.github/workflows/create-release-pr.yaml deleted file mode 100644 index 4daf75ad7b..0000000000 --- a/.github/workflows/create-release-pr.yaml +++ /dev/null @@ -1,118 +0,0 @@ -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' - secrets: - ANTHROPIC_API_KEY: - required: false - OPENAI_API_KEY: - required: false - GOOGLE_API_KEY: - required: false - OPENROUTER_API_KEY: - required: false - XAI_API_KEY: - required: false - TETRATE_API_KEY: - required: false - -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@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 - with: - ref: ${{ inputs.target_branch }} - fetch-depth: 0 # to generate complete release log - - - uses: cashapp/activate-hermit@e49f5cb4dd64ff0b0b659d1d8df499595451155a # v1 - - uses: astral-sh/setup-uv@61cb8a9741eeb8a550a1b8544337180c0fc8476b # v7.2.0 - - - 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: install dependencies - run: | - sudo apt update -y - sudo apt install -y libdbus-1-dev gnome-keyring libxcb1-dev - - - name: create release branch - env: - ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }} - OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }} - GOOGLE_API_KEY: ${{ secrets.GOOGLE_API_KEY }} - OPENROUTER_API_KEY: ${{ secrets.OPENROUTER_API_KEY }} - XAI_API_KEY: ${{ secrets.XAI_API_KEY }} - TETRATE_API_KEY: ${{ secrets.TETRATE_API_KEY }} - 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) - HEAD_REF=$(git rev-parse HEAD) - echo "branch_name=$BRANCH_NAME" >> $GITHUB_ENV - echo "head_ref=$HEAD_REF" >> $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: ${{ env.head_ref }} - prior_ref: ${{ env.prior_ref }} - - - name: Create Pull Request - run: | - PR_URL=$(gh pr create \ - -B "$TARGET_BRANCH" \ - -H "${{ env.branch_name }}" \ - --title "chore(release): release version ${{ env.version }} ($BUMP_TYPE)" \ - --body-file pr_body.txt) - echo "pr_url=$PR_URL" >> $GITHUB_ENV - env: - GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} - - - name: Post release checklist comment - run: | - sed 's/{{VERSION}}/${{ env.version }}/g' RELEASE_CHECKLIST.md > checklist_comment.md - gh pr comment "${{ env.pr_url }}" --body-file checklist_comment.md - env: - GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/create-version-bump-pr.yaml b/.github/workflows/create-version-bump-pr.yaml new file mode 100644 index 0000000000..4def147f8d --- /dev/null +++ b/.github/workflows/create-version-bump-pr.yaml @@ -0,0 +1,77 @@ +name: Create Version Bump PR + +on: + workflow_call: + secrets: + ANTHROPIC_API_KEY: + required: false + OPENAI_API_KEY: + required: false + GOOGLE_API_KEY: + required: false + OPENROUTER_API_KEY: + required: false + XAI_API_KEY: + required: false + TETRATE_API_KEY: + required: false + +permissions: + contents: write + pull-requests: write + +jobs: + create-version-bump: + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 + with: + ref: main + fetch-depth: 0 + + - uses: cashapp/activate-hermit@e49f5cb4dd64ff0b0b659d1d8df499595451155a # v1 + - uses: astral-sh/setup-uv@61cb8a9741eeb8a550a1b8544337180c0fc8476b # v7.2.0 + + - name: install dependencies + run: | + sudo apt update -y + sudo apt install -y libdbus-1-dev gnome-keyring libxcb1-dev + + - name: Compute version and create bump branch + env: + ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }} + OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }} + GOOGLE_API_KEY: ${{ secrets.GOOGLE_API_KEY }} + OPENROUTER_API_KEY: ${{ secrets.OPENROUTER_API_KEY }} + XAI_API_KEY: ${{ secrets.XAI_API_KEY }} + TETRATE_API_KEY: ${{ secrets.TETRATE_API_KEY }} + run: | + VERSION=$(just get-next-minor-version) + echo "version=$VERSION" >> $GITHUB_ENV + echo "Version: $VERSION (minor bump on main)" + + git config --local user.email "41898282+github-actions[bot]@users.noreply.github.com" + git config --local user.name "github-actions[bot]" + + BRANCH_NAME="version-bump/$VERSION" + git switch -c "$BRANCH_NAME" + + just prepare-release $VERSION + + echo "branch_name=$BRANCH_NAME" >> $GITHUB_ENV + + - name: Push bump branch + run: | + git push origin "${{ env.branch_name }}" + + - name: Create Pull Request + run: | + PR_URL=$(gh pr create \ + -B main \ + -H "${{ env.branch_name }}" \ + --title "chore(release): bump version to ${{ env.version }} (minor)" \ + --body "Bumps version to **${{ env.version }}**. Merging this PR will trigger creation of the \`release/${{ env.version }}\` branch and release PR.") + echo "pr_url=$PR_URL" >> $GITHUB_ENV + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/merge-release-pr-on-tag.yaml b/.github/workflows/merge-release-pr-on-tag.yaml deleted file mode 100644 index c0709fe064..0000000000 --- a/.github/workflows/merge-release-pr-on-tag.yaml +++ /dev/null @@ -1,148 +0,0 @@ -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@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 - - - 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" - - 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 diff --git a/.github/workflows/minor-release.yaml b/.github/workflows/minor-release.yaml index 8b81026133..106b41c22e 100644 --- a/.github/workflows/minor-release.yaml +++ b/.github/workflows/minor-release.yaml @@ -12,9 +12,7 @@ on: jobs: release: if: github.repository == 'block/goose' - uses: ./.github/workflows/create-release-pr.yaml - with: - bump_type: "minor" + uses: ./.github/workflows/create-version-bump-pr.yaml secrets: ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }} OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }} diff --git a/.github/workflows/patch-release.yaml b/.github/workflows/patch-release.yaml index c0a7217114..c3aa577413 100644 --- a/.github/workflows/patch-release.yaml +++ b/.github/workflows/patch-release.yaml @@ -1,4 +1,4 @@ -name: Create Patch Release PR +name: Create Patch Release permissions: contents: write @@ -8,20 +8,87 @@ on: workflow_dispatch: inputs: target_branch: - description: 'Target branch for hotfix' + description: 'Release branch for patch (e.g. release/1.25.0)' required: true type: string jobs: - hotfix: - uses: ./.github/workflows/create-release-pr.yaml - with: - bump_type: "patch" - target_branch: ${{ inputs.target_branch }} - secrets: - ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }} - OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }} - GOOGLE_API_KEY: ${{ secrets.GOOGLE_API_KEY }} - OPENROUTER_API_KEY: ${{ secrets.OPENROUTER_API_KEY }} - XAI_API_KEY: ${{ secrets.XAI_API_KEY }} - TETRATE_API_KEY: ${{ secrets.TETRATE_API_KEY }} + create-patch-release: + runs-on: ubuntu-latest + env: + TARGET_BRANCH: ${{ inputs.target_branch }} + + steps: + - uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 + with: + ref: ${{ inputs.target_branch }} + fetch-depth: 0 + + - uses: cashapp/activate-hermit@e49f5cb4dd64ff0b0b659d1d8df499595451155a # v1 + - uses: astral-sh/setup-uv@61cb8a9741eeb8a550a1b8544337180c0fc8476b # v7.2.0 + + - name: install dependencies + run: | + sudo apt update -y + sudo apt install -y libdbus-1-dev gnome-keyring libxcb1-dev + + - name: Compute version and create release branch + env: + ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }} + OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }} + GOOGLE_API_KEY: ${{ secrets.GOOGLE_API_KEY }} + OPENROUTER_API_KEY: ${{ secrets.OPENROUTER_API_KEY }} + XAI_API_KEY: ${{ secrets.XAI_API_KEY }} + TETRATE_API_KEY: ${{ secrets.TETRATE_API_KEY }} + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: | + VERSION=$(just get-next-patch-version) + echo "version=$VERSION" >> $GITHUB_ENV + echo "Version: $VERSION (patch on $TARGET_BRANCH)" + + PRIOR_TAG=$(just get-prior-version "$VERSION") + if [[ -z "$PRIOR_TAG" ]]; then + echo "No prior version (first release), using first commit" + PRIOR_TAG=$(git rev-list --max-parents=0 HEAD) + fi + echo "prior_ref=$PRIOR_TAG" >> $GITHUB_ENV + echo "Prior ref: $PRIOR_TAG" + + git config --local user.email "41898282+github-actions[bot]@users.noreply.github.com" + git config --local user.name "github-actions[bot]" + + BRANCH_NAME="release/$VERSION" + git switch -c "$BRANCH_NAME" + + just prepare-release $VERSION + + git push origin "$BRANCH_NAME" + + HEAD_REF=$(git rev-parse HEAD) + echo "branch_name=$BRANCH_NAME" >> $GITHUB_ENV + echo "head_ref=$HEAD_REF" >> $GITHUB_ENV + + - name: Generate release notes + uses: ./.github/actions/generate-release-pr-body + with: + version: ${{ env.version }} + head_ref: ${{ env.head_ref }} + prior_ref: ${{ env.prior_ref }} + + - name: Create Release PR + run: | + PR_URL=$(gh pr create \ + -B "$TARGET_BRANCH" \ + -H "${{ env.branch_name }}" \ + --title "chore(release): release version ${{ env.version }} (patch)" \ + --body-file pr_body.txt) + echo "pr_url=$PR_URL" >> $GITHUB_ENV + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + + - name: Post release checklist comment + run: | + sed 's/{{VERSION}}/${{ env.version }}/g' RELEASE_CHECKLIST.md > checklist_comment.md + gh pr comment "${{ env.pr_url }}" --body-file checklist_comment.md + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/update-release-pr.yaml b/.github/workflows/update-release-pr.yaml index 65d690e854..4b53f74031 100644 --- a/.github/workflows/update-release-pr.yaml +++ b/.github/workflows/update-release-pr.yaml @@ -19,12 +19,7 @@ jobs: steps: - uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 with: - fetch-depth: 0 # to generate complete release log - - - uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 - with: - ref: ${{ github.base_ref }} - path: './prior-version' + fetch-depth: 0 - uses: cashapp/activate-hermit@e49f5cb4dd64ff0b0b659d1d8df499595451155a # v1 - uses: astral-sh/setup-uv@61cb8a9741eeb8a550a1b8544337180c0fc8476b # v7.2.0 @@ -39,10 +34,16 @@ jobs: echo "Version: $VERSION" - name: Get prior version - working-directory: './prior-version' + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} run: | - PRIOR_VERSION=$(just get-tag-version) - echo "prior_ref=v$PRIOR_VERSION" >> $GITHUB_ENV + PRIOR_TAG=$(just get-prior-version "${{ env.version }}") + if [[ -z "$PRIOR_TAG" ]]; then + echo "No prior version (first release), using first commit" + PRIOR_TAG=$(git rev-list --max-parents=0 HEAD) + fi + echo "prior_ref=$PRIOR_TAG" >> $GITHUB_ENV + echo "Prior ref: $PRIOR_TAG" - name: Generate release notes uses: ./.github/actions/generate-release-pr-body diff --git a/Justfile b/Justfile index 2d29d7cdf0..0b201525eb 100644 --- a/Justfile +++ b/Justfile @@ -306,20 +306,41 @@ get-next-minor-version: get-next-patch-version: @python -c "import sys; v=sys.argv[1].split('.'); print(f'{v[0]}.{v[1]}.{int(v[2])+1}')" $(just get-tag-version) -# set cargo and app versions, must be semver -prepare-release version: +# derive the prior release tag from a version +# patch bump (e.g. 1.25.1): prior is v1.25.0 (deterministic) +# minor bump (e.g. 1.26.0): prior is highest v1.25.* GitHub release +get-prior-version version: + #!/usr/bin/env bash + IFS='.' read -r major minor patch <<< "{{ version }}" + if [[ "$patch" -gt 0 ]]; then + echo "v${major}.${minor}.$((patch - 1))" + elif [[ "$minor" -gt 0 ]]; then + prev_minor=$((minor - 1)) + prefix="v${major}.${prev_minor}." + best=$(gh release list --limit 100 --exclude-drafts --exclude-pre-releases \ + --json tagName --jq "[.[] | select(.tagName | startswith(\"${prefix}\"))][0].tagName") + if [[ -n "$best" && "$best" != "null" ]]; then + echo "$best" + fi + fi + +# update version numbers in all manifests +bump-version version: @just validate {{ version }} || exit 1 - - @git switch -c "release/{{ version }}" @uvx --from=toml-cli toml set --toml-path=Cargo.toml "workspace.package.version" {{ version }} - @cd ui/desktop && pnpm version {{ version }} --no-git-tag-version --allow-same-version - - # see --workspace flag https://doc.rust-lang.org/cargo/commands/cargo-update.html - # used to update Cargo.lock after we've bumped versions in Cargo.toml + # update Cargo.lock after bumping versions in Cargo.toml @cargo update --workspace @just set-openapi-version {{ version }} + +# rebuild canonical model registry and mapping report from models.dev +build-canonical-models: @cargo run --bin build_canonical_models + +# bump version, rebuild canonical models, and commit +prepare-release version: + @just bump-version {{ version }} + @just build-canonical-models @git add \ Cargo.toml \ Cargo.lock \ diff --git a/RELEASE.md b/RELEASE.md index 881ca6d290..2f78f5ae33 100644 --- a/RELEASE.md +++ b/RELEASE.md @@ -2,28 +2,49 @@ You'll generally create one of two release types: a regular feature release (minor version bump like 1.20) or a bug-fixing patch release (patch version bump like 1.20.1). -Regular releases start on main, while patch releases start with an existing release tag. goose uses GitHub actions to automate the creation of release branches. The actual releases are triggered by tags. -For bug-fixing releases, you will cherry-pick fixes into that branch, test, and then release from it. +goose uses GitHub actions to automate the release process. The actual releases are triggered by tags. ## Minor version releases -These are typically done once per week. There is an [action](https://github.com/block/goose/actions/workflows/minor-release.yaml) that cuts the branch every Tuesday, but it can also be triggered manually. Commits from main can be cherry-picked into this branch as needed before release. +These are typically done once per week. The process has two automated phases: -To trigger the release, find [the corresponding PR](https://github.com/block/goose/pulls?q=is%3Apr+%22chore%28release%29%22+%22%28minor%29%22+author%3Aapp%2Fgithub-actions+) and follow the instructions in the PR description. +1. **Version bump PR** — An [action](https://github.com/block/goose/actions/workflows/minor-release.yaml) runs every Tuesday (or can be triggered manually) that creates a PR to bump the version on `main`. Review and merge this PR. + +2. **Release branch + PR** — When the version bump PR merges, automation creates a `release/` branch from `main` and opens a release PR with a QA checklist. + +From there: +- Test locally if you can (`just run-ui`) +- Cherry-pick any last-minute fixes into the release branch if needed +- Download and test the .zip from the release PR +- When ready, follow the instructions on the release PR to tag and release + +To trigger the release, find [the corresponding PR](https://github.com/block/goose/pulls?q=is%3Apr+%22chore%28release%29%22+author%3Aapp%2Fgithub-actions+) and follow the instructions in the PR description. ## Patch version releases -Minor and patch releases both trigger the creation of a branch for a follow-on patch release. These branches can be used to create patch releases, or can be safely ignored/closed. -You can cherry pick fixes into this branch. +When a minor release is tagged, automation immediately creates the next patch release branch (e.g. `release/1.25.1` from `release/1.25.0`) with the version already bumped and a release PR open. Cherry-pick fixes into this branch, then tag when ready. To trigger the release, find [the corresponding PR](https://github.com/block/goose/pulls?q=is%3Apr+%22chore%28release%29%22+%22%28patch%29%22+author%3Aapp%2Fgithub-actions+) and follow the instructions in the PR description. - ## High level release flow: -* check out and cherry-pick (if needed) changes to the branch you are going to release (eg the patch branch) -* Test locally if you can (just run-ui) -* Push changes to that branch, wait for build -* Download and test the .zip from the release PR -* If happy, follow the instructions on the release PR to tag and release (tagging will trigger the real release from there) - +``` +minor-release (cron/manual) + │ + ▼ +version-bump PR → main (merge this) + │ + ▼ (on merge) +release/ branch created from main +release PR opened (for QA, cherry-picks, testing) + │ + ▼ (when ready) +git tag v origin/release/ && git push origin v + │ + ├─► release PR auto-closed (no merge needed) + ├─► release.yml builds & publishes + └─► patch release/ branch + PR auto-created + │ + ▼ (cherry-pick fixes, then when ready) + git tag v origin/release/ && git push origin v +```