mirror of
https://github.com/svnscha/mcp-windbg.git
synced 2026-10-18 13:41:41 +08:00
253 lines
11 KiB
YAML
253 lines
11 KiB
YAML
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, list the release in the MCP registry, 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:
|
|
# Pinned: the ubuntu-latest label moves to Ubuntu 26 on 2026-10-19,
|
|
# and a release should not change runner images by surprise.
|
|
runs-on: ubuntu-24.04
|
|
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-24.04
|
|
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-24.04
|
|
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-24.04
|
|
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' }}
|
|
# A called workflow may not ask for more than its caller grants, and the
|
|
# check is made when the run starts, not when the job does: with the
|
|
# workflow-level `contents: read` here, publish-mcp.yml's `attach` job
|
|
# asking for `contents: write` failed the whole run at startup, on every
|
|
# push to develop, release or not. Granted here so the signed exe can be
|
|
# uploaded to the release.
|
|
permissions:
|
|
contents: write
|
|
uses: ./.github/workflows/publish-mcp.yml
|
|
# The tag the signed mcp-windbg.exe is attached to. release-please has
|
|
# created both it and the release by this point.
|
|
with:
|
|
tag: ${{ needs.release-please.outputs.tag_name }}
|
|
secrets: inherit
|
|
|
|
# The MCP registry checks the package on PyPI before it lists a version, so
|
|
# this waits for publish. Skipped whenever publish is.
|
|
registry:
|
|
needs: publish
|
|
permissions:
|
|
contents: read
|
|
id-token: write # mcp-publisher logs in with GitHub OIDC
|
|
uses: ./.github/workflows/publish-mcp-registry.yml
|
|
|
|
# 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-24.04
|
|
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
|