Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
75 changes: 75 additions & 0 deletions .github/workflows/publish.yml
Original file line number Diff line number Diff line change
Expand Up @@ -98,3 +98,78 @@ jobs:
exit 0
fi
exit $status

github-release:
# Auto-create (or update) a GitHub Release matching the tag, with notes
# pulled from CHANGELOG.md. Closes #126. The Releases page is the
# canonical surface most users discover the project through; without
# this job it drifts behind tags / PyPI / MCP-registry state. Pattern
# ported from cmeans/mcp-synology publish.yml.
needs: publish-pypi
runs-on: ubuntu-latest
permissions:
contents: write
steps:
- uses: actions/checkout@v6
with:
fetch-depth: 0

- name: Extract release notes from CHANGELOG
id: notes
env:
TAG: ${{ github.ref_name }}
run: |
# CHANGELOG.md is the authoritative release narrative. Extract the
# section for the current tag and use it as the Release body so we
# don't duplicate content or rely on low-quality auto-generated
# commit lists. Falls back to --generate-notes if the CHANGELOG
# has no matching entry (e.g. an emergency tag without doc prep).
VERSION="${TAG#v}"
# mcp-clipboard CHANGELOG uses Keep-a-Changelog format:
# `## [2.5.1] - 2026-05-05`. The literal `[VERSION]` headings
# distinguish actual releases from `## [Unreleased]` (also a
# bracketed heading but the version-string never matches).
awk -v version="$VERSION" '
$0 ~ "^## \\["version"\\]" {flag=1; next}
flag && /^## / {exit}
flag
' CHANGELOG.md > release_notes.md

# Strip leading blank lines so the body does not start with an
# empty paragraph.
sed -i '/./,$!d' release_notes.md

if [ -s release_notes.md ]; then
echo "Found CHANGELOG entry for $VERSION ($(wc -l < release_notes.md) lines)"
echo "use_changelog=true" >> "$GITHUB_OUTPUT"
else
echo "::warning::No CHANGELOG entry for $VERSION — falling back to auto-generated notes"
echo "use_changelog=false" >> "$GITHUB_OUTPUT"
fi

- name: Create or update GitHub Release
env:
GH_TOKEN: ${{ github.token }}
TAG: ${{ github.ref_name }}
USE_CHANGELOG: ${{ steps.notes.outputs.use_changelog }}
run: |
# Idempotent: if a release for the tag already exists (e.g. it was
# hand-crafted before the publish workflow ran, or the workflow is
# being re-run after a partial failure), update its notes in place
# with `gh release edit` rather than failing with a 422.
if gh release view "$TAG" >/dev/null 2>&1; then
echo "Release $TAG already exists — updating notes in place"
if [ "$USE_CHANGELOG" = "true" ]; then
gh release edit "$TAG" --title "$TAG" --notes-file release_notes.md
else
# Cannot regenerate auto notes on edit — leave existing body.
gh release edit "$TAG" --title "$TAG"
fi
else
echo "Creating new release $TAG"
if [ "$USE_CHANGELOG" = "true" ]; then
gh release create "$TAG" --title "$TAG" --notes-file release_notes.md
else
gh release create "$TAG" --title "$TAG" --generate-notes
fi
fi
18 changes: 18 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,24 @@ All notable changes to this project will be documented here.

## [Unreleased]

### Added
- New `github-release` job in `publish.yml` auto-creates (or updates) a
GitHub Release matching the pushed tag, with notes pulled from the
matching `## [VERSION]` section of `CHANGELOG.md`. Idempotent: if a
Release for the tag already exists (hand-crafted, or a re-run of a
partially-failed publish), the job edits the notes in place via
`gh release edit` rather than failing with HTTP 422. Falls through to
`--generate-notes` (auto commit-list) when the CHANGELOG has no
matching entry. `needs: publish-pypi` so the Release only lands after
the wheel is on PyPI; `permissions: contents: write` enables
`gh release create`. Closes the GitHub-Releases-page-drift gap that
surfaced after v2.4.0/v2.5.0/v2.5.1 all landed on PyPI without
matching Releases (manually backfilled on 2026-05-06). Pattern ported
from mcp-synology with a CHANGELOG-format adaptation: mcp-clipboard
uses Keep-a-Changelog `## [VERSION]` brackets, so the awk anchor is
`^## \[VERSION\]` rather than mcp-synology's `^## VERSION( |\()`.
(#127) - closes #126.

## [2.5.1] - 2026-05-05

### Fixed
Expand Down