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 " 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