|
| 1 | +name: 'PR Size Labeler' |
| 2 | + |
| 3 | +on: |
| 4 | + pull_request: |
| 5 | + types: ['opened', 'synchronize', 'reopened'] |
| 6 | + workflow_dispatch: |
| 7 | + inputs: |
| 8 | + pr_number: |
| 9 | + description: 'PR number to label manually (for workflow_dispatch)' |
| 10 | + required: false |
| 11 | + type: 'string' |
| 12 | + |
| 13 | +permissions: |
| 14 | + pull-requests: 'write' |
| 15 | + issues: 'write' |
| 16 | + |
| 17 | +jobs: |
| 18 | + size-label: |
| 19 | + runs-on: 'ubuntu-latest' |
| 20 | + steps: |
| 21 | + - name: 'Run size labeler' |
| 22 | + env: |
| 23 | + GH_TOKEN: '${{ secrets.GITHUB_TOKEN }}' |
| 24 | + GH_REPO: '${{ github.repository }}' |
| 25 | + run: | |
| 26 | + # Determine the target PR number |
| 27 | + if [ -n "${{ github.event.pull_request.number }}" ]; then |
| 28 | + PR_NUMBER="${{ github.event.pull_request.number }}" |
| 29 | + elif [ -n "${{ github.event.inputs.pr_number }}" ]; then |
| 30 | + PR_NUMBER="${{ github.event.inputs.pr_number }}" |
| 31 | + else |
| 32 | + echo "❌ Error: No PR number provided." |
| 33 | + exit 1 |
| 34 | + fi |
| 35 | +
|
| 36 | + echo "Checking PR #$PR_NUMBER..." |
| 37 | +
|
| 38 | + # 1. Ensure standard premium size labels exist in the repository (self-healing) |
| 39 | + # size/XS: Light green (#7ee081) |
| 40 | + # size/S: Yellow-green (#a6d49f) |
| 41 | + # size/M: Amber/Yellow (#f7d070) |
| 42 | + # size/L: Orange (#f48c06) |
| 43 | + # size/XL: Red (#dc2f02) |
| 44 | + gh label create "size/XS" --color "7ee081" --description "XS: <10 lines changed" 2>/dev/null || true |
| 45 | + gh label create "size/S" --color "a6d49f" --description "S: 10-49 lines changed" 2>/dev/null || true |
| 46 | + gh label create "size/M" --color "f7d070" --description "M: 50-249 lines changed" 2>/dev/null || true |
| 47 | + gh label create "size/L" --color "f48c06" --description "L: 250-999 lines changed" 2>/dev/null || true |
| 48 | + gh label create "size/XL" --color "dc2f02" --description "XL: >=1000 lines changed" 2>/dev/null || true |
| 49 | +
|
| 50 | + # 2. Fetch PR details in a single efficient API call |
| 51 | + PR_DATA=$(gh pr view "$PR_NUMBER" --json additions,deletions,changedFiles,labels) |
| 52 | + if [ -z "$PR_DATA" ]; then |
| 53 | + echo "❌ Error: Could not fetch PR details." |
| 54 | + exit 1 |
| 55 | + fi |
| 56 | +
|
| 57 | + ADDITIONS=$(echo "$PR_DATA" | jq '.additions') |
| 58 | + DELETIONS=$(echo "$PR_DATA" | jq '.deletions') |
| 59 | + CHANGED_FILES=$(echo "$PR_DATA" | jq '.changedFiles') |
| 60 | + TOTAL=$((ADDITIONS + DELETIONS)) |
| 61 | +
|
| 62 | + echo "PR additions: $ADDITIONS, deletions: $DELETIONS, total changes: $TOTAL, files: $CHANGED_FILES" |
| 63 | +
|
| 64 | + # 3. Calculate new size label |
| 65 | + if [ $TOTAL -lt 10 ]; then |
| 66 | + SIZE="size/XS" |
| 67 | + elif [ $TOTAL -lt 50 ]; then |
| 68 | + SIZE="size/S" |
| 69 | + elif [ $TOTAL -lt 250 ]; then |
| 70 | + SIZE="size/M" |
| 71 | + elif [ $TOTAL -lt 1000 ]; then |
| 72 | + SIZE="size/L" |
| 73 | + else |
| 74 | + SIZE="size/XL" |
| 75 | + fi |
| 76 | +
|
| 77 | + # 4. Check existing labels and update only if necessary |
| 78 | + EXISTING_LABELS=$(echo "$PR_DATA" | jq -r '.labels[].name' 2>/dev/null || echo "") |
| 79 | +
|
| 80 | + LABELS_TO_REMOVE=() |
| 81 | + for L in size/XS size/S size/M size/L size/XL; do |
| 82 | + if echo "$EXISTING_LABELS" | grep -Fq "$L" && [ "$L" != "$SIZE" ]; then |
| 83 | + LABELS_TO_REMOVE+=("--remove-label" "$L") |
| 84 | + fi |
| 85 | + done |
| 86 | +
|
| 87 | + LABEL_TO_ADD=() |
| 88 | + if ! echo "$EXISTING_LABELS" | grep -Fq "$SIZE"; then |
| 89 | + LABEL_TO_ADD+=("--add-label" "$SIZE") |
| 90 | + fi |
| 91 | +
|
| 92 | + # Perform a single, highly atomic edit call if changes are needed |
| 93 | + if [ ${#LABELS_TO_REMOVE[@]} -gt 0 ] || [ ${#LABEL_TO_ADD[@]} -gt 0 ]; then |
| 94 | + echo "Updating labels: removing ${LABELS_TO_REMOVE[*]}, adding $SIZE" |
| 95 | + gh pr edit "$PR_NUMBER" "${LABELS_TO_REMOVE[@]}" "${LABEL_TO_ADD[@]}" |
| 96 | + else |
| 97 | + echo "✅ PR #$PR_NUMBER already has the correct size label ($SIZE)." |
| 98 | + fi |
| 99 | +
|
| 100 | + # 5. Premium, anti-spam comment logic (updates previous comment to keep thread clean) |
| 101 | + COMMENT="📊 PR Size: **$SIZE** |
| 102 | + - Lines changed: **$TOTAL** |
| 103 | + - Additions: +$ADDITIONS |
| 104 | + - Deletions: -$DELETIONS |
| 105 | + - Files changed: $CHANGED_FILES" |
| 106 | +
|
| 107 | + # Find any existing size labeler comment by the github-actions bot |
| 108 | + echo "Searching for existing size comment..." |
| 109 | + COMMENT_ID=$(gh api "repos/${{ github.repository }}/issues/$PR_NUMBER/comments" \ |
| 110 | + --jq '.[] | select(.user.login == "github-actions[bot]" and (.body | startswith("📊 PR Size:"))) | .id' | head -n 1) |
| 111 | +
|
| 112 | + if [ -n "$COMMENT_ID" ]; then |
| 113 | + echo "Updating existing comment (ID: $COMMENT_ID)..." |
| 114 | + gh api "repos/${{ github.repository }}/issues/comments/$COMMENT_ID" -X PATCH -f body="$COMMENT" > /dev/null |
| 115 | + else |
| 116 | + echo "Creating new comment..." |
| 117 | + gh pr comment "$PR_NUMBER" --body "$COMMENT" > /dev/null |
| 118 | + fi |
| 119 | +
|
| 120 | + echo "🎉 PR size labeling completed successfully." |
0 commit comments