Release v0.4.2-alpha #11
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
| name: Publish to PyPI | |
| on: | |
| push: | |
| tags: | |
| - "v*.*.*" | |
| jobs: | |
| # GATE — block build/publish/release until the required CI workflows pass | |
| # on this exact tagged commit. Polls the Actions API; a workflow that | |
| # concludes in any non-success terminal state aborts the whole pipeline. | |
| # (CI_DOES_THE_RELEASE, 2026-07-16 — matches the same fix applied to the | |
| # Python CLI repo after v10.12.2 shipped to PyPI with broken metadata | |
| # while its own CI test job was still failing on that commit.) | |
| require_ci_green: | |
| name: Require CI to pass on this commit | |
| runs-on: ubuntu-latest | |
| permissions: | |
| actions: read | |
| steps: | |
| - name: Poll required workflows | |
| env: | |
| GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
| SHA: ${{ github.sha }} | |
| REQUIRED: "ci.yml codeql.yml secret-scan.yml" | |
| run: | | |
| deadline=$(( $(date +%s) + 2400 )) # 40 min ceiling | |
| while :; do | |
| pending="" | |
| for wf in $REQUIRED; do | |
| out=$(gh run list --repo "$GITHUB_REPOSITORY" --workflow "$wf" \ | |
| --commit "$SHA" --limit 1 \ | |
| --json conclusion,status \ | |
| --jq '.[0] | (.conclusion // "none") + " " + (.status // "none")' \ | |
| 2>/dev/null || true) | |
| [ -z "$out" ] && out="none none" | |
| concl=${out%% *} | |
| echo " $wf -> conclusion=$concl" | |
| case "$concl" in | |
| success) ;; | |
| failure|cancelled|timed_out|startup_failure|action_required) | |
| echo "::error::$wf concluded '$concl' on $SHA — refusing to publish" | |
| exit 1 ;; | |
| *) pending="$pending $wf" ;; | |
| esac | |
| done | |
| if [ -z "$pending" ]; then | |
| echo "All required CI workflows passed on $SHA." | |
| break | |
| fi | |
| if [ "$(date +%s)" -ge "$deadline" ]; then | |
| echo "::error::Timed out waiting for required CI:$pending" | |
| exit 1 | |
| fi | |
| echo "Waiting for:$pending" | |
| sleep 25 | |
| done | |
| build: | |
| name: Build distribution | |
| needs: require_ci_green | |
| runs-on: ubuntu-latest | |
| steps: | |
| - uses: actions/checkout@v6 | |
| - name: Set up Python | |
| uses: actions/setup-python@v6 | |
| with: | |
| python-version: "3.11" | |
| - name: Install build | |
| run: python -m pip install --upgrade build | |
| - name: Build wheel and sdist | |
| run: python -m build | |
| - name: Upload build artifacts | |
| uses: actions/upload-artifact@v7 | |
| with: | |
| name: python-package-distributions | |
| path: dist/ | |
| publish: | |
| name: Publish to PyPI | |
| needs: build | |
| runs-on: ubuntu-latest | |
| environment: pypi | |
| permissions: | |
| id-token: write # required for OIDC trusted-publisher auth | |
| steps: | |
| - name: Download build artifacts | |
| uses: actions/download-artifact@v8 | |
| with: | |
| name: python-package-distributions | |
| path: dist/ | |
| - name: Publish to PyPI | |
| uses: pypa/gh-action-pypi-publish@release/v1 | |
| release: | |
| name: Publish GitHub Release | |
| needs: publish | |
| runs-on: ubuntu-latest | |
| if: startsWith(github.ref, 'refs/tags/') | |
| permissions: | |
| contents: write | |
| steps: | |
| - uses: actions/checkout@v6 | |
| with: | |
| fetch-depth: 0 | |
| - name: Extract release notes from CHANGELOG.md | |
| id: notes | |
| run: | | |
| VERSION="${GITHUB_REF_NAME#v}" | |
| # VERSION passed to awk via -v, matched with plain string ops (no | |
| # regex on it) — never interpolated into the program text. | |
| # GITHUB_REF_NAME is attacker-influenceable via the tag name, so | |
| # string interpolation there would be an awk/command-injection | |
| # vector. Preserves the original "## VERSION" + (space|end-of-line) | |
| # boundary check without needing a regex over VERSION itself. | |
| awk -v ver="$VERSION" ' | |
| { | |
| prefix = "## " ver | |
| if (index($0, prefix) == 1) { | |
| rest = substr($0, length(prefix) + 1) | |
| if (rest == "" || substr(rest, 1, 1) == " ") { flag=1; next } | |
| } | |
| } | |
| /^## / { flag=0 } | |
| flag { print } | |
| ' CHANGELOG.md > /tmp/release-notes.md | |
| if ! grep -q '[^[:space:]]' /tmp/release-notes.md; then | |
| echo "::error::No CHANGELOG.md entry found for ${VERSION} — add a '## ${VERSION}' section before tagging." | |
| exit 1 | |
| fi | |
| - name: Create or update GitHub release | |
| env: | |
| GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
| run: | | |
| TAG="${GITHUB_REF_NAME}" | |
| TITLE="${TAG#v}" | |
| if gh release view "$TAG" --repo "$GITHUB_REPOSITORY" >/dev/null 2>&1; then | |
| gh release edit "$TAG" --repo "$GITHUB_REPOSITORY" --title "$TITLE" --notes-file /tmp/release-notes.md | |
| else | |
| gh release create "$TAG" --repo "$GITHUB_REPOSITORY" --title "$TITLE" --notes-file /tmp/release-notes.md | |
| fi |