-
Notifications
You must be signed in to change notification settings - Fork 448
173 lines (158 loc) · 9.63 KB
/
Copy pathauto-label-prs.yml
File metadata and controls
173 lines (158 loc) · 9.63 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
163
164
165
166
167
168
169
170
171
172
173
name: Auto-label PRs
# PR labeling has two parts:
#
# 1. `paths` job — runs actions/labeler against .github/labeler.yml to
# apply [Aspect], [Focus], [Feature], and [Type] Documentation
# labels. These have no count limit and a wide refactor can match
# many of them legitimately.
#
# 2. `package-and-type` job — applies the `[Package][...]` labels and
# a single inferred `[Type]` label. This one needs custom logic:
#
# - [Package]: ranks packages by total lines changed
# (additions + deletions, file count as tiebreaker) and applies
# at most the top 3. Without this cap, a cross-cutting change
# ends up with a wall of package labels and the signal is lost.
#
# - [Type]: parses the PR title's conventional-commit prefix
# (fix:/feat:/perf:) and applies one matching label only when
# the signal is unambiguous. We skip refactor:/chore:/test:
# because they have no clean target label.
#
# History note: an earlier version used GitHub Models (actions/ai-inference)
# to suggest labels from PR title + body + paths. That returned 403
# because GitHub Models access in Actions is gated by an org-level toggle
# we can't flip. Path + PR-title heuristics cover the structured labels
# without any external service.
on:
pull_request_target:
types: [opened, synchronize, reopened, ready_for_review]
# Disable permissions for all available scopes by default.
# Any needed permissions should be configured at the job level.
permissions: {}
jobs:
paths:
if: github.repository == 'WordPress/wordpress-playground'
runs-on: ubuntu-latest
timeout-minutes: 10
permissions:
contents: read # Required for actions/labeler to read the configuration file.
pull-requests: write # Required to apply labels to pull requests.
steps:
# Pinned to a commit SHA, not a tag: this job runs with
# pull-requests:write, so a moved tag would be a supply-chain
# foothold. Bump deliberately when upgrading.
- uses: actions/labeler@8558fd74291d67161a8a78ce36a881fa63b766a9 # v5.0.0
with:
configuration-path: .github/labeler.yml
sync-labels: false
package-and-type:
if: github.repository == 'WordPress/wordpress-playground'
runs-on: ubuntu-latest
timeout-minutes: 10
permissions:
pull-requests: write # Required to apply labels to pull requests.
steps:
- uses: actions/github-script@60a0d83039c74a4aee543508d2ffcb1c3799cdea # v7.0.1
with:
script: |
// Path prefix → [Package][...] label. Order matters: the
// first matching prefix wins, so longer / more specific
// prefixes must come before shorter ones (e.g. `web-service-worker`
// before `web`).
const PACKAGE_RULES = [
['packages/php-wasm/cli-util/', '[Package][@php-wasm] CLI-Util'],
['packages/php-wasm/cli/', '[Package][@php-wasm] CLI'],
['packages/php-wasm/compile/', '[Package][@php-wasm] Compile'],
['packages/php-wasm/fs-journal/', '[Package][@php-wasm] FS Journal'],
['packages/php-wasm/logger/', '[Package][@php-wasm] Logger'],
['packages/php-wasm/node/', '[Package][@php-wasm] Node'],
['packages/php-wasm/progress/', '[Package][@php-wasm] Progress'],
['packages/php-wasm/scopes/', '[Package][@php-wasm] Scopes'],
['packages/php-wasm/stream-compression/','[Package][@php-wasm] Stream Compression'],
['packages/php-wasm/universal/', '[Package][@php-wasm] Universal'],
['packages/php-wasm/util/', '[Package][@php-wasm] Util'],
['packages/php-wasm/web-service-worker/','[Package][@php-wasm] Web'],
['packages/php-wasm/web/', '[Package][@php-wasm] Web'],
['packages/php-wasm/xdebug-bridge/', '[Package][@php-wasm] Xdebug Bridge'],
['packages/playground/blueprints/', '[Package][@wp-playground] Blueprints'],
['packages/playground/cli/', '[Package][@wp-playground] CLI'],
['packages/playground/client/', '[Package][@wp-playground] Client'],
['packages/playground/common/', '[Package][@wp-playground] Common'],
['packages/playground/components/', '[Package][@wp-playground] Components'],
['packages/playground/php-cors-proxy/', '[Package][@wp-playground] CORS Proxy'],
['packages/playground/remote/', '[Package][@wp-playground] Remote'],
['packages/playground/storage/', '[Package][@wp-playground] Storage'],
['packages/playground/sync/', '[Package][@wp-playground] Sync'],
['packages/playground/website-extras/', '[Package][@wp-playground] Website'],
['packages/playground/website/', '[Package][@wp-playground] Website'],
['packages/playground/wordpress-builds/','[Package][@wp-playground] WordPress Builds'],
['packages/playground/wordpress/', '[Package][@wp-playground] WordPress'],
];
// Conventional-commit prefix → [Type] label. Only entries
// with an unambiguous target label are listed; refactor /
// chore / test / build / ci are intentionally absent.
const TYPE_RULES = [
[/^fix(\(|:)/i, '[Type] Bug'],
[/^feat(ure)?(\(|:)/i, '[Type] Enhancement'],
[/^perf(\(|:)/i, '[Type] Performance'],
[/^docs(\(|:)/i, '[Type] Documentation'],
];
const pr = context.payload.pull_request;
// Very large PRs (e.g. recompiled WASM binaries) make
// GitHub's diff generation time out, so listFiles returns
// 422 "Sorry, this diff is taking too long to generate."
// That failure is persistent for such PRs — retrying just
// loads their servers without helping. Package/type labels
// are cosmetic, so skip them rather than fail the job.
let files;
try {
files = await github.paginate(
github.rest.pulls.listFiles,
{ owner: context.repo.owner, repo: context.repo.repo, pull_number: pr.number, per_page: 100 }
);
} catch (err) {
const status = err?.status;
const message = err?.message ?? String(err);
if (status !== 422 || !/diff is taking too long to generate/i.test(message)) {
throw err;
}
core.warning(
`Could not list PR files (status ${status}): ${message}. ` +
`Skipping package/type labeling for this PR.`
);
return;
}
// Tally lines + file count per package label.
const stats = new Map();
for (const f of files) {
const rule = PACKAGE_RULES.find(([prefix]) => f.filename.startsWith(prefix));
if (!rule) continue;
const label = rule[1];
const lines = (f.additions || 0) + (f.deletions || 0);
const cur = stats.get(label) || { lines: 0, files: 0 };
stats.set(label, { lines: cur.lines + lines, files: cur.files + 1 });
}
// Rank: lines desc, then file count desc, then label name
// for stable output.
const topPackages = [...stats.entries()]
.sort(([a, sa], [b, sb]) =>
sb.lines - sa.lines ||
sb.files - sa.files ||
a.localeCompare(b)
)
.slice(0, 3)
.map(([label]) => label);
const labels = [...topPackages];
const titleMatch = TYPE_RULES.find(([re]) => re.test(pr.title));
if (titleMatch) labels.push(titleMatch[1]);
core.info(`PR title: ${pr.title}`);
core.info(`Package stats: ${JSON.stringify([...stats])}`);
core.info(`Applying labels: ${JSON.stringify(labels)}`);
if (labels.length === 0) return;
await github.rest.issues.addLabels({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: pr.number,
labels,
});