Skip to content

chore(release): v1.7.4 #32

chore(release): v1.7.4

chore(release): v1.7.4 #32

name: release-validation
# Fires on every tag push that looks like a version (`v1.2.3`, `v1.2.3-beta.1`, etc.).
# Two jobs run in sequence:
# 1. validate-manifest-versions — confirms the .claude-plugin/* version sites
# match the tag. Catches drift like 1.2.1 / 1.3.0 shipping with stale 1.2.0
# manifests.
# 2. publish-release — extracts the matching CHANGELOG.md section and creates
# a GitHub Release with the tag, title, and notes. Closes the
# tag-pushed-but-no-release gap that left v1.4.7 through v1.5.1 unpublished
# as Releases until manually backfilled.
#
# Both jobs run for every `v*` tag push on every branch. The CHANGELOG is read
# from the tagged commit, so each branch's CHANGELOG governs its own line's
# release notes (1.4.x's CHANGELOG for 1.4.x releases, main's for 1.5.x+).
on:
push:
tags:
- 'v*'
permissions:
contents: write
jobs:
validate-manifest-versions:
name: Manifest versions match tag
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v5
- name: Compare versions
shell: bash
run: |
set -euo pipefail
TAG="${GITHUB_REF_NAME}" # e.g. v1.3.1
EXPECTED="${TAG#v}" # strip leading 'v' → 1.3.1
PLUGIN_VERSION=$(jq -r '.version' .claude-plugin/plugin.json)
MARKETPLACE_METADATA_VERSION=$(jq -r '.metadata.version' .claude-plugin/marketplace.json)
MARKETPLACE_PLUGIN_VERSION=$(jq -r '.plugins[0].version' .claude-plugin/marketplace.json)
echo "tag : ${TAG}"
echo "expected version : ${EXPECTED}"
echo "plugin.json .version : ${PLUGIN_VERSION}"
echo "marketplace.json .metadata.version : ${MARKETPLACE_METADATA_VERSION}"
echo "marketplace.json .plugins[0].ver. : ${MARKETPLACE_PLUGIN_VERSION}"
fail=0
if [ "${PLUGIN_VERSION}" != "${EXPECTED}" ]; then
echo "::error file=.claude-plugin/plugin.json::version is '${PLUGIN_VERSION}', expected '${EXPECTED}'"
fail=1
fi
if [ "${MARKETPLACE_METADATA_VERSION}" != "${EXPECTED}" ]; then
echo "::error file=.claude-plugin/marketplace.json::metadata.version is '${MARKETPLACE_METADATA_VERSION}', expected '${EXPECTED}'"
fail=1
fi
if [ "${MARKETPLACE_PLUGIN_VERSION}" != "${EXPECTED}" ]; then
echo "::error file=.claude-plugin/marketplace.json::plugins[0].version is '${MARKETPLACE_PLUGIN_VERSION}', expected '${EXPECTED}'"
fail=1
fi
if [ "${fail}" -ne 0 ]; then
echo
echo "Manifest versions do not match the pushed tag. Run \`bin/release.sh ${EXPECTED}\` and re-tag." >&2
exit 1
fi
echo
echo "All manifest versions match ${TAG}."
publish-release:
name: Publish GitHub Release
needs: validate-manifest-versions
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v5
with:
# Need history (and origin/main) to determine whether this tag
# belongs to the main line or a maintenance branch.
fetch-depth: 0
- name: Determine "Latest" flag
id: latest
shell: bash
run: |
set -euo pipefail
# The release should be marked "Latest" only when the tagged commit
# is part of the current line. We treat `origin/main` as the current
# line. Maintenance-branch releases (e.g. 1.4.x while main is on
# 1.5.x) should NOT displace main's Latest badge — GitHub's default
# behavior is to set "Latest" by publish timestamp, which gives the
# wrong result when a 1.4.x release is published a few seconds
# after a 1.5.x release.
#
# `git merge-base --is-ancestor` returns 0 iff the first commit is
# reachable from the second — i.e. the tag is on main's history.
if git merge-base --is-ancestor "${GITHUB_SHA}" origin/main 2>/dev/null; then
LATEST=true
else
LATEST=false
fi
echo "Tag ${GITHUB_REF_NAME} (commit ${GITHUB_SHA:0:8}) → --latest=${LATEST}"
echo "latest=${LATEST}" >> "${GITHUB_OUTPUT}"
- name: Extract CHANGELOG section for tag
shell: bash
run: |
set -euo pipefail
TAG="${GITHUB_REF_NAME}"
VERSION="${TAG#v}"
# Extract everything between "## VERSION " and the next "## " heading.
# Skips the heading line itself (gh release will set the title separately).
awk -v ver="$VERSION" '
$0 ~ "^## " ver "[ \t]" { capture=1; next }
capture && /^## [0-9]/ { exit }
capture { print }
' CHANGELOG.md > /tmp/release-notes.md
# Reject empty notes — that signals a missing CHANGELOG entry, which
# is a release-prep mistake worth failing the workflow over.
if [ -z "$(tr -d '[:space:]' < /tmp/release-notes.md)" ]; then
echo "::error file=CHANGELOG.md::No '## ${VERSION}' section found. Add the entry and re-tag."
exit 1
fi
echo "Release notes (first 20 lines):"
head -20 /tmp/release-notes.md
- name: Create GitHub Release
env:
GH_TOKEN: ${{ github.token }}
run: |
gh release create "${GITHUB_REF_NAME}" \
--title "${GITHUB_REF_NAME}" \
--notes-file /tmp/release-notes.md \
--latest=${{ steps.latest.outputs.latest }}