chore(deps): bump the go-deps group in /src with 3 updates #88
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 | |
| 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@v4 | |
| 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@v4 | |
| 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@v4 | |
| with: | |
| ref: ${{ needs.version.outputs.merge_sha }} | |
| - uses: actions/setup-go@v5 | |
| 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@v4 | |
| - 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@v4 | |
| with: | |
| ssh-key: ${{ secrets.VERSION_BUMP_KEY }} | |
| fetch-depth: 0 | |
| ref: main | |
| - uses: actions/setup-go@v5 | |
| 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@v4 | |
| - 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" |