Files
react/.github/workflows/scripts/ghstack/check_permissions.js
T
Lauren Tan e160ec8736 [ci] Add ghstack /land bot
Summary:

Test Plan:

Reviewers:

Subscribers:

Tasks:

Tags:
2025-04-08 11:30:09 -04:00

221 lines
4.7 KiB
JavaScript

#!/usr/bin/env node
'use strict';
const {spawnSync} = require('child_process');
const process = require('process');
const {Octokit} = require('@octokit/rest');
const OWNER = 'facebook';
const REPO = 'react';
async function must(cond, msg, octokit, issue_number) {
if (!cond) {
console.error(msg);
try {
await octokit.issues.createComment({
owner: OWNER,
repo: REPO,
issue_number,
body: `ghstack bot failed: ${msg}`,
});
} catch (error) {
console.error('Failed to post comment:', error);
}
process.exit(1);
}
}
async function main() {
const GITHUB_TOKEN = process.env.GITHUB_TOKEN;
if (!GITHUB_TOKEN) {
console.error('GITHUB_TOKEN environment variable is not set.');
process.exit(1);
}
const octokit = new Octokit({auth: GITHUB_TOKEN});
const prNumber = parseInt(process.argv[2]);
const headRef = process.argv[3];
console.log(headRef);
await must(
headRef && /^gh\/[A-Za-z0-9-]+\/[0-9]+\/head$/.test(headRef),
'Not a ghstack PR',
octokit,
OWNER,
REPO,
prNumber
);
const origRef = headRef.replace('/head', '/orig');
console.log(':: Fetching newest main...');
let result = spawnSync('git', ['fetch', 'origin', 'main'], {
stdio: 'inherit',
});
await must(
result.status === 0,
"Can't fetch main",
octokit,
OWNER,
REPO,
prNumber
);
console.log(':: Fetching orig branch...');
result = spawnSync('git', ['fetch', 'origin', origRef], {stdio: 'inherit'});
await must(
result.status === 0,
"Can't fetch orig branch",
octokit,
OWNER,
REPO,
prNumber
);
result = spawnSync(
'git',
['log', 'FETCH_HEAD...$(git merge-base FETCH_HEAD origin/main)'],
{shell: true}
);
const out = result.stdout.toString();
await must(
result.status === 0,
'`git log` command failed!',
octokit,
OWNER,
REPO,
prNumber
);
const regex =
/Pull Request resolved: https:\/\/github\.com\/.*?\/pull\/([0-9]+)/g;
const prNumbers = [];
let match;
while ((match = regex.exec(out)) !== null) {
prNumbers.push(parseInt(match[1], 10));
}
console.log(prNumbers);
await must(
prNumbers.length && prNumbers[0] === prNumber,
'Extracted PR numbers not seems right!',
octokit,
OWNER,
REPO,
prNumber
);
for (const n of prNumbers) {
process.stdout.write(`:: Checking PR status #${n}... `);
let prObj;
try {
const {data} = await octokit.pulls.get({
owner: OWNER,
repo: REPO,
pull_number: n,
});
prObj = data;
} catch (error) {
await must(
false,
'Error Getting PR Object!',
octokit,
OWNER,
REPO,
prNumber
);
}
let reviews;
try {
const {data} = await octokit.request(
'GET /repos/{owner}/{repo}/pulls/{pull_number}/reviews',
{
owner: OWNER,
repo: REPO,
pull_number: prNumber,
headers: {
'X-GitHub-Api-Version': '2022-11-28',
},
}
);
reviews = data;
} catch (error) {
await must(
false,
'Error Getting PR Reviews!',
octokit,
OWNER,
REPO,
prNumber
);
}
let approved = false;
for (const review of reviews) {
if (review.state === 'COMMENTED') continue;
await must(
['APPROVED', 'DISMISSED'].includes(review.state),
`@${review.user.login} has stamped PR #${n} \`${review.state}\`, please resolve it first!`,
octokit,
OWNER,
REPO,
prNumber
);
if (review.state === 'APPROVED') {
approved = true;
}
}
await must(
approved,
`PR #${n} is not approved yet!`,
octokit,
OWNER,
REPO,
prNumber
);
let checkruns;
try {
const {data} = await octokit.checks.listForRef({
owner: OWNER,
repo: REPO,
ref: prObj.head.sha,
});
checkruns = data;
} catch (error) {
await must(
false,
'Error getting check runs status!',
octokit,
OWNER,
REPO,
prNumber
);
}
for (const cr of checkruns.check_runs) {
const status = cr.conclusion ? cr.conclusion : cr.status;
const name = cr.name;
if (name === 'Copilot for PRs') continue;
await must(
['success', 'neutral'].includes(status),
`PR #${n} check-run \`${name}\`'s status \`${status}\` is not success!`,
octokit,
OWNER,
REPO,
prNumber
);
}
console.log('SUCCESS!');
}
console.log(':: All PRs are ready to be landed!');
}
main().catch(err => {
console.error('Unexpected error:', err);
process.exit(1);
});