Skip to content

Commit

Permalink
- make error handling robust
Browse files Browse the repository at this point in the history
- add retries and handle GitHub API rate limiting errors
  • Loading branch information
d-beloved committed Jan 26, 2025
1 parent ea57293 commit b5c8522
Show file tree
Hide file tree
Showing 2 changed files with 61 additions and 6 deletions.
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "git-repo-lines-of-code",
"version": "1.0.2",
"version": "1.0.3",
"description": "Get your Github repository's lines of code with an option of excluding any files from the stat",
"main": "dist/index.js",
"types": "dist/index.d.ts",
Expand Down
65 changes: 60 additions & 5 deletions src/index.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
import * as fs from "fs";
import * as path from "path";

const wait = (ms: number) => new Promise(resolve => setTimeout(resolve, ms));
const MAX_RETRIES = 3;

/**
* Reads the content of the given relative file paths and returns the total number of lines of code.
*
Expand All @@ -24,6 +27,49 @@ const getLinesOfCodeToExclude = async (filePaths: string[]): Promise<number> =>
return locToExclude;
}

/**
* Fetches data from a given GitHub API URL with retry logic for rate limiting.
*
* This function attempts to fetch data from the specified GitHub API URL. In case of a 403 response due to rate limiting,
* it will wait until the rate limit resets and retry the request up to a maximum number of retries.
*
* @param retryCount The current retry attempt count. Automatically managed by the function.
* @param url The GitHub API URL to fetch data from.
* @returns A promise that resolves to an object containing the HTTP response, calculated lines of code, and raw data.
* @throws Any error encountered during the fetch process after exhausting retry attempts.
*/
async function fetchGithubData(retryCount = 0, url: string): Promise<any> {
try {
const response = await fetch(url);

// Check if rate limited
if (response.status === 403 && response.headers.get('x-ratelimit-remaining') === '0') {
if (retryCount < MAX_RETRIES) {
// Get reset time from headers
const resetTime = parseInt(response.headers.get('x-ratelimit-reset') || '0') * 1000;
const waitTime = Math.max(resetTime - Date.now(), 0);

// Wait for rate limit to reset (with some buffer)
await wait(waitTime + 1000);

// Retry the request
return fetchGithubData(retryCount + 1, url);
}
}

const data = await response?.json();
let linesOfCode = Array.isArray(data) ? data.reduce((acc: number, curr: number[]) => acc + (curr[1] - Math.abs(curr[2])), 0) : null;

return { response, linesOfCode, data };
} catch (error) {
if (retryCount < MAX_RETRIES) {
// Exponential backoff: 2^retryCount seconds
await wait(Math.pow(2, retryCount) * 1000);
return fetchGithubData(retryCount + 1, url);
}
throw error;
}
}

/**
* Gets the total number of lines of code in a given Github repository.
Expand All @@ -40,13 +86,22 @@ const getRepoLinesOfCode = async (owner: string, repo: string, excludeFilePaths:
const url = `https://api.github.com/repos/${owner}/${repo}/stats/code_frequency`;

try {
const response = await fetch(url);
const data = await response?.json();

const linesOfCode = Array.isArray(data) ? data.reduce((acc: number, curr: number[]) => acc + (curr[1] - Math.abs(curr[2])), 0) : null;
const { response, linesOfCode, data } = await fetchGithubData(0, url);

if (!response.ok) {
return `Github API error: ${response.status} ${response.statusText}`;
}

if (!data) {
return "Github API error: Received empty response";
}

if (!Array.isArray(data)) {
return `Github API error - Invalid data format received: ${JSON.stringify(data)}`;
}

if (!linesOfCode) {
return "Github - No data found for the given repository";
return "Github - Rate limit exceeded. Please try again later.";
}

if (excludeFilePaths.length > 0) {
Expand Down

0 comments on commit b5c8522

Please sign in to comment.