Skip to content
Closed
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
12 changes: 9 additions & 3 deletions .github/workflows/pre-release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -22,10 +22,10 @@ on:
default: false
type: boolean

# Least privilege by default; jobs opt into what they need.
# `id-token: write` is granted ONLY to the TestPyPI upload job.
permissions:
contents: write
id-token: write
packages: write
contents: read

# Ensure only one pre-release runs at a time
concurrency:
Expand Down Expand Up @@ -323,6 +323,9 @@ jobs:
runs-on: ubuntu-latest
needs: [prepare-prerelease, build-prerelease]
environment: test-pypi
# No API tokens: authentication happens via short-lived OIDC credentials.
permissions:
id-token: write
steps:
- name: Download artifacts
uses: actions/download-artifact@v4
Expand Down Expand Up @@ -362,6 +365,9 @@ jobs:
runs-on: ubuntu-latest
needs: [prepare-prerelease, build-prerelease, test-pypi-upload]
if: always() && needs.prepare-prerelease.result == 'success' && needs.build-prerelease.result == 'success'
# Pushes the pre-release tag and creates the GitHub pre-release.
permissions:
contents: write
steps:
- uses: actions/checkout@v4

Expand Down
14 changes: 12 additions & 2 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -426,7 +426,12 @@ jobs:
name: Publish to PyPI
runs-on: ubuntu-latest
needs: [validate-release, quality-gates, security-scan, build-package, generate-changelog]
if: needs.validate-release.outputs.is-prerelease == 'false'
# Only ever upload from a tag push. A manual `workflow_dispatch` run is a
# dry run of build + quality gates: PyPI rejects re-uploading an existing
# version, so a dispatch that reached this job would always fail.
if: |
startsWith(github.ref, 'refs/tags/')
&& needs.validate-release.outputs.is-prerelease == 'false'
environment: pypi
# No API tokens: authentication happens via short-lived OIDC credentials.
permissions:
Expand Down Expand Up @@ -466,8 +471,11 @@ jobs:
needs: [validate-release, build-package, generate-changelog, publish]
# `always()` so prereleases (where `publish` is skipped) still get a GitHub
# release, but never when a required upstream job actually failed.
# Tag-only for the same reason as `publish`: a manual dispatch must not try
# to create a release for a ref that is not a tag.
if: |
always()
&& startsWith(github.ref, 'refs/tags/')
&& needs.validate-release.result == 'success'
&& needs.build-package.result == 'success'
&& needs.generate-changelog.result == 'success'
Expand Down Expand Up @@ -587,7 +595,9 @@ jobs:
name: Post-Release Validation
runs-on: ubuntu-latest
needs: [validate-release, create-github-release, publish, update-docs]
if: always()
# Tag-only: on a manual dispatch there is no release to validate, so this
# would always fail with "GitHub release missing".
if: always() && startsWith(github.ref, 'refs/tags/')
steps:
- name: Validate release completion
run: |
Expand Down
Loading