Skip to content

Commit f1dc4d7

Browse files
chore: Migrate releases to sampo (#398)
* chore: Migrate releases to `sampo` This is much closer to what we have in `posthog-js`, let's see if it's a good thing! There's still a lot to do before deploying this: - updating CI - updating README with instructions * Add sampo changeset * chore: Update to relase Python via Slack + sampo * Update release.yml * fix: Use pyproject.toml version as source of truth
1 parent 23dae56 commit f1dc4d7

9 files changed

Lines changed: 340 additions & 104 deletions

File tree

.github/workflows/release.yml

Lines changed: 219 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -1,58 +1,249 @@
11
name: "Release"
22

33
on:
4-
push:
5-
branches:
6-
- master
7-
paths:
8-
- "posthog/version.py"
4+
pull_request:
5+
types: [closed]
6+
branches: [master]
97
workflow_dispatch:
108

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+
1118
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+
1261
release:
13-
name: Publish release
62+
name: Release and publish
63+
needs: [check-release-label, notify-approval-needed]
1464
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
1569
permissions:
1670
contents: write
71+
actions: write
1772
id-token: write
1873
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
2193
with:
94+
ref: master
2295
fetch-depth: 0
96+
token: ${{ steps.releaser.outputs.token }}
2397

2498
- name: Set up Python
25-
uses: actions/setup-python@8d9ed9ac5c53483de85588cdf95a591a75ab9f55
99+
uses: actions/setup-python@v5
26100
with:
27101
python-version: 3.11.11
28102

29103
- name: Install uv
30-
uses: astral-sh/setup-uv@0c5e2b8115b80b4c7c5ddf6ffdd634974642d182 # v5.4.1
104+
uses: astral-sh/setup-uv@v5
31105
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"
37108

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
39127
run: uv sync --extra dev
40128

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
42136
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"
45142
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
47149
env:
48-
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
150+
GITHUB_TOKEN: ${{ steps.releaser.outputs.token }}
49151
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'
55204
env:
56205
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"
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
pypi/posthog: patch
3+
---
4+
5+
Add sampo to the project

.sampo/config.toml

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
# Sampo configuration
2+
version = 1
3+
4+
[git]
5+
default_branch = "master"
6+
short_tags = "posthog" # Tag with v1.2.3 rather than posthog-v1.2.3
7+
8+
[github]
9+
repository = "posthog/posthog-python"
10+
11+
[changelog]
12+
# Options for release notes generation.
13+
# show_commit_hash = true (default)
14+
# show_acknowledgments = true (default)
15+
16+
[packages]
17+
# Options for package discovery and filtering.
18+
# ignore_unpublished = false (default)
19+
# ignore = ["internal-*", "examples/*"]

0 commit comments

Comments
 (0)