|
| 1 | +name: Cut Release |
| 2 | + |
| 3 | +# The whole release runs here; nothing is cut from a laptop. |
| 4 | +# |
| 5 | +# 1. Trigger this workflow (Actions tab, or |
| 6 | +# `gh workflow run cut-release.yml -f bump=minor`). The `prepare` job runs |
| 7 | +# the existing `scripts/release.sh prepare`, which bumps the version, dates |
| 8 | +# the [Unreleased] changelog section, pushes `release/vX.Y.Z` and opens the |
| 9 | +# release PR. Auto-merge is enabled, so the PR merges once branch |
| 10 | +# protection is satisfied (checks green, review approved). |
| 11 | +# 2. When that PR merges, the `publish` job runs `scripts/release.sh publish`, |
| 12 | +# which tags main `vX.Y.Z` and pushes the tag. |
| 13 | +# 3. The tag push triggers publish.yml (PyPI) and release.yml (GitHub Release), |
| 14 | +# exactly as before. |
| 15 | +# |
| 16 | +# Both jobs act as the Hotdata automation GitHub App rather than GITHUB_TOKEN: |
| 17 | +# a PR opened or a tag pushed with GITHUB_TOKEN triggers no other workflows, so |
| 18 | +# the release PR would run no checks and the tag would publish nothing. |
| 19 | + |
| 20 | +on: |
| 21 | + workflow_dispatch: |
| 22 | + inputs: |
| 23 | + bump: |
| 24 | + description: 'patch | minor | major | explicit X.Y.Z' |
| 25 | + required: true |
| 26 | + default: patch |
| 27 | + type: string |
| 28 | + pull_request: |
| 29 | + types: [closed] |
| 30 | + branches: [main] |
| 31 | + |
| 32 | +permissions: {} |
| 33 | + |
| 34 | +concurrency: |
| 35 | + group: cut-release |
| 36 | + cancel-in-progress: false |
| 37 | + |
| 38 | +jobs: |
| 39 | + prepare: |
| 40 | + name: Open release PR |
| 41 | + if: github.event_name == 'workflow_dispatch' |
| 42 | + runs-on: ubuntu-latest |
| 43 | + steps: |
| 44 | + - name: Generate GitHub App token |
| 45 | + id: app-token |
| 46 | + uses: actions/create-github-app-token@bcd2ba49218906704ab6c1aa796996da409d3eb1 # v3.2.0 |
| 47 | + with: |
| 48 | + client-id: Iv23liKBX2RYMoZIYuKa |
| 49 | + private-key: ${{ secrets.HOTDATA_AUTOMATION_PRIVATE_KEY }} |
| 50 | + owner: hotdata-dev |
| 51 | + |
| 52 | + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v6 |
| 53 | + with: |
| 54 | + token: ${{ steps.app-token.outputs.token }} |
| 55 | + fetch-depth: 0 |
| 56 | + fetch-tags: true |
| 57 | + |
| 58 | + - uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6 |
| 59 | + with: |
| 60 | + python-version: '3.12' |
| 61 | + |
| 62 | + # `release.sh prepare` refreshes uv.lock when uv is on PATH, so the lock |
| 63 | + # the PR carries matches the bumped version. |
| 64 | + - uses: astral-sh/setup-uv@1c37ad07a6a961277cf70c0d37d6f313000f5884 # v10.3.0 |
| 65 | + |
| 66 | + - name: Configure git identity |
| 67 | + env: |
| 68 | + GH_TOKEN: ${{ steps.app-token.outputs.token }} |
| 69 | + run: | |
| 70 | + slug="${{ steps.app-token.outputs.app-slug }}[bot]" |
| 71 | + id="$(gh api "/users/${slug}" --jq .id)" |
| 72 | + git config user.name "$slug" |
| 73 | + git config user.email "${id}+${slug}@users.noreply.github.com" |
| 74 | +
|
| 75 | + - name: Prepare release |
| 76 | + env: |
| 77 | + GH_TOKEN: ${{ steps.app-token.outputs.token }} |
| 78 | + run: ./scripts/release.sh prepare "${{ inputs.bump }}" |
| 79 | + |
| 80 | + # Branch protection on main gates the merge on the checks and the |
| 81 | + # approving review, so this only merges once everything is green. |
| 82 | + - name: Enable auto-merge on the release PR |
| 83 | + env: |
| 84 | + GH_TOKEN: ${{ steps.app-token.outputs.token }} |
| 85 | + run: | |
| 86 | + branch="$(git rev-parse --abbrev-ref HEAD)" |
| 87 | + number="$(gh pr list --head "$branch" --state open --json number --jq '.[0].number')" |
| 88 | + [[ -n "$number" ]] || { echo "::error::no open PR for $branch"; exit 1; } |
| 89 | + gh pr merge "$number" --squash --auto |
| 90 | + echo "Release PR #$number opened from $branch with auto-merge enabled." |
| 91 | +
|
| 92 | + publish: |
| 93 | + name: Tag release |
| 94 | + if: >- |
| 95 | + github.event_name == 'pull_request' |
| 96 | + && github.event.pull_request.merged == true |
| 97 | + && startsWith(github.event.pull_request.head.ref, 'release/v') |
| 98 | + runs-on: ubuntu-latest |
| 99 | + steps: |
| 100 | + - name: Generate GitHub App token |
| 101 | + id: app-token |
| 102 | + uses: actions/create-github-app-token@bcd2ba49218906704ab6c1aa796996da409d3eb1 # v3.2.0 |
| 103 | + with: |
| 104 | + client-id: Iv23liKBX2RYMoZIYuKa |
| 105 | + private-key: ${{ secrets.HOTDATA_AUTOMATION_PRIVATE_KEY }} |
| 106 | + owner: hotdata-dev |
| 107 | + |
| 108 | + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v6 |
| 109 | + with: |
| 110 | + ref: main |
| 111 | + token: ${{ steps.app-token.outputs.token }} |
| 112 | + fetch-depth: 0 |
| 113 | + fetch-tags: true |
| 114 | + |
| 115 | + - uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6 |
| 116 | + with: |
| 117 | + python-version: '3.12' |
| 118 | + |
| 119 | + - name: Configure git identity |
| 120 | + env: |
| 121 | + GH_TOKEN: ${{ steps.app-token.outputs.token }} |
| 122 | + run: | |
| 123 | + slug="${{ steps.app-token.outputs.app-slug }}[bot]" |
| 124 | + id="$(gh api "/users/${slug}" --jq .id)" |
| 125 | + git config user.name "$slug" |
| 126 | + git config user.email "${id}+${slug}@users.noreply.github.com" |
| 127 | +
|
| 128 | + - name: Tag and push |
| 129 | + env: |
| 130 | + GH_TOKEN: ${{ steps.app-token.outputs.token }} |
| 131 | + run: ./scripts/release.sh publish |
0 commit comments