Skip to content

Commit a3583ea

Browse files
author
Cursor
committed
ci: align workflows with python-proxy-headers and add release skill
- Add Release on merge workflow for release/* PRs (GitHub release + tag). - Extend publish workflow with gate job and workflow_run trigger for token-created releases. - Bump checkout/setup-python to v6; align integration tests with 3.x. - Document /release flow in .cursor/skills/release/SKILL.md. Made-with: Cursor
1 parent eeabf99 commit a3583ea

4 files changed

Lines changed: 237 additions & 24 deletions

File tree

.cursor/skills/release/SKILL.md

Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
---
2+
name: release
3+
description: >-
4+
Prepares a version bump in pyproject.toml, opens a PR from branch release/VERSION
5+
toward main with auto-merge, and coordinates with CI that publishes a GitHub Release
6+
when that branch merges. Use when the user invokes /release, /release VERSION, asks
7+
for a release PR, version bump, or release automation.
8+
---
9+
10+
# Release (`/release` and optional VERSION)
11+
12+
## When this applies
13+
14+
- User message starts with **`/release`** or **`/release VERSION`** (VERSION optional).
15+
- User asks to cut a release, bump the package version, or open a release PR with auto-merge.
16+
17+
## Preconditions
18+
19+
- Working tree clean (`git status`); stash or commit unrelated work first.
20+
- `gh` CLI authenticated (`gh auth status`).
21+
- Remote `origin` is GitHub.
22+
- Repository allows **auto-merge** (Settings → General → Pull Requests → Allow auto-merge). If auto-merge is unavailable, open the PR anyway and tell the user to merge manually after checks pass.
23+
24+
## Version selection
25+
26+
1. Read the current version from `pyproject.toml` under `[project]``version` (PEP 440 / semver `MAJOR.MINOR.PATCH`).
27+
2. If **VERSION was provided**: set the new version to that string (must match `^\d+\.\d+\.\d+` unless the project already uses a different scheme—then follow existing `pyproject.toml` format).
28+
3. If **VERSION was omitted**: bump the **patch** segment only (e.g. `0.2.1``0.2.2`). If the current value is not `x.y.z`, stop and ask the user for an explicit VERSION.
29+
30+
## Git identity (this repo)
31+
32+
Configure once if needed:
33+
34+
```bash
35+
git config user.email "cursor@proxymesh.com"
36+
git config user.name "Cursor"
37+
```
38+
39+
## Steps
40+
41+
1. **Sync main**
42+
43+
```bash
44+
git fetch origin main
45+
```
46+
47+
2. **Compute** `NEW_VERSION` (per rules above). **Branch name** is `release/${NEW_VERSION}` (no `v` prefix in the branch name).
48+
49+
3. **Create branch from latest main**
50+
51+
```bash
52+
git checkout -B "release/${NEW_VERSION}" origin/main
53+
```
54+
55+
4. **Edit** `pyproject.toml`: set `version = "NEW_VERSION"` in `[project]`.
56+
57+
5. **Commit and push** (never push to `main`; push only the release branch)
58+
59+
```bash
60+
git add pyproject.toml
61+
git commit -m "chore: bump version to ${NEW_VERSION}"
62+
git push -u origin "release/${NEW_VERSION}"
63+
```
64+
65+
6. **Open PR** into `main` with a short body (no Cursor boilerplate). Example:
66+
67+
```bash
68+
gh pr create --base main --head "release/${NEW_VERSION}" \
69+
--title "Release ${NEW_VERSION}" \
70+
--body "Bumps the package version to ${NEW_VERSION} for release."
71+
```
72+
73+
7. **Enable auto-merge** after the PR exists. In non-interactive mode, `gh` requires an explicit merge strategy with `--auto` (use the repository default: usually **`--merge`** for a merge commit, or **`--squash`** / **`--rebase`** if that is what the repo uses).
74+
75+
```bash
76+
gh pr merge <PR_NUMBER_OR_URL> --auto --merge
77+
```
78+
79+
If `--auto` fails (permissions, auto-merge disabled, or pending checks), leave the PR open and report the error; the user can merge manually after CI passes. You can poll with `gh pr checks <PR_NUMBER_OR_URL> --watch` then retry `gh pr merge ... --auto --merge`, or merge manually.
80+
81+
## After merge
82+
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`.
84+
85+
## Quick reference
86+
87+
| Input | Result |
88+
|--------------------|---------------------------------------------|
89+
| `/release` | Patch bump, branch `release/x.y.(z+1)` |
90+
| `/release 1.4.0` | Version `1.4.0`, branch `release/1.4.0` |
Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
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).
4+
5+
name: Release on merge
6+
7+
on:
8+
pull_request:
9+
types: [closed]
10+
branches:
11+
- main
12+
13+
concurrency:
14+
group: release-on-merge-${{ github.event.pull_request.number }}
15+
cancel-in-progress: false
16+
17+
permissions:
18+
contents: write
19+
20+
jobs:
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
26+
runs-on: ubuntu-latest
27+
steps:
28+
- name: Checkout merge commit
29+
uses: actions/checkout@v6
30+
with:
31+
ref: ${{ github.event.pull_request.merge_commit_sha }}
32+
33+
- name: Set up Python
34+
uses: actions/setup-python@v6
35+
with:
36+
python-version: "3.12"
37+
38+
- name: Read version from pyproject.toml
39+
id: meta
40+
run: |
41+
python3 <<'PY'
42+
import os
43+
import tomllib
44+
45+
with open("pyproject.toml", "rb") as f:
46+
data = tomllib.load(f)
47+
version = data["project"]["version"]
48+
with open(os.environ["GITHUB_OUTPUT"], "a", encoding="utf-8") as out:
49+
out.write(f"version={version}\n")
50+
PY
51+
52+
- name: Create GitHub Release
53+
env:
54+
GH_TOKEN: ${{ github.token }}
55+
run: |
56+
set -euo pipefail
57+
VERSION="${{ steps.meta.outputs.version }}"
58+
TAG="v${VERSION}"
59+
MERGE_SHA="${{ github.event.pull_request.merge_commit_sha }}"
60+
if gh release view "${TAG}" --repo "${{ github.repository }}" >/dev/null 2>&1; then
61+
echo "Release ${TAG} already exists; skipping."
62+
exit 0
63+
fi
64+
gh release create "${TAG}" \
65+
--repo "${{ github.repository }}" \
66+
--target "${MERGE_SHA}" \
67+
--title "${TAG}" \
68+
--generate-notes

.github/workflows/proxy_integration_tests.yml

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -16,14 +16,14 @@ jobs:
1616
runs-on: ubuntu-latest
1717

1818
steps:
19-
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
19+
- uses: actions/checkout@v6
2020
with:
2121
persist-credentials: false
2222

2323
- name: Set up Python
24-
uses: actions/setup-python@v5
24+
uses: actions/setup-python@v6
2525
with:
26-
python-version: "3.12"
26+
python-version: "3.x"
2727

2828
- name: Install package and dependencies
2929
run: |

.github/workflows/publish.yml

Lines changed: 76 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -3,55 +3,110 @@ name: Publish to PyPI
33
on:
44
release:
55
types: [published]
6+
7+
# Allow manual trigger for testing
68
workflow_dispatch:
79
inputs:
810
test_pypi:
9-
description: 'Publish to TestPyPI instead of PyPI'
11+
description: "Publish to TestPyPI instead of PyPI"
1012
required: false
1113
default: false
1214
type: boolean
1315

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+
1427
jobs:
28+
gate:
29+
runs-on: ubuntu-latest
30+
outputs:
31+
publish: ${{ steps.decide.outputs.publish }}
32+
steps:
33+
- uses: actions/checkout@v6
34+
if: github.event_name == 'workflow_run'
35+
with:
36+
ref: main
37+
38+
- id: decide
39+
env:
40+
GH_TOKEN: ${{ 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+
1560
build:
1661
name: Build distribution
62+
needs: gate
63+
if: needs.gate.outputs.publish == 'true'
1764
runs-on: ubuntu-latest
65+
1866
steps:
19-
- uses: actions/checkout@v4
20-
67+
- uses: actions/checkout@v6
68+
with:
69+
ref: ${{ github.event_name == 'workflow_run' && 'main' || github.event_name == 'release' && github.ref || 'main' }}
70+
2171
- name: Set up Python
22-
uses: actions/setup-python@v5
72+
uses: actions/setup-python@v6
2373
with:
2474
python-version: "3.x"
25-
75+
2676
- name: Install build dependencies
27-
run: python -m pip install --upgrade pip build
28-
77+
run: |
78+
python -m pip install --upgrade pip
79+
pip install build
80+
2981
- name: Build package
3082
run: python -m build
31-
32-
- name: Upload distribution artifacts
33-
uses: actions/upload-artifact@v4
83+
84+
- name: Store distribution packages
85+
uses: actions/upload-artifact@v7
3486
with:
3587
name: python-package-distributions
3688
path: dist/
3789

3890
publish-to-pypi:
3991
name: Publish to PyPI
40-
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)
4196
needs: build
4297
runs-on: ubuntu-latest
98+
4399
environment:
44100
name: pypi
45101
url: https://pypi.org/p/scrapy-proxy-headers
46-
permissions:
47-
id-token: write
102+
48103
steps:
49-
- name: Download distribution artifacts
50-
uses: actions/download-artifact@v4
104+
- name: Download distribution packages
105+
uses: actions/download-artifact@v8
51106
with:
52107
name: python-package-distributions
53108
path: dist/
54-
109+
55110
- name: Publish to PyPI
56111
uses: pypa/gh-action-pypi-publish@release/v1
57112

@@ -60,18 +115,18 @@ jobs:
60115
if: github.event_name == 'workflow_dispatch' && inputs.test_pypi == true
61116
needs: build
62117
runs-on: ubuntu-latest
118+
63119
environment:
64120
name: testpypi
65121
url: https://test.pypi.org/p/scrapy-proxy-headers
66-
permissions:
67-
id-token: write
122+
68123
steps:
69-
- name: Download distribution artifacts
70-
uses: actions/download-artifact@v4
124+
- name: Download distribution packages
125+
uses: actions/download-artifact@v8
71126
with:
72127
name: python-package-distributions
73128
path: dist/
74-
129+
75130
- name: Publish to TestPyPI
76131
uses: pypa/gh-action-pypi-publish@release/v1
77132
with:

0 commit comments

Comments
 (0)