chore(release): v1.7.4 #32
Workflow file for this run
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-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 }} |