Skip to content

chore(deps): bump the go-deps group across 1 directory with 3 updates #93

chore(deps): bump the go-deps group across 1 directory with 3 updates

chore(deps): bump the go-deps group across 1 directory with 3 updates #93

Workflow file for this run

name: Release
on:
pull_request:
types: [closed]
branches: [main]
permissions:
contents: write
# Ordering invariant: the version-bump commit lands on main only AFTER the
# matching release is published and all binaries are uploaded. This closes
# the ~80s race window in which CC would sync plugin.json at the new version
# while the GitHub release was still a draft (asset URLs return 404 for
# unauthenticated downloads, so the wrapper's first-run download fails and
# CC caches the MCP startup failure until restart).
jobs:
# ===========================================================================
# Step 1: Determine the target version (no push yet)
# ===========================================================================
version:
if: github.event.pull_request.merged == true
runs-on: ubuntu-latest
outputs:
version: ${{ steps.version.outputs.version }}
bumped: ${{ steps.version.outputs.bumped }}
merge_sha: ${{ steps.resolve.outputs.merge_sha }}
steps:
- uses: actions/checkout@v7
with:
fetch-depth: 0
- name: Resolve PR merge commit
id: resolve
run: |
SHA="${{ github.event.pull_request.merge_commit_sha }}"
if [ -z "$SHA" ]; then
SHA=$(git rev-parse HEAD)
fi
echo "merge_sha=$SHA" >> "$GITHUB_OUTPUT"
- name: Determine version
id: version
run: |
PLUGIN_VERSION=$(jq -r '.version' .claude-plugin/plugin.json)
LATEST_TAG=$(git describe --tags --abbrev=0 2>/dev/null || echo "v0.0.0")
LATEST_TAG=${LATEST_TAG#v}
echo "plugin=$PLUGIN_VERSION latest_tag=$LATEST_TAG"
# If plugin.json version is ahead of the latest tag, use it (manual bump)
if [ "$PLUGIN_VERSION" != "$LATEST_TAG" ]; then
P_MAJOR=$(echo "$PLUGIN_VERSION" | cut -d. -f1)
P_MINOR=$(echo "$PLUGIN_VERSION" | cut -d. -f2)
P_PATCH=$(echo "$PLUGIN_VERSION" | cut -d. -f3)
TAG_MAJOR=$(echo "$LATEST_TAG" | cut -d. -f1)
TAG_MINOR=$(echo "$LATEST_TAG" | cut -d. -f2)
TAG_PATCH=$(echo "$LATEST_TAG" | cut -d. -f3)
if [ "$P_MAJOR" -gt "$TAG_MAJOR" ] 2>/dev/null || \
{ [ "$P_MAJOR" -eq "$TAG_MAJOR" ] && [ "$P_MINOR" -gt "$TAG_MINOR" ]; } 2>/dev/null || \
{ [ "$P_MAJOR" -eq "$TAG_MAJOR" ] && [ "$P_MINOR" -eq "$TAG_MINOR" ] && [ "$P_PATCH" -gt "$TAG_PATCH" ]; } 2>/dev/null; then
echo "Manual version bump detected: $LATEST_TAG → $PLUGIN_VERSION"
echo "version=$PLUGIN_VERSION" >> "$GITHUB_OUTPUT"
echo "bumped=manual" >> "$GITHUB_OUTPUT"
else
echo "plugin.json version $PLUGIN_VERSION is not ahead of tag $LATEST_TAG — auto-bumping patch"
echo "bumped=auto" >> "$GITHUB_OUTPUT"
fi
else
echo "No manual bump — auto-bumping patch"
echo "bumped=auto" >> "$GITHUB_OUTPUT"
fi
# Auto-bump patch if no manual bump was set
if ! grep -q "version=" "$GITHUB_OUTPUT" 2>/dev/null; then
MAJOR=$(echo "$LATEST_TAG" | cut -d. -f1)
MINOR=$(echo "$LATEST_TAG" | cut -d. -f2)
PATCH=$(echo "$LATEST_TAG" | cut -d. -f3)
NEW_PATCH=$((PATCH + 1))
NEW_VERSION="${MAJOR}.${MINOR}.${NEW_PATCH}"
echo "Auto-bumping: $LATEST_TAG → $NEW_VERSION"
echo "version=$NEW_VERSION" >> "$GITHUB_OUTPUT"
fi
# ===========================================================================
# Step 2: Create draft release, tagging the PR-merge commit
# ===========================================================================
# The tag points to the merge SHA so the tag exists before any bump-commit
# push. For auto-bumps this means the tagged tree has plugin.json one
# version behind the tag name (the bump lands in Step 5); for manual bumps
# the merge commit already contains the new plugin.json so the tagged tree
# matches the tag.
create-release:
needs: version
runs-on: ubuntu-latest
outputs:
release_created: ${{ steps.create_release.outputs.release_created }}
steps:
- uses: actions/checkout@v7
with:
fetch-depth: 0
ref: ${{ needs.version.outputs.merge_sha }}
- name: Generate release notes
id: notes
env:
PR_TITLE: ${{ github.event.pull_request.title }}
run: |
VERSION="${{ needs.version.outputs.version }}"
NOTES=$(awk "/^## ${VERSION}$/,/^## /{if(/^## ${VERSION}$/)next; if(/^## /)exit; print}" CHANGELOG.md 2>/dev/null)
if [ -z "$NOTES" ]; then
NOTES=$(awk "/^## v${VERSION}$/,/^## /{if(/^## v${VERSION}$/)next; if(/^## /)exit; print}" CHANGELOG.md 2>/dev/null)
fi
if [ -z "$NOTES" ]; then
NOTES="Merged: ${PR_TITLE}"
fi
{
echo "notes<<EOF"
echo "$NOTES"
echo "EOF"
} >> "$GITHUB_OUTPUT"
- name: Create draft release
id: create_release
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
RELEASE_NOTES: ${{ steps.notes.outputs.notes }}
run: |
TAG="v${{ needs.version.outputs.version }}"
MERGE_SHA="${{ needs.version.outputs.merge_sha }}"
if git ls-remote --tags origin "refs/tags/$TAG" | grep -q "$TAG"; then
echo "Tag $TAG already exists — skipping release"
echo "release_created=false" >> "$GITHUB_OUTPUT"
exit 0
fi
# Tag the PR-merge commit so the tag is in place before any
# plugin.json bump-commit lands on main.
git tag "$TAG" "$MERGE_SHA"
git push origin "$TAG"
# Create release; clean up tag on failure
if ! gh release create "$TAG" --title "$TAG" --target "$MERGE_SHA" --draft --notes "$RELEASE_NOTES"; then
echo "::warning::Release creation failed — cleaning up orphaned tag"
git push origin --delete "$TAG" || true
echo "release_created=false" >> "$GITHUB_OUTPUT"
exit 1
fi
echo "release_created=true" >> "$GITHUB_OUTPUT"
# ===========================================================================
# Step 3: Build binaries (parallel matrix)
# ===========================================================================
binaries:
needs: [version, create-release]
if: needs.create-release.outputs.release_created == 'true'
runs-on: ubuntu-latest
defaults:
run:
working-directory: src
strategy:
matrix:
os: [linux, darwin, windows]
arch: [amd64, arm64]
steps:
- uses: actions/checkout@v7
with:
ref: ${{ needs.version.outputs.merge_sha }}
- uses: actions/setup-go@v7
with:
go-version-file: src/go.mod
cache-dependency-path: src/go.sum
- name: Build binary
run: make build-for GOOS=${{ matrix.os }} GOARCH=${{ matrix.arch }} VERSION=v${{ needs.version.outputs.version }}
- name: Upload release asset
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
EXT=""
if [ "${{ matrix.os }}" = "windows" ]; then EXT=".exe"; fi
ASSET="bin/devkit-${{ matrix.os }}-${{ matrix.arch }}${EXT}"
echo "Uploading ${ASSET}"
gh release upload "v${{ needs.version.outputs.version }}" \
"$ASSET" \
--clobber
working-directory: src
# ===========================================================================
# Step 4: Generate checksums and publish (un-draft) the release
# ===========================================================================
publish:
needs: [version, create-release, binaries]
if: needs.create-release.outputs.release_created == 'true'
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v7
- name: Download all release assets and generate checksums
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
TAG="v${{ needs.version.outputs.version }}"
mkdir -p /tmp/release-assets
gh release download "$TAG" --dir /tmp/release-assets
(cd /tmp/release-assets && sha256sum devkit-* > checksums.txt && cat checksums.txt)
gh release upload "$TAG" /tmp/release-assets/checksums.txt --clobber
- name: Publish release
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: gh release edit "v${{ needs.version.outputs.version }}" --draft=false
# ===========================================================================
# Step 5: Push the version-bump commit (auto-bump only)
# ===========================================================================
# Runs last so plugin.json on main only advertises the new version after
# the release is fully published. Skipped for manual bumps where the
# merge commit already carries the bumped plugin.json.
#
# Bumps mcpb/manifest.json in lockstep with .claude-plugin/plugin.json and
# rebuilds devkit.mcpb + devkit.mcpb.sources.json so the MCPB bundle's
# advertised version matches what the launcher actually fetches, and the
# sidecar's sha256s stay consistent with the bundled files. Without this,
# manifest.json drifts behind every auto-bump and the next contributor who
# runs `make sync-version` locally trips the mcpb-bundle-integrity CI check.
bump:
needs: [version, create-release, binaries, publish]
if: >-
needs.create-release.outputs.release_created == 'true' &&
needs.version.outputs.bumped == 'auto'
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v7
with:
ssh-key: ${{ secrets.VERSION_BUMP_KEY }}
fetch-depth: 0
ref: main
- uses: actions/setup-go@v7
with:
go-version-file: src/go.mod
cache-dependency-path: src/go.sum
- name: Sync plugin.json + mcpb/manifest.json
env:
VERSION: ${{ needs.version.outputs.version }}
run: |
jq --arg v "$VERSION" '.version = $v' .claude-plugin/plugin.json > tmp.json && mv tmp.json .claude-plugin/plugin.json
jq --arg v "$VERSION" '.version = $v' mcpb/manifest.json > tmp.json && mv tmp.json mcpb/manifest.json
- name: Rebuild MCPB bundle
# manifest.json is hashed in devkit.mcpb.sources.json and zipped into
# devkit.mcpb, so a manifest bump invalidates both. The launcher
# cross-compile is deterministic (-trimpath, -s -w, no embedded
# version) so mcpb/server/devkit.exe is usually byte-identical and
# `git add` skips it; only the bundle + sidecar actually change.
run: bin/mcpb-build
- name: Commit and push version bump
env:
VERSION: ${{ needs.version.outputs.version }}
run: |
git config user.name "github-actions[bot]"
git config user.email "github-actions[bot]@users.noreply.github.com"
git add .claude-plugin/plugin.json \
mcpb/manifest.json \
mcpb/server/devkit.exe \
devkit.mcpb \
devkit.mcpb.sources.json
if git diff --cached --quiet; then
echo "Version files already at $VERSION — no commit needed"
else
git commit -m "bump to v${VERSION}"
git push
fi
# ===========================================================================
# Cleanup on failure
# ===========================================================================
# Note: bump failures do NOT trigger cleanup — by that point the release
# is already published, so the artifacts should stay. A failed bump leaves
# plugin.json on main one version behind; the next merge's auto-bump logic
# walks forward from the latest tag, so recovery is automatic on next merge.
cleanup-on-failure:
needs: [version, create-release, binaries, publish, bump]
if: >-
always() &&
needs.create-release.outputs.release_created == 'true' &&
(needs.binaries.result == 'failure' || needs.binaries.result == 'cancelled' ||
needs.publish.result == 'failure' || needs.publish.result == 'cancelled')
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v7
- name: Delete draft release on failure
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
TAG="v${{ needs.version.outputs.version }}"
echo "::warning::Cleaning up draft release $TAG due to workflow failure"
gh release delete "$TAG" --yes || echo "::warning::Failed to delete release $TAG — manual cleanup required"
git push origin --delete "$TAG" || echo "::warning::Failed to delete tag $TAG — manual cleanup required"