auto-release-on-merge.ymlโข14.4 kB
name: Auto Release on PR Merge
on:
pull_request:
types: [closed]
branches: [main]
jobs:
auto-release:
# Only run if PR was merged (not just closed) and not already handled by dependabot workflow
if: github.event.pull_request.merged == true && github.actor != 'dependabot[bot]'
runs-on: ubuntu-latest
outputs:
new-version: ${{ steps.bump-version.outputs.version }}
should-publish: ${{ steps.should-skip.outputs.should_skip != 'true' }}
permissions:
contents: write
pull-requests: write
actions: write
steps:
- name: Checkout code
uses: actions/checkout@v5
with:
fetch-depth: 0
token: ${{ secrets.GITHUB_TOKEN }}
- name: Setup Node.js
uses: actions/setup-node@v5
with:
node-version: '20'
cache: 'npm'
- name: Configure Git
run: |
git config --local user.email "action@github.com"
git config --local user.name "GitHub Action"
- name: Check auto-release configuration
id: config
run: |
if [[ -f ".github/auto-release.config.json" ]]; then
ENABLED=$(jq -r '.enabled' .github/auto-release.config.json)
SKIP_LABELS=$(jq -r '.skipLabels[]' .github/auto-release.config.json | tr '\n' ' ')
echo "enabled=$ENABLED" >> $GITHUB_OUTPUT
echo "skip_labels=$SKIP_LABELS" >> $GITHUB_OUTPUT
echo "Auto-release enabled: $ENABLED"
echo "Skip labels: $SKIP_LABELS"
else
echo "enabled=true" >> $GITHUB_OUTPUT
echo "skip_labels=" >> $GITHUB_OUTPUT
echo "No config found, using defaults"
fi
- name: Check if release should be skipped
id: should-skip
run: |
PR_LABELS="${{ join(github.event.pull_request.labels.*.name, ' ') }}"
SKIP_LABELS="${{ steps.config.outputs.skip_labels }}"
for label in $SKIP_LABELS; do
if [[ "$PR_LABELS" == *"$label"* ]]; then
echo "should_skip=true" >> $GITHUB_OUTPUT
echo "๐ซ Skipping release due to label: $label"
exit 0
fi
done
echo "should_skip=false" >> $GITHUB_OUTPUT
echo "โ
Release should proceed"
- name: Determine version bump type
id: version-type
if: steps.config.outputs.enabled == 'true' && steps.should-skip.outputs.should_skip == 'false'
env:
PR_TITLE: ${{ github.event.pull_request.title }}
PR_LABELS: ${{ join(github.event.pull_request.labels.*.name, ' ') }}
PR_BODY: ${{ github.event.pull_request.body }}
run: |
echo "Analyzing PR: ${PR_TITLE}"
echo "Labels: ${{ join(github.event.pull_request.labels.*.name, ', ') }}"
# Check for breaking changes or major version indicators
if [[ "${PR_TITLE}" == *"BREAKING"* ]] || [[ "${PR_TITLE}" == *"breaking"* ]] || \
[[ "${PR_LABELS}" == *"breaking"* ]] || [[ "${PR_LABELS}" == *"major"* ]] || \
[[ "${PR_BODY}" == *"BREAKING CHANGE"* ]]; then
VERSION_TYPE="major"
echo "๐จ Major version bump detected (breaking changes)"
# Check for features or minor version indicators
elif [[ "${PR_TITLE}" == *"feat"* ]] || [[ "${PR_TITLE}" == *"feature"* ]] || \
[[ "${PR_LABELS}" == *"feature"* ]] || [[ "${PR_LABELS}" == *"enhancement"* ]] || \
[[ "${PR_LABELS}" == *"minor"* ]]; then
VERSION_TYPE="minor"
echo "โจ Minor version bump detected (new features)"
# Default to patch for fixes, chores, docs, etc.
else
VERSION_TYPE="patch"
echo "๐ง Patch version bump detected (fixes/chores)"
fi
echo "version_type=$VERSION_TYPE" >> $GITHUB_OUTPUT
echo "Version bump type: $VERSION_TYPE"
- name: Bump version
id: bump-version
if: steps.config.outputs.enabled == 'true' && steps.should-skip.outputs.should_skip == 'false'
run: |
echo "Bumping version with type: ${{ steps.version-type.outputs.version_type }}"
# Bump version without creating git tag (we'll do that separately)
npm version ${{ steps.version-type.outputs.version_type }} --no-git-tag-version
NEW_VERSION=$(node -p "require('./package.json').version")
echo "NEW_VERSION=$NEW_VERSION" >> $GITHUB_ENV
echo "version=$NEW_VERSION" >> $GITHUB_OUTPUT
echo "New version: $NEW_VERSION"
- name: Find Release Drafter draft
id: find-draft
if: steps.config.outputs.enabled == 'true' && steps.should-skip.outputs.should_skip == 'false'
run: |
echo "๐ Looking for Release Drafter draft..."
# Get the latest draft release
DRAFT_RELEASE=$(gh api repos/${{ github.repository }}/releases \
--jq '.[] | select(.draft == true) | select(.name | test("v?[0-9]+\\.[0-9]+\\.[0-9]+")) | .[0]' \
| head -1)
if [[ -n "$DRAFT_RELEASE" ]]; then
DRAFT_ID=$(echo "$DRAFT_RELEASE" | jq -r '.id')
DRAFT_TAG=$(echo "$DRAFT_RELEASE" | jq -r '.tag_name')
DRAFT_NAME=$(echo "$DRAFT_RELEASE" | jq -r '.name')
DRAFT_BODY=$(echo "$DRAFT_RELEASE" | jq -r '.body')
echo "draft_found=true" >> $GITHUB_OUTPUT
echo "draft_id=$DRAFT_ID" >> $GITHUB_OUTPUT
echo "draft_tag=$DRAFT_TAG" >> $GITHUB_OUTPUT
echo "draft_name=$DRAFT_NAME" >> $GITHUB_OUTPUT
# Save draft body for later enhancement
echo "$DRAFT_BODY" > draft_body.md
echo "โ
Found Release Drafter draft: $DRAFT_NAME (ID: $DRAFT_ID)"
else
echo "draft_found=false" >> $GITHUB_OUTPUT
echo "โ ๏ธ No Release Drafter draft found, will create basic release notes"
fi
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- name: Update draft with current PR info
id: update-draft
if: steps.config.outputs.enabled == 'true' && steps.should-skip.outputs.should_skip == 'false'
run: |
PR_NUMBER="${{ github.event.pull_request.number }}"
PR_TITLE="${{ github.event.pull_request.title }}"
PR_AUTHOR="${{ github.event.pull_request.user.login }}"
PR_URL="${{ github.event.pull_request.html_url }}"
if [[ "${{ steps.find-draft.outputs.draft_found }}" == "true" ]]; then
echo "๐ Updating Release Drafter content with current PR..."
# Update the draft tag to match our new version
NEW_TAG="v${{ env.NEW_VERSION }}"
NEW_NAME="Release v${{ env.NEW_VERSION }}"
# Get existing draft body and enhance it
DRAFT_BODY=$(cat draft_body.md)
# Create enhanced release notes combining draft structure with current PR
cat > enhanced_release_notes.md << EOF
$DRAFT_BODY
## ๐ Auto-Release Information
This release was automatically triggered by:
- **PR**: $PR_TITLE (#$PR_NUMBER)
- **Author**: @$PR_AUTHOR
- **Type**: ${{ steps.version-type.outputs.version_type }} version bump
**Full PR Details**: [View PR]($PR_URL)
---
๐ค Auto-generated release following PR merge
EOF
echo "updated_tag=$NEW_TAG" >> $GITHUB_OUTPUT
echo "updated_name=$NEW_NAME" >> $GITHUB_OUTPUT
echo "โ
Enhanced release notes prepared"
else
# Fallback: create basic release notes
cat > enhanced_release_notes.md << EOF
# Release v${{ env.NEW_VERSION }}
## Changes
- $PR_TITLE (#$PR_NUMBER) by @$PR_AUTHOR
**Full Changelog**: [View PR]($PR_URL)
---
๐ค Auto-generated release following PR merge
EOF
echo "updated_tag=v${{ env.NEW_VERSION }}" >> $GITHUB_OUTPUT
echo "updated_name=Release v${{ env.NEW_VERSION }}" >> $GITHUB_OUTPUT
echo "โ ๏ธ Created fallback release notes"
fi
echo "Generated enhanced release notes:"
cat enhanced_release_notes.md
- name: Commit version bump
if: steps.config.outputs.enabled == 'true' && steps.should-skip.outputs.should_skip == 'false'
run: |
git add package.json package-lock.json
git commit -m "chore: release v${{ env.NEW_VERSION }}
Auto-release following PR merge:
- ${{ github.event.pull_request.title }} (#${{ github.event.pull_request.number }})
- Author: @${{ github.event.pull_request.user.login }}
- Type: ${{ steps.version-type.outputs.version_type }} version bump
๐ค Generated with [Claude Code](https://claude.ai/code)
Co-Authored-By: Claude <noreply@anthropic.com>"
- name: Push version bump
if: steps.config.outputs.enabled == 'true' && steps.should-skip.outputs.should_skip == 'false'
run: |
git push origin main
- name: Create and push tag
if: steps.config.outputs.enabled == 'true' && steps.should-skip.outputs.should_skip == 'false'
run: |
TAG_NAME="v${{ env.NEW_VERSION }}"
echo "Creating tag: $TAG_NAME"
# Create annotated tag with release info
git tag -a "$TAG_NAME" -m "Release $TAG_NAME
Auto-release following PR merge:
- ${{ github.event.pull_request.title }}
- PR #${{ github.event.pull_request.number }} by @${{ github.event.pull_request.user.login }}
- Type: ${{ steps.version-type.outputs.version_type }} version bump
This tag will trigger:
- AI-enhanced release notes generation
- NPM package publishing
- Documentation updates"
git push origin "$TAG_NAME"
echo "โ
Tag $TAG_NAME created and pushed"
echo "๐ This will trigger AI release notes generation and NPM publishing"
- name: Publish Release (from Draft or New)
if: steps.config.outputs.enabled == 'true' && steps.should-skip.outputs.should_skip == 'false'
run: |
if [[ "${{ steps.find-draft.outputs.draft_found }}" == "true" ]]; then
echo "๐ค Publishing existing Release Drafter draft..."
# Update and publish the existing draft
gh api repos/${{ github.repository }}/releases/${{ steps.find-draft.outputs.draft_id }} \
--method PATCH \
--field tag_name="${{ steps.update-draft.outputs.updated_tag }}" \
--field name="${{ steps.update-draft.outputs.updated_name }}" \
--field body="$(cat enhanced_release_notes.md)" \
--field draft=false \
--field prerelease=false
echo "โ
Successfully published Release Drafter draft as v${{ env.NEW_VERSION }}"
else
echo "๐ค Creating new release..."
# Create a new release
gh api repos/${{ github.repository }}/releases \
--method POST \
--field tag_name="v${{ env.NEW_VERSION }}" \
--field name="Release v${{ env.NEW_VERSION }}" \
--field body="$(cat enhanced_release_notes.md)" \
--field draft=false \
--field prerelease=false
echo "โ
Successfully created new release v${{ env.NEW_VERSION }}"
fi
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- name: Summary
if: steps.config.outputs.enabled == 'true' && steps.should-skip.outputs.should_skip == 'false'
run: |
echo "## ๐ Auto-release completed!" >> $GITHUB_STEP_SUMMARY
echo "**Version**: v${{ env.NEW_VERSION }}" >> $GITHUB_STEP_SUMMARY
echo "**Type**: ${{ steps.version-type.outputs.version_type }} bump" >> $GITHUB_STEP_SUMMARY
echo "**Triggered by**: ${{ github.event.pull_request.title }} (#${{ github.event.pull_request.number }})" >> $GITHUB_STEP_SUMMARY
echo "**Author**: @${{ github.event.pull_request.user.login }}" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
if [[ "${{ steps.find-draft.outputs.draft_found }}" == "true" ]]; then
echo "**Release Method**: ๐ Published Release Drafter draft" >> $GITHUB_STEP_SUMMARY
else
echo "**Release Method**: ๐ Created new release" >> $GITHUB_STEP_SUMMARY
fi
echo "" >> $GITHUB_STEP_SUMMARY
echo "### Next Steps:" >> $GITHUB_STEP_SUMMARY
echo "- โ
Tag v${{ env.NEW_VERSION }} created" >> $GITHUB_STEP_SUMMARY
echo "- โ
Release published" >> $GITHUB_STEP_SUMMARY
echo "- ๐ AI release notes enhancement will trigger" >> $GITHUB_STEP_SUMMARY
echo "- ๐ฆ NPM publishing workflow will trigger" >> $GITHUB_STEP_SUMMARY
echo "- ๐ Documentation will be updated" >> $GITHUB_STEP_SUMMARY
- name: Skipped Summary
if: steps.config.outputs.enabled == 'false' || steps.should-skip.outputs.should_skip == 'true'
run: |
echo "## โญ๏ธ Auto-release skipped" >> $GITHUB_STEP_SUMMARY
if [[ "${{ steps.config.outputs.enabled }}" == "false" ]]; then
echo "**Reason**: Auto-release disabled in configuration" >> $GITHUB_STEP_SUMMARY
else
echo "**Reason**: PR has skip label" >> $GITHUB_STEP_SUMMARY
fi
echo "**PR**: ${{ github.event.pull_request.title }} (#${{ github.event.pull_request.number }})" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
echo "To enable auto-release, update `.github/auto-release.config.json` or remove skip labels." >> $GITHUB_STEP_SUMMARY
# Trigger NPM publishing
publish-npm:
needs: auto-release
if: github.event.pull_request.merged == true && github.event.pull_request.user.login != 'dependabot[bot]' && needs.auto-release.outputs.should-publish == 'true'
uses: ./.github/workflows/publish.yml
with:
version: v${{ needs.auto-release.outputs.new-version }}
skip_tests: false
dry_run: false
secrets:
NPM_TOKEN: ${{ secrets.NPM_TOKEN }}