import "npm:array-unique-proposal"; import { components } from "npm:@octokit/openapi-types"; import { $, argv, YAML } from "npm:zx"; import { Reward } from "./type.ts"; $.verbose = true; const [ repositoryOwner, repositoryName, issueNumber, payer, // GitHub username of the payer (provided by workflow, defaults to issue creator) currency, reward, ] = argv._; interface PRMeta { author: components["schemas"]["simple-user"]; assignees: components["schemas"]["simple-user"][]; } const graphqlQuery = ` query($owner: String!, $name: String!, $number: Int!) { repository(owner: $owner, name: $name) { issue(number: $number) { closedByPullRequestsReferences(first: 10) { nodes { url merged mergeCommit { oid } } } } } } `; const PR_DATA = await $`gh api graphql \ -f query=${graphqlQuery} \ -f owner=${repositoryOwner} \ -f name=${repositoryName} \ -F number=${issueNumber} \ --jq '.data.repository.issue.closedByPullRequestsReferences.nodes[] | select(.merged == true) | {url: .url, mergeCommitSha: .mergeCommit.oid}' | head -n 1`; const prData = PR_DATA.text().trim(); if (!prData) throw new ReferenceError("No merged PR is found for the given issue number."); const { url: PR_URL, mergeCommitSha } = JSON.parse(prData); if (!PR_URL || !mergeCommitSha) throw new Error("Missing required fields in PR data"); console.table({ PR_URL, mergeCommitSha }); const { author, assignees }: PRMeta = await ( await $`gh pr view ${PR_URL} --json author,assignees` ).json(); function isBotUser(login: string) { const lowerLogin = login.toLowerCase(); return ( lowerLogin.includes("copilot") || lowerLogin.includes("[bot]") || lowerLogin === "github-actions[bot]" || lowerLogin.endsWith("[bot]") ); } // Filter out Bot users from the list const allUsers = [ author.login, ...assignees.map(({ login }) => login), ].uniqueBy(); const users = allUsers.filter((login) => !isBotUser(login)); console.log(`All users: ${allUsers.join(", ")}`); console.log(`Filtered users (excluding bots): ${users.join(", ")}`); if (!users[0]) throw new ReferenceError( "No real users found (all users are bots). Skipping reward distribution.", ); const rewardNumber = parseFloat(reward); if (isNaN(rewardNumber) || rewardNumber <= 0) throw new RangeError( `Reward amount is not a valid number, can not proceed with reward distribution. Received reward value: ${reward}`, ); const averageReward = (rewardNumber / users.length).toFixed(2); const list: Reward[] = users.map((login) => ({ issue: `#${issueNumber}`, payer: `@${payer}`, payee: `@${login}`, currency, reward: parseFloat(averageReward), })); const listText = YAML.stringify(list); console.log(listText); await $`git config user.name "github-actions[bot]"`; await $`git config user.email "github-actions[bot]@users.noreply.github.com"`; await $`git tag -a "reward-${issueNumber}" ${mergeCommitSha} -m ${listText}`; await $`git push origin --tags --no-verify`; await $`git config unset user.name`; await $`git config unset user.email`; const commentBody = `## Reward data \`\`\`yml ${listText} \`\`\` `; await $`gh issue comment ${issueNumber} --body ${commentBody}`;