This repository was archived by the owner on Aug 16, 2026. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 1
162 lines (142 loc) · 6.14 KB
/
Copy pathpr-triage.yml
File metadata and controls
162 lines (142 loc) · 6.14 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
159
160
161
162
name: PR Triage
on:
pull_request:
types: [opened, edited, synchronize, reopened, ready_for_review, converted_to_draft]
jobs:
triage:
runs-on: ubuntu-latest
permissions:
pull-requests: write
issues: write
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Triage PR
uses: actions/github-script@v9
with:
script: |
const pr = context.payload.pull_request;
const title = pr.title;
const author = pr.user.login;
const isDraft = pr.draft || /\bwip\b/i.test(title) || title.toLowerCase().startsWith('draft');
let labelsToAdd = [];
let labelsToRemove = [];
// 1. Handle Draft / WIP
if (isDraft) {
labelsToAdd.push('do-not-merge');
} else {
labelsToRemove.push('do-not-merge');
}
// 2. Title Linter & Priority Labeling
const titleRegex = /^(fix|bug|security|feat|docs|style|refactor|perf|test|chore)(\(.+\))?:\s.+/i;
const match = titleRegex.exec(title);
let isValidTitle = false;
let titlePrefix = "";
if (match) {
isValidTitle = true;
titlePrefix = match[1].toLowerCase();
}
if (!isValidTitle) {
const commentBody = `@${author} The PR title format is invalid. Please use the Conventional Commits format:\n\n\`<type>[optional scope]: <description>\`\n\nValid types are: \`fix\`, \`bug\`, \`security\`, \`feat\`, \`docs\`, \`style\`, \`refactor\`, \`perf\`, \`test\`, \`chore\`.\n\nExample: \`fix: resolve crash on startup\``;
// Check if we already commented to avoid spamming
const comments = await github.paginate(github.rest.issues.listComments, {
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: pr.number,
});
const hasCommented = comments.some(c => c.body.includes('The PR title format is invalid') && c.user.login === 'github-actions[bot]');
if (!hasCommented) {
await github.rest.issues.createComment({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: pr.number,
body: commentBody
});
}
core.setFailed('Invalid PR title format.');
return; // Stop execution if title is invalid
} else {
// Add priority based on prefix
if (['fix', 'bug'].includes(titlePrefix)) {
labelsToAdd.push('priority/high');
labelsToRemove.push('priority/critical', 'priority/medium', 'priority/low');
} else if (titlePrefix === 'security') {
labelsToAdd.push('priority/critical');
labelsToRemove.push('priority/high', 'priority/medium', 'priority/low');
} else {
labelsToAdd.push('priority/medium');
labelsToRemove.push('priority/critical', 'priority/high', 'priority/low');
}
}
// 3. Size Labeling
const additions = pr.additions;
const deletions = pr.deletions;
const totalChanges = additions + deletions;
let sizeLabel = '';
if (totalChanges < 200) {
sizeLabel = 'size/small';
labelsToRemove.push('size/medium', 'size/large');
} else if (totalChanges <= 700) {
sizeLabel = 'size/medium';
labelsToRemove.push('size/small', 'size/large');
} else {
sizeLabel = 'size/large';
labelsToRemove.push('size/small', 'size/medium');
}
labelsToAdd.push(sizeLabel);
// 4. Missing Tests Detection
const files = await github.paginate(github.rest.pulls.listFiles, {
owner: context.repo.owner,
repo: context.repo.repo,
pull_number: pr.number,
});
let sourceChanged = false;
let testsChanged = false;
for (const file of files) {
const filename = file.filename.toLowerCase();
if ((filename.endsWith('.c') || filename.endsWith('.h')) && !filename.includes('test')) {
sourceChanged = true;
}
if (filename.includes('test') && (filename.endsWith('.c') || filename.endsWith('.h'))) {
testsChanged = true;
}
}
if (sourceChanged && !testsChanged) {
labelsToAdd.push('needs-tests');
} else {
labelsToRemove.push('needs-tests');
}
// 5. Apply Labels
if (labelsToAdd.length > 0) {
// Remove duplicates
labelsToAdd = [...new Set(labelsToAdd)];
// Ensure labels exist before applying (optional, but good practice to avoid errors if label doesn't exist, though Github API usually auto-creates or we can ignore)
try {
await github.rest.issues.addLabels({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: pr.number,
labels: labelsToAdd
});
} catch (e) {
console.log('Error adding labels:', e);
}
}
// Remove old labels
if (labelsToRemove.length > 0) {
const currentLabels = pr.labels.map(l => l.name);
for (const label of labelsToRemove) {
if (currentLabels.includes(label)) {
try {
await github.rest.issues.removeLabel({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: pr.number,
name: label
});
} catch (e) {
console.log(`Could not remove label ${label}:`, e);
}
}
}
}