Files
tinyusb/.github/workflows/labeler.yml
Ha Thach ac32feafeb ci(labeler): auto-apply Port labels from changed driver files (#3673)
* ci(labeler): auto-apply Port labels from changed driver files

Add path-based labeling so a PR touching a dcd/hcd driver under
src/portable/ gets the matching "Port <ip>" label automatically.
2026-06-04 15:18:40 +07:00

183 lines
7.5 KiB
YAML

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