This repository was archived by the owner on Mar 17, 2025. It is now read-only.
release: next version 📦 #14
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 Management | |
on: | |
# Using pull_request_target instead of pull_request for security reasons: | |
# - Runs in the context of the BASE repository, not the fork | |
# - Has access to repository secrets | |
# - Can commit changes to protected branches | |
# - SECURITY NOTE: Be careful when checking out PR code with this event type | |
pull_request_target: | |
types: [closed] | |
branches: | |
- main | |
workflow_dispatch: | |
inputs: | |
release_type: | |
description: 'Force a specific release type (leave empty for auto-detection)' | |
required: false | |
type: choice | |
options: | |
- auto | |
- major | |
- minor | |
- patch | |
default: 'auto' | |
target_branch: | |
description: 'Target branch for manual release (usually develop)' | |
required: false | |
default: 'develop' | |
schedule: | |
# Run on the 1st and 15th of each month | |
- cron: '0 0 1,15 * *' | |
jobs: | |
prepare-release: | |
# Only run if: | |
# 1. PR from develop to main is merged, OR | |
# 2. Manually triggered, OR | |
# 3. Scheduled run | |
if: (github.event_name == 'pull_request_target' && github.event.pull_request.merged == true && github.event.pull_request.head.ref == 'develop') || github.event_name == 'workflow_dispatch' || github.event_name == 'schedule' | |
runs-on: ubuntu-latest | |
steps: | |
- name: Set checkout ref | |
id: set_ref | |
run: | | |
if [[ "${{ github.event_name }}" == "pull_request_target" ]]; then | |
echo "ref=main" >> $GITHUB_OUTPUT | |
else | |
echo "ref=${{ github.event.inputs.target_branch || 'develop' }}" >> $GITHUB_OUTPUT | |
fi | |
- name: Checkout code | |
uses: actions/checkout@v3 | |
with: | |
fetch-depth: 0 | |
# For PR events, check out the main branch | |
# For manual/scheduled events, check out the specified target branch or develop | |
ref: ${{ steps.set_ref.outputs.ref }} | |
# Use a personal access token with repo scope for better permissions | |
token: ${{ secrets.REPO_PAT }} | |
- name: Setup Node.js | |
uses: actions/setup-node@v3 | |
with: | |
node-version: '16' | |
- name: Install dependencies | |
run: npm ci | |
- name: Check for changesets | |
id: check_changesets | |
run: | | |
if [ -d ".changesets" ] && [ "$(ls -A .changesets)" ]; then | |
echo "has_changesets=true" >> $GITHUB_OUTPUT | |
else | |
echo "has_changesets=false" >> $GITHUB_OUTPUT | |
echo "No changesets found. Exiting." | |
exit 1 | |
fi | |
- name: Determine version bump | |
id: version_bump | |
run: | | |
if [[ "${{ github.event.inputs.release_type }}" != "auto" && "${{ github.event.inputs.release_type }}" != "" ]]; then | |
# Use the specified release type | |
npm run version:bump -- --type=${{ github.event.inputs.release_type }} | |
else | |
# Auto-detect release type from changesets | |
npm run version:bump | |
fi | |
# Get the new version after bump | |
NEW_VERSION=$(grep -oP "define\('AUTOMATION_TESTS_VERSION', '\K[^']+" constants.php) | |
echo "version=${NEW_VERSION}" >> $GITHUB_OUTPUT | |
- name: Update changelogs | |
run: | | |
# First, check if this is a breaking change release by using our new script | |
# This will automatically update the upgrade notice section if breaking changes are found | |
npm run upgrade-notice:update -- --version=${{ steps.version_bump.outputs.version }} --notes-file=/tmp/release-notes/release_notes.md | |
# Now update the changelogs as usual | |
npm run changelogs:update -- --version=${{ steps.version_bump.outputs.version }} | |
# Generate release notes BEFORE archiving changesets | |
- name: Generate release notes | |
id: release_notes | |
run: | | |
# Create a temporary directory outside the repository | |
mkdir -p /tmp/release-notes | |
# Check if this is a PR from develop to main | |
if [[ "${{ github.event_name }}" == "pull_request_target" && "${{ github.event.pull_request.head.ref }}" == "develop" ]]; then | |
# Extract release notes from the PR body | |
PR_BODY="${{ github.event.pull_request.body }}" | |
# Extract the content between "## Upcoming Changes" and the disclaimer | |
RELEASE_NOTES=$(echo "$PR_BODY" | sed -n '/## Upcoming Changes/,/This PR contains all changes/p' | sed '1d;$d') | |
# Save to file for use in later steps (in temporary directory) | |
echo "$RELEASE_NOTES" > /tmp/release-notes/release_notes.md | |
else | |
# Generate release notes in markdown format for release body | |
# Skip the npm command output by redirecting stderr to /dev/null and grep out the command line | |
npm run release:notes 2>/dev/null | grep -v "^>" > /tmp/release-notes/release_notes.md | |
fi | |
# Check if the file has content | |
if [ ! -s /tmp/release-notes/release_notes.md ]; then | |
# If empty, provide a default message | |
echo "## Release Notes" > /tmp/release-notes/release_notes.md | |
echo "" >> /tmp/release-notes/release_notes.md | |
echo "No changesets found. No changes to release at this time." >> /tmp/release-notes/release_notes.md | |
else | |
# If there is content, replace "Upcoming Changes" with "Release Notes" if present | |
sed -i 's/## Upcoming Changes/## Release Notes/g' /tmp/release-notes/release_notes.md | |
# Remove the note about PR updates if present | |
sed -i '/This PR contains all changes that will be included in the next release/d' /tmp/release-notes/release_notes.md | |
fi | |
# For debugging | |
echo "Generated release notes:" | |
cat /tmp/release-notes/release_notes.md | |
# Set the content for GitHub Actions output | |
# Properly escape the content for GitHub Actions | |
RELEASE_NOTES=$(cat /tmp/release-notes/release_notes.md) | |
RELEASE_NOTES="${RELEASE_NOTES//'%'/'%25'}" | |
RELEASE_NOTES="${RELEASE_NOTES//$'\n'/'%0A'}" | |
RELEASE_NOTES="${RELEASE_NOTES//$'\r'/'%0D'}" | |
echo "content<<EOF" >> $GITHUB_OUTPUT | |
echo "$RELEASE_NOTES" >> $GITHUB_OUTPUT | |
echo "EOF" >> $GITHUB_OUTPUT | |
- name: Commit changes to develop | |
if: github.event_name != 'pull_request_target' | |
uses: stefanzweifel/git-auto-commit-action@v4 | |
with: | |
commit_message: "release: prepare v${{ steps.version_bump.outputs.version }}" | |
file_pattern: "*.php *.md *.txt package.json" | |
# For manual/scheduled events, commit to the specified target branch or develop | |
branch: ${{ github.event.inputs.target_branch || 'develop' }} | |
env: | |
GITHUB_TOKEN: ${{ secrets.REPO_PAT }} | |
- name: Commit changes to main | |
if: github.event_name == 'pull_request_target' | |
run: | | |
git config --global user.name "GitHub Actions" | |
git config --global user.email "actions@github.com" | |
# Check if there are any changes to commit | |
if [[ -n "$(git status --porcelain)" ]]; then | |
echo "Changes detected, committing to main branch" | |
git add *.php *.md *.txt package.json | |
git commit -m "release: prepare v${{ steps.version_bump.outputs.version }}" | |
git push origin main | |
else | |
echo "No changes to commit" | |
fi | |
- name: Create and push tag | |
if: github.event_name == 'pull_request_target' | |
run: | | |
git config --global user.name "GitHub Actions" | |
git config --global user.email "actions@github.com" | |
# Check if tag already exists | |
if git rev-parse "v${{ steps.version_bump.outputs.version }}" >/dev/null 2>&1; then | |
echo "Tag v${{ steps.version_bump.outputs.version }} already exists. Skipping tag creation." | |
else | |
# Create an annotated tag with the changelog as the message | |
git tag -a "v${{ steps.version_bump.outputs.version }}" -m "Release v${{ steps.version_bump.outputs.version }}" | |
# Push the tag | |
git push origin "v${{ steps.version_bump.outputs.version }}" | |
fi | |
- name: Create GitHub Release | |
if: github.event_name == 'pull_request_target' | |
uses: actions/create-release@v1 | |
id: create_release | |
continue-on-error: true | |
env: | |
GITHUB_TOKEN: ${{ secrets.REPO_PAT }} | |
with: | |
tag_name: v${{ steps.version_bump.outputs.version }} | |
release_name: Release v${{ steps.version_bump.outputs.version }} | |
body_path: /tmp/release-notes/release_notes.md | |
draft: false | |
prerelease: false | |
- name: Handle release creation failure | |
if: github.event_name == 'pull_request_target' && steps.create_release.outcome == 'failure' | |
run: | | |
echo "Failed to create release. This could be because the tag already exists." | |
echo "Checking if release exists..." | |
RELEASE_EXISTS=$(curl -s -o /dev/null -w "%{http_code}" \ | |
-H "Authorization: token ${{ secrets.REPO_PAT }}" \ | |
-H "Accept: application/vnd.github.v3+json" \ | |
"https://api.github.com/repos/${{ github.repository }}/releases/tags/v${{ steps.version_bump.outputs.version }}") | |
if [[ "$RELEASE_EXISTS" == "200" ]]; then | |
echo "Release for v${{ steps.version_bump.outputs.version }} already exists. Skipping release creation." | |
else | |
echo "Release creation failed for an unknown reason." | |
exit 1 | |
fi | |
- name: Delete processed changesets | |
if: github.event_name == 'pull_request_target' | |
run: | | |
# Configure Git | |
git config --global user.name "GitHub Actions" | |
git config --global user.email "actions@github.com" | |
# Check if there are any changesets to delete | |
if [ -d ".changesets" ] && [ "$(find .changesets -maxdepth 1 -name "*.md" -type f)" ]; then | |
# List all changesets before deleting | |
echo "Changesets to delete:" | |
find .changesets -maxdepth 1 -name "*.md" -type f -exec basename {} \; | |
# Delete all changesets using find to ensure all files are removed | |
find .changesets -maxdepth 1 -name "*.md" -type f -exec git rm {} \; | |
# Commit the deleted changesets | |
git commit -m "chore: delete changesets after release v${{ steps.version_bump.outputs.version }}" | |
git push origin main | |
echo "Deleted changesets for v${{ steps.version_bump.outputs.version }}" | |
else | |
echo "No changesets found to delete" | |
fi | |
- name: Update develop branch | |
if: github.event_name == 'pull_request_target' | |
run: | | |
# Configure Git | |
git config --global user.name "GitHub Actions" | |
git config --global user.email "actions@github.com" | |
# Fetch all branches | |
git fetch --unshallow || git fetch | |
# Checkout develop branch | |
git checkout develop | |
git pull | |
# Merge main into develop with a descriptive message | |
git merge --no-ff origin/main -m "chore: sync main back to develop after release v${{ steps.version_bump.outputs.version }} [skip ci]" | |
# Push changes to develop | |
git push origin develop | |
echo "Successfully synced main back to develop branch" |