| |
| |
| |
| |
| |
|
|
| import type { IssueState, PrState, Patch, LabelOp, Issue } from "./types.js"; |
|
|
| |
| |
| |
|
|
| export const BOUNTY_GENERIC = "bounty"; |
| export const BOUNTY_CLAIMED = "bounty: claimed"; |
| export const BOUNTY_REWARDED = "bounty: rewarded"; |
|
|
| |
| |
| |
| export const VALUE_LABEL_RE = /^bounty: (?:π° )?\$\d/; |
|
|
| |
| |
| |
|
|
| |
| export function isBountyValue(label: string): boolean { |
| return VALUE_LABEL_RE.test(label); |
| } |
|
|
| |
| |
| export function linkedIssueNumbers(body: string | null): number[] { |
| const numbers: number[] = []; |
| const text = body ?? ""; |
| const re = /(?:closes?|fix(?:es)?|resolves?)\s+#(\d+)/gi; |
| let m: RegExpExecArray | null; |
| while ((m = re.exec(text)) !== null) { |
| numbers.push(parseInt(m[1]!, 10)); |
| } |
| return numbers; |
| } |
|
|
| |
| function diff( |
| target: number, |
| current: Set<string>, |
| desired: Set<string>, |
| comment?: string, |
| meta?: { title?: string; url?: string } |
| ): LabelOp | null { |
| const add = [...desired].filter((l) => !current.has(l)); |
| const remove = [...current].filter((l) => !desired.has(l)); |
| if (add.length === 0 && remove.length === 0 && !comment) return null; |
| return { target, title: meta?.title, url: meta?.url, add, remove, comment }; |
| } |
|
|
| |
| |
| |
|
|
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| export function computeIssuePatch({ issue, currentLabels }: IssueState): Patch { |
| const desired = new Set(currentLabels); |
|
|
| const hasValueLabel = [...currentLabels].some(isBountyValue); |
| const isAssigned = issue.assignees.length > 0; |
|
|
| |
| if (hasValueLabel) { |
| desired.add(BOUNTY_GENERIC); |
| } else { |
| desired.delete(BOUNTY_GENERIC); |
| } |
|
|
| |
| if (hasValueLabel && isAssigned) { |
| desired.add(BOUNTY_CLAIMED); |
| } else { |
| desired.delete(BOUNTY_CLAIMED); |
| } |
|
|
| const op = diff(issue.number, currentLabels, desired, undefined, { |
| title: issue.title, |
| url: issue.html_url, |
| }); |
| return { ops: op ? [op] : [] }; |
| } |
|
|
| |
| |
| |
|
|
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| export function computePrPatch({ pr, currentLabels, linkedIssues }: PrState): Patch { |
| const ops: LabelOp[] = []; |
|
|
| const prDesired = new Set(currentLabels); |
|
|
| |
| const labelsToPropagate: string[] = []; |
| for (const issue of linkedIssues) { |
| const issueValueLabels = issue.labels.map((l) => l.name).filter(isBountyValue); |
| for (const label of issueValueLabels) { |
| if (!prDesired.has(label)) { |
| labelsToPropagate.push(label); |
| prDesired.add(label); |
| } |
| } |
| } |
|
|
| const prHasValueLabel = [...prDesired].some(isBountyValue); |
|
|
| if (pr.merged && prHasValueLabel) { |
| |
| prDesired.add(BOUNTY_REWARDED); |
|
|
| const prOp = diff(pr.number, currentLabels, prDesired, undefined, { |
| title: pr.title, |
| url: pr.html_url, |
| }); |
| if (prOp) ops.push(prOp); |
|
|
| |
| for (const issue of linkedIssues) { |
| const issueCurrent = new Set(issue.labels.map((l) => l.name)); |
| const issueHasValue = [...issueCurrent].some(isBountyValue); |
| if (!issueHasValue) continue; |
|
|
| const issueDesired = new Set(issueCurrent); |
| issueDesired.add(BOUNTY_REWARDED); |
| issueDesired.delete(BOUNTY_CLAIMED); |
|
|
| const op = diff(issue.number, issueCurrent, issueDesired, undefined, { |
| title: issue.title, |
| url: issue.html_url, |
| }); |
| if (op) ops.push(op); |
| } |
| } else { |
| |
| const prOp = diff(pr.number, currentLabels, prDesired, undefined, { |
| title: pr.title, |
| url: pr.html_url, |
| }); |
| if (prOp) ops.push(prOp); |
|
|
| |
| if (labelsToPropagate.length > 0) { |
| for (const issue of linkedIssues) { |
| const hadValueLabel = issue.labels.map((l) => l.name).some(isBountyValue); |
| if (hadValueLabel) { |
| ops.push({ |
| target: issue.number, |
| title: issue.title, |
| url: issue.html_url, |
| add: [], |
| remove: [], |
| comment: `PR [#${pr.number}](${pr.html_url}) has been opened for this bounty by @${pr.user.login}.`, |
| }); |
| } |
| } |
| } |
| } |
|
|
| return { ops }; |
| } |
|
|