36
36
const INCLUDE_POLL_MERMAID_CHART = true ; // Set to false to disable Mermaid pie chart for polls
37
37
const RATE_LIMIT_SLEEP_SECONDS = 0.5 ; // Default sleep duration between API calls to avoid rate limiting
38
38
const DISCUSSION_PROCESSING_DELAY_SECONDS = 5 ; // Delay between processing discussions
39
- const RATE_LIMIT_RETRY_DELAY_SECONDS = 60 ; // Delay when hitting rate limits before retrying
40
- const MAX_RETRIES = 3 ; // Maximum number of retries for failed operations
39
+ const MAX_RETRIES = 3 ; // Maximum number of retries for failed operations (rate limits handled automatically by Octokit)
41
40
42
41
const { Octokit } = require ( "octokit" ) ;
43
42
@@ -134,15 +133,39 @@ if (!process.env.TARGET_TOKEN) {
134
133
const SOURCE_API_URL = process . env . SOURCE_API_URL || 'https://api.github.com' ;
135
134
const TARGET_API_URL = process . env . TARGET_API_URL || 'https://api.github.com' ;
136
135
137
- // Initialize Octokit instances
136
+ // Configure throttling for rate limit handling
137
+ const throttleOptions = {
138
+ onRateLimit : ( retryAfter , options , octokit ) => {
139
+ warn ( `Primary rate limit exhausted for request ${ options . method } ${ options . url } ` ) ;
140
+ if ( options . request . retryCount <= 2 ) {
141
+ warn ( `Retrying after ${ retryAfter } seconds (retry ${ options . request . retryCount + 1 } /3)` ) ;
142
+ return true ;
143
+ }
144
+ error ( `Max retries reached for rate limit` ) ;
145
+ return false ;
146
+ } ,
147
+ onSecondaryRateLimit : ( retryAfter , options , octokit ) => {
148
+ warn ( `Secondary rate limit detected for request ${ options . method } ${ options . url } ` ) ;
149
+ if ( options . request . retryCount <= 2 ) {
150
+ warn ( `Retrying after ${ retryAfter } seconds (retry ${ options . request . retryCount + 1 } /3)` ) ;
151
+ return true ;
152
+ }
153
+ error ( `Max retries reached for secondary rate limit` ) ;
154
+ return false ;
155
+ }
156
+ } ;
157
+
158
+ // Initialize Octokit instances with throttling enabled
138
159
const sourceOctokit = new Octokit ( {
139
160
auth : process . env . SOURCE_TOKEN ,
140
- baseUrl : SOURCE_API_URL
161
+ baseUrl : SOURCE_API_URL ,
162
+ throttle : throttleOptions
141
163
} ) ;
142
164
143
165
const targetOctokit = new Octokit ( {
144
166
auth : process . env . TARGET_TOKEN ,
145
- baseUrl : TARGET_API_URL
167
+ baseUrl : TARGET_API_URL ,
168
+ throttle : throttleOptions
146
169
} ) ;
147
170
148
171
// Tracking variables
@@ -178,39 +201,6 @@ async function rateLimitSleep(seconds = RATE_LIMIT_SLEEP_SECONDS) {
178
201
await sleep ( seconds ) ;
179
202
}
180
203
181
- function isRateLimitError ( err ) {
182
- const message = err . message ?. toLowerCase ( ) || '' ;
183
- const status = err . status || 0 ;
184
-
185
- // Check for primary rate limit (403 with rate limit message)
186
- if ( status === 403 && ( message . includes ( 'rate limit' ) || message . includes ( 'api rate limit' ) ) ) {
187
- return true ;
188
- }
189
-
190
- // Check for secondary rate limit (403 with abuse/secondary message)
191
- if ( status === 403 && ( message . includes ( 'secondary' ) || message . includes ( 'abuse' ) ) ) {
192
- return true ;
193
- }
194
-
195
- // Check for retry-after header indication
196
- if ( message . includes ( 'retry after' ) || message . includes ( 'try again later' ) ) {
197
- return true ;
198
- }
199
-
200
- return false ;
201
- }
202
-
203
- async function handleRateLimitError ( err , attemptNumber ) {
204
- if ( isRateLimitError ( err ) ) {
205
- const waitTime = RATE_LIMIT_RETRY_DELAY_SECONDS * attemptNumber ; // Exponential-ish backoff
206
- warn ( `Rate limit detected (attempt ${ attemptNumber } ). Waiting ${ waitTime } s before retry...` ) ;
207
- warn ( `Error details: ${ err . message } ` ) ;
208
- await sleep ( waitTime ) ;
209
- return true ;
210
- }
211
- return false ;
212
- }
213
-
214
204
function formatPollData ( poll ) {
215
205
if ( ! poll || ! poll . options || poll . options . nodes . length === 0 ) {
216
206
return '' ;
@@ -1039,7 +1029,7 @@ async function processDiscussionsPage(sourceOctokit, targetOctokit, owner, repo,
1039
1029
// Check if discussion is pinned
1040
1030
const isPinned = pinnedDiscussionIds . has ( discussion . id ) ;
1041
1031
1042
- // Create discussion with retry logic
1032
+ // Create discussion (Octokit throttling plugin handles rate limits automatically)
1043
1033
let newDiscussion = null ;
1044
1034
let createSuccess = false ;
1045
1035
@@ -1065,22 +1055,17 @@ async function processDiscussionsPage(sourceOctokit, targetOctokit, owner, repo,
1065
1055
log ( `✓ Created discussion #${ discussion . number } : '${ discussion . title } '` ) ;
1066
1056
1067
1057
} catch ( err ) {
1068
- // Handle rate limit errors with retry
1069
- const shouldRetry = await handleRateLimitError ( err , attempt ) ;
1058
+ // Octokit throttling handles rate limits; this catches other errors
1059
+ error ( `Failed to create discussion # ${ discussion . number } : ' ${ discussion . title } ' - ${ err . message } ` ) ;
1070
1060
1071
- if ( ! shouldRetry ) {
1072
- // Not a rate limit error, or unrecoverable error
1073
- error ( `Failed to create discussion #${ discussion . number } : '${ discussion . title } ' - ${ err . message } ` ) ;
1074
- if ( attempt < MAX_RETRIES ) {
1075
- warn ( `Retrying (attempt ${ attempt + 1 } /${ MAX_RETRIES } )...` ) ;
1076
- await sleep ( 5 ) ; // Brief pause before retry
1077
- } else {
1078
- error ( `Max retries (${ MAX_RETRIES } ) reached. Skipping discussion #${ discussion . number } .` ) ;
1079
- skippedDiscussions ++ ;
1080
- break ;
1081
- }
1061
+ if ( attempt < MAX_RETRIES ) {
1062
+ warn ( `Retrying (attempt ${ attempt + 1 } /${ MAX_RETRIES } ) in 5 seconds...` ) ;
1063
+ await sleep ( 5 ) ;
1064
+ } else {
1065
+ error ( `Max retries (${ MAX_RETRIES } ) reached. Skipping discussion #${ discussion . number } .` ) ;
1066
+ skippedDiscussions ++ ;
1067
+ break ;
1082
1068
}
1083
- // If shouldRetry is true, loop will continue to next attempt
1084
1069
}
1085
1070
}
1086
1071
0 commit comments