Skip to content

chore(deps): bump golang.org/x/crypto from 0.43.0 to 0.45.0 #97

chore(deps): bump golang.org/x/crypto from 0.43.0 to 0.45.0

chore(deps): bump golang.org/x/crypto from 0.43.0 to 0.45.0 #97

# ------------------------------------------------------------------------------------
# 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 and customizable.
#
# Configuration: All settings are loaded from .env.base and .env.custom files for
# centralized management across all workflows.
#
# Triggers: Pull request events (opened, reopened, ready for review, closed, synchronize)
#
# 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
#
# SECURITY MODEL:
# - Uses pull_request trigger (runs in PR context with limited permissions)
# - Safe to check out PR code as workflow has read-only access by default
# - Fork detection uses full_name comparison for accuracy (not owner.login which fails for org members)
# - Job-level permissions grant write access only where needed (labels, comments, cache cleanup)
# - Mutually exclusive with fork workflow - each PR triggers only ONE workflow
#
# ------------------------------------------------------------------------------------
name: PR Management
# --------------------------------------------------------------------
# Trigger Configuration
# --------------------------------------------------------------------
on:
pull_request:
types: [opened, reopened, ready_for_review, closed, synchronize]
# Security: Restrictive default permissions with job-level overrides for least privilege access
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 .env.base and .env.custom files
jobs:
# ----------------------------------------------------------------------------------
# Load Environment Variables
# ----------------------------------------------------------------------------------
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@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5.0.1
with:
sparse-checkout: |
.github/.env.base
.github/.env.custom
.github/actions/load-env
# --------------------------------------------------------------------
# Load and parse environment file
# --------------------------------------------------------------------
- name: 🌍 Load environment variables
uses: ./.github/actions/load-env
id: load-env
# ----------------------------------------------------------------------------------
# Detect if this is a same-repository PR with proper null handling
# ----------------------------------------------------------------------------------
detect-same-repo:
name: 🔍 Detect Same-Repo PR
runs-on: ubuntu-latest
permissions:
contents: read
outputs:
is-same-repo: ${{ steps.detection.outputs.is-same-repo }}
steps:
- name: 🔍 Same-repo detection with null checks
id: detection
env:
PR_HEAD_REPO: ${{ github.event.pull_request.head.repo && github.event.pull_request.head.repo.full_name || '' }}
BASE_REPO: ${{ github.repository }}
run: |
echo "════════════════════════════════════════════════════════════════"
echo "🔍 Same-Repo PR Detection Debug"
echo "════════════════════════════════════════════════════════════════"
echo " PR Head Repo: '${PR_HEAD_REPO}'"
echo " Base Repo: '${BASE_REPO}'"
echo " Event: ${{ github.event_name }}"
echo "════════════════════════════════════════════════════════════════"
# Check if this is a same-repo PR with proper null/empty handling
# A same-repo PR is when:
# 1. PR_HEAD_REPO is not empty (not null/undefined)
# 2. PR_HEAD_REPO == BASE_REPO (same repository)
if [[ -n "$PR_HEAD_REPO" ]] && [[ "$PR_HEAD_REPO" == "$BASE_REPO" ]]; then
echo "✅ SAME-REPO PR DETECTED"
echo "is-same-repo=true" >> $GITHUB_OUTPUT
else
echo "🚨 NOT A SAME-REPO PR (Fork or invalid head repo)"
echo "is-same-repo=false" >> $GITHUB_OUTPUT
fi
echo "════════════════════════════════════════════════════════════════"
# ----------------------------------------------------------------------------------
# Apply Labels Based on Branch and Title
# ----------------------------------------------------------------------------------
apply-labels:
name: 🏷️ Apply Labels
needs: [load-env, detect-same-repo]
runs-on: ubuntu-latest
permissions:
contents: read
pull-requests: write
# Only run for non-fork PRs (same repository) - using detection output
if: |
github.event.action != 'closed' &&
needs.detect-same-repo.outputs.is-same-repo == 'true'
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@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8.0.0
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, detect-same-repo]
runs-on: ubuntu-latest
permissions:
contents: read
pull-requests: write
# Only run for non-fork PRs (same repository) - using detection output
if: |
github.event.action != 'closed' &&
needs.detect-same-repo.outputs.is-same-repo == 'true'
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@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8.0.0
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, detect-same-repo]
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) &&
needs.detect-same-repo.outputs.is-same-repo == 'true'
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@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8.0.0
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, detect-same-repo]
runs-on: ubuntu-latest
permissions:
contents: read
pull-requests: write
if: |
github.event.action == 'opened' &&
needs.detect-same-repo.outputs.is-same-repo == 'true'
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@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8.0.0
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, detect-same-repo]
runs-on: ubuntu-latest
permissions:
actions: write # Required: Delete GitHub Actions caches for closed PRs
contents: read # Read repository content for cache management
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, detect-same-repo]
runs-on: ubuntu-latest
permissions:
contents: write # Required: Delete branches after PR merge
# Only run for non-fork PRs (same repository) that were merged - using detection output
if: |
github.event.action == 'closed' &&
github.event.pull_request.merged == true &&
needs.detect-same-repo.outputs.is-same-repo == 'true'
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@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8.0.0
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, detect-same-repo, 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 }}
IS_SAME_REPO: ${{ needs.detect-same-repo.outputs.is-same-repo }}
PR_HEAD_REPO: ${{ github.event.pull_request.head.repo && github.event.pull_request.head.repo.full_name || '' }}
BASE_REPO: ${{ github.repository }}
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
# Show repo detection results
echo "" >> $GITHUB_STEP_SUMMARY
echo "## 🔍 Repository Detection" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
echo "| Property | Value |" >> $GITHUB_STEP_SUMMARY
echo "|----------|-------|" >> $GITHUB_STEP_SUMMARY
echo "| PR Head Repo | \`$PR_HEAD_REPO\` |" >> $GITHUB_STEP_SUMMARY
echo "| Base Repo | \`$BASE_REPO\` |" >> $GITHUB_STEP_SUMMARY
echo "| Is Same Repo? | **$IS_SAME_REPO** |" >> $GITHUB_STEP_SUMMARY
if [ "$IS_SAME_REPO" = "true" ]; then
echo "| Status | ✅ Same-repo PR - Full automation enabled |" >> $GITHUB_STEP_SUMMARY
else
echo "| Status | ⚠️ **NOT a same-repo PR** - This workflow should not have processed this PR |" >> $GITHUB_STEP_SUMMARY
fi
echo "" >> $GITHUB_STEP_SUMMARY
# Add fork PR specific information if this is a fork PR
if [ "$IS_SAME_REPO" = "false" ]; then
echo "---" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
echo "## 🔐 Fork PR Status" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
echo "⚠️ **This is a FORK Pull Request** - Some automated actions are restricted for security." >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
if [ "$PR_ACTION" != "closed" ]; then
echo "### ✅ Actions Completed for Fork PR:" >> $GITHUB_STEP_SUMMARY
echo "- **Labels Applied** - Automated based on branch prefix and PR title" >> $GITHUB_STEP_SUMMARY
echo "- **Type Detection** - PR type classification" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
echo "### ⛔ Actions Skipped (Fork Restrictions):" >> $GITHUB_STEP_SUMMARY
echo "- **Default Assignee** - Fork PRs are not auto-assigned" >> $GITHUB_STEP_SUMMARY
echo "- **Size Analysis** - Only available for internal PRs" >> $GITHUB_STEP_SUMMARY
echo "- **Branch Operations** - Fork branches cannot be deleted from base repo" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
echo "### 📝 Why Are Actions Restricted?" >> $GITHUB_STEP_SUMMARY
echo "Fork PRs have limited permissions to protect repository security:" >> $GITHUB_STEP_SUMMARY
echo "- Prevents unauthorized repository modifications" >> $GITHUB_STEP_SUMMARY
echo "- Protects branch management operations" >> $GITHUB_STEP_SUMMARY
echo "- Ensures only repository members can perform sensitive actions" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
echo "**Note for Contributors:** Repository maintainers will review your PR and can manually" >> $GITHUB_STEP_SUMMARY
echo "apply additional labels, assignees, or other management actions as needed." >> $GITHUB_STEP_SUMMARY
else
echo "### 🧹 Cleanup Status for Fork PR:" >> $GITHUB_STEP_SUMMARY
echo "- **Cache Cleanup** - Runner caches cleaned" >> $GITHUB_STEP_SUMMARY
echo "- **Branch Deletion** - Fork branches remain in fork repository" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
echo "_Fork PR branches are managed by the contributor in their forked repository._" >> $GITHUB_STEP_SUMMARY
fi
echo "" >> $GITHUB_STEP_SUMMARY
echo "---" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
fi
# 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
elif [ "${{ needs.assign-assignee.result }}" = "skipped" ]; then
if [ "$IS_SAME_REPO" = "false" ]; then
echo "| 👤 Default Assignee | ⛔ Skipped (Fork PR) |" >> $GITHUB_STEP_SUMMARY
else
echo "| 👤 Default Assignee | Skipped |" >> $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
elif [ "${{ needs.analyze-size.result }}" = "skipped" ]; then
if [ "$IS_SAME_REPO" = "false" ]; then
echo "| 📏 Size Analysis | ⛔ Skipped (Fork PR) |" >> $GITHUB_STEP_SUMMARY
else
echo "| 📏 Size Analysis | Skipped |" >> $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" ]; then
if [ "${{ 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
elif [ "${{ needs.delete-branch.result }}" = "skipped" ]; then
if [ "$IS_SAME_REPO" = "false" ]; then
echo "| 🌿 Branch Deletion | ⛔ Skipped (Fork PR - managed by contributor) |" >> $GITHUB_STEP_SUMMARY
else
echo "| 🌿 Branch Deletion | Skipped |" >> $GITHUB_STEP_SUMMARY
fi
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!"