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,188 +11,25 @@ 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- steps :
37- - name : Download wheel and sdist from release
38- env :
39- GH_TOKEN : ${{ github.token }}
40- run : |
41- mkdir -p dist
42- gh release download "${{ github.event.release.tag_name }}" \
43- --repo "${{ github.repository }}" \
44- --pattern "*.whl" \
45- --pattern "*.tar.gz" \
46- --dir dist
47- echo "Downloaded assets:"
48- ls -la dist/
49-
50- - name : Verify assets were downloaded
51- run : |
52- if [ ! -f dist/*.whl ]; then
53- echo "Error: No wheel file found in release assets"
54- exit 1
55- fi
56- if [ ! -f dist/*.tar.gz ]; then
57- echo "Error: No sdist file found in release assets"
58- exit 1
59- fi
60- echo "Assets verified successfully"
61-
62- - uses : actions/upload-artifact@v6
63- with :
64- name : Packages-${{ github.run_id }}
65- path : |
66- dist/*.whl
67- dist/*.tar.gz
68-
69- outputs :
70- VERSION : ${{ github.event.release.tag_name }}
71-
72- # Build fresh package (only on workflow_dispatch, NOT on release)
73- build :
74- name : Build Python Package
75- runs-on : ubuntu-latest
76- if : github.event_name != 'release'
77- steps :
78- - name : Checkout Repo
79- uses : actions/checkout@v4
80- with :
81- fetch-depth : 0
82-
83- - name : Detect Prerelease Version using Dunamai
84- uses : mtkennerly/dunamai-action@v1
85- with :
86- args : --format "{base}.post{distance}.dev${{ github.run_id }}"
87- env-var : DETECTED_VERSION
88-
89- - name : Detect Release Tag Version from git ref ('${{ github.ref_name }}')
90- if : startsWith(github.ref, 'refs/tags/v')
91- run : |
92- echo "Overriding Dunamai detected version: '${{ env.DETECTED_VERSION || 'none' }}'"
93- # Extract the version from the git ref
94- DETECTED_VERSION=${{ github.ref_name }}
95- # Remove the 'v' prefix if it exists
96- DETECTED_VERSION="${DETECTED_VERSION#v}"
97- echo "Setting detected version to '$DETECTED_VERSION'"
98- echo "DETECTED_VERSION=${DETECTED_VERSION}" >> $GITHUB_ENV
99-
100- - name : Validate and set VERSION (detected='${{ env.DETECTED_VERSION }}', input='${{ github.event.inputs.version || 'none' }}')
101- id : set_version
102- run : |
103- INPUT_VERSION=${{ github.event.inputs.version }}
104- echo "Version input set to '${INPUT_VERSION}'"
105- # Exit with success if both detected and input versions are empty
106- if [ -z "${DETECTED_VERSION:-}" ] && [ -z "${INPUT_VERSION:-}" ]; then
107- echo "No version detected or input. Will publish to SHA tag instead."
108- echo 'VERSION=' >> $GITHUB_ENV
109- exit 0
110- fi
111- # Remove the 'v' prefix if it exists
112- INPUT_VERSION="${INPUT_VERSION#v}"
113- # Fail if detected version is non-empty and different from the input version
114- if [ -n "${DETECTED_VERSION:-}" ] && [ -n "${INPUT_VERSION:-}" ] && [ "${DETECTED_VERSION}" != "${INPUT_VERSION}" ]; then
115- echo "Warning: Version input '${INPUT_VERSION}' does not match detected version '${DETECTED_VERSION}'."
116- echo "Using input version '${INPUT_VERSION}' instead."
117- fi
118- # Set the version to the input version if non-empty, otherwise the detected version
119- VERSION="${INPUT_VERSION:-$DETECTED_VERSION}"
120- # Fail if the version is still empty
121- if [ -z "$VERSION" ]; then
122- echo "Error: VERSION is not set. Ensure the tag follows the format 'refs/tags/vX.Y.Z'."
123- exit 1
124- fi
125- echo "Setting version to '$VERSION'"
126- echo "VERSION=${VERSION}" >> $GITHUB_ENV
127- echo "VERSION=${VERSION}" >> $GITHUB_OUTPUT
128- # Check if version is a prerelease version (will not tag 'latest')
129- if [[ "${VERSION}" =~ ^[0-9]+\.[0-9]+\.[0-9]+$ ]]; then
130- echo "IS_PRERELEASE=false" >> $GITHUB_ENV
131- echo "IS_PRERELEASE=false" >> $GITHUB_OUTPUT
132- else
133- echo "IS_PRERELEASE=true" >> $GITHUB_ENV
134- echo "IS_PRERELEASE=true" >> $GITHUB_OUTPUT
135- fi
136-
137- - name : Install uv
138- uses : astral-sh/setup-uv@v7
139- with :
140- version : " latest"
141-
142- - name : Set up Python
143- uses : actions/setup-python@v5
144- with :
145- python-version : " 3.12"
146-
147- - name : Build package
148- env :
149- # Use UV_DYNAMIC_VERSIONING_BYPASS to override the version in pyproject.toml
150- # This ensures the build matches the detected/validated VERSION.
151- UV_DYNAMIC_VERSIONING_BYPASS : ${{ env.VERSION }}
152- run : uv build
153-
154- - uses : actions/upload-artifact@v6
155- with :
156- name : Packages-${{ github.run_id }}
157- path : |
158- dist/*.whl
159- dist/*.tar.gz
160-
161- outputs :
162- VERSION : ${{ steps.set_version.outputs.VERSION }}
163- IS_PRERELEASE : ${{ steps.set_version.outputs.IS_PRERELEASE }}
164-
16516 publish_to_pypi :
16617 name : Publish Package to PyPI
16718 runs-on : ubuntu-latest
168- # Depend on whichever job ran (build for dispatch, download for release)
169- needs : [build, download_release_assets]
170- # Always run if at least one of the needed jobs succeeded
171- if : |
172- always() &&
173- (needs.build.result == 'success' || needs.download_release_assets.result == 'success') &&
174- (
175- github.event_name == 'release' ||
176- (github.event_name == 'workflow_dispatch' && github.event.inputs.publish_to_pypi == 'true')
177- )
17819 permissions :
17920 id-token : write
180- contents : write
21+ contents : read
18122 environment :
18223 name : PyPI
18324 url : https://pypi.org/p/fastmcp-extensions/
184- env :
185- VERSION : ${{ needs.build.outputs.VERSION || needs.download_release_assets.outputs.VERSION }}
186- IS_PRERELEASE : ${{ needs.build.outputs.IS_PRERELEASE || 'false' }}
18725 steps :
188- - uses : actions/download-artifact@v6
26+ - name : Download release assets
27+ uses : robinraju/release-downloader@v1.12
18928 with :
190- name : Packages-${{ github.run_id }}
191- path : dist
192-
193- # Note: Wheel upload to GitHub release is handled by release-drafter.yml
194- # during the draft stage (before publish). This avoids the "immutable release"
195- # error that occurs when trying to upload assets after a release is published.
29+ tag : ${{ github.event.release.tag_name }}
30+ fileName : " *"
31+ out-file-path : dist
19632
33+ # Uses GitHub OIDC for passwordless authentication (see header comment)
19734 - name : Publish to PyPI
198- # Uses GitHub OIDC for passwordless authentication (see header comment)
19935 uses : pypa/gh-action-pypi-publish@v1.13.0
0 commit comments