Skip to content

Commit 1cfa3e4

Browse files
author
Cursor
committed
ci: chain publish to PyPI after Release on merge via workflow_run
Align with javascript-proxy-headers: add a gate job that runs after the release workflow completes, verify the GitHub release tag exists, then build and publish. Keep release: published for manually created releases. Rename the release workflow to "Release on merge", add concurrency and fork safety, and document the token limitation in the release skill. Made-with: Cursor
1 parent bf33352 commit 1cfa3e4

3 files changed

Lines changed: 78 additions & 26 deletions

File tree

.cursor/skills/release/SKILL.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -80,7 +80,7 @@ git config user.name "Cursor"
8080

8181
## After merge
8282

83-
Merging the PR into `main` triggers `.github/workflows/github_release_on_release_branch_merge.yml`, which creates a **GitHub Release** for tag `v{version}` from the merge commit. That **published** release event runs the existing PyPI **publish** workflow.
83+
Merging the PR into `main` runs **Release on merge** (`.github/workflows/github_release_on_release_branch_merge.yml`), which creates a **GitHub Release** for tag `v{version}` from the merge commit. Because releases created with the default `GITHUB_TOKEN` do not trigger other workflows, **Publish to PyPI** (`publish.yml`) is also started via **`workflow_run`** when that release workflow finishes. Manual or API-created releases still match the `release: published` trigger on `publish.yml`.
8484

8585
## Quick reference
8686

.github/workflows/github_release_on_release_branch_merge.yml

Lines changed: 13 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,28 @@
1-
# When a PR from release/* is merged into main, create a GitHub Release (tag vX.Y.Z).
2-
# The existing publish.yml workflow runs on release: published and uploads to PyPI.
1+
# When a release/* PR merges into main, create a GitHub release (tag vX.Y.Z).
2+
# Events from GITHUB_TOKEN do not start other workflows; publish.yml is triggered via
3+
# workflow_run when this workflow completes (see publish.yml).
34

4-
name: GitHub Release on release branch merge
5+
name: Release on merge
56

67
on:
78
pull_request:
89
types: [closed]
910
branches:
1011
- main
1112

13+
concurrency:
14+
group: release-on-merge-${{ github.event.pull_request.number }}
15+
cancel-in-progress: false
16+
1217
permissions:
1318
contents: write
1419

1520
jobs:
16-
create-release:
17-
if: github.event.pull_request.merged == true && startsWith(github.head_ref, 'release/')
21+
github-release:
22+
if: >-
23+
github.event.pull_request.merged == true &&
24+
startsWith(github.head_ref, 'release/') &&
25+
github.event.pull_request.head.repo.full_name == github.repository
1826
runs-on: ubuntu-latest
1927
steps:
2028
- name: Checkout merge commit

.github/workflows/publish.yml

Lines changed: 64 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -8,32 +8,79 @@ on:
88
workflow_dispatch:
99
inputs:
1010
test_pypi:
11-
description: 'Publish to TestPyPI instead of PyPI'
11+
description: "Publish to TestPyPI instead of PyPI"
1212
required: false
1313
default: false
1414
type: boolean
1515

16+
# GitHub does not start new workflow runs for events caused by the default
17+
# GITHUB_TOKEN (e.g. gh release create in another workflow). After
18+
# "Release on merge" creates a release, trigger publish here instead.
19+
workflow_run:
20+
workflows: [Release on merge]
21+
types: [completed]
22+
23+
permissions:
24+
contents: read
25+
id-token: write
26+
1627
jobs:
28+
gate:
29+
runs-on: ubuntu-latest
30+
outputs:
31+
publish: ${{ steps.decide.outputs.publish }}
32+
steps:
33+
- uses: actions/checkout@v4
34+
if: github.event_name == 'workflow_run'
35+
with:
36+
ref: main
37+
38+
- id: decide
39+
env:
40+
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
41+
run: |
42+
set -euo pipefail
43+
if [[ "${{ github.event_name }}" != "workflow_run" ]]; then
44+
echo "publish=true" >> "${GITHUB_OUTPUT}"
45+
exit 0
46+
fi
47+
if [[ "${{ github.event.workflow_run.conclusion }}" != "success" ]]; then
48+
echo "publish=false" >> "${GITHUB_OUTPUT}"
49+
exit 0
50+
fi
51+
VERSION="$(grep -m1 '^version = ' pyproject.toml | cut -d'"' -f2)"
52+
TAG="v${VERSION}"
53+
if gh release view "${TAG}" --repo "${{ github.repository }}" >/dev/null 2>&1; then
54+
echo "publish=true" >> "${GITHUB_OUTPUT}"
55+
else
56+
echo "No GitHub release ${TAG} yet (or release job was skipped); skipping publish."
57+
echo "publish=false" >> "${GITHUB_OUTPUT}"
58+
fi
59+
1760
build:
1861
name: Build distribution
62+
needs: gate
63+
if: needs.gate.outputs.publish == 'true'
1964
runs-on: ubuntu-latest
20-
65+
2166
steps:
2267
- uses: actions/checkout@v4
23-
68+
with:
69+
ref: ${{ github.event_name == 'workflow_run' && 'main' || github.event_name == 'release' && github.ref || 'main' }}
70+
2471
- name: Set up Python
2572
uses: actions/setup-python@v5
2673
with:
27-
python-version: '3.x'
28-
74+
python-version: "3.x"
75+
2976
- name: Install build dependencies
3077
run: |
3178
python -m pip install --upgrade pip
3279
pip install build
33-
80+
3481
- name: Build package
3582
run: python -m build
36-
83+
3784
- name: Store distribution packages
3885
uses: actions/upload-artifact@v4
3986
with:
@@ -42,24 +89,24 @@ jobs:
4289

4390
publish-to-pypi:
4491
name: Publish to PyPI
45-
if: github.event_name == 'release' || (github.event_name == 'workflow_dispatch' && inputs.test_pypi == false)
92+
if: >-
93+
github.event_name == 'workflow_run' ||
94+
github.event_name == 'release' ||
95+
(github.event_name == 'workflow_dispatch' && inputs.test_pypi == false)
4696
needs: build
4797
runs-on: ubuntu-latest
48-
98+
4999
environment:
50100
name: pypi
51101
url: https://pypi.org/p/python-proxy-headers
52-
53-
permissions:
54-
id-token: write # Required for trusted publishing
55-
102+
56103
steps:
57104
- name: Download distribution packages
58105
uses: actions/download-artifact@v4
59106
with:
60107
name: python-package-distributions
61108
path: dist/
62-
109+
63110
- name: Publish to PyPI
64111
uses: pypa/gh-action-pypi-publish@release/v1
65112

@@ -68,21 +115,18 @@ jobs:
68115
if: github.event_name == 'workflow_dispatch' && inputs.test_pypi == true
69116
needs: build
70117
runs-on: ubuntu-latest
71-
118+
72119
environment:
73120
name: testpypi
74121
url: https://test.pypi.org/p/python-proxy-headers
75-
76-
permissions:
77-
id-token: write
78-
122+
79123
steps:
80124
- name: Download distribution packages
81125
uses: actions/download-artifact@v4
82126
with:
83127
name: python-package-distributions
84128
path: dist/
85-
129+
86130
- name: Publish to TestPyPI
87131
uses: pypa/gh-action-pypi-publish@release/v1
88132
with:

0 commit comments

Comments
 (0)