From a82aeac9fbdd78c9d2c72aa484720c60f8374372 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=B8ren=20Louv-Jansen?= Date: Wed, 16 Feb 2022 01:28:46 +0100 Subject: [PATCH 1/7] Add support for merge commits Fix bug where backport would fail if no .backportrc.json file existed --- .eslintignore | 1 + .vscode/launch.json | 11 +- CONTRIBUTING.md | 22 +-- bin/backport | 2 + package.json | 2 +- src/entrypoint.cli.e2e.private.test.ts | 96 ++++++++++- src/runSequentially.test.ts | 4 +- src/runSequentially.ts | 1 + src/services/git.integration.test.ts | 154 ++++++++++++++---- src/services/git.test.ts | 22 ++- src/services/git.ts | 58 ++++++- src/services/github/v4/apiRequestV4.ts | 5 +- .../fetchCommits/fetchCommitByPullNumber.ts | 26 +-- .../v4/fetchCommits/fetchCommitBySha.ts | 26 +-- .../v4/fetchCommits/fetchCommitsByAuthor.ts | 19 ++- .../fetchPullRequestBySearchQuery.ts | 19 ++- .../getOptionsFromGithub.ts | 26 +-- src/services/remoteConfig.ts | 19 +++ ...ickAndCreateTargetPullRequest.test.ts.snap | 18 ++ ...errypickAndCreateTargetPullRequest.test.ts | 5 + tsconfig.eslint.json | 2 +- yarn.lock | 18 -- 22 files changed, 413 insertions(+), 143 deletions(-) create mode 100644 .eslintignore create mode 100755 bin/backport diff --git a/.eslintignore b/.eslintignore new file mode 100644 index 00000000..74877ab8 --- /dev/null +++ b/.eslintignore @@ -0,0 +1 @@ +bin/backport diff --git a/.vscode/launch.json b/.vscode/launch.json index fceb9e2f..7a91870f 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -7,16 +7,7 @@ { "type": "node", "request": "launch", - "name": "Run file", - "program": "${file}", - "runtimeArgs": ["-r", "ts-node/register/transpile-only"], - "console": "integratedTerminal" - }, - - { - "type": "node", - "request": "launch", - "name": "Start backport (CLI)", + "name": "Run backport (ts-node)", "program": "${workspaceRoot}/src/entrypoint.cli.ts", "runtimeArgs": ["-r", "ts-node/register/transpile-only"], "args": [ diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index e492209f..20779023 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -3,29 +3,19 @@ ### Run ``` -yarn start --branch 6.1 --repo-owner backport-org --repo-name backport-demo --all +yarn start --branch 6.1 --repo backport-org/backport-demo --all ``` -or +**Run `backport` CLI globally** +This will build backport continously and link it, so it can accessed with `backport` command globally ``` -# Compile -yarn tsc - -# Run -node dist/entrypoint.cli.js --branch 6.1 --repo-owner backport-org --repo-name backport-demo --all +yarn tsc && sudo chmod +x bin/backport && yarn link && yarn tsc --watch ``` -**Run `backport` CLI globally** - +**Remove linked backport** ``` -yarn global remove backport -npm -g uninstall backport -yarn unlink backport -yarn unlink -yarn link -sudo chmod +x dist/entrypoint.cli.js -yarn tsc --watch +yarn global remove backport; npm -g uninstall backport; yarn unlink backport; yarn unlink; ``` You can now use `backport` command anywhere, and it'll point to the development version. diff --git a/bin/backport b/bin/backport new file mode 100755 index 00000000..c13f9022 --- /dev/null +++ b/bin/backport @@ -0,0 +1,2 @@ +#!/usr/bin/env node +require('../dist/entrypoint.cli.js') diff --git a/package.json b/package.json index 3d21b66e..8c2c8213 100644 --- a/package.json +++ b/package.json @@ -18,7 +18,7 @@ "main": "./dist/entrypoint.module.js", "types": "dist/entrypoint.module.d.ts", "bin": { - "backport": "./dist/entrypoint.cli.js" + "backport": "./bin/backport" }, "license": "MIT", "scripts": { diff --git a/src/entrypoint.cli.e2e.private.test.ts b/src/entrypoint.cli.e2e.private.test.ts index d9cb3f15..7410e437 100644 --- a/src/entrypoint.cli.e2e.private.test.ts +++ b/src/entrypoint.cli.e2e.private.test.ts @@ -265,6 +265,77 @@ describe('inquirer cli', () => { `); }); + describe('repo: repo-with-backportrc-removed (missing .backportrc.json config file)', () => { + it('should list commits', async () => { + const output = await runBackportAsync( + [ + '--branch', + 'foo', + '--repo', + 'backport-org/repo-with-backportrc-removed', + '--accessToken', + devAccessToken, + ], + { waitForString: 'Select commit' } + ); + + expect(output).toMatchInlineSnapshot(` + "? Select commit (Use arrow keys) + ❯ 1. Rename README.me to README.md + 2. Merge pull request #1 from backport-org/add-readme + 3. Create README.me + 4. Delete .backportrc.json + 5. Create .backportrc.json + 6. Delete .backportrc.json + 7. Create .backportrc.json" + `); + }); + + it('should attempt to backport by PR', async () => { + const output = await runBackportAsync( + [ + '--branch', + 'foo', + '--repo', + 'backport-org/repo-with-backportrc-removed', + '--pr', + '1', + '--accessToken', + devAccessToken, + ], + { waitForString: "is invalid or doesn't exist" } + ); + + expect(output).toMatchInlineSnapshot(` + " + Backporting to foo: + The branch \\"foo\\" is invalid or doesn't exist" + `); + }); + + it('should attempt to backport by commit sha', async () => { + const output = await runBackportAsync( + [ + '--branch', + 'foo', + '--repo', + 'backport-org/repo-with-backportrc-removed', + '--sha', + 'be59df6912a550c8cb49ba3e18be3e512f3d608c', + '--accessToken', + devAccessToken, + ], + { waitForString: "is invalid or doesn't exist" } + ); + + expect(output).toMatchInlineSnapshot(` + " + Backporting to foo: + The branch \\"foo\\" is invalid or doesn't exist" + `); + }); + }); + describe('repo: different-merge-strategies', () => { it('list all commits regardless how they were merged', async () => { jest.setTimeout(TIMEOUT_IN_SECONDS * 1000 * 1.1); @@ -278,19 +349,30 @@ describe('inquirer cli', () => { 'different-merge-strategies', '--accessToken', devAccessToken, + '-n', + '20', ], { waitForString: 'Select commit' } ); expect(output).toMatchInlineSnapshot(` "? Select commit (Use arrow keys) - ❯ 1. Using squash to merge commits (#3) 7.x - 2. Rebase strategy: Second commit 7.x - 3. Rebase strategy: First commit - 4. Merge pull request #1 from backport-org/merge-strategy - 5. Merge strategy: Second commit - 6. Merge strategy: First commit - 7. Initial commit" + ❯ 1. Merge pull request #9 from backport-org/many-merge-commits + 2. Merge strategy: Eighth of many merges + 3. Merge strategy: Seventh of many merges + 4. Merge strategy: Sixth of many merges + 5. Merge strategy: Fifth of many merges + 6. Merge strategy: Fourth of many merges + 7. Merge strategy: Third of many merges + 8. Merge strategy: Second of many merges + 9. Merge strategy: First of many merges + 10.Using squash to merge commits (#3) 7.x + 11.Rebase strategy: Second commit 7.x + 12.Rebase strategy: First commit + 13.Merge pull request #1 from backport-org/merge-strategy + 14.Merge strategy: Second commit + 15.Merge strategy: First commit + 16.Initial commit" `); }); }); diff --git a/src/runSequentially.test.ts b/src/runSequentially.test.ts index 01b7a81d..71a6da01 100644 --- a/src/runSequentially.test.ts +++ b/src/runSequentially.test.ts @@ -92,7 +92,7 @@ describe('runSequentially', () => { rpcExecMock = jest .spyOn(childProcess, 'exec') - .mockResolvedValue({ stdout: 'success', stderr: '' }); + .mockResolvedValue({ stdout: '', stderr: '' }); rpcExecOriginalMock = jest.spyOn(childProcess, 'execAsCallback'); const scope = nock('https://api.github.com') @@ -176,6 +176,7 @@ describe('runSequentially', () => { '/myHomeDir/.backport/repositories/elastic/kibana', '/myHomeDir/.backport/repositories/elastic/kibana', '/myHomeDir/.backport/repositories/elastic/kibana', + '/myHomeDir/.backport/repositories/elastic/kibana', ]); }); @@ -189,6 +190,7 @@ describe('runSequentially', () => { 'git remote add elastic https://x-access-token:myAccessToken@github.com/elastic/kibana.git', 'git reset --hard && git clean -d --force && git fetch elastic 7.x && git checkout -B backport/7.x/pr-55 elastic/7.x --no-track', 'git fetch elastic master:master --force', + 'git rev-list -1 --merges abcd~1..abcd', 'git cherry-pick -x abcd', 'git push sqren_authenticated backport/7.x/pr-55:backport/7.x/pr-55 --force', 'git reset --hard && git checkout my-source-branch-from-options && git branch -D backport/7.x/pr-55', diff --git a/src/runSequentially.ts b/src/runSequentially.ts index 6e7b7980..a978f729 100755 --- a/src/runSequentially.ts +++ b/src/runSequentially.ts @@ -52,6 +52,7 @@ export async function runSequentially({ pullRequestNumber: number, }); } catch (e) { + process.exitCode = 1; results.push({ targetBranch, status: 'failure', diff --git a/src/services/git.integration.test.ts b/src/services/git.integration.test.ts index eabb5e65..a13be4ac 100644 --- a/src/services/git.integration.test.ts +++ b/src/services/git.integration.test.ts @@ -7,8 +7,10 @@ import * as childProcess from './child-process-promisified'; import { cherrypick, cloneRepo, + getCommitsInMergeCommit, getIsCommitInBranch, getSourceRepoPath, + getIsMergeCommit, } from './git'; import { getShortSha } from './github/commitFormatters'; import { RepoOwnerAndNameResponse } from './github/v4/getRepoOwnerAndNameFromGitRemotes'; @@ -16,37 +18,6 @@ import { RepoOwnerAndNameResponse } from './github/v4/getRepoOwnerAndNameFromGit jest.unmock('del'); jest.unmock('make-dir'); -async function createAndCommitFile({ - filename, - content, - execOpts, -}: { - filename: string; - content: string; - execOpts: { cwd: string }; -}) { - await childProcess.exec(`echo "${content}" > "${filename}"`, execOpts); - await childProcess.exec( - `git add -A && git commit -m 'Update ${filename}'`, - execOpts - ); - - return getCurrentSha(execOpts); -} - -async function getCurrentSha(execOpts: { cwd: string }) { - const { stdout } = await childProcess.exec('git rev-parse HEAD', execOpts); - return stdout.trim(); -} - -async function getCurrentMessage(execOpts: { cwd: string }) { - const { stdout } = await childProcess.exec( - 'git --no-pager log -1 --pretty=%B', - execOpts - ); - return stdout.trim(); -} - describe('git.integration', () => { describe('getIsCommitInBranch', () => { let firstSha: string; @@ -324,8 +295,129 @@ describe('git.integration', () => { ); }); }); + + describe('when cloning "backport-org/different-merge-strategies"', () => { + const MERGE_COMMIT_HASH_1 = 'bdc5a17f81e5f32129e27b05c742e055c650bc54'; + const MERGE_COMMIT_HASH_2 = '0db7f1ac1233461563d8708511d1c14adbab46da'; + const SQUASH_COMMIT_HASH = '74a76fa64b34e3ffe8f2a3f73840e1b42fd07299'; + const REBASE_COMMIT_HASH = '9059ae0ca31caa2eebc035f2542842d6c2fde83b'; + + let sandboxPath: string; + beforeAll(async () => { + sandboxPath = getSandboxPath({ + filename: __filename, + specname: 'different-merge-strategies', + }); + await resetSandbox(sandboxPath); + + await childProcess.exec( + 'git clone https://github.com/backport-org/different-merge-strategies.git ./', + { cwd: sandboxPath } + ); + }); + + describe('getIsMergeCommit', () => { + it('returns true for first merge commit', async () => { + const res = await getIsMergeCommit( + { dir: sandboxPath } as ValidConfigOptions, + MERGE_COMMIT_HASH_1 + ); + + expect(res).toBe(true); + }); + + it('returns true for second merge commit', async () => { + const res = await getIsMergeCommit( + { dir: sandboxPath } as ValidConfigOptions, + MERGE_COMMIT_HASH_2 + ); + + expect(res).toBe(true); + }); + + it('returns false for rebased commits', async () => { + const res = await getIsMergeCommit( + { dir: sandboxPath } as ValidConfigOptions, + REBASE_COMMIT_HASH + ); + + expect(res).toBe(false); + }); + + it('returns false for squashed commits', async () => { + const res = await getIsMergeCommit( + { dir: sandboxPath } as ValidConfigOptions, + SQUASH_COMMIT_HASH + ); + + expect(res).toBe(false); + }); + }); + + describe('getCommitsInMergeCommit', () => { + it('returns a list of commit hashes - excluding the merge hash itself', async () => { + const res = await getCommitsInMergeCommit( + { dir: sandboxPath } as ValidConfigOptions, + MERGE_COMMIT_HASH_1 + ); + + expect(res).not.toContain(MERGE_COMMIT_HASH_1); + + expect(res).toEqual([ + 'f9a760e0d9eb3ebcc64f8cb75ce885714b836496', + '7b92e29e88266004485ce0fae0260605b01df887', + 'a1facf8c006fb815d6a6ecd1b2907e6e64f29576', + 'b8d4bcfb0fd875be4ab0230f6db40ddf72f45378', + '6f224054db5f7772b04f23f17659070216bae84c', + '7fed54cbd1ba9cb973462670ff82ac80bf8a79f8', + '78c24d0058859e7d511d10ce91ebf279c7b58ac2', + '709ccf707d443dd8c001b3c3ae40fdf037bb43f5', + ]); + }); + + it('returns empty for squash commits', async () => { + const res = await getCommitsInMergeCommit( + { dir: sandboxPath } as ValidConfigOptions, + SQUASH_COMMIT_HASH + ); + + expect(res).toEqual([]); + }); + }); + }); }); +async function createAndCommitFile({ + filename, + content, + execOpts, +}: { + filename: string; + content: string; + execOpts: { cwd: string }; +}) { + await childProcess.exec(`echo "${content}" > "${filename}"`, execOpts); + await childProcess.exec( + `git add -A && git commit -m 'Update ${filename}'`, + execOpts + ); + + return getCurrentSha(execOpts); +} + +async function getCurrentSha(execOpts: { cwd: string }) { + const { stdout } = await childProcess.exec('git rev-parse HEAD', execOpts); + return stdout.trim(); +} + +async function getCurrentMessage(execOpts: { cwd: string }) { + const { stdout } = await childProcess.exec( + 'git --no-pager log -1 --pretty=%B', + execOpts + ); + return stdout.trim(); +} + function mockRepoOwnerAndName({ repoName, parentRepoOwner, diff --git a/src/services/git.test.ts b/src/services/git.test.ts index 2febcac3..5e1713ee 100644 --- a/src/services/git.test.ts +++ b/src/services/git.test.ts @@ -335,7 +335,9 @@ describe('cherrypick', () => { jest .spyOn(childProcess, 'exec') - // mock cherry pick command + // mock getIsMergeCommit(...) + .mockResolvedValueOnce({ stderr: '', stdout: '' }) + // mock cherrypick(...) .mockResolvedValueOnce({ stderr: '', stdout: '' }); expect(await cherrypick(options, 'abcd')).toEqual({ @@ -349,6 +351,9 @@ describe('cherrypick', () => { const execSpy = jest .spyOn(childProcess, 'exec') + // mock getIsMergeCommit(...) + .mockResolvedValueOnce({ stderr: '', stdout: '' }) + // mock cherry pick command .mockResolvedValueOnce({ stderr: '', stdout: '' }); @@ -363,6 +368,9 @@ describe('cherrypick', () => { jest .spyOn(childProcess, 'exec') + // mock getIsMergeCommit(...) + .mockResolvedValueOnce({ stderr: '', stdout: '' }) + // mock cherry pick command .mockRejectedValueOnce( new ExecError({ @@ -405,6 +413,9 @@ describe('cherrypick', () => { jest .spyOn(childProcess, 'exec') + // mock getIsMergeCommit(...) + .mockResolvedValueOnce({ stderr: '', stdout: '' }) + // mock cherry pick command .mockRejectedValueOnce( new ExecError({ @@ -434,6 +445,9 @@ Or refer to the git documentation for more information: https://git-scm.com/docs jest .spyOn(childProcess, 'exec') + // mock getIsMergeCommit(...) + .mockResolvedValueOnce({ stderr: '', stdout: '' }) + // mock cherry pick command .mockRejectedValueOnce( new ExecError({ @@ -457,6 +471,9 @@ Or refer to the git documentation for more information: https://git-scm.com/docs jest .spyOn(childProcess, 'exec') + // mock getIsMergeCommit(...) + .mockResolvedValueOnce({ stderr: '', stdout: '' }) + // mock cherry pick command .mockRejectedValueOnce( new ExecError({ @@ -493,6 +510,9 @@ Or refer to the git documentation for more information: https://git-scm.com/docs jest .spyOn(childProcess, 'exec') + // mock getIsMergeCommit(...) + .mockResolvedValueOnce({ stderr: '', stdout: '' }) + // mock cherry pick command .mockRejectedValueOnce(new Error('non-cherrypick error')) diff --git a/src/services/git.ts b/src/services/git.ts index a91d13a4..fa61355f 100644 --- a/src/services/git.ts +++ b/src/services/git.ts @@ -1,5 +1,5 @@ import { resolve as pathResolve } from 'path'; -import { uniq, isEmpty } from 'lodash'; +import { uniq, isEmpty, first, last } from 'lodash'; import { ValidConfigOptions } from '../options/options'; import { ora } from '../ui/ora'; import { filterNil } from '../utils/filterEmpty'; @@ -195,6 +195,40 @@ export async function fetchBranch(options: ValidConfigOptions, branch: string) { }); } +export async function getIsMergeCommit( + options: ValidConfigOptions, + sha: string +) { + const res = await exec(`git rev-list -1 --merges ${sha}~1..${sha}`, { + cwd: getRepoPath(options), + }); + + return res.stdout !== ''; +} + +export async function getCommitsInMergeCommit( + options: ValidConfigOptions, + sha: string +) { + try { + const res = await exec( + `git --no-pager log ${sha}^1..${sha}^2 --pretty=format:"%H"`, + { + cwd: getRepoPath(options), + } + ); + + return res.stdout.split('\n'); + } catch (e) { + // swallow error + if (e.code === 128) { + return []; + } + + throw e; + } +} + export async function cherrypick( options: ValidConfigOptions, sha: string, @@ -209,9 +243,27 @@ export async function cherrypick( }> { const mainlinArg = options.mainline != undefined ? ` --mainline ${options.mainline}` : ''; - const cherrypickRefArg = options.cherrypickRef === false ? '' : ' -x'; - const cmd = `git cherry-pick${cherrypickRefArg}${mainlinArg} ${sha}`; + + let shaOrRange = sha; + + if (!options.mainline) { + try { + const isMergeCommit = await getIsMergeCommit(options, sha); + if (isMergeCommit) { + const shas = await getCommitsInMergeCommit(options, sha); + shaOrRange = `${last(shas)}^..${first(shas)}`; + } + } catch (e) { + // swallow error if it's a known error + // exit 128 will happen for many things, among others when the cherrypicked commit is empty + if (e.code !== 128) { + throw e; + } + } + } + const cmd = `git cherry-pick${cherrypickRefArg}${mainlinArg} ${shaOrRange}`; + try { await exec(cmd, { cwd: getRepoPath(options) }); return { conflictingFiles: [], unstagedFiles: [], needsResolving: false }; diff --git a/src/services/github/v4/apiRequestV4.ts b/src/services/github/v4/apiRequestV4.ts index 03a98230..8b68af27 100644 --- a/src/services/github/v4/apiRequestV4.ts +++ b/src/services/github/v4/apiRequestV4.ts @@ -111,13 +111,16 @@ type AxiosGithubResponse = AxiosResponse< any >; export class GithubV4Exception extends Error { + axiosResponse: AxiosGithubResponse & { request: undefined }; + constructor( public message: string, - public axiosResponse: AxiosGithubResponse + axiosResponse: AxiosGithubResponse ) { super(message); Error.captureStackTrace(this, HandledError); this.name = 'GithubV4Exception'; + this.axiosResponse = { ...axiosResponse, request: undefined }; } } diff --git a/src/services/github/v4/fetchCommits/fetchCommitByPullNumber.ts b/src/services/github/v4/fetchCommits/fetchCommitByPullNumber.ts index ad956893..af1a6b8d 100644 --- a/src/services/github/v4/fetchCommits/fetchCommitByPullNumber.ts +++ b/src/services/github/v4/fetchCommits/fetchCommitByPullNumber.ts @@ -1,6 +1,7 @@ import gql from 'graphql-tag'; import { ValidConfigOptions } from '../../../../options/options'; import { HandledError } from '../../../HandledError'; +import { swallowMissingConfigFileException } from '../../../remoteConfig'; import { Commit, SourceCommitWithTargetPullRequest, @@ -44,16 +45,21 @@ export async function fetchCommitByPullNumber(options: { ${sourceCommitWithTargetPullRequestFragment} `; - const res = await apiRequestV4({ - githubApiBaseUrlV4, - accessToken, - query, - variables: { - repoOwner, - repoName, - pullNumber, - }, - }); + let res: CommitByPullNumberResponse; + try { + res = await apiRequestV4({ + githubApiBaseUrlV4, + accessToken, + query, + variables: { + repoOwner, + repoName, + pullNumber, + }, + }); + } catch (e) { + res = swallowMissingConfigFileException(e); + } const pullRequestNode = res.repository.pullRequest; if (!pullRequestNode) { diff --git a/src/services/github/v4/fetchCommits/fetchCommitBySha.ts b/src/services/github/v4/fetchCommits/fetchCommitBySha.ts index 6b4247df..9b51aa7e 100644 --- a/src/services/github/v4/fetchCommits/fetchCommitBySha.ts +++ b/src/services/github/v4/fetchCommits/fetchCommitBySha.ts @@ -1,6 +1,7 @@ import gql from 'graphql-tag'; import { ValidConfigOptions } from '../../../../options/options'; import { HandledError } from '../../../HandledError'; +import { swallowMissingConfigFileException } from '../../../remoteConfig'; import { Commit, SourceCommitWithTargetPullRequest, @@ -39,16 +40,21 @@ export async function fetchCommitBySha(options: { ${sourceCommitWithTargetPullRequestFragment} `; - const res = await apiRequestV4({ - githubApiBaseUrlV4, - accessToken, - query, - variables: { - repoOwner, - repoName, - sha, - }, - }); + let res: CommitsByShaResponse; + try { + res = await apiRequestV4({ + githubApiBaseUrlV4, + accessToken, + query, + variables: { + repoOwner, + repoName, + sha, + }, + }); + } catch (e) { + res = swallowMissingConfigFileException(e); + } const sourceCommit = res.repository.object; if (!sourceCommit) { diff --git a/src/services/github/v4/fetchCommits/fetchCommitsByAuthor.ts b/src/services/github/v4/fetchCommits/fetchCommitsByAuthor.ts index 72897297..f3c68075 100644 --- a/src/services/github/v4/fetchCommits/fetchCommitsByAuthor.ts +++ b/src/services/github/v4/fetchCommits/fetchCommitsByAuthor.ts @@ -3,6 +3,7 @@ import { isEmpty, uniqBy, orderBy } from 'lodash'; import { ValidConfigOptions } from '../../../../options/options'; import { filterNil } from '../../../../utils/filterEmpty'; import { HandledError } from '../../../HandledError'; +import { swallowMissingConfigFileException } from '../../../remoteConfig'; import { Commit, SourceCommitWithTargetPullRequest, @@ -12,7 +13,7 @@ import { import { apiRequestV4 } from '../apiRequestV4'; import { fetchAuthorId } from '../fetchAuthorId'; -function fetchByCommitPath({ +async function fetchByCommitPath({ options, authorId, commitPath, @@ -89,12 +90,16 @@ function fetchByCommitPath({ dateUntil, }; - return apiRequestV4({ - githubApiBaseUrlV4, - accessToken, - query, - variables, - }); + try { + return await apiRequestV4({ + githubApiBaseUrlV4, + accessToken, + query, + variables, + }); + } catch (e) { + return swallowMissingConfigFileException(e); + } } export async function fetchCommitsByAuthor(options: { diff --git a/src/services/github/v4/fetchCommits/fetchPullRequestBySearchQuery.ts b/src/services/github/v4/fetchCommits/fetchPullRequestBySearchQuery.ts index 245bc231..4555b079 100644 --- a/src/services/github/v4/fetchCommits/fetchPullRequestBySearchQuery.ts +++ b/src/services/github/v4/fetchCommits/fetchPullRequestBySearchQuery.ts @@ -2,6 +2,7 @@ import gql from 'graphql-tag'; import { isEmpty } from 'lodash'; import { ValidConfigOptions } from '../../../../options/options'; import { HandledError } from '../../../HandledError'; +import { swallowMissingConfigFileException } from '../../../remoteConfig'; import { Commit, SourceCommitWithTargetPullRequest, @@ -47,12 +48,18 @@ export async function fetchPullRequestBySearchQuery( query: searchQuery, maxNumber: maxNumber, }; - const res = await apiRequestV4({ - githubApiBaseUrlV4, - accessToken, - query, - variables, - }); + + let res; + try { + res = await apiRequestV4({ + githubApiBaseUrlV4, + accessToken, + query, + variables, + }); + } catch (e) { + res = swallowMissingConfigFileException(e); + } const commits = res.search.nodes.map((pullRequestNode) => { const sourceCommit = pullRequestNode.mergeCommit; diff --git a/src/services/github/v4/getOptionsFromGithub/getOptionsFromGithub.ts b/src/services/github/v4/getOptionsFromGithub/getOptionsFromGithub.ts index b3e1e8a1..ef656c1e 100644 --- a/src/services/github/v4/getOptionsFromGithub/getOptionsFromGithub.ts +++ b/src/services/github/v4/getOptionsFromGithub/getOptionsFromGithub.ts @@ -6,7 +6,10 @@ import { isLocalConfigFileModified, } from '../../../git'; import { logger } from '../../../logger'; -import { parseRemoteConfig } from '../../../remoteConfig'; +import { + parseRemoteConfig, + swallowMissingConfigFileException, +} from '../../../remoteConfig'; import { apiRequestV4, GithubV4Exception } from '../apiRequestV4'; import { throwOnInvalidAccessToken } from '../throwOnInvalidAccessToken'; import { GithubConfigOptionsResponse, query } from './query'; @@ -44,9 +47,8 @@ export async function getOptionsFromGithub(options: { throw e; } - const error = e as GithubV4Exception; - throwOnInvalidAccessToken({ error, repoName, repoOwner }); - res = swallowErrorIfConfigFileIsMissing(error); + throwOnInvalidAccessToken({ error: e, repoName, repoOwner }); + res = swallowMissingConfigFileException(e); } // it is not possible to have a branch named "backport" @@ -123,19 +125,3 @@ async function getRemoteConfigFileOptions( return parseRemoteConfig(remoteConfig); } - -function swallowErrorIfConfigFileIsMissing(error: GithubV4Exception) { - const { data, errors } = error.axiosResponse.data; - - const missingConfigError = errors?.some((error) => { - return error.path.includes('remoteConfig') && error.type === 'NOT_FOUND'; - }); - - // swallow error if it's just the config file that's missing - if (missingConfigError && data != null) { - return data; - } - - // Throw unexpected error - throw error; -} diff --git a/src/services/remoteConfig.ts b/src/services/remoteConfig.ts index f0ec45c7..db89b8bc 100644 --- a/src/services/remoteConfig.ts +++ b/src/services/remoteConfig.ts @@ -1,6 +1,7 @@ import gql from 'graphql-tag'; import { ConfigFileOptions } from '../entrypoint.module'; import { withConfigMigrations } from '../options/config/readConfigFile'; +import { GithubV4Exception } from './github/v4/apiRequestV4'; import { logger } from './logger'; export const RemoteConfigHistoryFragment = gql` @@ -49,3 +50,21 @@ export function parseRemoteConfig(remoteConfig: RemoteConfig) { return; } } + +export function swallowMissingConfigFileException( + error: GithubV4Exception +) { + const { data, errors } = error.axiosResponse.data; + + const missingConfigError = errors?.some((error) => { + return error.path.includes('remoteConfig') && error.type === 'NOT_FOUND'; + }); + + // swallow error if it's just the config file that's missing + if (missingConfigError && data != null) { + return data; + } + + // Throw unexpected error + throw error; +} diff --git a/src/ui/__snapshots__/cherrypickAndCreateTargetPullRequest.test.ts.snap b/src/ui/__snapshots__/cherrypickAndCreateTargetPullRequest.test.ts.snap index 1efa595e..a7d5ce33 100644 --- a/src/ui/__snapshots__/cherrypickAndCreateTargetPullRequest.test.ts.snap +++ b/src/ui/__snapshots__/cherrypickAndCreateTargetPullRequest.test.ts.snap @@ -14,6 +14,12 @@ Array [ "cwd": "/myHomeDir/.backport/repositories/elastic/kibana", }, ], + Array [ + "git rev-list -1 --merges mySha~1..mySha", + Object { + "cwd": "/myHomeDir/.backport/repositories/elastic/kibana", + }, + ], Array [ "git cherry-pick -x mySha", Object { @@ -115,6 +121,12 @@ Array [ "cwd": "/myHomeDir/.backport/repositories/elastic/kibana", }, ], + Array [ + "git rev-list -1 --merges mySha~1..mySha", + Object { + "cwd": "/myHomeDir/.backport/repositories/elastic/kibana", + }, + ], Array [ "git cherry-pick -x mySha", Object { @@ -127,6 +139,12 @@ Array [ "cwd": "/myHomeDir/.backport/repositories/elastic/kibana", }, ], + Array [ + "git rev-list -1 --merges mySha2~1..mySha2", + Object { + "cwd": "/myHomeDir/.backport/repositories/elastic/kibana", + }, + ], Array [ "git cherry-pick -x mySha2", Object { diff --git a/src/ui/cherrypickAndCreateTargetPullRequest.test.ts b/src/ui/cherrypickAndCreateTargetPullRequest.test.ts index 36b0c685..726f3278 100644 --- a/src/ui/cherrypickAndCreateTargetPullRequest.test.ts +++ b/src/ui/cherrypickAndCreateTargetPullRequest.test.ts @@ -490,6 +490,11 @@ function setupExecSpyForCherryPick() { return { stdout: ``, stderr: '' }; } + // getIsMergeCommit + if (cmd.startsWith('git rev-list -1 --merges')) { + return { stdout: ``, stderr: '' }; + } + throw new Error(`Missing exec mock for "${cmd}"`); }); } diff --git a/tsconfig.eslint.json b/tsconfig.eslint.json index d7a67395..10460ff8 100644 --- a/tsconfig.eslint.json +++ b/tsconfig.eslint.json @@ -1,4 +1,4 @@ { "extends": "./tsconfig.json", - "include": ["**.js", "./**/*.ts", "./**/*.js"] + "include": ["**.js", "**/*.ts", "**/*.js"] } diff --git a/yarn.lock b/yarn.lock index 79d1d05f..86357508 100644 --- a/yarn.lock +++ b/yarn.lock @@ -649,11 +649,6 @@ tslib "~2.3.0" value-or-promise "1.0.11" -"@graphql-typed-document-node/core@^3.1.1": - version "3.1.1" - resolved "https://registry.yarnpkg.com/@graphql-typed-document-node/core/-/core-3.1.1.tgz#076d78ce99822258cf813ecc1e7fa460fa74d052" - integrity sha512-NQ17ii0rK1b34VZonlmT2QMJFI70m0TRwbknO/ihlbatXyaktDhN/98vBiUU6kNBPljqGqyIrl2T4nY2RpFANg== - "@humanwhocodes/config-array@^0.9.2": version "0.9.2" resolved "https://registry.yarnpkg.com/@humanwhocodes/config-array/-/config-array-0.9.2.tgz#68be55c737023009dfc5fe245d51181bb6476914" @@ -1331,14 +1326,6 @@ "@typescript-eslint/types" "5.11.0" eslint-visitor-keys "^3.0.0" -"@urql/core@^2.4.1": - version "2.4.1" - resolved "https://registry.yarnpkg.com/@urql/core/-/core-2.4.1.tgz#bae449dafe98fb4944f3be61eb8e70ba33f8a46d" - integrity sha512-HnS54oNwO4pAACKl/2/tNLbRrxAxKawVJuG9UPiixqeEVekiecUQQnCjb9SpOW4Qr54HYzCMDbr3c5px3hfEEg== - dependencies: - "@graphql-typed-document-node/core" "^3.1.1" - wonka "^4.0.14" - abab@^2.0.3, abab@^2.0.5: version "2.0.5" resolved "https://registry.yarnpkg.com/abab/-/abab-2.0.5.tgz#c0b678fb32d60fc1219c784d6a826fe385aeb79a" @@ -5103,11 +5090,6 @@ winston@^3.5.1: triple-beam "^1.3.0" winston-transport "^4.4.2" -wonka@^4.0.14: - version "4.0.15" - resolved "https://registry.yarnpkg.com/wonka/-/wonka-4.0.15.tgz#9aa42046efa424565ab8f8f451fcca955bf80b89" - integrity sha512-U0IUQHKXXn6PFo9nqsHphVCE5m3IntqZNB9Jjn7EB1lrR7YTDY3YWgFvEvwniTzXSvOH/XMzAZaIfJF/LvHYXg== - word-wrap@^1.2.3, word-wrap@~1.2.3: version "1.2.3" resolved "https://registry.yarnpkg.com/word-wrap/-/word-wrap-1.2.3.tgz#610636f6b1f703891bd34771ccb17fb93b47079c" From 41f9fd561a9afa787f99cddc2010e27f9e0cdd77 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=B8ren=20Louv-Jansen?= Date: Wed, 16 Feb 2022 12:57:28 +0100 Subject: [PATCH 2/7] Auto-fix eslint --- .vscode/settings.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.vscode/settings.json b/.vscode/settings.json index 4461e67d..5f5a096f 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -5,6 +5,7 @@ "coverage": true }, "search.exclude": { + "dist/*": true, "**/node_modules": true, "**/bower_components": true, "**/*.code-search": true, @@ -14,7 +15,7 @@ }, "editor.formatOnSave": true, "editor.codeActionsOnSave": { - "source.fixAll.eslint": true + "source.fixAll": true }, "typescript.tsdk": "node_modules/typescript/lib" } From fd2578af9a06500bafac1b657203f2470cf6a2d1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=B8ren=20Louv-Jansen?= Date: Wed, 16 Feb 2022 21:35:12 +0100 Subject: [PATCH 3/7] Add tests Add tests --- src/entrypoint.module.ts | 21 +- src/options/ConfigOptions.ts | 2 +- src/options/cliArgs.ts | 5 + src/runSequentially.test.ts | 87 ++-- src/services/github/v3/createStatusComment.ts | 2 +- src/services/github/v4/apiRequestV4.ts | 42 +- ...tchCommitByPullNumber.private.test.ts.snap | 8 +- .../fetchCommitBySha.private.test.ts.snap | 8 +- .../fetchCommitsByAuthor.private.test.ts.snap | 8 +- .../fetchCommitsByAuthor.test.ts.snap | 8 +- .../fetchCommits/fetchCommitByPullNumber.ts | 6 +- .../v4/fetchCommits/fetchCommitBySha.ts | 6 +- .../v4/fetchCommits/fetchCommitsByAuthor.ts | 6 +- .../fetchPullRequestBySearchQuery.ts | 6 +- .../github/v4/fetchRemoteProjectConfig.ts | 2 +- .../github/v4/getOptionsFromGithub/query.ts | 2 +- .../github/v4/mocks/commitsByAuthorMock.ts | 7 +- .../v4/throwOnInvalidAccessToken.test.ts | 10 +- .../github/v4/throwOnInvalidAccessToken.ts | 13 +- src/services/remoteConfig.ts | 4 +- .../getExpectedTargetPullRequests.test.ts | 128 ++--- .../getExpectedTargetPullRequests.ts | 19 +- .../sourceCommit/getMockSourceCommit.ts | 40 +- .../sourceCommit/parseSourceCommit.ts | 33 +- src/test/backport-e2e.mutation.test.ts | 412 ---------------- .../e2e/cli.e2e.private.test.ts} | 50 +- src/test/e2e/module.e2e.private.test.ts | 454 ++++++++++++++++++ ...leUnbackportedPullRequests.private.test.ts | 2 - .../cherrypickAndCreateTargetPullRequest.ts | 63 +-- src/ui/getCommits.ts | 19 +- src/ui/maybeSetupRepo.test.ts | 42 +- src/ui/maybeSetupRepo.ts | 23 +- 32 files changed, 807 insertions(+), 731 deletions(-) delete mode 100644 src/test/backport-e2e.mutation.test.ts rename src/{entrypoint.cli.e2e.private.test.ts => test/e2e/cli.e2e.private.test.ts} (92%) create mode 100644 src/test/e2e/module.e2e.private.test.ts diff --git a/src/entrypoint.module.ts b/src/entrypoint.module.ts index 1ef4adf3..76222077 100644 --- a/src/entrypoint.module.ts +++ b/src/entrypoint.module.ts @@ -6,12 +6,13 @@ import { fetchCommitBySha } from './services/github/v4/fetchCommits/fetchCommitB import { fetchCommitsByAuthor } from './services/github/v4/fetchCommits/fetchCommitsByAuthor'; import { getOptionsFromGithub } from './services/github/v4/getOptionsFromGithub/getOptionsFromGithub'; import { initLogger } from './services/logger'; +import { Commit } from './services/sourceCommit/parseSourceCommit'; import { excludeUndefined } from './utils/excludeUndefined'; // public API export { BackportResponse } from './backportRun'; export { ConfigFileOptions } from './options/ConfigOptions'; -export { Commit } from './services/sourceCommit/parseSourceCommit'; +export { Commit }; export { fetchRemoteProjectConfig as getRemoteProjectConfig } from './services/github/v4/fetchRemoteProjectConfig'; export { getGlobalConfig as getLocalGlobalConfig } from './options/config/globalConfig'; @@ -41,12 +42,12 @@ export async function getCommits(options: { branchLabelMapping?: ValidConfigOptions['branchLabelMapping']; githubApiBaseUrlV4?: string; pullNumber?: number; - sha?: string; + sha?: string | string[]; skipRemoteConfig?: boolean; sourceBranch?: string; dateUntil?: string; dateSince?: string; -}) { +}): Promise { initLogger({ ci: true, accessToken: options.accessToken }); const optionsFromGithub = await getOptionsFromGithub(options); @@ -62,13 +63,13 @@ export async function getCommits(options: { } if (options.sha) { - return [ - await fetchCommitBySha({ - ...optionsFromGithub, - ...options, - sha: options.sha, - }), - ]; + const shas = Array.isArray(options.sha) ? options.sha : [options.sha]; + + return Promise.all( + shas.map((sha) => + fetchCommitBySha({ ...optionsFromGithub, ...options, sha }) + ) + ); } return fetchCommitsByAuthor({ diff --git a/src/options/ConfigOptions.ts b/src/options/ConfigOptions.ts index 52494684..6853c117 100644 --- a/src/options/ConfigOptions.ts +++ b/src/options/ConfigOptions.ts @@ -53,7 +53,7 @@ type Options = Partial<{ repoOwner: string; resetAuthor: boolean; reviewers: string[]; - sha: string; + sha: string | string[]; skipRemoteConfig: boolean; sourceBranch: string; sourcePRLabels: string[]; diff --git a/src/options/cliArgs.ts b/src/options/cliArgs.ts index 1dd71cf0..89fad25a 100644 --- a/src/options/cliArgs.ts +++ b/src/options/cliArgs.ts @@ -99,6 +99,11 @@ export function getOptionsFromCliArgs( type: 'boolean', }) + .option('dryRun', { + description: 'Run backport locally without pushing to Github', + type: 'boolean', + }) + .option('editor', { description: 'Editor to be opened during conflict resolution', type: 'string', diff --git a/src/runSequentially.test.ts b/src/runSequentially.test.ts index 71a6da01..85bdb737 100644 --- a/src/runSequentially.test.ts +++ b/src/runSequentially.test.ts @@ -162,38 +162,63 @@ describe('runSequentially', () => { `); }); - it('should run all exec commands in the either source or backport directory', () => { + it('should run commands in correct folders', () => { // eslint-disable-next-line @typescript-eslint/no-unused-vars - expect(rpcExecMock.mock.calls.map(([cmd, opts]) => opts.cwd)).toEqual([ - '/path/to/source/repo', - '/myHomeDir/.backport/repositories/elastic/kibana', - '/myHomeDir/.backport/repositories/elastic/kibana', - '/myHomeDir/.backport/repositories/elastic/kibana', - '/myHomeDir/.backport/repositories/elastic/kibana', - '/myHomeDir/.backport/repositories/elastic/kibana', - '/myHomeDir/.backport/repositories/elastic/kibana', - '/myHomeDir/.backport/repositories/elastic/kibana', - '/myHomeDir/.backport/repositories/elastic/kibana', - '/myHomeDir/.backport/repositories/elastic/kibana', - '/myHomeDir/.backport/repositories/elastic/kibana', - '/myHomeDir/.backport/repositories/elastic/kibana', - ]); - }); - - it('exec commands should be called with correct args', () => { - expect(rpcExecMock.mock.calls.map(([cmd]) => cmd)).toEqual([ - 'git remote --verbose', - 'git remote rm origin', - 'git remote rm sqren_authenticated', - 'git remote add sqren_authenticated https://x-access-token:myAccessToken@github.com/sqren_authenticated/kibana.git', - 'git remote rm elastic', - 'git remote add elastic https://x-access-token:myAccessToken@github.com/elastic/kibana.git', - 'git reset --hard && git clean -d --force && git fetch elastic 7.x && git checkout -B backport/7.x/pr-55 elastic/7.x --no-track', - 'git fetch elastic master:master --force', - 'git rev-list -1 --merges abcd~1..abcd', - 'git cherry-pick -x abcd', - 'git push sqren_authenticated backport/7.x/pr-55:backport/7.x/pr-55 --force', - 'git reset --hard && git checkout my-source-branch-from-options && git branch -D backport/7.x/pr-55', + expect( + rpcExecMock.mock.calls.map(([cmd, { cwd }]) => ({ cmd, cwd })) + ).toEqual([ + { + cmd: 'git rev-parse --show-toplevel', + cwd: '/path/to/source/repo', + }, + { + cmd: 'git remote --verbose', + cwd: '/path/to/source/repo', + }, + { + cmd: 'git remote rm origin', + cwd: '/myHomeDir/.backport/repositories/elastic/kibana', + }, + { + cmd: 'git remote rm sqren_authenticated', + cwd: '/myHomeDir/.backport/repositories/elastic/kibana', + }, + { + cmd: 'git remote add sqren_authenticated https://x-access-token:myAccessToken@github.com/sqren_authenticated/kibana.git', + cwd: '/myHomeDir/.backport/repositories/elastic/kibana', + }, + { + cmd: 'git remote rm elastic', + cwd: '/myHomeDir/.backport/repositories/elastic/kibana', + }, + { + cmd: 'git remote add elastic https://x-access-token:myAccessToken@github.com/elastic/kibana.git', + cwd: '/myHomeDir/.backport/repositories/elastic/kibana', + }, + { + cmd: 'git reset --hard && git clean -d --force && git fetch elastic 7.x && git checkout -B backport/7.x/pr-55 elastic/7.x --no-track', + cwd: '/myHomeDir/.backport/repositories/elastic/kibana', + }, + { + cmd: 'git fetch elastic master:master --force', + cwd: '/myHomeDir/.backport/repositories/elastic/kibana', + }, + { + cmd: 'git rev-list -1 --merges abcd~1..abcd', + cwd: '/myHomeDir/.backport/repositories/elastic/kibana', + }, + { + cmd: 'git cherry-pick -x abcd', + cwd: '/myHomeDir/.backport/repositories/elastic/kibana', + }, + { + cmd: 'git push sqren_authenticated backport/7.x/pr-55:backport/7.x/pr-55 --force', + cwd: '/myHomeDir/.backport/repositories/elastic/kibana', + }, + { + cmd: 'git reset --hard && git checkout my-source-branch-from-options && git branch -D backport/7.x/pr-55', + cwd: '/myHomeDir/.backport/repositories/elastic/kibana', + }, ]); }); diff --git a/src/services/github/v3/createStatusComment.ts b/src/services/github/v3/createStatusComment.ts index 02479104..15222e8a 100644 --- a/src/services/github/v3/createStatusComment.ts +++ b/src/services/github/v3/createStatusComment.ts @@ -22,7 +22,7 @@ export async function createStatusComment({ publishStatusComment, } = options; - if (!publishStatusComment) { + if (!publishStatusComment || options.dryRun) { return; } diff --git a/src/services/github/v4/apiRequestV4.ts b/src/services/github/v4/apiRequestV4.ts index 8b68af27..dfad4af1 100644 --- a/src/services/github/v4/apiRequestV4.ts +++ b/src/services/github/v4/apiRequestV4.ts @@ -45,18 +45,14 @@ export async function apiRequestV4({ ); if (response.data.errors) { - const message = `${response.data.errors - .map((error) => error.message) - .join(',')} (Github API v4)`; - - throw new GithubV4Exception(message, response); + throw new GithubV4Exception(response); } addDebugLogs({ githubApiBaseUrlV4, query, variables, - axiosResponse: response, + githubResponse: response, didSucceed: false, }); @@ -67,13 +63,10 @@ export async function apiRequestV4({ githubApiBaseUrlV4, query, variables, - axiosResponse: e.response, + githubResponse: e.response, didSucceed: false, }); - throw new GithubV4Exception( - `${e.message} (Unhandled Github API v4)`, - e.response - ); + throw new GithubV4Exception(e.response, e.message); } throw e; @@ -84,26 +77,26 @@ function addDebugLogs({ githubApiBaseUrlV4, query, variables, - axiosResponse, + githubResponse, didSucceed, }: { githubApiBaseUrlV4: string; query: DocumentNode; variables?: Variables; - axiosResponse: AxiosResponse; + githubResponse: AxiosResponse; didSucceed: boolean; }) { const gqlQueryName = getQueryName(query); logger.info( - `POST ${githubApiBaseUrlV4} (name:${gqlQueryName}, status: ${axiosResponse.status})` + `POST ${githubApiBaseUrlV4} (name:${gqlQueryName}, status: ${githubResponse.status})` ); const logMethod = didSucceed ? logger.verbose : logger.info; logMethod(`Query name ${gqlQueryName}`); logMethod(`Query: ${query}`); logMethod('Variables:', variables); - logMethod('Response headers:', axiosResponse.headers); - logMethod('Response data:', axiosResponse.data); + logMethod('Response headers:', githubResponse.headers); + logMethod('Response data:', githubResponse.data); } type AxiosGithubResponse = AxiosResponse< @@ -111,16 +104,25 @@ type AxiosGithubResponse = AxiosResponse< any >; export class GithubV4Exception extends Error { - axiosResponse: AxiosGithubResponse & { request: undefined }; + githubResponse: AxiosGithubResponse & { request: undefined }; constructor( - public message: string, - axiosResponse: AxiosGithubResponse + githubResponse: AxiosGithubResponse, + errorMessage?: string ) { + const githubMessage = githubResponse.data.errors + ?.map((error) => error.message) + .join(','); + + const message = `${ + errorMessage ?? githubMessage ?? 'Unknown error' + } (Github API v4)`; + super(message); Error.captureStackTrace(this, HandledError); this.name = 'GithubV4Exception'; - this.axiosResponse = { ...axiosResponse, request: undefined }; + this.message = message; + this.githubResponse = { ...githubResponse, request: undefined }; } } diff --git a/src/services/github/v4/fetchCommits/__snapshots__/fetchCommitByPullNumber.private.test.ts.snap b/src/services/github/v4/fetchCommits/__snapshots__/fetchCommitByPullNumber.private.test.ts.snap index 9064b99a..8708995a 100644 --- a/src/services/github/v4/fetchCommits/__snapshots__/fetchCommitByPullNumber.private.test.ts.snap +++ b/src/services/github/v4/fetchCommits/__snapshots__/fetchCommitByPullNumber.private.test.ts.snap @@ -5,14 +5,13 @@ exports[`fetchCommitByPullNumber snapshot request/response makes the right queri repository(owner: $repoOwner, name: $repoName) { pullRequest(number: $pullNumber) { mergeCommit { - ...SourceCommitWithTargetPullRequest + ...SourceCommitWithTargetPullRequestFragment } } } } -fragment SourceCommitWithTargetPullRequest on Commit { - ...RemoteConfigHistory +fragment SourceCommitWithTargetPullRequestFragment on Commit { repository { name owner { @@ -34,6 +33,7 @@ fragment SourceCommitWithTargetPullRequest on Commit { } baseRefName mergeCommit { + ...RemoteConfigHistoryFragment sha: oid message } @@ -80,7 +80,7 @@ fragment SourceCommitWithTargetPullRequest on Commit { } } -fragment RemoteConfigHistory on Commit { +fragment RemoteConfigHistoryFragment on Commit { remoteConfigHistory: history(first: 1, path: \\".backportrc.json\\") { edges { remoteConfig: node { diff --git a/src/services/github/v4/fetchCommits/__snapshots__/fetchCommitBySha.private.test.ts.snap b/src/services/github/v4/fetchCommits/__snapshots__/fetchCommitBySha.private.test.ts.snap index 303ff84b..14f6ae51 100644 --- a/src/services/github/v4/fetchCommits/__snapshots__/fetchCommitBySha.private.test.ts.snap +++ b/src/services/github/v4/fetchCommits/__snapshots__/fetchCommitBySha.private.test.ts.snap @@ -4,13 +4,12 @@ exports[`fetchCommitBySha snapshot request/response makes the right queries: Que "query CommitsBySha($repoOwner: String!, $repoName: String!, $sha: String!) { repository(owner: $repoOwner, name: $repoName) { object(expression: $sha) { - ...SourceCommitWithTargetPullRequest + ...SourceCommitWithTargetPullRequestFragment } } } -fragment SourceCommitWithTargetPullRequest on Commit { - ...RemoteConfigHistory +fragment SourceCommitWithTargetPullRequestFragment on Commit { repository { name owner { @@ -32,6 +31,7 @@ fragment SourceCommitWithTargetPullRequest on Commit { } baseRefName mergeCommit { + ...RemoteConfigHistoryFragment sha: oid message } @@ -78,7 +78,7 @@ fragment SourceCommitWithTargetPullRequest on Commit { } } -fragment RemoteConfigHistory on Commit { +fragment RemoteConfigHistoryFragment on Commit { remoteConfigHistory: history(first: 1, path: \\".backportrc.json\\") { edges { remoteConfig: node { diff --git a/src/services/github/v4/fetchCommits/__snapshots__/fetchCommitsByAuthor.private.test.ts.snap b/src/services/github/v4/fetchCommits/__snapshots__/fetchCommitsByAuthor.private.test.ts.snap index f7dea0b8..b79ff868 100644 --- a/src/services/github/v4/fetchCommits/__snapshots__/fetchCommitsByAuthor.private.test.ts.snap +++ b/src/services/github/v4/fetchCommits/__snapshots__/fetchCommitsByAuthor.private.test.ts.snap @@ -23,7 +23,7 @@ exports[`fetchCommitsByAuthor snapshot request/response makes the right queries: ) { edges { node { - ...SourceCommitWithTargetPullRequest + ...SourceCommitWithTargetPullRequestFragment } } } @@ -33,8 +33,7 @@ exports[`fetchCommitsByAuthor snapshot request/response makes the right queries: } } -fragment SourceCommitWithTargetPullRequest on Commit { - ...RemoteConfigHistory +fragment SourceCommitWithTargetPullRequestFragment on Commit { repository { name owner { @@ -56,6 +55,7 @@ fragment SourceCommitWithTargetPullRequest on Commit { } baseRefName mergeCommit { + ...RemoteConfigHistoryFragment sha: oid message } @@ -102,7 +102,7 @@ fragment SourceCommitWithTargetPullRequest on Commit { } } -fragment RemoteConfigHistory on Commit { +fragment RemoteConfigHistoryFragment on Commit { remoteConfigHistory: history(first: 1, path: \\".backportrc.json\\") { edges { remoteConfig: node { diff --git a/src/services/github/v4/fetchCommits/__snapshots__/fetchCommitsByAuthor.test.ts.snap b/src/services/github/v4/fetchCommits/__snapshots__/fetchCommitsByAuthor.test.ts.snap index 8a08c5a7..d70e78b2 100644 --- a/src/services/github/v4/fetchCommits/__snapshots__/fetchCommitsByAuthor.test.ts.snap +++ b/src/services/github/v4/fetchCommits/__snapshots__/fetchCommitsByAuthor.test.ts.snap @@ -32,7 +32,7 @@ Array [ ) { edges { node { - ...SourceCommitWithTargetPullRequest + ...SourceCommitWithTargetPullRequestFragment } } } @@ -42,8 +42,7 @@ Array [ } } -fragment SourceCommitWithTargetPullRequest on Commit { - ...RemoteConfigHistory +fragment SourceCommitWithTargetPullRequestFragment on Commit { repository { name owner { @@ -65,6 +64,7 @@ fragment SourceCommitWithTargetPullRequest on Commit { } baseRefName mergeCommit { + ...RemoteConfigHistoryFragment sha: oid message } @@ -111,7 +111,7 @@ fragment SourceCommitWithTargetPullRequest on Commit { } } -fragment RemoteConfigHistory on Commit { +fragment RemoteConfigHistoryFragment on Commit { remoteConfigHistory: history(first: 1, path: \\".backportrc.json\\") { edges { remoteConfig: node { diff --git a/src/services/github/v4/fetchCommits/fetchCommitByPullNumber.ts b/src/services/github/v4/fetchCommits/fetchCommitByPullNumber.ts index af1a6b8d..d7112727 100644 --- a/src/services/github/v4/fetchCommits/fetchCommitByPullNumber.ts +++ b/src/services/github/v4/fetchCommits/fetchCommitByPullNumber.ts @@ -5,7 +5,7 @@ import { swallowMissingConfigFileException } from '../../../remoteConfig'; import { Commit, SourceCommitWithTargetPullRequest, - sourceCommitWithTargetPullRequestFragment, + SourceCommitWithTargetPullRequestFragment, parseSourceCommit, } from '../../../sourceCommit/parseSourceCommit'; import { apiRequestV4 } from '../apiRequestV4'; @@ -36,13 +36,13 @@ export async function fetchCommitByPullNumber(options: { repository(owner: $repoOwner, name: $repoName) { pullRequest(number: $pullNumber) { mergeCommit { - ...SourceCommitWithTargetPullRequest + ...SourceCommitWithTargetPullRequestFragment } } } } - ${sourceCommitWithTargetPullRequestFragment} + ${SourceCommitWithTargetPullRequestFragment} `; let res: CommitByPullNumberResponse; diff --git a/src/services/github/v4/fetchCommits/fetchCommitBySha.ts b/src/services/github/v4/fetchCommits/fetchCommitBySha.ts index 9b51aa7e..ad04e0e1 100644 --- a/src/services/github/v4/fetchCommits/fetchCommitBySha.ts +++ b/src/services/github/v4/fetchCommits/fetchCommitBySha.ts @@ -5,7 +5,7 @@ import { swallowMissingConfigFileException } from '../../../remoteConfig'; import { Commit, SourceCommitWithTargetPullRequest, - sourceCommitWithTargetPullRequestFragment, + SourceCommitWithTargetPullRequestFragment, parseSourceCommit, } from '../../../sourceCommit/parseSourceCommit'; import { apiRequestV4 } from '../apiRequestV4'; @@ -32,12 +32,12 @@ export async function fetchCommitBySha(options: { query CommitsBySha($repoOwner: String!, $repoName: String!, $sha: String!) { repository(owner: $repoOwner, name: $repoName) { object(expression: $sha) { - ...SourceCommitWithTargetPullRequest + ...SourceCommitWithTargetPullRequestFragment } } } - ${sourceCommitWithTargetPullRequestFragment} + ${SourceCommitWithTargetPullRequestFragment} `; let res: CommitsByShaResponse; diff --git a/src/services/github/v4/fetchCommits/fetchCommitsByAuthor.ts b/src/services/github/v4/fetchCommits/fetchCommitsByAuthor.ts index f3c68075..f35b259a 100644 --- a/src/services/github/v4/fetchCommits/fetchCommitsByAuthor.ts +++ b/src/services/github/v4/fetchCommits/fetchCommitsByAuthor.ts @@ -7,7 +7,7 @@ import { swallowMissingConfigFileException } from '../../../remoteConfig'; import { Commit, SourceCommitWithTargetPullRequest, - sourceCommitWithTargetPullRequestFragment, + SourceCommitWithTargetPullRequestFragment, parseSourceCommit, } from '../../../sourceCommit/parseSourceCommit'; import { apiRequestV4 } from '../apiRequestV4'; @@ -66,7 +66,7 @@ async function fetchByCommitPath({ ) { edges { node { - ...SourceCommitWithTargetPullRequest + ...SourceCommitWithTargetPullRequestFragment } } } @@ -76,7 +76,7 @@ async function fetchByCommitPath({ } } - ${sourceCommitWithTargetPullRequestFragment} + ${SourceCommitWithTargetPullRequestFragment} `; const variables = { diff --git a/src/services/github/v4/fetchCommits/fetchPullRequestBySearchQuery.ts b/src/services/github/v4/fetchCommits/fetchPullRequestBySearchQuery.ts index 4555b079..6564488c 100644 --- a/src/services/github/v4/fetchCommits/fetchPullRequestBySearchQuery.ts +++ b/src/services/github/v4/fetchCommits/fetchPullRequestBySearchQuery.ts @@ -6,7 +6,7 @@ import { swallowMissingConfigFileException } from '../../../remoteConfig'; import { Commit, SourceCommitWithTargetPullRequest, - sourceCommitWithTargetPullRequestFragment, + SourceCommitWithTargetPullRequestFragment, parseSourceCommit, } from '../../../sourceCommit/parseSourceCommit'; import { apiRequestV4 } from '../apiRequestV4'; @@ -31,14 +31,14 @@ export async function fetchPullRequestBySearchQuery( nodes { ... on PullRequest { mergeCommit { - ...SourceCommitWithTargetPullRequest + ...SourceCommitWithTargetPullRequestFragment } } } } } - ${sourceCommitWithTargetPullRequestFragment} + ${SourceCommitWithTargetPullRequestFragment} `; const authorFilter = author ? ` author:${author}` : ''; diff --git a/src/services/github/v4/fetchRemoteProjectConfig.ts b/src/services/github/v4/fetchRemoteProjectConfig.ts index 0de8ea48..f25c03b3 100644 --- a/src/services/github/v4/fetchRemoteProjectConfig.ts +++ b/src/services/github/v4/fetchRemoteProjectConfig.ts @@ -26,7 +26,7 @@ export async function fetchRemoteProjectConfig(options: { repository(owner: $repoOwner, name: $repoName) { ref(qualifiedName: $sourceBranch) { target { - ...RemoteConfigHistory + ...RemoteConfigHistoryFragment } } } diff --git a/src/services/github/v4/getOptionsFromGithub/query.ts b/src/services/github/v4/getOptionsFromGithub/query.ts index 42123e42..d473e88f 100644 --- a/src/services/github/v4/getOptionsFromGithub/query.ts +++ b/src/services/github/v4/getOptionsFromGithub/query.ts @@ -30,7 +30,7 @@ export const query = gql` defaultBranchRef { name target { - ...RemoteConfigHistory + ...RemoteConfigHistoryFragment } } } diff --git a/src/services/github/v4/mocks/commitsByAuthorMock.ts b/src/services/github/v4/mocks/commitsByAuthorMock.ts index a2529889..f5eeed45 100644 --- a/src/services/github/v4/mocks/commitsByAuthorMock.ts +++ b/src/services/github/v4/mocks/commitsByAuthorMock.ts @@ -8,7 +8,6 @@ export const commitsByAuthorMock: CommitByAuthorResponse = { edges: [ { node: { - remoteConfigHistory: { edges: [] }, repository: { name: 'kibana', owner: { login: 'elastic' }, @@ -23,7 +22,6 @@ export const commitsByAuthorMock: CommitByAuthorResponse = { }, { node: { - remoteConfigHistory: { edges: [] }, repository: { name: 'kibana', owner: { login: 'elastic' }, @@ -42,6 +40,7 @@ export const commitsByAuthorMock: CommitByAuthorResponse = { }, number: 85, mergeCommit: { + remoteConfigHistory: { edges: [] }, sha: 'f3b618b9421fdecdb36862f907afbdd6344b361d', message: 'Add witch (#85)', }, @@ -56,7 +55,6 @@ export const commitsByAuthorMock: CommitByAuthorResponse = { }, { node: { - remoteConfigHistory: { edges: [] }, repository: { name: 'kibana', owner: { login: 'elastic' }, @@ -76,6 +74,7 @@ export const commitsByAuthorMock: CommitByAuthorResponse = { baseRefName: 'master', number: 80, mergeCommit: { + remoteConfigHistory: { edges: [] }, sha: '79cf18453ec32a4677009dcbab1c9c8c73fc14fe', message: 'Add SF mention (#80)\n\n* Add SF mention\r\n\r\n* Add several emojis!', @@ -126,7 +125,6 @@ export const commitsByAuthorMock: CommitByAuthorResponse = { }, { node: { - remoteConfigHistory: { edges: [] }, repository: { name: 'kibana', owner: { login: 'elastic' }, @@ -141,7 +139,6 @@ export const commitsByAuthorMock: CommitByAuthorResponse = { }, { node: { - remoteConfigHistory: { edges: [] }, repository: { name: 'kibana', owner: { login: 'elastic' }, diff --git a/src/services/github/v4/throwOnInvalidAccessToken.test.ts b/src/services/github/v4/throwOnInvalidAccessToken.test.ts index 94970a1e..62b264e4 100644 --- a/src/services/github/v4/throwOnInvalidAccessToken.test.ts +++ b/src/services/github/v4/throwOnInvalidAccessToken.test.ts @@ -4,7 +4,7 @@ describe('throwOnInvalidAccessToken', () => { describe('when status code is', () => { it('should handle invalid access token', () => { const error = { - axiosResponse: { + githubResponse: { status: 401, headers: {}, }, @@ -23,7 +23,7 @@ describe('throwOnInvalidAccessToken', () => { it('should handle SSO error', () => { const error = { - axiosResponse: { + githubResponse: { status: 200, headers: { 'x-github-sso': 'required; url=https://ssourl.com' }, data: { @@ -43,7 +43,7 @@ describe('throwOnInvalidAccessToken', () => { it('should handle non-existing repo', () => { const error = { - axiosResponse: { + githubResponse: { status: 200, headers: { 'x-oauth-scopes': 'a,b,c', @@ -66,7 +66,7 @@ describe('throwOnInvalidAccessToken', () => { it('should handle insufficient permissions (oauth scopes)', () => { const error = { - axiosResponse: { + githubResponse: { status: 200, headers: { 'x-oauth-scopes': 'a,b', @@ -89,7 +89,7 @@ describe('throwOnInvalidAccessToken', () => { it('should not handle unknown cases', () => { const error = { - axiosResponse: { + githubResponse: { status: 500, headers: {}, }, diff --git a/src/services/github/v4/throwOnInvalidAccessToken.ts b/src/services/github/v4/throwOnInvalidAccessToken.ts index a69b7d10..7aef9482 100644 --- a/src/services/github/v4/throwOnInvalidAccessToken.ts +++ b/src/services/github/v4/throwOnInvalidAccessToken.ts @@ -20,19 +20,20 @@ export function throwOnInvalidAccessToken({ } } - const statusCode = error.axiosResponse.status; + const statusCode = error.githubResponse.status; switch (statusCode) { case 200: { - const repoNotFound = error.axiosResponse.data.errors?.some( + const repoNotFound = error.githubResponse.data.errors?.some( (error) => error.type === 'NOT_FOUND' && error.path.join('.') === 'repository' ); - const grantedScopes = error.axiosResponse.headers['x-oauth-scopes'] || ''; + const grantedScopes = + error.githubResponse.headers['x-oauth-scopes'] || ''; const requiredScopes = - error.axiosResponse.headers['x-accepted-oauth-scopes'] || ''; - const ssoHeader = maybe(error.axiosResponse.headers['x-github-sso']); + error.githubResponse.headers['x-accepted-oauth-scopes'] || ''; + const ssoHeader = maybe(error.githubResponse.headers['x-github-sso']); if (repoNotFound) { const hasRequiredScopes = isEmpty( @@ -52,7 +53,7 @@ export function throwOnInvalidAccessToken({ ); } - const repoAccessForbidden = error.axiosResponse.data.errors?.some( + const repoAccessForbidden = error.githubResponse.data.errors?.some( (error) => error.type === 'FORBIDDEN' ); diff --git a/src/services/remoteConfig.ts b/src/services/remoteConfig.ts index db89b8bc..9110f0e1 100644 --- a/src/services/remoteConfig.ts +++ b/src/services/remoteConfig.ts @@ -5,7 +5,7 @@ import { GithubV4Exception } from './github/v4/apiRequestV4'; import { logger } from './logger'; export const RemoteConfigHistoryFragment = gql` - fragment RemoteConfigHistory on Commit { + fragment RemoteConfigHistoryFragment on Commit { remoteConfigHistory: history(first: 1, path: ".backportrc.json") { edges { remoteConfig: node { @@ -54,7 +54,7 @@ export function parseRemoteConfig(remoteConfig: RemoteConfig) { export function swallowMissingConfigFileException( error: GithubV4Exception ) { - const { data, errors } = error.axiosResponse.data; + const { data, errors } = error.githubResponse.data; const missingConfigError = errors?.some((error) => { return error.path.includes('remoteConfig') && error.type === 'NOT_FOUND'; diff --git a/src/services/sourceCommit/getExpectedTargetPullRequests.test.ts b/src/services/sourceCommit/getExpectedTargetPullRequests.test.ts index fa0d568a..d63a1a22 100644 --- a/src/services/sourceCommit/getExpectedTargetPullRequests.test.ts +++ b/src/services/sourceCommit/getExpectedTargetPullRequests.test.ts @@ -14,10 +14,10 @@ describe('getExpectedTargetPullRequests', () => { }); const branchLabelMapping = {}; - const expectedTargetPRs = getExpectedTargetPullRequests( - mockSourceCommit, - branchLabelMapping - ); + const expectedTargetPRs = getExpectedTargetPullRequests({ + sourceCommit: mockSourceCommit, + latestBranchLabelMapping: branchLabelMapping, + }); expect(expectedTargetPRs).toEqual([]); }); @@ -35,10 +35,10 @@ describe('getExpectedTargetPullRequests', () => { ], }); const branchLabelMapping = {}; - const expectedTargetPRs = getExpectedTargetPullRequests( - mockSourceCommit, - branchLabelMapping - ); + const expectedTargetPRs = getExpectedTargetPullRequests({ + sourceCommit: mockSourceCommit, + latestBranchLabelMapping: branchLabelMapping, + }); expect(expectedTargetPRs).toEqual([ { branch: '6.x', @@ -68,10 +68,10 @@ describe('getExpectedTargetPullRequests', () => { ], }); const branchLabelMapping = {}; - const expectedTargetPullRequests = getExpectedTargetPullRequests( - mockSourceCommit, - branchLabelMapping - ); + const expectedTargetPullRequests = getExpectedTargetPullRequests({ + sourceCommit: mockSourceCommit, + latestBranchLabelMapping: branchLabelMapping, + }); expect(expectedTargetPullRequests).toEqual([]); }); @@ -90,10 +90,10 @@ describe('getExpectedTargetPullRequests', () => { ], }); const branchLabelMapping = {}; - const expectedTargetPullRequests = getExpectedTargetPullRequests( - mockSourceCommit, - branchLabelMapping - ); + const expectedTargetPullRequests = getExpectedTargetPullRequests({ + sourceCommit: mockSourceCommit, + latestBranchLabelMapping: branchLabelMapping, + }); expect(expectedTargetPullRequests).toEqual([]); }); @@ -113,10 +113,10 @@ describe('getExpectedTargetPullRequests', () => { ], }); const branchLabelMapping = {}; - const expectedTargetPullRequests = getExpectedTargetPullRequests( - mockSourceCommit, - branchLabelMapping - ); + const expectedTargetPullRequests = getExpectedTargetPullRequests({ + sourceCommit: mockSourceCommit, + latestBranchLabelMapping: branchLabelMapping, + }); expect(expectedTargetPullRequests).toEqual([]); }); @@ -137,10 +137,10 @@ describe('getExpectedTargetPullRequests', () => { ], }); const branchLabelMapping = {}; - const expectedTargetPullRequests = getExpectedTargetPullRequests( - mockSourceCommit, - branchLabelMapping - ); + const expectedTargetPullRequests = getExpectedTargetPullRequests({ + sourceCommit: mockSourceCommit, + latestBranchLabelMapping: branchLabelMapping, + }); expect(expectedTargetPullRequests).toEqual([ { branch: '6.x', @@ -172,10 +172,10 @@ describe('getExpectedTargetPullRequests', () => { ], }); const branchLabelMapping = {}; - const expectedTargetPullRequests = getExpectedTargetPullRequests( - mockSourceCommit, - branchLabelMapping - ); + const expectedTargetPullRequests = getExpectedTargetPullRequests({ + sourceCommit: mockSourceCommit, + latestBranchLabelMapping: branchLabelMapping, + }); expect(expectedTargetPullRequests).toEqual([]); }); @@ -195,10 +195,10 @@ describe('getExpectedTargetPullRequests', () => { ], }); const branchLabelMapping = {}; - const expectedTargetPullRequests = getExpectedTargetPullRequests( - mockSourceCommit, - branchLabelMapping - ); + const expectedTargetPullRequests = getExpectedTargetPullRequests({ + sourceCommit: mockSourceCommit, + latestBranchLabelMapping: branchLabelMapping, + }); expect(expectedTargetPullRequests).toEqual([ { branch: '6.x', @@ -226,10 +226,10 @@ describe('getExpectedTargetPullRequests', () => { 'v8.0.0': 'master', '^v(\\d+).(\\d+).\\d+$': '$1.$2', }; - const expectedTargetPullRequests = getExpectedTargetPullRequests( - mockSourceCommit, - branchLabelMapping - ); + const expectedTargetPullRequests = getExpectedTargetPullRequests({ + sourceCommit: mockSourceCommit, + latestBranchLabelMapping: branchLabelMapping, + }); expect(expectedTargetPullRequests).toEqual([ { branch: '7.2', state: 'MISSING' }, { branch: '7.1', state: 'MISSING' }, @@ -257,10 +257,10 @@ describe('getExpectedTargetPullRequests', () => { 'v8.0.0': 'master', '^v(\\d+).(\\d+).\\d+$': '$1.$2', }; - const expectedTargetPullRequests = getExpectedTargetPullRequests( - mockSourceCommit, - branchLabelMapping - ); + const expectedTargetPullRequests = getExpectedTargetPullRequests({ + sourceCommit: mockSourceCommit, + latestBranchLabelMapping: branchLabelMapping, + }); expect(expectedTargetPullRequests).toEqual([ { branch: '7.2', @@ -315,10 +315,10 @@ describe('getExpectedTargetPullRequests', () => { }, }); - const expectedTargetPullRequests = getExpectedTargetPullRequests( - mockSourceCommit, - branchLabelMapping - ); + const expectedTargetPullRequests = getExpectedTargetPullRequests({ + sourceCommit: mockSourceCommit, + latestBranchLabelMapping: branchLabelMapping, + }); expect(expectedTargetPullRequests).toEqual([ { branch: '5.4', state: 'MISSING' }, @@ -359,10 +359,10 @@ describe('getExpectedTargetPullRequests', () => { }, }); - const expectedTargetPullRequests = getExpectedTargetPullRequests( - mockSourceCommit, - branchLabelMapping - ); + const expectedTargetPullRequests = getExpectedTargetPullRequests({ + sourceCommit: mockSourceCommit, + latestBranchLabelMapping: branchLabelMapping, + }); expect(expectedTargetPullRequests).toEqual([ { branch: 'dev', state: 'MISSING' }, @@ -384,10 +384,10 @@ describe('getExpectedTargetPullRequests', () => { }, }); - const expectedTargetPullRequests = getExpectedTargetPullRequests( - mockSourceCommit, - branchLabelMapping - ); + const expectedTargetPullRequests = getExpectedTargetPullRequests({ + sourceCommit: mockSourceCommit, + latestBranchLabelMapping: branchLabelMapping, + }); expect(expectedTargetPullRequests).toEqual([ { branch: 'branch-b', state: 'MISSING' }, @@ -415,10 +415,10 @@ describe('getExpectedTargetPullRequests', () => { ], }); - const expectedTargetPullRequests = getExpectedTargetPullRequests( - mockSourceCommit, - branchLabelMapping - ); + const expectedTargetPullRequests = getExpectedTargetPullRequests({ + sourceCommit: mockSourceCommit, + latestBranchLabelMapping: branchLabelMapping, + }); expect(expectedTargetPullRequests).toEqual([ { @@ -454,10 +454,10 @@ describe('getExpectedTargetPullRequests', () => { ], }); - const expectedTargetPullRequests = getExpectedTargetPullRequests( - mockSourceCommit, - branchLabelMapping - ); + const expectedTargetPullRequests = getExpectedTargetPullRequests({ + sourceCommit: mockSourceCommit, + latestBranchLabelMapping: branchLabelMapping, + }); expect(expectedTargetPullRequests).toEqual([ { branch: 'branch-1', state: 'MISSING' }, @@ -488,10 +488,10 @@ describe('getExpectedTargetPullRequests', () => { ], }); - const expectedTargetPullRequests = getExpectedTargetPullRequests( - mockSourceCommit, - branchLabelMapping - ); + const expectedTargetPullRequests = getExpectedTargetPullRequests({ + sourceCommit: mockSourceCommit, + latestBranchLabelMapping: branchLabelMapping, + }); expect(expectedTargetPullRequests).toEqual([ { diff --git a/src/services/sourceCommit/getExpectedTargetPullRequests.ts b/src/services/sourceCommit/getExpectedTargetPullRequests.ts index afc1143a..1208496d 100644 --- a/src/services/sourceCommit/getExpectedTargetPullRequests.ts +++ b/src/services/sourceCommit/getExpectedTargetPullRequests.ts @@ -2,6 +2,7 @@ import { uniq } from 'lodash'; import { ValidConfigOptions } from '../../options/options'; import { filterNil } from '../../utils/filterEmpty'; import { getFirstLine } from '../github/commitFormatters'; +import { parseRemoteConfig } from '../remoteConfig'; import { SourcePullRequestNode, SourceCommitWithTargetPullRequest, @@ -20,13 +21,23 @@ export type ExpectedTargetPullRequest = { }; }; -export function getExpectedTargetPullRequests( - sourceCommit: SourceCommitWithTargetPullRequest, - branchLabelMapping: ValidConfigOptions['branchLabelMapping'] -): ExpectedTargetPullRequest[] { +export function getExpectedTargetPullRequests({ + sourceCommit, + latestBranchLabelMapping, +}: { + sourceCommit: SourceCommitWithTargetPullRequest; + latestBranchLabelMapping: ValidConfigOptions['branchLabelMapping']; +}): ExpectedTargetPullRequest[] { const sourcePullRequest = sourceCommit.associatedPullRequests.edges?.[0]?.node; + const remoteConfig = + sourcePullRequest?.mergeCommit.remoteConfigHistory.edges?.[0]?.remoteConfig; + + const branchLabelMapping = + (remoteConfig && parseRemoteConfig(remoteConfig)?.branchLabelMapping) ?? + latestBranchLabelMapping; + // if there is no source pull request the commit was pushed directly to the source branch // in that case there will be no labels, and thus not possible to deduce the expected target branches if (!sourcePullRequest) { diff --git a/src/services/sourceCommit/getMockSourceCommit.ts b/src/services/sourceCommit/getMockSourceCommit.ts index 7707727a..b47cc822 100644 --- a/src/services/sourceCommit/getMockSourceCommit.ts +++ b/src/services/sourceCommit/getMockSourceCommit.ts @@ -34,25 +34,6 @@ export function getMockSourceCommit({ const defaultSourceCommitSha = 'DO NOT USE: default-source-commit-sha'; const baseMockCommit: SourceCommitWithTargetPullRequest = { - remoteConfigHistory: sourceCommit.remoteConfig - ? { - edges: [ - { - remoteConfig: { - committedDate: sourceCommit.remoteConfig.committedDate, - file: { - object: { - text: JSON.stringify({ - branchLabelMapping: - sourceCommit.remoteConfig.branchLabelMapping, - }), - }, - }, - }, - }, - ], - } - : { edges: [] }, repository: { name: 'kibana', owner: { login: 'elastic' }, @@ -67,6 +48,26 @@ export function getMockSourceCommit({ return baseMockCommit; } + const remoteConfigHistory = sourceCommit.remoteConfig + ? { + edges: [ + { + remoteConfig: { + committedDate: sourceCommit.remoteConfig.committedDate, + file: { + object: { + text: JSON.stringify({ + branchLabelMapping: + sourceCommit.remoteConfig.branchLabelMapping, + }), + }, + }, + }, + }, + ], + } + : { edges: [] }; + return { ...baseMockCommit, associatedPullRequests: { @@ -74,6 +75,7 @@ export function getMockSourceCommit({ { node: { mergeCommit: { + remoteConfigHistory, sha: sourceCommit.sha ?? defaultSourceCommitSha, message: sourceCommit.message, }, diff --git a/src/services/sourceCommit/parseSourceCommit.ts b/src/services/sourceCommit/parseSourceCommit.ts index e2879741..d216843d 100644 --- a/src/services/sourceCommit/parseSourceCommit.ts +++ b/src/services/sourceCommit/parseSourceCommit.ts @@ -1,7 +1,6 @@ import gql from 'graphql-tag'; import { ValidConfigOptions } from '../../options/options'; import { - parseRemoteConfig, RemoteConfigHistory, RemoteConfigHistoryFragment, } from '../remoteConfig'; @@ -38,6 +37,7 @@ export interface SourcePullRequestNode { }[]; }; mergeCommit: { + remoteConfigHistory: RemoteConfigHistory['remoteConfigHistory']; sha: string; message: string; }; @@ -84,7 +84,6 @@ interface TimelineIssueEdge { } export type SourceCommitWithTargetPullRequest = { - remoteConfigHistory: RemoteConfigHistory['remoteConfigHistory']; repository: { name: string; owner: { login: string }; @@ -110,22 +109,6 @@ export function parseSourceCommit({ const sourcePullRequest = sourceCommit.associatedPullRequests.edges?.[0]?.node; - // use info from associated pull request if available. Fall back to commit info - - const sourceBranch = sourcePullRequest?.baseRefName ?? options.sourceBranch; - const remoteConfig = - sourceCommit.remoteConfigHistory.edges?.[0]?.remoteConfig; - - const branchLabelMapping = remoteConfig - ? parseRemoteConfig(remoteConfig)?.branchLabelMapping ?? - options.branchLabelMapping - : options.branchLabelMapping; - - const expectedTargetPullRequests = getExpectedTargetPullRequests( - sourceCommit, - branchLabelMapping - ); - return { sourceCommit: { committedDate: sourceCommit.committedDate, @@ -142,15 +125,16 @@ export function parseSourceCommit({ }, } : undefined, - sourceBranch, - expectedTargetPullRequests, + sourceBranch: sourcePullRequest?.baseRefName ?? options.sourceBranch, + expectedTargetPullRequests: getExpectedTargetPullRequests({ + sourceCommit, + latestBranchLabelMapping: options.branchLabelMapping, + }), }; } -export const sourceCommitWithTargetPullRequestFragment = gql` - fragment SourceCommitWithTargetPullRequest on Commit { - ...RemoteConfigHistory - +export const SourceCommitWithTargetPullRequestFragment = gql` + fragment SourceCommitWithTargetPullRequestFragment on Commit { # Source Commit repository { name @@ -177,6 +161,7 @@ export const sourceCommitWithTargetPullRequestFragment = gql` # source merge commit (the commit that actually went into the source branch) mergeCommit { + ...RemoteConfigHistoryFragment sha: oid message } diff --git a/src/test/backport-e2e.mutation.test.ts b/src/test/backport-e2e.mutation.test.ts deleted file mode 100644 index 782bdf1d..00000000 --- a/src/test/backport-e2e.mutation.test.ts +++ /dev/null @@ -1,412 +0,0 @@ -/* eslint-disable jest/no-commented-out-tests */ -import os from 'os'; -import { Octokit } from '@octokit/rest'; -import nock from 'nock'; -import { getOptions } from '../options/options'; -import { runSequentially } from '../runSequentially'; -import { getCommits } from '../ui/getCommits'; -import { mockConfigFiles } from './mockConfigFiles'; -import { listenForCallsToNockScope } from './nockHelpers'; -import { getDevAccessToken } from './private/getDevAccessToken'; -import { getSandboxPath, resetSandbox } from './sandbox'; - -jest.setTimeout(10000); - -const sandboxPath = getSandboxPath({ filename: __filename }); -const REPO_OWNER = 'backport-org'; -const REPO_NAME = 'integration-test'; -const BRANCH_WITH_ONE_COMMIT = 'backport/7.x/commit-5bf29b7d'; -const BRANCH_WITH_TWO_COMMITS = 'backport/7.x/commit-5bf29b7d_commit-59d6ff1c'; -const AUTHOR = 'sqren'; - -describe('backport e2e', () => { - let accessToken: string; - afterAll(() => { - nock.cleanAll(); - }); - - beforeAll(async () => { - // set alternative homedir - jest.spyOn(os, 'homedir').mockReturnValue(`${sandboxPath}/homedir`); - accessToken = getDevAccessToken(); - - mockConfigFiles({ - globalConfig: {}, - projectConfig: {}, - }); - }); - - describe('when a single commit is backported', () => { - let res: Awaited>; - - let createPullRequestsMockCalls: unknown[]; - - beforeAll(async () => { - await resetState(accessToken); - - createPullRequestsMockCalls = mockCreatePullRequest({ - number: 1337, - html_url: 'myHtmlUrl', - }); - - const options = await getOptions([], { - accessToken, - author: AUTHOR, - ci: true, - githubApiBaseUrlV3: 'https://api.foo.com', - repoName: 'integration-test', - repoOwner: 'backport-org', - sha: '5bf29b7d847ea3dbde9280448f0f62ad0f22d3ad', - }); - const commits = await getCommits(options); - const targetBranches: string[] = ['7.x']; - - res = await runSequentially({ options, commits, targetBranches }); - }); - - it('returns pull request', () => { - expect(res).toEqual([ - { - didUpdate: false, - pullRequestUrl: 'myHtmlUrl', - pullRequestNumber: 1337, - status: 'success', - targetBranch: '7.x', - }, - ]); - }); - - it('sends the correct http body when creating pull request', () => { - expect(createPullRequestsMockCalls).toMatchInlineSnapshot(` - Array [ - Object { - "base": "7.x", - "body": "# Backport - - This will backport the following commits from \`master\` to \`7.x\`: - - Add ❤️ emoji (5bf29b7d) - - - - ### Questions ? - Please refer to the [Backport tool documentation](https://github.com/sqren/backport)", - "head": "sqren:backport/7.x/commit-5bf29b7d", - "title": "[7.x] Add ❤️ emoji", - }, - ] - `); - }); - - it('should not create new branches in origin (backport-org/integration-test)', async () => { - const branches = await getBranches({ - accessToken, - repoOwner: REPO_OWNER, - repoName: REPO_NAME, - }); - expect(branches.map((b) => b.name)).toEqual(['7.x', 'master']); - }); - - it('should create branch in the fork (sqren/integration-test)', async () => { - const branches = await getBranches({ - accessToken, - repoOwner: AUTHOR, - repoName: REPO_NAME, - }); - expect(branches.map((b) => b.name)).toEqual([ - '7.x', - BRANCH_WITH_ONE_COMMIT, - 'master', - ]); - }); - }); - - describe('when two commits are backported', () => { - let createPullRequestsMockCalls: unknown[]; - let res: Awaited>; - - beforeAll(async () => { - await resetState(accessToken); - - createPullRequestsMockCalls = mockCreatePullRequest({ - number: 1337, - html_url: 'myHtmlUrl', - }); - - const options = await getOptions([], { - accessToken, - author: AUTHOR, - ci: true, - githubApiBaseUrlV3: 'https://api.foo.com', - repoName: 'integration-test', - repoOwner: 'backport-org', - }); - const commits = [ - ...(await getCommits({ - ...options, - sha: '5bf29b7d847ea3dbde9280448f0f62ad0f22d3ad', - })), - ...(await getCommits({ - ...options, - sha: '59d6ff1ca90a4ce210c0a4f0e159214875c19d60', - })), - ]; - - const targetBranches: string[] = ['7.x']; - res = await runSequentially({ options, commits, targetBranches }); - }); - - it('contains both commits in the pull request body', () => { - expect(createPullRequestsMockCalls).toMatchInlineSnapshot(` - Array [ - Object { - "base": "7.x", - "body": "# Backport - - This will backport the following commits from \`master\` to \`7.x\`: - - Add ❤️ emoji (5bf29b7d) - - Add family emoji (#2) (59d6ff1c) - - - - ### Questions ? - Please refer to the [Backport tool documentation](https://github.com/sqren/backport)", - "head": "sqren:backport/7.x/commit-5bf29b7d_commit-59d6ff1c", - "title": "[7.x] Add ❤️ emoji | Add family emoji (#2)", - }, - ] - `); - }); - - it('returns the pull request response', () => { - expect(res).toEqual([ - { - didUpdate: false, - pullRequestNumber: 1337, - status: 'success', - pullRequestUrl: 'myHtmlUrl', - targetBranch: '7.x', - }, - ]); - }); - - it('should not create new branches in origin (backport-org/integration-test)', async () => { - const branches = await getBranches({ - accessToken, - repoOwner: REPO_OWNER, - repoName: REPO_NAME, - }); - expect(branches.map((b) => b.name)).toEqual(['7.x', 'master']); - }); - - it('should create branch in the fork (sqren/integration-test)', async () => { - const branches = await getBranches({ - accessToken, - repoOwner: AUTHOR, - repoName: REPO_NAME, - }); - expect(branches.map((b) => b.name)).toEqual([ - '7.x', - BRANCH_WITH_TWO_COMMITS, - 'master', - ]); - }); - }); - - describe('when disabling fork mode', () => { - let res: Awaited>; - let createPullRequestsMockCalls: unknown[]; - - beforeAll(async () => { - await resetState(accessToken); - - createPullRequestsMockCalls = mockCreatePullRequest({ - number: 1337, - html_url: 'myHtmlUrl', - }); - - const options = await getOptions([], { - accessToken, - author: AUTHOR, - ci: true, - fork: false, - githubApiBaseUrlV3: 'https://api.foo.com', - repoName: 'integration-test', - repoOwner: 'backport-org', - sha: '5bf29b7d847ea3dbde9280448f0f62ad0f22d3ad', - }); - const commits = await getCommits(options); - const targetBranches: string[] = ['7.x']; - - res = await runSequentially({ options, commits, targetBranches }); - }); - - it('sends the correct http body when creating pull request', () => { - expect(createPullRequestsMockCalls).toMatchInlineSnapshot(` - Array [ - Object { - "base": "7.x", - "body": "# Backport - - This will backport the following commits from \`master\` to \`7.x\`: - - Add ❤️ emoji (5bf29b7d) - - - - ### Questions ? - Please refer to the [Backport tool documentation](https://github.com/sqren/backport)", - "head": "backport-org:backport/7.x/commit-5bf29b7d", - "title": "[7.x] Add ❤️ emoji", - }, - ] - `); - }); - - it('returns pull request', () => { - expect(res).toEqual([ - { - didUpdate: false, - pullRequestNumber: 1337, - pullRequestUrl: 'myHtmlUrl', - status: 'success', - targetBranch: '7.x', - }, - ]); - }); - - it('should create new branches in origin (backport-org/integration-test)', async () => { - const branches = await getBranches({ - accessToken, - repoOwner: REPO_OWNER, - repoName: REPO_NAME, - }); - expect(branches.map((b) => b.name)).toEqual([ - '7.x', - BRANCH_WITH_ONE_COMMIT, - 'master', - ]); - }); - - it('should NOT create branch in the fork (sqren/integration-test)', async () => { - const branches = await getBranches({ - accessToken, - repoOwner: AUTHOR, - repoName: REPO_NAME, - }); - expect(branches.map((b) => b.name)).toEqual(['7.x', 'master']); - }); - }); -}); - -function mockCreatePullRequest(response: { number: number; html_url: string }) { - const scope = nock('https://api.foo.com', { allowUnmocked: true }) - .post('/repos/backport-org/integration-test/pulls') - .reply(200, response); - - return listenForCallsToNockScope(scope); -} - -async function getBranches({ - accessToken, - repoOwner, - repoName, -}: { - accessToken: string; - repoOwner: string; - repoName: string; -}) { - // console.log(`fetch branches for ${repoOwner}`); - const octokit = new Octokit({ - auth: accessToken, - }); - - const res = await octokit.repos.listBranches({ - owner: repoOwner, - repo: repoName, - }); - - return res.data; -} - -async function deleteBranch({ - accessToken, - repoOwner, - repoName, - branchName, -}: { - accessToken: string; - repoOwner: string; - repoName: string; - branchName: string; -}) { - try { - const octokit = new Octokit({ - auth: accessToken, - }); - // console.log({ accessToken }); - - const opts = { - owner: repoOwner, - repo: repoName, - ref: `heads/${branchName}`, - }; - - const res = await octokit.git.deleteRef(opts); - - // console.log(`Deleted ${repoOwner}:heads/${branchName}`); - - return res.data; - } catch (e) { - // console.log( - // `Could not delete ${repoOwner}:heads/${branchName} (${e.message})` - // ); - if (e.message === 'Reference does not exist') { - return; - } - - throw e; - } -} - -async function resetState(accessToken: string) { - const ownerBranches = await getBranches({ - accessToken, - repoOwner: REPO_OWNER, - repoName: REPO_NAME, - }); - - // delete all branches except master and 7.x - await Promise.all( - ownerBranches - .filter((b) => b.name !== 'master' && b.name !== '7.x') - .map((b) => { - return deleteBranch({ - accessToken, - repoOwner: REPO_OWNER, - repoName: REPO_NAME, - branchName: b.name, - }); - }) - ); - - const forkBranches = await getBranches({ - accessToken, - repoOwner: AUTHOR, - repoName: REPO_NAME, - }); - - // delete all branches except master and 7.x - await Promise.all( - forkBranches - .filter((b) => b.name !== 'master' && b.name !== '7.x') - .map((b) => { - return deleteBranch({ - accessToken, - repoOwner: AUTHOR, - repoName: REPO_NAME, - branchName: b.name, - }); - }) - ); - - await resetSandbox(sandboxPath); -} diff --git a/src/entrypoint.cli.e2e.private.test.ts b/src/test/e2e/cli.e2e.private.test.ts similarity index 92% rename from src/entrypoint.cli.e2e.private.test.ts rename to src/test/e2e/cli.e2e.private.test.ts index 7410e437..f9c666ee 100644 --- a/src/entrypoint.cli.e2e.private.test.ts +++ b/src/test/e2e/cli.e2e.private.test.ts @@ -1,25 +1,23 @@ import { spawn } from 'child_process'; import path from 'path'; import stripAnsi from 'strip-ansi'; -import { exec } from './services/child-process-promisified'; -import { getDevAccessToken } from './test/private/getDevAccessToken'; -import { getSandboxPath, resetSandbox } from './test/sandbox'; -import * as packageVersion from './utils/packageVersion'; - -const TIMEOUT_IN_SECONDS = 10; - -jest.setTimeout(15000); +import { exec } from '../../services/child-process-promisified'; +import * as packageVersion from '../../utils/packageVersion'; +import { getDevAccessToken } from '../private/getDevAccessToken'; +import { getSandboxPath, resetSandbox } from '../sandbox'; +const TIMEOUT_IN_SECONDS = 15; +jest.setTimeout(TIMEOUT_IN_SECONDS * 1000); const devAccessToken = getDevAccessToken(); describe('inquirer cli', () => { it('--version', async () => { - const res = await runBackportAsync([`--version`]); + const res = await runBackportViaCli([`--version`]); expect(res).toContain(process.env.npm_package_version); }); it('-v', async () => { - const res = await runBackportAsync([`-v`]); + const res = await runBackportViaCli([`-v`]); expect(res).toContain(process.env.npm_package_version); }); @@ -31,7 +29,7 @@ describe('inquirer cli', () => { }); it('--help', async () => { - const res = await runBackportAsync([`--help`]); + const res = await runBackportViaCli([`--help`]); expect(res).toMatchInlineSnapshot(` "entrypoint.cli.ts [args] Options: @@ -52,6 +50,7 @@ describe('inquirer cli', () => { --dir Location where the temporary repository will be stored [string] --details Show details about each commit [boolean] + --dryRun Run backport locally without pushing to Github [boolean] --editor Editor to be opened during conflict resolution [string] --skipRemoteConfig Use local .backportrc.json config instead of loading from Github [boolean] @@ -95,7 +94,7 @@ describe('inquirer cli', () => { }); it('should return error when branch is missing', async () => { - const res = await runBackportAsync([ + const res = await runBackportViaCli([ '--skip-remote-config', '--repo-owner', 'backport-org', @@ -119,7 +118,7 @@ describe('inquirer cli', () => { { cwd: sandboxPath } ); - const res = await runBackportAsync(['--accessToken', devAccessToken], { + const res = await runBackportViaCli(['--accessToken', devAccessToken], { cwd: sandboxPath, waitForString: 'Select commit', }); @@ -140,7 +139,7 @@ describe('inquirer cli', () => { }); it('should return error when access token is invalid', async () => { - const res = await runBackportAsync([ + const res = await runBackportViaCli([ '--branch', 'foo', '--repo-owner', @@ -156,7 +155,7 @@ describe('inquirer cli', () => { }); it(`should return error when repo doesn't exist`, async () => { - const res = await runBackportAsync([ + const res = await runBackportViaCli([ '--branch', 'foo', '--repo-owner', @@ -174,8 +173,7 @@ describe('inquirer cli', () => { }); it(`should list commits from master`, async () => { - jest.setTimeout(TIMEOUT_IN_SECONDS * 1000 * 1.1); - const output = await runBackportAsync( + const output = await runBackportViaCli( [ '--branch', 'foo', @@ -205,8 +203,7 @@ describe('inquirer cli', () => { }); it(`should filter commits by "since" and "until"`, async () => { - jest.setTimeout(TIMEOUT_IN_SECONDS * 1000 * 1.1); - const output = await runBackportAsync( + const output = await runBackportViaCli( [ '--branch', 'foo', @@ -234,7 +231,7 @@ describe('inquirer cli', () => { }); it(`should list commits from 7.x`, async () => { - const output = await runBackportAsync( + const output = await runBackportViaCli( [ '--branch', 'foo', @@ -267,7 +264,7 @@ describe('inquirer cli', () => { describe('repo: repo-with-backportrc-removed (missing .backportrc.json config file)', () => { it('should list commits', async () => { - const output = await runBackportAsync( + const output = await runBackportViaCli( [ '--branch', 'foo', @@ -292,7 +289,7 @@ describe('inquirer cli', () => { }); it('should attempt to backport by PR', async () => { - const output = await runBackportAsync( + const output = await runBackportViaCli( [ '--branch', 'foo', @@ -314,7 +311,7 @@ describe('inquirer cli', () => { }); it('should attempt to backport by commit sha', async () => { - const output = await runBackportAsync( + const output = await runBackportViaCli( [ '--branch', 'foo', @@ -338,8 +335,7 @@ describe('inquirer cli', () => { describe('repo: different-merge-strategies', () => { it('list all commits regardless how they were merged', async () => { - jest.setTimeout(TIMEOUT_IN_SECONDS * 1000 * 1.1); - const output = await runBackportAsync( + const output = await runBackportViaCli( [ '--branch', 'foo', @@ -378,7 +374,7 @@ describe('inquirer cli', () => { }); }); -function runBackportAsync( +function runBackportViaCli( cliArgs: string[], { waitForString, @@ -431,7 +427,7 @@ function runBackportAsync( // }); proc.on('error', (err) => { - reject(`runBackportAsync failed with: ${err}`); + reject(`runBackportViaCli failed with: ${err}`); }); }); diff --git a/src/test/e2e/module.e2e.private.test.ts b/src/test/e2e/module.e2e.private.test.ts new file mode 100644 index 00000000..c204ea6a --- /dev/null +++ b/src/test/e2e/module.e2e.private.test.ts @@ -0,0 +1,454 @@ +import { Octokit } from '@octokit/rest'; +import { BackportResponse, backportRun } from '../../entrypoint.module'; +import { getShortSha } from '../../services/github/commitFormatters'; +import { getDevAccessToken } from '../private/getDevAccessToken'; +import { getSandboxPath, resetSandbox } from '../sandbox'; + +jest.unmock('find-up'); + +jest.setTimeout(15000); + +const accessToken = getDevAccessToken(); +const octokit = new Octokit({ auth: accessToken }); +const sandboxPath = getSandboxPath({ filename: __filename }); + +// repo +const REPO_OWNER = 'backport-org'; +const REPO_NAME = 'integration-test'; +const AUTHOR = 'sqren'; + +// commit 1 +const COMMIT_SHA_1 = '5bf29b7d847ea3dbde9280448f0f62ad0f22d3ad'; +const BRANCH_WITH_ONE_COMMIT = `backport/7.x/commit-${getShortSha( + COMMIT_SHA_1 +)}`; + +// commit 2 +const COMMIT_SHA_2 = '59d6ff1ca90a4ce210c0a4f0e159214875c19d60'; +const BRANCH_WITH_TWO_COMMITS = `backport/7.x/commit-${getShortSha( + COMMIT_SHA_1 +)}_commit-${getShortSha(COMMIT_SHA_2)}`; + +describe('backport e2e', () => { + describe('when a single commit is backported', () => { + let res: BackportResponse; + let pullRequestResponse: Awaited>; + + beforeAll(async () => { + await resetState(accessToken); + res = await backportRun({ + dir: sandboxPath, + accessToken, + repoOwner: 'backport-org', + repoName: 'integration-test', + sha: COMMIT_SHA_1, + targetBranches: ['7.x'], + }); + + // @ts-expect-error + const pullRequestNumber = res.results[0].pullRequestNumber as number; + + pullRequestResponse = await octokit.pulls.get({ + owner: REPO_OWNER, + repo: REPO_NAME, + pull_number: pullRequestNumber, + }); + }); + + it('returns the backport result', () => { + expect(res).toEqual({ + commits: [ + { + expectedTargetPullRequests: [], + sourceBranch: 'master', + sourceCommit: { + committedDate: '2020-08-15T10:37:41Z', + message: 'Add ❤️ emoji', + sha: COMMIT_SHA_1, + }, + sourcePullRequest: undefined, + }, + ], + results: [ + { + didUpdate: false, + pullRequestNumber: expect.any(Number), + pullRequestUrl: expect.stringContaining( + 'https://github.com/backport-org/integration-test/pull/' + ), + status: 'success', + targetBranch: '7.x', + }, + ], + status: 'success', + }); + }); + + it('pull request: status code', async () => { + expect(pullRequestResponse.status).toEqual(200); + }); + + it('pull request: title', async () => { + expect(pullRequestResponse.data.title).toEqual('[7.x] Add ❤️ emoji'); + }); + + it('pull request: body', async () => { + expect(pullRequestResponse.data.body).toMatchInlineSnapshot(` + "# Backport + + This will backport the following commits from \`master\` to \`7.x\`: + - Add ❤️ emoji (5bf29b7d) + + + + ### Questions ? + Please refer to the [Backport tool documentation](https://github.com/sqren/backport)" + `); + }); + + it('pull request: head branch is in fork repo', async () => { + expect(pullRequestResponse.data.head.label).toEqual( + `sqren:${BRANCH_WITH_ONE_COMMIT}` + ); + }); + + it('pull request: base branch', async () => { + expect(pullRequestResponse.data.base.label).toEqual('backport-org:7.x'); + }); + + it('does not create any new branches in origin (backport-org/integration-test)', async () => { + const branches = await getBranchesOnGithub({ + accessToken, + repoOwner: REPO_OWNER, + repoName: REPO_NAME, + }); + expect(branches.map((b) => b.name)).toEqual(['7.x', 'master']); + }); + + it('creates a branch in the fork (sqren/integration-test)', async () => { + const branches = await getBranchesOnGithub({ + accessToken, + repoOwner: AUTHOR, + repoName: REPO_NAME, + }); + + expect(branches.map((b) => b.name)).toEqual([ + '7.x', + BRANCH_WITH_ONE_COMMIT, + 'master', + ]); + }); + }); + + describe('when two commits are backported', () => { + let res: BackportResponse; + let pullRequestResponse: Awaited>; + + beforeAll(async () => { + await resetState(accessToken); + res = await backportRun({ + dir: sandboxPath, + accessToken, + repoOwner: 'backport-org', + repoName: 'integration-test', + sha: [COMMIT_SHA_1, COMMIT_SHA_2], + targetBranches: ['7.x'], + }); + + // @ts-expect-error + const pullRequestNumber = res.results[0].pullRequestNumber as number; + + pullRequestResponse = await octokit.pulls.get({ + owner: REPO_OWNER, + repo: REPO_NAME, + pull_number: pullRequestNumber, + }); + }); + + it('returns the backport result containing both commits', () => { + expect(res).toEqual({ + commits: [ + { + expectedTargetPullRequests: [], + sourceBranch: 'master', + sourceCommit: { + committedDate: '2020-08-15T10:37:41Z', + message: 'Add ❤️ emoji', + sha: COMMIT_SHA_1, + }, + sourcePullRequest: undefined, + }, + { + expectedTargetPullRequests: [], + sourceBranch: 'master', + sourceCommit: { + committedDate: '2020-08-15T10:44:04Z', + message: 'Add family emoji (#2)', + sha: COMMIT_SHA_2, + }, + sourcePullRequest: undefined, + }, + ], + results: [ + { + didUpdate: false, + pullRequestNumber: expect.any(Number), + pullRequestUrl: expect.stringContaining( + 'https://github.com/backport-org/integration-test/pull/' + ), + status: 'success', + targetBranch: '7.x', + }, + ], + status: 'success', + }); + }); + + it('pull request: status code', async () => { + expect(pullRequestResponse.status).toEqual(200); + }); + + it('pull request: title', async () => { + expect(pullRequestResponse.data.title).toEqual( + '[7.x] Add ❤️ emoji | Add family emoji (#2)' + ); + }); + + it('pull request: body', async () => { + expect(pullRequestResponse.data.body).toMatchInlineSnapshot(` + "# Backport + + This will backport the following commits from \`master\` to \`7.x\`: + - Add ❤️ emoji (5bf29b7d) + - Add family emoji (#2) (59d6ff1c) + + + + ### Questions ? + Please refer to the [Backport tool documentation](https://github.com/sqren/backport)" + `); + }); + + it('pull request: head branch contains both commits in name', async () => { + expect(pullRequestResponse.data.head.label).toEqual( + `sqren:${BRANCH_WITH_TWO_COMMITS}` + ); + }); + + it('pull request: base branch', async () => { + expect(pullRequestResponse.data.base.label).toEqual('backport-org:7.x'); + }); + }); + + describe('when disabling fork mode', () => { + let res: BackportResponse; + let pullRequestResponse: Awaited>; + + beforeAll(async () => { + await resetState(accessToken); + res = await backportRun({ + fork: false, + dir: sandboxPath, + accessToken, + repoOwner: 'backport-org', + repoName: 'integration-test', + sha: COMMIT_SHA_1, + targetBranches: ['7.x'], + }); + + // @ts-expect-error + const pullRequestNumber = res.results[0].pullRequestNumber as number; + + pullRequestResponse = await octokit.pulls.get({ + owner: REPO_OWNER, + repo: REPO_NAME, + pull_number: pullRequestNumber, + }); + }); + + it('pull request: title', async () => { + expect(pullRequestResponse.data.title).toEqual('[7.x] Add ❤️ emoji'); + }); + + it('pull request: body', async () => { + expect(pullRequestResponse.data.body).toMatchInlineSnapshot(` + "# Backport + + This will backport the following commits from \`master\` to \`7.x\`: + - Add ❤️ emoji (5bf29b7d) + + + + ### Questions ? + Please refer to the [Backport tool documentation](https://github.com/sqren/backport)" + `); + }); + + it('pull request: head branch is in origin (non-fork) repo', async () => { + expect(pullRequestResponse.data.head.label).toEqual( + `backport-org:${BRANCH_WITH_ONE_COMMIT}` + ); + }); + + it('pull request: base branch', async () => { + expect(pullRequestResponse.data.base.label).toEqual('backport-org:7.x'); + }); + + it('returns pull request', () => { + expect(res).toEqual({ + commits: [ + { + expectedTargetPullRequests: [], + sourceBranch: 'master', + sourceCommit: { + committedDate: '2020-08-15T10:37:41Z', + message: 'Add ❤️ emoji', + sha: COMMIT_SHA_1, + }, + sourcePullRequest: undefined, + }, + ], + results: [ + { + didUpdate: false, + pullRequestNumber: expect.any(Number), + pullRequestUrl: expect.stringContaining( + 'https://github.com/backport-org/integration-test/pull/' + ), + status: 'success', + targetBranch: '7.x', + }, + ], + status: 'success', + }); + }); + + it('creates a new branch in origin (backport-org/integration-test)', async () => { + const branches = await getBranchesOnGithub({ + accessToken, + repoOwner: REPO_OWNER, + repoName: REPO_NAME, + }); + expect(branches.map((b) => b.name)).toEqual([ + '7.x', + BRANCH_WITH_ONE_COMMIT, + 'master', + ]); + }); + + it('does not create branches in the fork (sqren/integration-test)', async () => { + const branches = await getBranchesOnGithub({ + accessToken, + repoOwner: AUTHOR, + repoName: REPO_NAME, + }); + expect(branches.map((b) => b.name)).toEqual(['7.x', 'master']); + }); + }); +}); + +async function getBranchesOnGithub({ + accessToken, + repoOwner, + repoName, +}: { + accessToken: string; + repoOwner: string; + repoName: string; +}) { + // console.log(`fetch branches for ${repoOwner}`); + const octokit = new Octokit({ + auth: accessToken, + }); + + const res = await octokit.repos.listBranches({ + owner: repoOwner, + repo: repoName, + }); + + return res.data; +} + +async function deleteBranchOnGithub({ + accessToken, + repoOwner, + repoName, + branchName, +}: { + accessToken: string; + repoOwner: string; + repoName: string; + branchName: string; +}) { + try { + const octokit = new Octokit({ + auth: accessToken, + }); + // console.log({ accessToken }); + + const opts = { + owner: repoOwner, + repo: repoName, + ref: `heads/${branchName}`, + }; + + const res = await octokit.git.deleteRef(opts); + + // console.log(`Deleted ${repoOwner}:heads/${branchName}`); + + return res.data; + } catch (e) { + // console.log( + // `Could not delete ${repoOwner}:heads/${branchName} (${e.message})` + // ); + if (e.message === 'Reference does not exist') { + return; + } + + throw e; + } +} + +async function resetState(accessToken: string) { + const ownerBranches = await getBranchesOnGithub({ + accessToken, + repoOwner: REPO_OWNER, + repoName: REPO_NAME, + }); + + // delete all branches except master and 7.x + await Promise.all( + ownerBranches + .filter((b) => b.name !== 'master' && b.name !== '7.x') + .map((b) => { + return deleteBranchOnGithub({ + accessToken, + repoOwner: REPO_OWNER, + repoName: REPO_NAME, + branchName: b.name, + }); + }) + ); + + const forkBranches = await getBranchesOnGithub({ + accessToken, + repoOwner: AUTHOR, + repoName: REPO_NAME, + }); + + // delete all branches except master and 7.x + await Promise.all( + forkBranches + .filter((b) => b.name !== 'master' && b.name !== '7.x') + .map((b) => { + return deleteBranchOnGithub({ + accessToken, + repoOwner: AUTHOR, + repoName: REPO_NAME, + branchName: b.name, + }); + }) + ); + + await resetSandbox(sandboxPath); +} diff --git a/src/test/handleUnbackportedPullRequests.private.test.ts b/src/test/handleUnbackportedPullRequests.private.test.ts index 888c0dae..d31d0152 100644 --- a/src/test/handleUnbackportedPullRequests.private.test.ts +++ b/src/test/handleUnbackportedPullRequests.private.test.ts @@ -1,5 +1,4 @@ import { getCommits, backportRun, Commit } from '../entrypoint.module'; -import { exec } from '../services/child-process-promisified'; import { getDevAccessToken } from './private/getDevAccessToken'; import { getSandboxPath, resetSandbox } from './sandbox'; @@ -41,7 +40,6 @@ describe('Handle unbackported pull requests', () => { const accessToken = getDevAccessToken(); const sandboxPath = getSandboxPath({ filename: __filename }); await resetSandbox(sandboxPath); - await exec('git init', { cwd: sandboxPath }); const result = await backportRun({ accessToken: accessToken, diff --git a/src/ui/cherrypickAndCreateTargetPullRequest.ts b/src/ui/cherrypickAndCreateTargetPullRequest.ts index b3726ce9..6e3244f5 100644 --- a/src/ui/cherrypickAndCreateTargetPullRequest.ts +++ b/src/ui/cherrypickAndCreateTargetPullRequest.ts @@ -43,15 +43,29 @@ export async function cherrypickAndCreateTargetPullRequest({ options: ValidConfigOptions; commits: Commit[]; targetBranch: string; -}): Promise<{ - url: string; - number: number; - didUpdate: boolean; -}> { +}): Promise<{ url: string; number: number; didUpdate: boolean }> { const backportBranch = getBackportBranchName(targetBranch, commits); const repoForkOwner = getRepoForkOwner(options); consoleLog(`\n${chalk.bold(`Backporting to ${targetBranch}:`)}`); + await createBackportBranch({ options, targetBranch, backportBranch }); + + await sequentially(commits, (commit) => + waitForCherrypick(options, commit, targetBranch) + ); + + if (options.resetAuthor) { + await setCommitAuthor(options, options.authenticatedUsername); + } + + if (options.dryRun) { + ora(options.ci).succeed('Dry run'); + return { url: 'https://localhost/dry-run', didUpdate: false, number: 1337 }; + } + + await pushBackportBranch({ options, backportBranch }); + await deleteBackportBranch({ options, backportBranch }); + const prPayload: PullRequestPayload = { owner: options.repoOwner, repo: options.repoName, @@ -61,13 +75,7 @@ export async function cherrypickAndCreateTargetPullRequest({ base: targetBranch, // eg. 7.x }; - const targetPullRequest = await backportViaFilesystem({ - options, - prPayload, - targetBranch, - backportBranch, - commits, - }); + const targetPullRequest = await createPullRequest({ options, prPayload }); // add assignees to target pull request const assignees = options.autoAssign @@ -116,6 +124,7 @@ export async function cherrypickAndCreateTargetPullRequest({ ); } }); + await Promise.all(promises); } @@ -124,36 +133,6 @@ export async function cherrypickAndCreateTargetPullRequest({ return targetPullRequest; } -async function backportViaFilesystem({ - options, - prPayload, - commits, - targetBranch, - backportBranch, -}: { - options: ValidConfigOptions; - prPayload: PullRequestPayload; - commits: Commit[]; - targetBranch: string; - backportBranch: string; -}) { - logger.info('Backporting via filesystem'); - - await createBackportBranch({ options, targetBranch, backportBranch }); - - await sequentially(commits, (commit) => - waitForCherrypick(options, commit, targetBranch) - ); - - if (options.resetAuthor) { - await setCommitAuthor(options, options.authenticatedUsername); - } - - await pushBackportBranch({ options, backportBranch }); - await deleteBackportBranch({ options, backportBranch }); - return createPullRequest({ options, prPayload }); -} - /* * Returns the name of the backport branch without remote name * diff --git a/src/ui/getCommits.ts b/src/ui/getCommits.ts index 818f2676..932eade2 100644 --- a/src/ui/getCommits.ts +++ b/src/ui/getCommits.ts @@ -21,12 +21,23 @@ export async function getCommits(options: ValidConfigOptions) { try { if (options.sha) { - spinner.text = `Loading commit "${getShortSha(options.sha)}"`; - const commit = await fetchCommitBySha({ ...options, sha: options.sha }); + const shas = Array.isArray(options.sha) ? options.sha : [options.sha]; + + // TODO: use Intl.ListFormat to format the sha's + spinner.text = `Loading commit "${shas.map(getShortSha)}"`; + + const commits = await Promise.all( + shas.map((sha) => fetchCommitBySha({ ...options, sha })) + ); + spinner.stopAndPersist( - getOraPersistsOption('Select commit', commit.sourceCommit.message) + getOraPersistsOption( + 'Select commit', + commits.map((commit) => commit.sourceCommit.message).join(', ') + ) ); - return [commit]; + + return commits; } if (options.pullNumber) { diff --git a/src/ui/maybeSetupRepo.test.ts b/src/ui/maybeSetupRepo.test.ts index b4dcd14b..3a835beb 100644 --- a/src/ui/maybeSetupRepo.test.ts +++ b/src/ui/maybeSetupRepo.test.ts @@ -45,7 +45,8 @@ describe('maybeSetupRepo', () => { ).rejects.toThrowError('Simulated git clone failure'); expect(del).toHaveBeenCalledWith( - '/myHomeDir/.backport/repositories/elastic/kibana' + '/myHomeDir/.backport/repositories/elastic/kibana', + { force: true } ); }); }); @@ -144,15 +145,40 @@ describe('maybeSetupRepo', () => { authenticatedUsername: 'sqren_authenticated', repoName: 'kibana', repoOwner: 'elastic', + cwd: '/current/folder', } as ValidConfigOptions); - expect(execSpy.mock.calls.map(([cmd]) => cmd)).toEqual([ - 'git remote --verbose', - 'git remote rm origin', - 'git remote rm sqren_authenticated', - 'git remote add sqren_authenticated https://x-access-token:myAccessToken@github.com/sqren_authenticated/kibana.git', - 'git remote rm elastic', - 'git remote add elastic https://x-access-token:myAccessToken@github.com/elastic/kibana.git', + expect( + execSpy.mock.calls.map(([cmd, { cwd }]) => ({ cmd, cwd })) + ).toEqual([ + { + cmd: 'git rev-parse --show-toplevel', + cwd: '/current/folder', + }, + { + cmd: 'git remote --verbose', + cwd: '/current/folder', + }, + { + cmd: 'git remote rm origin', + cwd: '/myHomeDir/.backport/repositories/elastic/kibana', + }, + { + cmd: 'git remote rm sqren_authenticated', + cwd: '/myHomeDir/.backport/repositories/elastic/kibana', + }, + { + cmd: 'git remote add sqren_authenticated https://x-access-token:myAccessToken@github.com/sqren_authenticated/kibana.git', + cwd: '/myHomeDir/.backport/repositories/elastic/kibana', + }, + { + cmd: 'git remote rm elastic', + cwd: '/myHomeDir/.backport/repositories/elastic/kibana', + }, + { + cmd: 'git remote add elastic https://x-access-token:myAccessToken@github.com/elastic/kibana.git', + cwd: '/myHomeDir/.backport/repositories/elastic/kibana', + }, ]); }); }); diff --git a/src/ui/maybeSetupRepo.ts b/src/ui/maybeSetupRepo.ts index ae378e9b..513b2661 100644 --- a/src/ui/maybeSetupRepo.ts +++ b/src/ui/maybeSetupRepo.ts @@ -1,4 +1,3 @@ -import { stat } from 'fs/promises'; import del = require('del'); import ora = require('ora'); import { ValidConfigOptions } from '../options/options'; @@ -7,12 +6,13 @@ import { addRemote, cloneRepo, deleteRemote, + getGitProjectRoot, getSourceRepoPath, } from '../services/git'; export async function maybeSetupRepo(options: ValidConfigOptions) { const repoPath = getRepoPath(options); - const isAlreadyCloned = await getIsRepoCloned(repoPath); + const isAlreadyCloned = await getIsRepoCloned(options); // clone repo if folder does not already exists if (!isAlreadyCloned) { @@ -27,6 +27,8 @@ export async function maybeSetupRepo(options: ValidConfigOptions) { const spinnerCloneText = `Cloning repository from ${sourcePathHumanReadable} (one-time operation)`; spinner.text = `0% ${spinnerCloneText}`; + await del(repoPath, { force: true }); + await cloneRepo( { sourcePath, targetPath: repoPath }, (progress: number) => { @@ -37,7 +39,7 @@ export async function maybeSetupRepo(options: ValidConfigOptions) { spinner.succeed(`100% ${spinnerCloneText}`); } catch (e) { spinner.fail(); - await del(repoPath); + await del(repoPath, { force: true }); throw e; } } @@ -56,15 +58,8 @@ export async function maybeSetupRepo(options: ValidConfigOptions) { } } -async function getIsRepoCloned(path: string): Promise { - try { - const stats = await stat(path); - return stats.isDirectory(); - } catch (e) { - if (e.code === 'ENOENT') { - return false; - } - - throw e; - } +async function getIsRepoCloned(options: ValidConfigOptions): Promise { + const repoPath = getRepoPath(options); + const projectRoot = await getGitProjectRoot(options); + return repoPath === projectRoot; } From ab6ed5a969fd7bdb8f52a4e4fb437e2056b4e17d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=B8ren=20Louv-Jansen?= Date: Sat, 19 Feb 2022 12:11:34 +0100 Subject: [PATCH 4/7] Improve test --- .../getExpectedTargetPullRequests.test.ts | 70 +++++++++---------- 1 file changed, 32 insertions(+), 38 deletions(-) diff --git a/src/services/sourceCommit/getExpectedTargetPullRequests.test.ts b/src/services/sourceCommit/getExpectedTargetPullRequests.test.ts index d63a1a22..48e4534d 100644 --- a/src/services/sourceCommit/getExpectedTargetPullRequests.test.ts +++ b/src/services/sourceCommit/getExpectedTargetPullRequests.test.ts @@ -8,37 +8,39 @@ describe('getExpectedTargetPullRequests', () => { }); it('should return empty when there is no associated PR', () => { + // no associated pull request + const sourcePullRequest = null; + const mockSourceCommit = getMockSourceCommit({ sourceCommit: { message: 'identical messages (#1234)' }, - sourcePullRequest: null, + sourcePullRequest, }); - const branchLabelMapping = {}; const expectedTargetPRs = getExpectedTargetPullRequests({ sourceCommit: mockSourceCommit, - latestBranchLabelMapping: branchLabelMapping, + latestBranchLabelMapping: {}, }); expect(expectedTargetPRs).toEqual([]); }); it('should return a result when sourceCommit message matches the commit of the target pull request', () => { const mockSourceCommit = getMockSourceCommit({ - sourceCommit: { message: 'identical messages (#1234)' }, + sourceCommit: { message: 'identical messages (#1234)' }, // this message sourcePullRequest: { number: 1234 }, timelineItems: [ { state: 'MERGED', targetBranch: '6.x', - commitMessages: ['identical messages (#1234)'], + commitMessages: ['identical messages (#1234)'], // this message number: 5678, }, ], }); - const branchLabelMapping = {}; const expectedTargetPRs = getExpectedTargetPullRequests({ sourceCommit: mockSourceCommit, - latestBranchLabelMapping: branchLabelMapping, + latestBranchLabelMapping: {}, }); + expect(expectedTargetPRs).toEqual([ { branch: '6.x', @@ -63,14 +65,14 @@ describe('getExpectedTargetPullRequests', () => { targetBranch: '6.x', commitMessages: ['identical messages (#1234)'], number: 5678, - repoName: 'foo', // this repo name + repoName: 'foo', // this repoName does not match 'kibana' }, ], }); - const branchLabelMapping = {}; + const expectedTargetPullRequests = getExpectedTargetPullRequests({ sourceCommit: mockSourceCommit, - latestBranchLabelMapping: branchLabelMapping, + latestBranchLabelMapping: {}, }); expect(expectedTargetPullRequests).toEqual([]); }); @@ -85,61 +87,57 @@ describe('getExpectedTargetPullRequests', () => { targetBranch: '6.x', commitMessages: ['identical messages (#1234)'], number: 5678, - repoOwner: 'foo', // this + repoOwner: 'foo', // this repoOwner does not match `elastic` }, ], }); - const branchLabelMapping = {}; + const expectedTargetPullRequests = getExpectedTargetPullRequests({ sourceCommit: mockSourceCommit, - latestBranchLabelMapping: branchLabelMapping, + latestBranchLabelMapping: {}, }); expect(expectedTargetPullRequests).toEqual([]); }); it('should return empty when commit messages do not match', () => { const mockSourceCommit = getMockSourceCommit({ - sourceCommit: { message: 'message one (#1234)' }, - sourcePullRequest: { - number: 1234, - }, + sourceCommit: { message: 'message one (#1234)' }, // this commit message + sourcePullRequest: { number: 1234 }, timelineItems: [ { state: 'MERGED', targetBranch: '6.x', - commitMessages: ['message two (#1234)'], + commitMessages: ['message two (#1234)'], // this commit message number: 5678, }, ], }); - const branchLabelMapping = {}; + const expectedTargetPullRequests = getExpectedTargetPullRequests({ sourceCommit: mockSourceCommit, - latestBranchLabelMapping: branchLabelMapping, + latestBranchLabelMapping: {}, }); expect(expectedTargetPullRequests).toEqual([]); }); it('should return a result if commits messages are different but title includes message and number', () => { const mockSourceCommit = getMockSourceCommit({ - sourceCommit: { message: 'message one (#1234)' }, - sourcePullRequest: { - number: 1234, - }, + sourceCommit: { message: 'message one (#1234)' }, // message + sourcePullRequest: { number: 1234 }, timelineItems: [ { state: 'MERGED', targetBranch: '6.x', - commitMessages: ['message two (#1234)'], - title: 'message one (#1234)', + commitMessages: ['message two (#1234)'], // message + title: 'message one (#1234)', // title number: 5678, }, ], }); - const branchLabelMapping = {}; + const expectedTargetPullRequests = getExpectedTargetPullRequests({ sourceCommit: mockSourceCommit, - latestBranchLabelMapping: branchLabelMapping, + latestBranchLabelMapping: {}, }); expect(expectedTargetPullRequests).toEqual([ { @@ -158,9 +156,7 @@ describe('getExpectedTargetPullRequests', () => { it('should return empty when only pull request title (but not pull number) matches', () => { const mockSourceCommit = getMockSourceCommit({ sourceCommit: { message: 'message one (#1234)' }, - sourcePullRequest: { - number: 1234, - }, + sourcePullRequest: { number: 1234 }, timelineItems: [ { state: 'MERGED', @@ -171,10 +167,10 @@ describe('getExpectedTargetPullRequests', () => { }, ], }); - const branchLabelMapping = {}; + const expectedTargetPullRequests = getExpectedTargetPullRequests({ sourceCommit: mockSourceCommit, - latestBranchLabelMapping: branchLabelMapping, + latestBranchLabelMapping: {}, }); expect(expectedTargetPullRequests).toEqual([]); }); @@ -182,9 +178,7 @@ describe('getExpectedTargetPullRequests', () => { it('should return a result when first line of a multiline commit message matches', () => { const mockSourceCommit = getMockSourceCommit({ sourceCommit: { message: 'message one (#1234)' }, - sourcePullRequest: { - number: 1234, - }, + sourcePullRequest: { number: 1234 }, timelineItems: [ { state: 'MERGED', @@ -194,10 +188,10 @@ describe('getExpectedTargetPullRequests', () => { }, ], }); - const branchLabelMapping = {}; + const expectedTargetPullRequests = getExpectedTargetPullRequests({ sourceCommit: mockSourceCommit, - latestBranchLabelMapping: branchLabelMapping, + latestBranchLabelMapping: {}, }); expect(expectedTargetPullRequests).toEqual([ { From 12fa8e7982bba34570cee401d5aa6624a172ab95 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=B8ren=20Louv-Jansen?= Date: Sat, 19 Feb 2022 12:15:13 +0100 Subject: [PATCH 5/7] Bump deps --- .travis.yml | 1 - package.json | 16 ++-- yarn.lock | 235 ++++++++++++++++++++++++++------------------------- 3 files changed, 129 insertions(+), 123 deletions(-) diff --git a/.travis.yml b/.travis.yml index f0af08cd..9e476b56 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,6 +1,5 @@ os: - linux - - osx language: node_js node_js: - '16' diff --git a/package.json b/package.json index 8c2c8213..928250ad 100644 --- a/package.json +++ b/package.json @@ -78,7 +78,7 @@ "strip-json-comments": "^3.1.1", "terminal-link": "^2.1.1", "utility-types": "^3.10.0", - "winston": "^3.5.1", + "winston": "^3.6.0", "yargs": "^17.3.1", "yargs-parser": "^21.0.0" }, @@ -88,22 +88,22 @@ "@types/inquirer": "^8.2.0", "@types/jest": "^27.4.0", "@types/lodash": "^4.14.178", - "@types/node": "^17.0.8", + "@types/node": "^17.0.18", "@types/safe-json-stringify": "^1.1.2", "@types/yargs": "^17.0.8", "@types/yargs-parser": "^20.2.1", - "@typescript-eslint/eslint-plugin": "^5.11.0", - "@typescript-eslint/parser": "^5.11.0", - "eslint": "^8.8.0", - "eslint-config-prettier": "^8.3.0", + "@typescript-eslint/eslint-plugin": "^5.12.0", + "@typescript-eslint/parser": "^5.12.0", + "eslint": "^8.9.0", + "eslint-config-prettier": "^8.4.0", "eslint-plugin-import": "^2.25.4", - "eslint-plugin-jest": "^26.1.0", + "eslint-plugin-jest": "^26.1.1", "eslint-plugin-prettier": "^4.0.0", "graphql-config": "^4.1.0", "husky": "^7.0.4", "jest": "^27.5.1", "jest-snapshot-serializer-ansi": "^1.0.0", - "lint-staged": "^12.3.3", + "lint-staged": "^12.3.4", "nock": "^13.2.4", "prettier": "^2.5.1", "strip-ansi": "^6.0.1", diff --git a/yarn.lock b/yarn.lock index 86357508..084f9325 100644 --- a/yarn.lock +++ b/yarn.lock @@ -480,6 +480,11 @@ resolved "https://registry.yarnpkg.com/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz#75a2e8b51cb758a7553d6804a5932d7aace75c39" integrity sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw== +"@colors/colors@1.5.0": + version "1.5.0" + resolved "https://registry.yarnpkg.com/@colors/colors/-/colors-1.5.0.tgz#bb504579c1cae923e6576a4f5da43d25f97bdbd9" + integrity sha512-ooWCrlZP11i8GImSjTHYHLkvFDP48nS4+204nGb1RiX/WXYHmJA2III9/e2DWVabCESdW7hBAEzHRqUn9OUVvQ== + "@cspotcode/source-map-consumer@0.8.0": version "0.8.0" resolved "https://registry.yarnpkg.com/@cspotcode/source-map-consumer/-/source-map-consumer-0.8.0.tgz#33bf4b7b39c178821606f669bbc447a6a629786b" @@ -511,14 +516,14 @@ ts-node "^9" tslib "^2" -"@eslint/eslintrc@^1.0.5": - version "1.0.5" - resolved "https://registry.yarnpkg.com/@eslint/eslintrc/-/eslintrc-1.0.5.tgz#33f1b838dbf1f923bfa517e008362b78ddbbf318" - integrity sha512-BLxsnmK3KyPunz5wmCCpqy0YelEoxxGmH73Is+Z74oOTMtExcjkr3dDR6quwrjh1YspA8DH9gnX1o069KiS9AQ== +"@eslint/eslintrc@^1.1.0": + version "1.1.0" + resolved "https://registry.yarnpkg.com/@eslint/eslintrc/-/eslintrc-1.1.0.tgz#583d12dbec5d4f22f333f9669f7d0b7c7815b4d3" + integrity sha512-C1DfL7XX4nPqGd6jcP01W9pVM1HYCuUkFk1432D7F0v3JSlUIeOYn9oCoi3eoLZ+iwBSb29BMFxxny0YrrEZqg== dependencies: ajv "^6.12.4" debug "^4.3.2" - espree "^9.2.0" + espree "^9.3.1" globals "^13.9.0" ignore "^4.0.6" import-fresh "^3.2.1" @@ -1135,10 +1140,10 @@ resolved "https://registry.yarnpkg.com/@types/node/-/node-16.11.12.tgz#ac7fb693ac587ee182c3780c26eb65546a1a3c10" integrity sha512-+2Iggwg7PxoO5Kyhvsq9VarmPbIelXP070HMImEpbtGCoyWNINQj4wzjbQCXzdHTRXnqufutJb5KAURZANNBAw== -"@types/node@^17.0.8": - version "17.0.8" - resolved "https://registry.yarnpkg.com/@types/node/-/node-17.0.8.tgz#50d680c8a8a78fe30abe6906453b21ad8ab0ad7b" - integrity sha512-YofkM6fGv4gDJq78g4j0mMuGMkZVxZDgtU0JRdx6FgiJDG+0fY0GKVolOV8WqVmEhLCXkQRjwDdKyPxJp/uucg== +"@types/node@^17.0.18": + version "17.0.18" + resolved "https://registry.yarnpkg.com/@types/node/-/node-17.0.18.tgz#3b4fed5cfb58010e3a2be4b6e74615e4847f1074" + integrity sha512-eKj4f/BsN/qcculZiRSujogjvp5O/k4lOW5m35NopjZM/QwLOR075a8pJW5hD+Rtdm2DaCVPENS6KtSQnUD6BA== "@types/parse-json@^4.0.0": version "4.0.0" @@ -1200,14 +1205,14 @@ dependencies: "@types/yargs-parser" "*" -"@typescript-eslint/eslint-plugin@^5.11.0": - version "5.11.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.11.0.tgz#3b866371d8d75c70f9b81535e7f7d3aa26527c7a" - integrity sha512-HJh33bgzXe6jGRocOj4FmefD7hRY4itgjzOrSs3JPrTNXsX7j5+nQPciAUj/1nZtwo2kAc3C75jZO+T23gzSGw== +"@typescript-eslint/eslint-plugin@^5.12.0": + version "5.12.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.12.0.tgz#bb46dd7ce7015c0928b98af1e602118e97df6c70" + integrity sha512-fwCMkDimwHVeIOKeBHiZhRUfJXU8n6xW1FL9diDxAyGAFvKcH4csy0v7twivOQdQdA0KC8TDr7GGRd3L4Lv0rQ== dependencies: - "@typescript-eslint/scope-manager" "5.11.0" - "@typescript-eslint/type-utils" "5.11.0" - "@typescript-eslint/utils" "5.11.0" + "@typescript-eslint/scope-manager" "5.12.0" + "@typescript-eslint/type-utils" "5.12.0" + "@typescript-eslint/utils" "5.12.0" debug "^4.3.2" functional-red-black-tree "^1.0.1" ignore "^5.1.8" @@ -1215,14 +1220,14 @@ semver "^7.3.5" tsutils "^3.21.0" -"@typescript-eslint/parser@^5.11.0": - version "5.11.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/parser/-/parser-5.11.0.tgz#b4fcaf65513f9b34bdcbffdda055724a5efb7e04" - integrity sha512-x0DCjetHZYBRovJdr3U0zG9OOdNXUaFLJ82ehr1AlkArljJuwEsgnud+Q7umlGDFLFrs8tU8ybQDFocp/eX8mQ== +"@typescript-eslint/parser@^5.12.0": + version "5.12.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/parser/-/parser-5.12.0.tgz#0ca669861813df99ce54916f66f524c625ed2434" + integrity sha512-MfSwg9JMBojMUoGjUmX+D2stoQj1CBYTCP0qnnVtu9A+YQXVKNtLjasYh+jozOcrb/wau8TCfWOkQTiOAruBog== dependencies: - "@typescript-eslint/scope-manager" "5.11.0" - "@typescript-eslint/types" "5.11.0" - "@typescript-eslint/typescript-estree" "5.11.0" + "@typescript-eslint/scope-manager" "5.12.0" + "@typescript-eslint/types" "5.12.0" + "@typescript-eslint/typescript-estree" "5.12.0" debug "^4.3.2" "@typescript-eslint/scope-manager@5.10.2": @@ -1233,20 +1238,20 @@ "@typescript-eslint/types" "5.10.2" "@typescript-eslint/visitor-keys" "5.10.2" -"@typescript-eslint/scope-manager@5.11.0": - version "5.11.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/scope-manager/-/scope-manager-5.11.0.tgz#f5aef83ff253f457ecbee5f46f762298f0101e4b" - integrity sha512-z+K4LlahDFVMww20t/0zcA7gq/NgOawaLuxgqGRVKS0PiZlCTIUtX0EJbC0BK1JtR4CelmkPK67zuCgpdlF4EA== +"@typescript-eslint/scope-manager@5.12.0": + version "5.12.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/scope-manager/-/scope-manager-5.12.0.tgz#59619e6e5e2b1ce6cb3948b56014d3a24da83f5e" + integrity sha512-GAMobtIJI8FGf1sLlUWNUm2IOkIjvn7laFWyRx7CLrv6nLBI7su+B7lbStqVlK5NdLvHRFiJo2HhiDF7Ki01WQ== dependencies: - "@typescript-eslint/types" "5.11.0" - "@typescript-eslint/visitor-keys" "5.11.0" + "@typescript-eslint/types" "5.12.0" + "@typescript-eslint/visitor-keys" "5.12.0" -"@typescript-eslint/type-utils@5.11.0": - version "5.11.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/type-utils/-/type-utils-5.11.0.tgz#58be0ba73d1f6ef8983d79f7f0bc2209b253fefe" - integrity sha512-wDqdsYO6ofLaD4DsGZ0jGwxp4HrzD2YKulpEZXmgN3xo4BHJwf7kq49JTRpV0Gx6bxkSUmc9s0EIK1xPbFFpIA== +"@typescript-eslint/type-utils@5.12.0": + version "5.12.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/type-utils/-/type-utils-5.12.0.tgz#aaf45765de71c6d9707c66ccff76ec2b9aa31bb6" + integrity sha512-9j9rli3zEBV+ae7rlbBOotJcI6zfc6SHFMdKI9M3Nc0sy458LJ79Os+TPWeBBL96J9/e36rdJOfCuyRSgFAA0Q== dependencies: - "@typescript-eslint/utils" "5.11.0" + "@typescript-eslint/utils" "5.12.0" debug "^4.3.2" tsutils "^3.21.0" @@ -1255,10 +1260,10 @@ resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-5.10.2.tgz#604d15d795c4601fffba6ecb4587ff9fdec68ce8" integrity sha512-Qfp0qk/5j2Rz3p3/WhWgu4S1JtMcPgFLnmAKAW061uXxKSa7VWKZsDXVaMXh2N60CX9h6YLaBoy9PJAfCOjk3w== -"@typescript-eslint/types@5.11.0": - version "5.11.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-5.11.0.tgz#ba345818a2540fdf2755c804dc2158517ab61188" - integrity sha512-cxgBFGSRCoBEhvSVLkKw39+kMzUKHlJGVwwMbPcTZX3qEhuXhrjwaZXWMxVfxDgyMm+b5Q5b29Llo2yow8Y7xQ== +"@typescript-eslint/types@5.12.0": + version "5.12.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-5.12.0.tgz#5b4030a28222ee01e851836562c07769eecda0b8" + integrity sha512-JowqbwPf93nvf8fZn5XrPGFBdIK8+yx5UEGs2QFAYFI8IWYfrzz+6zqlurGr2ctShMaJxqwsqmra3WXWjH1nRQ== "@typescript-eslint/typescript-estree@5.10.2": version "5.10.2" @@ -1273,28 +1278,28 @@ semver "^7.3.5" tsutils "^3.21.0" -"@typescript-eslint/typescript-estree@5.11.0": - version "5.11.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-5.11.0.tgz#53f9e09b88368191e52020af77c312a4777ffa43" - integrity sha512-yVH9hKIv3ZN3lw8m/Jy5I4oXO4ZBMqijcXCdA4mY8ull6TPTAoQnKKrcZ0HDXg7Bsl0Unwwx7jcXMuNZc0m4lg== +"@typescript-eslint/typescript-estree@5.12.0": + version "5.12.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-5.12.0.tgz#cabf545fd592722f0e2b4104711e63bf89525cd2" + integrity sha512-Dd9gVeOqt38QHR0BEA8oRaT65WYqPYbIc5tRFQPkfLquVEFPD1HAtbZT98TLBkEcCkvwDYOAvuSvAD9DnQhMfQ== dependencies: - "@typescript-eslint/types" "5.11.0" - "@typescript-eslint/visitor-keys" "5.11.0" + "@typescript-eslint/types" "5.12.0" + "@typescript-eslint/visitor-keys" "5.12.0" debug "^4.3.2" globby "^11.0.4" is-glob "^4.0.3" semver "^7.3.5" tsutils "^3.21.0" -"@typescript-eslint/utils@5.11.0": - version "5.11.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/utils/-/utils-5.11.0.tgz#d91548ef180d74c95d417950336d9260fdbe1dc5" - integrity sha512-g2I480tFE1iYRDyMhxPAtLQ9HAn0jjBtipgTCZmd9I9s11OV8CTsG+YfFciuNDcHqm4csbAgC2aVZCHzLxMSUw== +"@typescript-eslint/utils@5.12.0": + version "5.12.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/utils/-/utils-5.12.0.tgz#92fd3193191621ab863add2f553a7b38b65646af" + integrity sha512-k4J2WovnMPGI4PzKgDtQdNrCnmBHpMUFy21qjX2CoPdoBcSBIMvVBr9P2YDP8jOqZOeK3ThOL6VO/sy6jtnvzw== dependencies: "@types/json-schema" "^7.0.9" - "@typescript-eslint/scope-manager" "5.11.0" - "@typescript-eslint/types" "5.11.0" - "@typescript-eslint/typescript-estree" "5.11.0" + "@typescript-eslint/scope-manager" "5.12.0" + "@typescript-eslint/types" "5.12.0" + "@typescript-eslint/typescript-estree" "5.12.0" eslint-scope "^5.1.1" eslint-utils "^3.0.0" @@ -1318,12 +1323,12 @@ "@typescript-eslint/types" "5.10.2" eslint-visitor-keys "^3.0.0" -"@typescript-eslint/visitor-keys@5.11.0": - version "5.11.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/visitor-keys/-/visitor-keys-5.11.0.tgz#888542381f1a2ac745b06d110c83c0b261487ebb" - integrity sha512-E8w/vJReMGuloGxJDkpPlGwhxocxOpSVgSvjiLO5IxZPmxZF30weOeJYyPSEACwM+X4NziYS9q+WkN/2DHYQwA== +"@typescript-eslint/visitor-keys@5.12.0": + version "5.12.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/visitor-keys/-/visitor-keys-5.12.0.tgz#1ac9352ed140b07ba144ebf371b743fdf537ec16" + integrity sha512-cFwTlgnMV6TgezQynx2c/4/tx9Tufbuo9LPzmWqyRC3QC4qTGkAG1C6pBr0/4I10PAI/FlYunI3vJjIcu+ZHMg== dependencies: - "@typescript-eslint/types" "5.11.0" + "@typescript-eslint/types" "5.12.0" eslint-visitor-keys "^3.0.0" abab@^2.0.3, abab@^2.0.5: @@ -1366,7 +1371,7 @@ acorn@^7.1.1: resolved "https://registry.yarnpkg.com/acorn/-/acorn-7.4.1.tgz#feaed255973d2e77555b83dbc08851a6c63520fa" integrity sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A== -acorn@^8.2.4, acorn@^8.4.1, acorn@^8.6.0: +acorn@^8.2.4, acorn@^8.4.1: version "8.6.0" resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.6.0.tgz#e3692ba0eb1a0c83eaa4f37f5fa7368dd7142895" integrity sha512-U1riIR+lBSNi3IbxtaHOIKdH8sLFv3NYfNv8sg7ZsNhcfl4HF2++BfqqrNAxoCLQW1iiylOj76ecnaUxz+z9yw== @@ -2186,10 +2191,10 @@ escodegen@^2.0.0: optionalDependencies: source-map "~0.6.1" -eslint-config-prettier@^8.3.0: - version "8.3.0" - resolved "https://registry.yarnpkg.com/eslint-config-prettier/-/eslint-config-prettier-8.3.0.tgz#f7471b20b6fe8a9a9254cc684454202886a2dd7a" - integrity sha512-BgZuLUSeKzvlL/VUjx/Yb787VQ26RU3gGjA3iiFvdsp/2bMfVIWUVP7tjxtjS0e+HP409cPlPvNkQloz8C91ew== +eslint-config-prettier@^8.4.0: + version "8.4.0" + resolved "https://registry.yarnpkg.com/eslint-config-prettier/-/eslint-config-prettier-8.4.0.tgz#8e6d17c7436649e98c4c2189868562921ef563de" + integrity sha512-CFotdUcMY18nGRo5KGsnNxpznzhkopOcOo0InID+sgQssPrzjvsyKZPvOgymTFeHrFuC3Tzdf2YndhXtULK9Iw== eslint-import-resolver-node@^0.3.6: version "0.3.6" @@ -2226,10 +2231,10 @@ eslint-plugin-import@^2.25.4: resolve "^1.20.0" tsconfig-paths "^3.12.0" -eslint-plugin-jest@^26.1.0: - version "26.1.0" - resolved "https://registry.yarnpkg.com/eslint-plugin-jest/-/eslint-plugin-jest-26.1.0.tgz#9f6c33e66f3cef3f2832c3a4d2caa21a75792dee" - integrity sha512-vjF6RvcKm4xZSJgCmXb9fXmhzTva+I9jtj9Qv5JeZQTRocU7WT1g3Kx0cZ+00SekPe2DtSWDawHtSj4RaxFhXQ== +eslint-plugin-jest@^26.1.1: + version "26.1.1" + resolved "https://registry.yarnpkg.com/eslint-plugin-jest/-/eslint-plugin-jest-26.1.1.tgz#7176dd745ef8bca3070263f62cdf112f2dfc9aa1" + integrity sha512-HRKOuPi5ADhza4ZBK5ufyNXy28bXXkib87w+pQqdvBhSTsamndh6sIAKPAUl8y0/n9jSWBdTPslrwtKWqkp8dA== dependencies: "@typescript-eslint/utils" "^5.10.0" @@ -2248,10 +2253,10 @@ eslint-scope@^5.1.1: esrecurse "^4.3.0" estraverse "^4.1.1" -eslint-scope@^7.1.0: - version "7.1.0" - resolved "https://registry.yarnpkg.com/eslint-scope/-/eslint-scope-7.1.0.tgz#c1f6ea30ac583031f203d65c73e723b01298f153" - integrity sha512-aWwkhnS0qAXqNOgKOK0dJ2nvzEbhEvpy8OlJ9kZ0FeZnA6zpjv1/Vei+puGFFX7zkPCkHHXb7IDX3A+7yPrRWg== +eslint-scope@^7.1.1: + version "7.1.1" + resolved "https://registry.yarnpkg.com/eslint-scope/-/eslint-scope-7.1.1.tgz#fff34894c2f65e5226d3041ac480b4513a163642" + integrity sha512-QKQM/UXpIiHcLqJ5AOyIW7XZmzjkzQXYE54n1++wb0u9V/abW3l9uQnxX8Z5Xd18xyKIMTUAyQ0k1e8pz6LUrw== dependencies: esrecurse "^4.3.0" estraverse "^5.2.0" @@ -2268,22 +2273,22 @@ eslint-visitor-keys@^2.0.0: resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-2.1.0.tgz#f65328259305927392c938ed44eb0a5c9b2bd303" integrity sha512-0rSmRBzXgDzIsD6mGdJgevzgezI534Cer5L/vyMX0kHzT/jiB43jRhd9YUlMGYLQy2zprNmoT8qasCGtY+QaKw== -eslint-visitor-keys@^3.0.0, eslint-visitor-keys@^3.1.0: +eslint-visitor-keys@^3.0.0: version "3.1.0" resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-3.1.0.tgz#eee4acea891814cda67a7d8812d9647dd0179af2" integrity sha512-yWJFpu4DtjsWKkt5GeNBBuZMlNcYVs6vRCLoCVEJrTjaSB6LC98gFipNK/erM2Heg/E8mIK+hXG/pJMLK+eRZA== -eslint-visitor-keys@^3.2.0: - version "3.2.0" - resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-3.2.0.tgz#6fbb166a6798ee5991358bc2daa1ba76cc1254a1" - integrity sha512-IOzT0X126zn7ALX0dwFiUQEdsfzrm4+ISsQS8nukaJXwEyYKRSnEIIDULYg1mCtGp7UUXgfGl7BIolXREQK+XQ== +eslint-visitor-keys@^3.3.0: + version "3.3.0" + resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-3.3.0.tgz#f6480fa6b1f30efe2d1968aa8ac745b862469826" + integrity sha512-mQ+suqKJVyeuwGYHAdjMFqjCyfl8+Ldnxuyp3ldiMBFKkvytrXUZWaiPCEav8qDHKty44bD+qV1IP4T+w+xXRA== -eslint@^8.8.0: - version "8.8.0" - resolved "https://registry.yarnpkg.com/eslint/-/eslint-8.8.0.tgz#9762b49abad0cb4952539ffdb0a046392e571a2d" - integrity sha512-H3KXAzQGBH1plhYS3okDix2ZthuYJlQQEGE5k0IKuEqUSiyu4AmxxlJ2MtTYeJ3xB4jDhcYCwGOg2TXYdnDXlQ== +eslint@^8.9.0: + version "8.9.0" + resolved "https://registry.yarnpkg.com/eslint/-/eslint-8.9.0.tgz#a2a8227a99599adc4342fd9b854cb8d8d6412fdb" + integrity sha512-PB09IGwv4F4b0/atrbcMFboF/giawbBLVC7fyDamk5Wtey4Jh2K+rYaBhCAbUyEI4QzB1ly09Uglc9iCtFaG2Q== dependencies: - "@eslint/eslintrc" "^1.0.5" + "@eslint/eslintrc" "^1.1.0" "@humanwhocodes/config-array" "^0.9.2" ajv "^6.10.0" chalk "^4.0.0" @@ -2291,10 +2296,10 @@ eslint@^8.8.0: debug "^4.3.2" doctrine "^3.0.0" escape-string-regexp "^4.0.0" - eslint-scope "^7.1.0" + eslint-scope "^7.1.1" eslint-utils "^3.0.0" - eslint-visitor-keys "^3.2.0" - espree "^9.3.0" + eslint-visitor-keys "^3.3.0" + espree "^9.3.1" esquery "^1.4.0" esutils "^2.0.2" fast-deep-equal "^3.1.3" @@ -2319,23 +2324,14 @@ eslint@^8.8.0: text-table "^0.2.0" v8-compile-cache "^2.0.3" -espree@^9.2.0: - version "9.2.0" - resolved "https://registry.yarnpkg.com/espree/-/espree-9.2.0.tgz#c50814e01611c2d0f8bd4daa83c369eabba80dbc" - integrity sha512-oP3utRkynpZWF/F2x/HZJ+AGtnIclaR7z1pYPxy7NYM2fSO6LgK/Rkny8anRSPK/VwEA1eqm2squui0T7ZMOBg== - dependencies: - acorn "^8.6.0" - acorn-jsx "^5.3.1" - eslint-visitor-keys "^3.1.0" - -espree@^9.3.0: - version "9.3.0" - resolved "https://registry.yarnpkg.com/espree/-/espree-9.3.0.tgz#c1240d79183b72aaee6ccfa5a90bc9111df085a8" - integrity sha512-d/5nCsb0JcqsSEeQzFZ8DH1RmxPcglRWh24EFTlUEmCKoehXGdpsx0RkHDubqUI8LSAIKMQp4r9SzQ3n+sm4HQ== +espree@^9.3.1: + version "9.3.1" + resolved "https://registry.yarnpkg.com/espree/-/espree-9.3.1.tgz#8793b4bc27ea4c778c19908e0719e7b8f4115bcd" + integrity sha512-bvdyLmJMfwkV3NCRl5ZhJf22zBFo1y8bYh3VYb+bfzqNB4Je68P2sSuXyuFquzWLebHpNd2/d5uv7yoP9ISnGQ== dependencies: acorn "^8.7.0" acorn-jsx "^5.3.1" - eslint-visitor-keys "^3.1.0" + eslint-visitor-keys "^3.3.0" esprima@^4.0.0, esprima@^4.0.1: version "4.0.1" @@ -3687,10 +3683,10 @@ lines-and-columns@^1.1.6: resolved "https://registry.yarnpkg.com/lines-and-columns/-/lines-and-columns-1.2.4.tgz#eca284f75d2965079309dc0ad9255abb2ebc1632" integrity sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg== -lint-staged@^12.3.3: - version "12.3.3" - resolved "https://registry.yarnpkg.com/lint-staged/-/lint-staged-12.3.3.tgz#0a465962fe53baa2b4b9da50801ead49a910e03b" - integrity sha512-OqcLsqcPOqzvsfkxjeBpZylgJ3SRG1RYqc9LxC6tkt6tNsq1bNVkAixBwX09f6CobcHswzqVOCBpFR1Fck0+ag== +lint-staged@^12.3.4: + version "12.3.4" + resolved "https://registry.yarnpkg.com/lint-staged/-/lint-staged-12.3.4.tgz#4b1ff8c394c3e6da436aaec5afd4db18b5dac360" + integrity sha512-yv/iK4WwZ7/v0GtVkNb3R82pdL9M+ScpIbJLJNyCXkJ1FGaXvRCOg/SeL59SZtPpqZhE7BD6kPKFLIDUhDx2/w== dependencies: cli-truncate "^3.1.0" colorette "^2.0.16" @@ -3796,6 +3792,17 @@ logform@^2.3.2: safe-stable-stringify "^1.1.0" triple-beam "^1.3.0" +logform@^2.4.0: + version "2.4.0" + resolved "https://registry.yarnpkg.com/logform/-/logform-2.4.0.tgz#131651715a17d50f09c2a2c1a524ff1a4164bcfe" + integrity sha512-CPSJw4ftjf517EhXZGGvTHHkYobo7ZCc0kvwUoOYcjfR2UVrI66RHj8MCrfAdEitdmFqbu2BYdYs8FHHZSb6iw== + dependencies: + "@colors/colors" "1.5.0" + fecha "^4.2.0" + ms "^2.1.1" + safe-stable-stringify "^2.3.1" + triple-beam "^1.3.0" + lru-cache@^6.0.0: version "6.0.0" resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-6.0.0.tgz#6d6fe6570ebd96aaf90fcad1dafa3b2566db3a94" @@ -4261,7 +4268,7 @@ react-is@^17.0.1: resolved "https://registry.yarnpkg.com/react-is/-/react-is-17.0.2.tgz#e691d4a8e9c789365655539ab372762b0efb54f0" integrity sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w== -readable-stream@^3.4.0: +readable-stream@^3.4.0, readable-stream@^3.6.0: version "3.6.0" resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-3.6.0.tgz#337bbda3adc0706bd3e024426a286d4b4b2c9198" integrity sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA== @@ -4769,7 +4776,7 @@ tr46@~0.0.3: resolved "https://registry.yarnpkg.com/tr46/-/tr46-0.0.3.tgz#8184fd347dac9cdc185992f3a6622e14b9d9ab6a" integrity sha1-gYT9NH2snNwYWZLzpmIuFLnZq2o= -triple-beam@^1.2.0, triple-beam@^1.3.0: +triple-beam@^1.3.0: version "1.3.0" resolved "https://registry.yarnpkg.com/triple-beam/-/triple-beam-1.3.0.tgz#a595214c7298db8339eeeee083e4d10bd8cb8dd9" integrity sha512-XrHUvV5HpdLmIj4uVMxHggLbFSZYIn7HEWsqePZcI50pco+MPqJ50wMGY794X7AOOhxOBAjbkqfAbEe/QMp2Lw== @@ -5065,30 +5072,30 @@ which@^2.0.1: dependencies: isexe "^2.0.0" -winston-transport@^4.4.2: - version "4.4.2" - resolved "https://registry.yarnpkg.com/winston-transport/-/winston-transport-4.4.2.tgz#554efe3fce229d046df006e0e3c411d240652e51" - integrity sha512-9jmhltAr5ygt5usgUTQbEiw/7RYXpyUbEAFRCSicIacpUzPkrnQsQZSPGEI12aLK9Jth4zNcYJx3Cvznwrl8pw== +winston-transport@^4.5.0: + version "4.5.0" + resolved "https://registry.yarnpkg.com/winston-transport/-/winston-transport-4.5.0.tgz#6e7b0dd04d393171ed5e4e4905db265f7ab384fa" + integrity sha512-YpZzcUzBedhlTAfJg6vJDlyEai/IFMIVcaEZZyl3UXIl4gmqRpU7AE89AHLkbzLUsv0NVmw7ts+iztqKxxPW1Q== dependencies: logform "^2.3.2" - readable-stream "^3.4.0" - triple-beam "^1.2.0" + readable-stream "^3.6.0" + triple-beam "^1.3.0" -winston@^3.5.1: - version "3.5.1" - resolved "https://registry.yarnpkg.com/winston/-/winston-3.5.1.tgz#b25cc899d015836dbf8c583dec8c4c4483a0da2e" - integrity sha512-tbRtVy+vsSSCLcZq/8nXZaOie/S2tPXPFt4be/Q3vI/WtYwm7rrwidxVw2GRa38FIXcJ1kUM6MOZ9Jmnk3F3UA== +winston@^3.6.0: + version "3.6.0" + resolved "https://registry.yarnpkg.com/winston/-/winston-3.6.0.tgz#be32587a099a292b88c49fac6fa529d478d93fb6" + integrity sha512-9j8T75p+bcN6D00sF/zjFVmPp+t8KMPB1MzbbzYjeN9VWxdsYnTB40TkbNUEXAmILEfChMvAMgidlX64OG3p6w== dependencies: "@dabh/diagnostics" "^2.0.2" async "^3.2.3" is-stream "^2.0.0" - logform "^2.3.2" + logform "^2.4.0" one-time "^1.0.0" readable-stream "^3.4.0" safe-stable-stringify "^2.3.1" stack-trace "0.0.x" triple-beam "^1.3.0" - winston-transport "^4.4.2" + winston-transport "^4.5.0" word-wrap@^1.2.3, word-wrap@~1.2.3: version "1.2.3" From 513bfc70d1e22c259691f17874de508ab44c9fcc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=B8ren=20Louv-Jansen?= Date: Sat, 19 Feb 2022 12:42:44 +0100 Subject: [PATCH 6/7] Fix problem with clone --- src/services/git.ts | 8 +++++--- src/ui/maybeSetupRepo.ts | 2 +- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/src/services/git.ts b/src/services/git.ts index fa61355f..c071afce 100644 --- a/src/services/git.ts +++ b/src/services/git.ts @@ -130,9 +130,11 @@ export async function getRepoInfoFromGitRemotes({ cwd }: { cwd: string }) { } } -export async function getGitProjectRoot({ cwd }: { cwd: string }) { +export async function getGitProjectRoot(dir: string) { try { - const { stdout } = await exec('git rev-parse --show-toplevel', { cwd }); + const { stdout } = await exec('git rev-parse --show-toplevel', { + cwd: dir, + }); return stdout.trim(); } catch (e) { logger.error('An error occurred while retrieving git project root', e); @@ -523,7 +525,7 @@ export async function getSourceRepoPath(options: ValidConfigOptions) { const sourcePath = options.repoName === gitRemote.repoName && options.repoOwner === gitRemote.repoOwner - ? (await getGitProjectRoot(options)) ?? remoteUrl + ? (await getGitProjectRoot(options.cwd)) ?? remoteUrl : remoteUrl; return sourcePath; diff --git a/src/ui/maybeSetupRepo.ts b/src/ui/maybeSetupRepo.ts index 513b2661..3519eac2 100644 --- a/src/ui/maybeSetupRepo.ts +++ b/src/ui/maybeSetupRepo.ts @@ -60,6 +60,6 @@ export async function maybeSetupRepo(options: ValidConfigOptions) { async function getIsRepoCloned(options: ValidConfigOptions): Promise { const repoPath = getRepoPath(options); - const projectRoot = await getGitProjectRoot(options); + const projectRoot = await getGitProjectRoot(repoPath); return repoPath === projectRoot; } From 33885575f9aa62b3bd382ce233092a43e8d15511 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=B8ren=20Louv-Jansen?= Date: Sun, 20 Feb 2022 00:20:33 +0100 Subject: [PATCH 7/7] Improve tests --- src/runSequentially.test.ts | 2 +- src/test/e2e/cli.e2e.private.test.ts | 91 ++++++++++++++++--- ...errypickAndCreateTargetPullRequest.test.ts | 4 +- src/ui/maybeSetupRepo.test.ts | 43 ++++++--- src/ui/maybeSetupRepo.ts | 11 ++- src/ui/ora.ts | 7 +- 6 files changed, 119 insertions(+), 39 deletions(-) diff --git a/src/runSequentially.test.ts b/src/runSequentially.test.ts index 85bdb737..9097011b 100644 --- a/src/runSequentially.test.ts +++ b/src/runSequentially.test.ts @@ -169,7 +169,7 @@ describe('runSequentially', () => { ).toEqual([ { cmd: 'git rev-parse --show-toplevel', - cwd: '/path/to/source/repo', + cwd: '/myHomeDir/.backport/repositories/elastic/kibana', }, { cmd: 'git remote --verbose', diff --git a/src/test/e2e/cli.e2e.private.test.ts b/src/test/e2e/cli.e2e.private.test.ts index f9c666ee..3715546c 100644 --- a/src/test/e2e/cli.e2e.private.test.ts +++ b/src/test/e2e/cli.e2e.private.test.ts @@ -12,13 +12,17 @@ const devAccessToken = getDevAccessToken(); describe('inquirer cli', () => { it('--version', async () => { - const res = await runBackportViaCli([`--version`]); - expect(res).toContain(process.env.npm_package_version); + const res = await runBackportViaCli([`--version`], { + showLoadingSpinner: true, + }); + expect(res).toEqual(process.env.npm_package_version); }); it('-v', async () => { - const res = await runBackportViaCli([`-v`]); - expect(res).toContain(process.env.npm_package_version); + const res = await runBackportViaCli([`-v`], { + showLoadingSpinner: true, + }); + expect(res).toEqual(process.env.npm_package_version); }); it('PACKAGE_VERSION should match', async () => { @@ -322,13 +326,12 @@ describe('inquirer cli', () => { '--accessToken', devAccessToken, ], - { waitForString: "is invalid or doesn't exist" } + { waitForString: `Backporting to foo:` } ); expect(output).toMatchInlineSnapshot(` " - Backporting to foo: - The branch \\"foo\\" is invalid or doesn't exist" + Backporting to foo:" `); }); }); @@ -372,14 +375,69 @@ describe('inquirer cli', () => { `); }); }); + + describe('repo: test-that-repo-can-be-cloned', () => { + let sandboxPath: string; + beforeAll(async () => { + sandboxPath = getSandboxPath({ + filename: __filename, + specname: 'test-cloning', + }); + await resetSandbox(sandboxPath); + }); + + function run() { + return runBackportViaCli( + [ + '--repo', + 'backport-org/test-that-repo-can-be-cloned', + '--branch', + 'foo', + '--pr', + '1', + '--dir', + sandboxPath, + '--dry-run', + '--accessToken', + devAccessToken, + ], + { showLoadingSpinner: true, waitForString: 'Backporting to foo:' } + ); + } + + it('clones the repo on the very first run', async () => { + const output = await run(); + + expect(output).toContain('Cloning repository from github.com'); + expect(output).toMatchInlineSnapshot(` + "- Initializing... + ? Select pull request Beginning of a beautiful repo (#1) + ✔ 100% Cloning repository from github.com (one-time operation) + Backporting to foo:" + `); + }); + + it('does not clone the repo on subsequent runs', async () => { + const output = await run(); + + expect(output).not.toContain('Cloning repository from github.com'); + expect(output).toMatchInlineSnapshot(` + "- Initializing... + ? Select pull request Beginning of a beautiful repo (#1) + Backporting to foo:" + `); + }); + }); }); function runBackportViaCli( cliArgs: string[], { + showLoadingSpinner, waitForString, cwd, }: { + showLoadingSpinner?: boolean; waitForString?: string; cwd?: string; } = {} @@ -410,21 +468,24 @@ function runBackportViaCli( proc.stdout.on('data', (chunk) => { data += chunk; - const output = data.toString(); + const rawOutput = data.toString(); + + // remove ansi codes and whitespace + const output = stripAnsi(rawOutput).replace(/\s+$/gm, ''); if (!waitForString || output.includes(waitForString)) { clearTimeout(timeout); - // remove ansi codes and whitespace - const strippedOutput = stripAnsi(output).replace(/\s+$/gm, ''); - resolve(strippedOutput); + resolve(output); } }); - // for debugging only - // proc.stderr.on('data', (chunk) => { - // console.log('stderr', chunk.toString()); - // }); + // ora (loading spinner) is redirected to stderr + if (showLoadingSpinner) { + proc.stderr.on('data', (chunk) => { + data += chunk; + }); + } proc.on('error', (err) => { reject(`runBackportViaCli failed with: ${err}`); diff --git a/src/ui/cherrypickAndCreateTargetPullRequest.test.ts b/src/ui/cherrypickAndCreateTargetPullRequest.test.ts index 726f3278..4f3d85c5 100644 --- a/src/ui/cherrypickAndCreateTargetPullRequest.test.ts +++ b/src/ui/cherrypickAndCreateTargetPullRequest.test.ts @@ -157,7 +157,7 @@ describe('cherrypickAndCreateTargetPullRequest', () => { }); it('should start the spinner with the correct text', () => { - expect((ora as any).mock.calls.map((call: any) => call[0])) + expect((ora as any).mock.calls.map((call: any) => call[0].text)) .toMatchInlineSnapshot(` Array [ "Pulling latest changes", @@ -382,7 +382,7 @@ describe('cherrypickAndCreateTargetPullRequest', () => { }); it('calls ora correctly', () => { - expect((ora as any).mock.calls.map((call: any) => call[0])) + expect((ora as any).mock.calls.map((call: any) => call[0].text)) .toMatchInlineSnapshot(` Array [ "Pulling latest changes", diff --git a/src/ui/maybeSetupRepo.test.ts b/src/ui/maybeSetupRepo.test.ts index 3a835beb..6c64f2af 100644 --- a/src/ui/maybeSetupRepo.test.ts +++ b/src/ui/maybeSetupRepo.test.ts @@ -1,4 +1,3 @@ -import { stat } from 'fs/promises'; import os from 'os'; import del from 'del'; import { ValidConfigOptions } from '../options/options'; @@ -6,7 +5,6 @@ import * as childProcess from '../services/child-process-promisified'; import * as git from '../services/git'; import { getOraMock } from '../test/mocks'; import { maybeSetupRepo } from './maybeSetupRepo'; -const fs = { stat }; describe('maybeSetupRepo', () => { let execSpy: jest.SpyInstance; @@ -41,6 +39,7 @@ describe('maybeSetupRepo', () => { maybeSetupRepo({ repoName: 'kibana', repoOwner: 'elastic', + cwd: '/path/to/source/repo', } as ValidConfigOptions) ).rejects.toThrowError('Simulated git clone failure'); @@ -98,6 +97,7 @@ describe('maybeSetupRepo', () => { await maybeSetupRepo({ repoName: 'kibana', repoOwner: 'elastic', + cwd: '/path/to/source/repo', } as ValidConfigOptions); expect(spinnerTextSpy.mock.calls.map((call) => call[0])) @@ -123,9 +123,6 @@ describe('maybeSetupRepo', () => { describe('if repo already exists', () => { beforeEach(() => { - // @ts-expect-error - jest.spyOn(fs, 'stat').mockResolvedValue({ isDirectory: () => true }); - jest .spyOn(childProcess, 'execAsCallback') //@ts-expect-error @@ -145,7 +142,7 @@ describe('maybeSetupRepo', () => { authenticatedUsername: 'sqren_authenticated', repoName: 'kibana', repoOwner: 'elastic', - cwd: '/current/folder', + cwd: '/path/to/source/repo', } as ValidConfigOptions); expect( @@ -153,11 +150,11 @@ describe('maybeSetupRepo', () => { ).toEqual([ { cmd: 'git rev-parse --show-toplevel', - cwd: '/current/folder', + cwd: '/myHomeDir/.backport/repositories/elastic/kibana', }, { cmd: 'git remote --verbose', - cwd: '/current/folder', + cwd: '/path/to/source/repo', }, { cmd: 'git remote rm origin', @@ -185,9 +182,6 @@ describe('maybeSetupRepo', () => { describe('if repo does not exists locally', () => { beforeEach(() => { - // @ts-expect-error - jest.spyOn(fs, 'stat').mockResolvedValue({ isDirectory: () => true }); - jest .spyOn(childProcess, 'execAsCallback') //@ts-expect-error @@ -207,6 +201,7 @@ describe('maybeSetupRepo', () => { gitHostname: 'github.com', repoName: 'kibana', repoOwner: 'elastic', + cwd: '/path/to/source/repo', } as ValidConfigOptions); expect(childProcess.execAsCallback).toHaveBeenCalledWith( @@ -219,9 +214,6 @@ describe('maybeSetupRepo', () => { describe('if repo does exist locally', () => { beforeEach(() => { - // @ts-expect-error - jest.spyOn(fs, 'stat').mockResolvedValue({ isDirectory: () => true }); - jest .spyOn(git, 'getSourceRepoPath') .mockResolvedValue('/path/to/source/repo'); @@ -243,6 +235,7 @@ describe('maybeSetupRepo', () => { await maybeSetupRepo({ repoName: 'kibana', repoOwner: 'elastic', + cwd: '/path/to/source/repo', } as ValidConfigOptions); expect(childProcess.execAsCallback).toHaveBeenCalledWith( @@ -252,4 +245,26 @@ describe('maybeSetupRepo', () => { ); }); }); + + describe('if `repoPath` is a parent of current working directory (cwd)', () => { + beforeEach(() => { + jest + .spyOn(git, 'getSourceRepoPath') + .mockResolvedValue('/path/to/source/repo'); + + jest.spyOn(childProcess, 'execAsCallback'); + }); + + it('should clone it from local folder', async () => { + await expect(() => + maybeSetupRepo({ + repoName: 'kibana', + repoOwner: 'elastic', + cwd: '/myHomeDir/.backport/repositories/elastic/kibana/foo', + } as ValidConfigOptions) + ).rejects.toThrowError( + 'Refusing to clone repo into "/myHomeDir/.backport/repositories/elastic/kibana" when current working directory is "/myHomeDir/.backport/repositories/elastic/kibana/foo". Please change backport directory via `--dir` option or run backport from another location' + ); + }); + }); }); diff --git a/src/ui/maybeSetupRepo.ts b/src/ui/maybeSetupRepo.ts index 3519eac2..4495425d 100644 --- a/src/ui/maybeSetupRepo.ts +++ b/src/ui/maybeSetupRepo.ts @@ -1,6 +1,6 @@ import del = require('del'); -import ora = require('ora'); import { ValidConfigOptions } from '../options/options'; +import { HandledError } from '../services/HandledError'; import { getRepoPath } from '../services/env'; import { addRemote, @@ -9,14 +9,21 @@ import { getGitProjectRoot, getSourceRepoPath, } from '../services/git'; +import { ora } from './ora'; export async function maybeSetupRepo(options: ValidConfigOptions) { const repoPath = getRepoPath(options); const isAlreadyCloned = await getIsRepoCloned(options); + if (options.cwd.includes(repoPath)) { + throw new HandledError( + `Refusing to clone repo into "${repoPath}" when current working directory is "${options.cwd}". Please change backport directory via \`--dir\` option or run backport from another location` + ); + } + // clone repo if folder does not already exists if (!isAlreadyCloned) { - const spinner = ora().start(); + const spinner = ora(options.ci).start(); try { const sourcePath = await getSourceRepoPath(options); diff --git a/src/ui/ora.ts b/src/ui/ora.ts index 47b8dea5..b701d651 100644 --- a/src/ui/ora.ts +++ b/src/ui/ora.ts @@ -11,9 +11,6 @@ const oraMock = { set text(value: string) {}, }; -export function ora( - ci: boolean | undefined, - options?: string | oraOriginal.Options | undefined -) { - return ci ? oraMock : oraOriginal(options); +export function ora(ci: boolean | undefined, text?: string | undefined) { + return ci ? oraMock : oraOriginal({ text }); }