Release / Publish Pipeline #28
Workflow file for this run
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| # ============================================================================= | |
| # coding-proxy: PyPI Publishing Workflow (Initial Release) | |
| # ============================================================================= | |
| # Trigger: GitHub Release publication event | |
| # Architecture: Build-Publish Separation (PyPA security best practice) | |
| # Paired with: promote.yml (prerelease → stable promotion gate) | |
| # - Job 1 (build): Low privileges, produces sdist+wheel artifacts | |
| # - Job 2 (publish-testpypi): Publish to TestPyPI (prereleases only) | |
| # - Job 3 (publish-pypi): Publish to PyPI (stable releases / hotfixes) | |
| # | |
| # Routing Logic: | |
| # - prerelease == true --> TestPyPI (with skip-existing tolerance) | |
| # --> THEN use promote.yml to promote to PyPI after validation | |
| # - prerelease == false --> PyPI production (direct stable release, e.g. hotfixes) | |
| # | |
| # Promotion Flow (see promote.yml): | |
| # 1. Create release with prerelease: true --> builds + publishes to TestPyPI | |
| # 2. Human validates package on TestPyPI | |
| # 3. Run promote.yml (workflow_dispatch) --> promotes to PyPI production | |
| # | |
| # Pre-requisites (choose ONE authentication method): | |
| # | |
| # Option A — OIDC Trusted Publishing (recommended, no secrets needed): | |
| # 1. Create GitHub Environments: "pypi" and "testpypi" | |
| # 2. Configure Trusted Publishers on PyPI/TestPyPI admin panels | |
| # 3. (Recommended) Set "Required reviewers" on "pypi" environment | |
| # | |
| # Option B — API Token Fallback (simpler initial setup): | |
| # 1. Set repository secrets: PYPI_API_TOKEN and/or TEST_PYPI_API_TOKEN | |
| # 2. (Optional) Create "pypi"/"testpypi" environments for deployment guards | |
| # If environments are not created, remove the `environment:` blocks below. | |
| # | |
| # References: | |
| # [1] https://packaging.python.org/guides/publishing-package-distribution-releases-using-github-actions-ci-cd-workflows/ | |
| # [2] https://github.com/pypa/gh-action-pypi-publish | |
| # [3] https://docs.pypi.org/trusted-publishers/using-a-publisher/ | |
| # ============================================================================= | |
| name: Release / Publish to PyPI | |
| on: | |
| release: | |
| types: [published] | |
| permissions: | |
| contents: read | |
| env: | |
| FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true | |
| concurrency: | |
| group: ${{ github.workflow }}-${{ github.ref }} | |
| cancel-in-progress: true | |
| jobs: | |
| # =========================================================================== | |
| # Job 1: BUILD -- Produce distribution artifacts (low privilege isolation) | |
| # =========================================================================== | |
| build: | |
| name: Build distributions | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 10 | |
| strategy: | |
| matrix: | |
| python-version: ["3.12", "3.13", "3.14"] | |
| steps: | |
| - name: Checkout repository | |
| uses: actions/checkout@v4 | |
| with: | |
| persist-credentials: false | |
| - name: Set up Python | |
| uses: actions/setup-python@v5 | |
| with: | |
| python-version: ${{ matrix.python-version }} | |
| - name: Set up uv | |
| uses: astral-sh/setup-uv@v4 | |
| with: | |
| enable-cache: true | |
| - name: Install build dependencies | |
| run: uv pip install --system build twine | |
| - name: Build sdist and wheel | |
| run: python -m build | |
| - name: Check package metadata | |
| run: twine check dist/* | |
| - name: Upload build artifacts | |
| uses: actions/upload-artifact@v4 | |
| with: | |
| name: dist-py${{ matrix.python-version }} | |
| path: dist/ | |
| retention-days: 14 | |
| # =========================================================================== | |
| # Job 2: PUBLISH TO TESTPYPI -- Prerelease / staging releases only | |
| # =========================================================================== | |
| publish-testpypi: | |
| name: Publish to TestPyPI | |
| runs-on: ubuntu-latest | |
| needs: build | |
| if: github.event.release.prerelease == true | |
| timeout-minutes: 10 | |
| environment: | |
| name: testpypi | |
| url: https://test.pypi.org/p/coding-proxy | |
| permissions: | |
| id-token: write | |
| contents: read | |
| steps: | |
| - name: Download build artifacts | |
| uses: actions/download-artifact@v4 | |
| with: | |
| pattern: dist-py* | |
| path: dist/ | |
| merge-multiple: true | |
| - name: Publish to TestPyPI | |
| uses: pypa/gh-action-pypi-publish@release/v1 | |
| with: | |
| password: ${{ secrets.TEST_PYPI_API_TOKEN }} | |
| repository-url: https://test.pypi.org/legacy/ | |
| skip-existing: true | |
| attestations: false # TestPyPI 不支持 attestations,必须显式禁用 | |
| verbose: true # 启用详细日志以获取 400 错误响应体 | |
| # =========================================================================== | |
| # Job 3: PUBLISH TO PYPI -- Production releases only | |
| # =========================================================================== | |
| publish-pypi: | |
| name: Publish to PyPI | |
| runs-on: ubuntu-latest | |
| needs: build | |
| if: github.event.release.prerelease == false | |
| timeout-minutes: 10 | |
| environment: | |
| name: pypi | |
| url: https://pypi.org/p/coding-proxy | |
| permissions: | |
| id-token: write | |
| contents: read | |
| steps: | |
| - name: Download build artifacts | |
| uses: actions/download-artifact@v4 | |
| with: | |
| pattern: dist-py* | |
| path: dist/ | |
| merge-multiple: true | |
| - name: Publish to PyPI | |
| uses: pypa/gh-action-pypi-publish@release/v1 | |
| with: | |
| password: ${{ secrets.PYPI_API_TOKEN }} |