skillhub/.github/scripts/share-reward.ts
South Drifter 66acc6e57b
chore: add GitHub-reward form, scripts & actions (#221)
* chore: add GitHub-reward form, scripts & actions

* fix: Deno system permissions in GitHub actions

* fix: 3 detail bugs
2026-04-07 19:02:59 +08:00

125 lines
3.2 KiB
TypeScript

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}`;