mirror of
https://github.com/facebook/react.git
synced 2025-11-01 09:12:30 +00:00
221 lines
4.7 KiB
JavaScript
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);
|
|
});
|