async function getMaintainers({ github, context }) { const collaborators = await github.paginate(github.rest.repos.listCollaborators, { owner: context.repo.owner, repo: context.repo.repo, }); return collaborators .filter( ({ role_name, login }) => ["admin", "maintain"].includes(role_name) || [ "alkispoly-db", "AveshCSingh", "danielseong1", "smoorjani", "SomtochiUmeh", "xsh310", ].includes(login) ) .map(({ login }) => login) .sort(); } async function getLinkedIssues({ github, owner, repo, prNumber }) { const query = ` query($owner: String!, $repo: String!, $number: Int!) { repository(owner: $owner, name: $repo) { pullRequest(number: $number) { createdAt closingIssuesReferences(first: 10) { nodes { number createdAt } } } } } `; const result = await github.graphql(query, { owner, repo, number: prNumber, }); return { prCreatedAt: result.repository.pullRequest.createdAt, issues: result.repository.pullRequest.closingIssuesReferences.nodes, }; } const SEVEN_DAYS_MS = 7 * 24 * 60 * 60 * 1000; async function findFirstMaintainerInIssueComments({ github, owner, repo, issueNumber, maintainers, }) { for await (const response of github.paginate.iterator(github.rest.issues.listComments, { owner, repo, issue_number: issueNumber, })) { for (const comment of response.data) { if (maintainers.has(comment.user.login)) { return comment.user.login; } } } return null; } async function findMaintainerFromLinkedIssue({ github, owner, repo, prNumber, maintainers }) { const { prCreatedAt, issues: linkedIssues } = await getLinkedIssues({ github, owner, repo, prNumber, }); if (linkedIssues.length !== 1 || !prCreatedAt) { return null; } const linkedIssue = linkedIssues[0]; const prCreatedDate = new Date(prCreatedAt); const issueCreatedDate = new Date(linkedIssue.createdAt); if (prCreatedDate - issueCreatedDate > SEVEN_DAYS_MS) { return null; } return findFirstMaintainerInIssueComments({ github, owner, repo, issueNumber: linkedIssue.number, maintainers, }); } module.exports = async ({ github, context, skipAssignment = false }) => { const { owner, repo } = context.repo; const maintainers = new Set(await getMaintainers({ github, context })); // Get current time minus 200 minutes to look for recent comments and PRs const lookbackTime = new Date(Date.now() - 200 * 60 * 1000); // Use search API to find recently updated open PRs const searchQuery = `repo:${owner}/${repo} is:pr is:open updated:>=${lookbackTime.toISOString()}`; const searchResults = await github.paginate(github.rest.search.issuesAndPullRequests, { q: searchQuery, sort: "updated", order: "desc", }); console.log(`Scanning ${searchResults.length} recently updated PRs`); for (const pr of searchResults) { // Get recent comments and reviews const issueComments = await github.rest.issues.listComments({ owner, repo, issue_number: pr.number, since: lookbackTime.toISOString(), }); const reviews = await github.rest.pulls.listReviews({ owner, repo, pull_number: pr.number, }); // Filter reviews by lookback time and extract authors const recentReviews = reviews.data.filter((r) => new Date(r.submitted_at) > lookbackTime); const commentAuthors = new Set([ ...issueComments.data.map((c) => c.user.login), ...recentReviews.map((r) => r.user.login), ]); // Use Set operations to find maintainers to assign const prAuthor = pr.user.login; const currentAssignees = new Set(pr.assignees.map((a) => a.login)); const excludeSet = new Set([prAuthor, ...currentAssignees]); let maintainersToAssign = [...commentAuthors.intersection(maintainers).difference(excludeSet)]; // Fall back to linked issue comments if no maintainers found from recent PR activity if (maintainersToAssign.length === 0) { const maintainer = await findMaintainerFromLinkedIssue({ github, owner, repo, prNumber: pr.number, maintainers, }); if (maintainer && !excludeSet.has(maintainer)) { maintainersToAssign = [maintainer]; } } if (maintainersToAssign.length === 0) { continue; } // Assign maintainers if (!skipAssignment) { await github.rest.issues.addAssignees({ owner, repo, issue_number: pr.number, assignees: maintainersToAssign, }); } console.log( `${skipAssignment ? "[DRY RUN] Would assign" : "Assigned"} [${maintainersToAssign.join( ", " )}] to PR #${pr.number}` ); } console.log("Scan completed"); };