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