Skip to content

Commit e95b57c

Browse files
authored
Merge pull request #157 from PostHog/haacked/better-release-process
Add label-driven release workflow
2 parents 4a23c6c + d863bff commit e95b57c

4 files changed

Lines changed: 249 additions & 34 deletions

File tree

.github/workflows/main.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ jobs:
3232
fetch-depth: 0
3333

3434
- name: Setup .NET Core
35-
uses: actions/setup-dotnet@v3.0.3
35+
uses: actions/setup-dotnet@v4
3636
with:
3737
dotnet-version: ${{ env.DOTNET_VERSION }}
3838

.github/workflows/release.yml

Lines changed: 232 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,232 @@
1+
name: Release
2+
3+
on:
4+
pull_request:
5+
types: [closed]
6+
branches: [main]
7+
8+
permissions:
9+
contents: read
10+
11+
concurrency:
12+
group: release
13+
cancel-in-progress: false
14+
15+
jobs:
16+
check-release-label:
17+
name: Check for release label
18+
if: |
19+
github.event.pull_request.merged == true
20+
&& contains(github.event.pull_request.labels.*.name, 'release')
21+
runs-on: ubuntu-latest
22+
outputs:
23+
should-release: ${{ steps.check.outputs.should-release }}
24+
bump-type: ${{ steps.check.outputs.bump-type }}
25+
steps:
26+
- name: Check release conditions
27+
id: check
28+
run: |
29+
if ${{ contains(github.event.pull_request.labels.*.name, 'bump-major') }}; then
30+
echo "bump-type=major" >> "$GITHUB_OUTPUT"
31+
echo "should-release=true" >> "$GITHUB_OUTPUT"
32+
elif ${{ contains(github.event.pull_request.labels.*.name, 'bump-minor') }}; then
33+
echo "bump-type=minor" >> "$GITHUB_OUTPUT"
34+
echo "should-release=true" >> "$GITHUB_OUTPUT"
35+
elif ${{ contains(github.event.pull_request.labels.*.name, 'bump-patch') }}; then
36+
echo "bump-type=patch" >> "$GITHUB_OUTPUT"
37+
echo "should-release=true" >> "$GITHUB_OUTPUT"
38+
else
39+
echo "should-release=false" >> "$GITHUB_OUTPUT"
40+
fi
41+
42+
notify-approval-needed:
43+
name: Notify Slack - Approval Needed
44+
needs: check-release-label
45+
if: needs.check-release-label.outputs.should-release == 'true'
46+
uses: posthog/.github/.github/workflows/notify-approval-needed.yml@main
47+
with:
48+
slack_channel_id: ${{ vars.SLACK_APPROVALS_CLIENT_LIBRARIES_CHANNEL_ID }}
49+
slack_user_group_id: ${{ vars.GROUP_CLIENT_LIBRARIES_SLACK_GROUP_ID }}
50+
secrets:
51+
slack_bot_token: ${{ secrets.SLACK_CLIENT_LIBRARIES_BOT_TOKEN }}
52+
posthog_project_api_key: ${{ secrets.POSTHOG_PROJECT_API_KEY }}
53+
54+
release:
55+
name: Bump version and release
56+
needs: [check-release-label, notify-approval-needed]
57+
runs-on: ubuntu-latest
58+
# Use `always()` so the release proceeds even if the Slack notification fails —
59+
# a Slack outage shouldn't block releases.
60+
if: always() && needs.check-release-label.outputs.should-release == 'true'
61+
environment: Release
62+
permissions:
63+
contents: write
64+
actions: write
65+
env:
66+
DOTNET_SKIP_FIRST_TIME_EXPERIENCE: 1
67+
DOTNET_NOLOGO: true
68+
DOTNET_VERSION: "9.0.102"
69+
steps:
70+
- name: Notify Slack - Approved
71+
if: needs.notify-approval-needed.outputs.slack_ts != ''
72+
uses: posthog/.github/.github/actions/slack-thread-reply@main
73+
with:
74+
slack_bot_token: ${{ secrets.SLACK_CLIENT_LIBRARIES_BOT_TOKEN }}
75+
slack_channel_id: ${{ vars.SLACK_APPROVALS_CLIENT_LIBRARIES_CHANNEL_ID }}
76+
thread_ts: ${{ needs.notify-approval-needed.outputs.slack_ts }}
77+
message: "✅ Release approved! Version bump in progress..."
78+
emoji_reaction: "white_check_mark"
79+
80+
- name: Get GitHub App token
81+
id: releaser
82+
uses: actions/create-github-app-token@v2
83+
with:
84+
app-id: ${{ secrets.GH_APP_POSTHOG_DOTNET_RELEASER_APP_ID }}
85+
private-key: ${{ secrets.GH_APP_POSTHOG_DOTNET_RELEASER_PRIVATE_KEY }}
86+
87+
- name: Checkout
88+
uses: actions/checkout@v4
89+
with:
90+
ref: main
91+
fetch-depth: 0
92+
token: ${{ steps.releaser.outputs.token }}
93+
94+
- name: Configure Git
95+
run: |
96+
git config user.name "github-actions[bot]"
97+
git config user.email "github-actions[bot]@users.noreply.github.com"
98+
99+
- name: Setup .NET
100+
uses: actions/setup-dotnet@v4
101+
with:
102+
dotnet-version: ${{ env.DOTNET_VERSION }}
103+
104+
- name: Calculate new version
105+
id: version
106+
run: |
107+
current=$(grep -o '<Version>[^<]*' Directory.Build.props | head -n 1 | sed 's/<Version>//')
108+
IFS='.' read -r major minor patch <<< "$current"
109+
patch=$(echo "$patch" | cut -d- -f1)
110+
case "${{ needs.check-release-label.outputs.bump-type }}" in
111+
major) major=$((major + 1)); minor=0; patch=0 ;;
112+
minor) minor=$((minor + 1)); patch=0 ;;
113+
patch) patch=$((patch + 1)) ;;
114+
esac
115+
new_version="$major.$minor.$patch"
116+
echo "current=$current" >> "$GITHUB_OUTPUT"
117+
echo "new=$new_version" >> "$GITHUB_OUTPUT"
118+
echo "Bumping $current → $new_version"
119+
120+
- name: Update version
121+
run: |
122+
sed -i "s|<Version>${{ steps.version.outputs.current }}</Version>|<Version>${{ steps.version.outputs.new }}</Version>|" Directory.Build.props
123+
124+
- name: Build
125+
run: dotnet build --configuration Release
126+
127+
- name: Test
128+
run: dotnet test --configuration Release --no-build
129+
130+
- name: Commit version bump
131+
id: commit-version-bump
132+
env:
133+
GITHUB_TOKEN: ${{ steps.releaser.outputs.token }}
134+
run: |
135+
git add Directory.Build.props src/PostHog/Generated/VersionConstants.cs
136+
if git diff --staged --quiet; then
137+
echo "No changes to commit"
138+
echo "committed=false" >> "$GITHUB_OUTPUT"
139+
else
140+
git commit -m "Bump version to ${{ steps.version.outputs.new }}"
141+
git push origin main
142+
echo "committed=true" >> "$GITHUB_OUTPUT"
143+
fi
144+
145+
- name: Create tag and GitHub Release
146+
if: steps.commit-version-bump.outputs.committed == 'true'
147+
env:
148+
GH_TOKEN: ${{ steps.releaser.outputs.token }}
149+
run: |
150+
version="${{ steps.version.outputs.new }}"
151+
git tag -a "v$version" -m "v$version"
152+
git push origin "v$version"
153+
gh release create "v$version" --generate-notes
154+
155+
- name: Send failure event to PostHog
156+
if: failure()
157+
uses: PostHog/posthog-github-action@v0.1
158+
with:
159+
posthog-token: "${{ secrets.POSTHOG_PROJECT_API_KEY }}"
160+
event: "posthog-dotnet-github-release-workflow-failure"
161+
properties: >-
162+
{
163+
"commitSha": "${{ github.sha }}",
164+
"jobStatus": "${{ job.status }}",
165+
"ref": "${{ github.ref }}",
166+
"version": "v${{ steps.version.outputs.new }}"
167+
}
168+
169+
- name: Notify Slack - Failed
170+
if: failure() && needs.notify-approval-needed.outputs.slack_ts != ''
171+
uses: posthog/.github/.github/actions/slack-thread-reply@main
172+
with:
173+
slack_bot_token: ${{ secrets.SLACK_CLIENT_LIBRARIES_BOT_TOKEN }}
174+
slack_channel_id: ${{ vars.SLACK_APPROVALS_CLIENT_LIBRARIES_CHANNEL_ID }}
175+
thread_ts: ${{ needs.notify-approval-needed.outputs.slack_ts }}
176+
message: "❌ Failed to release `posthog-dotnet@v${{ steps.version.outputs.new }}`! <https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }}|View logs>"
177+
emoji_reaction: "x"
178+
179+
notify-rejected:
180+
name: Notify Slack - Rejected
181+
needs: [release, notify-approval-needed]
182+
runs-on: ubuntu-latest
183+
if: always() && (needs.release.result == 'failure' || needs.release.result == 'skipped') && needs.notify-approval-needed.outputs.slack_ts != ''
184+
steps:
185+
- name: Check for rejection
186+
id: check-rejection
187+
env:
188+
GH_TOKEN: ${{ github.token }}
189+
run: |
190+
RESPONSE=$(gh api /repos/${{ github.repository }}/actions/runs/${{ github.run_id }}/approvals)
191+
REJECTED=$(echo "$RESPONSE" | jq '[.[] | select(.state == "rejected")] | length')
192+
if [ "$REJECTED" -gt 0 ]; then
193+
echo "was_rejected=true" >> "$GITHUB_OUTPUT"
194+
COMMENT=$(echo "$RESPONSE" | jq -r '.[] | select(.state == "rejected") | .comment // empty' | head -1)
195+
if [ -n "$COMMENT" ]; then
196+
{
197+
echo 'message<<EOF'
198+
echo "🚫 Release was rejected: $COMMENT"
199+
echo 'EOF'
200+
} >> "$GITHUB_OUTPUT"
201+
else
202+
echo "message=🚫 Release was rejected." >> "$GITHUB_OUTPUT"
203+
fi
204+
else
205+
echo "was_rejected=false" >> "$GITHUB_OUTPUT"
206+
fi
207+
208+
- name: Notify Slack - Rejected
209+
if: steps.check-rejection.outputs.was_rejected == 'true'
210+
continue-on-error: true
211+
uses: posthog/.github/.github/actions/slack-thread-reply@main
212+
with:
213+
slack_bot_token: ${{ secrets.SLACK_CLIENT_LIBRARIES_BOT_TOKEN }}
214+
slack_channel_id: ${{ vars.SLACK_APPROVALS_CLIENT_LIBRARIES_CHANNEL_ID }}
215+
thread_ts: ${{ needs.notify-approval-needed.outputs.slack_ts }}
216+
message: '${{ steps.check-rejection.outputs.message }}'
217+
emoji_reaction: 'no_entry_sign'
218+
219+
notify-released:
220+
name: Notify Slack - Released
221+
needs: [notify-approval-needed, release]
222+
runs-on: ubuntu-latest
223+
if: always() && needs.release.result == 'success' && needs.notify-approval-needed.outputs.slack_ts != ''
224+
steps:
225+
- name: Notify Slack - Released
226+
uses: posthog/.github/.github/actions/slack-thread-reply@main
227+
with:
228+
slack_bot_token: ${{ secrets.SLACK_CLIENT_LIBRARIES_BOT_TOKEN }}
229+
slack_channel_id: ${{ vars.SLACK_APPROVALS_CLIENT_LIBRARIES_CHANNEL_ID }}
230+
thread_ts: ${{ needs.notify-approval-needed.outputs.slack_ts }}
231+
message: "🚀 posthog-dotnet released successfully!"
232+
emoji_reaction: "rocket"

