Skip to content

chore(deps): bump the ghactions-all group with 2 updates #118

chore(deps): bump the ghactions-all group with 2 updates

chore(deps): bump the ghactions-all group with 2 updates #118

# ------------------------------------------------------------------------------------
# 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!"