Last active
April 9, 2024 03:14
-
-
Save jordanrastrick/0e67b911ef8a2eeaa1bd4846c3623870 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* | |
Gets all your PRs from GitHub. A simple-ish wrapper around the | |
GitHub cli which doesn't seem to paginate (?) or offer output | |
formats besides JSON (and personally I like to review these | |
things in Excel) | |
* Example usage: | |
node github-prs --from 2022-01-01 --to 2022-12-31 > prs.csv | |
* I can't be bothered including a package.json :P | |
You need to do: | |
> npm install lodash papaparse yargs moment | |
or equivalent. | |
Because who doesn't love a JS dependency. | |
* You also need to have installed the GitHub cli tool: | |
> brew install gh | |
And have authenticated it: | |
> gh auth login | |
(then follow the prompts). | |
* N.B. moment is probably not needed as a dependency as in theory | |
we're dealing only with ISO date strings that can be compared | |
directly for ordering. But that feels slightly icky. | |
* The script prints the CSV data directly to stdout in the UNIX | |
spirit of things, redirect or pipe it as you will. | |
* I hit rate limits in an earlier version of the script that made | |
requests in parallel but I don't think it should be an issue now | |
everything is serial. | |
*/ | |
const util = require('util'); | |
const exec = util.promisify(require('child_process').exec); | |
const _ = require('lodash') | |
const papa = require('papaparse') | |
const yargs = require('yargs') | |
const moment = require('moment') | |
function sleep(ms) { | |
return new Promise((resolve) => { | |
setTimeout(resolve, ms) | |
}) | |
} | |
async function getArgv() { | |
return await yargs | |
.option('from', { | |
type: 'date', | |
description: 'PR created from this date (as an ISO string)', | |
demandOption: true, | |
}) | |
.option('to', { | |
type: 'date', | |
description: 'PR created to this date (as an ISO string) - defaults to now', | |
demandOption: false, | |
}) | |
.option('json', { | |
type: 'boolean', | |
description: 'Output JSON instead of CSV', | |
demandOption: false | |
}) | |
.parse() | |
} | |
async function prsBefore(createdBefore) { | |
const delay = sleep(8500) | |
const cmd = [`gh search prs`, | |
`--sort=created`, | |
`--author=@me`, | |
`--created "<=${createdBefore.format()}"`, | |
`--json=createdAt,repository,url,title,body`, | |
].join(' ') | |
const { stdout } = await exec(cmd); | |
await delay | |
return JSON.parse(stdout) | |
} | |
async function getPrDump(from, to) { | |
let before = to | |
const allPrs = [] | |
while (before.isAfter(from)) { | |
const prs = await prsBefore(before) | |
allPrs.push(...prs) | |
if (prs.length > 0) { | |
const { createdAt } = prs[prs.length - 1] | |
before = moment(createdAt, moment.ISO_8601) | |
} else { | |
break | |
} | |
} | |
return allPrs | |
} | |
function processPrs(prs, from, to) { | |
return _(prs) | |
.uniqBy(pr => `${pr.title}::${pr.createdAt}`) | |
.filter(pr => | |
moment(pr.createdAt, moment.ISO_8601).isBetween(from, to) | |
) | |
.sortBy('createdAt') | |
.map(pr => Object.assign(pr, { repository: pr.repository.name })) | |
.value() | |
} | |
async function main() { | |
const { from: fromString, to: toString, json } = await getArgv() | |
const from = moment(fromString, moment.ISO_8601) | |
const to = toString ? moment(toString, moment.ISO_8601) : moment() | |
const allPrs = await getPrDump(from, to) | |
const processed = processPrs(allPrs, from, to) | |
const output = json ? | |
JSON.stringify(processed) : | |
papa.unparse(processed) | |
console.log(output) | |
} | |
main() |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment