From ddc065dc9fd929fcc94b0993c3af4b56877b1cd6 Mon Sep 17 00:00:00 2001 From: Ha Thach Date: Thu, 4 Jun 2026 21:59:59 +0700 Subject: [PATCH] Remove Sponsor Triage workflow (migrated to hathach/hathach) (#3675) This personal automation now lives in the hathach/hathach repo alongside the other personal project-sync workflows; it has no place in the tinyusb library. Co-authored-by: Claude Opus 4.8 (1M context) --- .github/workflows/sponsor-triage.yml | 165 --------------------------- 1 file changed, 165 deletions(-) delete mode 100644 .github/workflows/sponsor-triage.yml diff --git a/.github/workflows/sponsor-triage.yml b/.github/workflows/sponsor-triage.yml deleted file mode 100644 index 48b2ac7cc..000000000 --- a/.github/workflows/sponsor-triage.yml +++ /dev/null @@ -1,165 +0,0 @@ -name: Sponsor Triage - -# Periodically add open issues and PRs opened by sponsors (public and private) to the private "Sponsor Triage" project board -# Requires a PAT in secret SPONSOR_TOKEN with scopes: -# - project (write project items / fields) -# - read:org (search org issues, check Adafruit membership) -# - read:user / sponsors (read own sponsorships incl. private) - -on: - schedule: - - cron: '0 */6 * * *' # every 6 hours - workflow_dispatch: - -concurrency: - group: sponsor-triage - cancel-in-progress: false - -jobs: - sync: - runs-on: ubuntu-latest - steps: - - name: Sync sponsor issues to project board - uses: actions/github-script@v8 - with: - github-token: ${{ secrets.SPONSOR_TOKEN }} - script: | - const PROJECT_OWNER = 'hathach'; - const PROJECT_NUMBER = 3; - // Where to look for sponsor-authored open issues. - const SEARCH_SCOPES = ['org:adafruit', 'user:hathach']; - - const tierFromMonthly = (m) => { - if (m >= 512) return 'QWORD'; - if (m >= 128) return 'DWORD'; - if (m >= 32) return 'Word'; - if (m >= 8) return 'Byte'; - if (m >= 2) return 'Bit'; - return null; - }; - - // --- 1. Resolve project id + field/option ids (by name, never hardcoded) --- - const proj = await github.graphql(` - query($owner: String!, $number: Int!) { - user(login: $owner) { - projectV2(number: $number) { - id - fields(first: 50) { - nodes { - ... on ProjectV2SingleSelectField { - id name options { id name } - } - } - } - } - } - }`, { owner: PROJECT_OWNER, number: PROJECT_NUMBER }); - - const project = proj.user.projectV2; - const fieldByName = {}; - for (const f of project.fields.nodes) { - if (f && f.name) { - fieldByName[f.name] = { id: f.id, options: {} }; - for (const o of (f.options || [])) fieldByName[f.name].options[o.name] = o.id; - } - } - const tierField = fieldByName['Tier']; - const visField = fieldByName['Visibility']; - - // --- 2. Build sponsor map: loginLower -> { tier, visibility } --- - const sponsors = new Map(); - - // 2a. GitHub Sponsors of the owner, including private ones. - let after = null; - for (let page = 0; page < 20; page++) { - const res = await github.graphql(` - query($owner: String!, $after: String) { - user(login: $owner) { - sponsorshipsAsMaintainer(includePrivate: true, first: 100, after: $after) { - pageInfo { hasNextPage endCursor } - nodes { - privacyLevel - tier { monthlyPriceInDollars } - sponsorEntity { - ... on User { login } - ... on Organization { login } - } - } - } - } - }`, { owner: PROJECT_OWNER, after }); - const conn = res.user.sponsorshipsAsMaintainer; - for (const n of conn.nodes) { - const login = n.sponsorEntity && n.sponsorEntity.login; - if (!login) continue; - const tier = tierFromMonthly((n.tier && n.tier.monthlyPriceInDollars) || 0); - const visibility = n.privacyLevel === 'PRIVATE' ? 'Private' : 'Public'; - sponsors.set(login.toLowerCase(), { login, tier, visibility }); - } - if (!conn.pageInfo.hasNextPage) break; - after = conn.pageInfo.endCursor; - } - - core.info(`Resolved ${sponsors.size} sponsor account(s).`); - if (sponsors.size === 0) return; - - // 2b. Adafruit org members get the Adafruit tier regardless of $ amount. - for (const s of sponsors.values()) { - try { - const m = await github.rest.orgs.checkMembershipForUser({ org: 'adafruit', username: s.login }); - if (m.status === 204) s.tier = 'Adafruit'; - } catch (e) { /* not an Adafruit member */ } - } - - // --- 3. Find each sponsor's open issues and PRs in the search scopes --- - // (search type ISSUE returns both issues and pull requests) - const found = new Map(); // contentId -> { tier, visibility } - for (const s of sponsors.values()) { - for (const scope of SEARCH_SCOPES) { - const q = `${scope} is:open author:${s.login}`; - try { - const res = await github.graphql(` - query($q: String!) { - search(query: $q, type: ISSUE, first: 100) { - nodes { ... on Issue { id } ... on PullRequest { id } } - } - }`, { q }); - for (const node of res.search.nodes) { - if (node && node.id && !found.has(node.id)) { - found.set(node.id, { tier: s.tier, visibility: s.visibility }); - } - } - } catch (e) { - core.warning(`Search failed for one scope: ${e.message}`); - } - } - } - core.info(`Found ${found.size} open sponsor issue(s) across scopes.`); - - // --- 4. Add to board + set Tier / Visibility (idempotent) --- - const setField = async (itemId, field, optionName) => { - if (!field || !optionName) return; - const optId = field.options[optionName]; - if (!optId) return; - await github.graphql(` - mutation($p: ID!, $i: ID!, $f: ID!, $o: String!) { - updateProjectV2ItemFieldValue(input: { - projectId: $p, itemId: $i, fieldId: $f, value: { singleSelectOptionId: $o } - }) { projectV2Item { id } } - }`, { p: project.id, i: itemId, f: field.id, o: optId }); - }; - - let added = 0; - for (const [contentId, meta] of found) { - const res = await github.graphql(` - mutation($p: ID!, $c: ID!) { - addProjectV2ItemById(input: { projectId: $p, contentId: $c }) { - item { id } - } - }`, { p: project.id, c: contentId }); - const itemId = res.addProjectV2ItemById.item.id; - await setField(itemId, tierField, meta.tier); - await setField(itemId, visField, meta.visibility); - added++; - } - core.info(`Synced ${added} item(s) to the board.`);