Last active
October 23, 2023 06:25
-
-
Save wojtekmaj/ebfafc017304d95fc3a7adccf89d43d6 to your computer and use it in GitHub Desktop.
GitHub monthly issues stats
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
import fs from 'node:fs'; | |
import { asyncForEach, asyncForEachStrict } from '@wojtekmaj/async-array-utils'; | |
import chalk from 'chalk'; | |
import { endOfMonth, formatISO, startOfMonth } from 'date-fns'; | |
const CACHE_DIR = '.cache'; | |
const GITHUB_API_URL = 'https://api.github.com'; | |
const GITHUB_TOKEN = process.env.GITHUB_TOKEN; | |
const DEBUG = process.env.DEBUG === 'true'; | |
if (!GITHUB_TOKEN) { | |
throw new Error('GITHUB_TOKEN environment variable must be set'); | |
} | |
function log(...args: unknown[]) { | |
if (DEBUG) { | |
console.log(...args); | |
} | |
} | |
type SearchResultsPage = { | |
total_count: number; | |
}; | |
class NetworkError extends Error { | |
status: number; | |
constructor(status: number, statusText: string) { | |
super(statusText); | |
this.name = 'NetworkError'; | |
this.status = status; | |
} | |
} | |
/** | |
* Fetches a URL and caches it in the .cache directory. On subsequent calls, it | |
* will return the cached version if it exists. | |
*/ | |
async function fetchWithCache(input: string | URL, init?: RequestInit): Promise<string> { | |
// log(chalk.gray`Getting %s…`, input); | |
const cacheKey = input | |
.toString() | |
.replace(/[^a-z0-9]/gi, '_') | |
.toLowerCase(); | |
const cachePath = `${CACHE_DIR}/fetch/${cacheKey}`; | |
const cacheExists = fs.existsSync(cachePath); | |
// If the cache exists, return it | |
if (cacheExists) { | |
// log(chalk.gray` Retrieving from cache`); | |
const cachedFile = fs.readFileSync(cachePath, 'utf-8'); | |
return cachedFile; | |
} | |
// Otherwise, fetch the URL | |
// log(chalk.gray` Fetching from network`, input); | |
const response = await fetch(input, init); | |
if (!response.ok) { | |
throw new NetworkError(response.status, response.statusText); | |
} | |
const requestText = await response.text(); | |
// Ensure the cache directory exists | |
fs.mkdirSync(CACHE_DIR, { recursive: true }); | |
// Write the response to the cache | |
fs.writeFileSync(cachePath, requestText); | |
return requestText; | |
} | |
const startDate = new Date(2020, 0, 1); | |
const endDate = new Date(); | |
// List of monthly date ranges | |
const ranges: [Date, Date][] = []; | |
// Generate the list of monthly date ranges | |
for (let date = startDate; date <= endDate; date.setMonth(date.getMonth() + 1)) { | |
const start = startOfMonth(date); | |
const end = endOfMonth(date); | |
ranges.push([start, end]); | |
} | |
const results: number[] = []; | |
await (DEBUG ? asyncForEachStrict : asyncForEach)(ranges, async ([date1, date2], i) => { | |
const date1String = formatISO(date1, { representation: 'date' }); | |
const date2String = formatISO(date2, { representation: 'date' }); | |
log(chalk.gray`Fetching issues from %s to %s`, date1String, date2String); | |
const url = new URL(`${GITHUB_API_URL}/search/issues`); | |
url.searchParams.set( | |
'q', | |
`"this makes the require call ambiguous and unsound" created:${date1String}..${date2String}`, | |
); | |
const rawResponse = await fetchWithCache(url, { | |
headers: { | |
Authorization: `token ${process.env.GITHUB_TOKEN}`, | |
}, | |
}); | |
const response = JSON.parse(rawResponse) as SearchResultsPage; | |
results[i] = response.total_count; | |
}); | |
log(results); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment