Skip to content

Commit 53df1be

Browse files
committed
👷 ci(release): share the RST release path
Release preparation, the distribution build and GitHub note rendering move to tox-dev/github-workflows 6.0.1, with release-creator 4.1 or later in the release-* groups (the lock resolves 4.1.0). tasks/release.py goes away, along with the unused release, release-check and release-validate groups. docs/changelog.rst is rebuilt from the 25 release tags with categorized sections and stable anchors, and new entries come from Towncrier fragments in docs/changelog, checked by the news workflow. Credits are no longer written by hand: each entry credits the merged pull request's commit author and co-authors when the changelog is built, and prepare runs release-creator contributors to add a New Contributors subsection. The tag workflow builds through validate-python-release while a read-only notes job renders the GitHub Release body from the changelog section. PyPI publishing keeps OIDC in the release environment and drops skip-existing. A single github-release job then reconciles the PyPI digests and creates the release with one publish-github-release call; it has no checkout, so its setup-uv step sets ignore-empty-workdir. Artifacts pass between jobs by ID. The workflow runs under release-${{ github.ref }} concurrency without cancelling. Writers are trusted and validated at pull request merge, so the checks that only caught a writer's mistake outside a pull request go: the step that re-checked the tag still named the release commit, and the pull_request condition repeating the news workflow's only trigger. github-workflows 6.0.1 drops its own tag, lock and prepare re-checks the same way. Every job and step now has a name that starts with an emoji. pylock.toml is generated by Nab from [tool.nab] (universal, seven-day cutoff, CPython 3.15 on linux_x86_64, project-runtime base group) and covers every group and extra. A build group carries the hatchling backend plus cython and setuptools, because PyYAML has no cp315 wheel. A temporary release-creator cutoff of 2026-10-26T18:24:46Z is to be deleted after 2026-11-02. The weekly updater relocks pylock.toml. Hook updates move to Dependabot's pre-commit ecosystem with a seven-day cooldown, and pre-commit.ci autoupdates quarterly. Revisions stay frozen; the hook-cooldown check, its rerun workflow and the managed closure pins are gone. CodeQL moves from default setup to a committed advanced workflow on ubuntu-26.04, scanning actions and python with security-extended and skipping tests.
1 parent 1b760f3 commit 53df1be

19 files changed

Lines changed: 3198 additions & 558 deletions

‎.github/dependabot.yaml‎

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,3 +15,19 @@ updates:
1515
- "*"
1616
cooldown:
1717
default-days: 7
18+
- package-ecosystem: "pre-commit"
19+
directory: "/"
20+
labels:
21+
- dependencies
22+
- skip news
23+
schedule:
24+
interval: "weekly"
25+
day: "tuesday"
26+
groups:
27+
formatting-plugins:
28+
patterns:
29+
- "mdformat-gfm"
30+
- "mdformat-ruff"
31+
- "sphinx"
32+
cooldown:
33+
default-days: 7

‎.github/release.yaml‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,4 +2,5 @@ changelog:
22
exclude:
33
authors:
44
- dependabot[bot]
5+
- github-actions[bot]
56
- pre-commit-ci[bot]

‎.github/workflows/check.yaml‎

