Release #1
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: | |
| workflow_dispatch: | |
| inputs: | |
| bump: | |
| description: 'Version bump type' | |
| required: true | |
| type: choice | |
| options: | |
| - patch | |
| - minor | |
| - major | |
| dry_run: | |
| description: 'Dry run (skip tag push, release creation, and proxy trigger)' | |
| required: false | |
| type: boolean | |
| default: false | |
| permissions: | |
| contents: write | |
| jobs: | |
| release: | |
| runs-on: ubuntu-latest | |
| steps: | |
| - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 | |
| with: | |
| fetch-depth: 0 | |
| fetch-tags: true | |
| - name: Authorize release actor | |
| run: | | |
| ACTOR="${{ github.actor }}" | |
| ALLOWED="" | |
| if [ -f CODEOWNERS ]; then | |
| ALLOWED="$ALLOWED $(grep -oP '@\K[\w-]+' CODEOWNERS | sort -u)" | |
| fi | |
| if [ -f RELEASERS ]; then | |
| ALLOWED="$ALLOWED $(grep -vE '^\s*(#|$)' RELEASERS | tr -s '[:space:]' ' ')" | |
| fi | |
| for user in $ALLOWED; do | |
| if [ "$ACTOR" = "$user" ]; then | |
| echo "Authorized: $ACTOR is in CODEOWNERS or RELEASERS" | |
| exit 0 | |
| fi | |
| done | |
| echo "::error::$ACTOR is not authorized to create releases. Only users listed in CODEOWNERS or RELEASERS may trigger this workflow." | |
| exit 1 | |
| - uses: actions/setup-go@4a3601121dd01d1626a1e23e37211e3254c1c06c # v6.4.0 | |
| with: | |
| go-version-file: go.mod | |
| - name: Determine next version | |
| id: version | |
| run: | | |
| LATEST=$(git tag --sort=-v:refname | grep -E '^v[0-9]+\.[0-9]+\.[0-9]+$' | head -1) | |
| if [ -z "$LATEST" ]; then | |
| LATEST="v0.0.0" | |
| fi | |
| echo "current=$LATEST" >> "$GITHUB_OUTPUT" | |
| MAJOR=$(echo "$LATEST" | sed 's/^v//' | cut -d. -f1) | |
| MINOR=$(echo "$LATEST" | sed 's/^v//' | cut -d. -f2) | |
| PATCH=$(echo "$LATEST" | sed 's/^v//' | cut -d. -f3) | |
| case "${{ inputs.bump }}" in | |
| major) MAJOR=$((MAJOR + 1)); MINOR=0; PATCH=0 ;; | |
| minor) MINOR=$((MINOR + 1)); PATCH=0 ;; | |
| patch) PATCH=$((PATCH + 1)) ;; | |
| esac | |
| NEXT="v${MAJOR}.${MINOR}.${PATCH}" | |
| echo "next=$NEXT" >> "$GITHUB_OUTPUT" | |
| echo "### Version bump: $LATEST → $NEXT (${{ inputs.bump }})" >> "$GITHUB_STEP_SUMMARY" | |
| - name: Verify CI passed on HEAD | |
| run: | | |
| HEAD_SHA=$(git rev-parse HEAD) | |
| echo "Checking CI status for $HEAD_SHA..." | |
| CONCLUSION=$(gh run list --commit "$HEAD_SHA" --workflow ci.yml --json conclusion --jq '.[0].conclusion // "none"') | |
| if [ "$CONCLUSION" != "success" ]; then | |
| echo "::error::CI has not passed on HEAD ($HEAD_SHA). Latest conclusion: $CONCLUSION" | |
| exit 1 | |
| fi | |
| env: | |
| GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
| - name: Verify CHANGELOG has entry for next version | |
| run: | | |
| NEXT="${{ steps.version.outputs.next }}" | |
| if ! grep -q "## $NEXT" CHANGELOG.md; then | |
| echo "::error::CHANGELOG.md does not contain an entry for $NEXT. Add the changelog entry before releasing." | |
| exit 1 | |
| fi | |
| - name: Build and test | |
| run: | | |
| make build | |
| make test-race | |
| - name: Extract changelog for release notes | |
| id: notes | |
| run: | | |
| NEXT="${{ steps.version.outputs.next }}" | |
| NOTES=$(awk "/^## $NEXT/{flag=1; next} /^## v[0-9]/{flag=0} flag" CHANGELOG.md) | |
| { | |
| echo "body<<RELEASE_NOTES_EOF" | |
| echo "$NOTES" | |
| echo "RELEASE_NOTES_EOF" | |
| } >> "$GITHUB_OUTPUT" | |
| - name: Create annotated tag | |
| if: ${{ !inputs.dry_run }} | |
| run: | | |
| NEXT="${{ steps.version.outputs.next }}" | |
| git config user.name "github-actions[bot]" | |
| git config user.email "41898282+github-actions[bot]@users.noreply.github.com" | |
| git tag -a "$NEXT" -m "$NEXT" | |
| git push origin "$NEXT" | |
| - name: Create GitHub release | |
| if: ${{ !inputs.dry_run }} | |
| run: | | |
| NEXT="${{ steps.version.outputs.next }}" | |
| gh release create "$NEXT" \ | |
| --title "$NEXT" \ | |
| --notes "$RELEASE_NOTES" \ | |
| --verify-tag | |
| env: | |
| GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
| RELEASE_NOTES: ${{ steps.notes.outputs.body }} | |
| - name: Trigger Go module proxy indexing | |
| if: ${{ !inputs.dry_run }} | |
| run: | | |
| NEXT="${{ steps.version.outputs.next }}" | |
| MODULE=$(head -1 go.mod | awk '{print $2}') | |
| echo "Requesting proxy indexing for ${MODULE}@${NEXT}..." | |
| HTTP_CODE=$(curl -s -o /dev/null -w "%{http_code}" "https://proxy.golang.org/${MODULE}/@v/${NEXT}.info") | |
| echo "Proxy response: $HTTP_CODE" | |
| if [ "$HTTP_CODE" -ge 400 ]; then | |
| echo "::warning::Go module proxy returned $HTTP_CODE — indexing may be delayed" | |
| fi | |
| - name: Verify pkg.go.dev availability | |
| if: ${{ !inputs.dry_run }} | |
| run: | | |
| NEXT="${{ steps.version.outputs.next }}" | |
| MODULE=$(head -1 go.mod | awk '{print $2}') | |
| echo "Waiting for pkg.go.dev to index ${MODULE}@${NEXT}..." | |
| for i in 1 2 3 4 5; do | |
| sleep 15 | |
| HTTP_CODE=$(curl -s -o /dev/null -w "%{http_code}" "https://pkg.go.dev/${MODULE}@${NEXT}") | |
| echo "Attempt $i: HTTP $HTTP_CODE" | |
| if [ "$HTTP_CODE" -eq 200 ]; then | |
| echo "pkg.go.dev is serving ${MODULE}@${NEXT}" | |
| echo "### pkg.go.dev: [${MODULE}@${NEXT}](https://pkg.go.dev/${MODULE}@${NEXT})" >> "$GITHUB_STEP_SUMMARY" | |
| exit 0 | |
| fi | |
| done | |
| echo "::warning::pkg.go.dev has not indexed ${NEXT} yet — check manually at https://pkg.go.dev/${MODULE}@${NEXT}" | |
| - name: Summary | |
| run: | | |
| NEXT="${{ steps.version.outputs.next }}" | |
| CURRENT="${{ steps.version.outputs.current }}" | |
| if [ "${{ inputs.dry_run }}" = "true" ]; then | |
| echo "### Dry run complete" >> "$GITHUB_STEP_SUMMARY" | |
| echo "Would have tagged **$NEXT** (from $CURRENT, ${{ inputs.bump }} bump)" >> "$GITHUB_STEP_SUMMARY" | |
| else | |
| echo "### Release $NEXT published" >> "$GITHUB_STEP_SUMMARY" | |
| echo "- Tag: [$NEXT](https://github.com/${{ github.repository }}/releases/tag/$NEXT)" >> "$GITHUB_STEP_SUMMARY" | |
| echo "- Bump: $CURRENT → $NEXT (${{ inputs.bump }})" >> "$GITHUB_STEP_SUMMARY" | |
| fi |