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
174
175
176
177
178
179
180
181
182
|
name: Labeler
on:
issues:
types: [opened]
pull_request_target:
types: [opened, synchronize, reopened]
discussion:
types: [created]
jobs:
label-priority:
# Author-based priority labels: only on issue/PR/discussion creation, not on PR updates.
if: github.event_name != 'pull_request_target' || github.event.action == 'opened'
runs-on: ubuntu-latest
permissions:
issues: write
pull-requests: write
discussions: write
steps:
- name: Label New Issue, PR or Discussion
uses: actions/github-script@v8
with:
github-token: ${{ secrets.GITHUB_TOKEN }}
script: |
let labels = [];
let username = '';
let issueOrPrNumber = 0;
let discussionNodeId = '';
if (context.eventName === 'issues') {
username = context.payload.issue.user.login;
issueOrPrNumber = context.payload.issue.number;
} else if (context.eventName === 'pull_request_target') {
username = context.payload.pull_request.user.login;
issueOrPrNumber = context.payload.pull_request.number;
} else if (context.eventName === 'discussion') {
username = context.payload.discussion.user.login;
discussionNodeId = context.payload.discussion.node_id;
}
// Maintainer is an Adafruit member; skip the Adafruit perks for their own
// issues/PRs and treat them as a plain contributor (Prio only).
const isOwner = username.toLowerCase() === 'hathach';
// Check if an Adafruit member: Adafruit + Sponsor + top priority
if (!isOwner) {
try {
const adafruitResponse = await github.rest.orgs.checkMembershipForUser({
org: 'adafruit',
username: username
});
if (adafruitResponse.status === 204) {
console.log('Adafruit Member');
labels = ['Adafruit 🌸', 'Sponsor 💖', 'Prio Top 🚨'];
}
} catch (error) {
console.log('Not an Adafruit member');
}
}
// Check if a public GitHub Sponsor of the repo owner.
// Word ($32) tier and up get triage priority; DWORD/QWORD ($128+) go to the top.
// Private sponsorships are not visible to GITHUB_TOKEN, so only public sponsors are detected.
if (labels.length === 0) {
try {
const result = await github.graphql(`
query($sponsorable: String!, $sponsor: String!) {
user(login: $sponsorable) {
isSponsoredBy(accountLogin: $sponsor)
sponsorshipsAsMaintainer(includePrivate: false, first: 100) {
nodes {
sponsorEntity {
... on User { login }
... on Organization { login }
}
tier { monthlyPriceInDollars }
}
}
}
}`, { sponsorable: context.repo.owner, sponsor: username });
const owner = result.user;
if (owner && owner.isSponsoredBy) {
let monthly = 0;
const nodes = (owner.sponsorshipsAsMaintainer && owner.sponsorshipsAsMaintainer.nodes) || [];
for (const node of nodes) {
const login = node.sponsorEntity && node.sponsorEntity.login;
if (login && login.toLowerCase() === username.toLowerCase()) {
monthly = (node.tier && node.tier.monthlyPriceInDollars) || 0;
break;
}
}
if (monthly >= 128) {
console.log('Sponsor (DWORD/QWORD tier)');
labels = ['Sponsor 💖', 'Prio Top 🚨'];
} else if (monthly >= 32) {
console.log('Sponsor (Word tier)');
labels = ['Sponsor 💖', 'Prio 📌'];
} else {
console.log('Sponsor (below Word tier or tier not visible)');
labels = ['Sponsor 💖'];
}
} else {
console.log('Not a public sponsor');
}
} catch (error) {
console.log('Sponsor lookup failed: ' + error.message);
}
}
// Check if a contributor: prioritized in triage queue
if (labels.length === 0) {
try {
const collaboratorResponse = await github.rest.repos.checkCollaborator({
owner: context.repo.owner,
repo: context.repo.repo,
username: username
});
if (collaboratorResponse.status === 204) {
console.log('Contributor');
labels = ['Prio 📌'];
}
} catch (error) {
console.log('Not a contributor');
}
}
if (labels.length !== 0) {
if (context.eventName === 'discussion') {
// Discussions are not covered by the REST issues API; resolve the label
// names to node IDs and attach them with the GraphQL labelable mutation.
const labelIds = [];
for (const name of labels) {
const res = await github.graphql(`
query($owner: String!, $repo: String!, $name: String!) {
repository(owner: $owner, name: $repo) {
label(name: $name) { id }
}
}`, { owner: context.repo.owner, repo: context.repo.repo, name: name });
if (res.repository.label) {
labelIds.push(res.repository.label.id);
}
}
if (labelIds.length !== 0) {
await github.graphql(`
mutation($labelableId: ID!, $labelIds: [ID!]!) {
addLabelsToLabelable(input: { labelableId: $labelableId, labelIds: $labelIds }) {
clientMutationId
}
}`, { labelableId: discussionNodeId, labelIds: labelIds });
}
} else {
await github.rest.issues.addLabels({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: issueOrPrNumber,
labels: labels
});
}
}
# Path-based Port labels: attach "Port <ip>" when a PR touches the matching
# dcd/hcd driver under src/portable/. Mapping lives in .github/labeler.yml.
label-port:
if: github.event_name == 'pull_request_target'
runs-on: ubuntu-latest
permissions:
contents: read
pull-requests: write
issues: write # allow auto-creating a Port label that doesn't exist yet
steps:
- uses: actions/labeler@v5
with:
configuration-path: .github/labeler.yml
# sync-labels so a Port label is removed once a PR no longer touches
# that driver (job reruns on synchronize). Only labels listed in
# labeler.yml are managed, so author-based Prio/Sponsor labels are untouched.
sync-labels: true
|