release: v0.11.3 (F22 corrective -- dispatch whitelist alignment) #16
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: Release | |
| on: | |
| push: | |
| tags: ['v*'] | |
| jobs: | |
| build: | |
| runs-on: ubuntu-latest | |
| steps: | |
| - uses: actions/checkout@v4 | |
| with: | |
| # Need full history (or at least the merge-base) for the | |
| # ancestry check below. | |
| fetch-depth: 0 | |
| - name: Set up Python | |
| uses: actions/setup-python@v5 | |
| with: | |
| python-version: "3.12" | |
| - name: Install build deps | |
| run: python -m pip install --upgrade pip build | |
| - name: Build sdist + wheel | |
| run: python -m build | |
| - name: Verify build | |
| run: | | |
| ls -lh dist/ | |
| # furqan-lint is pure Python; expect exactly one wheel | |
| # (py3-none-any) and one sdist (.tar.gz). | |
| test "$(ls dist/*.whl | wc -l)" -eq 1 | |
| test "$(ls dist/*.tar.gz | wc -l)" -eq 1 | |
| # Wheel must be py3-none-any (we are pure Python). | |
| ls dist/*.whl | grep -q "py3-none-any.whl" | |
| - name: Verify tag is an ancestor of origin/main | |
| run: | | |
| # Defends against tags pushed at local-only commits that | |
| # never made it to main. Release artifacts must reflect | |
| # what is actually on the published branch. | |
| git fetch origin main --depth=50 | |
| if ! git merge-base --is-ancestor "$GITHUB_SHA" origin/main; then | |
| echo "Tag commit $GITHUB_SHA is not an ancestor of origin/main" | |
| exit 1 | |
| fi | |
| echo "Tag commit is on origin/main: ok" | |
| - name: Verify version-tag-CHANGELOG sync | |
| run: | | |
| TAG_VERSION="${GITHUB_REF#refs/tags/v}" | |
| # Workflow uses Python 3.12; tomllib is stdlib since 3.11. | |
| # No tomli shim needed. | |
| PYPROJECT_VERSION=$(python -c " | |
| import tomllib | |
| with open('pyproject.toml', 'rb') as f: | |
| print(tomllib.load(f)['project']['version']) | |
| ") | |
| if [ "$TAG_VERSION" != "$PYPROJECT_VERSION" ]; then | |
| echo "Tag version $TAG_VERSION != pyproject.toml version $PYPROJECT_VERSION" | |
| exit 1 | |
| fi | |
| if ! grep -q "^## \[${TAG_VERSION}\]" CHANGELOG.md; then | |
| echo "CHANGELOG.md has no entry for [${TAG_VERSION}]" | |
| exit 1 | |
| fi | |
| echo "Tag/pyproject/CHANGELOG version sync: $TAG_VERSION" | |
| - name: Upload artifacts | |
| uses: actions/upload-artifact@v4 | |
| with: | |
| name: dist | |
| path: dist/ | |
| publish: | |
| needs: build | |
| runs-on: ubuntu-latest | |
| environment: pypi | |
| permissions: | |
| id-token: write | |
| contents: write | |
| steps: | |
| - uses: actions/checkout@v4 | |
| with: | |
| fetch-depth: 0 | |
| - name: Set up Python | |
| uses: actions/setup-python@v5 | |
| with: | |
| python-version: "3.12" | |
| - name: Download artifacts | |
| uses: actions/download-artifact@v4 | |
| with: | |
| name: dist | |
| path: dist/ | |
| - name: Publish to PyPI | |
| uses: pypa/gh-action-pypi-publish@release/v1 | |
| - name: Verify PyPI publication | |
| # Phase G10.5 (al-Mubin) T01: closes F7 by verifying that | |
| # the just-published version is reachable through PyPI's | |
| # index. Twelve polls at ten-second intervals (120s total) | |
| # per the S5 calibration. PyPI CDN propagation is typically | |
| # 1-15 seconds; tail observations of 90-180 seconds exist; | |
| # the 60-second window we considered earlier risks false- | |
| # failure on a successful publish. | |
| run: | | |
| set -euo pipefail | |
| VERSION="${GITHUB_REF#refs/tags/v}" | |
| echo "Verifying furqan-lint==${VERSION} is installable from PyPI..." | |
| for i in 1 2 3 4 5 6 7 8 9 10 11 12; do | |
| if pip index versions furqan-lint 2>/dev/null | grep -q "Available versions:.*${VERSION}"; then | |
| echo "furqan-lint ${VERSION} is now installable." | |
| exit 0 | |
| fi | |
| echo "Attempt ${i}/12: not yet visible, sleeping 10s..." | |
| sleep 10 | |
| done | |
| echo "ERROR: furqan-lint ${VERSION} did not become visible on PyPI within 120 seconds." | |
| exit 1 | |
| - name: Create GitHub Release | |
| # Phase G10.5 (al-Mubin) T02: closes F8 going forward by | |
| # automatically creating a Release object with notes | |
| # extracted from CHANGELOG.md whenever a v* tag is pushed. | |
| # Notes are extracted by scripts/extract_changelog_section.py | |
| # which slices between the version's ## [X.Y.Z] header and | |
| # the next ## [ header. --verify-tag asserts the tag exists | |
| # at the workflow's GITHUB_SHA, defending against re-runs | |
| # that point at a different SHA. | |
| env: | |
| GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
| run: | | |
| set -euo pipefail | |
| VERSION="${GITHUB_REF#refs/tags/v}" | |
| NOTES_FILE="$(mktemp)" | |
| python scripts/extract_changelog_section.py "${VERSION}" > "${NOTES_FILE}" | |
| if [ ! -s "${NOTES_FILE}" ]; then | |
| echo "ERROR: No CHANGELOG section found for v${VERSION}." | |
| exit 1 | |
| fi | |
| gh release create "v${VERSION}" \ | |
| --title "v${VERSION}" \ | |
| --notes-file "${NOTES_FILE}" \ | |
| --verify-tag |