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) <noreply@anthropic.com>
This commit is contained in:
Ha Thach
2026-06-04 21:59:59 +07:00
committed by GitHub
parent 38debe00ba
commit ddc065dc9f

View File

@ -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.`);