release-kit 0.2.8

A canonical release workflow: a technology-agnostic method, per-technology bindings, and the rk CLI that lands and serves them.
Documentation
# The release automation and the PyPI trusted publisher. This filename is
# registered at PyPI, together with the pypi environment the publish job runs
# in; no other workflow may carry id-token: write.
#
# Both jobs run on every push to the trunk, and to a release/* line when a
# project keeps older lines. release-please maintains the one release pull
# request against the pushed branch — it only bumps pyproject.toml and writes
# the changelog, because skip-github-release in release-please-config.json
# keeps it from tagging — and merging that request is the release: the bump
# push is what makes tag-and-publish tag the commit, build the distributions,
# and publish them over OIDC. On a release/* line the same pair backports a
# patch and tags it there.
name: release-please

permissions: {}

on:
  push:
    branches: [master, 'release/**']

jobs:
  release-please-pr:
    if: github.repository_owner == 'OWNER'
    runs-on: ubuntu-latest
    permissions:
      contents: read
    concurrency:
      group: release-please-${{ github.ref }}
      cancel-in-progress: false
    steps:
      - uses: actions/create-github-app-token@bcd2ba49218906704ab6c1aa796996da409d3eb1 # v3
        id: app-token
        with:
          app-id: ${{ secrets.RELEASE_BOT_APP_ID }}
          private-key: ${{ secrets.RELEASE_BOT_APP_PRIVATE_KEY }}
      - uses: googleapis/release-please-action@45996ed1f6d02564a971a2fa1b5860e934307cf7 # v5
        with:
          token: ${{ steps.app-token.outputs.token }}
          target-branch: ${{ github.ref_name }}

  # The publish half. Only the push that lands the bot's own bump releases:
  # the detect step compares the committed version against the parent commit
  # and against the tags, so an ordinary work merge builds and publishes
  # nothing. The app token is what lets the tag land under protection; the
  # OIDC token is minted only here, inside the pypi environment the trusted
  # publisher names.
  tag-and-publish:
    if: github.repository_owner == 'OWNER'
    runs-on: ubuntu-latest
    environment: pypi
    permissions:
      contents: read
      id-token: write
    steps:
      - uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4
        with:
          fetch-depth: 0
          persist-credentials: false
      - name: detect the pending release
        id: bump
        env:
          SHA: ${{ github.sha }}
        run: |
          set -eu
          version="$(sed -n 's/^version *= *"\([^"]*\)".*/\1/p' pyproject.toml | head -n 1)"
          if [ -z "$version" ]; then
            echo "::error::could not read the version from pyproject.toml."
            exit 1
          fi
          previous="$(git show "$SHA^:pyproject.toml" | sed -n 's/^version *= *"\([^"]*\)".*/\1/p' | head -n 1)"
          if [ "$previous" = "$version" ]; then
            echo "this commit does not bump the version; nothing to release."
            echo "pending=false" >> "$GITHUB_OUTPUT"
            exit 0
          fi
          if [ -n "$(git ls-remote --tags origin "refs/tags/v$version")" ]; then
            echo "v$version is already tagged; no release is pending."
            echo "pending=false" >> "$GITHUB_OUTPUT"
            exit 0
          fi
          echo "pending=true" >> "$GITHUB_OUTPUT"
          echo "version=$version" >> "$GITHUB_OUTPUT"
      - uses: actions/create-github-app-token@bcd2ba49218906704ab6c1aa796996da409d3eb1 # v3
        if: steps.bump.outputs.pending == 'true'
        id: app-token
        with:
          app-id: ${{ secrets.RELEASE_BOT_APP_ID }}
          private-key: ${{ secrets.RELEASE_BOT_APP_PRIVATE_KEY }}
      - name: tag the release commit
        if: steps.bump.outputs.pending == 'true'
        env:
          GH_TOKEN: ${{ steps.app-token.outputs.token }}
          GH_REPO: ${{ github.repository }}
          SHA: ${{ github.sha }}
          VERSION: ${{ steps.bump.outputs.version }}
        run: |
          set -eu
          gh api -X POST "repos/$GH_REPO/git/refs" \
            -f "ref=refs/tags/v$VERSION" -f "sha=$SHA" >/dev/null
          echo "tagged v$VERSION at $SHA."
      - uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7
        if: steps.bump.outputs.pending == 'true'
        with:
          python-version: "3.x"
      - name: build the distributions
        if: steps.bump.outputs.pending == 'true'
        run: |
          python -m pip install --upgrade build
          python -m build
      - name: publish over OIDC
        if: steps.bump.outputs.pending == 'true'
        uses: pypa/gh-action-pypi-publish@dc37677b2e1c63e2034f94d8a5b11f265b73ba33 # release/v1
        with:
          skip-existing: true