11import * as fs from "fs" ;
22import * as path from "path" ;
33
4+ const wait = ( ms : number ) => new Promise ( resolve => setTimeout ( resolve , ms ) ) ;
5+ const MAX_RETRIES = 3 ;
6+
47/**
58 * Reads the content of the given relative file paths and returns the total number of lines of code.
69 *
@@ -24,6 +27,49 @@ const getLinesOfCodeToExclude = async (filePaths: string[]): Promise<number> =>
2427 return locToExclude ;
2528}
2629
30+ /**
31+ * Fetches data from a given GitHub API URL with retry logic for rate limiting.
32+ *
33+ * This function attempts to fetch data from the specified GitHub API URL. In case of a 403 response due to rate limiting,
34+ * it will wait until the rate limit resets and retry the request up to a maximum number of retries.
35+ *
36+ * @param retryCount The current retry attempt count. Automatically managed by the function.
37+ * @param url The GitHub API URL to fetch data from.
38+ * @returns A promise that resolves to an object containing the HTTP response, calculated lines of code, and raw data.
39+ * @throws Any error encountered during the fetch process after exhausting retry attempts.
40+ */
41+ async function fetchGithubData ( retryCount = 0 , url : string ) : Promise < any > {
42+ try {
43+ const response = await fetch ( url ) ;
44+
45+ // Check if rate limited
46+ if ( response . status === 403 && response . headers . get ( 'x-ratelimit-remaining' ) === '0' ) {
47+ if ( retryCount < MAX_RETRIES ) {
48+ // Get reset time from headers
49+ const resetTime = parseInt ( response . headers . get ( 'x-ratelimit-reset' ) || '0' ) * 1000 ;
50+ const waitTime = Math . max ( resetTime - Date . now ( ) , 0 ) ;
51+
52+ // Wait for rate limit to reset (with some buffer)
53+ await wait ( waitTime + 1000 ) ;
54+
55+ // Retry the request
56+ return fetchGithubData ( retryCount + 1 , url ) ;
57+ }
58+ }
59+
60+ const data = await response ?. json ( ) ;
61+ let linesOfCode = Array . isArray ( data ) ? data . reduce ( ( acc : number , curr : number [ ] ) => acc + ( curr [ 1 ] - Math . abs ( curr [ 2 ] ) ) , 0 ) : null ;
62+
63+ return { response, linesOfCode, data } ;
64+ } catch ( error ) {
65+ if ( retryCount < MAX_RETRIES ) {
66+ // Exponential backoff: 2^retryCount seconds
67+ await wait ( Math . pow ( 2 , retryCount ) * 1000 ) ;
68+ return fetchGithubData ( retryCount + 1 , url ) ;
69+ }
70+ throw error ;
71+ }
72+ }
2773
2874/**
2975 * Gets the total number of lines of code in a given Github repository.
@@ -40,13 +86,22 @@ const getRepoLinesOfCode = async (owner: string, repo: string, excludeFilePaths:
4086 const url = `https://api.github.com/repos/${ owner } /${ repo } /stats/code_frequency` ;
4187
4288 try {
43- const response = await fetch ( url ) ;
44- const data = await response ?. json ( ) ;
45-
46- const linesOfCode = Array . isArray ( data ) ? data . reduce ( ( acc : number , curr : number [ ] ) => acc + ( curr [ 1 ] - Math . abs ( curr [ 2 ] ) ) , 0 ) : null ;
89+ const { response, linesOfCode, data } = await fetchGithubData ( 0 , url ) ;
4790
91+ if ( ! response . ok ) {
92+ return `Github API error: ${ response . status } ${ response . statusText } ` ;
93+ }
94+
95+ if ( ! data ) {
96+ return "Github API error: Received empty response" ;
97+ }
98+
99+ if ( ! Array . isArray ( data ) ) {
100+ return `Github API error - Invalid data format received: ${ JSON . stringify ( data ) } ` ;
101+ }
102+
48103 if ( ! linesOfCode ) {
49- return "Github - No data found for the given repository " ;
104+ return "Github - Rate limit exceeded. Please try again later. " ;
50105 }
51106
52107 if ( excludeFilePaths . length > 0 ) {
0 commit comments