From 73aa68cab92a3a83dcca287a38acdbaecbfca235 Mon Sep 17 00:00:00 2001 From: Sami Jaghouar Date: Thu, 21 May 2026 10:54:02 -0700 Subject: [PATCH] ci: auto-publish dev release to PyPI on every commit to main Adds .github/workflows/publish-dev.yml which, on every push to main, tags the commit as ``renderers-v.dev`` and publishes the resulting wheel to PyPI as a pre-release. Build runs from the freshly-created tag so hatch-vcs produces a clean PEP 440 version without the ``+gHASH`` local segment PyPI rejects. Mirrors the build/publish split from publish.yml so OIDC stays scoped to the publish job only. Co-Authored-By: Claude Opus 4.7 (1M context) --- .github/workflows/publish-dev.yml | 104 ++++++++++++++++++++++++++++++ 1 file changed, 104 insertions(+) create mode 100644 .github/workflows/publish-dev.yml diff --git a/.github/workflows/publish-dev.yml b/.github/workflows/publish-dev.yml new file mode 100644 index 0000000..4b67697 --- /dev/null +++ b/.github/workflows/publish-dev.yml @@ -0,0 +1,104 @@ +name: Publish Dev + +# Tag every commit on main as ``renderers-v.dev`` and publish the +# wheel to PyPI as a pre-release. ```` is the latest release tag with +# its patch bumped; ```` is the number of commits since that release so +# each main commit maps to a unique PEP 440 dev version. +# +# Building from the freshly-created tag means hatch-vcs resolves the version +# cleanly (no ``+gHASH`` local segment), which PyPI requires. + +on: + push: + branches: [main] + +concurrency: + group: publish-dev-${{ github.ref }} + cancel-in-progress: false + +jobs: + tag: + runs-on: ubuntu-latest + permissions: + contents: write + outputs: + tag: ${{ steps.compute.outputs.tag }} + steps: + - uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - name: Compute next dev tag + id: compute + run: | + set -euo pipefail + LATEST_RELEASE=$(git tag --list 'renderers-v*' --sort=-v:refname \ + | grep -Ev '(dev|rc|a[0-9]|b[0-9])' \ + | head -1) + if [ -z "$LATEST_RELEASE" ]; then + echo "No release tag matching 'renderers-v' found" >&2 + exit 1 + fi + BASE=${LATEST_RELEASE#renderers-v} + MAJOR=$(echo "$BASE" | cut -d. -f1) + MINOR=$(echo "$BASE" | cut -d. -f2) + PATCH=$(echo "$BASE" | cut -d. -f3) + NEXT="${MAJOR}.${MINOR}.$((PATCH + 1))" + N=$(git rev-list --count "${LATEST_RELEASE}..HEAD") + TAG="renderers-v${NEXT}.dev${N}" + echo "tag=${TAG}" >> "$GITHUB_OUTPUT" + echo "Computed tag: ${TAG} (base=${LATEST_RELEASE}, commits=${N})" + + - name: Create and push tag + env: + TAG: ${{ steps.compute.outputs.tag }} + run: | + set -euo pipefail + if git ls-remote --exit-code --tags origin "refs/tags/${TAG}" >/dev/null 2>&1; then + echo "Tag ${TAG} already exists on origin — nothing to do" >&2 + exit 0 + fi + git config user.name 'github-actions[bot]' + git config user.email '41898282+github-actions[bot]@users.noreply.github.com' + git tag -a "$TAG" -m "Automated dev release ${TAG}" + git push origin "$TAG" + + build: + needs: tag + runs-on: ubuntu-latest + permissions: + contents: read + steps: + - uses: actions/checkout@v4 + with: + fetch-depth: 0 + ref: refs/tags/${{ needs.tag.outputs.tag }} + + - uses: astral-sh/setup-uv@v7 + + - name: Build renderers + run: uv build + + - name: Upload dist artifacts + uses: actions/upload-artifact@v4 + with: + name: dist-dev + path: dist/ + if-no-files-found: error + retention-days: 7 + + publish: + needs: build + runs-on: ubuntu-latest + environment: pypi-prod + permissions: + id-token: write + steps: + - name: Download dist artifacts + uses: actions/download-artifact@v4 + with: + name: dist-dev + path: dist/ + + - name: Publish to PyPI + uses: pypa/gh-action-pypi-publish@cef221092ed1bacb1cc03d23a2d87d1d172e277b # v1.14.0