Release #1
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: | |
| workflow_run: | |
| workflows: | |
| - CI | |
| types: | |
| - completed | |
| permissions: | |
| contents: write | |
| concurrency: | |
| group: release-${{ github.event.workflow_run.head_branch }} | |
| cancel-in-progress: true | |
| jobs: | |
| release: | |
| if: >- | |
| ${{ | |
| github.event.workflow_run.conclusion == 'success' && | |
| (github.event.workflow_run.head_branch == 'main' || github.event.workflow_run.head_branch == 'master') | |
| }} | |
| runs-on: ubuntu-latest | |
| steps: | |
| - name: Checkout tested commit | |
| uses: actions/checkout@v4 | |
| with: | |
| fetch-depth: 0 | |
| ref: ${{ github.event.workflow_run.head_sha }} | |
| - name: Install uv | |
| uses: astral-sh/setup-uv@v7 | |
| - name: Set up Python | |
| uses: actions/setup-python@v5 | |
| with: | |
| python-version: "3.12" | |
| - name: Ensure tested commit is branch tip | |
| id: tip | |
| env: | |
| BRANCH_NAME: ${{ github.event.workflow_run.head_branch }} | |
| HEAD_SHA: ${{ github.event.workflow_run.head_sha }} | |
| run: | | |
| git fetch origin "${BRANCH_NAME}" --tags | |
| REMOTE_SHA="$(git rev-parse "origin/${BRANCH_NAME}")" | |
| if [ "${REMOTE_SHA}" != "${HEAD_SHA}" ]; then | |
| echo "Branch ${BRANCH_NAME} moved from ${HEAD_SHA} to ${REMOTE_SHA}; skipping stale release." | |
| echo "is_tip=false" >> "${GITHUB_OUTPUT}" | |
| exit 0 | |
| fi | |
| echo "is_tip=true" >> "${GITHUB_OUTPUT}" | |
| - name: Decide release bump | |
| if: steps.tip.outputs.is_tip == 'true' | |
| id: bump | |
| run: | | |
| python - <<'PY' >> "${GITHUB_OUTPUT}" | |
| import re | |
| import subprocess | |
| import sys | |
| def git(*args: str) -> str: | |
| return subprocess.check_output(["git", *args], text=True).strip() | |
| try: | |
| last_tag = git("describe", "--tags", "--abbrev=0") | |
| except subprocess.CalledProcessError: | |
| last_tag = "" | |
| rev_range = f"{last_tag}..HEAD" if last_tag else "HEAD" | |
| raw = git("log", rev_range, "--format=%s%n%b%x00") | |
| entries = [entry.strip() for entry in raw.split("\x00") if entry.strip()] | |
| if not entries: | |
| print("should_release=false") | |
| print("reason=no_commits") | |
| sys.exit(0) | |
| bump = None | |
| release_notes = [] | |
| for entry in entries: | |
| lines = entry.splitlines() | |
| subject = lines[0].strip() | |
| body = "\n".join(lines[1:]) | |
| text = f"{subject}\n{body}" | |
| lower = subject.lower() | |
| if "breaking change" in text.lower() or re.match(r"^[^:]+!:", subject): | |
| bump = "major" | |
| release_notes.append(subject) | |
| break | |
| if lower.startswith("feat:"): | |
| bump = "minor" if bump != "major" else bump | |
| release_notes.append(subject) | |
| continue | |
| if lower.startswith("fix:"): | |
| if bump not in {"major", "minor"}: | |
| bump = "patch" | |
| release_notes.append(subject) | |
| continue | |
| print(f"last_tag={last_tag}") | |
| print(f"should_release={'true' if bump else 'false'}") | |
| print(f"bump={bump or ''}") | |
| PY | |
| - name: Stop when no release is needed | |
| if: steps.tip.outputs.is_tip == 'true' && steps.bump.outputs.should_release != 'true' | |
| run: echo "No feat/fix/breaking changes since the last tag." | |
| - name: Bump version | |
| if: steps.tip.outputs.is_tip == 'true' && steps.bump.outputs.should_release == 'true' | |
| run: uv version --bump "${{ steps.bump.outputs.bump }}" --frozen | |
| - name: Read version | |
| if: steps.tip.outputs.is_tip == 'true' && steps.bump.outputs.should_release == 'true' | |
| id: version | |
| run: echo "value=$(uv version --short)" >> "${GITHUB_OUTPUT}" | |
| - name: Commit release change | |
| if: steps.tip.outputs.is_tip == 'true' && steps.bump.outputs.should_release == 'true' | |
| env: | |
| VERSION: ${{ steps.version.outputs.value }} | |
| run: | | |
| git config user.name "github-actions[bot]" | |
| git config user.email "41898282+github-actions[bot]@users.noreply.github.com" | |
| git switch -C "${{ github.event.workflow_run.head_branch }}" | |
| git add pyproject.toml | |
| git commit -m "chore: release ${VERSION} [skip ci]" | |
| - name: Create tag | |
| if: steps.tip.outputs.is_tip == 'true' && steps.bump.outputs.should_release == 'true' | |
| env: | |
| VERSION: ${{ steps.version.outputs.value }} | |
| run: git tag -a "v${VERSION}" -m "Release v${VERSION}" | |
| - name: Push release commit and tag | |
| if: steps.tip.outputs.is_tip == 'true' && steps.bump.outputs.should_release == 'true' | |
| env: | |
| BRANCH_NAME: ${{ github.event.workflow_run.head_branch }} | |
| VERSION: ${{ steps.version.outputs.value }} | |
| run: | | |
| git push origin "HEAD:${BRANCH_NAME}" | |
| git push origin "v${VERSION}" |