Skip to content

fix(ci-policy): em-dash sweep + extend CI guard to docs/ #21

fix(ci-policy): em-dash sweep + extend CI guard to docs/

fix(ci-policy): em-dash sweep + extend CI guard to docs/ #21

Workflow file for this run

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