|
1 | 1 | name: "Release" |
2 | 2 |
|
3 | 3 | on: |
4 | | - push: |
5 | | - branches: |
6 | | - - master |
7 | | - paths: |
8 | | - - "posthog/version.py" |
| 4 | + pull_request: |
| 5 | + types: [closed] |
| 6 | + branches: [master] |
9 | 7 | workflow_dispatch: |
10 | 8 |
|
| 9 | +permissions: |
| 10 | + contents: read |
| 11 | + |
| 12 | +# Concurrency control: only one release process can run at a time |
| 13 | +# This prevents race conditions if multiple PRs with 'release' label merge simultaneously |
| 14 | +concurrency: |
| 15 | + group: release |
| 16 | + cancel-in-progress: false |
| 17 | + |
11 | 18 | jobs: |
| 19 | + check-release-label: |
| 20 | + name: Check for release label |
| 21 | + runs-on: ubuntu-latest |
| 22 | + # Run when PR with 'release' label is merged to master |
| 23 | + if: | |
| 24 | + github.event_name == 'workflow_dispatch' || |
| 25 | + (github.event_name == 'pull_request' && |
| 26 | + github.event.pull_request.merged == true && |
| 27 | + contains(github.event.pull_request.labels.*.name, 'release')) |
| 28 | + outputs: |
| 29 | + should-release: ${{ steps.check.outputs.should-release }} |
| 30 | + steps: |
| 31 | + - name: Checkout repository |
| 32 | + uses: actions/checkout@v4 |
| 33 | + with: |
| 34 | + ref: master |
| 35 | + fetch-depth: 0 |
| 36 | + |
| 37 | + - name: Check release conditions |
| 38 | + id: check |
| 39 | + run: | |
| 40 | + changeset_count=$(find .sampo/changesets -name '*.md' 2>/dev/null | wc -l) |
| 41 | + if [ "$changeset_count" -gt 0 ]; then |
| 42 | + echo "should-release=true" >> "$GITHUB_OUTPUT" |
| 43 | + echo "Found $changeset_count changeset(s), ready to release" |
| 44 | + else |
| 45 | + echo "should-release=false" >> "$GITHUB_OUTPUT" |
| 46 | + echo "No changesets to release" |
| 47 | + fi |
| 48 | +
|
| 49 | + notify-approval-needed: |
| 50 | + name: Notify Slack - Approval Needed |
| 51 | + needs: check-release-label |
| 52 | + if: needs.check-release-label.outputs.should-release == 'true' |
| 53 | + uses: posthog/.github/.github/workflows/notify-approval-needed.yml@main |
| 54 | + with: |
| 55 | + slack_channel_id: ${{ vars.SLACK_APPROVALS_CLIENT_LIBRARIES_CHANNEL_ID }} |
| 56 | + slack_user_group_id: ${{ vars.GROUP_CLIENT_LIBRARIES_SLACK_GROUP_ID }} |
| 57 | + secrets: |
| 58 | + slack_bot_token: ${{ secrets.SLACK_CLIENT_LIBRARIES_BOT_TOKEN }} |
| 59 | + posthog_project_api_key: ${{ secrets.POSTHOG_PROJECT_API_KEY }} |
| 60 | + |
12 | 61 | release: |
13 | | - name: Publish release |
| 62 | + name: Release and publish |
| 63 | + needs: [check-release-label, notify-approval-needed] |
14 | 64 | runs-on: ubuntu-latest |
| 65 | + # Use `always()` to ensure the job runs even if notify-approval-needed is skipped, |
| 66 | + # but still depend on it to access `needs.notify-approval-needed.outputs.slack_ts` |
| 67 | + if: always() && needs.check-release-label.outputs.should-release == 'true' |
| 68 | + environment: "Release" # This will require an approval from a maintainer, they are notified in Slack above |
15 | 69 | permissions: |
16 | 70 | contents: write |
| 71 | + actions: write |
17 | 72 | id-token: write |
18 | 73 | steps: |
19 | | - - name: Checkout the repository |
20 | | - uses: actions/checkout@85e6279cec87321a52edac9c87bce653a07cf6c2 |
| 74 | + - name: Notify Slack - Approved |
| 75 | + if: needs.notify-approval-needed.outputs.slack_ts != '' |
| 76 | + uses: posthog/.github/.github/actions/slack-thread-reply@main |
| 77 | + with: |
| 78 | + slack_bot_token: ${{ secrets.SLACK_CLIENT_LIBRARIES_BOT_TOKEN }} |
| 79 | + slack_channel_id: ${{ vars.SLACK_APPROVALS_CLIENT_LIBRARIES_CHANNEL_ID }} |
| 80 | + thread_ts: ${{ needs.notify-approval-needed.outputs.slack_ts }} |
| 81 | + message: "✅ Release approved! Version bump in progress..." |
| 82 | + emoji_reaction: "white_check_mark" |
| 83 | + |
| 84 | + - name: Get GitHub App token |
| 85 | + id: releaser |
| 86 | + uses: actions/create-github-app-token@v2 |
| 87 | + with: |
| 88 | + app-id: ${{ secrets.GH_APP_POSTHOG_PYTHON_RELEASER_APP_ID }} |
| 89 | + private-key: ${{ secrets.GH_APP_POSTHOG_PYTHON_RELEASER_PRIVATE_KEY }} |
| 90 | + |
| 91 | + - name: Checkout repository |
| 92 | + uses: actions/checkout@v4 |
21 | 93 | with: |
| 94 | + ref: master |
22 | 95 | fetch-depth: 0 |
| 96 | + token: ${{ steps.releaser.outputs.token }} |
23 | 97 |
|
24 | 98 | - name: Set up Python |
25 | | - uses: actions/setup-python@8d9ed9ac5c53483de85588cdf95a591a75ab9f55 |
| 99 | + uses: actions/setup-python@v5 |
26 | 100 | with: |
27 | 101 | python-version: 3.11.11 |
28 | 102 |
|
29 | 103 | - name: Install uv |
30 | | - uses: astral-sh/setup-uv@0c5e2b8115b80b4c7c5ddf6ffdd634974642d182 # v5.4.1 |
| 104 | + uses: astral-sh/setup-uv@v5 |
31 | 105 | with: |
32 | | - enable-cache: true |
33 | | - pyproject-file: 'pyproject.toml' |
34 | | - |
35 | | - - name: Detect version |
36 | | - run: echo "REPO_VERSION=$(python3 posthog/version.py)" >> $GITHUB_ENV |
| 106 | + enable-cache: true |
| 107 | + pyproject-file: "pyproject.toml" |
37 | 108 |
|
38 | | - - name: Prepare for building release |
| 109 | + - name: Install Rust |
| 110 | + uses: dtolnay/rust-toolchain@0b1efabc08b657293548b77fb76cc02d26091c7e |
| 111 | + with: |
| 112 | + toolchain: 1.91.1 |
| 113 | + components: cargo |
| 114 | + |
| 115 | + - name: Cache Sampo CLI |
| 116 | + id: cache-sampo |
| 117 | + uses: actions/cache@v3 |
| 118 | + with: |
| 119 | + path: ~/.cargo/bin/sampo |
| 120 | + key: sampo-${{ runner.os }}-${{ runner.arch }} |
| 121 | + |
| 122 | + - name: Install Sampo CLI |
| 123 | + if: steps.cache-sampo.outputs.cache-hit != 'true' |
| 124 | + run: cargo install sampo |
| 125 | + |
| 126 | + - name: Install dependencies |
39 | 127 | run: uv sync --extra dev |
40 | 128 |
|
41 | | - - name: Push releases to PyPI |
| 129 | + - name: Configure Git |
| 130 | + run: | |
| 131 | + git config user.name "github-actions[bot]" |
| 132 | + git config user.email "github-actions[bot]@users.noreply.github.com" |
| 133 | +
|
| 134 | + - name: Prepare release with Sampo |
| 135 | + id: sampo-release |
42 | 136 | env: |
43 | | - TWINE_USERNAME: __token__ |
44 | | - run: uv run make release && uv run make release_analytics |
| 137 | + GITHUB_TOKEN: ${{ steps.releaser.outputs.token }} |
| 138 | + run: | |
| 139 | + sampo release |
| 140 | + new_version=$(python3 -c "import tomllib; print(tomllib.load(open('pyproject.toml', 'rb'))['project']['version'])") |
| 141 | + echo "new_version=$new_version" >> "$GITHUB_OUTPUT" |
45 | 142 |
|
46 | | - - name: Create GitHub release |
| 143 | + - name: Sync version to posthog/version.py |
| 144 | + run: | |
| 145 | + echo 'VERSION = "${{ steps.sampo-release.outputs.new_version }}"' > posthog/version.py |
| 146 | +
|
| 147 | + - name: Commit release changes |
| 148 | + id: commit-release |
47 | 149 | env: |
48 | | - GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} |
| 150 | + GITHUB_TOKEN: ${{ steps.releaser.outputs.token }} |
49 | 151 | run: | |
50 | | - gh release create "v${{ env.REPO_VERSION }}" \ |
51 | | - --title "${{ env.REPO_VERSION }}" \ |
52 | | - --generate-notes |
53 | | - |
54 | | - - name: Dispatch generate-references for posthog-python |
| 152 | + git add -A |
| 153 | + if git diff --staged --quiet; then |
| 154 | + echo "No changes to commit" |
| 155 | + echo "committed=false" >> "$GITHUB_OUTPUT" |
| 156 | + else |
| 157 | + git commit -m "chore: Release v${{ steps.sampo-release.outputs.new_version }}" |
| 158 | + git push origin master |
| 159 | + echo "committed=true" >> "$GITHUB_OUTPUT" |
| 160 | + fi |
| 161 | +
|
| 162 | + # Publishing is done manually (not via `sampo publish`) because we need to |
| 163 | + # publish both `posthog` and `posthoganalytics` packages to PyPI. |
| 164 | + # Sampo only knows about the `posthog` package, so we handle both here. |
| 165 | + # Both packages use PyPI OIDC trusted publishing (no API tokens needed). |
| 166 | + - name: Build posthog |
| 167 | + if: steps.commit-release.outputs.committed == 'true' |
| 168 | + run: uv run make build_release |
| 169 | + |
| 170 | + - name: Publish posthog to PyPI |
| 171 | + if: steps.commit-release.outputs.committed == 'true' |
| 172 | + uses: pypa/gh-action-pypi-publish@release/v1 |
| 173 | + |
| 174 | + # The `posthoganalytics` package is a mirror of `posthog` published under |
| 175 | + # a different name for backwards compatibility. The make target handles |
| 176 | + # copying, renaming imports, and building the dist automatically. |
| 177 | + - name: Build posthoganalytics |
| 178 | + if: steps.commit-release.outputs.committed == 'true' |
| 179 | + run: uv run make build_release_analytics |
| 180 | + |
| 181 | + - name: Publish posthoganalytics to PyPI |
| 182 | + if: steps.commit-release.outputs.committed == 'true' |
| 183 | + uses: pypa/gh-action-pypi-publish@release/v1 |
| 184 | + |
| 185 | + # We skip `sampo publish` (which normally creates the tag) because we |
| 186 | + # need to publish both posthog and posthoganalytics manually, so we |
| 187 | + # create the tag ourselves. |
| 188 | + - name: Tag release |
| 189 | + if: steps.commit-release.outputs.committed == 'true' |
| 190 | + run: git tag "v${{ steps.sampo-release.outputs.new_version }}" |
| 191 | + |
| 192 | + - name: Push tags |
| 193 | + if: steps.commit-release.outputs.committed == 'true' |
| 194 | + run: git push origin --tags |
| 195 | + |
| 196 | + - name: Create GitHub Release |
| 197 | + if: steps.commit-release.outputs.committed == 'true' |
| 198 | + env: |
| 199 | + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} |
| 200 | + run: gh release create "v${{ steps.sampo-release.outputs.new_version }}" --generate-notes |
| 201 | + |
| 202 | + - name: Dispatch generate-references |
| 203 | + if: steps.commit-release.outputs.committed == 'true' |
55 | 204 | env: |
56 | 205 | GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} |
57 | | - run: | |
58 | | - gh workflow run generate-references.yml --ref master |
| 206 | + run: gh workflow run generate-references.yml --ref master |
| 207 | + |
| 208 | + # Notify in case of a failure |
| 209 | + - name: Send failure event to PostHog |
| 210 | + if: ${{ failure() }} |
| 211 | + uses: PostHog/posthog-github-action@v0.1 |
| 212 | + with: |
| 213 | + posthog-token: "${{ secrets.POSTHOG_PROJECT_API_KEY }}" |
| 214 | + event: "posthog-python-github-release-workflow-failure" |
| 215 | + properties: >- |
| 216 | + { |
| 217 | + "commitSha": "${{ github.sha }}", |
| 218 | + "jobStatus": "${{ job.status }}", |
| 219 | + "ref": "${{ github.ref }}", |
| 220 | + "version": "v${{ steps.sampo-release.outputs.new_version }}" |
| 221 | + } |
| 222 | +
|
| 223 | + - name: Notify Slack - Failed |
| 224 | + if: ${{ failure() && needs.notify-approval-needed.outputs.slack_ts != '' }} |
| 225 | + uses: posthog/.github/.github/actions/slack-thread-reply@main |
| 226 | + with: |
| 227 | + slack_bot_token: ${{ secrets.SLACK_CLIENT_LIBRARIES_BOT_TOKEN }} |
| 228 | + slack_channel_id: ${{ vars.SLACK_APPROVALS_CLIENT_LIBRARIES_CHANNEL_ID }} |
| 229 | + thread_ts: ${{ needs.notify-approval-needed.outputs.slack_ts }} |
| 230 | + message: "❌ Failed to release `posthog-python@v${{ steps.sampo-release.outputs.new_version }}`! <https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }}|View logs>" |
| 231 | + emoji_reaction: "x" |
| 232 | + |
| 233 | + notify-released: |
| 234 | + name: Notify Slack - Released |
| 235 | + needs: [check-release-label, notify-approval-needed, release] |
| 236 | + runs-on: ubuntu-latest |
| 237 | + if: always() && needs.release.result == 'success' && needs.notify-approval-needed.outputs.slack_ts != '' |
| 238 | + steps: |
| 239 | + - name: Checkout repository |
| 240 | + uses: actions/checkout@v4 |
| 241 | + |
| 242 | + - name: Notify Slack - Released |
| 243 | + uses: posthog/.github/.github/actions/slack-thread-reply@main |
| 244 | + with: |
| 245 | + slack_bot_token: ${{ secrets.SLACK_CLIENT_LIBRARIES_BOT_TOKEN }} |
| 246 | + slack_channel_id: ${{ vars.SLACK_APPROVALS_CLIENT_LIBRARIES_CHANNEL_ID }} |
| 247 | + thread_ts: ${{ needs.notify-approval-needed.outputs.slack_ts }} |
| 248 | + message: "🚀 posthog-python released successfully!" |
| 249 | + emoji_reaction: "rocket" |
0 commit comments