chore(deps): bump the ghactions-all group with 2 updates #118
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
# ------------------------------------------------------------------------------------ | |
# Pull Request Management Workflow | |
# | |
# Purpose: Comprehensive PR lifecycle management including automated labeling, | |
# assignments, size analysis, welcomes for new contributors, and cleanup | |
# tasks when PRs are closed. All configuration is centralized in .env.shared. | |
# | |
# Configuration: All settings are loaded from .github/.env.shared for centralized | |
# management across all workflows. | |
# | |
# Triggers: Pull request events (opened, reopened, ready for review, closed) | |
# | |
# Features: | |
# - Automatic labeling based on branch prefix and PR title | |
# - Default assignee management | |
# - Welcome messages for first-time contributors | |
# - PR size analysis and labeling | |
# - Cache cleanup on PR close | |
# - Branch deletion after merge | |
# | |
# Maintainer: @mrz1836 | |
# | |
# ------------------------------------------------------------------------------------ | |
name: PR Management | |
# ———————————————————————————————————————————————————————————————— | |
# Trigger Configuration | |
# ———————————————————————————————————————————————————————————————— | |
on: | |
pull_request: | |
types: [opened, reopened, ready_for_review, closed] | |
# ———————————————————————————————————————————————————————————————— | |
# Permissions | |
# ———————————————————————————————————————————————————————————————— | |
permissions: | |
contents: read | |
# ———————————————————————————————————————————————————————————————— | |
# Concurrency Control | |
# ———————————————————————————————————————————————————————————————— | |
concurrency: | |
group: ${{ github.workflow }}-${{ github.event.pull_request.number }} | |
cancel-in-progress: true | |
# ———————————————————————————————————————————————————————————————— | |
# Environment Variables | |
# ———————————————————————————————————————————————————————————————— | |
# Note: Configuration variables are loaded from .github/.env.shared | |
jobs: | |
# ---------------------------------------------------------------------------------- | |
# Load Environment Variables from .env.shared | |
# ---------------------------------------------------------------------------------- | |
load-env: | |
name: 🌍 Load Environment Variables | |
runs-on: ubuntu-latest | |
outputs: | |
env-json: ${{ steps.load-env.outputs.env-json }} | |
steps: | |
# ———————————————————————————————————————————————————————————————— | |
# Check out code to access env file | |
# ———————————————————————————————————————————————————————————————— | |
- name: 📥 Checkout code (sparse) | |
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 | |
with: | |
sparse-checkout: | | |
.github/.env.shared | |
.github/actions/load-env | |
# ———————————————————————————————————————————————————————————————— | |
# Load and parse environment file | |
# ———————————————————————————————————————————————————————————————— | |
- name: 🌍 Load environment variables | |
uses: ./.github/actions/load-env | |
id: load-env | |
# ---------------------------------------------------------------------------------- | |
# Apply Labels Based on Branch and Title | |
# ---------------------------------------------------------------------------------- | |
apply-labels: | |
name: 🏷️ Apply Labels | |
needs: [load-env] | |
runs-on: ubuntu-latest | |
permissions: | |
contents: read | |
pull-requests: write | |
if: github.event.action != 'closed' | |
outputs: | |
labels-applied: ${{ steps.apply-labels.outputs.labels-applied }} | |
steps: | |
# ———————————————————————————————————————————————————————————————— | |
# Extract configuration from env-json | |
# ———————————————————————————————————————————————————————————————— | |
- name: 🔧 Extract configuration | |
id: config | |
env: | |
ENV_JSON: ${{ needs.load-env.outputs.env-json }} | |
run: | | |
echo "📋 Extracting PR management configuration from environment..." | |
# Extract all needed variables | |
SKIP_BOT_USERS=$(echo "$ENV_JSON" | jq -r '.PR_MANAGEMENT_SKIP_BOT_USERS') | |
APPLY_TYPE_LABELS=$(echo "$ENV_JSON" | jq -r '.PR_MANAGEMENT_APPLY_TYPE_LABELS') | |
# Set as environment variables for all subsequent steps | |
echo "SKIP_BOT_USERS=$SKIP_BOT_USERS" >> $GITHUB_ENV | |
echo "APPLY_TYPE_LABELS=$APPLY_TYPE_LABELS" >> $GITHUB_ENV | |
# Log configuration | |
echo "🔍 Configuration loaded:" | |
echo " 🤖 Skip bot users: $SKIP_BOT_USERS" | |
echo " 🏷️ Apply type labels: $APPLY_TYPE_LABELS" | |
# ———————————————————————————————————————————————————————————————— | |
# Apply labels based on branch and title patterns | |
# ———————————————————————————————————————————————————————————————— | |
- name: 🏷️ Apply labels based on patterns | |
id: apply-labels | |
if: env.APPLY_TYPE_LABELS == 'true' | |
uses: actions/github-script@60a0d83039c74a4aee543508d2ffcb1c3799cdea # v7.0.1 | |
with: | |
github-token: ${{ secrets.GITHUB_TOKEN }} | |
script: | | |
const branch = context.payload.pull_request.head.ref; | |
const prTitle = context.payload.pull_request.title; | |
const prNumber = context.payload.pull_request.number; | |
const prAuthor = context.payload.pull_request.user.login; | |
// Check if PR author is a bot to skip | |
const skipBotUsers = process.env.SKIP_BOT_USERS.split(',').map(u => u.trim()); | |
if (skipBotUsers.includes(prAuthor)) { | |
console.log(`⏭️ Skipping label application for bot user: ${prAuthor}`); | |
core.setOutput('labels-applied', '[]'); | |
return; | |
} | |
console.log(`🔍 Processing PR #${prNumber}`); | |
console.log(`🌿 Branch: ${branch}`); | |
console.log(`📝 Title: ${prTitle}`); | |
console.log('════════════════════════════════════════════════════════════════'); | |
// Branch-based label rules (prefix matching) | |
const branchRules = [ | |
{ pattern: /^(bug)?fix\//i, labels: ['bug-P3'] }, | |
{ pattern: /^chore\//i, labels: ['chore', 'update'] }, | |
{ pattern: /^deps\//i, labels: ['chore', 'dependencies'] }, | |
{ pattern: /^docs\//i, labels: ['documentation', 'update'] }, | |
{ pattern: /^feat(ure)?\//i, labels: ['feature'] }, | |
{ pattern: /^hotfix\//i, labels: ['hot-fix'] }, | |
{ pattern: /^idea\//i, labels: ['idea'] }, | |
{ pattern: /^proto(type)?\//i, labels: ['prototype', 'idea'] }, | |
{ pattern: /^question\//i, labels: ['question'] }, | |
{ pattern: /^refactor\//i, labels: ['refactor'] }, | |
{ pattern: /^test\//i, labels: ['test'] }, | |
]; | |
// Title-based label rules (keyword matching) | |
const titleRules = [ | |
{ pattern: /\b(fix|bug|error|issue|problem|broken)\b/i, labels: ['bug-P3'] }, | |
{ pattern: /\b(chore|cleanup|maintenance|housekeeping)\b/i, labels: ['chore', 'update'] }, | |
{ pattern: /\b(deps?|dependencies|dependency|upgrade|update.*deps?)\b/i, labels: ['chore', 'dependencies'] }, | |
{ pattern: /\b(docs?|documentation|readme|guide|manual)\b/i, labels: ['documentation', 'update'] }, | |
{ pattern: /\b(feat|feature|add|new|implement|enhancement)\b/i, labels: ['feature'] }, | |
{ pattern: /\b(hotfix|urgent|critical|emergency)\b/i, labels: ['hot-fix'] }, | |
{ pattern: /\b(idea|proposal|suggestion|concept)\b/i, labels: ['idea'] }, | |
{ pattern: /\b(prototype|proto|draft|experiment|poc|proof.of.concept)\b/i, labels: ['prototype', 'idea'] }, | |
{ pattern: /\b(question|help|how.to|unclear|clarification)\b/i, labels: ['question'] }, | |
{ pattern: /\b(refactor|restructure|reorganize|cleanup|improve)\b/i, labels: ['refactor'] }, | |
{ pattern: /\b(test|testing|spec|coverage|unit.test|integration.test)\b/i, labels: ['test'] }, | |
{ pattern: /\b(security|vulnerability|CVE|exploit|patch)\b/i, labels: ['security'] }, | |
{ pattern: /\b(performance|perf|optimization|optimize|speed|slow)\b/i, labels: ['performance'] }, | |
{ pattern: /\b(breaking.change|breaking|major|incompatible)\b/i, labels: ['requires-manual-review'] }, | |
{ pattern: /\b(wip|work.in.progress|draft|incomplete)\b/i, labels: ['work-in-progress'] }, | |
]; | |
// Collect labels from both branch and title | |
const labelsToAdd = new Set(); // Use Set to avoid duplicates | |
// Check branch patterns | |
console.log('🌿 Checking branch patterns...'); | |
for (const rule of branchRules) { | |
if (rule.pattern.test(branch)) { | |
rule.labels.forEach(label => labelsToAdd.add(label)); | |
console.log(` ✅ Matched ${rule.pattern} → adding: ${rule.labels.join(', ')}`); | |
} | |
} | |
// Check title patterns | |
console.log('📝 Checking title patterns...'); | |
for (const rule of titleRules) { | |
if (rule.pattern.test(prTitle)) { | |
rule.labels.forEach(label => labelsToAdd.add(label)); | |
console.log(` ✅ Matched ${rule.pattern} → adding: ${rule.labels.join(', ')}`); | |
} | |
} | |
const finalLabels = Array.from(labelsToAdd); | |
if (finalLabels.length === 0) { | |
console.log('ℹ️ No patterns matched in branch or title'); | |
core.setOutput('labels-applied', '[]'); | |
return; | |
} | |
console.log('════════════════════════════════════════════════════════════════'); | |
console.log(`📋 Total labels to apply: ${finalLabels.join(', ')}`); | |
// Get existing labels to avoid duplicates | |
try { | |
const { data: existingLabels } = await github.rest.issues.listLabelsOnIssue({ | |
owner: context.repo.owner, | |
repo: context.repo.repo, | |
issue_number: prNumber, | |
}); | |
const existingLabelNames = existingLabels.map(label => label.name); | |
const newLabels = finalLabels.filter(label => !existingLabelNames.includes(label)); | |
if (newLabels.length > 0) { | |
await github.rest.issues.addLabels({ | |
owner: context.repo.owner, | |
repo: context.repo.repo, | |
issue_number: prNumber, | |
labels: newLabels, | |
}); | |
console.log(`✅ Added new labels: ${newLabels.join(', ')}`); | |
if (existingLabelNames.length > 0) { | |
console.log(`ℹ️ Labels already present: ${existingLabelNames.join(', ')}`); | |
} | |
core.setOutput('labels-applied', JSON.stringify(newLabels)); | |
} else { | |
console.log('ℹ️ All matching labels already present, no changes needed'); | |
console.log(` 📋 Existing labels: ${existingLabelNames.join(', ')}`); | |
core.setOutput('labels-applied', '[]'); | |
} | |
} catch (error) { | |
console.error(`❌ Failed to apply labels: ${error.message}`); | |
core.setOutput('labels-applied', '[]'); | |
// Don't fail the entire workflow for label issues | |
} | |
# ---------------------------------------------------------------------------------- | |
# Assign Default Assignee | |
# ---------------------------------------------------------------------------------- | |
assign-assignee: | |
name: 👤 Assign Default Assignee | |
needs: [load-env] | |
runs-on: ubuntu-latest | |
permissions: | |
contents: read | |
pull-requests: write | |
if: | | |
github.event.action != 'closed' && | |
github.event.pull_request.head.repo.owner.login == github.repository_owner | |
outputs: | |
assignee-added: ${{ steps.assign.outputs.assignee-added }} | |
steps: | |
# ———————————————————————————————————————————————————————————————— | |
# Extract configuration from env-json | |
# ———————————————————————————————————————————————————————————————— | |
- name: 🔧 Extract configuration | |
id: config | |
env: | |
ENV_JSON: ${{ needs.load-env.outputs.env-json }} | |
run: | | |
echo "📋 Extracting PR management configuration from environment..." | |
# Extract all needed variables | |
DEFAULT_ASSIGNEE=$(echo "$ENV_JSON" | jq -r '.PR_MANAGEMENT_DEFAULT_ASSIGNEE') | |
SKIP_BOT_USERS=$(echo "$ENV_JSON" | jq -r '.PR_MANAGEMENT_SKIP_BOT_USERS') | |
# Set as environment variables for all subsequent steps | |
echo "DEFAULT_ASSIGNEE=$DEFAULT_ASSIGNEE" >> $GITHUB_ENV | |
echo "SKIP_BOT_USERS=$SKIP_BOT_USERS" >> $GITHUB_ENV | |
# Log configuration | |
echo "🔍 Configuration loaded:" | |
echo " 👤 Default assignee: $DEFAULT_ASSIGNEE" | |
echo " 🤖 Skip bot users: $SKIP_BOT_USERS" | |
# ———————————————————————————————————————————————————————————————— | |
# Assign default assignee if needed | |
# ———————————————————————————————————————————————————————————————— | |
- name: 👤 Assign default assignee | |
id: assign | |
uses: actions/github-script@60a0d83039c74a4aee543508d2ffcb1c3799cdea # v7.0.1 | |
with: | |
github-token: ${{ secrets.GITHUB_TOKEN }} | |
script: | | |
const pr = context.payload.pull_request; | |
const prAuthor = pr.user.login; | |
const assignees = pr.assignees || []; | |
// Check if PR author is a bot to skip | |
const skipBotUsers = process.env.SKIP_BOT_USERS.split(',').map(u => u.trim()); | |
if (skipBotUsers.includes(prAuthor)) { | |
console.log(`⏭️ Skipping assignment for bot user: ${prAuthor}`); | |
core.setOutput('assignee-added', 'false'); | |
return; | |
} | |
if (assignees.length > 0) { | |
console.log(`ℹ️ PR already has ${assignees.length} assignee(s): ${assignees.map(a => a.login).join(', ')}`); | |
console.log('⏭️ Skipping default assignment'); | |
core.setOutput('assignee-added', 'false'); | |
return; | |
} | |
try { | |
await github.rest.issues.addAssignees({ | |
owner: context.repo.owner, | |
repo: context.repo.repo, | |
issue_number: pr.number, | |
assignees: [process.env.DEFAULT_ASSIGNEE], | |
}); | |
console.log(`✅ Assigned PR to @${process.env.DEFAULT_ASSIGNEE}`); | |
core.setOutput('assignee-added', 'true'); | |
} catch (error) { | |
console.error(`❌ Failed to assign PR: ${error.message}`); | |
core.setOutput('assignee-added', 'false'); | |
// Don't fail the workflow for assignment issues | |
} | |
# ---------------------------------------------------------------------------------- | |
# Welcome New Contributors | |
# ---------------------------------------------------------------------------------- | |
welcome-contributor: | |
name: 👋 Welcome New Contributor | |
needs: [load-env] | |
runs-on: ubuntu-latest | |
permissions: | |
contents: read | |
pull-requests: write | |
if: | | |
github.event.action == 'opened' && | |
contains(fromJSON('["FIRST_TIMER", "FIRST_TIME_CONTRIBUTOR"]'), github.event.pull_request.author_association) && | |
github.event.pull_request.head.repo.owner.login == github.repository_owner | |
outputs: | |
welcomed: ${{ steps.welcome.outputs.welcomed }} | |
steps: | |
# ———————————————————————————————————————————————————————————————— | |
# Extract configuration from env-json | |
# ———————————————————————————————————————————————————————————————— | |
- name: 🔧 Extract configuration | |
id: config | |
env: | |
ENV_JSON: ${{ needs.load-env.outputs.env-json }} | |
run: | | |
echo "📋 Extracting PR management configuration from environment..." | |
# Extract all needed variables | |
WELCOME_FIRST_TIME=$(echo "$ENV_JSON" | jq -r '.PR_MANAGEMENT_WELCOME_FIRST_TIME') | |
SKIP_BOT_USERS=$(echo "$ENV_JSON" | jq -r '.PR_MANAGEMENT_SKIP_BOT_USERS') | |
# Set as environment variables for all subsequent steps | |
echo "WELCOME_FIRST_TIME=$WELCOME_FIRST_TIME" >> $GITHUB_ENV | |
echo "SKIP_BOT_USERS=$SKIP_BOT_USERS" >> $GITHUB_ENV | |
# Log configuration | |
echo "🔍 Configuration loaded:" | |
echo " 👋 Welcome first-time contributors: $WELCOME_FIRST_TIME" | |
echo " 🤖 Skip bot users: $SKIP_BOT_USERS" | |
# ———————————————————————————————————————————————————————————————— | |
# Post welcome message | |
# ———————————————————————————————————————————————————————————————— | |
- name: 👋 Welcome new contributor | |
id: welcome | |
if: env.WELCOME_FIRST_TIME == 'true' | |
uses: actions/github-script@60a0d83039c74a4aee543508d2ffcb1c3799cdea # v7.0.1 | |
with: | |
github-token: ${{ secrets.GITHUB_TOKEN }} | |
script: | | |
const author = context.payload.pull_request.user.login; | |
const repoName = context.repo.repo; | |
const repoOwner = context.repo.owner; | |
// Check if PR author is a bot to skip | |
const skipBotUsers = process.env.SKIP_BOT_USERS.split(',').map(u => u.trim()); | |
if (skipBotUsers.includes(author)) { | |
console.log(`⏭️ Skipping welcome for bot user: ${author}`); | |
core.setOutput('welcomed', 'false'); | |
return; | |
} | |
const welcomeMessage = `## 👋 Welcome, @${author}! | |
Thank you for opening your first pull request in **${repoOwner}/${repoName}**! 🎉 | |
Here's what happens next: | |
- 🤖 Automated tests will run to check your changes | |
- 👀 A maintainer will review your contribution | |
- 💬 You might receive feedback or suggestions | |
- ✅ Once approved, your PR will be merged | |
**Need help?** Feel free to ask questions in the comments below. | |
Thanks for contributing to the project! 🚀`; | |
try { | |
await github.rest.issues.createComment({ | |
owner: context.repo.owner, | |
repo: context.repo.repo, | |
issue_number: context.payload.pull_request.number, | |
body: welcomeMessage, | |
}); | |
console.log(`✅ Posted welcome comment for new contributor @${author}`); | |
core.setOutput('welcomed', 'true'); | |
} catch (error) { | |
console.error(`❌ Failed to post welcome comment: ${error.message}`); | |
core.setOutput('welcomed', 'false'); | |
} | |
# ---------------------------------------------------------------------------------- | |
# Analyze PR Size | |
# ---------------------------------------------------------------------------------- | |
analyze-size: | |
name: 📏 Analyze PR Size | |
needs: [load-env] | |
runs-on: ubuntu-latest | |
permissions: | |
contents: read | |
pull-requests: write | |
if: github.event.action == 'opened' | |
outputs: | |
size-label: ${{ steps.analyze.outputs.size-label }} | |
total-changes: ${{ steps.analyze.outputs.total-changes }} | |
steps: | |
# ———————————————————————————————————————————————————————————————— | |
# Extract configuration from env-json | |
# ———————————————————————————————————————————————————————————————— | |
- name: 🔧 Extract configuration | |
id: config | |
env: | |
ENV_JSON: ${{ needs.load-env.outputs.env-json }} | |
run: | | |
echo "📋 Extracting PR management configuration from environment..." | |
# Extract all needed variables | |
APPLY_SIZE_LABELS=$(echo "$ENV_JSON" | jq -r '.PR_MANAGEMENT_APPLY_SIZE_LABELS') | |
SIZE_XS=$(echo "$ENV_JSON" | jq -r '.PR_MANAGEMENT_SIZE_XS_THRESHOLD') | |
SIZE_S=$(echo "$ENV_JSON" | jq -r '.PR_MANAGEMENT_SIZE_S_THRESHOLD') | |
SIZE_M=$(echo "$ENV_JSON" | jq -r '.PR_MANAGEMENT_SIZE_M_THRESHOLD') | |
SIZE_L=$(echo "$ENV_JSON" | jq -r '.PR_MANAGEMENT_SIZE_L_THRESHOLD') | |
# Set as environment variables for all subsequent steps | |
echo "APPLY_SIZE_LABELS=$APPLY_SIZE_LABELS" >> $GITHUB_ENV | |
echo "SIZE_XS=$SIZE_XS" >> $GITHUB_ENV | |
echo "SIZE_S=$SIZE_S" >> $GITHUB_ENV | |
echo "SIZE_M=$SIZE_M" >> $GITHUB_ENV | |
echo "SIZE_L=$SIZE_L" >> $GITHUB_ENV | |
# Log configuration | |
echo "🔍 Configuration loaded:" | |
echo " 📏 Apply size labels: $APPLY_SIZE_LABELS" | |
echo " 📊 Size thresholds: XS≤$SIZE_XS, S≤$SIZE_S, M≤$SIZE_M, L≤$SIZE_L, XL>$SIZE_L" | |
# ———————————————————————————————————————————————————————————————— | |
# Analyze and label PR size | |
# ———————————————————————————————————————————————————————————————— | |
- name: 📏 Add size label | |
id: analyze | |
if: env.APPLY_SIZE_LABELS == 'true' | |
uses: actions/github-script@60a0d83039c74a4aee543508d2ffcb1c3799cdea # v7.0.1 | |
with: | |
github-token: ${{ secrets.GITHUB_TOKEN }} | |
script: | | |
const pr = context.payload.pull_request; | |
const additions = pr.additions || 0; | |
const deletions = pr.deletions || 0; | |
const totalChanges = additions + deletions; | |
console.log(`📊 PR Statistics:`); | |
console.log(` ➕ Additions: ${additions}`); | |
console.log(` ➖ Deletions: ${deletions}`); | |
console.log(` 📈 Total changes: ${totalChanges}`); | |
// Determine size label based on configurable thresholds | |
let sizeLabel = ''; | |
const thresholds = { | |
XS: parseInt(process.env.SIZE_XS), | |
S: parseInt(process.env.SIZE_S), | |
M: parseInt(process.env.SIZE_M), | |
L: parseInt(process.env.SIZE_L) | |
}; | |
if (totalChanges <= thresholds.XS) { | |
sizeLabel = 'size/XS'; | |
} else if (totalChanges <= thresholds.S) { | |
sizeLabel = 'size/S'; | |
} else if (totalChanges <= thresholds.M) { | |
sizeLabel = 'size/M'; | |
} else if (totalChanges <= thresholds.L) { | |
sizeLabel = 'size/L'; | |
} else { | |
sizeLabel = 'size/XL'; | |
} | |
try { | |
await github.rest.issues.addLabels({ | |
owner: context.repo.owner, | |
repo: context.repo.repo, | |
issue_number: pr.number, | |
labels: [sizeLabel], | |
}); | |
console.log(`✅ Added size label: ${sizeLabel}`); | |
core.setOutput('size-label', sizeLabel); | |
core.setOutput('total-changes', totalChanges.toString()); | |
} catch (error) { | |
console.error(`❌ Failed to add size label: ${error.message}`); | |
core.setOutput('size-label', ''); | |
core.setOutput('total-changes', totalChanges.toString()); | |
} | |
# ---------------------------------------------------------------------------------- | |
# Clean Runner Cache (on PR close) | |
# ---------------------------------------------------------------------------------- | |
clean-cache: | |
name: 🧹 Clean Runner Cache | |
needs: [load-env] | |
runs-on: ubuntu-latest | |
permissions: | |
actions: write | |
contents: read | |
if: github.event.action == 'closed' | |
outputs: | |
caches-cleaned: ${{ steps.clean.outputs.caches-cleaned }} | |
steps: | |
# ———————————————————————————————————————————————————————————————— | |
# Extract configuration from env-json | |
# ———————————————————————————————————————————————————————————————— | |
- name: 🔧 Extract configuration | |
id: config | |
env: | |
ENV_JSON: ${{ needs.load-env.outputs.env-json }} | |
run: | | |
echo "📋 Extracting PR management configuration from environment..." | |
# Extract all needed variables | |
CLEAN_CACHE=$(echo "$ENV_JSON" | jq -r '.PR_MANAGEMENT_CLEAN_CACHE_ON_CLOSE') | |
# Set as environment variables for all subsequent steps | |
echo "CLEAN_CACHE=$CLEAN_CACHE" >> $GITHUB_ENV | |
# Log configuration | |
echo "🔍 Configuration loaded:" | |
echo " 🧹 Clean cache on close: $CLEAN_CACHE" | |
# ———————————————————————————————————————————————————————————————— | |
# Clean up caches associated with the PR | |
# ———————————————————————————————————————————————————————————————— | |
- name: 🧹 Cleanup caches | |
id: clean | |
if: env.CLEAN_CACHE == 'true' | |
env: | |
PR_NUMBER: ${{ github.event.pull_request.number }} | |
PR_HEAD_REF: ${{ github.event.pull_request.head.ref }} | |
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
GH_REPO: ${{ github.repository }} | |
run: | | |
echo "🧹 Cleaning up caches for PR #$PR_NUMBER..." | |
echo "════════════════════════════════════════════════════════════════" | |
# Fetch the list of cache keys for this PR | |
echo "📋 Fetching cache list for PR #$PR_NUMBER..." | |
# Get all caches and filter for this PR (checking multiple possible refs) | |
allCaches=$(gh cache list --limit 100 --json id,key,ref) | |
# Debug: Show what refs we're looking for | |
echo "🔍 Looking for caches with refs:" | |
echo " - refs/pull/$PR_NUMBER/merge" | |
echo " - refs/pull/$PR_NUMBER/head" | |
echo " - refs/heads/$PR_HEAD_REF" | |
# Filter caches that belong to this PR (multiple possible refs) | |
cacheKeysForPR=$(echo "$allCaches" | jq -r --arg pr "$PR_NUMBER" --arg branch "$PR_HEAD_REF" \ | |
'.[] | select( | |
.ref == "refs/pull/\($pr)/merge" or | |
.ref == "refs/pull/\($pr)/head" or | |
.ref == "refs/heads/\($branch)" | |
) | .id') | |
# Count caches - handle empty results properly | |
if [ -z "$cacheKeysForPR" ]; then | |
cacheCount=0 | |
else | |
cacheCount=$(echo "$cacheKeysForPR" | wc -l | tr -d ' ') | |
fi | |
if [ "$cacheCount" -eq "0" ]; then | |
echo "ℹ️ No caches found for this PR" | |
echo "caches-cleaned=0" >> $GITHUB_OUTPUT | |
exit 0 | |
fi | |
echo "🗑️ Found $cacheCount cache(s) to clean" | |
# Setting this to not fail the workflow while deleting cache keys | |
set +e | |
cleanedCount=0 | |
# Delete each cache | |
for cacheKey in $cacheKeysForPR; do | |
if gh cache delete "$cacheKey"; then | |
echo " ✅ Deleted cache: $cacheKey" | |
((cleanedCount++)) | |
else | |
echo " ⚠️ Failed to delete cache: $cacheKey" | |
fi | |
done | |
echo "════════════════════════════════════════════════════════════════" | |
echo "✅ Cleaned $cleanedCount out of $cacheCount cache(s)" | |
echo "caches-cleaned=$cleanedCount" >> $GITHUB_OUTPUT | |
# ---------------------------------------------------------------------------------- | |
# Delete Merged Branch | |
# ---------------------------------------------------------------------------------- | |
delete-branch: | |
name: 🌿 Delete Merged Branch | |
needs: [load-env] | |
runs-on: ubuntu-latest | |
permissions: | |
contents: write | |
if: | | |
github.event.action == 'closed' && | |
github.event.pull_request.merged == true && | |
github.event.pull_request.head.repo.full_name == github.repository | |
outputs: | |
branch-deleted: ${{ steps.delete.outputs.branch-deleted }} | |
steps: | |
# ———————————————————————————————————————————————————————————————— | |
# Extract configuration from env-json | |
# ———————————————————————————————————————————————————————————————— | |
- name: 🔧 Extract configuration | |
id: config | |
env: | |
ENV_JSON: ${{ needs.load-env.outputs.env-json }} | |
run: | | |
echo "📋 Extracting PR management configuration from environment..." | |
# Extract all needed variables | |
DELETE_BRANCH=$(echo "$ENV_JSON" | jq -r '.PR_MANAGEMENT_DELETE_BRANCH_ON_MERGE') | |
PROTECTED_BRANCHES=$(echo "$ENV_JSON" | jq -r '.PR_MANAGEMENT_PROTECTED_BRANCHES') | |
# Set as environment variables for all subsequent steps | |
echo "DELETE_BRANCH=$DELETE_BRANCH" >> $GITHUB_ENV | |
echo "PROTECTED_BRANCHES=$PROTECTED_BRANCHES" >> $GITHUB_ENV | |
# Log configuration | |
echo "🔍 Configuration loaded:" | |
echo " 🗑️ Delete branch on merge: $DELETE_BRANCH" | |
echo " 🔒 Protected branches: $PROTECTED_BRANCHES" | |
# ———————————————————————————————————————————————————————————————— | |
# Delete the merged branch | |
# ———————————————————————————————————————————————————————————————— | |
- name: 🌿 Delete branch | |
id: delete | |
if: env.DELETE_BRANCH == 'true' | |
uses: actions/github-script@60a0d83039c74a4aee543508d2ffcb1c3799cdea # v7.0.1 | |
with: | |
github-token: ${{ secrets.GITHUB_TOKEN }} | |
script: | | |
// Get repo owner, name, and branch to delete | |
const owner = context.repo.owner; | |
const repo = context.repo.repo; | |
const branch = context.payload.pull_request.head.ref; | |
console.log(`🌿 Processing branch deletion for: ${branch}`); | |
// Fetch repository data to determine the default branch | |
const { data: repoData } = await github.rest.repos.get({ | |
owner, | |
repo, | |
}); | |
const defaultBranch = repoData.default_branch; | |
// Build list of protected branches from config and default | |
const configProtected = process.env.PROTECTED_BRANCHES.split(',').map(b => b.trim()); | |
const protectedBranches = [...new Set([...configProtected, defaultBranch])]; | |
console.log(`🔒 Protected branches: ${protectedBranches.join(', ')}`); | |
// Only delete if not a protected branch | |
if (!protectedBranches.includes(branch)) { | |
try { | |
// Attempt to delete the branch ref | |
await github.rest.git.deleteRef({ | |
owner, | |
repo, | |
ref: `heads/${branch}`, | |
}); | |
console.log(`✅ Deleted branch: ${branch}`); | |
core.setOutput('branch-deleted', 'true'); | |
} catch (error) { | |
// Handle case where branch is already deleted or protected | |
if (error.status === 422) { | |
console.log(`ℹ️ Branch ${branch} already deleted or protected`); | |
core.setOutput('branch-deleted', 'false'); | |
} else { | |
// Fail the workflow for other errors | |
console.error(`❌ Failed to delete branch ${branch}: ${error.message}`); | |
core.setOutput('branch-deleted', 'false'); | |
core.setFailed(`Failed to delete branch ${branch}: ${error.message}`); | |
} | |
} | |
} else { | |
console.log(`⏭️ Skipping deletion for protected branch: ${branch}`); | |
core.setOutput('branch-deleted', 'skip'); | |
} | |
# ---------------------------------------------------------------------------------- | |
# Generate Workflow Summary Report | |
# ---------------------------------------------------------------------------------- | |
summary: | |
name: 📊 Generate Summary | |
if: always() | |
needs: [load-env, apply-labels, assign-assignee, welcome-contributor, analyze-size, clean-cache, delete-branch] | |
runs-on: ubuntu-latest | |
steps: | |
# ———————————————————————————————————————————————————————————————— | |
# Generate a workflow summary report | |
# ———————————————————————————————————————————————————————————————— | |
- name: 📊 Generate workflow summary | |
env: | |
ENV_JSON: ${{ needs.load-env.outputs.env-json }} | |
PR_NUMBER: ${{ github.event.pull_request.number }} | |
PR_TITLE: ${{ github.event.pull_request.title }} | |
PR_ACTION: ${{ github.event.action }} | |
PR_AUTHOR: ${{ github.event.pull_request.user.login }} | |
PR_MERGED: ${{ github.event.pull_request.merged }} | |
run: | | |
echo "📊 Generating workflow summary..." | |
echo "# 🔧 Pull Request Management Summary" >> $GITHUB_STEP_SUMMARY | |
echo "" >> $GITHUB_STEP_SUMMARY | |
echo "**⏰ Processed:** $(date -u '+%Y-%m-%d %H:%M:%S UTC')" >> $GITHUB_STEP_SUMMARY | |
echo "**📋 PR:** #$PR_NUMBER - $PR_TITLE" >> $GITHUB_STEP_SUMMARY | |
echo "**🎬 Action:** $PR_ACTION" >> $GITHUB_STEP_SUMMARY | |
echo "**👤 Author:** @$PR_AUTHOR" >> $GITHUB_STEP_SUMMARY | |
echo "**🔗 Source:** ${{ github.event.pull_request.head.repo.full_name == github.repository && 'Internal' || 'Fork' }}" >> $GITHUB_STEP_SUMMARY | |
echo "" >> $GITHUB_STEP_SUMMARY | |
# Show results based on action type | |
if [ "$PR_ACTION" != "closed" ]; then | |
echo "## 📋 Actions Taken" >> $GITHUB_STEP_SUMMARY | |
echo "" >> $GITHUB_STEP_SUMMARY | |
echo "| Action | Result |" >> $GITHUB_STEP_SUMMARY | |
echo "|--------|--------|" >> $GITHUB_STEP_SUMMARY | |
# Labels applied | |
if [ "${{ needs.apply-labels.result }}" = "success" ]; then | |
LABELS="${{ needs.apply-labels.outputs.labels-applied }}" | |
if [ "$LABELS" != "[]" ] && [ -n "$LABELS" ]; then | |
echo "| 🏷️ Labels Applied | $LABELS |" >> $GITHUB_STEP_SUMMARY | |
else | |
echo "| 🏷️ Labels Applied | None needed |" >> $GITHUB_STEP_SUMMARY | |
fi | |
fi | |
# Assignee | |
if [ "${{ needs.assign-assignee.result }}" = "success" ]; then | |
if [ "${{ needs.assign-assignee.outputs.assignee-added }}" = "true" ]; then | |
echo "| 👤 Default Assignee | Added |" >> $GITHUB_STEP_SUMMARY | |
else | |
echo "| 👤 Default Assignee | Already assigned |" >> $GITHUB_STEP_SUMMARY | |
fi | |
fi | |
# Welcome message | |
if [ "${{ needs.welcome-contributor.result }}" = "success" ]; then | |
if [ "${{ needs.welcome-contributor.outputs.welcomed }}" = "true" ]; then | |
echo "| 👋 Welcome Message | Posted |" >> $GITHUB_STEP_SUMMARY | |
fi | |
fi | |
# Size label | |
if [ "${{ needs.analyze-size.result }}" = "success" ]; then | |
SIZE_LABEL="${{ needs.analyze-size.outputs.size-label }}" | |
TOTAL_CHANGES="${{ needs.analyze-size.outputs.total-changes }}" | |
if [ -n "$SIZE_LABEL" ]; then | |
echo "| 📏 Size Analysis | $SIZE_LABEL ($TOTAL_CHANGES changes) |" >> $GITHUB_STEP_SUMMARY | |
fi | |
fi | |
else | |
echo "## 🧹 Cleanup Actions" >> $GITHUB_STEP_SUMMARY | |
echo "" >> $GITHUB_STEP_SUMMARY | |
echo "| Action | Result |" >> $GITHUB_STEP_SUMMARY | |
echo "|--------|--------|" >> $GITHUB_STEP_SUMMARY | |
# Cache cleanup | |
if [ "${{ needs.clean-cache.result }}" = "success" ]; then | |
CACHES="${{ needs.clean-cache.outputs.caches-cleaned }}" | |
echo "| 🧹 Cache Cleanup | $CACHES cache(s) cleaned |" >> $GITHUB_STEP_SUMMARY | |
fi | |
# Branch deletion | |
if [ "$PR_MERGED" = "true" ] && [ "${{ needs.delete-branch.result }}" = "success" ]; then | |
DELETED="${{ needs.delete-branch.outputs.branch-deleted }}" | |
if [ "$DELETED" = "true" ]; then | |
echo "| 🌿 Branch Deletion | Deleted |" >> $GITHUB_STEP_SUMMARY | |
elif [ "$DELETED" = "skip" ]; then | |
echo "| 🌿 Branch Deletion | Skipped (protected) |" >> $GITHUB_STEP_SUMMARY | |
else | |
echo "| 🌿 Branch Deletion | Already deleted |" >> $GITHUB_STEP_SUMMARY | |
fi | |
fi | |
fi | |
echo "" >> $GITHUB_STEP_SUMMARY | |
echo "### 🔧 Configuration" >> $GITHUB_STEP_SUMMARY | |
echo "" >> $GITHUB_STEP_SUMMARY | |
# Extract key configuration for display | |
DEFAULT_ASSIGNEE=$(echo "$ENV_JSON" | jq -r '.PR_MANAGEMENT_DEFAULT_ASSIGNEE') | |
APPLY_SIZE_LABELS=$(echo "$ENV_JSON" | jq -r '.PR_MANAGEMENT_APPLY_SIZE_LABELS') | |
APPLY_TYPE_LABELS=$(echo "$ENV_JSON" | jq -r '.PR_MANAGEMENT_APPLY_TYPE_LABELS') | |
WELCOME_FIRST_TIME=$(echo "$ENV_JSON" | jq -r '.PR_MANAGEMENT_WELCOME_FIRST_TIME') | |
echo "| Setting | Value |" >> $GITHUB_STEP_SUMMARY | |
echo "|---------|-------|" >> $GITHUB_STEP_SUMMARY | |
echo "| Default Assignee | @$DEFAULT_ASSIGNEE |" >> $GITHUB_STEP_SUMMARY | |
echo "| Apply Size Labels | $APPLY_SIZE_LABELS |" >> $GITHUB_STEP_SUMMARY | |
echo "| Apply Type Labels | $APPLY_TYPE_LABELS |" >> $GITHUB_STEP_SUMMARY | |
echo "| Welcome First-timers | $WELCOME_FIRST_TIME |" >> $GITHUB_STEP_SUMMARY | |
echo "" >> $GITHUB_STEP_SUMMARY | |
echo "---" >> $GITHUB_STEP_SUMMARY | |
echo "🤖 _Automated by GitHub Actions_" >> $GITHUB_STEP_SUMMARY | |
# ———————————————————————————————————————————————————————————————— | |
# Report final workflow status | |
# ———————————————————————————————————————————————————————————————— | |
- name: 📢 Report workflow status | |
env: | |
PR_NUMBER: ${{ github.event.pull_request.number }} | |
PR_ACTION: ${{ github.event.action }} | |
PR_AUTHOR: ${{ github.event.pull_request.user.login }} | |
PR_MERGED: ${{ github.event.pull_request.merged }} | |
run: | | |
echo "=== 🔧 Pull Request Management Summary ===" | |
echo "📋 PR: #$PR_NUMBER" | |
echo "🎬 Action: $PR_ACTION" | |
echo "👤 Author: @$PR_AUTHOR" | |
# Summary based on action | |
if [ "$PR_ACTION" != "closed" ]; then | |
echo "✅ PR management tasks completed" | |
else | |
if [ "$PR_MERGED" = "true" ]; then | |
echo "✅ PR merged and cleanup completed" | |
else | |
echo "✅ PR closed and cleanup completed" | |
fi | |
fi | |
echo "🕐 Completed: $(date -u '+%Y-%m-%d %H:%M:%S UTC')" | |
echo "✅ Workflow completed!" |