Skip to content

Instantly share code, notes, and snippets.

@jordanrastrick
Last active April 9, 2024 03:14
Show Gist options
  • Save jordanrastrick/0e67b911ef8a2eeaa1bd4846c3623870 to your computer and use it in GitHub Desktop.
Save jordanrastrick/0e67b911ef8a2eeaa1bd4846c3623870 to your computer and use it in GitHub Desktop.
/*
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