Skip to content

src/github-errors.ts

43 lines · 1.4 KB · TypeScript
Raw
const TRANSIENT_GITHUB_STATUSES = new Set([
	408, 429, 500, 502, 503, 504, 520, 521, 522, 523, 524,
])

export function githubFailureStatus(error: unknown): number | null {
	if (!error || typeof error !== 'object') return null
	const response = 'response' in error ? error.response : null
	if (response && typeof response === 'object' && 'status' in response) {
		const status = response.status
		if (typeof status === 'number' && Number.isFinite(status)) return status
	}
	const message = error instanceof Error ? error.message : ''
	const match = message.match(/GitHub request failed with (\d+)/)
	return match ? Number(match[1]) : null
}

export function isTransientGithubFailure(error: unknown): boolean {
	if (error instanceof Error && error.name === 'GitHubRequestTimeoutError') {
		return true
	}
	const status = githubFailureStatus(error)
	return status !== null && TRANSIENT_GITHUB_STATUSES.has(status)
}

export async function withTransientGithubRetry<T>(
	run: () => Promise<T>,
	options: { retries?: number; delayMs?: number } = {},
): Promise<T> {
	const retries = options.retries ?? 1
	const delayMs = options.delayMs ?? 400
	let attempt = 0
	while (true) {
		try {
			return await run()
		} catch (error) {
			if (attempt >= retries || !isTransientGithubFailure(error)) throw error
			attempt += 1
			if (delayMs > 0) {
				await new Promise((resolve) => setTimeout(resolve, delayMs))
			}
		}
	}
}