Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
56 commits
Select commit Hold shift + click to select a range
45a95ed
🔧 build: admit release-creator 0.6.7
gaborbernat Oct 23, 2026
ed56310
🐛 fix(lock): check exact group markers
gaborbernat Oct 23, 2026
4aae17f
🐛 fix(release): isolate patch validation
gaborbernat Oct 23, 2026
0437c82
👷 ci: check upgrade artifacts separately
gaborbernat Oct 23, 2026
a2abbe4
📝 docs: record updater path checks
gaborbernat Oct 23, 2026
69d73c5
🐛 fix(lock): verify caller files against PyPI
gaborbernat Oct 23, 2026
572feb9
🐛 fix(lock): check duplicate artifacts
gaborbernat Oct 23, 2026
1a88b30
🧪 test: measure Action subprocess coverage
gaborbernat Oct 23, 2026
13e6ba2
🧪 test: use PyPI for upgrade fixture
gaborbernat Oct 23, 2026
c84300a
🐛 fix(lock): reject missing indexes
gaborbernat Oct 23, 2026
a3ab8b8
🧪 test: use PyPI metadata in export fixtures
gaborbernat Oct 23, 2026
8bf88e4
🧪 test: stub PyPI metadata for Actions
gaborbernat Oct 23, 2026
e2259e1
🧪 test: cover dynamic build metadata
gaborbernat Oct 23, 2026
b4734dd
🧪 test(lock): cover metadata boundaries
gaborbernat Oct 23, 2026
84e7ed6
🧪 test: cover PyPI file verification
gaborbernat Oct 23, 2026
c0aa82b
🧪 test: cover Nab lock comparison paths
gaborbernat Oct 23, 2026
06798c8
♻️ refactor(lock): share package selection
gaborbernat Oct 23, 2026
dd0c915
🧪 test: cover release tool edge cases
gaborbernat Oct 23, 2026
ff4f85c
🧪 test: cover cutoff CLI boundaries
gaborbernat Oct 23, 2026
1a97315
🧪 test: require full tool coverage
gaborbernat Oct 23, 2026
d69c7ce
🔒 fix: check base lock selection
gaborbernat Oct 23, 2026
f44543d
🔒 fix: reject bot hook config edits
gaborbernat Oct 23, 2026
106ccfc
🔧 fix: download release artifacts by ID
gaborbernat Oct 23, 2026
41b6722
fix: expose distribution artifact ID
gaborbernat Oct 23, 2026
31070d1
perf: trust the pinned shared lock
gaborbernat Oct 23, 2026
f594ba7
test: derive lock fixtures from the lock
gaborbernat Oct 23, 2026
42a8cf4
refactor: trim unused action inputs
gaborbernat Oct 23, 2026
1d463a0
refactor: simplify release inputs
gaborbernat Oct 23, 2026
1da538a
fix: retry PyPI lock metadata reads
gaborbernat Oct 23, 2026
e1038e3
test: cover PyPI HTTP retries
gaborbernat Oct 23, 2026
e942158
Fix PyPI retry test headers
gaborbernat Oct 23, 2026
200b7b4
perf: shrink the release lock reader
gaborbernat Oct 23, 2026
a3eee2c
🐛 fix(lock): check every release target
gaborbernat Oct 24, 2026
0d5ebdd
🐛 fix(lock): pin PyPI metadata trust
gaborbernat Oct 24, 2026
5ca2d56
🔒 fix: pin PyPI verification trust store
gaborbernat Oct 24, 2026
b3123aa
🔒 fix: check updater PR hook age
gaborbernat Oct 24, 2026
d03b69f
📝 docs: correct release change entries
gaborbernat Oct 24, 2026
da27712
fix: verify version outside assembly
gaborbernat Oct 24, 2026
726545f
fix: download generated patches by ID
gaborbernat Oct 24, 2026
b4dd0ef
fix: verify bundled secondary locks
gaborbernat Oct 24, 2026
2637a33
docs: show secondary lock verification
gaborbernat Oct 24, 2026
3bfe4a7
fix: verify secondary build tool groups
gaborbernat Oct 24, 2026
1b37603
🐛 fix(hooks): verify managed update pins
gaborbernat Oct 24, 2026
5eae88d
🐛 fix(locks): check secondary PR locks
gaborbernat Oct 24, 2026
e1318f5
🐛 fix(hooks): pass App ID from caller
gaborbernat Oct 24, 2026
e147974
🧪 test(locks): derive shared tool version
gaborbernat Oct 24, 2026
a6a87a2
🐛 fix(locks): isolate reader environment
gaborbernat Oct 24, 2026
91874e1
📝 docs: note isolated lock reader
gaborbernat Oct 24, 2026
61060da
🐛 fix(hooks): cool CI hook installs
gaborbernat Oct 24, 2026
a89b2e4
🐛 fix(release): retain finalized version
gaborbernat Oct 24, 2026
3879458
🐛 fix(locks): reject malformed PyPI listings
gaborbernat Oct 24, 2026
0136421
🔒 fix(news): check Nab policy changes
gaborbernat Oct 24, 2026
d5f1a84
📝 docs: explain removed release inputs
gaborbernat Oct 24, 2026
54b9511
🔧 build: lock release-creator 1.0.0
gaborbernat Oct 24, 2026
3aec0b7
🐛 fix(locks): retry stale PyPI listings
gaborbernat Oct 24, 2026
4bcaf99
🐛 fix(locks): retry fresh PyPI 404s
gaborbernat Oct 24, 2026
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
12 changes: 6 additions & 6 deletions .github/workflows/check.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,8 @@ jobs:
groups: release-check
venv: .release-tools
- name: Check repository formatting
env:
UV_EXCLUDE_NEWER: 7 days
run: prek run --all-files
shell:
runs-on: ubuntu-26.04
Expand Down Expand Up @@ -105,12 +107,8 @@ jobs:
with:
groups: release-news release-shell
venv: .release-tools
- name: Install current test tools
run: |
uv venv --python 3.15 .venv
uv pip install --python .venv/bin/python --group test
- name: Run Action tests
run: .venv/bin/pytest -q -n auto
run: uvx --with tox-uv tox run -e test
type:
runs-on: ubuntu-26.04
permissions:
Expand Down Expand Up @@ -139,6 +137,8 @@ jobs:
pull-requests: read
steps:
- uses: $/check-hook-update-cooldown
with:
release-app-client-id: ${{ vars.RELEASE_APP_CLIENT_ID }}
release-tools-preview:
if: github.event_name == 'pull_request'
runs-on: ubuntu-26.04
Expand All @@ -160,7 +160,7 @@ jobs:
- uses: $/refresh-release-lock
with:
lock-groups: >-
build lock-tools release-notes release-news release-prepare release-validate release-check release-attestations release-shell release-hooks release-hook-check
build lock-tools lock-reader release-notes release-news release-prepare release-validate release-check release-attestations release-shell release-hooks release-hook-check
mode: check
gate:
name: ${{ github.event_name == 'pull_request' && 'Required checks pass' || 'Required checks pass (non-PR)' }}
Expand Down
74 changes: 66 additions & 8 deletions .github/workflows/prepare-towncrier-release.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -63,10 +63,10 @@ jobs:
local-tool-source: ${{ inputs.local-tool-source }}
- name: Install prek hooks
id: hooks
env:
UV_EXCLUDE_NEWER: 7 days
run: prek install-hooks
background: true
- name: Create a fresh validation worktree
run: git worktree add --detach "$RUNNER_TEMP/release-validation" "$GITHUB_SHA"
- name: Build local release tool wheel
if: inputs.local-tool-source
env:
Expand Down Expand Up @@ -97,20 +97,78 @@ jobs:
with:
version: ${{ steps.prepare.outputs.version }}
phase: finalize
- uses: $/validate-release-patch
with:
version: ${{ steps.prepare.outputs.version }}
patch: ${{ runner.temp }}/release.patch
working-directory: ${{ runner.temp }}/release-validation
- id: patch
uses: actions/upload-artifact@cf430e030ddbb5b0abf93d22962f4752f3646cd9 # v7.0.2
with:
name: release-patch
path: ${{ runner.temp }}/release.patch
if-no-files-found: error
push:
validate:
needs: assemble
runs-on: ubuntu-26.04
permissions:
contents: read
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
fetch-depth: 0
persist-credentials: false
ref: ${{ github.sha }}
- uses: astral-sh/setup-uv@1c37ad07a6a961277cf70c0d37d6f313000f5884 # v10.3.0
with:
version: latest-known
python-version: "3.15"
enable-cache: false
- id: tools
uses: $/install-locked-tools
with:
groups: release-validate${{ inputs.local-tool-source && ' project-runtime' || '' }}
venv: .venv
local-tool-source: ${{ inputs.local-tool-source }}
- name: Build local release tool wheel
if: inputs.local-tool-source
env:
BUILD_CONSTRAINTS: ${{ steps.tools.outputs.build-constraints }}
run: |
uv build --wheel --build-constraints "$BUILD_CONSTRAINTS" \
--require-build-hashes --out-dir "$RUNNER_TEMP/local-tool-dist" .
- name: Install local release tool wheel
if: inputs.local-tool-source
run: uv pip install --python .venv/bin/python --no-deps "$RUNNER_TEMP/local-tool-dist/"*.whl
- name: Expose local release command
if: inputs.local-tool-source
run: |
mkdir -p "$RUNNER_TEMP/local-release-cli"
ln -s "$GITHUB_WORKSPACE/.venv/bin/release-creator" "$RUNNER_TEMP/local-release-cli/release-creator"
echo "$RUNNER_TEMP/local-release-cli" >>"$GITHUB_PATH"
- id: config
uses: $/read-towncrier-config
- name: Verify selected release version
env:
BUMP: ${{ inputs.bump }}
TAG_PREFIX: ${{ inputs.tag-prefix }}
FRAGMENTS: ${{ steps.config.outputs.fragments }}
VERSION: ${{ needs.assemble.outputs.version }}
run: |
set -euo pipefail
args=(--next "$BUMP" --directory "$FRAGMENTS")
if [[ -n "$TAG_PREFIX" ]]; then args+=(--prefix "$TAG_PREFIX"); fi
expected=$(git tag --list | release-creator tags "${args[@]}")
if [[ "$VERSION" != "$expected" ]]; then
echo "::error::assembled release version differs from $expected" >&2
exit 1
fi
- uses: actions/download-artifact@9000827ccba6bdab643e8b6fd33ac0654aef8333 # v8.0.2
with:
artifact-ids: ${{ needs.assemble.outputs.artifact-id }}
path: ${{ runner.temp }}/release-patch
- uses: $/validate-release-patch
with:
version: ${{ needs.assemble.outputs.version }}
patch: ${{ runner.temp }}/release-patch/release.patch
push:
needs: [assemble, validate]
runs-on: ubuntu-26.04
environment:
name: release-auth
deployment: ${{ inputs.release-auth-deployment }}
Expand Down
2 changes: 2 additions & 0 deletions .github/workflows/release.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,8 @@ jobs:
groups: release-check
venv: .venv
- name: Check tagged source
env:
UV_EXCLUDE_NEWER: 7 days
run: .venv/bin/prek run --all-files
source:
runs-on: ubuntu-26.04
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/update-tools.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ jobs:
RELEASE_APP_PRIVATE_KEY: ${{ secrets.RELEASE_APP_PRIVATE_KEY }}
with:
lock-groups: >-
build lock-tools release-notes release-news release-prepare release-validate release-check release-attestations release-shell release-hooks release-hook-check
build lock-tools lock-reader release-notes release-news release-prepare release-validate release-check release-attestations release-shell release-hooks release-hook-check
commit-message: "🔧 build: refresh release tool lock"
pull-request-title: "🔧 build: refresh release tool lock"
pull-request-body: "Refresh the locked tools used by release preparation and CI."
57 changes: 38 additions & 19 deletions .github/workflows/upgrade-release-tools.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@ jobs:
contents: read
outputs:
changed: ${{ steps.lock.outputs.changed }}
artifact-id: ${{ steps.upload.outputs.artifact-id }}
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
Expand All @@ -75,7 +76,8 @@ jobs:
additional-matrix-python: ${{ inputs.additional-matrix-python }}
additional-matrix-platforms: ${{ inputs.additional-matrix-platforms }}
additional-matrix-implementations: ${{ inputs.additional-matrix-implementations }}
- uses: actions/upload-artifact@cf430e030ddbb5b0abf93d22962f4752f3646cd9 # v7.0.2
- id: upload
uses: actions/upload-artifact@cf430e030ddbb5b0abf93d22962f4752f3646cd9 # v7.0.2
if: steps.lock.outputs.changed == 'true'
with:
name: release-tools-lock
Expand All @@ -91,6 +93,7 @@ jobs:
contents: read
outputs:
changed: ${{ steps.hooks.outputs.changed }}
artifact-id: ${{ steps.upload.outputs.artifact-id }}
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
Expand All @@ -111,7 +114,7 @@ jobs:
uv run --no-project --python "$RUNNER_TEMP/hook-tools" prek update --config .pre-commit-config.yaml --freeze --cooldown-days 7 --no-progress
- name: Refresh exact hook dependencies
run: >-
uv run --no-project --python "$RUNNER_TEMP/hook-tools" release-creator hooks --exclude-newer '7 days' --python-version 3.15
uv run --no-project --python "$RUNNER_TEMP/hook-tools" release-creator hooks
- id: hooks
name: Write hook patch
run: |
Expand All @@ -122,7 +125,8 @@ jobs:
fi
echo 'changed=true' >>"$GITHUB_OUTPUT"
git diff --binary -- .pre-commit-config.yaml >"$RUNNER_TEMP/hooks.patch"
- uses: actions/upload-artifact@cf430e030ddbb5b0abf93d22962f4752f3646cd9 # v7.0.2
- id: upload
uses: actions/upload-artifact@cf430e030ddbb5b0abf93d22962f4752f3646cd9 # v7.0.2
if: steps.hooks.outputs.changed == 'true'
with:
name: release-tools-hooks
Expand All @@ -147,37 +151,52 @@ jobs:
- uses: actions/download-artifact@9000827ccba6bdab643e8b6fd33ac0654aef8333 # v8.0.2
if: needs.generate-lock.outputs.changed == 'true'
with:
name: release-tools-lock
artifact-ids: ${{ needs.generate-lock.outputs.artifact-id }}
path: ${{ runner.temp }}/release-tools-lock
- uses: actions/download-artifact@9000827ccba6bdab643e8b6fd33ac0654aef8333 # v8.0.2
if: needs.generate-hooks.outputs.changed == 'true'
with:
name: release-tools-hooks
artifact-ids: ${{ needs.generate-hooks.outputs.artifact-id }}
path: ${{ runner.temp }}/release-tools-hooks
- name: Apply lock patch
if: needs.generate-lock.outputs.changed == 'true'
run: git apply --index "$RUNNER_TEMP/release-tools-lock/lock.patch"
- name: Apply hook patch
if: needs.generate-hooks.outputs.changed == 'true'
run: git apply --index "$RUNNER_TEMP/release-tools-hooks/hooks.patch"
- name: Check dependency patch paths
- name: Check artifact patch paths
env:
ADDITIONAL_LOCK: ${{ inputs.additional-lock }}
LOCK_CHANGED: ${{ needs.generate-lock.outputs.changed }}
HOOKS_CHANGED: ${{ needs.generate-hooks.outputs.changed }}
run: |
set -euo pipefail
if [[ -n "$ADDITIONAL_LOCK" &&
("$ADDITIONAL_LOCK" != pylock.*.toml || "$ADDITIONAL_LOCK" == */*) ]]; then
echo "::error::invalid additional lock: $ADDITIONAL_LOCK" >&2
exit 1
fi
git diff --cached --name-only --no-renames -z >"$RUNNER_TEMP/dependency-paths"
while IFS= read -r -d '' path; do
if [[ "$path" != pylock.toml && "$path" != pyproject.toml &&
"$path" != .pre-commit-config.yaml && "$path" != "$ADDITIONAL_LOCK" ]]; then
echo "::error::unexpected dependency path: $path" >&2
exit 1
for artifact in lock hooks; do
if [[ "$artifact" == lock ]]; then
[[ "$LOCK_CHANGED" == true ]] || continue
patch="$RUNNER_TEMP/release-tools-lock/lock.patch"
else
[[ "$HOOKS_CHANGED" == true ]] || continue
patch="$RUNNER_TEMP/release-tools-hooks/hooks.patch"
fi
done <"$RUNNER_TEMP/dependency-paths"
GIT_INDEX_FILE="$RUNNER_TEMP/$artifact.index" git read-tree HEAD
GIT_INDEX_FILE="$RUNNER_TEMP/$artifact.index" git apply --cached "$patch"
GIT_INDEX_FILE="$RUNNER_TEMP/$artifact.index" \
git diff --cached --name-only --no-renames -z >"$RUNNER_TEMP/$artifact-paths"
while IFS= read -r -d '' path; do
if [[ "$artifact" == lock && "$path" != pylock.toml && "$path" != pyproject.toml &&
"$path" != "$ADDITIONAL_LOCK" ]] ||
[[ "$artifact" == hooks && "$path" != .pre-commit-config.yaml ]]; then
echo "::error::unexpected $artifact patch path: $path" >&2
exit 1
fi
done <"$RUNNER_TEMP/$artifact-paths"
done
- name: Apply lock patch
if: needs.generate-lock.outputs.changed == 'true'
run: git apply --index "$RUNNER_TEMP/release-tools-lock/lock.patch"
- name: Apply hook patch
if: needs.generate-hooks.outputs.changed == 'true'
run: git apply --index "$RUNNER_TEMP/release-tools-hooks/hooks.patch"
- name: Check dependency patch file mode
run: |
set -euo pipefail
Expand Down
36 changes: 16 additions & 20 deletions .github/workflows/validate-python-release.yaml
Original file line number Diff line number Diff line change
@@ -1,23 +1,11 @@
name: Validate Python release
on:
workflow_call:
outputs:
distributions-artifact-id:
description: Upload ID for the validated wheel and sdist artifact
value: ${{ jobs.build.outputs.artifact-id }}
inputs:
changelog:
description: Deprecated; notes are rendered by the caller
type: string
default: ""
artifact-base-url:
description: Deprecated; notes are rendered by the caller
type: string
default: ""
artifact-label:
description: Deprecated; notes are rendered by the caller
type: string
default: PyPI release
local-tool-source:
description: Deprecated; distribution builds do not install release-creator
type: boolean
default: false
source:
description: Package source directory
default: .
Expand Down Expand Up @@ -48,10 +36,14 @@ jobs:
groups: release-check
venv: ${{ runner.temp }}/release-tools
- name: Run tagged source checks
env:
UV_EXCLUDE_NEWER: 7 days
run: >-
"$RUNNER_TEMP/release-tools/bin/prek" run --all-files
constraints:
runs-on: ubuntu-26.04
outputs:
artifact-id: ${{ steps.upload.outputs.artifact-id }}
permissions:
contents: read
steps:
Expand All @@ -70,14 +62,17 @@ jobs:
env:
SOURCE: ${{ steps.constraints.outputs.build-constraints }}
run: cp "$SOURCE" "$RUNNER_TEMP/build-constraints.txt"
- uses: actions/upload-artifact@cf430e030ddbb5b0abf93d22962f4752f3646cd9 # v7.0.2
- id: upload
uses: actions/upload-artifact@cf430e030ddbb5b0abf93d22962f4752f3646cd9 # v7.0.2
with:
name: ${{ inputs.artifact-prefix }}build-constraints
path: ${{ runner.temp }}/build-constraints.txt
if-no-files-found: error
build:
needs: constraints
runs-on: ubuntu-26.04
outputs:
artifact-id: ${{ steps.upload.outputs.artifact-id }}
permissions:
contents: read
steps:
Expand All @@ -102,7 +97,7 @@ jobs:
enable-cache: false
- uses: actions/download-artifact@9000827ccba6bdab643e8b6fd33ac0654aef8333 # v8.0.2
with:
name: ${{ inputs.artifact-prefix }}build-constraints
artifact-ids: ${{ needs.constraints.outputs.artifact-id }}
path: ${{ runner.temp }}
- name: Require empty distribution directory
run: test ! -d dist || test -z "$(find dist -mindepth 1 -maxdepth 1 -print -quit)"
Expand Down Expand Up @@ -132,7 +127,8 @@ jobs:
echo "::error::wheel and sdist versions must match $TAG" >&2
exit 1
fi
- uses: actions/upload-artifact@cf430e030ddbb5b0abf93d22962f4752f3646cd9 # v7.0.2
- id: upload
uses: actions/upload-artifact@cf430e030ddbb5b0abf93d22962f4752f3646cd9 # v7.0.2
with:
name: ${{ inputs.artifact-prefix }}python-package-distributions
path: dist/*
Expand All @@ -154,7 +150,7 @@ jobs:
enable-cache: false
- uses: actions/download-artifact@9000827ccba6bdab643e8b6fd33ac0654aef8333 # v8.0.2
with:
name: ${{ inputs.artifact-prefix }}python-package-distributions
artifact-ids: ${{ needs.build.outputs.artifact-id }}
path: dist
- name: Install distribution validators
run: |
Expand Down
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
.venv/
__pycache__/
.coverage*
uv.lock
Loading
Loading