README.md

Lines changed: 16 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -73,47 +73,30 @@ This testing approach ensures broad compatibility without requiring users to ins
7373

7474
## Publishing Releases
7575

76-
To create a release, use the `bin/release` script which automates the version bumping and release preparation process.
76+
Releases are driven by PR labels. When a PR with the right labels is merged to `main`, a GitHub Actions workflow handles version bumping, tagging, creating a GitHub Release (with auto-generated notes), and publishing to NuGet.
7777

7878
### Release Process
7979

80-
1. **Prepare the release**: Run the release script with the type of version bump you want:
80+
1. Add the `release` label and exactly one of `bump-patch`, `bump-minor`, or `bump-major` to your PR
81+
2. Merge the PR to `main`
82+
3. Approve the release in the GitHub Environment gate (the workflow pauses for maintainer approval)
83+
4. The workflow bumps the version in `Directory.Build.props`, commits to `main`, creates a git tag, and creates a GitHub Release
84+
5. The GitHub Release triggers the [`main.yaml`](.github/workflows/main.yaml) workflow, which builds and publishes the packages to NuGet
8185

82-
```bash
83-
# For a patch release (1.0.6 -> 1.0.7)
84-
./bin/release patch
85-
86-
# For a minor release (1.0.6 -> 1.1.0)
87-
./bin/release minor
88-
89-
# For a major release (1.0.6 -> 2.0.0)
90-
./bin/release major
91-
```
86+
### Manual Fallback
9287