Lines changed: 29 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ on:
55
branches: ["main"]
66
tags-ignore: ["**"]
77
pull_request:
8-
types: [opened, synchronize, reopened, labeled, unlabeled]
8+
types: [opened, synchronize, reopened]
99
schedule:
1010
- cron: "0 8 * * *"
1111
concurrency:
@@ -15,6 +15,7 @@ permissions:
1515
contents: read
1616
jobs:
1717
test:
18+
name: 🧪 Test
1819
runs-on: ubuntu-26.04
1920
strategy:
2021
fail-fast: false
@@ -33,80 +34,66 @@ jobs:
3334
- dev
3435
- pkg_meta
3536
steps:
36-
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
37+
- name: 📥 Check out code
38+
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
3739
with:
3840
fetch-depth: 0
3941
persist-credentials: false
40-
- name: Install the latest version of uv
41-
uses: astral-sh/setup-uv@1c37ad07a6a961277cf70c0d37d6f313000f5884 # v10.3.0
42+
- name: 🐍 Set up uv
43+
uses: astral-sh/setup-uv@07a047e608db84b3d8c06cd9a7c7dcc9c355c18e # v10.3.2
4244
with:
45+
version: latest-known
4346
python-version: "3.15"
4447
enable-cache: false
4548
cache-dependency-glob: "pyproject.toml"
4649
github-token: ${{ secrets.GITHUB_TOKEN }}
47-
- name: Install tox
50+
- name: 📦 Install tox
4851
run: uv tool install --python-preference only-managed --python 3.15 tox --with tox-uv
49-
- name: Install Python
52+
- name: 🐍 Install Python
5053
if: startsWith(matrix.env, '3.') && matrix.env != '3.15'
5154
run: uv python install --python-preference only-managed ${{ matrix.env }}
52-
- name: Setup test suite
55+
- name: 📦 Set up test suite
5356
run: tox run -vv --notest --skip-missing-interpreters false -e ${{ matrix.env }}
54-
- name: Run test suite
57+
- name: 🧪 Run test suite
5558
run: tox run --skip-pkg-install -e ${{ matrix.env }}
5659
env:
5760
PYTEST_ADDOPTS: "-vv --durations=20"
5861
DIFF_AGAINST: HEAD
5962
old-target:
63+
name: 🧪 Test on old target
6064
runs-on: ubuntu-26.04
6165
steps:
62-
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
66+
- name: 📥 Check out code
67+
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
6368
with:
6469
fetch-depth: 0
6570
persist-credentials: false
66-
- name: Install the latest version of uv
67-
uses: astral-sh/setup-uv@1c37ad07a6a961277cf70c0d37d6f313000f5884 # v10.3.0
71+
- name: 🐍 Set up uv
72+
uses: astral-sh/setup-uv@07a047e608db84b3d8c06cd9a7c7dcc9c355c18e # v10.3.2
6873
with:
74+
version: latest-known
6975
python-version: "3.15"
7076
enable-cache: false
7177
cache-dependency-glob: "pyproject.toml"
7278
github-token: ${{ secrets.GITHUB_TOKEN }}
73-
- name: Build the wheel
79+
- name: 🏗️ Build the wheel
7480
run: uv build --wheel --out-dir dist .
7581
- parallel:
76-
- name: Discover Python 3.6
82+
- name: 🧪 Discover Python 3.6
7783
run: tasks/old_target_check.sh 3.6 discover
78-
- name: Discover Python 3.7
84+
- name: 🧪 Discover Python 3.7
7985
run: tasks/old_target_check.sh 3.7 discover
80-
- name: Reject Python 3.5
86+
- name: 🧪 Reject Python 3.5
8187
run: tasks/old_target_check.sh 3.5 rejected
82-
- name: Reject Python 2.7
88+
- name: 🧪 Reject Python 2.7
8389
run: tasks/old_target_check.sh 2.7 rejected
84-
changelog:
85-
if: github.event_name == 'pull_request' && !contains(github.event.pull_request.labels.*.name, 'skip news')
86-
runs-on: ubuntu-24.04
87-
steps:
88-
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
89-
with:
90-
fetch-depth: 0
91-
persist-credentials: false
92-
- uses: astral-sh/setup-uv@1c37ad07a6a961277cf70c0d37d6f313000f5884 # v10.3.0
93-
with:
94-
python-version: "3.15"
95-
enable-cache: false
96-
- run: uvx towncrier check --compare-with "origin/${GITHUB_BASE_REF}"
9790
gate:
98-
name: Required checks pass
99-
if: always()
100-
needs: [test, old-target, changelog]
101-
runs-on: ubuntu-24.04
91+
name: 🚦 ${{ github.event_name == 'pull_request' && 'Required checks pass' || 'Non-PR checks pass' }}
92+
if: ${{ !cancelled() }} # codespell:ignore cancelled
93+
needs: [test, old-target]
94+
runs-on: ubuntu-26.04
10295
steps:
103-
- env:
96+
- name: 🚦 Require all needed jobs to pass
97+
env:
10498
NEEDS_JSON: ${{ toJSON(needs) }}
105-
run: |
106-
jq -e '
107-
all(
108-
to_entries[];
109-
.value.result == "success" or
110-
(.key == "changelog" and .value.result == "skipped")
111-
)
112-
' <<< "$NEEDS_JSON"
99+
run: jq -e 'all(to_entries[]; .value.result == "success")' <<<"$NEEDS_JSON"

‎.github/workflows/codeql.yaml‎

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
name: 🛡️ CodeQL
2+
on:
3+
workflow_dispatch:
4+
push:
5+
branches: ["main"]
6+
pull_request:
7+
schedule:
8+
- cron: "0 8 * * 2"
9+
concurrency:
10+
group: ${{ github.workflow }}-${{ github.ref }}
11+
cancel-in-progress: true
12+
permissions: {}
13+
jobs:
14+
analyze:
15+
name: 🛡️ Analyze ${{ matrix.language }}
16+
runs-on: ubuntu-26.04
17+
permissions:
18+
contents: read
19+
security-events: write
20+
strategy:
21+
fail-fast: false
22+
matrix:
23+
language: [actions, python]
24+
steps:
25+
- name: 📥 Check out repository
26+
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
27+
with:
28+
persist-credentials: false
29+
- name: 🛡️ Initialize CodeQL
30+
uses: github/codeql-action/init@6f2ac81f5cd81fe351a197ba0b00d91d03cd2aee # v4.39.0
31+
with:
32+
languages: ${{ matrix.language }}
33+
build-mode: none
34+
queries: security-extended
35+
config: |
36+
paths-ignore:
37+
- tests
38+
- name: 🛡️ Analyze
39+
uses: github/codeql-action/analyze@6f2ac81f5cd81fe351a197ba0b00d91d03cd2aee # v4.39.0
40+
with:
41+
category: "/language:${{ matrix.language }}"

‎.github/workflows/news.yaml‎

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
name: Changelog
2+
on:
3+
pull_request:
4+
types: [opened, reopened, synchronize, labeled, unlabeled]
5+
concurrency:
6+
group: ${{ github.workflow }}-${{ github.event.pull_request.number }}
7+
cancel-in-progress: true
8+
permissions:
9+
contents: read
10+
jobs:
11+
changelog:
12+
name: 📝 News fragment
13+
runs-on: ubuntu-26.04
14+
steps:
15+
- name: 📥 Check out code
16+
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
17+
with:
18+
fetch-depth: 0
19+
persist-credentials: false
20+
- name: 🐍 Install uv
21+
uses: astral-sh/setup-uv@07a047e608db84b3d8c06cd9a7c7dcc9c355c18e # v10.3.2
22+
with:
23+
version: latest-known
24+
python-version: "3.15"
25+
enable-cache: false
26+
- name: 📦 Install locked release tools
27+
uses: tox-dev/github-workflows/install-locked-tools@259a3d3fb578d00517ffb73341fdd463259b0b2f # 6.0.1
28+
with:
29+
groups: release-news
30+
venv: ${{ runner.temp }}/release-tools
31+
- name: 🔍 Validate news fragments
32+
uses: tox-dev/github-workflows/validate-news@259a3d3fb578d00517ffb73341fdd463259b0b2f # 6.0.1
Lines changed: 13 additions & 146 deletions
Original file line numberDiff line numberDiff line change
@@ -1,156 +1,23 @@
1-
name: Prepare Release
1+
name: Prepare release
22
on:
33
workflow_dispatch:
44
inputs:
55
bump:
6-
description: "Version bump type"
6+
description: Version bump type
77
required: true
88
type: choice
9-
options:
10-
- auto
11-
- major
12-
- minor
13-
- patch
9+
options: [auto, major, minor, patch]
1410
default: auto
15-
permissions:
16-
contents: read
17-
env:
18-
FORCE_COLOR: 1
11+
permissions: {}
1912
jobs:
20-
assemble:
21-
if: github.ref == 'refs/heads/main'
22-
runs-on: ubuntu-24.04
13+
prepare:
14+
name: 🏷️ Prepare Towncrier release
15+
uses: tox-dev/github-workflows/.github/workflows/prepare-towncrier-release.yaml@259a3d3fb578d00517ffb73341fdd463259b0b2f # 6.0.1
2316
permissions:
2417
contents: read
25-
outputs:
26-
version: ${{ steps.version.outputs.version }}
27-
steps:
28-
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
29-
with:
30-
fetch-depth: 0 # The next version comes from the tags.
31-
persist-credentials: false
32-
- name: Install uv
33-
uses: astral-sh/setup-uv@1c37ad07a6a961277cf70c0d37d6f313000f5884 # v10.3.0
34-
with:
35-
python-version: "3.15"
36-
enable-cache: false
37-
- name: Require a news fragment
38-
run: |
39-
if ! compgen -G 'docs/changelog/*.*.rst' > /dev/null; then
40-
echo '::error::add a docs/changelog/*.rst fragment before preparing a release'
41-
exit 1
42-
fi
43-
- name: Set up remote tracking
44-
run: |
45-
git remote -v
46-
git fetch origin
47-
git branch -a
48-
- name: Calculate next version
49-
id: version
50-
env:
51-
BUMP: ${{ inputs.bump }}
52-
run: |
53-
current=$(git describe --tags --abbrev=0 2>/dev/null || echo "0.0.0")
54-
echo "Current version: $current"
55-
56-
IFS='.' read -r major minor patch <<< "$current"
57-
58-
bump_type="$BUMP"
59-
if [ "$bump_type" = "auto" ]; then
60-
feature_count=$(find docs/changelog -name "*.feature.rst" 2>/dev/null | wc -l)
61-
breaking_count=$(find docs/changelog -name "*.breaking.rst" 2>/dev/null | wc -l)
62-
if [ "$breaking_count" -gt 0 ]; then
63-
bump_type="major"
64-
echo "Auto-detected: major bump (found $breaking_count breaking changelog(s))"
65-
elif [ "$feature_count" -gt 0 ]; then
66-
bump_type="minor"
67-
echo "Auto-detected: minor bump (found $feature_count feature changelog(s))"
68-
else
69-
bump_type="patch"
70-
echo "Auto-detected: patch bump (no feature changelogs)"
71-
fi
72-
fi
73-
74-
case "$bump_type" in
75-
major)
76-
next="$((major + 1)).0.0"
77-
;;
78-
minor)
79-
next="$major.$((minor + 1)).0"
80-
;;
81-
patch)
82-
next="$major.$minor.$((patch + 1))"
83-
;;
84-
esac
85-
86-
echo "Next version: $next"
87-
echo "version=$next" >> "$GITHUB_OUTPUT"
88-
- name: Build release patch
89-
env:
90-
VERSION: ${{ steps.version.outputs.version }}
91-
run: |
92-
set -euo pipefail
93-
uvx towncrier build --yes --version "$VERSION"
94-
uvx --with pre-commit-uv pre-commit run --files docs/changelog.rst ||
95-
uvx --with pre-commit-uv pre-commit run --files docs/changelog.rst
96-
git diff HEAD --binary > "$RUNNER_TEMP/release.patch"
97-
test -s "$RUNNER_TEMP/release.patch"
98-
- uses: actions/upload-artifact@cf430e030ddbb5b0abf93d22962f4752f3646cd9 # v7.0.2
99-
with:
100-
name: release-patch
101-
path: ${{ runner.temp }}/release.patch
102-
if-no-files-found: error
103-
push:
104-
needs: assemble
105-
runs-on: ubuntu-24.04
106-
environment: release-auth
107-
permissions:
108-
contents: read
109-
steps:
110-
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
111-
with:
112-
fetch-depth: 0
113-
persist-credentials: false
114-
- uses: actions/download-artifact@9000827ccba6bdab643e8b6fd33ac0654aef8333 # v8.0.2
115-
with:
116-
name: release-patch
117-
path: ${{ runner.temp }}/release-patch
118-
- name: Validate release patch
119-
env:
120-
VERSION: ${{ needs.assemble.outputs.version }}
121-
run: |
122-
set -euo pipefail
123-
git fetch origin main
124-
git reset --hard origin/main
125-
git apply --check "$RUNNER_TEMP/release-patch/release.patch"
126-
git apply --index "$RUNNER_TEMP/release-patch/release.patch"
127-
while IFS= read -r path; do
128-
case "$path" in
129-
docs/changelog.rst|docs/changelog/*.rst) ;;
130-
*) echo "::error::unexpected release path: $path"; exit 1 ;;
131-
esac
132-
done < <(git diff --cached --name-only)
133-
grep -qE "^ v${VERSION//./\\.} \(" docs/changelog.rst
134-
git diff --cached --check
135-
- id: token
136-
uses: actions/create-github-app-token@64ab729a6294000f5aa4a4bc0b73d1f32bc45510 # v3.3.0
137-
with:
138-
client-id: ${{ vars.RELEASE_APP_CLIENT_ID }}
139-
private-key: ${{ secrets.RELEASE_APP_PRIVATE_KEY }}
140-
owner: ${{ github.repository_owner }}
141-
repositories: ${{ github.event.repository.name }}
142-
permission-contents: write
143-
- name: Commit and tag the release
144-
env:
145-
VERSION: ${{ needs.assemble.outputs.version }}
146-
GH_TOKEN: ${{ steps.token.outputs.token }}
147-
APP_SLUG: ${{ steps.token.outputs.app-slug }}
148-
run: |
149-
set -euo pipefail
150-
app_user_id=$(gh api "/users/${APP_SLUG}[bot]" --jq .id)
151-
git config user.name "${APP_SLUG}[bot]"
152-
git config user.email "${app_user_id}+${APP_SLUG}[bot]@users.noreply.github.com"
153-
git commit -m "release $VERSION"
154-
git tag -a "$VERSION" -m "release $VERSION"
155-
gh auth setup-git --hostname github.com --force
156-
git push --atomic origin HEAD:main "refs/tags/$VERSION"
18+
pull-requests: read
19+
with:
20+
bump: ${{ inputs.bump }}
21+
annotated: true
22+
secrets:
23+
RELEASE_APP_PRIVATE_KEY: ${{ secrets.RELEASE_APP_PRIVATE_KEY }}

0 commit comments

Comments
 (0)