Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
29 changes: 25 additions & 4 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,9 @@
# repository. The project's trusted publisher on PyPI names this repository, this
# workflow file and the pypi environment.
#
# A release whose publish failed can be published again by hand: run this
# workflow with the release's tag. It publishes that tag and nothing else.
#
# Release PRs are opened with the default GITHUB_TOKEN, so CI waits for approval
# on them. They change only the version and the changelog, and the publish job
# runs the full checks before it uploads.
Expand All @@ -17,6 +20,11 @@ name: release
on:
push:
branches: [main]
workflow_dispatch:
inputs:
tag:
description: An existing release tag to publish (vX.Y.Z)
required: true

permissions:
contents: read
Expand All @@ -25,7 +33,7 @@ jobs:
release-please:
name: Release PR
runs-on: ubuntu-latest
if: github.repository_owner == 'fells-code'
if: github.repository_owner == 'fells-code' && github.event_name == 'push'
permissions:
contents: write
pull-requests: write
Expand All @@ -43,7 +51,9 @@ jobs:
publish:
name: Publish to PyPI
needs: release-please
if: needs.release-please.outputs.release_created == 'true'
if: >-
!cancelled() && github.repository_owner == 'fells-code' &&
(needs.release-please.outputs.release_created == 'true' || github.event_name == 'workflow_dispatch')
runs-on: ubuntu-latest
environment:
name: pypi
Expand All @@ -54,14 +64,25 @@ jobs:
steps:
- uses: actions/checkout@v4
with:
ref: ${{ needs.release-please.outputs.tag_name }}
ref: ${{ inputs.tag || needs.release-please.outputs.tag_name }}
persist-credentials: false
- name: The tag is a release of this version
env:
TAG: ${{ inputs.tag || needs.release-please.outputs.tag_name }}
run: |
echo "$TAG" | grep -Eq '^v[0-9]+\.[0-9]+\.[0-9]+$' || { echo "not a release tag: $TAG"; exit 1; }
git rev-parse -q --verify "refs/tags/$TAG" > /dev/null || { echo "no such tag: $TAG"; exit 1; }
version=$(grep -m1 '^version = ' pyproject.toml | cut -d'"' -f2)
[ "v$version" = "$TAG" ] || { echo "pyproject.toml says $version, tag is $TAG"; exit 1; }
- uses: astral-sh/setup-uv@v10.2.0
with:
python-version: '3.13'
- name: Checks
# Frozen: the locked dependencies exactly, without checking the project's
# own version in the lockfile, which a tag cut before release-please bumped
# uv.lock (v0.1.0) still has as the previous one. CI holds main to --locked.
run: |
uv sync --locked
uv sync --frozen
uv run ruff check .
uv run mypy
uv run pytest
Expand Down
4 changes: 4 additions & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -101,5 +101,9 @@ bump the version, edit the changelog, or tag by hand:
minor version while pre-1.0. `ci`, `chore`, `test`, `style` and `build` commits make no release.
- Merging that PR tags `vX.Y.Z`, creates the GitHub release, then checks, builds and publishes the
package to PyPI through trusted publishing (no token in the repository).
- The release PR also bumps the package's own version in `uv.lock` (`extra-files` in
`release-please-config.json`), or `uv sync --locked` fails on main after every release.
- If a publish fails after the release is tagged, fix the cause on main, then publish that tag by
hand: `gh workflow run release.yml -f tag=vX.Y.Z`. It checks the tag matches `pyproject.toml`.

Pre-1.0: a breaking change is a minor bump, and 1.0 is a deliberate decision, not a side effect.
67 changes: 56 additions & 11 deletions release-please-config.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,17 +12,62 @@
"pull-request-header": "## Release",
"pull-request-footer": "Merging this PR tags the release, creates the GitHub release and publishes the package to PyPI.",
"changelog-sections": [
{ "type": "feat", "section": "Added" },
{ "type": "fix", "section": "Fixed" },
{ "type": "perf", "section": "Performance" },
{ "type": "refactor", "section": "Changed" },
{ "type": "docs", "section": "Documentation" },
{ "type": "revert", "section": "Reverted" },
{ "type": "build", "section": "Build", "hidden": true },
{ "type": "chore", "section": "Chores", "hidden": true },
{ "type": "ci", "section": "CI", "hidden": true },
{ "type": "style", "section": "Style", "hidden": true },
{ "type": "test", "section": "Tests", "hidden": true }
{
"type": "feat",
"section": "Added"
},
{
"type": "fix",
"section": "Fixed"
},
{
"type": "perf",
"section": "Performance"
},
{
"type": "refactor",
"section": "Changed"
},
{
"type": "docs",
"section": "Documentation"
},
{
"type": "revert",
"section": "Reverted"
},
{
"type": "build",
"section": "Build",
"hidden": true
},
{
"type": "chore",
"section": "Chores",
"hidden": true
},
{
"type": "ci",
"section": "CI",
"hidden": true
},
{
"type": "style",
"section": "Style",
"hidden": true
},
{
"type": "test",
"section": "Tests",
"hidden": true
}
],
"extra-files": [
{
"type": "toml",
"path": "uv.lock",
"jsonpath": "$.package[?(@.name.value=='seamless-auth')].version"
}
]
}
}
Expand Down
2 changes: 1 addition & 1 deletion uv.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading