Skip to content

chore: release 5.2.3 (#17) #25

chore: release 5.2.3 (#17)

chore: release 5.2.3 (#17) #25

# =============================================================================
# Release Please Workflow for open-runtime/grpc-dart Fork
# =============================================================================
# Automates releases using Conventional Commits:
# - feat: β†’ Minor version bump
# - fix: β†’ Patch version bump
# - feat!/fix!/BREAKING CHANGE: β†’ Major version bump
#
# Flow:
# 1. Creates/updates a Release PR when conventional commits land
# 2. Claude enhances the PR (via enhance-release-pr.yml)
# 3. When merged: Creates tag and GitHub Release
# 4. Dart packages can consume via tag_pattern: "^v"
# =============================================================================
name: Release Please
on:
push:
branches:
- main
workflow_dispatch: # Allow manual triggering
# Only one Release Please run per branch at a time
concurrency:
group: release-please-${{ github.ref_name }}
cancel-in-progress: false # Don't cancel - Release Please should complete
permissions:
contents: write
pull-requests: write
issues: write
actions: write
jobs:
# =============================================================================
# Step 1: Handle Untagged Merged Release PRs FIRST
# =============================================================================
# This MUST run before release-please to prevent "untagged PRs outstanding" abort
# Release Please aborts if it finds merged PRs with "autorelease: pending" label
# By running this first, we ensure all untagged PRs are tagged before Release Please checks
handle-untagged-releases:
name: πŸ”§ Handle Untagged Releases (Pre-Check)
runs-on: ubuntu-latest
outputs:
release_created: ${{ steps.process.outputs.release_created }}
tag_name: ${{ steps.process.outputs.tag_name }}
version: ${{ steps.process.outputs.version }}
steps:
- name: πŸ“₯ Checkout source code
uses: actions/checkout@v4
with:
fetch-depth: 0
- name: πŸ” Find and process untagged merged release PRs
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
echo "πŸ” Checking for untagged merged release PRs..."
# Find merged PRs with autorelease: pending label
PENDING_PRS=$(gh pr list --repo ${{ github.repository }} \
--state merged \
--label "autorelease: pending" \
--json number,title,mergeCommit \
--jq '.[] | select(.title | test("^chore: release "))')
if [ -z "$PENDING_PRS" ]; then
echo "βœ… No untagged merged release PRs found"
exit 0
fi
echo "πŸ“‹ Found untagged release PRs:"
echo "$PENDING_PRS"
# Process each pending PR
echo "$PENDING_PRS" | jq -c '.' | while read -r pr; do
PR_NUM=$(echo "$pr" | jq -r '.number')
PR_TITLE=$(echo "$pr" | jq -r '.title')
MERGE_SHA=$(echo "$pr" | jq -r '.mergeCommit.oid')
# Extract version from title "chore: release X.Y.Z"
VERSION=$(echo "$PR_TITLE" | grep -oP '\d+\.\d+\.\d+')
if [ -z "$VERSION" ]; then
echo "⚠️ Could not extract version from PR #$PR_NUM title: $PR_TITLE"
continue
fi
TAG_NAME="v$VERSION"
echo "πŸ“¦ Processing PR #$PR_NUM: $PR_TITLE"
echo " Version: $VERSION"
echo " SHA: $MERGE_SHA"
echo " Tag: $TAG_NAME"
if gh api repos/${{ github.repository }}/git/refs/tags/$TAG_NAME 2>/dev/null; then
echo "βœ… Tag $TAG_NAME already exists"
else
echo "🏷️ Creating tag $TAG_NAME at $MERGE_SHA"
gh api repos/${{ github.repository }}/git/refs \
-X POST \
-f ref="refs/tags/$TAG_NAME" \
-f sha="$MERGE_SHA"
fi
if gh release view $TAG_NAME --repo ${{ github.repository }} 2>/dev/null; then
echo "βœ… Release $TAG_NAME already exists"
else
echo "πŸŽ‰ Creating GitHub Release $TAG_NAME"
RELEASE_CREATED=true
LATEST_TAG="$TAG_NAME"
LATEST_VERSION="$VERSION"
CHANGELOG=$(awk -v ver="$VERSION" '
/^## \[/ {
if (found) exit
if ($0 ~ "## \\[" ver "\\]") found=1
}
found { print }
' CHANGELOG.md | tail -n +2)
if [ -z "$CHANGELOG" ]; then
CHANGELOG="Release v$VERSION - See [CHANGELOG.md](https://github.com/${{ github.repository }}/blob/${{ github.ref_name }}/CHANGELOG.md) for details."
fi
{
echo "## πŸ“¦ Installation"
echo ""
echo "**Dart 3.9+ (recommended):**"
echo "\`\`\`yaml"
echo "dependencies:"
echo " grpc:"
echo " git:"
echo " url: https://github.com/open-runtime/grpc-dart"
echo " tag_pattern: \"^v\""
echo " version: ^$VERSION"
echo "\`\`\`"
echo ""
echo "**Direct tag reference:**"
echo "\`\`\`yaml"
echo "dependencies:"
echo " grpc:"
echo " git:"
echo " url: https://github.com/open-runtime/grpc-dart"
echo " ref: v$VERSION"
echo "\`\`\`"
echo ""
echo "---"
echo ""
echo "$CHANGELOG"
} > /tmp/release_notes.md
gh release create $TAG_NAME \
--repo ${{ github.repository }} \
--title "v$VERSION" \
--notes-file /tmp/release_notes.md
fi
echo "🏷️ Updating PR #$PR_NUM label to autorelease: tagged"
gh pr edit $PR_NUM \
--repo ${{ github.repository }} \
--remove-label "autorelease: pending" \
--add-label "autorelease: tagged" || true
echo "βœ… Processed PR #$PR_NUM"
done < <(echo "$PENDING_PRS" | jq -c '.')
# Output the latest release info (if any was created)
if [ "$RELEASE_CREATED" = "true" ]; then
echo "release_created=true" >> $GITHUB_OUTPUT
echo "tag_name=$LATEST_TAG" >> $GITHUB_OUTPUT
echo "version=$LATEST_VERSION" >> $GITHUB_OUTPUT
else
echo "release_created=false" >> $GITHUB_OUTPUT
fi
# =============================================================================
# Step 2: Run Release Please (after untagged PRs are handled)
# =============================================================================
release-please:
name: πŸ“¦ Release Please
needs: handle-untagged-releases
runs-on: ubuntu-latest
outputs:
release_created: ${{ steps.release.outputs.release_created }}
releases_created: ${{ steps.release.outputs.releases_created }}
tag_name: ${{ steps.release.outputs.tag_name }}
version: ${{ steps.release.outputs.version }}
major: ${{ steps.release.outputs.major }}
minor: ${{ steps.release.outputs.minor }}
patch: ${{ steps.release.outputs.patch }}
sha: ${{ steps.release.outputs.sha }}
pr: ${{ steps.release.outputs.pr }}
prs_created: ${{ steps.release.outputs.prs_created }}
steps:
- name: πŸ“₯ Checkout source code
uses: actions/checkout@v4
with:
fetch-depth: 0
- name: πŸ” Final Check for Untagged PRs (Race Condition Protection)
id: final_check
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
echo "πŸ” Final check for untagged merged release PRs (race condition protection)..."
PENDING_PRS=$(gh pr list --repo ${{ github.repository }} \
--state merged \
--label "autorelease: pending" \
--json number,title,mergeCommit \
--jq '.[] | select(.title | test("^chore: release "))')
if [ -z "$PENDING_PRS" ]; then
echo "βœ… No untagged merged release PRs found"
exit 0
fi
echo "⚠️ Found untagged PRs merged during workflow run - processing now..."
while IFS= read -r pr; do
PR_NUM=$(echo "$pr" | jq -r '.number')
PR_TITLE=$(echo "$pr" | jq -r '.title')
MERGE_SHA=$(echo "$pr" | jq -r '.mergeCommit.oid')
VERSION=$(echo "$PR_TITLE" | sed -E 's/.*([0-9]+\.[0-9]+\.[0-9]+).*/\1/' || echo "")
if [ -z "$VERSION" ]; then
echo "⚠️ Could not extract version from PR #$PR_NUM title: $PR_TITLE"
continue
fi
TAG_NAME="v$VERSION"
echo "πŸ“¦ Processing PR #$PR_NUM: $PR_TITLE"
# Create tag if missing
if ! gh api repos/${{ github.repository }}/git/refs/tags/$TAG_NAME 2>/dev/null; then
echo "🏷️ Creating tag $TAG_NAME at $MERGE_SHA"
gh api repos/${{ github.repository }}/git/refs \
-X POST \
-f ref="refs/tags/$TAG_NAME" \
-f sha="$MERGE_SHA"
fi
# Create release if missing
if ! gh release view $TAG_NAME --repo ${{ github.repository }} 2>/dev/null; then
echo "πŸŽ‰ Creating GitHub Release $TAG_NAME"
CHANGELOG=$(awk -v ver="$VERSION" '
/^## \[/ {
if (found) exit
if ($0 ~ "## \\[" ver "\\]") found=1
}
found { print }
' CHANGELOG.md | tail -n +2)
if [ -z "$CHANGELOG" ]; then
CHANGELOG="Release v$VERSION - See [CHANGELOG.md](https://github.com/${{ github.repository }}/blob/${{ github.ref_name }}/CHANGELOG.md) for details."
fi
{
echo "## πŸ“¦ Installation"
echo ""
echo "**Dart 3.9+ (recommended):**"
echo "\`\`\`yaml"
echo "dependencies:"
echo " grpc:"
echo " git:"
echo " url: https://github.com/open-runtime/grpc-dart"
echo " tag_pattern: \"^v\""
echo " version: ^$VERSION"
echo "\`\`\`"
echo ""
echo "---"
echo ""
echo "$CHANGELOG"
} > /tmp/release_notes.md
gh release create $TAG_NAME \
--repo ${{ github.repository }} \
--title "v$VERSION" \
--notes-file /tmp/release_notes.md
fi
# Update label
gh pr edit $PR_NUM \
--repo ${{ github.repository }} \
--remove-label "autorelease: pending" \
--add-label "autorelease: tagged" || true
echo "βœ… Processed PR #$PR_NUM"
done < <(echo "$PENDING_PRS" | jq -c '.')
echo "βœ… Final check complete - all untagged PRs processed"
- name: πŸ€– Run Release Please
id: release
uses: googleapis/release-please-action@v4
with:
token: ${{ secrets.GITHUB_TOKEN }}
config-file: release-please-config.json
manifest-file: .release-please-manifest.json
target-branch: ${{ github.ref_name }}
- name: πŸ“ Log Release Info
if: ${{ steps.release.outputs.releases_created == 'true' }}
run: |
echo "πŸŽ‰ Release created!"
echo "πŸ“¦ Version: ${{ steps.release.outputs.version }}"
echo "🏷️ Tag: ${{ steps.release.outputs.tag_name }}"
echo "πŸ”— https://github.com/${{ github.repository }}/releases/tag/${{ steps.release.outputs.tag_name }}"
# =============================================================================
# Step 3: Verify Release
# =============================================================================
# =============================================================================
# Verify Release
# =============================================================================
verify-release:
name: βœ… Verify Release
needs: [release-please, handle-untagged-releases]
if: |
needs.release-please.outputs.releases_created == 'true' ||
(needs.handle-untagged-releases.outputs.release_created == 'true' && needs.handle-untagged-releases.result == 'success')
runs-on: ubuntu-latest
steps:
- name: πŸ“₯ Checkout source code
uses: actions/checkout@v4
with:
ref: ${{ needs.release-please.outputs.releases_created == 'true' && needs.release-please.outputs.tag_name || needs.handle-untagged-releases.outputs.tag_name }}
- name: 🎯 Install Dart SDK
uses: dart-lang/setup-dart@v1
with:
sdk: stable
- name: πŸ“š Get dependencies
run: dart pub get
- name: πŸ” Verify package
run: |
echo "πŸ“¦ Verifying package version..."
VERSION=$(grep '^version:' pubspec.yaml | awk '{print $2}')
EXPECTED="${{ needs.release-please.outputs.releases_created == 'true' && needs.release-please.outputs.version || needs.handle-untagged-releases.outputs.version }}"
if [ "$VERSION" != "$EXPECTED" ]; then
echo "❌ Version mismatch: pubspec.yaml has $VERSION, expected $EXPECTED"
exit 1
fi
echo "βœ… Version verified: $VERSION"
- name: 🧹 Run analysis
run: dart analyze --fatal-infos .
- name: πŸ§ͺ Run tests
run: dart test --platform vm
- name: πŸ“‹ Release Summary
run: |
VERSION="${{ needs.release-please.outputs.releases_created == 'true' && needs.release-please.outputs.version || needs.handle-untagged-releases.outputs.version }}"
echo "## πŸŽ‰ Release v$VERSION Verified!" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
echo "### πŸ“¦ Installation" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
echo "**Dart 3.9+ with tag_pattern (recommended):**" >> $GITHUB_STEP_SUMMARY
echo "\`\`\`yaml" >> $GITHUB_STEP_SUMMARY
echo "dependencies:" >> $GITHUB_STEP_SUMMARY
echo " grpc:" >> $GITHUB_STEP_SUMMARY
echo " git:" >> $GITHUB_STEP_SUMMARY
echo " url: https://github.com/open-runtime/grpc-dart" >> $GITHUB_STEP_SUMMARY
echo " tag_pattern: \"^v\"" >> $GITHUB_STEP_SUMMARY
echo " version: ^$VERSION" >> $GITHUB_STEP_SUMMARY
echo "\`\`\`" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
echo "### πŸ”— Links" >> $GITHUB_STEP_SUMMARY
echo "- [GitHub Release](https://github.com/${{ github.repository }}/releases/tag/v$VERSION)" >> $GITHUB_STEP_SUMMARY
echo "- [Changelog](https://github.com/${{ github.repository }}/blob/${{ github.ref_name }}/CHANGELOG.md)" >> $GITHUB_STEP_SUMMARY