1- # This workflow builds the python package and publishes to PyPI.
1+ # This workflow publishes the python package to PyPI.
22#
33# Triggers:
44# - release: published - When user clicks "Publish" on a draft release (downloads pre-built assets)
5- # - workflow_dispatch - Manual trigger for prereleases (builds fresh)
65#
76# Authentication: This workflow expects GitHub OIDC for passwordless PyPI publishing.
87# For more info: https://docs.pypi.org/trusted-publishers/
@@ -12,190 +11,31 @@ name: Publish Package
1211on :
1312 release :
1413 types : [published]
15- workflow_dispatch :
16- inputs :
17- version :
18- description : >
19- Note that this workflow is intended for prereleases. For public-facing stable releases,
20- please use the GitHub Releases workflow instead.
21- For prereleases, please leave the version blank to use the detected version. Alternatively,
22- you can override the dynamic versioning for special use cases.
23- required : false
24- publish_to_pypi :
25- description : " Publish to PyPI. If true, the workflow will publish to PyPI."
26- type : boolean
27- required : true
28- default : true
2914
3015jobs :
31- # Download pre-built assets from the release (only on release: published)
32- download_release_assets :
33- name : Download Release Assets
34- runs-on : ubuntu-latest
35- if : github.event_name == 'release'
36- permissions :
37- contents : read
38- steps :
39- - name : Download wheel and sdist from release
40- env :
41- GH_TOKEN : ${{ github.token }}
42- run : |
43- mkdir -p dist
44- gh release download "${{ github.event.release.tag_name }}" \
45- --repo "${{ github.repository }}" \
46- --pattern "*.whl" \
47- --pattern "*.tar.gz" \
48- --dir dist
49- echo "Downloaded assets:"
50- ls -la dist/
51-
52- - name : Verify assets were downloaded
53- run : |
54- if [ ! -f dist/*.whl ]; then
55- echo "Error: No wheel file found in release assets"
56- exit 1
57- fi
58- if [ ! -f dist/*.tar.gz ]; then
59- echo "Error: No sdist file found in release assets"
60- exit 1
61- fi
62- echo "Assets verified successfully"
63-
64- - uses : actions/upload-artifact@v6
65- with :
66- name : Packages-${{ github.run_id }}
67- path : |
68- dist/*.whl
69- dist/*.tar.gz
70-
71- outputs :
72- VERSION : ${{ github.event.release.tag_name }}
73-
74- # Build fresh package (only on workflow_dispatch, NOT on release)
75- build :
76- name : Build Python Package
77- runs-on : ubuntu-latest
78- if : github.event_name != 'release'
79- steps :
80- - name : Checkout Repo
81- uses : actions/checkout@v4
82- with :
83- fetch-depth : 0
84-
85- - name : Detect Prerelease Version using Dunamai
86- uses : mtkennerly/dunamai-action@v1
87- with :
88- args : --format "{base}.post{distance}.dev${{ github.run_id }}"
89- env-var : DETECTED_VERSION
90-
91- - name : Detect Release Tag Version from git ref ('${{ github.ref_name }}')
92- if : startsWith(github.ref, 'refs/tags/v')
93- run : |
94- echo "Overriding Dunamai detected version: '${{ env.DETECTED_VERSION || 'none' }}'"
95- # Extract the version from the git ref
96- DETECTED_VERSION=${{ github.ref_name }}
97- # Remove the 'v' prefix if it exists
98- DETECTED_VERSION="${DETECTED_VERSION#v}"
99- echo "Setting detected version to '$DETECTED_VERSION'"
100- echo "DETECTED_VERSION=${DETECTED_VERSION}" >> $GITHUB_ENV
101-
102- - name : Validate and set VERSION (detected='${{ env.DETECTED_VERSION }}', input='${{ github.event.inputs.version || 'none' }}')
103- id : set_version
104- run : |
105- INPUT_VERSION=${{ github.event.inputs.version }}
106- echo "Version input set to '${INPUT_VERSION}'"
107- # Exit with success if both detected and input versions are empty
108- if [ -z "${DETECTED_VERSION:-}" ] && [ -z "${INPUT_VERSION:-}" ]; then
109- echo "No version detected or input. Will publish to SHA tag instead."
110- echo 'VERSION=' >> $GITHUB_ENV
111- exit 0
112- fi
113- # Remove the 'v' prefix if it exists
114- INPUT_VERSION="${INPUT_VERSION#v}"
115- # Fail if detected version is non-empty and different from the input version
116- if [ -n "${DETECTED_VERSION:-}" ] && [ -n "${INPUT_VERSION:-}" ] && [ "${DETECTED_VERSION}" != "${INPUT_VERSION}" ]; then
117- echo "Warning: Version input '${INPUT_VERSION}' does not match detected version '${DETECTED_VERSION}'."
118- echo "Using input version '${INPUT_VERSION}' instead."
119- fi
120- # Set the version to the input version if non-empty, otherwise the detected version
121- VERSION="${INPUT_VERSION:-$DETECTED_VERSION}"
122- # Fail if the version is still empty
123- if [ -z "$VERSION" ]; then
124- echo "Error: VERSION is not set. Ensure the tag follows the format 'refs/tags/vX.Y.Z'."
125- exit 1
126- fi
127- echo "Setting version to '$VERSION'"
128- echo "VERSION=${VERSION}" >> $GITHUB_ENV
129- echo "VERSION=${VERSION}" >> $GITHUB_OUTPUT
130- # Check if version is a prerelease version (will not tag 'latest')
131- if [[ "${VERSION}" =~ ^[0-9]+\.[0-9]+\.[0-9]+$ ]]; then
132- echo "IS_PRERELEASE=false" >> $GITHUB_ENV
133- echo "IS_PRERELEASE=false" >> $GITHUB_OUTPUT
134- else
135- echo "IS_PRERELEASE=true" >> $GITHUB_ENV
136- echo "IS_PRERELEASE=true" >> $GITHUB_OUTPUT
137- fi
138-
139- - name : Install uv
140- uses : astral-sh/setup-uv@v7
141- with :
142- version : " latest"
143-
144- - name : Set up Python
145- uses : actions/setup-python@v5
146- with :
147- python-version : " 3.12"
148-
149- - name : Build package
150- env :
151- # Use UV_DYNAMIC_VERSIONING_BYPASS to override the version in pyproject.toml
152- # This ensures the build matches the detected/validated VERSION.
153- UV_DYNAMIC_VERSIONING_BYPASS : ${{ env.VERSION }}
154- run : uv build
155-
156- - uses : actions/upload-artifact@v6
157- with :
158- name : Packages-${{ github.run_id }}
159- path : |
160- dist/*.whl
161- dist/*.tar.gz
162-
163- outputs :
164- VERSION : ${{ steps.set_version.outputs.VERSION }}
165- IS_PRERELEASE : ${{ steps.set_version.outputs.IS_PRERELEASE }}
166-
16716 publish_to_pypi :
16817 name : Publish Package to PyPI
16918 runs-on : ubuntu-latest
170- # Depend on whichever job ran (build for dispatch, download for release)
171- needs : [build, download_release_assets]
172- # Always run if at least one of the needed jobs succeeded
173- if : |
174- always() &&
175- (needs.build.result == 'success' || needs.download_release_assets.result == 'success') &&
176- (
177- github.event_name == 'release' ||
178- (github.event_name == 'workflow_dispatch' && github.event.inputs.publish_to_pypi == 'true')
179- )
18019 permissions :
18120 id-token : write
182- contents : write
21+ contents : read
18322 environment :
18423 name : PyPI
18524 url : https://pypi.org/p/fastmcp-extensions/
186- env :
187- VERSION : ${{ needs.build.outputs.VERSION || needs.download_release_assets.outputs.VERSION }}
188- IS_PRERELEASE : ${{ needs.build.outputs.IS_PRERELEASE || 'false' }}
18925 steps :
190- - uses : actions/download-artifact@v6
26+ - name : Download wheel from release
27+ uses : robinraju/release-downloader@v1.12
19128 with :
192- name : Packages-${{ github.run_id }}
193- path : dist
29+ tag : ${{ github.event.release.tag_name }}
30+ fileName : " *.whl"
31+ out-file-path : dist
19432
195- # Note: Wheel upload to GitHub release is handled by release-drafter.yml
196- # during the draft stage (before publish). This avoids the "immutable release"
197- # error that occurs when trying to upload assets after a release is published.
33+ - name : Download sdist from release
34+ uses : robinraju/release-downloader@v1.12
35+ with :
36+ tag : ${{ github.event.release.tag_name }}
37+ fileName : " *.tar.gz"
38+ out-file-path : dist
19839
19940 - name : Publish to PyPI
200- # Uses GitHub OIDC for passwordless authentication (see header comment)
20141 uses : pypa/gh-action-pypi-publish@v1.13.0
0 commit comments