Skip to content

Commit 0c2d9da

Browse files
committed
Support manual tag releases & add sanity checks
Add workflow_dispatch input (release_tag) and validate its format so the workflow can be run manually for a specific tag. Update checkout steps to use the provided tag for manual runs, and relax job guards to allow workflow_dispatch to trigger release/build/publish jobs. Add per-run validation steps that ensure a tag-like value is provided. Bump publish job Python to 3.12, add an explicit pre-upload check for the TWINE_API_KEY secret, and remove the twine --verbose flag. Also update the workflow usage comment to mention manual re-runs.
1 parent f50973f commit 0c2d9da

File tree

1 file changed

+55
-5
lines changed

1 file changed

+55
-5
lines changed

.github/workflows/python-package.yml

Lines changed: 55 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,15 @@ name: Build, validate & Release
33
# Usage:
44
# - For PRs: this workflow runs automatically to validate the package builds and installs correctly on multiple Python versions. No artifacts are published for PRs.
55
# - For releases: when you push a tag like v1.2.3, this workflow runs the full matrix validation, then builds the release artifacts, and finally publishes to PyPI if all checks pass.
6+
# - For manual re-runs: use "Run workflow" and provide a tag-like value such as v2.0.0rc1.
67

78
on:
89
workflow_dispatch:
10+
inputs:
11+
release_tag:
12+
description: 'Tag to build/publish (e.g. v1.2.3 or v2.0.0rc1)'
13+
required: true
14+
type: string
915
# Release pipeline: run only when pushing a version-like tag (e.g. v1.2.3)
1016
# Test pipeline: run tests on main/master pushes & pull requests AND tags.
1117
push:
@@ -36,9 +42,24 @@ jobs:
3642
python-version: ["3.10", "3.11", "3.12"]
3743

3844
steps:
39-
# Fetch repository sources so we can build/test
45+
- name: Validate manual tag input
46+
if: ${{ github.event_name == 'workflow_dispatch' }}
47+
shell: bash
48+
run: |
49+
case "${{ inputs.release_tag }}" in
50+
v*.*.*)
51+
echo "Manual release tag accepted: ${{ inputs.release_tag }}"
52+
;;
53+
*)
54+
echo "release_tag must look like v1.2.3 (or similar, e.g. v2.0.0rc1)"
55+
exit 1
56+
;;
57+
esac
58+
4059
- name: Checkout sources
4160
uses: actions/checkout@v6
61+
with:
62+
ref: ${{ github.event_name == 'workflow_dispatch' && format('refs/tags/{0}', inputs.release_tag) || github.ref }}
4263

4364
- name: Set up Python
4465
uses: actions/setup-python@v6
@@ -86,12 +107,28 @@ jobs:
86107
runs-on: ubuntu-latest
87108
needs: test_matrix
88109
# Safety gate: only run for version tags, never for PRs/branches
89-
if: startsWith(github.ref, 'refs/tags/v')
110+
if: ${{ startsWith(github.ref, 'refs/tags/v') || github.event_name == 'workflow_dispatch' }}
90111

91112
steps:
113+
- name: Validate manual tag input
114+
if: ${{ github.event_name == 'workflow_dispatch' }}
115+
shell: bash
116+
run: |
117+
case "${{ inputs.release_tag }}" in
118+
v*.*.*)
119+
echo "Manual release tag accepted: ${{ inputs.release_tag }}"
120+
;;
121+
*)
122+
echo "release_tag must look like v1.2.3 (or similar, e.g. v2.0.0rc1)"
123+
exit 1
124+
;;
125+
esac
92126
# Fetch sources for the tagged revision
93127
- name: Checkout sources
94128
uses: actions/checkout@v6
129+
with:
130+
# For a manual run, we want to check out the commit at the provided tag
131+
ref: ${{ github.event_name == 'workflow_dispatch' && format('refs/tags/{0}', inputs.release_tag) || github.ref }}
95132

96133
# Use a single, modern Python for the canonical release build
97134
- name: Set up Python (release build)
@@ -124,7 +161,7 @@ jobs:
124161
runs-on: ubuntu-latest
125162
needs: build_release
126163
# Safety gate: only run for version tags
127-
if: startsWith(github.ref, 'refs/tags/v')
164+
if: ${{ startsWith(github.ref, 'refs/tags/v') || github.event_name == 'workflow_dispatch' }}
128165

129166
steps:
130167
# Retrieve the exact distributions produced in build_release
@@ -138,16 +175,29 @@ jobs:
138175
- name: Set up Python (publish)
139176
uses: actions/setup-python@v6
140177
with:
141-
python-version: "3.x"
178+
python-version: "3.12"
142179

143180
# Install twine for uploading
144181
- name: Install Twine
145182
run: python -m pip install -U twine
146183

184+
# Check that the PyPI API token is present before attempting upload (fails fast if not set)
185+
- name: Check PyPI credential presence
186+
shell: bash
187+
env:
188+
TWINE_PASSWORD: ${{ secrets.TWINE_API_KEY }}
189+
run: |
190+
if [ -z "$TWINE_PASSWORD" ]; then
191+
echo "TWINE_PASSWORD is empty"
192+
exit 1
193+
else
194+
echo "TWINE_PASSWORD is present"
195+
fi
196+
147197
# Upload to PyPI using an API token stored in repo secrets.
148198
# --skip-existing avoids failing if you re-run a workflow for the same version.
149199
- name: Publish to PyPI
150200
env:
151201
TWINE_USERNAME: __token__
152202
TWINE_PASSWORD: ${{ secrets.TWINE_API_KEY }}
153-
run: python -m twine upload --non-interactive --verbose --skip-existing dist/*
203+
run: python -m twine upload --non-interactive --skip-existing dist/*

0 commit comments

Comments
 (0)