Repository navigation
159 lines (137 loc) Β· 6.5 KB
/
Copy pathpr-size-check.yml
File metadata and controls
159 lines (137 loc) Β· 6.5 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
name: PR Size Check
on:
pull_request:
types: [opened, synchronize, reopened]
permissions:
contents: read
pull-requests: write
jobs:
check-pr-size:
runs-on: ubuntu-latest
timeout-minutes: 10
steps:
- name: Harden the runner
uses: step-security/harden-runner@ec9f2d5744a09debf3a187a3f4f675c53b671911 # v2.13.0
with:
egress-policy: audit
- name: Checkout code
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
with:
fetch-depth: 0
- name: Check PR size
id: check-size
run: |
set -euo pipefail
# Fetch the base branch
git fetch origin "${{ github.base_ref }}"
# Get diff stats
diff_output=$(git diff --numstat "origin/${{ github.base_ref }}"...HEAD)
# Count lines, excluding:
# - Test files (*.test.ts, *.spec.tsx, etc.)
# - Locale files (locales/*.json, i18n/*.json)
# - Lock files (pnpm-lock.yaml, package-lock.json, yarn.lock)
# - Generated files (dist/, coverage/, build/, .next/)
# - Storybook stories (*.stories.tsx)
total_additions=0
total_deletions=0
counted_files=0
excluded_files=0
while IFS=$'\t' read -r additions deletions file; do
# Skip if additions or deletions are "-" (binary files)
if [ "$additions" = "-" ] || [ "$deletions" = "-" ]; then
continue
fi
# Check if file should be excluded
case "$file" in
*.test.ts|*.test.tsx|*.spec.ts|*.spec.tsx|*.test.js|*.test.jsx|*.spec.js|*.spec.jsx)
excluded_files=$((excluded_files + 1))
continue
;;
*/locales/*.json|*/i18n/*.json)
excluded_files=$((excluded_files + 1))
continue
;;
pnpm-lock.yaml|package-lock.json|yarn.lock)
excluded_files=$((excluded_files + 1))
continue
;;
dist/*|coverage/*|build/*|node_modules/*|test-results/*|playwright-report/*|.next/*|*.tsbuildinfo)
excluded_files=$((excluded_files + 1))
continue
;;
*.stories.ts|*.stories.tsx|*.stories.js|*.stories.jsx)
excluded_files=$((excluded_files + 1))
continue
;;
esac
total_additions=$((total_additions + additions))
total_deletions=$((total_deletions + deletions))
counted_files=$((counted_files + 1))
done <<EOF
${diff_output}
EOF
total_changes=$((total_additions + total_deletions))
echo "counted_files=${counted_files}" >> "${GITHUB_OUTPUT}"
echo "excluded_files=${excluded_files}" >> "${GITHUB_OUTPUT}"
echo "total_additions=${total_additions}" >> "${GITHUB_OUTPUT}"
echo "total_deletions=${total_deletions}" >> "${GITHUB_OUTPUT}"
echo "total_changes=${total_changes}" >> "${GITHUB_OUTPUT}"
# Set flag if PR is too large (> 800 lines)
if [ ${total_changes} -gt 800 ]; then
echo "is_too_large=true" >> "${GITHUB_OUTPUT}"
else
echo "is_too_large=false" >> "${GITHUB_OUTPUT}"
fi
- name: Comment on PR if too large
if: steps.check-size.outputs.is_too_large == 'true'
uses: actions/github-script@60a0d83039c74a4aee543508d2ffcb1c3799cdea # v7.0.1
with:
github-token: ${{ secrets.GITHUB_TOKEN }}
script: |
const totalChanges = ${{ steps.check-size.outputs.total_changes }};
const countedFiles = ${{ steps.check-size.outputs.counted_files }};
const excludedFiles = ${{ steps.check-size.outputs.excluded_files }};
const additions = ${{ steps.check-size.outputs.total_additions }};
const deletions = ${{ steps.check-size.outputs.total_deletions }};
const body = '## π¨ PR Size Warning\n\n' +
'This PR has approximately **' + totalChanges + ' lines** of changes (' + additions + ' additions, ' + deletions + ' deletions across ' + countedFiles + ' files).\n\n' +
'Large PRs (>800 lines) are significantly harder to review and increase the chance of merge conflicts. Consider splitting this into smaller, self-contained PRs.\n\n' +
'### π‘ Suggestions:\n' +
'- **Split by feature or module** - Break down into logical, independent pieces\n' +
'- **Create a sequence of PRs** - Each building on the previous one\n' +
'- **Branch off PR branches** - Don\'t wait for reviews to continue dependent work\n\n' +
'### π What was counted:\n' +
'- β
Source files, stylesheets, configuration files\n' +
'- β Excluded ' + excludedFiles + ' files (tests, locales, locks, generated files)\n\n' +
'### π Guidelines:\n' +
'- **Ideal:** 300-500 lines per PR\n' +
'- **Warning:** 500-800 lines\n' +
'- **Critical:** 800+ lines β οΈ\n\n' +
'If this large PR is unavoidable (e.g., migration, dependency update, major refactor), please explain in the PR description why it couldn\'t be split.';
// Check if we already commented
const { data: comments } = await github.rest.issues.listComments({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: context.issue.number,
});
const botComment = comments.find(comment =>
comment.user.type === 'Bot' &&
comment.body.includes('π¨ PR Size Warning')
);
if (botComment) {
// Update existing comment
await github.rest.issues.updateComment({
owner: context.repo.owner,
repo: context.repo.repo,
comment_id: botComment.id,
body: body
});
} else {
// Create new comment
await github.rest.issues.createComment({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: context.issue.number,
body: body
});
}