diff --git a/__tests__/greeter-spec.ts b/__tests__/greeter-spec.ts deleted file mode 100644 index f4d6d21..0000000 --- a/__tests__/greeter-spec.ts +++ /dev/null @@ -1,6 +0,0 @@ -import { Greeter } from '../src/greeter'; - -test('Should greet with message', () => { - const greeter = new Greeter('friend'); - expect(greeter.greet()).toBe('Bonjour, friend!'); -}); diff --git a/src/gh.ts b/src/gh.ts index db3f968..9033270 100644 --- a/src/gh.ts +++ b/src/gh.ts @@ -2,6 +2,7 @@ import * as Octokit from '@octokit/rest'; import assert = require('assert'); import Bottleneck from 'bottleneck'; +// +++ General init +++ const octokit = new Octokit() as OctokitMod; // Add rate limiter @@ -20,6 +21,10 @@ octokit.authenticate({ secret: '2228539a48032f0622d6c12a66f56253d0a30d60', }); +// --- End General init --- + +// +++ All typescript definitions +++ + interface RequestOptions { method: string; url: string; @@ -73,7 +78,7 @@ interface ResponseWithMetaLink { interface ResponseWithDataArrayAndMeta extends ResponseWithDataArray, - ResponseWithMetaLink {} + ResponseWithMetaLink { } interface ResponseFromGetUserRepo extends ResponseWithMetaLink { data: RepoFromGetUserRepo[]; @@ -163,362 +168,374 @@ interface RepoNameWithParentRepo { interface RepoNameWithBranchesAndParent extends RepoNameWithBranches, - RepoNameWithParentRepo {} - -const username = 'rousan'; + RepoNameWithParentRepo { } interface ObjectWithPerPage { per_page?: number; } -async function paginate( - method: ( - args: TFirstParam - ) => Promise>, - args: TFirstParam -): Promise> { - // Set per_page - args.per_page = 100; +// --- End all typescript definitions --- - let response: ResponseWithDataArrayAndMeta = await method(args); +class GithubDetectUnusedRepos { + private username: string; - // Concat all data - let { data } = response; - while (octokit.hasNextPage(response)) { - response = await octokit.getNextPage(response); - data = data.concat(response.data); + constructor(username: string) { + this.username = username; } - return { - data, - }; -} + public async fetchUnusedForkedRepos() { + const params: Octokit.ReposGetForUserParams = { + this.username, + }; -async function fetchRepoNameWithBranches( - repoName: string -): Promise { - const params: Octokit.ReposGetBranchesParams = { - owner: username, - repo: repoName, - }; + const repos: ResponseWithDataArray = await this.paginate( + ( + tmpFirstParam: Octokit.ReposGetForUserParams + ): Promise => { + return (octokit.repos.getForUser(tmpFirstParam) as any) as Promise< + ResponseFromGetUserRepo + >; + }, + params + ); - const branchesResponse: ResponseWithDataArray< - BranchFromGetBranches - > = await paginate((tmpFirstParam: Octokit.ReposGetBranchesParams): Promise< - ResponseFromGetBranches - > => { - return (octokit.repos.getBranches(tmpFirstParam) as any) as Promise< - ResponseFromGetBranches - >; - }, params); + const forkedRepoNames = repos.data + .filter(repo => repo.fork) + .map(repo => repo.name); - return { - repoName, - branches: branchesResponse.data, - }; -} + // tslint:disable-next-line:no-console + console.log(forkedRepoNames); -async function fetchRepoNameWithParentRepo( - forkedRepoName: string -): Promise { - const responseRepoDetails: ResponseFromGetRepo = await octokit.repos.get({ - owner: username, - repo: forkedRepoName, - }); + const allPromiseRepoNameWithBranches = forkedRepoNames.map(repoName => { + return this.fetchRepoNameWithBranches(repoName); + }); - const repoDetails = responseRepoDetails.data; + const allPromiseRepoNameWithParentRepo = forkedRepoNames.map( + forkedRepoName => { + return this.fetchRepoNameWithParentRepo(forkedRepoName); + } + ); - return { - repoName: forkedRepoName, - parentRepo: repoDetails.parent, - }; -} + const allRepoNameWithBranches = await Promise.all( + allPromiseRepoNameWithBranches + ); + const allRepoNameWithParentRepo = await Promise.all( + allPromiseRepoNameWithParentRepo + ); -async function fetchForkBranchIsNotAhead( - parentRepoOwner: string, - parentRepoName: string, - forkedRepoOwner: string, - forkedRepoName: string, - forkedBranchName: string, - parentBranchName?: string -): Promise { - let commitObject: ResponseFromCompareCommits; + // Both the above list (of objects) have same number of items and + // items in the same index (of both lists) have same value for repoName key. - if (parentBranchName === undefined) { - parentBranchName = forkedBranchName; - } + // So join them together into one list of objects (each object containing branches and parent repo) - try { - commitObject = ((await octokit.repos.compareCommits({ - // both owner and repo can be either parent or forked, for this usecase - owner: parentRepoOwner, - repo: parentRepoName, - base: `${parentRepoOwner}:${parentBranchName}`, - head: `${forkedRepoOwner}:${forkedBranchName}`, - })) as any) as ResponseFromCompareCommits; - } catch { - const fallBackParentBranchName = 'master'; + assert.strictEqual( + allRepoNameWithBranches.length, + allRepoNameWithParentRepo.length, + 'Length of `allRepoNameWithBranches` and `allRepoNameWithParentRepo` should be same' + ); - if (parentBranchName !== fallBackParentBranchName) { - return fetchForkBranchIsNotAhead( - parentRepoOwner, - parentRepoName, - forkedRepoOwner, - forkedRepoName, - forkedBranchName, - fallBackParentBranchName + const allRepoNameWithBranchesAndParent: RepoNameWithBranchesAndParent[] = []; + { + const tmpLength = allRepoNameWithBranches.length; + + for (let index = 0; index < tmpLength; index++) { + const tmpRepoNameWithBranches = allRepoNameWithBranches[index]; + const tmpRepoNameWithParentRepo = allRepoNameWithParentRepo[index]; + + assert.strictEqual( + tmpRepoNameWithBranches.repoName, + tmpRepoNameWithParentRepo.repoName, + 'Reponame should be same for objects stored at same index in `allRepoNameWithBranches` and `allRepoNameWithParentRepo`' + ); + + const repoName = tmpRepoNameWithBranches.repoName; + + const combinedObject: RepoNameWithBranchesAndParent = { + repoName, + branches: tmpRepoNameWithBranches.branches, + parentRepo: tmpRepoNameWithParentRepo.parentRepo, + }; + + allRepoNameWithBranchesAndParent.push(combinedObject); + } + } + + const allRepoWithFlagTillStep4: RepoNameWithUnusedFlag[] = []; + + { + const allPromiseRepoWithFlagFromCommit = allRepoNameWithBranchesAndParent.map( + (repoInfo: RepoNameWithBranchesAndParent) => { + return this.fetchNoneOfForkBranchesIsAhead(repoInfo); + } ); + + const allPromiseRepoWithFlagFromForkContrib = allRepoNameWithBranchesAndParent.map( + ({ repoName }) => { + return this.fetchUserIsNotContributor(this.username, repoName); + } + ); + + const allPromiseRepoWithFlagFromParentContrib = allRepoNameWithBranchesAndParent.map( + ({ repoName, parentRepo }) => { + return this.fetchUserIsNotContributor(parentRepo.owner.login, repoName); + } + ); + + const allRepoWithFlagFromCommit: RepoNameWithUnusedFlag[] = await Promise.all( + allPromiseRepoWithFlagFromCommit + ); + + const allRepoWithFlagFromForkContrib: RepoNameWithUnusedFlag[] = await Promise.all( + allPromiseRepoWithFlagFromForkContrib + ); + + const allRepoWithFlagFromParentContrib: RepoNameWithUnusedFlag[] = await Promise.all( + allPromiseRepoWithFlagFromParentContrib + ); + + { + assert.strictEqual( + allRepoWithFlagFromCommit.length, + allRepoWithFlagFromForkContrib.length, + 'Length of `allRepoWithFlagFromCommit` and `allRepoWithFlagFromContrib` should be same' + ); + + const repoCount = allRepoWithFlagFromCommit.length; + + for (let index = 0; index < repoCount; index++) { + const tmpObjFromCommit = allRepoWithFlagFromCommit[index]; + const tmpObjFromForkContrib = allRepoWithFlagFromForkContrib[index]; + const tmpObjFromParentContrib = allRepoWithFlagFromParentContrib[index]; + + assert.strictEqual( + tmpObjFromCommit.repoName, + tmpObjFromForkContrib.repoName, + 'Reponame from same index of `allRepoWithFlagFromCommit` and `allRepoWithFlagFromContrib` should be same' + ); + + assert.strictEqual( + tmpObjFromForkContrib.repoName, + tmpObjFromParentContrib.repoName, + 'Reponame from same index of `tmpObjFromForkContrib` and `tmpObjFromParentContrib` should be same' + ); + + const tmpRepoName = tmpObjFromCommit.repoName; + + allRepoWithFlagTillStep4.push({ + repoName: tmpRepoName, + unused: + tmpObjFromCommit.unused && + tmpObjFromForkContrib.unused && + tmpObjFromParentContrib.unused, + }); + } + } + } + + const unusedRepoNames = allRepoWithFlagTillStep4 + .filter(tmp => tmp.unused) + .map(tmp => tmp.repoName); + + return unusedRepoNames; + } + + private async paginate( + method: ( + args: TFirstParam + ) => Promise>, + args: TFirstParam + ): Promise> { + // Set per_page + args.per_page = 100; + + let response: ResponseWithDataArrayAndMeta = await method(args); + + // Concat all data + let { data } = response; + while (octokit.hasNextPage(response)) { + response = await octokit.getNextPage(response); + data = data.concat(response.data); + } + + return { + data, + }; + } + + private async fetchRepoNameWithBranches( + repoName: string + ): Promise { + const params: Octokit.ReposGetBranchesParams = { + owner: this.username, + repo: repoName, + }; + + const branchesResponse: ResponseWithDataArray< + BranchFromGetBranches + > = await this.paginate((tmpFirstParam: Octokit.ReposGetBranchesParams): Promise< + ResponseFromGetBranches + > => { + return (octokit.repos.getBranches(tmpFirstParam) as any) as Promise< + ResponseFromGetBranches + >; + }, params); + + return { + repoName, + branches: branchesResponse.data, + }; + } + + private async fetchRepoNameWithParentRepo( + forkedRepoName: string + ): Promise { + const responseRepoDetails: ResponseFromGetRepo = await octokit.repos.get({ + owner: this.username, + repo: forkedRepoName, + }); + + const repoDetails = responseRepoDetails.data; + + return { + repoName: forkedRepoName, + parentRepo: repoDetails.parent, + }; + } + + private async fetchForkBranchIsNotAhead( + parentRepoOwner: string, + parentRepoName: string, + forkedRepoOwner: string, + forkedRepoName: string, + forkedBranchName: string, + parentBranchName?: string + ): Promise { + let commitObject: ResponseFromCompareCommits; + + if (parentBranchName === undefined) { + parentBranchName = forkedBranchName; + } + + try { + commitObject = ((await octokit.repos.compareCommits({ + // both owner and repo can be either parent or forked, for this usecase + owner: parentRepoOwner, + repo: parentRepoName, + base: `${parentRepoOwner}:${parentBranchName}`, + head: `${forkedRepoOwner}:${forkedBranchName}`, + })) as any) as ResponseFromCompareCommits; + } catch { + const fallBackParentBranchName = 'master'; + + if (parentBranchName !== fallBackParentBranchName) { + return this.fetchForkBranchIsNotAhead( + parentRepoOwner, + parentRepoName, + forkedRepoOwner, + forkedRepoName, + forkedBranchName, + fallBackParentBranchName + ); + } else { + return false; + } + } + + if (commitObject.data.ahead_by === 0) { + return true; } else { return false; } } - if (commitObject.data.ahead_by === 0) { - return true; - } else { - return false; - } -} + // Async determine if parent repo contains all commits which are + // currently set as HEAD in each branch of the original repo + private async fetchNoneOfForkBranchesIsAhead( + repoInfo: RepoNameWithBranchesAndParent + ): Promise { + const allPromiseBranchUnused = repoInfo.branches.map(tmpBranch => { + return this.fetchForkBranchIsNotAhead( + repoInfo.parentRepo.owner.login, + repoInfo.parentRepo.name, + this.username, + repoInfo.repoName, + tmpBranch.name + ); + }); -// Async determine if parent repo contains all commits which are -// currently set as HEAD in each branch of the original repo -async function fetchNoneOfForkBranchesIsAhead( - repoInfo: RepoNameWithBranchesAndParent -): Promise { - const allPromiseBranchUnused = repoInfo.branches.map(tmpBranch => { - return fetchForkBranchIsNotAhead( - repoInfo.parentRepo.owner.login, - repoInfo.parentRepo.name, - username, - repoInfo.repoName, - tmpBranch.name + const allBranchUnused = await Promise.all(allPromiseBranchUnused); + + const everyBranchUnused = allBranchUnused.every( + tmpBoolean => tmpBoolean === true ); - }); - const allBranchUnused = await Promise.all(allPromiseBranchUnused); + return { + repoName: repoInfo.repoName, + unused: everyBranchUnused, + }; + } - const everyBranchUnused = allBranchUnused.every( - tmpBoolean => tmpBoolean === true - ); + private async fetchUserIsNotContributor( + repoOwner: string, + repoName: string + ): Promise { + const params: Octokit.ReposGetContributorsParams = { + owner: repoOwner, + repo: repoName, + anon: '0', + }; - return { - repoName: repoInfo.repoName, - unused: everyBranchUnused, - }; -} + const responseFromGetContributors: ResponseWithDataArray< + OwnerFromGetContributors + > = await this.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); -async function fetchUserIsNotContributor( - repoOwner: string, - repoName: string -): Promise { - const params: Octokit.ReposGetContributorsParams = { - owner: repoOwner, - repo: repoName, - anon: '0', - }; + let dataNormalized = response.data; + if (dataNormalized === undefined) { + dataNormalized = []; + } - 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); + const responseNormalized: ResponseWithDataArrayAndMeta< + OwnerFromGetContributors + > = { + data: dataNormalized, + meta: response.meta, + }; - let dataNormalized = response.data; - if (dataNormalized === undefined) { - dataNormalized = []; - } - - const responseNormalized: ResponseWithDataArrayAndMeta< - OwnerFromGetContributors - > = { - data: dataNormalized, - meta: response.meta, - }; - - return responseNormalized; - }, - params - ); - - const contributors = responseFromGetContributors.data; - - const matchingContributor = contributors.find( - tmpContributor => tmpContributor.login === username - ); - - return { - // tslint:disable-next-line:object-literal-shorthand - repoName: repoName, - unused: matchingContributor === undefined, - }; -} - -export async function fetchUnusedForkedRepos() { - const params: Octokit.ReposGetForUserParams = { - username, - }; - - 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) - .map(repo => repo.name); - - // tslint:disable-next-line:no-console - console.log(forkedRepoNames); - - const allPromiseRepoNameWithBranches = forkedRepoNames.map(repoName => { - return fetchRepoNameWithBranches(repoName); - }); - - const allPromiseRepoNameWithParentRepo = forkedRepoNames.map( - forkedRepoName => { - return fetchRepoNameWithParentRepo(forkedRepoName); - } - ); - - const allRepoNameWithBranches = await Promise.all( - allPromiseRepoNameWithBranches - ); - const allRepoNameWithParentRepo = await Promise.all( - allPromiseRepoNameWithParentRepo - ); - - // Both the above list (of objects) have same number of items and - // items in the same index (of both lists) have same value for repoName key. - - // So join them together into one list of objects (each object containing branches and parent repo) - - assert.strictEqual( - allRepoNameWithBranches.length, - allRepoNameWithParentRepo.length, - 'Length of `allRepoNameWithBranches` and `allRepoNameWithParentRepo` should be same' - ); - - const allRepoNameWithBranchesAndParent: RepoNameWithBranchesAndParent[] = []; - { - const tmpLength = allRepoNameWithBranches.length; - - for (let index = 0; index < tmpLength; index++) { - const tmpRepoNameWithBranches = allRepoNameWithBranches[index]; - const tmpRepoNameWithParentRepo = allRepoNameWithParentRepo[index]; - - assert.strictEqual( - tmpRepoNameWithBranches.repoName, - tmpRepoNameWithParentRepo.repoName, - 'Reponame should be same for objects stored at same index in `allRepoNameWithBranches` and `allRepoNameWithParentRepo`' + return responseNormalized; + }, + params ); - const repoName = tmpRepoNameWithBranches.repoName; + const contributors = responseFromGetContributors.data; - const combinedObject: RepoNameWithBranchesAndParent = { - repoName, - branches: tmpRepoNameWithBranches.branches, - parentRepo: tmpRepoNameWithParentRepo.parentRepo, - }; + const matchingContributor = contributors.find( + tmpContributor => tmpContributor.login === this.username + ); - allRepoNameWithBranchesAndParent.push(combinedObject); - } + return { + // tslint:disable-next-line:object-literal-shorthand + repoName: repoName, + unused: matchingContributor === undefined, + }; } - - const allRepoWithFlagTillStep4: RepoNameWithUnusedFlag[] = []; - - { - const allPromiseRepoWithFlagFromCommit = allRepoNameWithBranchesAndParent.map( - (repoInfo: RepoNameWithBranchesAndParent) => { - return fetchNoneOfForkBranchesIsAhead(repoInfo); - } - ); - - const allPromiseRepoWithFlagFromForkContrib = allRepoNameWithBranchesAndParent.map( - ({ repoName }) => { - return fetchUserIsNotContributor(username, repoName); - } - ); - - const allPromiseRepoWithFlagFromParentContrib = allRepoNameWithBranchesAndParent.map( - ({ repoName, parentRepo }) => { - return fetchUserIsNotContributor(parentRepo.owner.login, repoName); - } - ); - - const allRepoWithFlagFromCommit: RepoNameWithUnusedFlag[] = await Promise.all( - allPromiseRepoWithFlagFromCommit - ); - - const allRepoWithFlagFromForkContrib: RepoNameWithUnusedFlag[] = await Promise.all( - allPromiseRepoWithFlagFromForkContrib - ); - - const allRepoWithFlagFromParentContrib: RepoNameWithUnusedFlag[] = await Promise.all( - allPromiseRepoWithFlagFromParentContrib - ); - - { - assert.strictEqual( - allRepoWithFlagFromCommit.length, - allRepoWithFlagFromForkContrib.length, - 'Length of `allRepoWithFlagFromCommit` and `allRepoWithFlagFromContrib` should be same' - ); - - const repoCount = allRepoWithFlagFromCommit.length; - - for (let index = 0; index < repoCount; index++) { - const tmpObjFromCommit = allRepoWithFlagFromCommit[index]; - const tmpObjFromForkContrib = allRepoWithFlagFromForkContrib[index]; - const tmpObjFromParentContrib = allRepoWithFlagFromParentContrib[index]; - - assert.strictEqual( - tmpObjFromCommit.repoName, - tmpObjFromForkContrib.repoName, - 'Reponame from same index of `allRepoWithFlagFromCommit` and `allRepoWithFlagFromContrib` should be same' - ); - - assert.strictEqual( - tmpObjFromForkContrib.repoName, - tmpObjFromParentContrib.repoName, - 'Reponame from same index of `tmpObjFromForkContrib` and `tmpObjFromParentContrib` should be same' - ); - - const tmpRepoName = tmpObjFromCommit.repoName; - - allRepoWithFlagTillStep4.push({ - repoName: tmpRepoName, - unused: - tmpObjFromCommit.unused && - tmpObjFromForkContrib.unused && - tmpObjFromParentContrib.unused, - }); - } - } - } - - const unusedRepoNames = allRepoWithFlagTillStep4 - .filter(tmp => tmp.unused) - .map(tmp => tmp.repoName); - - return unusedRepoNames; } -export function runMain() { - fetchUnusedForkedRepos().then(unusedRepoNames => { - // tslint:disable-next-line:no-console - console.log(unusedRepoNames); - }); + + +export function runMain(username: string = 'bendtherules') { + new GithubDetectUnusedRepos(username) + .fetchUnusedForkedRepos() + .then(unusedRepoNames => { + // tslint:disable-next-line:no-console + console.log(unusedRepoNames); + }); } // Next steps diff --git a/src/greeter.ts b/src/greeter.ts deleted file mode 100644 index 26c66d5..0000000 --- a/src/greeter.ts +++ /dev/null @@ -1,11 +0,0 @@ -export class Greeter { - private greeting: string; - - constructor(message: string) { - this.greeting = message; - } - - public greet(): string { - return `Bonjour, ${this.greeting}!`; - } -} diff --git a/src/index.ts b/src/index.ts index 20e5a8e..7add6e9 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,8 +1,6 @@ import 'babel-polyfill'; -import { Greeter } from './greeter'; // tslint:disable-next-line:ordered-imports import { fetchUnusedForkedRepos, runMain } from './gh'; -export * from './greeter'; // tslint:disable-next-line:only-arrow-functions (function(currentWindow: Window) { @@ -13,7 +11,6 @@ export * from './greeter'; // assign Greeter to global const myWindow = currentWindow as any; - myWindow.Greeter = Greeter; myWindow.runMain = runMain; // we can use Greeter , execute following in console // var greet = new Greeter('myName');