This repository was archived by the owner on Mar 17, 2025. It is now read-only.
chore: add workflow visualization #19
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 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' | |
# Add caching for npm dependencies | |
- name: Cache npm dependencies | |
uses: actions/cache@v3 | |
id: npm-cache | |
with: | |
path: | | |
**/node_modules | |
~/.npm | |
key: ${{ runner.os }}-npm-${{ hashFiles('**/package-lock.json') }} | |
restore-keys: | | |
${{ runner.os }}-npm- | |
- name: Install dependencies | |
# Only run full install if cache miss | |
if: steps.npm-cache.outputs.cache-hit != 'true' | |
run: npm ci | |
# If cache hit, just check for any missing dependencies | |
- name: Check dependencies | |
if: steps.npm-cache.outputs.cache-hit == 'true' | |
run: npm ci --prefer-offline --no-audit | |
- 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 | |
# Generate release notes in parallel with version bump | |
- name: Generate release notes | |
id: release_notes | |
run: | | |
# Create a temporary directory outside the repository with proper permissions | |
mkdir -p /tmp/release-notes | |
chmod 777 /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 | |
# Ensure the temporary directory and files are readable by subsequent steps | |
chmod -R 755 /tmp/release-notes | |
# List the contents of the directory to verify the file exists | |
echo "Verifying release notes file exists:" | |
ls -la /tmp/release-notes/ | |
# Copy the release notes to a more permanent location in the workspace | |
# This ensures it's available even if the temporary directory is cleaned up | |
cp /tmp/release-notes/release_notes.md $GITHUB_WORKSPACE/release_notes.md | |
# Run version bump in parallel with release notes generation | |
- 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 | |
# Update changelogs after both release notes and version bump are complete | |
- 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 }} | |
- 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: ${{ github.workspace }}/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." | |
# Check for rate limiting | |
RATE_LIMIT=$(curl -s \ | |
-H "Authorization: token ${{ secrets.REPO_PAT }}" \ | |
-H "Accept: application/vnd.github.v3+json" \ | |
"https://api.github.com/rate_limit") | |
REMAINING=$(echo "$RATE_LIMIT" | jq -r '.resources.core.remaining') | |
RESET_TIME=$(echo "$RATE_LIMIT" | jq -r '.resources.core.reset') | |
RESET_TIME_HUMAN=$(date -d @$RESET_TIME) | |
if [[ "$REMAINING" -le 10 ]]; then | |
echo "::warning::GitHub API rate limit is low: $REMAINING requests remaining. Resets at $RESET_TIME_HUMAN" | |
fi | |
# Try again with a different approach | |
echo "Attempting to create release using GitHub CLI..." | |
# Install GitHub CLI if not already installed | |
if ! command -v gh &> /dev/null; then | |
echo "Installing GitHub CLI..." | |
curl -fsSL https://cli.github.com/packages/githubcli-archive-keyring.gpg | sudo dd of=/usr/share/keyrings/githubcli-archive-keyring.gpg | |
echo "deb [arch=$(dpkg --print-architecture) signed-by=/usr/share/keyrings/githubcli-archive-keyring.gpg] https://cli.github.com/packages stable main" | sudo tee /etc/apt/sources.list.d/github-cli.list > /dev/null | |
sudo apt update | |
sudo apt install gh | |
fi | |
# Authenticate with GitHub CLI | |
echo "${{ secrets.REPO_PAT }}" | gh auth login --with-token | |
# Verify the release notes file exists | |
if [ -f "${{ github.workspace }}/release_notes.md" ]; then | |
echo "Using release notes from workspace:" | |
cat "${{ github.workspace }}/release_notes.md" | |
# Try to create the release with GitHub CLI | |
if gh release create "v${{ steps.version_bump.outputs.version }}" \ | |
--title "Release v${{ steps.version_bump.outputs.version }}" \ | |
--notes-file "${{ github.workspace }}/release_notes.md"; then | |
echo "Successfully created release using GitHub CLI" | |
else | |
echo "::error::Failed to create release using GitHub CLI with notes file." | |
# Try one more time with inline notes from the output | |
echo "Trying with inline notes..." | |
if gh release create "v${{ steps.version_bump.outputs.version }}" \ | |
--title "Release v${{ steps.version_bump.outputs.version }}" \ | |
--notes "${{ steps.release_notes.outputs.content }}"; then | |
echo "Successfully created release using GitHub CLI with inline notes" | |
else | |
echo "::error::Failed to create release using all methods. Please check logs for details." | |
exit 1 | |
fi | |
fi | |
else | |
echo "Release notes file not found in workspace. Using inline notes..." | |
# Try with inline notes | |
if gh release create "v${{ steps.version_bump.outputs.version }}" \ | |
--title "Release v${{ steps.version_bump.outputs.version }}" \ | |
--notes "${{ steps.release_notes.outputs.content }}"; then | |
echo "Successfully created release using GitHub CLI with inline notes" | |
else | |
echo "::error::Failed to create release using all methods. Please check logs for details." | |
exit 1 | |
fi | |
fi | |
fi | |
# Add a cleanup step to ensure temporary files are removed | |
# This step runs AFTER the GitHub release is created | |
- name: Cleanup temporary files | |
if: always() | |
run: | | |
# Check if the temporary directory exists before attempting to remove it | |
if [ -d "/tmp/release-notes" ]; then | |
echo "Cleaning up temporary files..." | |
rm -rf /tmp/release-notes | |
echo "Temporary files removed." | |
else | |
echo "No temporary files to clean up." | |
fi | |
# Also remove the release notes file from the workspace | |
# This prevents it from being versioned in the repository | |
if [ -f "$GITHUB_WORKSPACE/release_notes.md" ]; then | |
echo "Removing release notes file from workspace..." | |
rm -f "$GITHUB_WORKSPACE/release_notes.md" | |
echo "Workspace release notes file removed." | |
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 the .changesets directory exists and contains files | |
if [ -d ".changesets" ]; then | |
# List all changesets before deleting (for logging purposes) | |
echo "Changesets found in directory:" | |
ls -la .changesets/ | |
# Remove all .md files in the .changesets directory | |
find .changesets -type f -name "*.md" -exec rm -f {} \; | |
# Check if any files were removed | |
if [ $? -eq 0 ]; then | |
echo "Changesets removed successfully" | |
# Stage the deletions | |
git add -A .changesets/ | |
# Also make sure the release_notes.md file is not staged | |
git reset -- release_notes.md || true | |
# 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 "Error removing changesets" | |
exit 1 | |
fi | |
else | |
echo "No .changesets directory found" | |
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" |