name: Publish to PyPI on: workflow_dispatch: inputs: tag: description: 'Release tag to publish (e.g., v0.10.1)' required: true type: string permissions: contents: read jobs: build: runs-on: ubuntu-latest permissions: contents: read actions: write steps: - name: Verify tag format run: | TAG="${{ inputs.tag }}" if [[ ! "$TAG" =~ ^v[0-9]+\.[0-9]+\.[0-9]+$ ]]; then echo "Error: '$TAG' is not a valid release tag (expected vX.Y.Z)" exit 1 fi - name: Checkout release tag uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 with: ref: refs/tags/${{ inputs.tag }} - name: Install uv uses: astral-sh/setup-uv@fac544c07dec837d0ccb6301d7b5580bf5edae39 # v8.2.0 - name: Set up Python uses: actions/setup-python@ece7cb06caefa5fff74198d8649806c4678c61a1 # v6 with: python-version: "3.13" - name: Verify tag matches package version run: | TAG_VERSION="${{ inputs.tag }}" TAG_VERSION="${TAG_VERSION#v}" PROJECT_VERSION="$(python -c 'import tomllib; print(tomllib.load(open("pyproject.toml","rb"))["project"]["version"])')" if [[ "$TAG_VERSION" != "$PROJECT_VERSION" ]]; then echo "Error: Tag version ($TAG_VERSION) does not match pyproject.toml version ($PROJECT_VERSION)" exit 1 fi - name: Build package run: uv build - name: Upload build artifacts uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 with: name: dist path: dist/ if-no-files-found: error publish: needs: build runs-on: ubuntu-latest environment: pypi permissions: id-token: write actions: read steps: - name: Download build artifacts uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 with: name: dist path: dist/ - name: Install uv uses: astral-sh/setup-uv@fac544c07dec837d0ccb6301d7b5580bf5edae39 # v8.2.0 - name: Publish to PyPI run: uv publish