Skip to content

Commit 7e03833

Browse files
authored
Merge pull request #20 from proxymesh/release/0.2.2
Release 0.2.2
2 parents 55427b0 + 1cfa3e4 commit 7e03833

4 files changed

Lines changed: 223 additions & 21 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@v4
30+
with:
31+
ref: ${{ github.event.pull_request.merge_commit_sha }}
32+
33+
- name: Set up Python
34+
uses: actions/setup-python@v5
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/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:

pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
44

55
[project]
66
name = "python-proxy-headers"
7-
version = "0.2.1"
7+
version = "0.2.2"
88
authors = [
99
{ name="ProxyMesh", email="support@proxymesh.com" },
1010
]

0 commit comments

Comments
 (0)