diff --git a/package.json b/package.json index dcb8e9d..66ae5c2 100644 --- a/package.json +++ b/package.json @@ -33,6 +33,7 @@ }, "dependencies": { "@octokit/rest": "^15.6.2", + "bottleneck": "^2.3.1", "octonode": "^0.9.2" }, "devDependencies": { diff --git a/src/gh.ts b/src/gh.ts index 6a8fce2..e1c7e99 100644 --- a/src/gh.ts +++ b/src/gh.ts @@ -1,26 +1,79 @@ -import rest = require('@octokit/rest'); +import * as Octokit from '@octokit/rest'; import assert = require('assert'); +import Bottleneck from 'bottleneck'; -const octokit = new rest(); +const octokit = new Octokit() as OctokitMod; + +// Add rate limiter +const limiter = new Bottleneck({ + maxConcurrent: 10, + minTime: 50, +}); + +const noop = () => Promise.resolve(); +octokit.hook.before('request', limiter.schedule.bind(limiter, noop)); + +// Add key and secret for query octokit.authenticate({ type: 'oauth', key: '05e5f5ec65387c49137b', secret: '2228539a48032f0622d6c12a66f56253d0a30d60', }); +interface RequestOptions { + method: string; + url: string; + headers: any; + query?: string; + variables?: Variables; +} + +interface Result { + headers: { + status: string; + }; +} + +interface OctokitError { + code: number; + status: string; +} + +interface OctokitMod extends Octokit { + // The following are added because Octokit does not expose the hook.error, hook.before, and hook.after methods + hook: { + error: ( + when: 'request', + callback: (error: OctokitError, options: RequestOptions) => void + ) => void; + before: ( + when: 'request', + callback: (result: Result, options: RequestOptions) => void + ) => void; + after: ( + when: 'request', + callback: (result: Result, options: RequestOptions) => void + ) => void; + }; +} + +interface Variables { + [key: string]: any; +} + interface ResponseWithDataArray { data: T[]; } interface ResponseWithMetaLink { meta: { - link: string + link: string; }; } -interface ResponseWithDataArrayAndMeta extends ResponseWithDataArray, ResponseWithMetaLink { - -} +interface ResponseWithDataArrayAndMeta + extends ResponseWithDataArray, + ResponseWithMetaLink {} interface ResponseFromGetUserRepo extends ResponseWithMetaLink { data: RepoFromGetUserRepo[]; @@ -110,19 +163,20 @@ interface RepoNameWithParentRepo { interface RepoNameWithBranchesAndParent extends RepoNameWithBranches, - RepoNameWithParentRepo { } + RepoNameWithParentRepo {} -const username = 'bendtherules'; +const username = 'rousan'; interface ObjectWithPerPage { per_page?: number; } async function paginate( - method: (args: TFirstParam) => Promise>, + method: ( + args: TFirstParam + ) => Promise>, args: TFirstParam ): Promise> { - // Set per_page args.per_page = 100; @@ -136,25 +190,27 @@ async function paginate( } return { - data + data, }; } async function fetchRepoNameWithBranches( repoName: string ): Promise { - const params: rest.ReposGetBranchesParams = { + const params: Octokit.ReposGetBranchesParams = { owner: username, repo: repoName, }; - const branchesResponse: ResponseWithDataArray = - await paginate( - (tmpFirstParam: rest.ReposGetBranchesParams): Promise => { - return octokit.repos.getBranches(tmpFirstParam) as any as Promise; - }, - params - ); + const branchesResponse: ResponseWithDataArray< + BranchFromGetBranches + > = await paginate((tmpFirstParam: Octokit.ReposGetBranchesParams): Promise< + ResponseFromGetBranches + > => { + return (octokit.repos.getBranches(tmpFirstParam) as any) as Promise< + ResponseFromGetBranches + >; + }, params); return { repoName, @@ -254,35 +310,42 @@ async function fetchNoneOfForkBranchesIsAhead( async function fetchUserIsNotContributor( repoName: string ): Promise { - const params: rest.ReposGetContributorsParams = { + const params: Octokit.ReposGetContributorsParams = { owner: username, repo: repoName, anon: '0', }; - const responseFromGetContributors: ResponseWithDataArray = - await paginate( - async (tmpFirstParam: rest.ReposGetContributorsParams): Promise> => { - // Modify getContributors to return empty contributor data array instead of undefined for empty repos - const response = await (octokit.repos.getContributors(tmpFirstParam) as any as Promise); + const responseFromGetContributors: ResponseWithDataArray< + OwnerFromGetContributors + > = await paginate( + async ( + tmpFirstParam: Octokit.ReposGetContributorsParams + ): Promise> => { + // Modify getContributors to return empty contributor data array instead of undefined for empty repos + const response = await ((octokit.repos.getContributors( + tmpFirstParam + ) as any) as Promise); - let dataNormalized = response.data; - if (dataNormalized === undefined) { - dataNormalized = []; - } + let dataNormalized = response.data; + if (dataNormalized === undefined) { + dataNormalized = []; + } - const responseNormalized: ResponseWithDataArrayAndMeta = { - data: dataNormalized, - meta: response.meta - }; + const responseNormalized: ResponseWithDataArrayAndMeta< + OwnerFromGetContributors + > = { + data: dataNormalized, + meta: response.meta, + }; - return responseNormalized; - }, - params - ); + return responseNormalized; + }, + params + ); const contributors = responseFromGetContributors.data; - + const matchingContributor = contributors.find( tmpContributor => tmpContributor.login === username ); @@ -295,17 +358,20 @@ async function fetchUserIsNotContributor( } async function fetchUnusedForkedRepos() { - const params: rest.ReposGetForUserParams = { + const params: Octokit.ReposGetForUserParams = { username, - } + }; - const repos: ResponseWithDataArray = - await paginate( - (tmpFirstParam: rest.ReposGetForUserParams): Promise => { - return octokit.repos.getForUser(tmpFirstParam) as any as Promise; - }, - params - ); + const repos: ResponseWithDataArray = await paginate( + ( + tmpFirstParam: Octokit.ReposGetForUserParams + ): Promise => { + return (octokit.repos.getForUser(tmpFirstParam) as any) as Promise< + ResponseFromGetUserRepo + >; + }, + params + ); const forkedRepoNames = repos.data .filter(repo => repo.fork) diff --git a/yarn.lock b/yarn.lock index c6203f0..66c9058 100644 --- a/yarn.lock +++ b/yarn.lock @@ -991,6 +991,10 @@ boom@2.x.x: dependencies: hoek "2.x.x" +bottleneck@^2.3.1: + version "2.3.1" + resolved "https://registry.yarnpkg.com/bottleneck/-/bottleneck-2.3.1.tgz#16292092ff040ccbf2d05d0a09d69eab7b1c46bc" + brace-expansion@^1.1.7: version "1.1.11" resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-1.1.11.tgz#3c7fcbf529d87226f3d2f52b966ff5271eb441dd"