chore(develop): release 1.2.2 #21
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 Please | |
| # Release automation, in two phases that both run from this one file. | |
| # | |
| # Work lands on develop. main is moved only by this workflow, so it always | |
| # equals the last released commit. | |
| # | |
| # 1. Every push to develop, release-please recomputes what the next version | |
| # would be from the Conventional Commit subjects since the last release, and | |
| # opens or rewrites a "chore(develop): release X.Y.Z" PR carrying the version | |
| # bumps and the CHANGELOG entry. | |
| # 2. Merging that PR pushes to develop, which runs this workflow again. This | |
| # time release-please sees a merged release PR and creates the tag and the | |
| # GitHub release. The jobs below then fast-forward main onto that commit, | |
| # ship to PyPI, and publish the docs. | |
| # | |
| # Everything downstream lives in this same workflow on purpose. A release - or a | |
| # push - made with the default GITHUB_TOKEN does not trigger other workflows, so | |
| # an `on: push: tags` publish, or a pages build watching main, would silently | |
| # never fire. Calling them through `needs` sidesteps that entirely. | |
| on: | |
| push: | |
| branches: | |
| - develop | |
| # Rebuild the release PR on demand. release-please leaves an open release PR | |
| # alone when the version it computes has not changed, so a config fix that | |
| # only alters the PR's *contents* is not picked up on its own - delete the | |
| # release branch and run this to have it rebuilt. | |
| workflow_dispatch: | |
| permissions: | |
| contents: read | |
| jobs: | |
| # The last moment before a tag exists. release-please creates the tag and the | |
| # GitHub release in a single step, so anything asserted after it - as the | |
| # version-consistency check was, from the publish job - reports a problem that | |
| # has already shipped and needs a second release to correct. Everything that | |
| # must be true of a release is therefore asserted here, and release-please does | |
| # not run unless it holds. | |
| # | |
| # Only a release merge is checked. On an ordinary push CHANGELOG.md carries a | |
| # `## [Unreleased]` heading, which is correct there and would fail the version | |
| # comparison, so the check would cry wolf on every commit. | |
| preflight: | |
| runs-on: ubuntu-latest | |
| steps: | |
| - uses: actions/checkout@v7 | |
| - name: Assert every version and the CHANGELOG agree | |
| shell: pwsh | |
| run: | | |
| $subject = git log -1 --pretty=%s | |
| if ($subject -notmatch '^chore\(develop\): release ') { | |
| Write-Host "Not a release merge - nothing to assert." | |
| Write-Host " head commit: $subject" | |
| exit 0 | |
| } | |
| Write-Host "Release merge detected: $subject" | |
| ./scripts/check-version-consistency.ps1 | |
| release-please: | |
| needs: preflight | |
| runs-on: ubuntu-latest | |
| permissions: | |
| contents: write # tags, releases, and the release branch | |
| pull-requests: write # open and rewrite the release PR | |
| issues: write # the autorelease:* labels are an issues-scope API | |
| outputs: | |
| # `releases_created` is always set to the string 'true' or 'false'. Compare | |
| # it explicitly: any non-empty string is truthy in a GitHub Actions | |
| # expression, so a bare `if: ...outputs.releases_created` is always true. | |
| releases_created: ${{ steps.release.outputs.releases_created }} | |
| tag_name: ${{ steps.release.outputs.tag_name }} | |
| # The release commit on develop. main is fast-forwarded onto exactly this. | |
| sha: ${{ steps.release.outputs.sha }} | |
| steps: | |
| - uses: googleapis/release-please-action@v5 | |
| id: release | |
| with: | |
| config-file: release-please-config.json | |
| manifest-file: .release-please-manifest.json | |
| # Without this release-please works against the repository's default | |
| # branch. That is develop today, but stating it keeps this workflow | |
| # correct if the default is ever changed back. | |
| target-branch: develop | |
| # Releases and PRs made with GITHUB_TOKEN do not trigger workflows, so | |
| # CI does not run on the release PR itself. Supply a PAT or GitHub App | |
| # token here if you want the PR gated by the test matrix before merge. | |
| # token: ${{ secrets.RELEASE_PLEASE_TOKEN }} | |
| # uv.lock records the project's own version, so every version bump leaves it a | |
| # release behind and fails `uv lock --check`. release-please cannot fix that: | |
| # its updaters re-serialise the file they edit, which is safe for a small hand | |
| # written manifest and emphatically not for a generated 900-line lockfile. So | |
| # uv regenerates it, on the release PR, before anyone merges it. | |
| sync-lockfile: | |
| needs: release-please | |
| runs-on: ubuntu-latest | |
| permissions: | |
| contents: write | |
| pull-requests: read | |
| steps: | |
| - uses: actions/checkout@v7 | |
| with: | |
| fetch-depth: 0 | |
| # Asking for the PR rather than reading release-please's `pr` output: that | |
| # output is only set on the run that creates or updates the PR, and | |
| # release-please deliberately leaves an open release PR alone when the | |
| # version it computes has not changed. Every later push to develop would | |
| # therefore skip this job with the PR sitting there unsynced. | |
| - name: Find the open release PR | |
| id: find | |
| env: | |
| GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
| run: | | |
| branch=$(gh pr list --state open --base develop --json headRefName --jq '[.[] | select(.headRefName | startswith("release-please--"))][0].headRefName // ""') | |
| if [ -z "$branch" ]; then | |
| echo "No open release PR - nothing to sync." | |
| else | |
| echo "Release PR branch: $branch" | |
| fi | |
| echo "branch=$branch" >> "$GITHUB_OUTPUT" | |
| - uses: astral-sh/setup-uv@v7 | |
| if: steps.find.outputs.branch != '' | |
| with: | |
| version: "latest" | |
| - name: Regenerate uv.lock and commit it if it moved | |
| if: steps.find.outputs.branch != '' | |
| env: | |
| BRANCH: ${{ steps.find.outputs.branch }} | |
| run: | | |
| git checkout -B "$BRANCH" "origin/$BRANCH" | |
| uv lock | |
| if git diff --quiet -- uv.lock; then | |
| echo "uv.lock is already in step with pyproject.toml." | |
| exit 0 | |
| fi | |
| git config user.name "github-actions[bot]" | |
| git config user.email "41898282+github-actions[bot]@users.noreply.github.com" | |
| git commit -m "chore: sync uv.lock with the release version" -- uv.lock | |
| git push origin "$BRANCH" | |
| echo "uv.lock synced onto $BRANCH." | |
| # release-please is configured with skip-changelog, so CHANGELOG.md stays | |
| # hand-written - but it still creates the GitHub release with its own notes, | |
| # generated from commit subjects. Those are thinner than the entry a human | |
| # wrote, so replace them with it. | |
| release-notes: | |
| needs: release-please | |
| if: ${{ needs.release-please.outputs.releases_created == 'true' }} | |
| runs-on: ubuntu-latest | |
| permissions: | |
| contents: write | |
| steps: | |
| - uses: actions/checkout@v7 | |
| - name: Use the hand-written CHANGELOG entry as the release notes | |
| env: | |
| GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
| TAG: ${{ needs.release-please.outputs.tag_name }} | |
| run: | | |
| CLEAN_VERSION="${TAG#v}" | |
| # Matched with index()/substr() rather than a dynamic regex: awk | |
| # treats "\[" in a string as a plain "[", which silently turns the | |
| # pattern into a character class that matches every "## [" heading, | |
| # so the notes become whichever section happens to be first. | |
| awk -v hdr="## [$CLEAN_VERSION]" ' | |
| index($0, hdr) == 1 { found = 1; next } | |
| found && substr($0, 1, 4) == "## [" { exit } | |
| found { print } | |
| ' CHANGELOG.md > release_notes.md | |
| if [ -s release_notes.md ]; then | |
| gh release edit "$TAG" --notes-file release_notes.md | |
| echo "Release notes taken from the CHANGELOG entry for $CLEAN_VERSION." | |
| else | |
| # Not fatal: the release exists either way, and the publish job's | |
| # version-consistency check is what actually fails a missing entry. | |
| echo "::warning::No CHANGELOG section for $CLEAN_VERSION - keeping the generated notes." | |
| fi | |
| publish: | |
| needs: release-please | |
| if: ${{ needs.release-please.outputs.releases_created == 'true' }} | |
| uses: ./.github/workflows/publish-mcp.yml | |
| secrets: inherit | |
| # main is a pointer to the last released commit, moved only from here. This is | |
| # a plain fast-forward, so it fails loudly rather than rewriting anything if | |
| # main carries commits develop does not - which happens only if someone pushed | |
| # to main directly. | |
| fast-forward-main: | |
| needs: release-please | |
| if: ${{ needs.release-please.outputs.releases_created == 'true' }} | |
| runs-on: ubuntu-latest | |
| permissions: | |
| contents: write | |
| steps: | |
| - uses: actions/checkout@v7 | |
| with: | |
| fetch-depth: 0 | |
| - name: Fast-forward main onto the released commit | |
| env: | |
| SHA: ${{ needs.release-please.outputs.sha }} | |
| TAG: ${{ needs.release-please.outputs.tag_name }} | |
| run: | | |
| if git push origin "$SHA:refs/heads/main"; then | |
| echo "main now points at $SHA ($TAG)." | |
| else | |
| echo "::error::Could not fast-forward main onto $SHA ($TAG). main has commits that develop does not; merge main into develop and re-run this workflow." | |
| exit 1 | |
| fi | |
| # Docs describe the released version, so they publish from a release rather | |
| # than from every push. Chained rather than triggered: the fast-forward above | |
| # is a GITHUB_TOKEN push and so does not start pages.yml on its own. | |
| docs: | |
| needs: [release-please, fast-forward-main] | |
| if: ${{ needs.release-please.outputs.releases_created == 'true' }} | |
| permissions: | |
| contents: read | |
| pages: write | |
| id-token: write | |
| uses: ./.github/workflows/pages.yml |