93-
This script will:
94-
- Calculate the new version based on the current version in [`Directory.Build.props`](Directory.Build.props)
95-
- Create a new release branch (`release-{version}`)
96-
- Update the version in `Directory.Build.props`
97-
- Build and test the solution
98-
- Commit the version change and generated files
99-
- Create and push the git tag
88+
If you need to perform an emergency release bypassing the automated workflow, the `bin/release-local` script is available:
10089

101-
2. **Create a Pull Request**: Push the release branch and create a PR to merge it into `main`:
102-
103-
```bash
104-
git push origin release-{version}
105-
```
106-
107-
Then create a pull request on GitHub to merge the release branch into `main`.
108-
109-
3. **Create GitHub Release**: Once the PR is merged, go to GitHub to [Draft a new Release](https://github.com/Posthog/posthog-dotnet/releases/new), select the tag that was already created, and click "Auto-generate release notes". Edit the notes as needed and publish the release.
90+
```bash
91+
# For a patch release (1.0.6 -> 1.0.7)
92+
./bin/release-local patch
11093

111-
When you create the Release, the [`main.yml`](.github/workflows/main.yml) workflow builds and publishes the package to NuGet.
94+
# For a minor release (1.0.6 -> 1.1.0)
95+
./bin/release-local minor
11296

113-
> [!IMPORTANT]
114-
> When creating a release, it's important to create and publish it in one go. If you save a draft of the release and
115-
> then later publish it, the workflow will not run. If you find yourself in that position, you can [manually trigger the workflow run](https://github.com/PostHog/posthog-dotnet/actions/workflows/main.yaml)
116-
> and select the tag to publish.
97+
# For a major release (1.0.6 -> 2.0.0)
98+
./bin/release-local major
99+
```
117100

118101
## Installation
119102

File renamed without changes.

0 commit comments

Comments
 (0)