diff --git a/__test__/get-retry-options.test.ts b/__test__/get-retry-options.test.ts index 13fd3f7b..8d7bea67 100644 --- a/__test__/get-retry-options.test.ts +++ b/__test__/get-retry-options.test.ts @@ -4,7 +4,7 @@ import {getRetryOptions} from '../src/retry-options' describe('getRequestOptions', () => { test('retries disabled if retries == 0', async () => { - const [retryOptions, requestOptions] = getRetryOptions( + const [retryOptions, requestOptions, throttlingOptions] = getRetryOptions( 0, [400, 500, 502], [] @@ -14,10 +14,12 @@ describe('getRequestOptions', () => { expect(retryOptions.doNotRetry).toBeFalsy() expect(requestOptions?.retries).toBeFalsy() + expect(throttlingOptions?.onRateLimit).toBeFalsy() + expect(throttlingOptions?.onSecondaryRateLimit).toBeFalsy() }) test('properties set if retries > 0', async () => { - const [retryOptions, requestOptions] = getRetryOptions( + const [retryOptions, requestOptions, throttlingOptions] = getRetryOptions( 1, [400, 500, 502], [] @@ -27,10 +29,12 @@ describe('getRequestOptions', () => { expect(retryOptions.doNotRetry).toEqual([400, 500, 502]) expect(requestOptions?.retries).toEqual(1) + expect(throttlingOptions?.onRateLimit).toBeDefined() + expect(throttlingOptions?.onSecondaryRateLimit).toBeDefined() }) test('properties set if retries > 0', async () => { - const [retryOptions, requestOptions] = getRetryOptions( + const [retryOptions, requestOptions, throttlingOptions] = getRetryOptions( 1, [400, 500, 502], [] @@ -40,24 +44,36 @@ describe('getRequestOptions', () => { expect(retryOptions.doNotRetry).toEqual([400, 500, 502]) expect(requestOptions?.retries).toEqual(1) + expect(throttlingOptions?.onRateLimit).toBeDefined() + expect(throttlingOptions?.onSecondaryRateLimit).toBeDefined() }) test('retryOptions.doNotRetry not set if exemptStatusCodes isEmpty', async () => { - const [retryOptions, requestOptions] = getRetryOptions(1, [], []) + const [retryOptions, requestOptions, throttlingOptions] = getRetryOptions( + 1, + [], + [] + ) expect(retryOptions.enabled).toBe(true) expect(retryOptions.doNotRetry).toBeUndefined() expect(requestOptions?.retries).toEqual(1) + expect(throttlingOptions?.onRateLimit).toBeDefined() + expect(throttlingOptions?.onSecondaryRateLimit).toBeDefined() }) test('requestOptions does not override defaults from @actions/github', async () => { - const [retryOptions, requestOptions] = getRetryOptions(1, [], { - request: { - agent: 'default-user-agent' - }, - foo: 'bar' - }) + const [retryOptions, requestOptions, throttlingOptions] = getRetryOptions( + 1, + [], + { + request: { + agent: 'default-user-agent' + }, + foo: 'bar' + } + ) expect(retryOptions.enabled).toBe(true) expect(retryOptions.doNotRetry).toBeUndefined() @@ -65,5 +81,8 @@ describe('getRequestOptions', () => { expect(requestOptions?.retries).toEqual(1) expect(requestOptions?.agent).toEqual('default-user-agent') expect(requestOptions?.foo).toBeUndefined() // this should not be in the `options.request` object, but at the same level as `request` + + expect(throttlingOptions?.onRateLimit).toBeDefined() + expect(throttlingOptions?.onSecondaryRateLimit).toBeDefined() }) }) diff --git a/package-lock.json b/package-lock.json index 4b8d9185..0b9b0183 100644 --- a/package-lock.json +++ b/package-lock.json @@ -17,6 +17,7 @@ "@octokit/core": "^5.0.1", "@octokit/plugin-request-log": "^4.0.0", "@octokit/plugin-retry": "^6.0.1", + "@octokit/plugin-throttling": "^8.2.0", "@types/node": "^20.9.0" }, "devDependencies": { @@ -1345,9 +1346,9 @@ } }, "node_modules/@octokit/openapi-types": { - "version": "19.0.0", - "resolved": "https://registry.npmjs.org/@octokit/openapi-types/-/openapi-types-19.0.0.tgz", - "integrity": "sha512-PclQ6JGMTE9iUStpzMkwLCISFn/wDeRjkZFIKALpvJQNBGwDoYYi2fFvuHwssoQ1rXI5mfh6jgTgWuddeUzfWw==" + "version": "20.0.0", + "resolved": "https://registry.npmjs.org/@octokit/openapi-types/-/openapi-types-20.0.0.tgz", + "integrity": "sha512-EtqRBEjp1dL/15V7WiX5LJMIxxkdiGJnabzYx5Apx4FkQIFgAfKumXeYAqqJCj1s+BMX4cPFIFC4OLCR6stlnA==" }, "node_modules/@octokit/plugin-paginate-rest": { "version": "9.0.0", @@ -1404,6 +1405,21 @@ "@octokit/core": ">=5" } }, + "node_modules/@octokit/plugin-throttling": { + "version": "8.2.0", + "resolved": "https://registry.npmjs.org/@octokit/plugin-throttling/-/plugin-throttling-8.2.0.tgz", + "integrity": "sha512-nOpWtLayKFpgqmgD0y3GqXafMFuKcA4tRPZIfu7BArd2lEZeb1988nhWhwx4aZWmjDmUfdgVf7W+Tt4AmvRmMQ==", + "dependencies": { + "@octokit/types": "^12.2.0", + "bottleneck": "^2.15.3" + }, + "engines": { + "node": ">= 18" + }, + "peerDependencies": { + "@octokit/core": "^5.0.0" + } + }, "node_modules/@octokit/request": { "version": "8.1.1", "resolved": "https://registry.npmjs.org/@octokit/request/-/request-8.1.1.tgz", @@ -1459,11 +1475,11 @@ } }, "node_modules/@octokit/types": { - "version": "12.0.0", - "resolved": "https://registry.npmjs.org/@octokit/types/-/types-12.0.0.tgz", - "integrity": "sha512-EzD434aHTFifGudYAygnFlS1Tl6KhbTynEWELQXIbTY8Msvb5nEqTZIm7sbPEt4mQYLZwu3zPKVdeIrw0g7ovg==", + "version": "12.6.0", + "resolved": "https://registry.npmjs.org/@octokit/types/-/types-12.6.0.tgz", + "integrity": "sha512-1rhSOfRa6H9w4YwK0yrf5faDaDTb+yLyBUKOCV4xtCDB5VmIPqd/v9yr9o6SAzOAlRxMiRiCic6JVM1/kunVkw==", "dependencies": { - "@octokit/openapi-types": "^19.0.0" + "@octokit/openapi-types": "^20.0.0" } }, "node_modules/@pkgr/utils": { @@ -8372,9 +8388,9 @@ } }, "@octokit/openapi-types": { - "version": "19.0.0", - "resolved": "https://registry.npmjs.org/@octokit/openapi-types/-/openapi-types-19.0.0.tgz", - "integrity": "sha512-PclQ6JGMTE9iUStpzMkwLCISFn/wDeRjkZFIKALpvJQNBGwDoYYi2fFvuHwssoQ1rXI5mfh6jgTgWuddeUzfWw==" + "version": "20.0.0", + "resolved": "https://registry.npmjs.org/@octokit/openapi-types/-/openapi-types-20.0.0.tgz", + "integrity": "sha512-EtqRBEjp1dL/15V7WiX5LJMIxxkdiGJnabzYx5Apx4FkQIFgAfKumXeYAqqJCj1s+BMX4cPFIFC4OLCR6stlnA==" }, "@octokit/plugin-paginate-rest": { "version": "9.0.0", @@ -8408,6 +8424,15 @@ "bottleneck": "^2.15.3" } }, + "@octokit/plugin-throttling": { + "version": "8.2.0", + "resolved": "https://registry.npmjs.org/@octokit/plugin-throttling/-/plugin-throttling-8.2.0.tgz", + "integrity": "sha512-nOpWtLayKFpgqmgD0y3GqXafMFuKcA4tRPZIfu7BArd2lEZeb1988nhWhwx4aZWmjDmUfdgVf7W+Tt4AmvRmMQ==", + "requires": { + "@octokit/types": "^12.2.0", + "bottleneck": "^2.15.3" + } + }, "@octokit/request": { "version": "8.1.1", "resolved": "https://registry.npmjs.org/@octokit/request/-/request-8.1.1.tgz", @@ -8461,11 +8486,11 @@ } }, "@octokit/types": { - "version": "12.0.0", - "resolved": "https://registry.npmjs.org/@octokit/types/-/types-12.0.0.tgz", - "integrity": "sha512-EzD434aHTFifGudYAygnFlS1Tl6KhbTynEWELQXIbTY8Msvb5nEqTZIm7sbPEt4mQYLZwu3zPKVdeIrw0g7ovg==", + "version": "12.6.0", + "resolved": "https://registry.npmjs.org/@octokit/types/-/types-12.6.0.tgz", + "integrity": "sha512-1rhSOfRa6H9w4YwK0yrf5faDaDTb+yLyBUKOCV4xtCDB5VmIPqd/v9yr9o6SAzOAlRxMiRiCic6JVM1/kunVkw==", "requires": { - "@octokit/openapi-types": "^19.0.0" + "@octokit/openapi-types": "^20.0.0" } }, "@pkgr/utils": { diff --git a/package.json b/package.json index b3033b70..1d2d8afb 100644 --- a/package.json +++ b/package.json @@ -47,6 +47,7 @@ "@octokit/core": "^5.0.1", "@octokit/plugin-request-log": "^4.0.0", "@octokit/plugin-retry": "^6.0.1", + "@octokit/plugin-throttling": "^8.2.0", "@types/node": "^20.9.0" }, "devDependencies": { @@ -64,4 +65,4 @@ "ts-jest": "^29.1.1", "typescript": "^5.2.2" } -} \ No newline at end of file +} diff --git a/src/main.ts b/src/main.ts index 81df4f0e..78e5cc71 100644 --- a/src/main.ts +++ b/src/main.ts @@ -6,6 +6,7 @@ import * as glob from '@actions/glob' import * as io from '@actions/io' import {requestLog} from '@octokit/plugin-request-log' import {retry} from '@octokit/plugin-retry' +import {throttling, ThrottlingOptions} from '@octokit/plugin-throttling' import {RequestRequestOptions} from '@octokit/types' import {callAsyncFunction} from './async-function' import {RetryOptions, getRetryOptions, parseNumberArray} from './retry-options' @@ -20,6 +21,7 @@ type Options = { baseUrl?: string previews?: string[] retry?: RetryOptions + throttle?: ThrottlingOptions request?: RequestRequestOptions } @@ -33,7 +35,7 @@ async function main(): Promise { const exemptStatusCodes = parseNumberArray( core.getInput('retry-exempt-status-codes') ) - const [retryOpts, requestOpts] = getRetryOptions( + const [retryOpts, requestOpts, throttleOpts] = getRetryOptions( retries, exemptStatusCodes, defaultGitHubOptions @@ -44,7 +46,8 @@ async function main(): Promise { userAgent: userAgent || undefined, previews: previews ? previews.split(',') : undefined, retry: retryOpts, - request: requestOpts + request: requestOpts, + throttle: throttleOpts } // Setting `baseUrl` to undefined will prevent the default value from being used @@ -53,7 +56,7 @@ async function main(): Promise { opts.baseUrl = baseUrl } - const github = getOctokit(token, opts, retry, requestLog) + const github = getOctokit(token, opts, retry, requestLog, throttling) const script = core.getInput('script', {required: true}) // Using property/value shorthand on `require` (e.g. `{require}`) causes compilation errors. diff --git a/src/retry-options.ts b/src/retry-options.ts index 683beaad..10b949c7 100644 --- a/src/retry-options.ts +++ b/src/retry-options.ts @@ -1,5 +1,6 @@ import * as core from '@actions/core' import {OctokitOptions} from '@octokit/core/dist-types/types' +import {ThrottlingOptions} from '@octokit/plugin-throttling' import {RequestRequestOptions} from '@octokit/types' export type RetryOptions = { @@ -11,9 +12,13 @@ export function getRetryOptions( retries: number, exemptStatusCodes: number[], defaultOptions: OctokitOptions -): [RetryOptions, RequestRequestOptions | undefined] { +): [ + RetryOptions, + RequestRequestOptions | undefined, + ThrottlingOptions | undefined +] { if (retries <= 0) { - return [{enabled: false}, defaultOptions.request] + return [{enabled: false}, defaultOptions.request, undefined] } const retryOptions: RetryOptions = { @@ -32,6 +37,21 @@ export function getRetryOptions( retries } + const throttleOptions: ThrottlingOptions = { + onRateLimit: (retryAfter, options, octokit, retryCount) => { + core.debug( + `Request quota exhausted for request ${options.method} ${options.url}` + ) + return retryCount < retries + }, + onSecondaryRateLimit: (retryAfter, options, octokit, retryCount) => { + core.debug( + `Secondary quota detected for request ${options.method} ${options.url}` + ) + return retryCount < retries + } + } + core.debug( `GitHub client configured with: (retries: ${ requestOptions.retries @@ -40,7 +60,7 @@ export function getRetryOptions( })` ) - return [retryOptions, requestOptions] + return [retryOptions, requestOptions, throttleOptions] } export function parseNumberArray(listString: string): number[] {