From 1fe9e8690600b6536290726c39f5e58353ffad90 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=B8ren=20Louv-Jansen?= Date: Fri, 11 Mar 2022 13:54:44 +0100 Subject: [PATCH 01/15] Renaming Rename HandledError to BackportError Rename /ui to /lib Rename /services to /lib --- src/backportRun.ts | 36 +++++++------ src/entrypoint.module.private.test.ts | 2 +- src/entrypoint.module.ts | 33 ++++++++---- .../BackportError.ts} | 8 +-- ...ickAndCreateTargetPullRequest.test.ts.snap | 0 ...errypickAndCreateTargetPullRequest.test.ts | 6 +-- .../cherrypickAndCreateTargetPullRequest.ts | 36 ++++++------- .../child-process-promisified.ts | 0 src/{services => lib}/env.test.ts | 2 +- src/{services => lib}/env.ts | 0 src/{ui => lib}/getCommits.ts | 16 +++--- .../getCommitsWithoutBackports.test.ts | 6 +-- src/{ui => lib}/getCommitsWithoutBackports.ts | 6 +-- src/{ui => lib}/getGitConfigAuthor.ts | 2 +- src/{ui => lib}/getTargetBranches.test.ts | 4 +- src/{ui => lib}/getTargetBranches.ts | 10 ++-- src/{services => lib}/git.private.test.ts | 0 src/{services => lib}/git.test.ts | 2 +- src/{services => lib}/git.ts | 16 +++--- .../github/commitFormatters.test.ts | 0 .../github/commitFormatters.ts | 0 .../v3/addAssigneesToPullRequest.test.ts | 0 .../github/v3/addAssigneesToPullRequest.ts | 2 +- .../github/v3/addLabelsToPullRequest.ts | 2 +- .../github/v3/addReviewersToPullRequest.ts | 2 +- .../github/v3/createPullRequest.test.ts | 0 .../github/v3/createPullRequest.ts | 6 +-- .../github/v3/createStatusComment.test.ts | 9 ++-- .../github/v3/createStatusComment.ts | 4 +- .../github/v3/getGithubV3ErrorMessage.ts | 0 .../github/v4/FetchPullRequestId.ts | 0 .../throwOnInvalidAccessToken.test.ts.snap | 0 .../github/v4/apiRequestV4.test.ts | 4 +- .../github/v4/apiRequestV4.ts | 4 +- .../github/v4/disablePullRequestAutoMerge.ts | 0 ...enablePullRequestAutoMerge.private.test.ts | 0 .../github/v4/enablePullRequestAutoMerge.ts | 2 +- .../github/v4/fetchAuthorId.private.test.ts | 0 .../github/v4/fetchAuthorId.ts | 0 ...tchCommitByPullNumber.private.test.ts.snap | 0 .../fetchCommitBySha.private.test.ts.snap | 0 .../fetchCommitsByAuthor.private.test.ts.snap | 0 .../fetchCommitsByAuthor.test.ts.snap | 0 .../fetchCommits/allFetchers.private.test.ts | 0 .../fetchCommitByPullNumber.private.test.ts | 0 .../fetchCommits/fetchCommitByPullNumber.ts | 6 +-- .../fetchCommitBySha.private.test.ts | 0 .../v4/fetchCommits/fetchCommitBySha.ts | 4 +- .../fetchCommitsByAuthor.private.test.ts | 0 .../fetchCommits/fetchCommitsByAuthor.test.ts | 0 .../v4/fetchCommits/fetchCommitsByAuthor.ts | 6 +-- ...chPullRequestBySearchQuery.private.test.ts | 0 .../fetchPullRequestsBySearchQuery.ts | 4 +- .../fetchExistingPullRequest.private.test.ts | 0 .../github/v4/fetchExistingPullRequest.ts | 0 .../v4/fetchPullRequestAutoMergeMethod.ts | 0 .../fetchRemoteProjectConfig.private.test.ts | 0 .../github/v4/fetchRemoteProjectConfig.ts | 0 .../getOptionsFromGithub.private.test.ts | 0 .../getOptionsFromGithub.ts | 4 +- .../github/v4/getOptionsFromGithub/query.ts | 0 ...OwnerAndNameFromGitRemotes.private.test.ts | 0 .../v4/getRepoOwnerAndNameFromGitRemotes.ts | 0 .../github/v4/mocks/commitsByAuthorMock.ts | 0 .../v4/throwOnInvalidAccessToken.test.ts | 0 .../github/v4/throwOnInvalidAccessToken.ts | 10 ++-- src/{services => lib}/logger.ts | 0 src/{ui => lib}/ora.ts | 0 src/{services => lib}/prompt.test.ts | 0 src/{services => lib}/prompts.ts | 0 src/{services => lib}/remoteConfig.ts | 0 src/{services => lib}/sequentially.ts | 0 src/{ui => lib}/setupRepo.test.ts | 4 +- src/{ui => lib}/setupRepo.ts | 8 +-- .../getExpectedTargetPullRequests.test.ts | 0 .../getExpectedTargetPullRequests.ts | 0 .../sourceCommit/getMockSourceCommit.ts | 0 .../sourceCommit/parseSourceCommit.test.ts | 0 .../sourceCommit/parseSourceCommit.ts | 0 src/options/config/globalConfig.ts | 6 +-- src/options/config/readConfigFile.ts | 4 +- src/options/options.test.ts | 8 +-- src/options/options.ts | 20 +++---- src/runSequentially.ts | 53 ++++++++++--------- src/scripts/postinstall.test.ts | 2 +- src/scripts/postinstall.ts | 4 +- .../e2e/cli/commit-author.private.test.ts | 2 +- ...different-merge-strategies.private.test.ts | 2 +- .../e2e/cli/entrypoint.cli.private.test.ts | 10 ++-- ...st-that-repo-can-be-cloned.private.test.ts | 2 +- .../module/entrypoint.module.mutation.test.ts | 2 +- src/test/setupFiles/automatic-mocks.ts | 2 +- 92 files changed, 200 insertions(+), 183 deletions(-) rename src/{services/HandledError.ts => errors/BackportError.ts} (86%) rename src/{ui => lib}/__snapshots__/cherrypickAndCreateTargetPullRequest.test.ts.snap (100%) rename src/{ui => lib}/cherrypickAndCreateTargetPullRequest.test.ts (98%) rename src/{ui => lib}/cherrypickAndCreateTargetPullRequest.ts (90%) rename src/{services => lib}/child-process-promisified.ts (100%) rename src/{services => lib}/env.test.ts (88%) rename src/{services => lib}/env.ts (100%) rename src/{ui => lib}/getCommits.ts (78%) rename src/{ui => lib}/getCommitsWithoutBackports.test.ts (97%) rename src/{ui => lib}/getCommitsWithoutBackports.ts (93%) rename src/{ui => lib}/getGitConfigAuthor.ts (87%) rename src/{ui => lib}/getTargetBranches.test.ts (98%) rename src/{ui => lib}/getTargetBranches.ts (89%) rename src/{services => lib}/git.private.test.ts (100%) rename src/{services => lib}/git.test.ts (99%) rename src/{services => lib}/git.ts (98%) rename src/{services => lib}/github/commitFormatters.test.ts (100%) rename src/{services => lib}/github/commitFormatters.ts (100%) rename src/{services => lib}/github/v3/addAssigneesToPullRequest.test.ts (100%) rename src/{services => lib}/github/v3/addAssigneesToPullRequest.ts (96%) rename src/{services => lib}/github/v3/addLabelsToPullRequest.ts (95%) rename src/{services => lib}/github/v3/addReviewersToPullRequest.ts (96%) rename src/{services => lib}/github/v3/createPullRequest.test.ts (100%) rename src/{services => lib}/github/v3/createPullRequest.ts (96%) rename src/{services => lib}/github/v3/createStatusComment.test.ts (98%) rename src/{services => lib}/github/v3/createStatusComment.ts (98%) rename src/{services => lib}/github/v3/getGithubV3ErrorMessage.ts (100%) rename src/{services => lib}/github/v4/FetchPullRequestId.ts (100%) rename src/{services => lib}/github/v4/__snapshots__/throwOnInvalidAccessToken.test.ts.snap (100%) rename src/{services => lib}/github/v4/apiRequestV4.test.ts (95%) rename src/{services => lib}/github/v4/apiRequestV4.ts (96%) rename src/{services => lib}/github/v4/disablePullRequestAutoMerge.ts (100%) rename src/{services => lib}/github/v4/enablePullRequestAutoMerge.private.test.ts (100%) rename src/{services => lib}/github/v4/enablePullRequestAutoMerge.ts (97%) rename src/{services => lib}/github/v4/fetchAuthorId.private.test.ts (100%) rename src/{services => lib}/github/v4/fetchAuthorId.ts (100%) rename src/{services => lib}/github/v4/fetchCommits/__snapshots__/fetchCommitByPullNumber.private.test.ts.snap (100%) rename src/{services => lib}/github/v4/fetchCommits/__snapshots__/fetchCommitBySha.private.test.ts.snap (100%) rename src/{services => lib}/github/v4/fetchCommits/__snapshots__/fetchCommitsByAuthor.private.test.ts.snap (100%) rename src/{services => lib}/github/v4/fetchCommits/__snapshots__/fetchCommitsByAuthor.test.ts.snap (100%) rename src/{services => lib}/github/v4/fetchCommits/allFetchers.private.test.ts (100%) rename src/{services => lib}/github/v4/fetchCommits/fetchCommitByPullNumber.private.test.ts (100%) rename src/{services => lib}/github/v4/fetchCommits/fetchCommitByPullNumber.ts (90%) rename src/{services => lib}/github/v4/fetchCommits/fetchCommitBySha.private.test.ts (100%) rename src/{services => lib}/github/v4/fetchCommits/fetchCommitBySha.ts (95%) rename src/{services => lib}/github/v4/fetchCommits/fetchCommitsByAuthor.private.test.ts (100%) rename src/{services => lib}/github/v4/fetchCommits/fetchCommitsByAuthor.test.ts (100%) rename src/{services => lib}/github/v4/fetchCommits/fetchCommitsByAuthor.ts (97%) rename src/{services => lib}/github/v4/fetchCommits/fetchPullRequestBySearchQuery.private.test.ts (100%) rename src/{services => lib}/github/v4/fetchCommits/fetchPullRequestsBySearchQuery.ts (96%) rename src/{services => lib}/github/v4/fetchExistingPullRequest.private.test.ts (100%) rename src/{services => lib}/github/v4/fetchExistingPullRequest.ts (100%) rename src/{services => lib}/github/v4/fetchPullRequestAutoMergeMethod.ts (100%) rename src/{services => lib}/github/v4/fetchRemoteProjectConfig.private.test.ts (100%) rename src/{services => lib}/github/v4/fetchRemoteProjectConfig.ts (100%) rename src/{services => lib}/github/v4/getOptionsFromGithub/getOptionsFromGithub.private.test.ts (100%) rename src/{services => lib}/github/v4/getOptionsFromGithub/getOptionsFromGithub.ts (97%) rename src/{services => lib}/github/v4/getOptionsFromGithub/query.ts (100%) rename src/{services => lib}/github/v4/getRepoOwnerAndNameFromGitRemotes.private.test.ts (100%) rename src/{services => lib}/github/v4/getRepoOwnerAndNameFromGitRemotes.ts (100%) rename src/{services => lib}/github/v4/mocks/commitsByAuthorMock.ts (100%) rename src/{services => lib}/github/v4/throwOnInvalidAccessToken.test.ts (100%) rename src/{services => lib}/github/v4/throwOnInvalidAccessToken.ts (91%) rename src/{services => lib}/logger.ts (100%) rename src/{ui => lib}/ora.ts (100%) rename src/{services => lib}/prompt.test.ts (100%) rename src/{services => lib}/prompts.ts (100%) rename src/{services => lib}/remoteConfig.ts (100%) rename src/{services => lib}/sequentially.ts (100%) rename src/{ui => lib}/setupRepo.test.ts (98%) rename src/{ui => lib}/setupRepo.ts (93%) rename src/{services => lib}/sourceCommit/getExpectedTargetPullRequests.test.ts (100%) rename src/{services => lib}/sourceCommit/getExpectedTargetPullRequests.ts (100%) rename src/{services => lib}/sourceCommit/getMockSourceCommit.ts (100%) rename src/{services => lib}/sourceCommit/parseSourceCommit.test.ts (100%) rename src/{services => lib}/sourceCommit/parseSourceCommit.ts (100%) diff --git a/src/backportRun.ts b/src/backportRun.ts index 1030b23b..b9b4d61c 100755 --- a/src/backportRun.ts +++ b/src/backportRun.ts @@ -1,25 +1,26 @@ import chalk from 'chalk'; import yargsParser from 'yargs-parser'; +import { BackportError } from './errors/BackportError'; +import { getLogfilePath } from './lib/env'; +import { getCommits } from './lib/getCommits'; +import { getGitConfigAuthor } from './lib/getGitConfigAuthor'; +import { getTargetBranches } from './lib/getTargetBranches'; +import { createStatusComment } from './lib/github/v3/createStatusComment'; +import { GithubV4Exception } from './lib/github/v4/apiRequestV4'; +import { consoleLog, initLogger } from './lib/logger'; +import { ora } from './lib/ora'; +import { setupRepo } from './lib/setupRepo'; +import { Commit } from './lib/sourceCommit/parseSourceCommit'; import { ConfigFileOptions } from './options/ConfigOptions'; import { CliError } from './options/cliArgs'; import { getOptions, ValidConfigOptions } from './options/options'; import { runSequentially, Result } from './runSequentially'; -import { HandledError } from './services/HandledError'; -import { getLogfilePath } from './services/env'; -import { createStatusComment } from './services/github/v3/createStatusComment'; -import { GithubV4Exception } from './services/github/v4/apiRequestV4'; -import { consoleLog, initLogger } from './services/logger'; -import { Commit } from './services/sourceCommit/parseSourceCommit'; -import { getCommits } from './ui/getCommits'; -import { getGitConfigAuthor } from './ui/getGitConfigAuthor'; -import { getTargetBranches } from './ui/getTargetBranches'; -import { ora } from './ui/ora'; -import { setupRepo } from './ui/setupRepo'; export type BackportAbortResponse = { status: 'aborted'; commits: Commit[]; - error: HandledError; + error: BackportError; + errorMessage: string; }; export type BackportSuccessResponse = { @@ -31,7 +32,7 @@ export type BackportSuccessResponse = { export type BackportFailureResponse = { status: 'failure'; commits: Commit[]; - error: Error | HandledError; + error: Error | BackportError; errorMessage: string; }; @@ -117,16 +118,17 @@ export async function backportRun({ let backportResponse: BackportResponse; if ( - e instanceof HandledError && + e instanceof BackportError && e.errorContext.code === 'no-branches-exception' ) { backportResponse = { status: 'aborted', commits, error: e, + errorMessage: e.message, }; - // this will catch both HandledError and Error + // this will catch both BackportError and Error } else if (e instanceof Error) { backportResponse = { status: 'failure', @@ -159,10 +161,10 @@ function outputError({ e, logFilePath, }: { - e: HandledError | GithubV4Exception | Error; + e: BackportError | GithubV4Exception | Error; logFilePath?: string; }) { - if (e instanceof HandledError || e instanceof GithubV4Exception) { + if (e instanceof BackportError || e instanceof GithubV4Exception) { consoleLog(e.message); return; } diff --git a/src/entrypoint.module.private.test.ts b/src/entrypoint.module.private.test.ts index 339e9410..33e6492b 100644 --- a/src/entrypoint.module.private.test.ts +++ b/src/entrypoint.module.private.test.ts @@ -3,7 +3,7 @@ import { BackportSuccessResponse, } from './backportRun'; import { backportRun, Commit, getCommits } from './entrypoint.module'; -import { getFirstLine } from './services/github/commitFormatters'; +import { getFirstLine } from './lib/github/commitFormatters'; import { getDevAccessToken } from './test/private/getDevAccessToken'; const accessToken = getDevAccessToken(); diff --git a/src/entrypoint.module.ts b/src/entrypoint.module.ts index 254af237..8eadcf76 100644 --- a/src/entrypoint.module.ts +++ b/src/entrypoint.module.ts @@ -1,21 +1,32 @@ import { backportRun as run } from './backportRun'; import { BackportResponse } from './backportRun'; +import { fetchCommitByPullNumber } from './lib/github/v4/fetchCommits/fetchCommitByPullNumber'; +import { fetchCommitBySha } from './lib/github/v4/fetchCommits/fetchCommitBySha'; +import { fetchCommitsByAuthor } from './lib/github/v4/fetchCommits/fetchCommitsByAuthor'; +import { fetchPullRequestsBySearchQuery } from './lib/github/v4/fetchCommits/fetchPullRequestsBySearchQuery'; +import { getOptionsFromGithub } from './lib/github/v4/getOptionsFromGithub/getOptionsFromGithub'; +import { initLogger } from './lib/logger'; +import type { Commit } from './lib/sourceCommit/parseSourceCommit'; import { ConfigFileOptions } from './options/ConfigOptions'; import { ValidConfigOptions } from './options/options'; -import { fetchCommitByPullNumber } from './services/github/v4/fetchCommits/fetchCommitByPullNumber'; -import { fetchCommitBySha } from './services/github/v4/fetchCommits/fetchCommitBySha'; -import { fetchCommitsByAuthor } from './services/github/v4/fetchCommits/fetchCommitsByAuthor'; -import { fetchPullRequestsBySearchQuery } from './services/github/v4/fetchCommits/fetchPullRequestsBySearchQuery'; -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 }; -export { Commit }; -export { ConfigFileOptions } from './options/ConfigOptions'; -export { fetchRemoteProjectConfig as getRemoteProjectConfig } from './services/github/v4/fetchRemoteProjectConfig'; +export type { + HandledErrorResult, + SuccessResult, + UnhandledErrorResult, +} from './runSequentially'; +export type { + BackportAbortResponse, + BackportFailureResponse, + BackportResponse, + BackportSuccessResponse, +} from './backportRun'; +export { BackportError } from './errors/BackportError'; +export type { Commit } from './lib/sourceCommit/parseSourceCommit'; +export type { ConfigFileOptions } from './options/ConfigOptions'; +export { fetchRemoteProjectConfig as getRemoteProjectConfig } from './lib/github/v4/fetchRemoteProjectConfig'; export { getGlobalConfig as getLocalGlobalConfig } from './options/config/globalConfig'; export function backportRun({ diff --git a/src/services/HandledError.ts b/src/errors/BackportError.ts similarity index 86% rename from src/services/HandledError.ts rename to src/errors/BackportError.ts index 509cc68e..0f2a13e6 100644 --- a/src/services/HandledError.ts +++ b/src/errors/BackportError.ts @@ -1,4 +1,4 @@ -import { Commit } from './sourceCommit/parseSourceCommit'; +import { Commit } from '../lib/sourceCommit/parseSourceCommit'; type ErrorContext = | { @@ -32,7 +32,7 @@ function getMessage(errorContext: ErrorContext): string { } } -export class HandledError extends Error { +export class BackportError extends Error { errorContext: ErrorContext; constructor(errorContextOrString: ErrorContext | string) { const errorContext: ErrorContext = @@ -41,8 +41,8 @@ export class HandledError extends Error { : errorContextOrString; const message = getMessage(errorContext); super(message); - Error.captureStackTrace(this, HandledError); - this.name = 'HandledError'; + Error.captureStackTrace(this, BackportError); + this.name = 'BackportError'; this.message = message; this.errorContext = errorContext; } diff --git a/src/ui/__snapshots__/cherrypickAndCreateTargetPullRequest.test.ts.snap b/src/lib/__snapshots__/cherrypickAndCreateTargetPullRequest.test.ts.snap similarity index 100% rename from src/ui/__snapshots__/cherrypickAndCreateTargetPullRequest.test.ts.snap rename to src/lib/__snapshots__/cherrypickAndCreateTargetPullRequest.test.ts.snap diff --git a/src/ui/cherrypickAndCreateTargetPullRequest.test.ts b/src/lib/cherrypickAndCreateTargetPullRequest.test.ts similarity index 98% rename from src/ui/cherrypickAndCreateTargetPullRequest.test.ts rename to src/lib/cherrypickAndCreateTargetPullRequest.test.ts index 7aafd6a2..a4b5b947 100644 --- a/src/ui/cherrypickAndCreateTargetPullRequest.test.ts +++ b/src/lib/cherrypickAndCreateTargetPullRequest.test.ts @@ -2,12 +2,12 @@ import os from 'os'; import nock from 'nock'; import ora from 'ora'; import { ValidConfigOptions } from '../options/options'; -import * as childProcess from '../services/child-process-promisified'; -import * as logger from '../services/logger'; -import { Commit } from '../services/sourceCommit/parseSourceCommit'; import { listenForCallsToNockScope } from '../test/nockHelpers'; import { SpyHelper } from '../types/SpyHelper'; import { cherrypickAndCreateTargetPullRequest } from './cherrypickAndCreateTargetPullRequest'; +import * as childProcess from './child-process-promisified'; +import * as logger from './logger'; +import { Commit } from './sourceCommit/parseSourceCommit'; describe('cherrypickAndCreateTargetPullRequest', () => { let execSpy: SpyHelper; diff --git a/src/ui/cherrypickAndCreateTargetPullRequest.ts b/src/lib/cherrypickAndCreateTargetPullRequest.ts similarity index 90% rename from src/ui/cherrypickAndCreateTargetPullRequest.ts rename to src/lib/cherrypickAndCreateTargetPullRequest.ts index 33815f8e..76151df8 100644 --- a/src/ui/cherrypickAndCreateTargetPullRequest.ts +++ b/src/lib/cherrypickAndCreateTargetPullRequest.ts @@ -1,9 +1,11 @@ import chalk from 'chalk'; import { isEmpty, difference } from 'lodash'; +import { BackportError } from '../errors/BackportError'; import { ValidConfigOptions } from '../options/options'; -import { HandledError } from '../services/HandledError'; -import { spawnPromise } from '../services/child-process-promisified'; -import { getRepoPath } from '../services/env'; +import { spawnPromise } from './child-process-promisified'; +import { getRepoPath } from './env'; +import { getCommitsWithoutBackports } from './getCommitsWithoutBackports'; +import { GitConfigAuthor } from './getGitConfigAuthor'; import { cherrypick, createBackportBranch, @@ -15,25 +17,23 @@ import { getRepoForkOwner, fetchBranch, ConflictingFiles, -} from '../services/git'; -import { getFirstLine, getShortSha } from '../services/github/commitFormatters'; -import { addAssigneesToPullRequest } from '../services/github/v3/addAssigneesToPullRequest'; -import { addLabelsToPullRequest } from '../services/github/v3/addLabelsToPullRequest'; -import { addReviewersToPullRequest } from '../services/github/v3/addReviewersToPullRequest'; +} from './git'; +import { getFirstLine, getShortSha } from './github/commitFormatters'; +import { addAssigneesToPullRequest } from './github/v3/addAssigneesToPullRequest'; +import { addLabelsToPullRequest } from './github/v3/addLabelsToPullRequest'; +import { addReviewersToPullRequest } from './github/v3/addReviewersToPullRequest'; import { createPullRequest, getTitle, getPullRequestBody, PullRequestPayload, -} from '../services/github/v3/createPullRequest'; -import { enablePullRequestAutoMerge } from '../services/github/v4/enablePullRequestAutoMerge'; -import { consoleLog, logger } from '../services/logger'; -import { confirmPrompt } from '../services/prompts'; -import { sequentially } from '../services/sequentially'; -import { Commit } from '../services/sourceCommit/parseSourceCommit'; -import { getCommitsWithoutBackports } from './getCommitsWithoutBackports'; -import { GitConfigAuthor } from './getGitConfigAuthor'; +} from './github/v3/createPullRequest'; +import { enablePullRequestAutoMerge } from './github/v4/enablePullRequestAutoMerge'; +import { consoleLog, logger } from './logger'; import { ora } from './ora'; +import { confirmPrompt } from './prompts'; +import { sequentially } from './sequentially'; +import { Commit } from './sourceCommit/parseSourceCommit'; export async function cherrypickAndCreateTargetPullRequest({ options, @@ -235,7 +235,7 @@ async function waitForCherrypick( }); if (options.ci) { - throw new HandledError({ + throw new BackportError({ code: 'merge-conflict-exception', commitsWithoutBackports, conflictingFiles: conflictingFilesRelative, @@ -335,7 +335,7 @@ ${unstagedSection} Press ENTER when the conflicts are resolved and files are staged`); if (!didConfirm) { - throw new HandledError({ code: 'abort-exception' }); + throw new BackportError({ code: 'abort-exception' }); } const MAX_RETRIES = 100; diff --git a/src/services/child-process-promisified.ts b/src/lib/child-process-promisified.ts similarity index 100% rename from src/services/child-process-promisified.ts rename to src/lib/child-process-promisified.ts diff --git a/src/services/env.test.ts b/src/lib/env.test.ts similarity index 88% rename from src/services/env.test.ts rename to src/lib/env.test.ts index 0f249787..1cac9f20 100644 --- a/src/services/env.test.ts +++ b/src/lib/env.test.ts @@ -1,6 +1,6 @@ import os from 'os'; import { ValidConfigOptions } from '../options/options'; -import { getGlobalConfigPath, getRepoPath } from '../services/env'; +import { getGlobalConfigPath, getRepoPath } from './env'; describe('env', () => { beforeEach(() => { diff --git a/src/services/env.ts b/src/lib/env.ts similarity index 100% rename from src/services/env.ts rename to src/lib/env.ts diff --git a/src/ui/getCommits.ts b/src/lib/getCommits.ts similarity index 78% rename from src/ui/getCommits.ts rename to src/lib/getCommits.ts index 15069a51..bd610ef2 100644 --- a/src/ui/getCommits.ts +++ b/src/lib/getCommits.ts @@ -1,13 +1,13 @@ import chalk from 'chalk'; +import { BackportError } from '../errors/BackportError'; import { ValidConfigOptions } from '../options/options'; -import { HandledError } from '../services/HandledError'; -import { getFirstLine, getShortSha } from '../services/github/commitFormatters'; -import { fetchCommitByPullNumber } from '../services/github/v4/fetchCommits/fetchCommitByPullNumber'; -import { fetchCommitBySha } from '../services/github/v4/fetchCommits/fetchCommitBySha'; -import { fetchCommitsByAuthor } from '../services/github/v4/fetchCommits/fetchCommitsByAuthor'; -import { fetchPullRequestsBySearchQuery } from '../services/github/v4/fetchCommits/fetchPullRequestsBySearchQuery'; -import { promptForCommits } from '../services/prompts'; +import { getFirstLine, getShortSha } from './github/commitFormatters'; +import { fetchCommitByPullNumber } from './github/v4/fetchCommits/fetchCommitByPullNumber'; +import { fetchCommitBySha } from './github/v4/fetchCommits/fetchCommitBySha'; +import { fetchCommitsByAuthor } from './github/v4/fetchCommits/fetchCommitsByAuthor'; +import { fetchPullRequestsBySearchQuery } from './github/v4/fetchCommits/fetchPullRequestsBySearchQuery'; import { ora } from './ora'; +import { promptForCommits } from './prompts'; function getOraPersistsOption(question: string, answer: string) { return { @@ -59,7 +59,7 @@ export async function getCommits(options: ValidConfigOptions) { } if (options.ci && !options.ls) { - throw new HandledError( + throw new BackportError( 'When "--ci" flag is enabled either `--sha` or `--pr` must be specified' ); } diff --git a/src/ui/getCommitsWithoutBackports.test.ts b/src/lib/getCommitsWithoutBackports.test.ts similarity index 97% rename from src/ui/getCommitsWithoutBackports.test.ts rename to src/lib/getCommitsWithoutBackports.test.ts index feaf66d2..0f26f87d 100644 --- a/src/ui/getCommitsWithoutBackports.test.ts +++ b/src/lib/getCommitsWithoutBackports.test.ts @@ -1,9 +1,9 @@ import stripAnsi from 'strip-ansi'; import { ValidConfigOptions } from '../options/options'; -import * as git from '../services/git'; -import * as fetchCommitsByAuthorModule from '../services/github/v4/fetchCommits/fetchCommitsByAuthor'; -import { ExpectedTargetPullRequest } from '../services/sourceCommit/getExpectedTargetPullRequests'; import { getCommitsWithoutBackports } from './getCommitsWithoutBackports'; +import * as git from './git'; +import * as fetchCommitsByAuthorModule from './github/v4/fetchCommits/fetchCommitsByAuthor'; +import { ExpectedTargetPullRequest } from './sourceCommit/getExpectedTargetPullRequests'; describe('getCommitsWithoutBackports', () => { afterEach(() => { diff --git a/src/ui/getCommitsWithoutBackports.ts b/src/lib/getCommitsWithoutBackports.ts similarity index 93% rename from src/ui/getCommitsWithoutBackports.ts rename to src/lib/getCommitsWithoutBackports.ts index 9c60bd67..1af3ec5e 100644 --- a/src/ui/getCommitsWithoutBackports.ts +++ b/src/lib/getCommitsWithoutBackports.ts @@ -1,9 +1,9 @@ import chalk from 'chalk'; import { Commit } from '../entrypoint.module'; import { ValidConfigOptions } from '../options/options'; -import { getIsCommitInBranch } from '../services/git'; -import { getFirstLine } from '../services/github/commitFormatters'; -import { fetchCommitsByAuthor } from '../services/github/v4/fetchCommits/fetchCommitsByAuthor'; +import { getIsCommitInBranch } from './git'; +import { getFirstLine } from './github/commitFormatters'; +import { fetchCommitsByAuthor } from './github/v4/fetchCommits/fetchCommitsByAuthor'; // when the user is facing a git conflict we should help them understand // why the conflict occurs. In many cases it's because one or more commits haven't been backported yet diff --git a/src/ui/getGitConfigAuthor.ts b/src/lib/getGitConfigAuthor.ts similarity index 87% rename from src/ui/getGitConfigAuthor.ts rename to src/lib/getGitConfigAuthor.ts index 6b73470f..9471ffec 100644 --- a/src/ui/getGitConfigAuthor.ts +++ b/src/lib/getGitConfigAuthor.ts @@ -1,5 +1,5 @@ import { ValidConfigOptions } from '../options/options'; -import { getGitConfig, getLocalSourceRepoPath } from '../services/git'; +import { getGitConfig, getLocalSourceRepoPath } from './git'; export type GitConfigAuthor = { name?: string; diff --git a/src/ui/getTargetBranches.test.ts b/src/lib/getTargetBranches.test.ts similarity index 98% rename from src/ui/getTargetBranches.test.ts rename to src/lib/getTargetBranches.test.ts index 642ff290..2557e806 100644 --- a/src/ui/getTargetBranches.test.ts +++ b/src/lib/getTargetBranches.test.ts @@ -1,9 +1,9 @@ import { TargetBranchChoice } from '../options/ConfigOptions'; import { ValidConfigOptions } from '../options/options'; -import * as prompts from '../services/prompts'; -import { Commit } from '../services/sourceCommit/parseSourceCommit'; import { SpyHelper } from '../types/SpyHelper'; import { getTargetBranches, getTargetBranchChoices } from './getTargetBranches'; +import * as prompts from './prompts'; +import { Commit } from './sourceCommit/parseSourceCommit'; describe('getTargetBranches', () => { let promptSpy: SpyHelper; diff --git a/src/ui/getTargetBranches.ts b/src/lib/getTargetBranches.ts similarity index 89% rename from src/ui/getTargetBranches.ts rename to src/lib/getTargetBranches.ts index 90f75c8d..5e1a66a8 100644 --- a/src/ui/getTargetBranches.ts +++ b/src/lib/getTargetBranches.ts @@ -1,12 +1,12 @@ import { isEmpty, isString } from 'lodash'; +import { BackportError } from '../errors/BackportError'; import { TargetBranchChoice, TargetBranchChoiceOrString, } from '../options/ConfigOptions'; import { ValidConfigOptions } from '../options/options'; -import { HandledError } from '../services/HandledError'; -import { promptForTargetBranches } from '../services/prompts'; -import { Commit } from '../services/sourceCommit/parseSourceCommit'; +import { promptForTargetBranches } from './prompts'; +import { Commit } from './sourceCommit/parseSourceCommit'; export function getTargetBranches( options: ValidConfigOptions, @@ -28,7 +28,7 @@ export function getTargetBranches( // automatically backport to specified target branches if (options.ci) { if (isEmpty(missingTargetBranches)) { - throw new HandledError({ code: 'no-branches-exception' }); + throw new BackportError({ code: 'no-branches-exception' }); } return missingTargetBranches; @@ -62,7 +62,7 @@ export function getTargetBranchChoices( ).filter((choice) => choice.name !== sourceBranch); if (isEmpty(targetBranchesChoices)) { - throw new HandledError('Missing target branch choices'); + throw new BackportError('Missing target branch choices'); } if (!options.branchLabelMapping) { diff --git a/src/services/git.private.test.ts b/src/lib/git.private.test.ts similarity index 100% rename from src/services/git.private.test.ts rename to src/lib/git.private.test.ts diff --git a/src/services/git.test.ts b/src/lib/git.test.ts similarity index 99% rename from src/services/git.test.ts rename to src/lib/git.test.ts index fd424a21..59cdac9f 100644 --- a/src/services/git.test.ts +++ b/src/lib/git.test.ts @@ -1,7 +1,7 @@ import os from 'os'; import { ValidConfigOptions } from '../options/options'; -import * as childProcess from '../services/child-process-promisified'; import { SpyHelper } from '../types/SpyHelper'; +import * as childProcess from './child-process-promisified'; import { addRemote, getUnstagedFiles, diff --git a/src/services/git.ts b/src/lib/git.ts similarity index 98% rename from src/services/git.ts rename to src/lib/git.ts index 1f1b4cda..f824c340 100644 --- a/src/services/git.ts +++ b/src/lib/git.ts @@ -1,9 +1,9 @@ import { resolve as pathResolve } from 'path'; import { uniq, isEmpty, first, last } from 'lodash'; +import { BackportError } from '../errors/BackportError'; +import { ora } from '../lib/ora'; import { ValidConfigOptions } from '../options/options'; -import { ora } from '../ui/ora'; import { filterNil } from '../utils/filterEmpty'; -import { HandledError } from './HandledError'; import { spawnPromise, SpawnError, @@ -341,7 +341,7 @@ export async function cherrypick({ if (isSpawnError) { // missing `mainline` option if (e.message.includes('is a merge but no -m option was given')) { - throw new HandledError( + throw new BackportError( 'Cherrypick failed because the selected commit was a merge commit. Please try again by specifying the parent with the `mainline` argument:\n\n> backport --mainline\n\nor:\n\n> backport --mainline \n\nOr refer to the git documentation for more information: https://git-scm.com/docs/git-cherry-pick#Documentation/git-cherry-pick.txt---mainlineparent-number' ); } @@ -350,7 +350,7 @@ export async function cherrypick({ if (e.message.includes('The previous cherry-pick is now empty')) { const shortSha = getShortSha(sha); - throw new HandledError( + throw new BackportError( `Cherrypick failed because the selected commit (${shortSha}) is empty. ${ mergedTargetPullRequest?.url ? `It looks like the commit was already backported in ${mergedTargetPullRequest.url}` @@ -360,7 +360,7 @@ export async function cherrypick({ } if (e.message.includes(`bad object ${sha}`)) { - throw new HandledError( + throw new BackportError( `Cherrypick failed because commit "${sha}" was not found` ); } @@ -555,7 +555,7 @@ export async function createBackportBranch({ ); if (isBranchInvalid) { - throw new HandledError( + throw new BackportError( `The branch "${targetBranch}" is invalid or doesn't exist` ); } @@ -623,7 +623,7 @@ export async function pushBackportBranch({ e instanceof SpawnError && e.context.stderr.toLowerCase().includes(`repository not found`) ) { - throw new HandledError( + throw new BackportError( `Error pushing to https://github.com/${repoForkOwner}/${options.repoName}. Repository does not exist. Either fork the repository (https://github.com/${options.repoOwner}/${options.repoName}) or disable fork mode via "--no-fork".\nRead more about fork mode in the docs: https://github.com/sqren/backport/blob/main/docs/configuration.md#fork` ); } @@ -632,7 +632,7 @@ export async function pushBackportBranch({ e instanceof SpawnError && e.context.stderr.includes(`could not read Username for`) ) { - throw new HandledError(`Invalid credentials: ${e.message}`); + throw new BackportError(`Invalid credentials: ${e.message}`); } throw e; diff --git a/src/services/github/commitFormatters.test.ts b/src/lib/github/commitFormatters.test.ts similarity index 100% rename from src/services/github/commitFormatters.test.ts rename to src/lib/github/commitFormatters.test.ts diff --git a/src/services/github/commitFormatters.ts b/src/lib/github/commitFormatters.ts similarity index 100% rename from src/services/github/commitFormatters.ts rename to src/lib/github/commitFormatters.ts diff --git a/src/services/github/v3/addAssigneesToPullRequest.test.ts b/src/lib/github/v3/addAssigneesToPullRequest.test.ts similarity index 100% rename from src/services/github/v3/addAssigneesToPullRequest.test.ts rename to src/lib/github/v3/addAssigneesToPullRequest.test.ts diff --git a/src/services/github/v3/addAssigneesToPullRequest.ts b/src/lib/github/v3/addAssigneesToPullRequest.ts similarity index 96% rename from src/services/github/v3/addAssigneesToPullRequest.ts rename to src/lib/github/v3/addAssigneesToPullRequest.ts index 007539dd..70747b8b 100644 --- a/src/services/github/v3/addAssigneesToPullRequest.ts +++ b/src/lib/github/v3/addAssigneesToPullRequest.ts @@ -1,6 +1,6 @@ import { Octokit } from '@octokit/rest'; +import { ora } from '../../../lib/ora'; import { ValidConfigOptions } from '../../../options/options'; -import { ora } from '../../../ui/ora'; import { logger } from '../../logger'; export async function addAssigneesToPullRequest( diff --git a/src/services/github/v3/addLabelsToPullRequest.ts b/src/lib/github/v3/addLabelsToPullRequest.ts similarity index 95% rename from src/services/github/v3/addLabelsToPullRequest.ts rename to src/lib/github/v3/addLabelsToPullRequest.ts index b4e4c4de..c3494399 100644 --- a/src/services/github/v3/addLabelsToPullRequest.ts +++ b/src/lib/github/v3/addLabelsToPullRequest.ts @@ -1,6 +1,6 @@ import { Octokit } from '@octokit/rest'; +import { ora } from '../../../lib/ora'; import { ValidConfigOptions } from '../../../options/options'; -import { ora } from '../../../ui/ora'; import { logger } from '../../logger'; export async function addLabelsToPullRequest( diff --git a/src/services/github/v3/addReviewersToPullRequest.ts b/src/lib/github/v3/addReviewersToPullRequest.ts similarity index 96% rename from src/services/github/v3/addReviewersToPullRequest.ts rename to src/lib/github/v3/addReviewersToPullRequest.ts index 8af366d3..eda49e27 100644 --- a/src/services/github/v3/addReviewersToPullRequest.ts +++ b/src/lib/github/v3/addReviewersToPullRequest.ts @@ -1,6 +1,6 @@ import { Octokit } from '@octokit/rest'; +import { ora } from '../../../lib/ora'; import { ValidConfigOptions } from '../../../options/options'; -import { ora } from '../../../ui/ora'; import { logger } from '../../logger'; export async function addReviewersToPullRequest( diff --git a/src/services/github/v3/createPullRequest.test.ts b/src/lib/github/v3/createPullRequest.test.ts similarity index 100% rename from src/services/github/v3/createPullRequest.test.ts rename to src/lib/github/v3/createPullRequest.test.ts diff --git a/src/services/github/v3/createPullRequest.ts b/src/lib/github/v3/createPullRequest.ts similarity index 96% rename from src/services/github/v3/createPullRequest.ts rename to src/lib/github/v3/createPullRequest.ts index 98ea015f..8ed7a7d0 100644 --- a/src/services/github/v3/createPullRequest.ts +++ b/src/lib/github/v3/createPullRequest.ts @@ -1,8 +1,8 @@ import { Octokit } from '@octokit/rest'; +import { BackportError } from '../../../errors/BackportError'; +import { ora } from '../../../lib/ora'; import { ValidConfigOptions } from '../../../options/options'; -import { ora } from '../../../ui/ora'; import { PACKAGE_VERSION } from '../../../utils/packageVersion'; -import { HandledError } from '../../HandledError'; import { logger } from '../../logger'; import { Commit } from '../../sourceCommit/parseSourceCommit'; import { getFirstLine, getShortSha } from '../commitFormatters'; @@ -75,7 +75,7 @@ export async function createPullRequest({ } spinner.fail(); - throw new HandledError( + throw new BackportError( //@ts-expect-error `Could not create pull request: ${getGithubV3ErrorMessage(e)}` ); diff --git a/src/services/github/v3/createStatusComment.test.ts b/src/lib/github/v3/createStatusComment.test.ts similarity index 98% rename from src/services/github/v3/createStatusComment.test.ts rename to src/lib/github/v3/createStatusComment.test.ts index bf196971..de795d0d 100644 --- a/src/services/github/v3/createStatusComment.test.ts +++ b/src/lib/github/v3/createStatusComment.test.ts @@ -3,8 +3,8 @@ import { BackportResponse, BackportSuccessResponse, } from '../../../backportRun'; +import { BackportError } from '../../../errors/BackportError'; import { ValidConfigOptions } from '../../../options/options'; -import { HandledError } from '../../HandledError'; import { createStatusComment, getCommentBody } from './createStatusComment'; describe('createStatusComment', () => { @@ -295,7 +295,7 @@ describe('getCommentBody', () => { { status: 'failure', targetBranch: '7.1', - error: new HandledError({ + error: new BackportError({ code: 'merge-conflict-exception', conflictingFiles: ['readme.md'], commitsWithoutBackports: [ @@ -354,7 +354,7 @@ describe('getCommentBody', () => { { status: 'failure', targetBranch: '7.2', - error: new HandledError({ + error: new BackportError({ code: 'merge-conflict-exception', conflictingFiles: ['my-file.txt'], commitsWithoutBackports: [], @@ -409,7 +409,8 @@ describe('getCommentBody', () => { backportResponse: { status: 'aborted', commits: [], - error: new HandledError({ code: 'no-branches-exception' }), + error: new BackportError({ code: 'no-branches-exception' }), + errorMessage: 'my message', } as BackportResponse, }); diff --git a/src/services/github/v3/createStatusComment.ts b/src/lib/github/v3/createStatusComment.ts similarity index 98% rename from src/services/github/v3/createStatusComment.ts rename to src/lib/github/v3/createStatusComment.ts index f4fa0524..cde4c2d4 100644 --- a/src/services/github/v3/createStatusComment.ts +++ b/src/lib/github/v3/createStatusComment.ts @@ -1,9 +1,9 @@ import { Octokit } from '@octokit/rest'; import { BackportResponse } from '../../../backportRun'; +import { BackportError } from '../../../errors/BackportError'; import { ValidConfigOptions } from '../../../options/options'; import { PACKAGE_VERSION } from '../../../utils/packageVersion'; import { redact } from '../../../utils/redact'; -import { HandledError } from '../../HandledError'; import { logger } from '../../logger'; import { getFirstLine } from '../commitFormatters'; @@ -128,7 +128,7 @@ ${manualBackportCommand}${questionsAndLinkToBackport}${packageVersionSection}`; } if ( - result.error instanceof HandledError && + result.error instanceof BackportError && result.error.errorContext.code === 'merge-conflict-exception' ) { const unmergedBackports = diff --git a/src/services/github/v3/getGithubV3ErrorMessage.ts b/src/lib/github/v3/getGithubV3ErrorMessage.ts similarity index 100% rename from src/services/github/v3/getGithubV3ErrorMessage.ts rename to src/lib/github/v3/getGithubV3ErrorMessage.ts diff --git a/src/services/github/v4/FetchPullRequestId.ts b/src/lib/github/v4/FetchPullRequestId.ts similarity index 100% rename from src/services/github/v4/FetchPullRequestId.ts rename to src/lib/github/v4/FetchPullRequestId.ts diff --git a/src/services/github/v4/__snapshots__/throwOnInvalidAccessToken.test.ts.snap b/src/lib/github/v4/__snapshots__/throwOnInvalidAccessToken.test.ts.snap similarity index 100% rename from src/services/github/v4/__snapshots__/throwOnInvalidAccessToken.test.ts.snap rename to src/lib/github/v4/__snapshots__/throwOnInvalidAccessToken.test.ts.snap diff --git a/src/services/github/v4/apiRequestV4.test.ts b/src/lib/github/v4/apiRequestV4.test.ts similarity index 95% rename from src/services/github/v4/apiRequestV4.test.ts rename to src/lib/github/v4/apiRequestV4.test.ts index 8e6210d6..53edafc5 100644 --- a/src/services/github/v4/apiRequestV4.test.ts +++ b/src/lib/github/v4/apiRequestV4.test.ts @@ -1,7 +1,7 @@ import gql from 'graphql-tag'; import nock from 'nock'; +import { BackportError } from '../../../errors/BackportError'; import { mockGqlRequest } from '../../../test/nockHelpers'; -import { HandledError } from '../../HandledError'; import { apiRequestV4 } from './apiRequestV4'; describe('apiRequestV4', () => { @@ -83,7 +83,7 @@ describe('apiRequestV4', () => { }, }) ).rejects.toThrowError( - new HandledError(`some error,some other error (Github API v4)`) + new BackportError(`some error,some other error (Github API v4)`) ); }); }); diff --git a/src/services/github/v4/apiRequestV4.ts b/src/lib/github/v4/apiRequestV4.ts similarity index 96% rename from src/services/github/v4/apiRequestV4.ts rename to src/lib/github/v4/apiRequestV4.ts index c8e0112f..56619bfe 100644 --- a/src/services/github/v4/apiRequestV4.ts +++ b/src/lib/github/v4/apiRequestV4.ts @@ -1,7 +1,7 @@ import axios, { AxiosResponse } from 'axios'; import { DocumentNode } from 'graphql'; import { print } from 'graphql/language/printer'; -import { HandledError } from '../../HandledError'; +import { BackportError } from '../../../errors/BackportError'; import { logger } from '../../logger'; interface GithubError { @@ -114,7 +114,7 @@ export class GithubV4Exception extends Error { } (Github API v4)`; super(message); - Error.captureStackTrace(this, HandledError); + Error.captureStackTrace(this, BackportError); this.name = 'GithubV4Exception'; this.message = message; this.githubResponse = { ...githubResponse, request: undefined }; diff --git a/src/services/github/v4/disablePullRequestAutoMerge.ts b/src/lib/github/v4/disablePullRequestAutoMerge.ts similarity index 100% rename from src/services/github/v4/disablePullRequestAutoMerge.ts rename to src/lib/github/v4/disablePullRequestAutoMerge.ts diff --git a/src/services/github/v4/enablePullRequestAutoMerge.private.test.ts b/src/lib/github/v4/enablePullRequestAutoMerge.private.test.ts similarity index 100% rename from src/services/github/v4/enablePullRequestAutoMerge.private.test.ts rename to src/lib/github/v4/enablePullRequestAutoMerge.private.test.ts diff --git a/src/services/github/v4/enablePullRequestAutoMerge.ts b/src/lib/github/v4/enablePullRequestAutoMerge.ts similarity index 97% rename from src/services/github/v4/enablePullRequestAutoMerge.ts rename to src/lib/github/v4/enablePullRequestAutoMerge.ts index 2f92ee09..0324f301 100644 --- a/src/services/github/v4/enablePullRequestAutoMerge.ts +++ b/src/lib/github/v4/enablePullRequestAutoMerge.ts @@ -1,6 +1,6 @@ import gql from 'graphql-tag'; +import { ora } from '../../../lib/ora'; import { ValidConfigOptions } from '../../../options/options'; -import { ora } from '../../../ui/ora'; import { logger } from '../../logger'; import { fetchPullRequestId } from './FetchPullRequestId'; import { apiRequestV4 } from './apiRequestV4'; diff --git a/src/services/github/v4/fetchAuthorId.private.test.ts b/src/lib/github/v4/fetchAuthorId.private.test.ts similarity index 100% rename from src/services/github/v4/fetchAuthorId.private.test.ts rename to src/lib/github/v4/fetchAuthorId.private.test.ts diff --git a/src/services/github/v4/fetchAuthorId.ts b/src/lib/github/v4/fetchAuthorId.ts similarity index 100% rename from src/services/github/v4/fetchAuthorId.ts rename to src/lib/github/v4/fetchAuthorId.ts diff --git a/src/services/github/v4/fetchCommits/__snapshots__/fetchCommitByPullNumber.private.test.ts.snap b/src/lib/github/v4/fetchCommits/__snapshots__/fetchCommitByPullNumber.private.test.ts.snap similarity index 100% rename from src/services/github/v4/fetchCommits/__snapshots__/fetchCommitByPullNumber.private.test.ts.snap rename to src/lib/github/v4/fetchCommits/__snapshots__/fetchCommitByPullNumber.private.test.ts.snap diff --git a/src/services/github/v4/fetchCommits/__snapshots__/fetchCommitBySha.private.test.ts.snap b/src/lib/github/v4/fetchCommits/__snapshots__/fetchCommitBySha.private.test.ts.snap similarity index 100% rename from src/services/github/v4/fetchCommits/__snapshots__/fetchCommitBySha.private.test.ts.snap rename to src/lib/github/v4/fetchCommits/__snapshots__/fetchCommitBySha.private.test.ts.snap diff --git a/src/services/github/v4/fetchCommits/__snapshots__/fetchCommitsByAuthor.private.test.ts.snap b/src/lib/github/v4/fetchCommits/__snapshots__/fetchCommitsByAuthor.private.test.ts.snap similarity index 100% rename from src/services/github/v4/fetchCommits/__snapshots__/fetchCommitsByAuthor.private.test.ts.snap rename to src/lib/github/v4/fetchCommits/__snapshots__/fetchCommitsByAuthor.private.test.ts.snap diff --git a/src/services/github/v4/fetchCommits/__snapshots__/fetchCommitsByAuthor.test.ts.snap b/src/lib/github/v4/fetchCommits/__snapshots__/fetchCommitsByAuthor.test.ts.snap similarity index 100% rename from src/services/github/v4/fetchCommits/__snapshots__/fetchCommitsByAuthor.test.ts.snap rename to src/lib/github/v4/fetchCommits/__snapshots__/fetchCommitsByAuthor.test.ts.snap diff --git a/src/services/github/v4/fetchCommits/allFetchers.private.test.ts b/src/lib/github/v4/fetchCommits/allFetchers.private.test.ts similarity index 100% rename from src/services/github/v4/fetchCommits/allFetchers.private.test.ts rename to src/lib/github/v4/fetchCommits/allFetchers.private.test.ts diff --git a/src/services/github/v4/fetchCommits/fetchCommitByPullNumber.private.test.ts b/src/lib/github/v4/fetchCommits/fetchCommitByPullNumber.private.test.ts similarity index 100% rename from src/services/github/v4/fetchCommits/fetchCommitByPullNumber.private.test.ts rename to src/lib/github/v4/fetchCommits/fetchCommitByPullNumber.private.test.ts diff --git a/src/services/github/v4/fetchCommits/fetchCommitByPullNumber.ts b/src/lib/github/v4/fetchCommits/fetchCommitByPullNumber.ts similarity index 90% rename from src/services/github/v4/fetchCommits/fetchCommitByPullNumber.ts rename to src/lib/github/v4/fetchCommits/fetchCommitByPullNumber.ts index f3fe7d8d..876d785d 100644 --- a/src/services/github/v4/fetchCommits/fetchCommitByPullNumber.ts +++ b/src/lib/github/v4/fetchCommits/fetchCommitByPullNumber.ts @@ -1,6 +1,6 @@ import gql from 'graphql-tag'; +import { BackportError } from '../../../../errors/BackportError'; import { ValidConfigOptions } from '../../../../options/options'; -import { HandledError } from '../../../HandledError'; import { swallowMissingConfigFileException } from '../../../remoteConfig'; import { Commit, @@ -64,12 +64,12 @@ export async function fetchCommitByPullNumber(options: { const pullRequestNode = res.repository.pullRequest; if (!pullRequestNode) { - throw new HandledError(`The PR #${pullNumber} does not exist`); + throw new BackportError(`The PR #${pullNumber} does not exist`); } const sourceCommit = pullRequestNode.mergeCommit; if (sourceCommit === null) { - throw new HandledError(`The PR #${pullNumber} is not merged`); + throw new BackportError(`The PR #${pullNumber} is not merged`); } return parseSourceCommit({ options, sourceCommit }); } diff --git a/src/services/github/v4/fetchCommits/fetchCommitBySha.private.test.ts b/src/lib/github/v4/fetchCommits/fetchCommitBySha.private.test.ts similarity index 100% rename from src/services/github/v4/fetchCommits/fetchCommitBySha.private.test.ts rename to src/lib/github/v4/fetchCommits/fetchCommitBySha.private.test.ts diff --git a/src/services/github/v4/fetchCommits/fetchCommitBySha.ts b/src/lib/github/v4/fetchCommits/fetchCommitBySha.ts similarity index 95% rename from src/services/github/v4/fetchCommits/fetchCommitBySha.ts rename to src/lib/github/v4/fetchCommits/fetchCommitBySha.ts index 0017057c..76c242fa 100644 --- a/src/services/github/v4/fetchCommits/fetchCommitBySha.ts +++ b/src/lib/github/v4/fetchCommits/fetchCommitBySha.ts @@ -1,6 +1,6 @@ import gql from 'graphql-tag'; +import { BackportError } from '../../../../errors/BackportError'; import { ValidConfigOptions } from '../../../../options/options'; -import { HandledError } from '../../../HandledError'; import { swallowMissingConfigFileException } from '../../../remoteConfig'; import { Commit, @@ -59,7 +59,7 @@ export async function fetchCommitBySha(options: { const sourceCommit = res.repository.object; if (!sourceCommit) { - throw new HandledError( + throw new BackportError( `No commit found on branch "${sourceBranch}" with sha "${sha}"` ); } diff --git a/src/services/github/v4/fetchCommits/fetchCommitsByAuthor.private.test.ts b/src/lib/github/v4/fetchCommits/fetchCommitsByAuthor.private.test.ts similarity index 100% rename from src/services/github/v4/fetchCommits/fetchCommitsByAuthor.private.test.ts rename to src/lib/github/v4/fetchCommits/fetchCommitsByAuthor.private.test.ts diff --git a/src/services/github/v4/fetchCommits/fetchCommitsByAuthor.test.ts b/src/lib/github/v4/fetchCommits/fetchCommitsByAuthor.test.ts similarity index 100% rename from src/services/github/v4/fetchCommits/fetchCommitsByAuthor.test.ts rename to src/lib/github/v4/fetchCommits/fetchCommitsByAuthor.test.ts diff --git a/src/services/github/v4/fetchCommits/fetchCommitsByAuthor.ts b/src/lib/github/v4/fetchCommits/fetchCommitsByAuthor.ts similarity index 97% rename from src/services/github/v4/fetchCommits/fetchCommitsByAuthor.ts rename to src/lib/github/v4/fetchCommits/fetchCommitsByAuthor.ts index d32d2036..642fe20b 100644 --- a/src/services/github/v4/fetchCommits/fetchCommitsByAuthor.ts +++ b/src/lib/github/v4/fetchCommits/fetchCommitsByAuthor.ts @@ -1,9 +1,9 @@ import gql from 'graphql-tag'; import { isEmpty, uniqBy, orderBy } from 'lodash'; +import { BackportError } from '../../../../errors/BackportError'; import { ValidConfigOptions } from '../../../../options/options'; import { filterNil } from '../../../../utils/filterEmpty'; import { filterUnmergedCommits } from '../../../../utils/filterUnmergedCommits'; -import { HandledError } from '../../../HandledError'; import { swallowMissingConfigFileException } from '../../../remoteConfig'; import { Commit, @@ -131,7 +131,7 @@ export async function fetchCommitsByAuthor(options: { // we only need to check if the first item is `null` (if the first is `null` they all are) if (responses[0].repository.ref === null) { - throw new HandledError( + throw new BackportError( `The upstream branch "${sourceBranch}" does not exist. Try specifying a different branch with "--source-branch "` ); } @@ -154,7 +154,7 @@ export async function fetchCommitsByAuthor(options: { ? `There are no commits by "${options.author}" in this repository${pathText}. Try with \`--all\` for commits by all users or \`--author=\` for commits from a specific user` : `There are no commits in this repository${pathText}`; - throw new HandledError(errorText); + throw new BackportError(errorText); } const commitsUnique = uniqBy(commits, (c) => c.sourceCommit.sha); diff --git a/src/services/github/v4/fetchCommits/fetchPullRequestBySearchQuery.private.test.ts b/src/lib/github/v4/fetchCommits/fetchPullRequestBySearchQuery.private.test.ts similarity index 100% rename from src/services/github/v4/fetchCommits/fetchPullRequestBySearchQuery.private.test.ts rename to src/lib/github/v4/fetchCommits/fetchPullRequestBySearchQuery.private.test.ts diff --git a/src/services/github/v4/fetchCommits/fetchPullRequestsBySearchQuery.ts b/src/lib/github/v4/fetchCommits/fetchPullRequestsBySearchQuery.ts similarity index 96% rename from src/services/github/v4/fetchCommits/fetchPullRequestsBySearchQuery.ts rename to src/lib/github/v4/fetchCommits/fetchPullRequestsBySearchQuery.ts index 74b93f51..f47191ab 100644 --- a/src/services/github/v4/fetchCommits/fetchPullRequestsBySearchQuery.ts +++ b/src/lib/github/v4/fetchCommits/fetchPullRequestsBySearchQuery.ts @@ -1,7 +1,7 @@ import gql from 'graphql-tag'; import { isEmpty } from 'lodash'; +import { BackportError } from '../../../../errors/BackportError'; import { filterUnmergedCommits } from '../../../../utils/filterUnmergedCommits'; -import { HandledError } from '../../../HandledError'; import { swallowMissingConfigFileException } from '../../../remoteConfig'; import { Commit, @@ -84,7 +84,7 @@ export async function fetchPullRequestsBySearchQuery(options: { ? `No commits found for query:\n ${searchQuery}\n\nUse \`--all\` to see commits by all users or \`--author=\` for commits from a specific user` : `No commits found for query:\n ${searchQuery}`; - throw new HandledError(errorText); + throw new BackportError(errorText); } if (options.onlyMissing) { diff --git a/src/services/github/v4/fetchExistingPullRequest.private.test.ts b/src/lib/github/v4/fetchExistingPullRequest.private.test.ts similarity index 100% rename from src/services/github/v4/fetchExistingPullRequest.private.test.ts rename to src/lib/github/v4/fetchExistingPullRequest.private.test.ts diff --git a/src/services/github/v4/fetchExistingPullRequest.ts b/src/lib/github/v4/fetchExistingPullRequest.ts similarity index 100% rename from src/services/github/v4/fetchExistingPullRequest.ts rename to src/lib/github/v4/fetchExistingPullRequest.ts diff --git a/src/services/github/v4/fetchPullRequestAutoMergeMethod.ts b/src/lib/github/v4/fetchPullRequestAutoMergeMethod.ts similarity index 100% rename from src/services/github/v4/fetchPullRequestAutoMergeMethod.ts rename to src/lib/github/v4/fetchPullRequestAutoMergeMethod.ts diff --git a/src/services/github/v4/fetchRemoteProjectConfig.private.test.ts b/src/lib/github/v4/fetchRemoteProjectConfig.private.test.ts similarity index 100% rename from src/services/github/v4/fetchRemoteProjectConfig.private.test.ts rename to src/lib/github/v4/fetchRemoteProjectConfig.private.test.ts diff --git a/src/services/github/v4/fetchRemoteProjectConfig.ts b/src/lib/github/v4/fetchRemoteProjectConfig.ts similarity index 100% rename from src/services/github/v4/fetchRemoteProjectConfig.ts rename to src/lib/github/v4/fetchRemoteProjectConfig.ts diff --git a/src/services/github/v4/getOptionsFromGithub/getOptionsFromGithub.private.test.ts b/src/lib/github/v4/getOptionsFromGithub/getOptionsFromGithub.private.test.ts similarity index 100% rename from src/services/github/v4/getOptionsFromGithub/getOptionsFromGithub.private.test.ts rename to src/lib/github/v4/getOptionsFromGithub/getOptionsFromGithub.private.test.ts diff --git a/src/services/github/v4/getOptionsFromGithub/getOptionsFromGithub.ts b/src/lib/github/v4/getOptionsFromGithub/getOptionsFromGithub.ts similarity index 97% rename from src/services/github/v4/getOptionsFromGithub/getOptionsFromGithub.ts rename to src/lib/github/v4/getOptionsFromGithub/getOptionsFromGithub.ts index e151a20b..2fd2b8b6 100644 --- a/src/services/github/v4/getOptionsFromGithub/getOptionsFromGithub.ts +++ b/src/lib/github/v4/getOptionsFromGithub/getOptionsFromGithub.ts @@ -1,5 +1,5 @@ +import { BackportError } from '../../../../errors/BackportError'; import { ConfigFileOptions } from '../../../../options/ConfigOptions'; -import { HandledError } from '../../../HandledError'; import { getLocalConfigFileCommitDate, isLocalConfigFileUntracked, @@ -52,7 +52,7 @@ export async function getOptionsFromGithub(options: { // it is not possible to have a branch named "backport" if (res.repository.illegalBackportBranch) { - throw new HandledError( + throw new BackportError( 'You must delete the branch "backport" to continue. See https://github.com/sqren/backport/issues/155 for details' ); } diff --git a/src/services/github/v4/getOptionsFromGithub/query.ts b/src/lib/github/v4/getOptionsFromGithub/query.ts similarity index 100% rename from src/services/github/v4/getOptionsFromGithub/query.ts rename to src/lib/github/v4/getOptionsFromGithub/query.ts diff --git a/src/services/github/v4/getRepoOwnerAndNameFromGitRemotes.private.test.ts b/src/lib/github/v4/getRepoOwnerAndNameFromGitRemotes.private.test.ts similarity index 100% rename from src/services/github/v4/getRepoOwnerAndNameFromGitRemotes.private.test.ts rename to src/lib/github/v4/getRepoOwnerAndNameFromGitRemotes.private.test.ts diff --git a/src/services/github/v4/getRepoOwnerAndNameFromGitRemotes.ts b/src/lib/github/v4/getRepoOwnerAndNameFromGitRemotes.ts similarity index 100% rename from src/services/github/v4/getRepoOwnerAndNameFromGitRemotes.ts rename to src/lib/github/v4/getRepoOwnerAndNameFromGitRemotes.ts diff --git a/src/services/github/v4/mocks/commitsByAuthorMock.ts b/src/lib/github/v4/mocks/commitsByAuthorMock.ts similarity index 100% rename from src/services/github/v4/mocks/commitsByAuthorMock.ts rename to src/lib/github/v4/mocks/commitsByAuthorMock.ts diff --git a/src/services/github/v4/throwOnInvalidAccessToken.test.ts b/src/lib/github/v4/throwOnInvalidAccessToken.test.ts similarity index 100% rename from src/services/github/v4/throwOnInvalidAccessToken.test.ts rename to src/lib/github/v4/throwOnInvalidAccessToken.test.ts diff --git a/src/services/github/v4/throwOnInvalidAccessToken.ts b/src/lib/github/v4/throwOnInvalidAccessToken.ts similarity index 91% rename from src/services/github/v4/throwOnInvalidAccessToken.ts rename to src/lib/github/v4/throwOnInvalidAccessToken.ts index afc6c733..1272af3e 100644 --- a/src/services/github/v4/throwOnInvalidAccessToken.ts +++ b/src/lib/github/v4/throwOnInvalidAccessToken.ts @@ -1,6 +1,6 @@ import { isEmpty, difference } from 'lodash'; +import { BackportError } from '../../../errors/BackportError'; import { maybe } from '../../../utils/maybe'; -import { HandledError } from '../../HandledError'; import { getGlobalConfigPath } from '../../env'; import { GithubV4Exception } from './apiRequestV4'; @@ -42,13 +42,13 @@ export function throwOnInvalidAccessToken({ // user does not have permission to the repo if (!hasRequiredScopes) { - throw new HandledError( + throw new BackportError( `You do not have access to the repository "${repoOwner}/${repoName}". Please make sure your access token has the required scopes.\n\nRequired scopes: ${requiredScopes}\nAccess token scopes: ${grantedScopes}` ); } // repo does not exist - throw new HandledError( + throw new BackportError( `The repository "${repoOwner}/${repoName}" doesn't exist` ); } @@ -61,7 +61,7 @@ export function throwOnInvalidAccessToken({ // user does not have permissions if (repoAccessForbidden && ssoAuthUrl) { - throw new HandledError( + throw new BackportError( `Please follow the link to authorize your personal access token with SSO:\n\n${ssoAuthUrl}` ); } @@ -69,7 +69,7 @@ export function throwOnInvalidAccessToken({ } case 401: - throw new HandledError( + throw new BackportError( `Please check your access token and make sure it is valid.\nConfig: ${getGlobalConfigPath()}` ); diff --git a/src/services/logger.ts b/src/lib/logger.ts similarity index 100% rename from src/services/logger.ts rename to src/lib/logger.ts diff --git a/src/ui/ora.ts b/src/lib/ora.ts similarity index 100% rename from src/ui/ora.ts rename to src/lib/ora.ts diff --git a/src/services/prompt.test.ts b/src/lib/prompt.test.ts similarity index 100% rename from src/services/prompt.test.ts rename to src/lib/prompt.test.ts diff --git a/src/services/prompts.ts b/src/lib/prompts.ts similarity index 100% rename from src/services/prompts.ts rename to src/lib/prompts.ts diff --git a/src/services/remoteConfig.ts b/src/lib/remoteConfig.ts similarity index 100% rename from src/services/remoteConfig.ts rename to src/lib/remoteConfig.ts diff --git a/src/services/sequentially.ts b/src/lib/sequentially.ts similarity index 100% rename from src/services/sequentially.ts rename to src/lib/sequentially.ts diff --git a/src/ui/setupRepo.test.ts b/src/lib/setupRepo.test.ts similarity index 98% rename from src/ui/setupRepo.test.ts rename to src/lib/setupRepo.test.ts index 1e304941..b92621f2 100644 --- a/src/ui/setupRepo.test.ts +++ b/src/lib/setupRepo.test.ts @@ -1,10 +1,10 @@ import os from 'os'; import del from 'del'; import { ValidConfigOptions } from '../options/options'; -import * as childProcess from '../services/child-process-promisified'; -import * as gitModule from '../services/git'; import { getOraMock } from '../test/mocks'; import { SpyHelper } from '../types/SpyHelper'; +import * as childProcess from './child-process-promisified'; +import * as gitModule from './git'; import { setupRepo } from './setupRepo'; describe('setupRepo', () => { diff --git a/src/ui/setupRepo.ts b/src/lib/setupRepo.ts similarity index 93% rename from src/ui/setupRepo.ts rename to src/lib/setupRepo.ts index 9344ab36..77ba6c61 100644 --- a/src/ui/setupRepo.ts +++ b/src/lib/setupRepo.ts @@ -1,7 +1,7 @@ import del = require('del'); +import { BackportError } from '../errors/BackportError'; import { ValidConfigOptions } from '../options/options'; -import { HandledError } from '../services/HandledError'; -import { getRepoPath } from '../services/env'; +import { getRepoPath } from './env'; import { addRemote, cloneRepo, @@ -9,7 +9,7 @@ import { getGitProjectRootPath, getLocalSourceRepoPath, getRemoteUrl, -} from '../services/git'; +} from './git'; import { ora } from './ora'; export async function setupRepo(options: ValidConfigOptions) { @@ -17,7 +17,7 @@ export async function setupRepo(options: ValidConfigOptions) { const isAlreadyCloned = await getIsRepoCloned(options); if (options.cwd.includes(repoPath)) { - throw new HandledError( + throw new BackportError( `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` ); } diff --git a/src/services/sourceCommit/getExpectedTargetPullRequests.test.ts b/src/lib/sourceCommit/getExpectedTargetPullRequests.test.ts similarity index 100% rename from src/services/sourceCommit/getExpectedTargetPullRequests.test.ts rename to src/lib/sourceCommit/getExpectedTargetPullRequests.test.ts diff --git a/src/services/sourceCommit/getExpectedTargetPullRequests.ts b/src/lib/sourceCommit/getExpectedTargetPullRequests.ts similarity index 100% rename from src/services/sourceCommit/getExpectedTargetPullRequests.ts rename to src/lib/sourceCommit/getExpectedTargetPullRequests.ts diff --git a/src/services/sourceCommit/getMockSourceCommit.ts b/src/lib/sourceCommit/getMockSourceCommit.ts similarity index 100% rename from src/services/sourceCommit/getMockSourceCommit.ts rename to src/lib/sourceCommit/getMockSourceCommit.ts diff --git a/src/services/sourceCommit/parseSourceCommit.test.ts b/src/lib/sourceCommit/parseSourceCommit.test.ts similarity index 100% rename from src/services/sourceCommit/parseSourceCommit.test.ts rename to src/lib/sourceCommit/parseSourceCommit.test.ts diff --git a/src/services/sourceCommit/parseSourceCommit.ts b/src/lib/sourceCommit/parseSourceCommit.ts similarity index 100% rename from src/services/sourceCommit/parseSourceCommit.ts rename to src/lib/sourceCommit/parseSourceCommit.ts diff --git a/src/options/config/globalConfig.ts b/src/options/config/globalConfig.ts index 75059c3c..c80c7667 100644 --- a/src/options/config/globalConfig.ts +++ b/src/options/config/globalConfig.ts @@ -1,7 +1,7 @@ import { chmod, writeFile } from 'fs/promises'; import makeDir from 'make-dir'; -import { HandledError } from '../../services/HandledError'; -import { getBackportDirPath, getGlobalConfigPath } from '../../services/env'; +import { BackportError } from '../../errors/BackportError'; +import { getBackportDirPath, getGlobalConfigPath } from '../../lib/env'; import { isErrnoError } from '../../utils/isErrnoError'; import { ConfigFileOptions } from '../ConfigOptions'; import { readConfigFile } from './readConfigFile'; @@ -52,7 +52,7 @@ export async function createGlobalConfigIfNotExist( // handle error if folder does not exist const FOLDER_NOT_EXISTS = 'ENOENT'; if (e.code === FOLDER_NOT_EXISTS) { - throw new HandledError( + throw new BackportError( `The .backport folder (${globalConfigPath}) does not exist. ` ); } diff --git a/src/options/config/readConfigFile.ts b/src/options/config/readConfigFile.ts index 874e8fee..f8bd6f27 100644 --- a/src/options/config/readConfigFile.ts +++ b/src/options/config/readConfigFile.ts @@ -1,6 +1,6 @@ import { readFile } from 'fs/promises'; import stripJsonComments from 'strip-json-comments'; -import { HandledError } from '../../services/HandledError'; +import { BackportError } from '../../errors/BackportError'; import { excludeUndefined } from '../../utils/excludeUndefined'; import { ConfigFileOptions } from '../ConfigOptions'; @@ -13,7 +13,7 @@ export async function readConfigFile( try { return withConfigMigrations(JSON.parse(configWithoutComments)); } catch (e) { - throw new HandledError( + throw new BackportError( `"${filepath}" contains invalid JSON:\n\n${fileContents}` ); } diff --git a/src/options/options.test.ts b/src/options/options.test.ts index 24bac58a..7788af68 100644 --- a/src/options/options.test.ts +++ b/src/options/options.test.ts @@ -1,10 +1,10 @@ import fs from 'fs/promises'; import os from 'os'; import nock from 'nock'; -import * as git from '../services/git'; -import { GithubConfigOptionsResponse } from '../services/github/v4/getOptionsFromGithub/query'; -import { RepoOwnerAndNameResponse } from '../services/github/v4/getRepoOwnerAndNameFromGitRemotes'; -import * as logger from '../services/logger'; +import * as git from '../lib/git'; +import { GithubConfigOptionsResponse } from '../lib/github/v4/getOptionsFromGithub/query'; +import { RepoOwnerAndNameResponse } from '../lib/github/v4/getRepoOwnerAndNameFromGitRemotes'; +import * as logger from '../lib/logger'; import { mockConfigFiles } from '../test/mockConfigFiles'; import { mockGqlRequest } from '../test/nockHelpers'; import { ConfigFileOptions } from './ConfigOptions'; diff --git a/src/options/options.ts b/src/options/options.ts index c5a3f8f8..1dfaedee 100644 --- a/src/options/options.ts +++ b/src/options/options.ts @@ -1,12 +1,12 @@ import { isEmpty } from 'lodash'; -import { HandledError } from '../services/HandledError'; -import { getGlobalConfigPath } from '../services/env'; +import { BackportError } from '../errors/BackportError'; +import { getGlobalConfigPath } from '../lib/env'; import { getOptionsFromGithub, OptionsFromGithub, -} from '../services/github/v4/getOptionsFromGithub/getOptionsFromGithub'; -import { getRepoOwnerAndNameFromGitRemotes } from '../services/github/v4/getRepoOwnerAndNameFromGitRemotes'; -import { setAccessToken } from './../services/logger'; +} from '../lib/github/v4/getOptionsFromGithub/getOptionsFromGithub'; +import { getRepoOwnerAndNameFromGitRemotes } from '../lib/github/v4/getRepoOwnerAndNameFromGitRemotes'; +import { setAccessToken } from '../lib/logger'; import { ConfigFileOptions, TargetBranchChoiceOrString } from './ConfigOptions'; import { getOptionsFromCliArgs, OptionsFromCliArgs } from './cliArgs'; import { @@ -125,13 +125,13 @@ async function getRequiredOptions(combined: OptionsFromConfigAndCli) { // require access token if (!accessToken) { if (combined.ci) { - throw new HandledError( + throw new BackportError( `Access token missing. It must be explicitly supplied when using "--ci" option. Example: --access-token very-secret` ); } const globalConfigPath = getGlobalConfigPath(); - throw new HandledError( + throw new BackportError( `Please update your config file: "${globalConfigPath}".\nIt must contain a valid "accessToken".\n\nRead more: ${GLOBAL_CONFIG_DOCS_LINK}` ); } @@ -144,7 +144,7 @@ async function getRequiredOptions(combined: OptionsFromConfigAndCli) { }); if (!gitRemote.repoName || !gitRemote.repoOwner) { - throw new HandledError( + throw new BackportError( `Please specify a repository: "--repo elastic/kibana".\n\nRead more: ${PROJECT_CONFIG_DOCS_LINK}` ); } @@ -166,7 +166,7 @@ function throwForRequiredOptions( // this is primarily necessary on CI where `targetBranches` and `targetBranchChoices` and not given isEmpty(options.branchLabelMapping) ) { - throw new HandledError( + throw new BackportError( `Please specify a target branch: "--branch 6.1".\n\nRead more: ${PROJECT_CONFIG_DOCS_LINK}` ); } @@ -199,7 +199,7 @@ function throwForRequiredOptions( optionKeys.forEach((optionName) => { const option = options[optionName] as string; if (option === '') { - throw new HandledError(`"${optionName}" cannot be empty!`); + throw new BackportError(`"${optionName}" cannot be empty!`); } }); } diff --git a/src/runSequentially.ts b/src/runSequentially.ts index 48e46930..77ff39f9 100755 --- a/src/runSequentially.ts +++ b/src/runSequentially.ts @@ -1,29 +1,32 @@ +import { BackportError } from './errors/BackportError'; +import { cherrypickAndCreateTargetPullRequest } from './lib/cherrypickAndCreateTargetPullRequest'; +import { GitConfigAuthor } from './lib/getGitConfigAuthor'; +import { logger, consoleLog } from './lib/logger'; +import { sequentially } from './lib/sequentially'; +import { Commit } from './lib/sourceCommit/parseSourceCommit'; import { ValidConfigOptions } from './options/options'; -import { HandledError } from './services/HandledError'; -import { logger, consoleLog } from './services/logger'; -import { sequentially } from './services/sequentially'; -import { Commit } from './services/sourceCommit/parseSourceCommit'; -import { cherrypickAndCreateTargetPullRequest } from './ui/cherrypickAndCreateTargetPullRequest'; -import { GitConfigAuthor } from './ui/getGitConfigAuthor'; -export type Result = - | { - status: 'success'; - didUpdate: boolean; - targetBranch: string; - pullRequestUrl: string; - pullRequestNumber: number; - } - | { - status: 'handled-error'; - targetBranch: string; - error: HandledError; - } - | { - status: 'unhandled-error'; - targetBranch: string; - error: Error; - }; +export type SuccessResult = { + status: 'success'; + didUpdate: boolean; + targetBranch: string; + pullRequestUrl: string; + pullRequestNumber: number; +}; + +export type HandledErrorResult = { + status: 'handled-error'; + targetBranch: string; + error: BackportError; +}; + +export type UnhandledErrorResult = { + status: 'unhandled-error'; + targetBranch: string; + error: Error; +}; + +export type Result = SuccessResult | HandledErrorResult | UnhandledErrorResult; export async function runSequentially({ options, @@ -59,7 +62,7 @@ export async function runSequentially({ pullRequestNumber: number, }); } catch (e) { - const isHandledError = e instanceof HandledError; + const isHandledError = e instanceof BackportError; if (isHandledError) { results.push({ targetBranch, diff --git a/src/scripts/postinstall.test.ts b/src/scripts/postinstall.test.ts index f983878a..53c091eb 100644 --- a/src/scripts/postinstall.test.ts +++ b/src/scripts/postinstall.test.ts @@ -1,6 +1,6 @@ import os from 'os'; +import * as logger from '../lib/logger'; import * as globalConfig from '../options/config/globalConfig'; -import * as logger from '../services/logger'; import { postinstall } from './postinstall'; describe('postinstall', () => { diff --git a/src/scripts/postinstall.ts b/src/scripts/postinstall.ts index 891c25c4..1aa9cbef 100644 --- a/src/scripts/postinstall.ts +++ b/src/scripts/postinstall.ts @@ -1,6 +1,6 @@ +import { getGlobalConfigPath } from '../lib/env'; +import { consoleLog } from '../lib/logger'; import { createGlobalConfigAndFolderIfNotExist } from '../options/config/globalConfig'; -import { getGlobalConfigPath } from '../services/env'; -import { consoleLog } from '../services/logger'; export async function postinstall() { try { diff --git a/src/test/e2e/cli/commit-author.private.test.ts b/src/test/e2e/cli/commit-author.private.test.ts index 4b1d84d1..d75a7989 100644 --- a/src/test/e2e/cli/commit-author.private.test.ts +++ b/src/test/e2e/cli/commit-author.private.test.ts @@ -1,6 +1,6 @@ import fs from 'fs/promises'; import { ConfigFileOptions } from '../../../entrypoint.module'; -import { exec } from '../../../services/child-process-promisified'; +import { exec } from '../../../lib/child-process-promisified'; import { getDevAccessToken } from '../../private/getDevAccessToken'; import { getSandboxPath, resetSandbox } from '../../sandbox'; import { runBackportViaCli } from './runBackportViaCli'; diff --git a/src/test/e2e/cli/different-merge-strategies.private.test.ts b/src/test/e2e/cli/different-merge-strategies.private.test.ts index aa7f948c..2cbe5fda 100644 --- a/src/test/e2e/cli/different-merge-strategies.private.test.ts +++ b/src/test/e2e/cli/different-merge-strategies.private.test.ts @@ -1,4 +1,4 @@ -import { exec } from '../../../services/child-process-promisified'; +import { exec } from '../../../lib/child-process-promisified'; import { getDevAccessToken } from '../../private/getDevAccessToken'; import { getSandboxPath, resetSandbox } from '../../sandbox'; import { runBackportViaCli } from './runBackportViaCli'; diff --git a/src/test/e2e/cli/entrypoint.cli.private.test.ts b/src/test/e2e/cli/entrypoint.cli.private.test.ts index c224943b..8c900a06 100644 --- a/src/test/e2e/cli/entrypoint.cli.private.test.ts +++ b/src/test/e2e/cli/entrypoint.cli.private.test.ts @@ -3,8 +3,8 @@ import { BackportFailureResponse, BackportSuccessResponse, } from '../../../backportRun'; -import { exec } from '../../../services/child-process-promisified'; -import { getBackportDirPath } from '../../../services/env'; +import { exec } from '../../../lib/child-process-promisified'; +import { getBackportDirPath } from '../../../lib/env'; import * as packageVersion from '../../../utils/packageVersion'; import { getDevAccessToken } from '../../private/getDevAccessToken'; import { getSandboxPath, resetSandbox } from '../../sandbox'; @@ -349,7 +349,7 @@ describe('entrypoint cli', () => { code: 'message-only-exception', message: 'The branch "foobar" is invalid or doesn\'t exist', }, - name: 'HandledError', + name: 'BackportError', }, status: 'handled-error', targetBranch: 'foobar', @@ -432,7 +432,7 @@ describe('entrypoint cli', () => { code: 'message-only-exception', message: 'The PR #12 is not merged', }, - name: 'HandledError', + name: 'BackportError', }); }); @@ -448,7 +448,7 @@ describe('entrypoint cli', () => { expect(backportResult.status).toBe('aborted'); expect(backportResult.error).toEqual({ errorContext: { code: 'no-branches-exception' }, - name: 'HandledError', + name: 'BackportError', }); }); }); diff --git a/src/test/e2e/cli/test-that-repo-can-be-cloned.private.test.ts b/src/test/e2e/cli/test-that-repo-can-be-cloned.private.test.ts index e4eb2895..cd198070 100644 --- a/src/test/e2e/cli/test-that-repo-can-be-cloned.private.test.ts +++ b/src/test/e2e/cli/test-that-repo-can-be-cloned.private.test.ts @@ -1,4 +1,4 @@ -import { exec } from '../../../services/child-process-promisified'; +import { exec } from '../../../lib/child-process-promisified'; import { getDevAccessToken } from '../../private/getDevAccessToken'; import { getSandboxPath, resetSandbox } from '../../sandbox'; import { runBackportViaCli } from './runBackportViaCli'; diff --git a/src/test/e2e/module/entrypoint.module.mutation.test.ts b/src/test/e2e/module/entrypoint.module.mutation.test.ts index a40c9e3e..ef5ade2a 100644 --- a/src/test/e2e/module/entrypoint.module.mutation.test.ts +++ b/src/test/e2e/module/entrypoint.module.mutation.test.ts @@ -1,6 +1,6 @@ import { Octokit } from '@octokit/rest'; import { BackportResponse, backportRun } from '../../../entrypoint.module'; -import { getShortSha } from '../../../services/github/commitFormatters'; +import { getShortSha } from '../../../lib/github/commitFormatters'; import { getDevAccessToken } from '../../private/getDevAccessToken'; import { getSandboxPath, resetSandbox } from '../../sandbox'; diff --git a/src/test/setupFiles/automatic-mocks.ts b/src/test/setupFiles/automatic-mocks.ts index 1664fe9e..06491b1a 100644 --- a/src/test/setupFiles/automatic-mocks.ts +++ b/src/test/setupFiles/automatic-mocks.ts @@ -30,7 +30,7 @@ jest.mock('del', () => { mockOra(); -jest.mock('../../services/logger', () => { +jest.mock('../../lib/logger', () => { const spy = jest.fn(); const logger = { spy: spy, From 10290f348a96bce666ec8616eff5327a2396aab8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=B8ren=20Louv-Jansen?= Date: Sat, 12 Mar 2022 00:08:07 +0100 Subject: [PATCH 02/15] =?UTF-8?q?Rename=20`=E2=80=94ci`=20to=20`=E2=80=94i?= =?UTF-8?q?nteractive`?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 8 +- src/backportRun.ts | 73 ++++--- src/entrypoint.cli.ts | 7 +- src/entrypoint.module.private.test.ts | 6 +- src/entrypoint.module.ts | 2 +- ...errypickAndCreateTargetPullRequest.test.ts | 1 + .../cherrypickAndCreateTargetPullRequest.ts | 13 +- src/lib/getCommits.ts | 6 +- src/lib/getTargetBranches.test.ts | 18 +- src/lib/getTargetBranches.ts | 6 +- src/lib/git.ts | 6 +- .../github/v3/addAssigneesToPullRequest.ts | 4 +- src/lib/github/v3/addLabelsToPullRequest.ts | 4 +- .../github/v3/addReviewersToPullRequest.ts | 4 +- src/lib/github/v3/createPullRequest.ts | 2 +- src/lib/github/v3/createStatusComment.test.ts | 30 +-- src/lib/github/v3/createStatusComment.ts | 11 +- .../github/v4/enablePullRequestAutoMerge.ts | 2 +- src/lib/logger.ts | 10 +- src/lib/ora.ts | 4 +- src/lib/setupRepo.test.ts | 5 + src/lib/setupRepo.ts | 2 +- src/options/ConfigOptions.ts | 2 +- src/options/cliArgs.test.ts | 91 ++++++++- src/options/cliArgs.ts | 33 ++-- src/options/config/config.test.ts | 1 - src/options/config/config.ts | 11 +- src/options/config/globalConfig.ts | 12 +- src/options/options.test.ts | 179 ++++++++++++------ src/options/options.ts | 25 +-- .../e2e/cli/entrypoint.cli.private.test.ts | 55 ++++-- ...leUnbackportedPullRequests.private.test.ts | 2 +- 32 files changed, 409 insertions(+), 226 deletions(-) diff --git a/README.md b/README.md index 8ee5bfd4..6431517a 100644 --- a/README.md +++ b/README.md @@ -91,13 +91,13 @@ See [configuration.md](https://github.com/sqren/backport/blob/main/docs/configur | --author | | Filter commits by Github username. Opposite of `--all` | _Current user_ | | --auto-assign | | Assign current user to the target PR | false | | --branch | -b | Target branch to backport to | | -| --ci | | Disable interactive prompts | false | | --config-file | | Custom path to project config file (.backportrc.json) | | | --dir | | Clone repository into custom directory | ~/.backport/repositories/ | | --dry-run | | Perform backport without pushing to Github | false | | --editor | | Editor (eg. `code`) to open and resolve conflicts | nano | | --fork | | Create backports in fork repo | true | | --git-hostname | | Hostname for Git | github.com | +| --interactive | | Enable interactive prompts | true | | --mainline | | Parent id of merge commit | 1 | | --max-number | --number, -n | Number of commits to choose from | 10 | | --multiple | | Multi-select for commits and branches | false | @@ -153,8 +153,8 @@ Filter commits by pull request number `sha` _string_
Filter commits by commit sha -`ci` _boolean_
-Enabling this will disable the interactive prompts +`interactive` _boolean_
+Enable interactive prompts #### Example @@ -167,7 +167,7 @@ const result = await backportRun({ repoName: 'kibana', repoOwner: 'elastic', pullNumber: 121633, - ci: true, + interactive: false, }, }); diff --git a/src/backportRun.ts b/src/backportRun.ts index b9b4d61c..d7a04eae 100755 --- a/src/backportRun.ts +++ b/src/backportRun.ts @@ -1,5 +1,4 @@ import chalk from 'chalk'; -import yargsParser from 'yargs-parser'; import { BackportError } from './errors/BackportError'; import { getLogfilePath } from './lib/env'; import { getCommits } from './lib/getCommits'; @@ -12,8 +11,12 @@ import { ora } from './lib/ora'; import { setupRepo } from './lib/setupRepo'; import { Commit } from './lib/sourceCommit/parseSourceCommit'; import { ConfigFileOptions } from './options/ConfigOptions'; -import { CliError } from './options/cliArgs'; -import { getOptions, ValidConfigOptions } from './options/options'; +import { getOptionsFromCliArgs, OptionsFromCliArgs } from './options/cliArgs'; +import { + defaultConfigOptions, + getOptions, + ValidConfigOptions, +} from './options/options'; import { runSequentially, Result } from './runSequentially'; export type BackportAbortResponse = { @@ -50,39 +53,47 @@ export async function backportRun({ optionsFromModule?: ConfigFileOptions; exitCodeOnFailure: boolean; }): Promise { - const argv = yargsParser(processArgs) as ConfigFileOptions; - const ci = argv.ci ?? optionsFromModule.ci; - const logFilePath = argv.logFilePath ?? optionsFromModule.logFilePath; - const logger = initLogger({ ci, logFilePath }); + let optionsFromCliArgs: OptionsFromCliArgs; + try { + optionsFromCliArgs = getOptionsFromCliArgs(processArgs); + } catch (e) { + if (e instanceof Error) { + consoleLog(e.message); + consoleLog(`Run "backport --help" to see all options`); + return { + status: 'failure', + error: e, + errorMessage: e.message, + commits: [], + } as BackportResponse; + } + + throw e; + } + + const { interactive, logFilePath } = { + ...defaultConfigOptions, + ...optionsFromModule, + ...optionsFromCliArgs, + }; + + const logger = initLogger({ interactive, logFilePath }); let options: ValidConfigOptions | null = null; let commits: Commit[] = []; try { - const spinner = ora(ci); - try { - // don't show spinner for yargs commands that exit the process without stopping the spinner first - if (!argv.help && !argv.version && !argv.v) { - spinner.start('Initializing...'); - } - - options = await getOptions(processArgs, optionsFromModule); - logger.info('Backporting options', options); - spinner.stop(); - } catch (e) { - spinner.stop(); - if (e instanceof CliError) { - consoleLog(e.message); - consoleLog(`Run "backport --help" to see all options`); - return { - status: 'failure', - error: e, - errorMessage: e.message, - commits: [], - } as BackportResponse; - } - throw e; - } + const spinner = ora(interactive); + + // don't show spinner for yargs commands that exit the process without stopping the spinner first + spinner.start('Initializing...'); + + options = await getOptions({ + optionsFromCliArgs, + optionsFromModule, + }); + logger.info('Backporting options', options); + spinner.stop(); commits = await getCommits(options); logger.info('Commits', commits); diff --git a/src/entrypoint.cli.ts b/src/entrypoint.cli.ts index 7c8071e2..8cd952e4 100644 --- a/src/entrypoint.cli.ts +++ b/src/entrypoint.cli.ts @@ -1,14 +1,13 @@ import yargsParser from 'yargs-parser'; import { backportRun } from './backportRun'; -import { ConfigFileOptions } from './entrypoint.module'; const processArgs = process.argv.slice(2); // this is the entrypoint when running from command line backportRun({ processArgs, exitCodeOnFailure: true }).then( (backportResponse) => { - const argv = yargsParser(processArgs) as ConfigFileOptions; - const { ci, ls } = argv; - if (ci || ls) { + const { interactive, ls } = yargsParser(processArgs); + + if (!interactive || ls) { // eslint-disable-next-line no-console console.log(JSON.stringify(backportResponse)); } diff --git a/src/entrypoint.module.private.test.ts b/src/entrypoint.module.private.test.ts index 33e6492b..27729db2 100644 --- a/src/entrypoint.module.private.test.ts +++ b/src/entrypoint.module.private.test.ts @@ -21,7 +21,7 @@ describe('entrypoint.module', () => { options: { repoOwner: 'backport-org', repoName: 'repo-with-conflicts', - ci: true, + interactive: false, accessToken, pullNumber: 12, targetBranches: ['7.x'], @@ -64,7 +64,7 @@ describe('entrypoint.module', () => { options: { repoOwner: 'backport-org', repoName: 'repo-with-conflicts', - ci: true, + interactive: false, accessToken, pullNumber: 12, }, @@ -88,7 +88,7 @@ describe('entrypoint.module', () => { options: { repoOwner: 'backport-org', repoName: 'repo-with-conflicts', - ci: true, + interactive: false, accessToken, pullNumber: 8, dryRun: true, diff --git a/src/entrypoint.module.ts b/src/entrypoint.module.ts index 8eadcf76..de7b3642 100644 --- a/src/entrypoint.module.ts +++ b/src/entrypoint.module.ts @@ -72,7 +72,7 @@ export async function getCommits(options: { dateUntil?: string; dateSince?: string; }): Promise { - initLogger({ ci: true, accessToken: options.accessToken }); + initLogger({ interactive: false, accessToken: options.accessToken }); const optionsFromGithub = await getOptionsFromGithub(options); diff --git a/src/lib/cherrypickAndCreateTargetPullRequest.test.ts b/src/lib/cherrypickAndCreateTargetPullRequest.test.ts index a4b5b947..8b74126d 100644 --- a/src/lib/cherrypickAndCreateTargetPullRequest.test.ts +++ b/src/lib/cherrypickAndCreateTargetPullRequest.test.ts @@ -49,6 +49,7 @@ describe('cherrypickAndCreateTargetPullRequest', () => { authenticatedUsername: 'sqren_authenticated', author: 'sqren', fork: true, + interactive: true, prTitle: '[{targetBranch}] {commitMessages}', repoForkOwner: 'sqren', repoName: 'kibana', diff --git a/src/lib/cherrypickAndCreateTargetPullRequest.ts b/src/lib/cherrypickAndCreateTargetPullRequest.ts index 76151df8..190f25bd 100644 --- a/src/lib/cherrypickAndCreateTargetPullRequest.ts +++ b/src/lib/cherrypickAndCreateTargetPullRequest.ts @@ -57,7 +57,7 @@ export async function cherrypickAndCreateTargetPullRequest({ ); if (options.dryRun) { - ora(options.ci).succeed('Dry run complete'); + ora(options.interactive).succeed('Dry run complete'); return { url: 'https://localhost/dry-run', didUpdate: false, number: 1337 }; } @@ -165,7 +165,7 @@ async function waitForCherrypick( (pr) => pr.state === 'MERGED' && pr.branch === targetBranch ); - const cherrypickSpinner = ora(options.ci, spinnerText).start(); + const cherrypickSpinner = ora(options.interactive, spinnerText).start(); let conflictingFiles: ConflictingFiles; let unstagedFiles: string[]; @@ -204,7 +204,7 @@ async function waitForCherrypick( // resolve conflicts automatically if (options.autoFixConflicts) { const autoResolveSpinner = ora( - options.ci, + options.interactive, 'Attempting to resolve conflicts automatically' ).start(); @@ -234,7 +234,7 @@ async function waitForCherrypick( conflictingFiles: conflictingFilesRelative, }); - if (options.ci) { + if (!options.interactive) { throw new BackportError({ code: 'merge-conflict-exception', commitsWithoutBackports, @@ -276,7 +276,10 @@ async function waitForCherrypick( }); // Conflicts should be resolved and files staged at this point - const stagingSpinner = ora(options.ci, `Finalizing cherrypick`).start(); + const stagingSpinner = ora( + options.interactive, + `Finalizing cherrypick` + ).start(); try { // Run `git commit` await commitChanges(commit, options); diff --git a/src/lib/getCommits.ts b/src/lib/getCommits.ts index bd610ef2..db118801 100644 --- a/src/lib/getCommits.ts +++ b/src/lib/getCommits.ts @@ -17,7 +17,7 @@ function getOraPersistsOption(question: string, answer: string) { } export async function getCommits(options: ValidConfigOptions) { - const spinner = ora(options.ci).start(); + const spinner = ora(options.interactive).start(); try { if (options.sha) { @@ -58,9 +58,9 @@ export async function getCommits(options: ValidConfigOptions) { return [commit]; } - if (options.ci && !options.ls) { + if (!options.interactive && !options.ls) { throw new BackportError( - 'When "--ci" flag is enabled either `--sha` or `--pr` must be specified' + 'When "--interactive" is disabled either `--sha` or `--pr` must be specified' ); } diff --git a/src/lib/getTargetBranches.test.ts b/src/lib/getTargetBranches.test.ts index 2557e806..43402468 100644 --- a/src/lib/getTargetBranches.test.ts +++ b/src/lib/getTargetBranches.test.ts @@ -21,10 +21,11 @@ describe('getTargetBranches', () => { beforeEach(async () => { const options = { - targetBranches: [], + targetBranches: [] as string[], targetBranchChoices: [{ name: 'branchA' }, { name: 'branchB' }], multipleBranches: false, - } as unknown as ValidConfigOptions; + interactive: true, + } as ValidConfigOptions; const commits: Commit[] = [ { @@ -85,7 +86,7 @@ describe('getTargetBranches', () => { }); }); - describe('when ci=true', () => { + describe('when interactive=false', () => { it('should throw when there are no missing backports', () => { const commits: Commit[] = []; @@ -93,7 +94,7 @@ describe('getTargetBranches', () => { return getTargetBranches( { expectedTargetPullRequests: [], - ci: true, + interactive: false, } as unknown as ValidConfigOptions, commits ); @@ -112,7 +113,7 @@ describe('getTargetBranches', () => { ] as Commit[]; const targetBranches = getTargetBranches( - { ci: true } as unknown as ValidConfigOptions, + { interactive: false } as unknown as ValidConfigOptions, commits ); expect(targetBranches).toEqual(['7.x']); @@ -123,7 +124,8 @@ describe('getTargetBranches', () => { let targetBranchChoices: TargetBranchChoice[]; beforeEach(async () => { const options = { - targetBranches: [], + interactive: true, + targetBranches: [] as string[], multipleBranches: true, targetBranchChoices: [ { name: 'master' }, @@ -134,7 +136,7 @@ describe('getTargetBranches', () => { ] as TargetBranchChoice[], branchLabelMapping: {}, sourceBranch: 'master', - } as unknown as ValidConfigOptions; + } as ValidConfigOptions; const commits: Commit[] = [ { @@ -187,7 +189,7 @@ describe('getTargetBranches', () => { describe('getTargetBranchChoices', () => { const options = { - ci: false, + interactive: true, targetBranchChoices: [ { name: 'master', checked: true }, { name: '7.x', checked: true }, diff --git a/src/lib/getTargetBranches.ts b/src/lib/getTargetBranches.ts index 5e1a66a8..348c64da 100644 --- a/src/lib/getTargetBranches.ts +++ b/src/lib/getTargetBranches.ts @@ -25,8 +25,8 @@ export function getTargetBranches( .map((pr) => pr.branch) : []; - // automatically backport to specified target branches - if (options.ci) { + // require target branches to be specified when when in non-interactive mode + if (!options.interactive) { if (isEmpty(missingTargetBranches)) { throw new BackportError({ code: 'no-branches-exception' }); } @@ -44,7 +44,7 @@ export function getTargetBranches( sourceBranch ); - // render interactive list of branches + // render prmompt for selecting target branches return promptForTargetBranches({ targetBranchChoices, isMultipleChoice: options.multipleBranches, diff --git a/src/lib/git.ts b/src/lib/git.ts index f824c340..c8e66005 100644 --- a/src/lib/git.ts +++ b/src/lib/git.ts @@ -520,7 +520,7 @@ export async function createBackportBranch({ targetBranch: string; backportBranch: string; }) { - const spinner = ora(options.ci, 'Pulling latest changes').start(); + const spinner = ora(options.interactive, 'Pulling latest changes').start(); try { const cwd = getRepoPath(options); @@ -572,7 +572,7 @@ export async function deleteBackportBranch({ options: ValidConfigOptions; backportBranch: string; }) { - const spinner = ora(options.ci).start(); + const spinner = ora(options.interactive).start(); const cwd = getRepoPath(options); await spawnPromise('git', ['reset', '--hard'], cwd); @@ -602,7 +602,7 @@ export async function pushBackportBranch({ }) { const repoForkOwner = getRepoForkOwner(options); const spinner = ora( - options.ci, + options.interactive, `Pushing branch "${repoForkOwner}:${backportBranch}"` ).start(); diff --git a/src/lib/github/v3/addAssigneesToPullRequest.ts b/src/lib/github/v3/addAssigneesToPullRequest.ts index 70747b8b..874e0814 100644 --- a/src/lib/github/v3/addAssigneesToPullRequest.ts +++ b/src/lib/github/v3/addAssigneesToPullRequest.ts @@ -10,7 +10,7 @@ export async function addAssigneesToPullRequest( repoOwner, accessToken, autoAssign, - ci, + interactive, }: ValidConfigOptions, pullNumber: number, assignees: string[] @@ -19,7 +19,7 @@ export async function addAssigneesToPullRequest( ? `Self-assigning to #${pullNumber}` : `Adding assignees to #${pullNumber}: ${assignees.join(', ')}`; logger.info(text); - const spinner = ora(ci, text).start(); + const spinner = ora(interactive, text).start(); try { const octokit = new Octokit({ diff --git a/src/lib/github/v3/addLabelsToPullRequest.ts b/src/lib/github/v3/addLabelsToPullRequest.ts index c3494399..abf1f6b7 100644 --- a/src/lib/github/v3/addLabelsToPullRequest.ts +++ b/src/lib/github/v3/addLabelsToPullRequest.ts @@ -9,14 +9,14 @@ export async function addLabelsToPullRequest( repoName, repoOwner, accessToken, - ci, + interactive, }: ValidConfigOptions, pullNumber: number, labels: string[] ): Promise { const text = `Adding labels: ${labels.join(', ')}`; logger.info(text); - const spinner = ora(ci, text).start(); + const spinner = ora(interactive, text).start(); try { const octokit = new Octokit({ diff --git a/src/lib/github/v3/addReviewersToPullRequest.ts b/src/lib/github/v3/addReviewersToPullRequest.ts index eda49e27..b585ff6e 100644 --- a/src/lib/github/v3/addReviewersToPullRequest.ts +++ b/src/lib/github/v3/addReviewersToPullRequest.ts @@ -9,14 +9,14 @@ export async function addReviewersToPullRequest( repoName, repoOwner, accessToken, - ci, + interactive, }: ValidConfigOptions, pullNumber: number, reviewers: string[] ) { const text = `Adding reviewers: ${reviewers}`; logger.info(text); - const spinner = ora(ci, text).start(); + const spinner = ora(interactive, text).start(); try { const octokit = new Octokit({ diff --git a/src/lib/github/v3/createPullRequest.ts b/src/lib/github/v3/createPullRequest.ts index 8ed7a7d0..c6ed7b25 100644 --- a/src/lib/github/v3/createPullRequest.ts +++ b/src/lib/github/v3/createPullRequest.ts @@ -35,7 +35,7 @@ export async function createPullRequest({ ); const { accessToken, githubApiBaseUrlV3 } = options; - const spinner = ora(options.ci, `Creating pull request`).start(); + const spinner = ora(options.interactive, `Creating pull request`).start(); try { const octokit = new Octokit({ diff --git a/src/lib/github/v3/createStatusComment.test.ts b/src/lib/github/v3/createStatusComment.test.ts index de795d0d..97e59d45 100644 --- a/src/lib/github/v3/createStatusComment.test.ts +++ b/src/lib/github/v3/createStatusComment.test.ts @@ -28,7 +28,7 @@ describe('createStatusComment', () => { backportBinary: 'node scripts/backport', publishStatusComment: true, githubApiBaseUrlV3: 'https://api.github.com', - ci: true, + interactive: false, } as ValidConfigOptions, backportResponse: { commits: [{ sourcePullRequest: { number: 100 } }], @@ -62,8 +62,8 @@ describe('getCommentBody', () => { } as BackportResponse, }); - it('posts a comment when running on ci', () => { - const params = getParams({ ci: true }); + it('posts a comment when running in non-interactive mode', () => { + const params = getParams({ interactive: false }); expect(getCommentBody(params)).toMatchInlineSnapshot(` "## 💔 Backport failed The pull request could not be backported due to the following error: @@ -83,7 +83,7 @@ describe('getCommentBody', () => { }); it('does not post a comment when running manually', () => { - const params = getParams({ ci: false }); + const params = getParams({ interactive: true }); expect(getCommentBody(params)).toBe(undefined); }); }); @@ -117,8 +117,8 @@ describe('getCommentBody', () => { } as BackportResponse, }); - it('posts a comment on ci', () => { - const params = getParams({ ci: true }); + it('posts a comment when in non-interactive mode', () => { + const params = getParams({ interactive: false }); expect(getCommentBody(params)).toMatchInlineSnapshot(` "## 💚 All backports created successfully @@ -137,7 +137,7 @@ describe('getCommentBody', () => { }); it('posts a comment when running locally', () => { - const params = getParams({ ci: false }); + const params = getParams({ interactive: true }); expect(getCommentBody(params)).toMatchInlineSnapshot(` "## 💚 All backports created successfully @@ -184,7 +184,7 @@ describe('getCommentBody', () => { }); it('posts a comment on CI', () => { - const params = getParams({ ci: true }); + const params = getParams({ interactive: false }); expect(getCommentBody(params)).toMatchInlineSnapshot(` "## 💔 All backports failed @@ -207,7 +207,7 @@ describe('getCommentBody', () => { }); it('does not post a comment when running manaully', () => { - const params = getParams({ ci: false }); + const params = getParams({ interactive: true }); expect(getCommentBody(params)).toBe(undefined); }); }); @@ -242,7 +242,7 @@ describe('getCommentBody', () => { }); it('post a comment when running on CI', () => { - const params = getParams({ ci: true }); + const params = getParams({ interactive: false }); expect(getCommentBody(params)).toMatchInlineSnapshot(` "## 💔 Some backports could not be created @@ -267,7 +267,7 @@ describe('getCommentBody', () => { }); it('does not post a comment when running manually because some backports failed', () => { - const params = getParams({ ci: false }); + const params = getParams({ interactive: true }); expect(getCommentBody(params)).toMatchInlineSnapshot(`undefined`); }); }); @@ -365,7 +365,7 @@ describe('getCommentBody', () => { }); it('posts a comment when running on CI', () => { - const params = getParams({ ci: true }); + const params = getParams({ interactive: false }); expect(getCommentBody(params)).toMatchInlineSnapshot(` "## 💔 Some backports could not be created @@ -391,7 +391,7 @@ describe('getCommentBody', () => { }); it('does not post a comment when running manually because some backports failed', () => { - const params = getParams({ ci: false }); + const params = getParams({ interactive: true }); expect(getCommentBody(params)).toBe(undefined); }); }); @@ -415,7 +415,7 @@ describe('getCommentBody', () => { }); it('posts a comment when running on CI', () => { - const params = getParams({ ci: true }); + const params = getParams({ interactive: false }); expect(getCommentBody(params)).toMatchInlineSnapshot(` "## ⚪ Backport skipped The pull request was not backported as there were no branches to backport to. If this is a mistake, please apply the desired version labels or run the backport tool manually. @@ -434,7 +434,7 @@ describe('getCommentBody', () => { }); it('does not post a comment when running manually', () => { - const params = getParams({ ci: false }); + const params = getParams({ interactive: true }); expect(getCommentBody(params)).toBe(undefined); }); }); diff --git a/src/lib/github/v3/createStatusComment.ts b/src/lib/github/v3/createStatusComment.ts index cde4c2d4..f3bbc080 100644 --- a/src/lib/github/v3/createStatusComment.ts +++ b/src/lib/github/v3/createStatusComment.ts @@ -75,9 +75,9 @@ export function getCommentBody({ }): string | undefined { const { repoName, repoOwner, autoMerge } = options; - // custom handling when running backport locally (as opposed to on CI) - if (!options.ci) { - // only post successful backports when not running in CI + // TODO; make setting to specify whether to post comments for successful and failures + if (options.interactive) { + // only post successful backports when running interactively (as opposed to programatically as on ci) if (backportResponse.status !== 'success') { return; } @@ -115,8 +115,9 @@ ${manualBackportCommand}${questionsAndLinkToBackport}${packageVersionSection}`; const tableBody = backportResponse.results .filter((result) => { - // only post status updates for successful backports when running manually (non-ci) - return options.ci || result.status === 'success'; + // only post status updates for successful backports when running in --interactive mode + // TODO: this seems duplicated from above + return !options.interactive || result.status === 'success'; }) .map((result) => { if (result.status === 'success') { diff --git a/src/lib/github/v4/enablePullRequestAutoMerge.ts b/src/lib/github/v4/enablePullRequestAutoMerge.ts index 0324f301..fbbe8914 100644 --- a/src/lib/github/v4/enablePullRequestAutoMerge.ts +++ b/src/lib/github/v4/enablePullRequestAutoMerge.ts @@ -20,7 +20,7 @@ export async function enablePullRequestAutoMerge( } = options; const text = `Enabling auto merging via ${options.autoMergeMethod}`; logger.info(text); - const spinner = ora(options.ci, text).start(); + const spinner = ora(options.interactive, text).start(); const pullRequestId = await fetchPullRequestId( options, diff --git a/src/lib/logger.ts b/src/lib/logger.ts index a6f1b060..835a9b68 100644 --- a/src/lib/logger.ts +++ b/src/lib/logger.ts @@ -4,19 +4,19 @@ import { getLogfilePath } from './env'; export let logger: winston.Logger; let _accessToken: string | undefined; -let _ci: boolean | undefined; +let _interactive: boolean; export function initLogger({ - ci, + interactive, accessToken, logFilePath, }: { - ci: boolean | undefined; + interactive: boolean; accessToken?: string; logFilePath?: string; }) { _accessToken = accessToken; - _ci = ci; + _interactive = interactive; logger = winston.createLogger({ format: format.combine( @@ -38,7 +38,7 @@ export function initLogger({ // wrapper around console.log export function consoleLog(message: string) { - if (!_ci) { + if (_interactive) { // eslint-disable-next-line no-console console.log(redactAccessToken(message)); } diff --git a/src/lib/ora.ts b/src/lib/ora.ts index ea73d346..b6c164d0 100644 --- a/src/lib/ora.ts +++ b/src/lib/ora.ts @@ -12,8 +12,8 @@ const oraMock = { } as oraOriginal.Ora; export function ora( - ci: boolean | undefined, + interactive: boolean | undefined, text?: string | undefined ): oraOriginal.Ora { - return ci ? oraMock : oraOriginal({ text }); + return interactive ? oraOriginal({ text }) : oraMock; } diff --git a/src/lib/setupRepo.test.ts b/src/lib/setupRepo.test.ts index b92621f2..99938eda 100644 --- a/src/lib/setupRepo.test.ts +++ b/src/lib/setupRepo.test.ts @@ -41,6 +41,7 @@ describe('setupRepo', () => { repoName: 'kibana', repoOwner: 'elastic', cwd: '/path/to/source/repo', + interactive: true, } as ValidConfigOptions) ).rejects.toThrowError('Simulated git clone failure'); @@ -101,6 +102,7 @@ describe('setupRepo', () => { repoOwner: 'elastic', gitHostname: 'github.com', cwd: '/path/to/source/repo', + interactive: true, } as ValidConfigOptions); expect(spinnerTextSpy.mock.calls.map((call) => call[0])) @@ -235,6 +237,7 @@ describe('setupRepo', () => { repoName: 'kibana', repoOwner: 'elastic', cwd: '/path/to/source/repo', + interactive: true, } as ValidConfigOptions); }); @@ -272,6 +275,7 @@ describe('setupRepo', () => { repoName: 'kibana', repoOwner: 'elastic', cwd: '/path/to/source/repo', + interactive: true, } as ValidConfigOptions); }); @@ -297,6 +301,7 @@ describe('setupRepo', () => { repoOwner: 'elastic', cwd: '/myHomeDir/.backport/repositories/owner/repo/foo', dir: '/myHomeDir/.backport/repositories/owner/repo', + interactive: true, } as ValidConfigOptions) ).rejects.toThrowError( 'Refusing to clone repo into "/myHomeDir/.backport/repositories/owner/repo" when current working directory is "/myHomeDir/.backport/repositories/owner/repo/foo". Please change backport directory via `--dir` option or run backport from another location' diff --git a/src/lib/setupRepo.ts b/src/lib/setupRepo.ts index 77ba6c61..e47dbe2b 100644 --- a/src/lib/setupRepo.ts +++ b/src/lib/setupRepo.ts @@ -24,7 +24,7 @@ export async function setupRepo(options: ValidConfigOptions) { // clone repo if folder does not already exists if (!isAlreadyCloned) { - const spinner = ora(options.ci).start(); + const spinner = ora(options.interactive).start(); try { const localRepoPath = await getLocalSourceRepoPath(options); const remoteRepoPath = getRemoteUrl(options, options.repoOwner); diff --git a/src/options/ConfigOptions.ts b/src/options/ConfigOptions.ts index e4dce9a2..5422c825 100644 --- a/src/options/ConfigOptions.ts +++ b/src/options/ConfigOptions.ts @@ -28,7 +28,6 @@ type Options = Partial<{ autoMergeMethod: string; backportBinary: string; cherrypickRef: boolean; - ci: boolean; // only available via cli and module options (not project or global config) commitPaths: string[]; details: boolean; dir: string; @@ -40,6 +39,7 @@ type Options = Partial<{ gitHostname: string; githubApiBaseUrlV3: string; githubApiBaseUrlV4: string; + interactive: boolean; // only available via cli and module options (not project or global config) logFilePath: string; ls: boolean; // only available via cli maxNumber: number; diff --git a/src/options/cliArgs.test.ts b/src/options/cliArgs.test.ts index e3900743..f918b2b7 100644 --- a/src/options/cliArgs.test.ts +++ b/src/options/cliArgs.test.ts @@ -51,6 +51,32 @@ describe('getOptionsFromCliArgs', () => { }); }); + describe('reviewers', () => { + it('should handle all variations', () => { + const argv = ['--reviewer=peter']; + + const res = getOptionsFromCliArgs(argv); + expect(res.reviewers).toEqual(['peter']); + }); + }); + + describe('author', () => { + it('has a default author', () => { + const res = getOptionsFromCliArgs([]); + expect(res.author).toEqual(undefined); + }); + + it('sets the author', () => { + const res = getOptionsFromCliArgs(['--author=sqren']); + expect(res.author).toEqual('sqren'); + }); + + it('sets the author to null', () => { + const res = getOptionsFromCliArgs(['--all']); + expect(res.author).toEqual(null); + }); + }); + describe('pullNumber', () => { it('should accept `--pr` alias but only return the full representation (`pullNumber`)', () => { const argv = ['--pr', '1337']; @@ -112,6 +138,50 @@ describe('getOptionsFromCliArgs', () => { }); }); + describe('fork', () => { + it('--fork', () => { + const argv = ['--fork']; + const res = getOptionsFromCliArgs(argv); + expect(res.fork).toEqual(true); + }); + + it('--no-fork', () => { + const argv = ['--no-fork']; + const res = getOptionsFromCliArgs(argv); + expect(res.fork).toEqual(false); + }); + + it('defaults to undefined', () => { + const argv = [] as const; + const res = getOptionsFromCliArgs(argv); + expect(res.fork).toEqual(undefined); + }); + }); + + describe('interactive', () => { + it('defaults to undefined', () => { + const res = getOptionsFromCliArgs([]); + expect(res.interactive).toEqual(undefined); + }); + + it('noStatusComment', () => { + const res = getOptionsFromCliArgs(['--non-interactive']); + expect(res.interactive).toEqual(false); + }); + }); + + describe('publishStatusComment', () => { + it('defaults to undefined', () => { + const res = getOptionsFromCliArgs([]); + expect(res.publishStatusComment).toEqual(undefined); + }); + + it('noStatusComment', () => { + const res = getOptionsFromCliArgs(['--noStatusComment']); + expect(res.publishStatusComment).toEqual(false); + }); + }); + describe('noVerify', () => { it('should be undefined by default', () => { const argv = [] as const; @@ -138,6 +208,18 @@ describe('getOptionsFromCliArgs', () => { }); }); + describe('cherrypickRef', () => { + it('should be undefined by default', () => { + const res = getOptionsFromCliArgs([]); + expect(res.cherrypickRef).toBe(undefined); + }); + + it('can be disabled', () => { + const res = getOptionsFromCliArgs(['--no-cherrypick-ref']); + expect(res.cherrypickRef).toBe(false); + }); + }); + describe('mainline', () => { it('should default to 1', () => { const argv = ['--mainline']; @@ -175,6 +257,13 @@ describe('getOptionsFromCliArgs', () => { expect(res.repoName).toEqual('kibana'); }); + it('accepts --repo-name and --repo-owner', () => { + const argv = ['--repo-owner=elastic', '--repo-name=kibana']; + const res = getOptionsFromCliArgs(argv); + expect(res.repoOwner).toEqual('elastic'); + expect(res.repoName).toEqual('kibana'); + }); + it('throw if both --repo and --repo-name is given', () => { const argv = ['--repo', 'elastic/kibana', '--repo-name', 'foo']; expect(() => getOptionsFromCliArgs(argv)).toThrowError( @@ -183,7 +272,7 @@ describe('getOptionsFromCliArgs', () => { }); }); - describe('since/until', () => { + describe('dateSince and dateUntil', () => { it('should always be UTC time (configured globally in jest.config.js)', () => { expect(new Date().getTimezoneOffset()).toBe(0); }); diff --git a/src/options/cliArgs.ts b/src/options/cliArgs.ts index 41a695e6..1786dc3f 100644 --- a/src/options/cliArgs.ts +++ b/src/options/cliArgs.ts @@ -60,11 +60,6 @@ export function getOptionsFromCliArgs(processArgs: readonly string[]) { conflicts: 'all', }) - .option('ci', { - description: 'Disable interactive prompts', - type: 'boolean', - }) - .option('cherrypickRef', { description: 'Append commit message with "(cherry picked from commit...)', type: 'boolean', @@ -83,6 +78,11 @@ export function getOptionsFromCliArgs(processArgs: readonly string[]) { type: 'string', }) + .option('globalConfigFile', { + description: 'Path to global config', + type: 'string', + }) + .option('since', { description: 'ISO-8601 date for filtering commits', type: 'string', @@ -154,6 +154,13 @@ export function getOptionsFromCliArgs(processArgs: readonly string[]) { type: 'string', }) + .option('nonInteractive', { + alias: ['json'], + description: 'Disable interactive prompts and return response as JSON', + type: 'boolean', + conflicts: 'interactive', + }) + .option('logFilePath', { hidden: true, description: `Path to log file`, @@ -365,13 +372,13 @@ export function getOptionsFromCliArgs(processArgs: readonly string[]) { ) // don't kill process upon error // and don't log error to console - .fail((msg, err, yargs) => { + .fail((msg, err) => { // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition if (err) { throw err; } - throw new CliError(msg, yargs); + throw new Error(msg); }); const { @@ -384,7 +391,7 @@ export function getOptionsFromCliArgs(processArgs: readonly string[]) { multipleCommits, all, - // repoName and repoOwner + // shorthands repo, // filters @@ -398,6 +405,7 @@ export function getOptionsFromCliArgs(processArgs: readonly string[]) { noStatusComment, noVerify, verify, + nonInteractive, // array types (should be renamed to plural form) assignee, @@ -446,13 +454,6 @@ export function getOptionsFromCliArgs(processArgs: readonly string[]) { fork: noFork === true ? false : restOptions.fork, noVerify: verify ?? noVerify, publishStatusComment: noStatusComment === true ? false : undefined, + interactive: nonInteractive === true ? false : undefined, }); } - -export class CliError extends Error { - constructor(public message: string, public yargs: yargs.Argv) { - super(message); - Error.captureStackTrace(this, CliError); - this.name = 'CliError'; - } -} diff --git a/src/options/config/config.test.ts b/src/options/config/config.test.ts index 8116b30e..17543a00 100644 --- a/src/options/config/config.test.ts +++ b/src/options/config/config.test.ts @@ -16,7 +16,6 @@ describe('getOptionsFromConfigFiles', () => { res = await getOptionsFromConfigFiles({ optionsFromCliArgs: {}, optionsFromModule: {}, - defaultConfigOptions: { ci: false }, }); }); diff --git a/src/options/config/config.ts b/src/options/config/config.ts index 5b4747ad..21367a6b 100644 --- a/src/options/config/config.ts +++ b/src/options/config/config.ts @@ -9,23 +9,18 @@ export type OptionsFromConfigFiles = Awaited< export async function getOptionsFromConfigFiles({ optionsFromCliArgs, optionsFromModule, - defaultConfigOptions, }: { optionsFromCliArgs: OptionsFromCliArgs; optionsFromModule: ConfigFileOptions; - defaultConfigOptions: ConfigFileOptions; }) { - // ci: cli and module only flag - const ci = - optionsFromCliArgs.ci ?? optionsFromModule.ci ?? defaultConfigOptions.ci; - - // ci: cli and module only flag const projectConfigFile = optionsFromCliArgs.projectConfigFile ?? optionsFromModule.projectConfigFile; + const globalConfigFile = optionsFromCliArgs.globalConfigFile; + const [projectConfig, globalConfig] = await Promise.all([ getProjectConfig({ projectConfigFile }), - ci ? undefined : getGlobalConfig(), + getGlobalConfig({ globalConfigFile }), ]); return { diff --git a/src/options/config/globalConfig.ts b/src/options/config/globalConfig.ts index c80c7667..9a7feed2 100644 --- a/src/options/config/globalConfig.ts +++ b/src/options/config/globalConfig.ts @@ -1,4 +1,5 @@ import { chmod, writeFile } from 'fs/promises'; +import path from 'path'; import makeDir from 'make-dir'; import { BackportError } from '../../errors/BackportError'; import { getBackportDirPath, getGlobalConfigPath } from '../../lib/env'; @@ -6,8 +7,15 @@ import { isErrnoError } from '../../utils/isErrnoError'; import { ConfigFileOptions } from '../ConfigOptions'; import { readConfigFile } from './readConfigFile'; -export async function getGlobalConfig(): Promise { - const globalConfigPath = getGlobalConfigPath(); +export async function getGlobalConfig({ + globalConfigFile, +}: { + globalConfigFile?: string; +} = {}): Promise { + const globalConfigPath = globalConfigFile + ? path.resolve(globalConfigFile) + : getGlobalConfigPath(); + await createGlobalConfigAndFolderIfNotExist(globalConfigPath); return readConfigFile(globalConfigPath); } diff --git a/src/options/options.test.ts b/src/options/options.test.ts index 7788af68..48259241 100644 --- a/src/options/options.test.ts +++ b/src/options/options.test.ts @@ -45,8 +45,12 @@ describe('getOptions', () => { globalConfig: { accessToken: undefined }, }); - await expect(() => getOptions([], {})).rejects - .toThrowErrorMatchingInlineSnapshot(` + await expect(() => + getOptions({ + optionsFromCliArgs: {}, + optionsFromModule: {}, + }) + ).rejects.toThrowErrorMatchingInlineSnapshot(` "Please update your config file: \\"/myHomeDir/.backport/config.json\\". It must contain a valid \\"accessToken\\". @@ -54,19 +58,6 @@ describe('getOptions', () => { `); }); - it('when accessToken is missing with --ci', async () => { - mockConfigFiles({ - projectConfig: defaultConfigs.projectConfig, - globalConfig: { accessToken: undefined }, - }); - - await expect(() => - getOptions([], { ci: true }) - ).rejects.toThrowErrorMatchingInlineSnapshot( - `"Access token missing. It must be explicitly supplied when using \\"--ci\\" option. Example: --access-token very-secret"` - ); - }); - it('when `targetBranches`, `targetBranchChoices` and `branchLabelMapping` are all empty', async () => { mockProjectConfig({ targetBranches: undefined, @@ -74,8 +65,9 @@ describe('getOptions', () => { branchLabelMapping: undefined, }); - await expect(() => getOptions([], {})).rejects - .toThrowErrorMatchingInlineSnapshot(` + await expect(() => + getOptions({ optionsFromCliArgs: {}, optionsFromModule: {} }) + ).rejects.toThrowErrorMatchingInlineSnapshot(` "Please specify a target branch: \\"--branch 6.1\\". Read more: https://github.com/sqren/backport/blob/main/docs/configuration.md#project-config-backportrcjson" @@ -85,7 +77,10 @@ describe('getOptions', () => { describe('whe option is an empty string', () => { it('throws for "username"', async () => { await expect(() => - getOptions([], { repoForkOwner: '', author: 'sqren' }) + getOptions({ + optionsFromCliArgs: {}, + optionsFromModule: { repoForkOwner: '', author: 'sqren' }, + }) ).rejects.toThrowErrorMatchingInlineSnapshot( `"\\"repoForkOwner\\" cannot be empty!"` ); @@ -93,15 +88,22 @@ describe('getOptions', () => { it('throws for "author"', async () => { await expect(() => - getOptions([], { author: '' }) + getOptions({ + optionsFromCliArgs: {}, + optionsFromModule: { author: '' }, + }) ).rejects.toThrowErrorMatchingInlineSnapshot( `"\\"author\\" cannot be empty!"` ); }); it('throws for "accessToken"', async () => { - await expect(() => getOptions([], { accessToken: '' })).rejects - .toThrowErrorMatchingInlineSnapshot(` + await expect(() => + getOptions({ + optionsFromCliArgs: {}, + optionsFromModule: { accessToken: '' }, + }) + ).rejects.toThrowErrorMatchingInlineSnapshot(` "Please update your config file: \\"/myHomeDir/.backport/config.json\\". It must contain a valid \\"accessToken\\". @@ -118,8 +120,9 @@ describe('getOptions', () => { it('should throw if there are no remotes', async () => { jest.spyOn(git, 'getRepoInfoFromGitRemotes').mockResolvedValue([]); - await expect(() => getOptions([], {})).rejects - .toThrowErrorMatchingInlineSnapshot(` + await expect(() => + getOptions({ optionsFromCliArgs: {}, optionsFromModule: {} }) + ).rejects.toThrowErrorMatchingInlineSnapshot(` "Please specify a repository: \\"--repo elastic/kibana\\". Read more: https://github.com/sqren/backport/blob/main/docs/configuration.md#project-config-backportrcjson" @@ -137,7 +140,10 @@ describe('getOptions', () => { .spyOn(git, 'getRepoInfoFromGitRemotes') .mockResolvedValue([{ repoName: 'kibana', repoOwner: 'sqren' }]); - const options = await getOptions([], {}); + const options = await getOptions({ + optionsFromCliArgs: {}, + optionsFromModule: {}, + }); expect(options.repoName).toBe('kibana'); expect(options.repoOwner).toBe('elastic'); @@ -147,7 +153,10 @@ describe('getOptions', () => { it('reads options from remote config', async () => { mockGithubConfigOptions({ hasRemoteConfig: true }); - const options = await getOptions([], {}); + const options = await getOptions({ + optionsFromCliArgs: {}, + optionsFromModule: {}, + }); expect(options.branchLabelMapping).toEqual({ '^v8.2.0$': 'option-from-remote', }); @@ -157,7 +166,9 @@ describe('getOptions', () => { it('should ensure that "backport" branch does not exist', async () => { mockGithubConfigOptions({ hasBackportBranch: true }); - await expect(getOptions([], {})).rejects.toThrowError( + await expect( + getOptions({ optionsFromCliArgs: {}, optionsFromModule: {} }) + ).rejects.toThrowError( 'You must delete the branch "backport" to continue. See https://github.com/sqren/backport/issues/155 for details' ); }); @@ -166,13 +177,16 @@ describe('getOptions', () => { mockGithubConfigOptions({}); const myFn = async () => true; - const options = await getOptions([], { autoFixConflicts: myFn }); + const options = await getOptions({ + optionsFromCliArgs: {}, + optionsFromModule: { autoFixConflicts: myFn }, + }); expect(options.autoFixConflicts).toBe(myFn); }); it('should call setAccessToken', async () => { mockGithubConfigOptions({}); - await getOptions([], {}); + await getOptions({ optionsFromCliArgs: {}, optionsFromModule: {} }); expect(logger.setAccessToken).toHaveBeenCalledTimes(1); }); @@ -182,7 +196,10 @@ describe('getOptions', () => { viewerLogin: 'john.diller', defaultBranchRef: 'default-branch-from-github', }); - const options = await getOptions([], {}); + const options = await getOptions({ + optionsFromCliArgs: {}, + optionsFromModule: {}, + }); expect(options).toEqual({ accessToken: 'abc', @@ -194,7 +211,6 @@ describe('getOptions', () => { autoMergeMethod: 'merge', backportBinary: 'backport', cherrypickRef: true, - ci: false, commitPaths: [], cwd: expect.any(String), dateSince: null, @@ -204,6 +220,7 @@ describe('getOptions', () => { fork: true, gitHostname: 'github.com', githubApiBaseUrlV4: 'http://localhost/graphql', + interactive: true, maxNumber: 10, multipleBranches: true, multipleCommits: false, @@ -228,15 +245,18 @@ describe('getOptions', () => { }); it('uses the `defaultBranchRef` as default', async () => { - const options = await getOptions([], {}); + const options = await getOptions({ + optionsFromCliArgs: {}, + optionsFromModule: {}, + }); expect(options.sourceBranch).toBe('some-default-branch'); }); it('uses the sourceBranch given via cli instead of `defaultBranchRef`', async () => { - const options = await getOptions( - ['--source-branch', 'cli-source-branch'], - {} - ); + const options = await getOptions({ + optionsFromCliArgs: { sourceBranch: 'cli-source-branch' }, + optionsFromModule: {}, + }); expect(options.sourceBranch).toBe('cli-source-branch'); }); }); @@ -247,18 +267,27 @@ describe('getOptions', () => { }); it('is enabled by default', async () => { - const { fork } = await getOptions([], {}); + const { fork } = await getOptions({ + optionsFromCliArgs: {}, + optionsFromModule: {}, + }); expect(fork).toBe(true); }); - it('can be disabled via `--no-fork` flag', async () => { - const { fork } = await getOptions(['--no-fork'], {}); + it('can be disabled via cli', async () => { + const { fork } = await getOptions({ + optionsFromCliArgs: { fork: false }, + optionsFromModule: {}, + }); expect(fork).toBe(false); }); it('can be disabled via config file', async () => { mockProjectConfig({ fork: false }); - const { fork } = await getOptions([], {}); + const { fork } = await getOptions({ + optionsFromCliArgs: {}, + optionsFromModule: {}, + }); expect(fork).toBe(false); }); }); @@ -268,14 +297,20 @@ describe('getOptions', () => { mockGithubConfigOptions({}); }); - it('can be set via `--reviewer` flag', async () => { - const { reviewers } = await getOptions(['--reviewer', 'peter'], {}); + it('can be set via cli', async () => { + const { reviewers } = await getOptions({ + optionsFromCliArgs: { reviewers: ['peter'] }, + optionsFromModule: {}, + }); expect(reviewers).toEqual(['peter']); }); it('can be set via config file', async () => { mockProjectConfig({ reviewers: ['john'] }); - const { reviewers } = await getOptions([], {}); + const { reviewers } = await getOptions({ + optionsFromCliArgs: {}, + optionsFromModule: {}, + }); expect(reviewers).toEqual(['john']); }); }); @@ -286,17 +321,26 @@ describe('getOptions', () => { }); it('is not enabled by default', async () => { - const { mainline } = await getOptions([], {}); + const { mainline } = await getOptions({ + optionsFromCliArgs: {}, + optionsFromModule: {}, + }); expect(mainline).toBe(undefined); }); it('can be set via `--mainline` flag', async () => { - const { mainline } = await getOptions(['--mainline'], {}); + const { mainline } = await getOptions({ + optionsFromCliArgs: { mainline: 1 }, + optionsFromModule: {}, + }); expect(mainline).toBe(1); }); it('accepts numeric values', async () => { - const { mainline } = await getOptions(['--mainline', '2'], {}); + const { mainline } = await getOptions({ + optionsFromCliArgs: { mainline: 2 }, + optionsFromModule: {}, + }); expect(mainline).toBe(2); }); }); @@ -307,29 +351,36 @@ describe('getOptions', () => { }); it('defaults to authenticated user', async () => { - const { author } = await getOptions([], {}); + const { author } = await getOptions({ + optionsFromCliArgs: {}, + optionsFromModule: {}, + }); expect(author).toBe('billy.bob'); }); it('can be overridden via `--author` flag', async () => { - const { author } = await getOptions(['--author', 'john.doe'], {}); + const { author } = await getOptions({ + optionsFromCliArgs: { author: 'john.doe' }, + optionsFromModule: {}, + }); expect(author).toBe('john.doe'); }); - it('can be reset via `--all` flag', async () => { - const { author } = await getOptions(['--all'], {}); - expect(author).toBe(null); - }); - it('can be reset via config file (similar to `--all` flag)', async () => { mockProjectConfig({ author: null }); - const { author } = await getOptions([], {}); + const { author } = await getOptions({ + optionsFromCliArgs: {}, + optionsFromModule: {}, + }); expect(author).toBe(null); }); it('can be overridden via config file', async () => { mockProjectConfig({ author: 'jane.doe' }); - const { author } = await getOptions([], {}); + const { author } = await getOptions({ + optionsFromCliArgs: {}, + optionsFromModule: {}, + }); expect(author).toBe('jane.doe'); }); }); @@ -340,24 +391,28 @@ describe('getOptions', () => { }); it('should default to true', async () => { - const { cherrypickRef } = await getOptions([], {}); + const { cherrypickRef } = await getOptions({ + optionsFromCliArgs: {}, + optionsFromModule: {}, + }); expect(cherrypickRef).toBe(true); }); - it('should negate with `noCherrypickRef` cli arg', async () => { - const { cherrypickRef } = await getOptions(['--no-cherrypick-ref'], {}); - expect(cherrypickRef).toBe(false); - }); - it('should be settable via config file', async () => { mockProjectConfig({ cherrypickRef: false }); - const { cherrypickRef } = await getOptions([], {}); + const { cherrypickRef } = await getOptions({ + optionsFromCliArgs: {}, + optionsFromModule: {}, + }); expect(cherrypickRef).toBe(false); }); it('cli args overwrites config', async () => { mockProjectConfig({ cherrypickRef: false }); - const { cherrypickRef } = await getOptions(['--cherrypick-ref'], {}); + const { cherrypickRef } = await getOptions({ + optionsFromCliArgs: { cherrypickRef: true }, + optionsFromModule: {}, + }); expect(cherrypickRef).toBe(true); }); }); diff --git a/src/options/options.ts b/src/options/options.ts index 1dfaedee..0c5feaa1 100644 --- a/src/options/options.ts +++ b/src/options/options.ts @@ -8,7 +8,7 @@ import { import { getRepoOwnerAndNameFromGitRemotes } from '../lib/github/v4/getRepoOwnerAndNameFromGitRemotes'; import { setAccessToken } from '../lib/logger'; import { ConfigFileOptions, TargetBranchChoiceOrString } from './ConfigOptions'; -import { getOptionsFromCliArgs, OptionsFromCliArgs } from './cliArgs'; +import { OptionsFromCliArgs } from './cliArgs'; import { getOptionsFromConfigFiles, OptionsFromConfigFiles, @@ -24,14 +24,13 @@ export type ValidConfigOptions = Readonly< Awaited> >; -const defaultConfigOptions = { +export const defaultConfigOptions = { assignees: [] as Array, autoAssign: false, autoMerge: false, autoMergeMethod: 'merge', backportBinary: 'backport', cherrypickRef: true, - ci: false, commitPaths: [] as Array, cwd: process.cwd(), dateSince: null, @@ -39,6 +38,7 @@ const defaultConfigOptions = { details: false, fork: true, gitHostname: 'github.com', + interactive: true, maxNumber: 10, multipleBranches: true, multipleCommits: false, @@ -52,15 +52,16 @@ const defaultConfigOptions = { targetPRLabels: [] as string[], }; -export async function getOptions( - processArgs: string[], - optionsFromModule: ConfigFileOptions -) { - const optionsFromCliArgs = getOptionsFromCliArgs(processArgs); +export async function getOptions({ + optionsFromCliArgs, + optionsFromModule, +}: { + optionsFromCliArgs: OptionsFromCliArgs; + optionsFromModule: ConfigFileOptions; +}) { const optionsFromConfigFiles = await getOptionsFromConfigFiles({ optionsFromCliArgs, optionsFromModule, - defaultConfigOptions, }); // combined options from cli and config files @@ -124,12 +125,6 @@ async function getRequiredOptions(combined: OptionsFromConfigAndCli) { // require access token if (!accessToken) { - if (combined.ci) { - throw new BackportError( - `Access token missing. It must be explicitly supplied when using "--ci" option. Example: --access-token very-secret` - ); - } - const globalConfigPath = getGlobalConfigPath(); throw new BackportError( `Please update your config file: "${globalConfigPath}".\nIt must contain a valid "accessToken".\n\nRead more: ${GLOBAL_CONFIG_DOCS_LINK}` diff --git a/src/test/e2e/cli/entrypoint.cli.private.test.ts b/src/test/e2e/cli/entrypoint.cli.private.test.ts index 8c900a06..b6a2d250 100644 --- a/src/test/e2e/cli/entrypoint.cli.private.test.ts +++ b/src/test/e2e/cli/entrypoint.cli.private.test.ts @@ -1,8 +1,11 @@ +import fs from 'fs/promises'; +import path from 'path'; import { BackportAbortResponse, BackportFailureResponse, BackportSuccessResponse, } from '../../../backportRun'; +import { ConfigFileOptions } from '../../../entrypoint.module'; import { exec } from '../../../lib/child-process-promisified'; import { getBackportDirPath } from '../../../lib/env'; import * as packageVersion from '../../../utils/packageVersion'; @@ -10,8 +13,7 @@ import { getDevAccessToken } from '../../private/getDevAccessToken'; import { getSandboxPath, resetSandbox } from '../../sandbox'; import { runBackportViaCli } from './runBackportViaCli'; -const TIMEOUT_IN_SECONDS = 15; -jest.setTimeout(TIMEOUT_IN_SECONDS * 1000); +// jest.setTimeout(15_000); const accessToken = getDevAccessToken(); describe('entrypoint cli', () => { @@ -49,10 +51,10 @@ describe('entrypoint cli', () => { --autoMerge Enable auto-merge for created pull requests [boolean] --autoMergeMethod Sets auto-merge method when using --auto-merge. Default: merge [string] [choices: \\"merge\\", \\"rebase\\", \\"squash\\"] - --ci Disable interactive prompts [boolean] --cherrypickRef Append commit message with \\"(cherry picked from commit...) [boolean] --projectConfigFile, --config Path to project config [string] + --globalConfigFile Path to global config [string] --since ISO-8601 date for filtering commits [string] --until ISO-8601 date for filtering commits [string] --dir Path to temporary backport repo [string] @@ -65,6 +67,8 @@ describe('entrypoint cli', () => { [boolean] --gitAuthorName Set commit author name [string] --gitAuthorEmail Set commit author email [string] + --nonInteractive, --json Disable interactive prompts and return response as JSON + [boolean] --ls List commits instead of backporting them [boolean] --mainline Parent id of merge commit. Defaults to 1 when supplied without arguments [number] @@ -196,6 +200,7 @@ describe('entrypoint cli', () => { ], { waitForString: 'Press ENTER when the conflicts', + timeoutSeconds: 5, } ); const backportDir = getBackportDirPath(); @@ -284,19 +289,33 @@ describe('entrypoint cli', () => { `); }); - describe('ci failure cases', () => { + async function createConfigFile(options: ConfigFileOptions) { + const sandboxPath = getSandboxPath({ filename: __filename }); + const configPath = path.join(sandboxPath, 'config.json'); + await fs.writeFile(configPath, JSON.stringify(options)); + return configPath; + } + + describe('failure cases in json mode (and non-interactive)', () => { it(`when access token is missing`, async () => { - const output = await runBackportViaCli(['--ci']); + const configFilePath = await createConfigFile({}); + const output = await runBackportViaCli([ + '--json', + `--globalConfigFile=${configFilePath}`, + ]); const backportResult = JSON.parse(output) as BackportFailureResponse; expect(backportResult.status).toBe('failure'); - expect(backportResult.errorMessage).toEqual( - 'Access token missing. It must be explicitly supplied when using "--ci" option. Example: --access-token very-secret' - ); + expect(backportResult.errorMessage).toMatchInlineSnapshot(` + "Please update your config file: \\"/Users/sqren/.backport/config.json\\". + It must contain a valid \\"accessToken\\". + + Read more: https://github.com/sqren/backport/blob/main/docs/configuration.md#global-config-backportconfigjson" + `); }); it(`when argument is invalid`, async () => { - const output = await runBackportViaCli(['--ci', '--foo']); + const output = await runBackportViaCli(['--json', '--foo']); const backportResult = JSON.parse(output) as BackportFailureResponse; expect(backportResult.status).toBe('failure'); @@ -305,8 +324,8 @@ describe('entrypoint cli', () => { it('when `--repo` is invalid', async () => { const output = await runBackportViaCli([ + '--json', '--repo=backport-org/backport-e2e-foo', - '--ci', `--accessToken=${accessToken}`, ]); @@ -319,9 +338,9 @@ describe('entrypoint cli', () => { it('when `--sha` is invalid', async () => { const output = await runBackportViaCli([ + '--json', '--repo=backport-org/backport-e2e', '--sha=abcdefg', - '--ci', `--accessToken=${accessToken}`, ]); @@ -334,10 +353,10 @@ describe('entrypoint cli', () => { it('when `--branch` is invalid', async () => { const output = await runBackportViaCli([ + '--json', '--repo=backport-org/backport-e2e', '--pr=9', '--branch=foobar', - '--ci', `--accessToken=${accessToken}`, ]); @@ -358,10 +377,10 @@ describe('entrypoint cli', () => { it('when `--pr` is invalid', async () => { const output = await runBackportViaCli([ + '--json', '--repo=backport-org/backport-e2e', '--pr=900', '--branch=foobar', - '--ci', `--accessToken=${accessToken}`, ]); @@ -372,12 +391,12 @@ describe('entrypoint cli', () => { ); }); - it('when having conflicts', async () => { + it('when having conflicts in non-interactive mode', async () => { const output = await runBackportViaCli([ + '--json', '--repo=backport-org/repo-with-conflicts', '--pr=12', '--branch=7.x', - '--ci', `--accessToken=${accessToken}`, ]); @@ -391,11 +410,11 @@ describe('entrypoint cli', () => { it('when `--source-branch` is invalid', async () => { const output = await runBackportViaCli([ + '--json', '--repo=backport-org/backport-e2e', '--pr=9', '--branch=7.x', '--source-branch=foo', - '--ci', `--accessToken=${accessToken}`, ]); @@ -419,9 +438,9 @@ describe('entrypoint cli', () => { it('when PR is not merged', async () => { const output = await runBackportViaCli([ + '--json', '--repo=backport-org/backport-e2e', '--pr=12', - '--ci', `--accessToken=${accessToken}`, ]); @@ -438,9 +457,9 @@ describe('entrypoint cli', () => { it('when target branch is missing', async () => { const output = await runBackportViaCli([ + '--json', '--repo=backport-org/backport-e2e', '--pr=9', - '--ci', `--accessToken=${accessToken}`, ]); diff --git a/src/test/handleUnbackportedPullRequests.private.test.ts b/src/test/handleUnbackportedPullRequests.private.test.ts index ec888c01..8ee9d693 100644 --- a/src/test/handleUnbackportedPullRequests.private.test.ts +++ b/src/test/handleUnbackportedPullRequests.private.test.ts @@ -49,7 +49,7 @@ describe('Handle unbackported pull requests', () => { pullNumber: 12, targetBranches: ['7.x'], dir: sandboxPath, - ci: true, + interactive: false, publishStatusComment: false, }, }); From e321d51a9298e11927916839a5c4566899142fd9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=B8ren=20Louv-Jansen?= Date: Sun, 13 Mar 2022 08:38:47 +0100 Subject: [PATCH 03/15] Improve output --- package.json | 6 +- src/backportRun.ts | 31 +- src/entrypoint.cli.ts | 4 +- src/options/cliArgs.test.ts | 50 +++- src/options/cliArgs.ts | 20 ++ .../e2e/cli/commit-author.private.test.ts | 6 +- ...different-merge-strategies.private.test.ts | 41 +-- .../e2e/cli/entrypoint.cli.private.test.ts | 280 ++++++++++-------- ...po-with-backportrc-removed.private.test.ts | 18 +- src/test/e2e/cli/runBackportViaCli.ts | 63 ++-- ...st-that-repo-can-be-cloned.private.test.ts | 2 + yarn.lock | 45 ++- 12 files changed, 333 insertions(+), 233 deletions(-) diff --git a/package.json b/package.json index 58c399d6..f4cfd0eb 100644 --- a/package.json +++ b/package.json @@ -65,14 +65,14 @@ }, "dependencies": { "@octokit/rest": "^18.12.0", - "axios": "^0.26.0", + "axios": "^0.26.1", "dedent": "^0.7.0", "del": "^6.0.0", "dotenv": "^16.0.0", "find-up": "^5.0.0", "graphql": "^16.3.0", "graphql-tag": "^2.12.6", - "inquirer": "^8.2.0", + "inquirer": "^8.2.1", "lodash": "^4.17.21", "make-dir": "^3.1.0", "ora": "^5.4.1", @@ -96,7 +96,7 @@ "@types/yargs-parser": "^21.0.0", "@typescript-eslint/eslint-plugin": "^5.14.0", "@typescript-eslint/parser": "^5.14.0", - "eslint": "^8.10.0", + "eslint": "^8.11.0", "eslint-config-prettier": "^8.5.0", "eslint-plugin-import": "^2.25.4", "eslint-plugin-jest": "^26.1.1", diff --git a/src/backportRun.ts b/src/backportRun.ts index d7a04eae..92c73614 100755 --- a/src/backportRun.ts +++ b/src/backportRun.ts @@ -11,12 +11,12 @@ import { ora } from './lib/ora'; import { setupRepo } from './lib/setupRepo'; import { Commit } from './lib/sourceCommit/parseSourceCommit'; import { ConfigFileOptions } from './options/ConfigOptions'; -import { getOptionsFromCliArgs, OptionsFromCliArgs } from './options/cliArgs'; import { - defaultConfigOptions, - getOptions, - ValidConfigOptions, -} from './options/options'; + getEarlyArguments, + getOptionsFromCliArgs, + OptionsFromCliArgs, +} from './options/cliArgs'; +import { getOptions, ValidConfigOptions } from './options/options'; import { runSequentially, Result } from './runSequentially'; export type BackportAbortResponse = { @@ -53,6 +53,12 @@ export async function backportRun({ optionsFromModule?: ConfigFileOptions; exitCodeOnFailure: boolean; }): Promise { + const { interactive, logFilePath } = getEarlyArguments( + processArgs, + optionsFromModule + ); + const logger = initLogger({ interactive, logFilePath }); + let optionsFromCliArgs: OptionsFromCliArgs; try { optionsFromCliArgs = getOptionsFromCliArgs(processArgs); @@ -71,23 +77,11 @@ export async function backportRun({ throw e; } - const { interactive, logFilePath } = { - ...defaultConfigOptions, - ...optionsFromModule, - ...optionsFromCliArgs, - }; - - const logger = initLogger({ interactive, logFilePath }); - let options: ValidConfigOptions | null = null; let commits: Commit[] = []; + const spinner = ora(interactive).start('Initializing...'); try { - const spinner = ora(interactive); - - // don't show spinner for yargs commands that exit the process without stopping the spinner first - spinner.start('Initializing...'); - options = await getOptions({ optionsFromCliArgs, optionsFromModule, @@ -126,6 +120,7 @@ export async function backportRun({ await createStatusComment({ options, backportResponse }); return backportResponse; } catch (e) { + spinner.stop(); let backportResponse: BackportResponse; if ( diff --git a/src/entrypoint.cli.ts b/src/entrypoint.cli.ts index 8cd952e4..c5249627 100644 --- a/src/entrypoint.cli.ts +++ b/src/entrypoint.cli.ts @@ -1,11 +1,11 @@ -import yargsParser from 'yargs-parser'; import { backportRun } from './backportRun'; +import { getEarlyArguments } from './options/cliArgs'; const processArgs = process.argv.slice(2); // this is the entrypoint when running from command line backportRun({ processArgs, exitCodeOnFailure: true }).then( (backportResponse) => { - const { interactive, ls } = yargsParser(processArgs); + const { interactive, ls } = getEarlyArguments(processArgs); if (!interactive || ls) { // eslint-disable-next-line no-console diff --git a/src/options/cliArgs.test.ts b/src/options/cliArgs.test.ts index f918b2b7..6e9b77d8 100644 --- a/src/options/cliArgs.test.ts +++ b/src/options/cliArgs.test.ts @@ -1,4 +1,4 @@ -import { getOptionsFromCliArgs } from './cliArgs'; +import { getEarlyArguments, getOptionsFromCliArgs } from './cliArgs'; describe('getOptionsFromCliArgs', () => { describe('yargs settings', () => { @@ -304,3 +304,51 @@ describe('getOptionsFromCliArgs', () => { }); }); }); + +describe('getEarlyArguments', () => { + describe('interactive', () => { + it('--non-interactive flag', () => { + const { interactive } = getEarlyArguments(['--non-interactive']); + expect(interactive).toEqual(false); + }); + + it('--json flag', () => { + const { interactive } = getEarlyArguments(['--json']); + expect(interactive).toEqual(false); + }); + + it('default', () => { + const { interactive } = getEarlyArguments([]); + expect(interactive).toEqual(true); + }); + + it('setting via module options', () => { + const { interactive } = getEarlyArguments([], { interactive: false }); + expect(interactive).toEqual(false); + }); + }); + + describe('ls', () => { + it('by default', () => { + const { ls } = getEarlyArguments([]); + expect(ls).toEqual(undefined); + }); + + it('--ls flag', () => { + const { ls } = getEarlyArguments(['--ls']); + expect(ls).toEqual(true); + }); + }); + + describe('logFilePath', () => { + it('by default', () => { + const { logFilePath } = getEarlyArguments([]); + expect(logFilePath).toEqual(undefined); + }); + + it('--log-file-path flag', () => { + const { logFilePath } = getEarlyArguments(['--log-file-path']); + expect(logFilePath).toEqual(true); + }); + }); +}); diff --git a/src/options/cliArgs.ts b/src/options/cliArgs.ts index 1786dc3f..ab143dea 100644 --- a/src/options/cliArgs.ts +++ b/src/options/cliArgs.ts @@ -1,5 +1,8 @@ import yargs from 'yargs'; +import yargsParser from 'yargs-parser'; import { excludeUndefined } from '../utils/excludeUndefined'; +import { ConfigFileOptions } from './ConfigOptions'; +import { defaultConfigOptions } from './options'; export type OptionsFromCliArgs = ReturnType; export function getOptionsFromCliArgs(processArgs: readonly string[]) { @@ -457,3 +460,20 @@ export function getOptionsFromCliArgs(processArgs: readonly string[]) { interactive: nonInteractive === true ? false : undefined, }); } + +export function getEarlyArguments( + processArgs: string[], + optionsFromModule?: ConfigFileOptions +) { + const { nonInteractive, json, logFilePath, ls } = yargsParser(processArgs); + const base = { ...defaultConfigOptions, ...optionsFromModule }; + + return { + interactive: + nonInteractive === undefined && json === undefined + ? base.interactive + : !(nonInteractive === true || json === true), + logFilePath, + ls, + }; +} diff --git a/src/test/e2e/cli/commit-author.private.test.ts b/src/test/e2e/cli/commit-author.private.test.ts index d75a7989..19a7b5e9 100644 --- a/src/test/e2e/cli/commit-author.private.test.ts +++ b/src/test/e2e/cli/commit-author.private.test.ts @@ -63,7 +63,7 @@ describe('commit author', () => { '--pr=2', '--dry-run', ], - { waitForString: 'Dry run complete', cwd: sourceRepo, showOra: true } + { cwd: sourceRepo, showOra: true } ); const { authorEmail, authorName } = await getCommitAuthor({ @@ -85,7 +85,7 @@ describe('commit author', () => { '--gitAuthorName="Donald Duck"', '--gitAuthorEmail=duck@disney.com', ], - { waitForString: 'Dry run complete', cwd: sourceRepo, showOra: true } + { cwd: sourceRepo, showOra: true } ); const { authorEmail, authorName } = await getCommitAuthor({ @@ -106,7 +106,7 @@ describe('commit author', () => { '--dry-run', '--reset-author', ], - { waitForString: 'Dry run complete', cwd: sourceRepo, showOra: true } + { cwd: sourceRepo, showOra: true } ); const { authorEmail, authorName } = await getCommitAuthor({ diff --git a/src/test/e2e/cli/different-merge-strategies.private.test.ts b/src/test/e2e/cli/different-merge-strategies.private.test.ts index 2cbe5fda..5ca7b985 100644 --- a/src/test/e2e/cli/different-merge-strategies.private.test.ts +++ b/src/test/e2e/cli/different-merge-strategies.private.test.ts @@ -17,24 +17,24 @@ describe('different-merge-strategies', () => { ); expect(output).toMatchInlineSnapshot(` - "? Select commit (Use arrow keys) - ❯ 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" - `); + "? Select commit (Use arrow keys) + ❯ 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" + `); }); describe('when selecting a merge commit', () => { @@ -53,7 +53,7 @@ describe('different-merge-strategies', () => { '--pr=9', '--dry-run', ], - { waitForString: 'Dry run complete', showOra: true } + { showOra: true } ); }); @@ -62,6 +62,7 @@ describe('different-merge-strategies', () => { "- Initializing... ? Select pull request Merge pull request #9 from backport-org/many-merge-commits ✔ 100% Cloning repository from github.com (one-time operation) + Backporting to 7.x: - Pulling latest changes ✔ Pulling latest changes @@ -101,7 +102,7 @@ describe('different-merge-strategies', () => { '--pr=1', '--dry-run', ], - { waitForString: 'Dry run complete', showOra: true } + { showOra: true } ); }); diff --git a/src/test/e2e/cli/entrypoint.cli.private.test.ts b/src/test/e2e/cli/entrypoint.cli.private.test.ts index b6a2d250..b62cf96e 100644 --- a/src/test/e2e/cli/entrypoint.cli.private.test.ts +++ b/src/test/e2e/cli/entrypoint.cli.private.test.ts @@ -21,6 +21,7 @@ describe('entrypoint cli', () => { const res = await runBackportViaCli([`--version`], { showOra: true, }); + expect(res).toEqual(process.env.npm_package_version); }); @@ -42,6 +43,7 @@ describe('entrypoint cli', () => { const res = await runBackportViaCli([`--help`]); expect(res).toMatchInlineSnapshot(` "entrypoint.cli.ts [args] + Options: -v, --version Show version number [boolean] --accessToken, --accesstoken Github access token [string] @@ -103,34 +105,12 @@ describe('entrypoint cli', () => { -l, --targetPRLabel, --label Add labels to the target (backport) PR [array] --verify Opposite of no-verify [boolean] --help Show help [boolean] + For bugs, feature requests or questions: https://github.com/sqren/backport/issues Or contact me directly: https://twitter.com/sorenlouv" `); }); - it('should output error when branch is missing', async () => { - const res = await runBackportViaCli([ - '--skip-remote-config', - '--repo=backport-org/backport-e2e', - `--accessToken=${accessToken}`, - ]); - expect(res).toMatchInlineSnapshot(` - "Please specify a target branch: \\"--branch 6.1\\". - Read more: https://github.com/sqren/backport/blob/main/docs/configuration.md#project-config-backportrcjson" - `); - }); - - it('should output error when access token is invalid', async () => { - const res = await runBackportViaCli([ - '--branch=foo', - '--repo=foo/bar', - '--accessToken=some-token', - ]); - expect(res).toContain( - 'Please check your access token and make sure it is valid' - ); - }); - it('should list commits based on .git/config when `repoOwner`/`repoName` is missing', async () => { const sandboxPath = getSandboxPath({ filename: __filename }); await resetSandbox(sandboxPath); @@ -147,80 +127,19 @@ describe('entrypoint cli', () => { expect(res).toMatchInlineSnapshot(` "? Select commit (Use arrow keys) - ❯ 1. Add sheep emoji (#9) 7.8 - 2. Change Ulysses to Gretha (conflict) (#8) 7.x - 3. Add 🍏 emoji (#5) 7.x, 7.8 - 4. Add family emoji (#2) 7.x - 5. Add \`backport\` dep - 6. Merge pull request #1 from backport-org/add-heart-emoji - 7. Add ❤️ emoji - 8. Update .backportrc.json - 9. Bump to 8.0.0 + ❯ 1. Add sheep emoji (#9) 7.8 + 2. Change Ulysses to Gretha (conflict) (#8) 7.x + 3. Add 🍏 emoji (#5) 7.x, 7.8 + 4. Add family emoji (#2) 7.x + 5. Add \`backport\` dep + 6. Merge pull request #1 from backport-org/add-heart-emoji + 7. Add ❤️ emoji + 8. Update .backportrc.json + 9. Bump to 8.0.0 10.Add package.json" `); }); - it(`should output error when repo doesn't exist`, async () => { - const res = await runBackportViaCli([ - '--branch=foo', - '--repo=foo/bar', - '--author=sqren', - `--accessToken=${accessToken}`, - ]); - expect(res).toMatchInlineSnapshot( - `"The repository \\"foo/bar\\" doesn't exist"` - ); - }); - - it(`should output error when given branch is invalid`, async () => { - const res = await runBackportViaCli( - [ - '--branch=foo', - '--repo=backport-org/backport-e2e', - '--pr=9', - `--accessToken=${accessToken}`, - ], - { waitForString: "is invalid or doesn't exist" } - ); - expect(res).toMatchInlineSnapshot(` - " - Backporting to foo: - The branch \\"foo\\" is invalid or doesn't exist" - `); - }); - - it(`should output merge conflict`, async () => { - const res = await runBackportViaCli( - [ - '--repo=backport-org/repo-with-conflicts', - '--pr=12', - '--branch=7.x', - `--accessToken=${accessToken}`, - '--dry-run', - ], - { - waitForString: 'Press ENTER when the conflicts', - timeoutSeconds: 5, - } - ); - const backportDir = getBackportDirPath(); - - expect(res.replaceAll(backportDir, '')).toMatchInlineSnapshot(` - " - Backporting to 7.x: - The commit could not be backported due to conflicts - Please fix the conflicts in /repositories/backport-org/repo-with-conflicts - Hint: Before fixing the conflicts manually you should consider backporting the following pull requests to \\"7.x\\": - - Change Barca to Braithwaite (#8) (backport missing) - https://github.com/backport-org/repo-with-conflicts/pull/8 - ? Fix the following conflicts manually: - Conflicting files: - - /repositories/backport-org/repo-with-conflicts/la-liga. - md - Press ENTER when the conflicts are resolved and files are staged (Y/n)" - `); - }); - it(`should list commits from master`, async () => { const output = await runBackportViaCli( [ @@ -235,11 +154,11 @@ describe('entrypoint cli', () => { expect(output).toMatchInlineSnapshot(` "? Select commit (Use arrow keys) - ❯ 1. Add sheep emoji (#9) 7.8 - 2. Change Ulysses to Gretha (conflict) (#8) 7.x - 3. Add 🍏 emoji (#5) 7.x, 7.8 - 4. Add family emoji (#2) 7.x - 5. Add \`backport\` dep + ❯ 1. Add sheep emoji (#9) 7.8 + 2. Change Ulysses to Gretha (conflict) (#8) 7.x + 3. Add 🍏 emoji (#5) 7.x, 7.8 + 4. Add family emoji (#2) 7.x + 5. Add \`backport\` dep 6. Merge pull request #1 from backport-org/add-heart-emoji" `); }); @@ -258,9 +177,9 @@ describe('entrypoint cli', () => { expect(output).toMatchInlineSnapshot(` "? Select commit (Use arrow keys) - ❯ 1. Bump to 8.0.0 - 2. Add package.json - 3. Update .backportrc.json + ❯ 1. Bump to 8.0.0 + 2. Add package.json + 3. Update .backportrc.json 4. Create .backportrc.json" `); }); @@ -280,11 +199,11 @@ describe('entrypoint cli', () => { expect(output).toMatchInlineSnapshot(` "? Select commit (Use arrow keys) - ❯ 1. Add 🍏 emoji (#5) (#6) - 2. Change Ulysses to Carol - 3. Add family emoji (#2) (#4) - 4. Update .backportrc.json - 5. Branch off: 7.9.0 (7.x) + ❯ 1. Add 🍏 emoji (#5) (#6) + 2. Change Ulysses to Carol + 3. Add family emoji (#2) (#4) + 4. Update .backportrc.json + 5. Branch off: 7.9.0 (7.x) 6. Bump to 8.0.0" `); }); @@ -296,6 +215,103 @@ describe('entrypoint cli', () => { return configPath; } + describe('errors in interactive mode (non-json)', () => { + it('when branch is missing', async () => { + const res = await runBackportViaCli([ + '--skip-remote-config', + '--repo=backport-org/backport-e2e', + `--accessToken=${accessToken}`, + ]); + expect(res).toMatchInlineSnapshot(` + "Please specify a target branch: \\"--branch 6.1\\". + + Read more: https://github.com/sqren/backport/blob/main/docs/configuration.md#project-config-backportrcjson" + `); + }); + + it('when supplying invalid argument', async () => { + const res = await runBackportViaCli([`--foo`]); + expect(res).toMatchInlineSnapshot(` + "Unknown argument: foo + Run \\"backport --help\\" to see all options" + `); + }); + + it('when access token is invalid', async () => { + const res = await runBackportViaCli([ + '--branch=foo', + '--repo=foo/bar', + '--accessToken=some-token', + ]); + expect(res).toContain( + 'Please check your access token and make sure it is valid' + ); + }); + + it(`should output error when repo doesn't exist`, async () => { + const res = await runBackportViaCli([ + '--branch=foo', + '--repo=foo/bar', + '--author=sqren', + `--accessToken=${accessToken}`, + ]); + expect(res).toMatchInlineSnapshot( + `"The repository \\"foo/bar\\" doesn't exist"` + ); + }); + + it(`should output error when given branch is invalid`, async () => { + const res = await runBackportViaCli([ + '--branch=foo', + '--repo=backport-org/backport-e2e', + '--pr=9', + `--accessToken=${accessToken}`, + ]); + expect(res).toMatchInlineSnapshot(` + "Backporting to foo: + The branch \\"foo\\" is invalid or doesn't exist" + `); + }); + + it(`should output merge conflict`, async () => { + const res = await runBackportViaCli( + [ + '--repo=backport-org/repo-with-conflicts', + '--pr=12', + '--branch=7.x', + `--accessToken=${accessToken}`, + '--dry-run', + ], + { + waitForString: 'Press ENTER when the conflicts', + timeoutSeconds: 5, + } + ); + const backportDir = getBackportDirPath(); + + expect(res.replaceAll(backportDir, '')).toMatchInlineSnapshot(` + "Backporting to 7.x: + + The commit could not be backported due to conflicts + + Please fix the conflicts in /repositories/backport-org/repo-with-conflicts + Hint: Before fixing the conflicts manually you should consider backporting the following pull requests to \\"7.x\\": + - Change Barca to Braithwaite (#8) (backport missing) + https://github.com/backport-org/repo-with-conflicts/pull/8 + + + ? Fix the following conflicts manually: + + Conflicting files: + - /repositories/backport-org/repo-with-conflicts/la-liga. + md + + + Press ENTER when the conflicts are resolved and files are staged (Y/n)" + `); + }); + }); + describe('failure cases in json mode (and non-interactive)', () => { it(`when access token is missing`, async () => { const configFilePath = await createConfigFile({}); @@ -314,8 +330,42 @@ describe('entrypoint cli', () => { `); }); + it('when target branches cannot be inferred from pull request', async () => { + const output = await runBackportViaCli([ + '--json', + '--repo=backport-org/backport-e2e', + '--pr=9', + `--accessToken=${accessToken}`, + ]); + + const backportResult = JSON.parse(output) as BackportAbortResponse; + expect(backportResult.status).toBe('aborted'); + expect(backportResult.error).toEqual({ + errorContext: { code: 'no-branches-exception' }, + name: 'BackportError', + }); + expect(backportResult.errorMessage).toBe( + 'There are no branches to backport to. Aborting.' + ); + }); + + it(`when target branch and branch label mapping are missing`, async () => { + const output = await runBackportViaCli([ + '--json', + `--access-token=${accessToken}`, + ]); + + const backportResult = JSON.parse(output) as BackportFailureResponse; + expect(backportResult.status).toBe('failure'); + expect(backportResult.errorMessage).toMatchInlineSnapshot(` + "Please specify a target branch: \\"--branch 6.1\\". + + Read more: https://github.com/sqren/backport/blob/main/docs/configuration.md#project-config-backportrcjson" + `); + }); + it(`when argument is invalid`, async () => { - const output = await runBackportViaCli(['--json', '--foo']); + const output = await runBackportViaCli(['--json', '--foo'], {}); const backportResult = JSON.parse(output) as BackportFailureResponse; expect(backportResult.status).toBe('failure'); @@ -454,21 +504,5 @@ describe('entrypoint cli', () => { name: 'BackportError', }); }); - - it('when target branch is missing', async () => { - const output = await runBackportViaCli([ - '--json', - '--repo=backport-org/backport-e2e', - '--pr=9', - `--accessToken=${accessToken}`, - ]); - - const backportResult = JSON.parse(output) as BackportAbortResponse; - expect(backportResult.status).toBe('aborted'); - expect(backportResult.error).toEqual({ - errorContext: { code: 'no-branches-exception' }, - name: 'BackportError', - }); - }); }); }); diff --git a/src/test/e2e/cli/repo-with-backportrc-removed.private.test.ts b/src/test/e2e/cli/repo-with-backportrc-removed.private.test.ts index 625be9e4..b9880b99 100644 --- a/src/test/e2e/cli/repo-with-backportrc-removed.private.test.ts +++ b/src/test/e2e/cli/repo-with-backportrc-removed.private.test.ts @@ -14,15 +14,15 @@ describe('repo-with-backportrc-removed (missing .backportrc.json config file)', ); 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" - `); + "? 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('backports via pr', async () => { diff --git a/src/test/e2e/cli/runBackportViaCli.ts b/src/test/e2e/cli/runBackportViaCli.ts index 71d40a08..53636fda 100644 --- a/src/test/e2e/cli/runBackportViaCli.ts +++ b/src/test/e2e/cli/runBackportViaCli.ts @@ -7,7 +7,7 @@ const TIMEOUT_IN_SECONDS = 15; jest.setTimeout(TIMEOUT_IN_SECONDS * 1000); export function runBackportViaCli( - cliArgs: string[], + backportArgs: string[], { timeoutSeconds = 2, showOra, @@ -22,63 +22,68 @@ export function runBackportViaCli( ) { const tsNodeBinary = path.resolve('./node_modules/.bin/ts-node'); const entrypointFile = path.resolve('./src/entrypoint.cli.ts'); + const cmdArgs = [ + '--transpile-only', + entrypointFile, + '--log-file-path=/dev/null', + ...backportArgs, + ]; - const proc = spawn( - tsNodeBinary, - [ - '--transpile-only', - entrypointFile, - '--log-file-path', - '/dev/null', - ...cliArgs, - ], - { cwd } - ); + const proc = spawn(tsNodeBinary, cmdArgs, { cwd }); return new Promise((resolve, reject) => { let data = ''; - const rejectOnTimeout = debounce( + const postponeTimeout = debounce( () => { + const formattedData = formatData(data); + const cmd = [tsNodeBinary, ...cmdArgs].join(' '); reject( - `Expectation '${waitForString}' not found within ${timeoutSeconds} second in:\n\n${data.toString()}` + waitForString + ? `Expectation '${waitForString}' not found within ${timeoutSeconds} second in:\n\n${formattedData}\n\nCommand: ${cmd}` + : `Timeout. Received:\n${formattedData}\n\nCommand: ${cmd}` ); }, timeoutSeconds * 1000, { maxWait: 15000 } ); - // fail if expectations hasn't been found within 10 seconds + function formatData(data: string) { + return stripAnsi(data.toString()).trim(); + } const onChunk = (chunk: any) => { data += chunk; - const stringfiedData = data.toString(); - rejectOnTimeout(); - - // remove ansi codes and whitespace - const output = stripAnsi(stringfiedData).replace(/\s+$/gm, ''); + const output = formatData(data); - if (!waitForString || output.includes(waitForString)) { - rejectOnTimeout.cancel(); + if (waitForString && output.includes(waitForString)) { + postponeTimeout.cancel(); resolve(output); } }; proc.on('exit', (code) => { - rejectOnTimeout.cancel(); + const output = formatData(data); + postponeTimeout.cancel(); if (code !== null && code === 0) { - resolve(data.toString()); + resolve(output); } else { - reject(code); + resolve(output); } }); - proc.stdout.on('data', onChunk); + proc.stdout.on('data', (chunk: any) => { + postponeTimeout(); + onChunk(chunk); + }); // ora (loading spinner) is redirected to stderr - if (showOra) { - proc.stderr.on('data', onChunk); - } + proc.stderr.on('data', (chunk: any) => { + postponeTimeout(); + if (showOra) { + onChunk(chunk); + } + }); proc.on('error', (err) => { reject(`runBackportViaCli failed with: ${err}`); diff --git a/src/test/e2e/cli/test-that-repo-can-be-cloned.private.test.ts b/src/test/e2e/cli/test-that-repo-can-be-cloned.private.test.ts index cd198070..e2bab30d 100644 --- a/src/test/e2e/cli/test-that-repo-can-be-cloned.private.test.ts +++ b/src/test/e2e/cli/test-that-repo-can-be-cloned.private.test.ts @@ -34,6 +34,7 @@ describe('test-that-repo-can-be-cloned', () => { "- Initializing... ? Select pull request Beginning of a beautiful repo (#1) ✔ 100% Cloning repository from github.com (one-time operation) + Backporting to foo:" `); }); @@ -45,6 +46,7 @@ describe('test-that-repo-can-be-cloned', () => { expect(output).toMatchInlineSnapshot(` "- Initializing... ? Select pull request Beginning of a beautiful repo (#1) + Backporting to foo:" `); }); diff --git a/yarn.lock b/yarn.lock index 14d8a731..98758791 100644 --- a/yarn.lock +++ b/yarn.lock @@ -329,16 +329,16 @@ ts-node "^9" tslib "^2" -"@eslint/eslintrc@^1.2.0": - version "1.2.0" - resolved "https://registry.yarnpkg.com/@eslint/eslintrc/-/eslintrc-1.2.0.tgz#7ce1547a5c46dfe56e1e45c3c9ed18038c721c6a" - integrity sha512-igm9SjJHNEJRiUnecP/1R5T3wKLEJ7pL6e2P+GUSfCd0dGjPYYZve08uzw8L2J8foVHFz+NGu12JxRcU2gGo6w== +"@eslint/eslintrc@^1.2.1": + version "1.2.1" + resolved "https://registry.yarnpkg.com/@eslint/eslintrc/-/eslintrc-1.2.1.tgz#8b5e1c49f4077235516bc9ec7d41378c0f69b8c6" + integrity sha512-bxvbYnBPN1Gibwyp6NrpnFzA3YtRL3BBAyEAFVIpNTm2Rn4Vy87GA5M4aSn3InRrlsbX5N0GW7XIx+U4SAEKdQ== dependencies: ajv "^6.12.4" debug "^4.3.2" espree "^9.3.1" globals "^13.9.0" - ignore "^4.0.6" + ignore "^5.2.0" import-fresh "^3.2.1" js-yaml "^4.1.0" minimatch "^3.0.4" @@ -1282,10 +1282,10 @@ asynckit@^0.4.0: resolved "https://registry.yarnpkg.com/asynckit/-/asynckit-0.4.0.tgz#c79ed97f7f34cb8f2ba1bc9790bcc366474b4b79" integrity sha1-x57Zf380y48robyXkLzDZkdLS3k= -axios@^0.26.0: - version "0.26.0" - resolved "https://registry.yarnpkg.com/axios/-/axios-0.26.0.tgz#9a318f1c69ec108f8cd5f3c3d390366635e13928" - integrity sha512-lKoGLMYtHvFrPVt3r+RBMp9nh34N0M8zEfCWqdWZx6phynIEhQqAdydpyBAAG211zlhX9Rgu08cOamy6XjE5Og== +axios@^0.26.1: + version "0.26.1" + resolved "https://registry.yarnpkg.com/axios/-/axios-0.26.1.tgz#1ede41c51fcf51bbbd6fd43669caaa4f0495aaa9" + integrity sha512-fPwcX4EvnSHuInCMItEhAGnaSEXRBjtzh9fOtsE6E1G6p7vl7edEeZe11QHf18+6+9gR5PbKV/sGKNaD8YaMeA== dependencies: follow-redirects "^1.14.8" @@ -2039,12 +2039,12 @@ eslint-visitor-keys@^3.0.0, eslint-visitor-keys@^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.10.0: - version "8.10.0" - resolved "https://registry.yarnpkg.com/eslint/-/eslint-8.10.0.tgz#931be395eb60f900c01658b278e05b6dae47199d" - integrity sha512-tcI1D9lfVec+R4LE1mNDnzoJ/f71Kl/9Cv4nG47jOueCMBrCCKYXr4AUVS7go6mWYGFD4+EoN6+eXSrEbRzXVw== +eslint@^8.11.0: + version "8.11.0" + resolved "https://registry.yarnpkg.com/eslint/-/eslint-8.11.0.tgz#88b91cfba1356fc10bb9eb592958457dfe09fb37" + integrity sha512-/KRpd9mIRg2raGxHRGwW9ZywYNAClZrHjdueHcrVDuO3a6bj83eoTirCCk0M0yPwOjWYKHwRVRid+xK4F/GHgA== dependencies: - "@eslint/eslintrc" "^1.2.0" + "@eslint/eslintrc" "^1.2.1" "@humanwhocodes/config-array" "^0.9.2" ajv "^6.10.0" chalk "^4.0.0" @@ -2564,11 +2564,6 @@ ieee754@^1.1.13: resolved "https://registry.yarnpkg.com/ieee754/-/ieee754-1.2.1.tgz#8eb7a10a63fff25d15a57b001586d177d1b0d352" integrity sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA== -ignore@^4.0.6: - version "4.0.6" - resolved "https://registry.yarnpkg.com/ignore/-/ignore-4.0.6.tgz#750e3db5862087b4737ebac8207ffd1ef27b25fc" - integrity sha512-cyFDKrqc/YdcWFniJhzI42+AzS+gNwmUzOSFcRCQYwySuBBBy/KjuxWLZ/FHEH6Moq1NizMOBWyTcv8O4OZIMg== - ignore@^5.1.8, ignore@^5.2.0: version "5.2.0" resolved "https://registry.yarnpkg.com/ignore/-/ignore-5.2.0.tgz#6d3bac8fa7fe0d45d9f9be7bac2fc279577e345a" @@ -2613,10 +2608,10 @@ inherits@2, inherits@^2.0.3, inherits@^2.0.4: resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c" integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ== -inquirer@^8.2.0: - version "8.2.0" - resolved "https://registry.yarnpkg.com/inquirer/-/inquirer-8.2.0.tgz#f44f008dd344bbfc4b30031f45d984e034a3ac3a" - integrity sha512-0crLweprevJ02tTuA6ThpoAERAGyVILC4sS74uib58Xf/zSr1/ZWtmm7D5CI+bSQEaA04f0K7idaHpQbSWgiVQ== +inquirer@^8.2.1: + version "8.2.1" + resolved "https://registry.yarnpkg.com/inquirer/-/inquirer-8.2.1.tgz#e00022e3e8930a92662f760f020686530a84671d" + integrity sha512-pxhBaw9cyTFMjwKtkjePWDhvwzvrNGAw7En4hottzlPvz80GZaMZthdDU35aA6/f5FRZf3uhE057q8w1DE3V2g== dependencies: ansi-escapes "^4.2.1" chalk "^4.1.1" @@ -2628,7 +2623,7 @@ inquirer@^8.2.0: mute-stream "0.0.8" ora "^5.4.1" run-async "^2.4.0" - rxjs "^7.2.0" + rxjs "^7.5.5" string-width "^4.1.0" strip-ansi "^6.0.0" through "^2.3.6" @@ -4065,7 +4060,7 @@ run-parallel@^1.1.9: dependencies: queue-microtask "^1.2.2" -rxjs@^7.2.0, rxjs@^7.5.4: +rxjs@^7.2.0, rxjs@^7.5.4, rxjs@^7.5.5: version "7.5.5" resolved "https://registry.yarnpkg.com/rxjs/-/rxjs-7.5.5.tgz#2ebad89af0f560f460ad5cc4213219e1f7dd4e9f" integrity sha512-sy+H0pQofO95VDmFLzyaw9xNJU4KTRSwQIGM6+iG3SypAtCiLDzpeG8sJrNCWn2Up9km+KhkvTdbkrdy+yzZdw== From a83ca5f5b981059aff44e7bad03472dbd9fd3611 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=B8ren=20Louv-Jansen?= Date: Mon, 14 Mar 2022 06:57:53 +0100 Subject: [PATCH 04/15] Fix test --- docs/configuration.md | 20 +++- jest.config.all.js | 2 +- jest.config.mutation.js | 2 +- jest.config.private.js | 2 +- package.json | 2 +- src/lib/github/v3/createStatusComment.test.ts | 65 ++++------ src/lib/github/v3/createStatusComment.ts | 49 ++++---- src/options/ConfigOptions.ts | 3 +- src/options/cliArgs.test.ts | 18 ++- src/options/cliArgs.ts | 5 +- src/options/options.test.ts | 3 +- src/options/options.ts | 3 +- ...different-merge-strategies.private.test.ts | 7 +- .../e2e/cli/entrypoint.cli.private.test.ts | 94 ++++++++------- ...po-with-backportrc-removed.private.test.ts | 8 +- src/test/e2e/cli/runBackportViaCli.ts | 111 ++++++++++-------- ...st-that-repo-can-be-cloned.private.test.ts | 32 +++-- .../module/entrypoint.module.mutation.test.ts | 2 +- ...leUnbackportedPullRequests.private.test.ts | 5 +- 19 files changed, 239 insertions(+), 194 deletions(-) diff --git a/docs/configuration.md b/docs/configuration.md index 6afec41b..6d4a6e64 100644 --- a/docs/configuration.md +++ b/docs/configuration.md @@ -418,9 +418,9 @@ Config: } ``` -#### `publishStatusComment` +#### `publishStatusCommentOnSuccess` -Whether to publish a status comment to Github with the results of the backport or not +Publish a status comment to the source pull request if all backports succeeded Default: `true` @@ -428,7 +428,21 @@ Config: ```json { - "publishStatusComment": false + "publishStatusCommentOnSuccess": false +} +``` + +#### `publishStatusCommentOnFailure` + +Publish a status comment to the source pull request if some backports failed + +Default: `true` + +Config: + +```json +{ + "publishStatusCommentOnFailure": false } ``` diff --git a/jest.config.all.js b/jest.config.all.js index 63136714..64d4cf6e 100644 --- a/jest.config.all.js +++ b/jest.config.all.js @@ -4,6 +4,6 @@ const config = require('./jest.config'); module.exports = { ...config, - modulePathIgnorePatterns: [], + modulePathIgnorePatterns: ['.*/_tmp_sandbox_/.*$'], testSequencer: './jest.testSequencer.js', }; diff --git a/jest.config.mutation.js b/jest.config.mutation.js index ec38a76d..71baf990 100644 --- a/jest.config.mutation.js +++ b/jest.config.mutation.js @@ -6,5 +6,5 @@ module.exports = { // only include "mutation" tests that cannot run on in parallel (like they are on CI) because they mutate shared state testRegex: ['.*.mutation.test.ts$'], - modulePathIgnorePatterns: [], + modulePathIgnorePatterns: ['.*/_tmp_sandbox_/.*$'], }; diff --git a/jest.config.private.js b/jest.config.private.js index d8abf3b4..409dbc99 100644 --- a/jest.config.private.js +++ b/jest.config.private.js @@ -6,5 +6,5 @@ module.exports = { // only include (private) tests that cannot run on CI because they require credentials and thus exclude external contributors testRegex: ['.*.private.test.ts$'], - modulePathIgnorePatterns: [], + modulePathIgnorePatterns: ['.*/_tmp_sandbox_/.*$'], }; diff --git a/package.json b/package.json index f4cfd0eb..9b9ef1d3 100644 --- a/package.json +++ b/package.json @@ -30,7 +30,7 @@ "lint-and-test": "yarn tsc && yarn lint && yarn test-all", "lint": "echo \"Running lint\" && eslint './**/*.{ts,js}'", "start": "ts-node --transpile-only ./src/entrypoint.cli.ts", - "test-all": "jest --config ./jest.config.all.js", + "test-all": "rm -rf src/test/_tmp_sandbox_ && jest --config ./jest.config.all.js", "test-mutation": "jest --config ./jest.config.mutation.js", "test-private": "jest --config ./jest.config.private.js", "test": "jest", diff --git a/src/lib/github/v3/createStatusComment.test.ts b/src/lib/github/v3/createStatusComment.test.ts index 97e59d45..380e99fe 100644 --- a/src/lib/github/v3/createStatusComment.test.ts +++ b/src/lib/github/v3/createStatusComment.test.ts @@ -26,7 +26,8 @@ describe('createStatusComment', () => { repoOwner: 'elastic', accessToken, backportBinary: 'node scripts/backport', - publishStatusComment: true, + publishStatusCommentOnSuccess: true, + publishStatusCommentOnFailure: true, githubApiBaseUrlV3: 'https://api.github.com', interactive: false, } as ValidConfigOptions, @@ -62,8 +63,8 @@ describe('getCommentBody', () => { } as BackportResponse, }); - it('posts a comment when running in non-interactive mode', () => { - const params = getParams({ interactive: false }); + it('posts a comment when `publishStatusCommentOnFailure = true`', () => { + const params = getParams({ publishStatusCommentOnFailure: true }); expect(getCommentBody(params)).toMatchInlineSnapshot(` "## 💔 Backport failed The pull request could not be backported due to the following error: @@ -82,8 +83,8 @@ describe('getCommentBody', () => { `); }); - it('does not post a comment when running manually', () => { - const params = getParams({ interactive: true }); + it('does not post a comment when `publishStatusCommentOnFailure = false`', () => { + const params = getParams({ publishStatusCommentOnFailure: false }); expect(getCommentBody(params)).toBe(undefined); }); }); @@ -117,8 +118,8 @@ describe('getCommentBody', () => { } as BackportResponse, }); - it('posts a comment when in non-interactive mode', () => { - const params = getParams({ interactive: false }); + it('posts a comment when `publishStatusCommentOnSuccess = true`', () => { + const params = getParams({ publishStatusCommentOnSuccess: true }); expect(getCommentBody(params)).toMatchInlineSnapshot(` "## 💚 All backports created successfully @@ -136,23 +137,9 @@ describe('getCommentBody', () => { `); }); - it('posts a comment when running locally', () => { + it('does not post a comment when `publishStatusCommentOnSuccess = false`', () => { const params = getParams({ interactive: true }); - expect(getCommentBody(params)).toMatchInlineSnapshot(` - "## 💚 All backports created successfully - - | Status | Branch | Result | - |:------:|:------:|:------| - |✅|7.x|[](url-to-pr)| - |✅|7.1|[](url-to-pr)| - - Note: Successful backport PRs will be merged automatically after passing CI. - - ### Questions ? - Please refer to the [Backport tool documentation](https://github.com/sqren/backport) - - " - `); + expect(getCommentBody(params)).toMatchInlineSnapshot(`undefined`); }); }); @@ -183,8 +170,8 @@ describe('getCommentBody', () => { } as BackportSuccessResponse, }); - it('posts a comment on CI', () => { - const params = getParams({ interactive: false }); + it('posts a comment when `publishStatusCommentOnFailure = true`', () => { + const params = getParams({ publishStatusCommentOnFailure: true }); expect(getCommentBody(params)).toMatchInlineSnapshot(` "## 💔 All backports failed @@ -206,8 +193,8 @@ describe('getCommentBody', () => { `); }); - it('does not post a comment when running manaully', () => { - const params = getParams({ interactive: true }); + it('does not post a comment when `publishStatusCommentOnFailure = false`', () => { + const params = getParams({ publishStatusCommentOnFailure: false }); expect(getCommentBody(params)).toBe(undefined); }); }); @@ -241,8 +228,8 @@ describe('getCommentBody', () => { } as BackportResponse, }); - it('post a comment when running on CI', () => { - const params = getParams({ interactive: false }); + it('posts a comment when `publishStatusCommentOnFailure = true`', () => { + const params = getParams({ publishStatusCommentOnFailure: true }); expect(getCommentBody(params)).toMatchInlineSnapshot(` "## 💔 Some backports could not be created @@ -266,8 +253,8 @@ describe('getCommentBody', () => { `); }); - it('does not post a comment when running manually because some backports failed', () => { - const params = getParams({ interactive: true }); + it('does not post a comment when running `publishStatusCommentOnFailure = false`', () => { + const params = getParams({ publishStatusCommentOnFailure: false }); expect(getCommentBody(params)).toMatchInlineSnapshot(`undefined`); }); }); @@ -364,8 +351,8 @@ describe('getCommentBody', () => { } as BackportResponse, }); - it('posts a comment when running on CI', () => { - const params = getParams({ interactive: false }); + it('posts a comment when `publishStatusCommentOnFailure = true`', () => { + const params = getParams({ publishStatusCommentOnFailure: true }); expect(getCommentBody(params)).toMatchInlineSnapshot(` "## 💔 Some backports could not be created @@ -390,8 +377,8 @@ describe('getCommentBody', () => { `); }); - it('does not post a comment when running manually because some backports failed', () => { - const params = getParams({ interactive: true }); + it('does not post a comment when `publishStatusCommentOnFailure = false`', () => { + const params = getParams({ publishStatusCommentOnFailure: false }); expect(getCommentBody(params)).toBe(undefined); }); }); @@ -414,8 +401,8 @@ describe('getCommentBody', () => { } as BackportResponse, }); - it('posts a comment when running on CI', () => { - const params = getParams({ interactive: false }); + it('posts a comment when `publishStatusCommentOnFailure = true`', () => { + const params = getParams({ publishStatusCommentOnFailure: true }); expect(getCommentBody(params)).toMatchInlineSnapshot(` "## ⚪ Backport skipped The pull request was not backported as there were no branches to backport to. If this is a mistake, please apply the desired version labels or run the backport tool manually. @@ -433,8 +420,8 @@ describe('getCommentBody', () => { `); }); - it('does not post a comment when running manually', () => { - const params = getParams({ interactive: true }); + it('does not post a comment when `publishStatusCommentOnFailure = false`', () => { + const params = getParams({ publishStatusCommentOnFailure: false }); expect(getCommentBody(params)).toBe(undefined); }); }); diff --git a/src/lib/github/v3/createStatusComment.ts b/src/lib/github/v3/createStatusComment.ts index f3bbc080..b0fe134a 100644 --- a/src/lib/github/v3/createStatusComment.ts +++ b/src/lib/github/v3/createStatusComment.ts @@ -19,10 +19,14 @@ export async function createStatusComment({ repoName, repoOwner, accessToken, - publishStatusComment, + publishStatusCommentOnFailure, + publishStatusCommentOnSuccess, } = options; - if (!publishStatusComment || options.dryRun) { + if ( + (!publishStatusCommentOnFailure && !publishStatusCommentOnSuccess) || + options.dryRun + ) { return; } @@ -73,23 +77,28 @@ export function getCommentBody({ pullNumber: number; backportResponse: BackportResponse; }): string | undefined { - const { repoName, repoOwner, autoMerge } = options; - - // TODO; make setting to specify whether to post comments for successful and failures - if (options.interactive) { - // only post successful backports when running interactively (as opposed to programatically as on ci) - if (backportResponse.status !== 'success') { - return; - } - - // only post a comment if all backports succeeded - const didAllBackportsSucceed = backportResponse.results.every( - (r) => r.status === 'success' - ); + const { + repoName, + repoOwner, + autoMerge, + publishStatusCommentOnSuccess, + publishStatusCommentOnFailure, + } = options; + + // TODO; add new cli args to specify whether to post comments for successful and failures + // eg. in addition to `--noStatusComment` add `--noFailureStatusComment` and `--noSuccessStatusComment` where the former will overwrite the two latter - if (!didAllBackportsSucceed) { - return; - } + const didAllBackportsSucceed = + backportResponse.status === 'success' && + backportResponse.results.every((r) => r.status === 'success'); + + if ( + // dont publish comment if all backports suceeded + (didAllBackportsSucceed && !publishStatusCommentOnSuccess) || + // dont publish comment if some failed + (!didAllBackportsSucceed && !publishStatusCommentOnFailure) + ) { + return; } const packageVersionSection = `\n`; @@ -162,10 +171,6 @@ ${manualBackportCommand}${questionsAndLinkToBackport}${packageVersionSection}`; ? `\n\n| Status | Branch | Result |\n|:------:|:------:|:------|\n|${tableBody}|\n` : ''; - const didAllBackportsSucceed = backportResponse.results.every( - (r) => r.status === 'success' - ); - const didAnyBackportsSucceed = backportResponse.results.some( (r) => r.status === 'success' ); diff --git a/src/options/ConfigOptions.ts b/src/options/ConfigOptions.ts index 5422c825..50640da3 100644 --- a/src/options/ConfigOptions.ts +++ b/src/options/ConfigOptions.ts @@ -51,7 +51,8 @@ type Options = Partial<{ prFilter: string; projectConfigFile: string; // only available via cli and module options (not project or global config) prTitle: string; - publishStatusComment: boolean; + publishStatusCommentOnSuccess: boolean; + publishStatusCommentOnFailure: boolean; pullNumber: number; repoForkOwner: string; repoName: string; diff --git a/src/options/cliArgs.test.ts b/src/options/cliArgs.test.ts index 6e9b77d8..78718da7 100644 --- a/src/options/cliArgs.test.ts +++ b/src/options/cliArgs.test.ts @@ -170,15 +170,27 @@ describe('getOptionsFromCliArgs', () => { }); }); - describe('publishStatusComment', () => { + describe('publishStatusCommentOnFailure', () => { it('defaults to undefined', () => { const res = getOptionsFromCliArgs([]); - expect(res.publishStatusComment).toEqual(undefined); + expect(res.publishStatusCommentOnFailure).toEqual(undefined); }); it('noStatusComment', () => { const res = getOptionsFromCliArgs(['--noStatusComment']); - expect(res.publishStatusComment).toEqual(false); + expect(res.publishStatusCommentOnFailure).toEqual(false); + }); + }); + + describe('publishStatusCommentOnSuccess', () => { + it('defaults to undefined', () => { + const res = getOptionsFromCliArgs([]); + expect(res.publishStatusCommentOnSuccess).toEqual(undefined); + }); + + it('noStatusComment', () => { + const res = getOptionsFromCliArgs(['--noStatusComment']); + expect(res.publishStatusCommentOnSuccess).toEqual(false); }); }); diff --git a/src/options/cliArgs.ts b/src/options/cliArgs.ts index ab143dea..e9f2b86c 100644 --- a/src/options/cliArgs.ts +++ b/src/options/cliArgs.ts @@ -231,6 +231,8 @@ export function getOptionsFromCliArgs(processArgs: readonly string[]) { conflicts: ['cherrypickRef'], }) + // cli-only + // negation of `publishStatusCommentOnSuccess` and `publishStatusCommentOnFailure` .option('noStatusComment', { description: "Don't publish status comment to Github", type: 'boolean', @@ -456,7 +458,8 @@ export function getOptionsFromCliArgs(processArgs: readonly string[]) { cherrypickRef: noCherrypickRef === true ? false : restOptions.cherrypickRef, fork: noFork === true ? false : restOptions.fork, noVerify: verify ?? noVerify, - publishStatusComment: noStatusComment === true ? false : undefined, + publishStatusCommentOnSuccess: noStatusComment === true ? false : undefined, + publishStatusCommentOnFailure: noStatusComment === true ? false : undefined, interactive: nonInteractive === true ? false : undefined, }); } diff --git a/src/options/options.test.ts b/src/options/options.test.ts index 48259241..74bd6a35 100644 --- a/src/options/options.test.ts +++ b/src/options/options.test.ts @@ -225,7 +225,8 @@ describe('getOptions', () => { multipleBranches: true, multipleCommits: false, noVerify: true, - publishStatusComment: true, + publishStatusCommentOnSuccess: true, + publishStatusCommentOnFailure: true, repoForkOwner: 'john.diller', repoName: 'kibana', repoOwner: 'elastic', diff --git a/src/options/options.ts b/src/options/options.ts index 0c5feaa1..8634a2ca 100644 --- a/src/options/options.ts +++ b/src/options/options.ts @@ -43,7 +43,8 @@ export const defaultConfigOptions = { multipleBranches: true, multipleCommits: false, noVerify: true, - publishStatusComment: true, + publishStatusCommentOnSuccess: true, + publishStatusCommentOnFailure: true, resetAuthor: false, reviewers: [] as Array, sourcePRLabels: [] as string[], diff --git a/src/test/e2e/cli/different-merge-strategies.private.test.ts b/src/test/e2e/cli/different-merge-strategies.private.test.ts index 5ca7b985..71dc8687 100644 --- a/src/test/e2e/cli/different-merge-strategies.private.test.ts +++ b/src/test/e2e/cli/different-merge-strategies.private.test.ts @@ -6,14 +6,14 @@ const accessToken = getDevAccessToken(); describe('different-merge-strategies', () => { it('list all commits regardless how they were merged', async () => { - const output = await runBackportViaCli( + const { output } = await runBackportViaCli( [ '--branch=foo', '--repo=backport-org/different-merge-strategies', `--accessToken=${accessToken}`, '-n=20', ], - { waitForString: 'Select commit' } + { waitForString: 'Select commit', timeoutSeconds: 4 } ); expect(output).toMatchInlineSnapshot(` @@ -44,7 +44,7 @@ describe('different-merge-strategies', () => { sandboxPath = getSandboxPath({ filename: __filename }); await resetSandbox(sandboxPath); - output = await runBackportViaCli( + const res = await runBackportViaCli( [ '--branch=7.x', '--repo=backport-org/different-merge-strategies', @@ -55,6 +55,7 @@ describe('different-merge-strategies', () => { ], { showOra: true } ); + output = res.output; }); it('runs to completion without errors', () => { diff --git a/src/test/e2e/cli/entrypoint.cli.private.test.ts b/src/test/e2e/cli/entrypoint.cli.private.test.ts index b62cf96e..05ad94b8 100644 --- a/src/test/e2e/cli/entrypoint.cli.private.test.ts +++ b/src/test/e2e/cli/entrypoint.cli.private.test.ts @@ -7,29 +7,28 @@ import { } from '../../../backportRun'; import { ConfigFileOptions } from '../../../entrypoint.module'; import { exec } from '../../../lib/child-process-promisified'; -import { getBackportDirPath } from '../../../lib/env'; import * as packageVersion from '../../../utils/packageVersion'; import { getDevAccessToken } from '../../private/getDevAccessToken'; import { getSandboxPath, resetSandbox } from '../../sandbox'; import { runBackportViaCli } from './runBackportViaCli'; -// jest.setTimeout(15_000); +jest.setTimeout(10_000); const accessToken = getDevAccessToken(); describe('entrypoint cli', () => { it('--version', async () => { - const res = await runBackportViaCli([`--version`], { + const { output } = await runBackportViaCli([`--version`], { showOra: true, }); - expect(res).toEqual(process.env.npm_package_version); + expect(output).toEqual(process.env.npm_package_version); }); it('-v', async () => { - const res = await runBackportViaCli([`-v`], { + const { output } = await runBackportViaCli([`-v`], { showOra: true, }); - expect(res).toEqual(process.env.npm_package_version); + expect(output).toEqual(process.env.npm_package_version); }); it('PACKAGE_VERSION should match', async () => { @@ -40,8 +39,8 @@ describe('entrypoint cli', () => { }); it('--help', async () => { - const res = await runBackportViaCli([`--help`]); - expect(res).toMatchInlineSnapshot(` + const { output } = await runBackportViaCli([`--help`]); + expect(output).toMatchInlineSnapshot(` "entrypoint.cli.ts [args] Options: @@ -120,12 +119,15 @@ describe('entrypoint cli', () => { { cwd: sandboxPath } ); - const res = await runBackportViaCli([`--accessToken=${accessToken}`], { - cwd: sandboxPath, - waitForString: 'Select commit', - }); + const { output } = await runBackportViaCli( + [`--accessToken=${accessToken}`], + { + cwd: sandboxPath, + waitForString: 'Select commit', + } + ); - expect(res).toMatchInlineSnapshot(` + expect(output).toMatchInlineSnapshot(` "? Select commit (Use arrow keys) ❯ 1. Add sheep emoji (#9) 7.8 2. Change Ulysses to Gretha (conflict) (#8) 7.x @@ -141,7 +143,7 @@ describe('entrypoint cli', () => { }); it(`should list commits from master`, async () => { - const output = await runBackportViaCli( + const { output } = await runBackportViaCli( [ '--branch=foo', '--repo=backport-org/backport-e2e', @@ -164,7 +166,7 @@ describe('entrypoint cli', () => { }); it(`should filter commits by "since" and "until"`, async () => { - const output = await runBackportViaCli( + const { output } = await runBackportViaCli( [ '--branch=foo', '--repo=backport-org/backport-e2e', @@ -185,7 +187,7 @@ describe('entrypoint cli', () => { }); it(`should list commits from 7.x`, async () => { - const output = await runBackportViaCli( + const { output } = await runBackportViaCli( [ '--branch=foo', '--repo=backport-org/backport-e2e', @@ -217,12 +219,12 @@ describe('entrypoint cli', () => { describe('errors in interactive mode (non-json)', () => { it('when branch is missing', async () => { - const res = await runBackportViaCli([ + const { output } = await runBackportViaCli([ '--skip-remote-config', '--repo=backport-org/backport-e2e', `--accessToken=${accessToken}`, ]); - expect(res).toMatchInlineSnapshot(` + expect(output).toMatchInlineSnapshot(` "Please specify a target branch: \\"--branch 6.1\\". Read more: https://github.com/sqren/backport/blob/main/docs/configuration.md#project-config-backportrcjson" @@ -230,56 +232,59 @@ describe('entrypoint cli', () => { }); it('when supplying invalid argument', async () => { - const res = await runBackportViaCli([`--foo`]); - expect(res).toMatchInlineSnapshot(` + const { output } = await runBackportViaCli([`--foo`]); + expect(output).toMatchInlineSnapshot(` "Unknown argument: foo Run \\"backport --help\\" to see all options" `); }); it('when access token is invalid', async () => { - const res = await runBackportViaCli([ + const { output } = await runBackportViaCli([ '--branch=foo', '--repo=foo/bar', '--accessToken=some-token', ]); - expect(res).toContain( + expect(output).toContain( 'Please check your access token and make sure it is valid' ); }); it(`should output error when repo doesn't exist`, async () => { - const res = await runBackportViaCli([ + const { output } = await runBackportViaCli([ '--branch=foo', '--repo=foo/bar', '--author=sqren', `--accessToken=${accessToken}`, ]); - expect(res).toMatchInlineSnapshot( + expect(output).toMatchInlineSnapshot( `"The repository \\"foo/bar\\" doesn't exist"` ); }); it(`should output error when given branch is invalid`, async () => { - const res = await runBackportViaCli([ + const { output } = await runBackportViaCli([ '--branch=foo', '--repo=backport-org/backport-e2e', '--pr=9', `--accessToken=${accessToken}`, ]); - expect(res).toMatchInlineSnapshot(` + expect(output).toMatchInlineSnapshot(` "Backporting to foo: The branch \\"foo\\" is invalid or doesn't exist" `); }); it(`should output merge conflict`, async () => { - const res = await runBackportViaCli( + const backportDir = getSandboxPath({ filename: __filename }); + await resetSandbox(backportDir); + const { output } = await runBackportViaCli( [ '--repo=backport-org/repo-with-conflicts', '--pr=12', '--branch=7.x', `--accessToken=${accessToken}`, + `--dir=${backportDir}`, '--dry-run', ], { @@ -287,14 +292,14 @@ describe('entrypoint cli', () => { timeoutSeconds: 5, } ); - const backportDir = getBackportDirPath(); - expect(res.replaceAll(backportDir, '')).toMatchInlineSnapshot(` + expect(output.replaceAll(backportDir, '')) + .toMatchInlineSnapshot(` "Backporting to 7.x: The commit could not be backported due to conflicts - Please fix the conflicts in /repositories/backport-org/repo-with-conflicts + Please fix the conflicts in Hint: Before fixing the conflicts manually you should consider backporting the following pull requests to \\"7.x\\": - Change Barca to Braithwaite (#8) (backport missing) https://github.com/backport-org/repo-with-conflicts/pull/8 @@ -303,8 +308,8 @@ describe('entrypoint cli', () => { ? Fix the following conflicts manually: Conflicting files: - - /repositories/backport-org/repo-with-conflicts/la-liga. - md + - / + la-liga.md Press ENTER when the conflicts are resolved and files are staged (Y/n)" @@ -315,12 +320,13 @@ describe('entrypoint cli', () => { describe('failure cases in json mode (and non-interactive)', () => { it(`when access token is missing`, async () => { const configFilePath = await createConfigFile({}); - const output = await runBackportViaCli([ + const { output, code } = await runBackportViaCli([ '--json', `--globalConfigFile=${configFilePath}`, ]); const backportResult = JSON.parse(output) as BackportFailureResponse; + expect(code).toBe(1); expect(backportResult.status).toBe('failure'); expect(backportResult.errorMessage).toMatchInlineSnapshot(` "Please update your config file: \\"/Users/sqren/.backport/config.json\\". @@ -331,13 +337,14 @@ describe('entrypoint cli', () => { }); it('when target branches cannot be inferred from pull request', async () => { - const output = await runBackportViaCli([ + const { output, code } = await runBackportViaCli([ '--json', '--repo=backport-org/backport-e2e', '--pr=9', `--accessToken=${accessToken}`, ]); + expect(code).toBe(0); const backportResult = JSON.parse(output) as BackportAbortResponse; expect(backportResult.status).toBe('aborted'); expect(backportResult.error).toEqual({ @@ -350,11 +357,12 @@ describe('entrypoint cli', () => { }); it(`when target branch and branch label mapping are missing`, async () => { - const output = await runBackportViaCli([ + const { output, code } = await runBackportViaCli([ '--json', `--access-token=${accessToken}`, ]); + expect(code).toBe(1); const backportResult = JSON.parse(output) as BackportFailureResponse; expect(backportResult.status).toBe('failure'); expect(backportResult.errorMessage).toMatchInlineSnapshot(` @@ -365,7 +373,7 @@ describe('entrypoint cli', () => { }); it(`when argument is invalid`, async () => { - const output = await runBackportViaCli(['--json', '--foo'], {}); + const { output } = await runBackportViaCli(['--json', '--foo'], {}); const backportResult = JSON.parse(output) as BackportFailureResponse; expect(backportResult.status).toBe('failure'); @@ -373,7 +381,7 @@ describe('entrypoint cli', () => { }); it('when `--repo` is invalid', async () => { - const output = await runBackportViaCli([ + const { output } = await runBackportViaCli([ '--json', '--repo=backport-org/backport-e2e-foo', `--accessToken=${accessToken}`, @@ -387,7 +395,7 @@ describe('entrypoint cli', () => { }); it('when `--sha` is invalid', async () => { - const output = await runBackportViaCli([ + const { output } = await runBackportViaCli([ '--json', '--repo=backport-org/backport-e2e', '--sha=abcdefg', @@ -402,7 +410,7 @@ describe('entrypoint cli', () => { }); it('when `--branch` is invalid', async () => { - const output = await runBackportViaCli([ + const { output } = await runBackportViaCli([ '--json', '--repo=backport-org/backport-e2e', '--pr=9', @@ -426,7 +434,7 @@ describe('entrypoint cli', () => { }); it('when `--pr` is invalid', async () => { - const output = await runBackportViaCli([ + const { output } = await runBackportViaCli([ '--json', '--repo=backport-org/backport-e2e', '--pr=900', @@ -442,7 +450,7 @@ describe('entrypoint cli', () => { }); it('when having conflicts in non-interactive mode', async () => { - const output = await runBackportViaCli([ + const { output } = await runBackportViaCli([ '--json', '--repo=backport-org/repo-with-conflicts', '--pr=12', @@ -459,7 +467,7 @@ describe('entrypoint cli', () => { }); it('when `--source-branch` is invalid', async () => { - const output = await runBackportViaCli([ + const { output } = await runBackportViaCli([ '--json', '--repo=backport-org/backport-e2e', '--pr=9', @@ -487,7 +495,7 @@ describe('entrypoint cli', () => { }); it('when PR is not merged', async () => { - const output = await runBackportViaCli([ + const { output } = await runBackportViaCli([ '--json', '--repo=backport-org/backport-e2e', '--pr=12', diff --git a/src/test/e2e/cli/repo-with-backportrc-removed.private.test.ts b/src/test/e2e/cli/repo-with-backportrc-removed.private.test.ts index b9880b99..f25e0671 100644 --- a/src/test/e2e/cli/repo-with-backportrc-removed.private.test.ts +++ b/src/test/e2e/cli/repo-with-backportrc-removed.private.test.ts @@ -4,7 +4,7 @@ const accessToken = getDevAccessToken(); describe('repo-with-backportrc-removed (missing .backportrc.json config file)', () => { it('lists commits', async () => { - const output = await runBackportViaCli( + const { output } = await runBackportViaCli( [ '--branch=foo', '--repo=backport-org/repo-with-backportrc-removed', @@ -26,7 +26,7 @@ describe('repo-with-backportrc-removed (missing .backportrc.json config file)', }); it('backports via pr', async () => { - const output = await runBackportViaCli( + const { output } = await runBackportViaCli( [ '--branch=production', '--repo=backport-org/repo-with-backportrc-removed', @@ -35,7 +35,6 @@ describe('repo-with-backportrc-removed (missing .backportrc.json config file)', '--dry-run', ], { - waitForString: `Dry run complete`, showOra: true, } ); @@ -46,7 +45,7 @@ describe('repo-with-backportrc-removed (missing .backportrc.json config file)', }); it('backport by commit sha', async () => { - const output = await runBackportViaCli( + const { output } = await runBackportViaCli( [ '--branch=production', '--repo=backport-org/repo-with-backportrc-removed', @@ -55,7 +54,6 @@ describe('repo-with-backportrc-removed (missing .backportrc.json config file)', '--dry-run', ], { - waitForString: `Dry run complete`, showOra: true, } ); diff --git a/src/test/e2e/cli/runBackportViaCli.ts b/src/test/e2e/cli/runBackportViaCli.ts index 53636fda..2b0f4b7c 100644 --- a/src/test/e2e/cli/runBackportViaCli.ts +++ b/src/test/e2e/cli/runBackportViaCli.ts @@ -2,9 +2,9 @@ import { spawn } from 'child_process'; import path from 'path'; import { debounce } from 'lodash'; import stripAnsi from 'strip-ansi'; +import { getSandboxPath, resetSandbox } from '../../sandbox'; -const TIMEOUT_IN_SECONDS = 15; -jest.setTimeout(TIMEOUT_IN_SECONDS * 1000); +jest.setTimeout(15_000); export function runBackportViaCli( backportArgs: string[], @@ -22,73 +22,80 @@ export function runBackportViaCli( ) { const tsNodeBinary = path.resolve('./node_modules/.bin/ts-node'); const entrypointFile = path.resolve('./src/entrypoint.cli.ts'); + const randomString = Math.random().toString(36).slice(2); + const sandboxPath = getSandboxPath({ + filename: __filename, + specname: randomString, + }); + resetSandbox(sandboxPath); + const cmdArgs = [ '--transpile-only', entrypointFile, '--log-file-path=/dev/null', + ...(backportArgs.some((arg) => arg.includes('--dir')) + ? [] + : [`--dir=${sandboxPath}`]), ...backportArgs, ]; const proc = spawn(tsNodeBinary, cmdArgs, { cwd }); - return new Promise((resolve, reject) => { - let data = ''; - - const postponeTimeout = debounce( - () => { - const formattedData = formatData(data); - const cmd = [tsNodeBinary, ...cmdArgs].join(' '); - reject( - waitForString - ? `Expectation '${waitForString}' not found within ${timeoutSeconds} second in:\n\n${formattedData}\n\nCommand: ${cmd}` - : `Timeout. Received:\n${formattedData}\n\nCommand: ${cmd}` - ); - }, - timeoutSeconds * 1000, - { maxWait: 15000 } - ); + return new Promise<{ output: string; code: number | null }>( + (resolve, reject) => { + let data = ''; - function formatData(data: string) { - return stripAnsi(data.toString()).trim(); - } - - const onChunk = (chunk: any) => { - data += chunk; - const output = formatData(data); + const postponeTimeout = debounce( + () => { + const formattedData = formatData(data); + const cmd = [tsNodeBinary, ...cmdArgs].join(' '); + reject( + waitForString + ? `Expectation '${waitForString}' not found within ${timeoutSeconds} second in:\n\n${formattedData}\n\nCommand: ${cmd}` + : `Timeout. Received:\n${formattedData}\n\nCommand: ${cmd}` + ); + }, + timeoutSeconds * 1000, + { maxWait: 15000 } + ); - if (waitForString && output.includes(waitForString)) { - postponeTimeout.cancel(); - resolve(output); + function formatData(data: string) { + return stripAnsi(data.toString()).trim(); } - }; - proc.on('exit', (code) => { - const output = formatData(data); - postponeTimeout.cancel(); - if (code !== null && code === 0) { - resolve(output); - } else { - resolve(output); - } - }); + const onChunk = (chunk: any) => { + data += chunk; + const output = formatData(data); + + if (waitForString && output.includes(waitForString)) { + postponeTimeout.cancel(); + resolve({ output, code: null }); + } + }; - proc.stdout.on('data', (chunk: any) => { - postponeTimeout(); - onChunk(chunk); - }); + proc.on('exit', (code) => { + postponeTimeout.cancel(); + resolve({ output: formatData(data), code }); + }); - // ora (loading spinner) is redirected to stderr - proc.stderr.on('data', (chunk: any) => { - postponeTimeout(); - if (showOra) { + proc.stdout.on('data', (chunk: any) => { + postponeTimeout(); onChunk(chunk); - } - }); + }); + + // ora (loading spinner) is redirected to stderr + proc.stderr.on('data', (chunk: any) => { + postponeTimeout(); + if (showOra) { + onChunk(chunk); + } + }); - proc.on('error', (err) => { - reject(`runBackportViaCli failed with: ${err}`); - }); - }).finally(() => { + proc.on('error', (err) => { + reject(`runBackportViaCli failed with: ${err}`); + }); + } + ).finally(() => { proc.kill(); }); } diff --git a/src/test/e2e/cli/test-that-repo-can-be-cloned.private.test.ts b/src/test/e2e/cli/test-that-repo-can-be-cloned.private.test.ts index e2bab30d..f44f53a7 100644 --- a/src/test/e2e/cli/test-that-repo-can-be-cloned.private.test.ts +++ b/src/test/e2e/cli/test-that-repo-can-be-cloned.private.test.ts @@ -16,18 +16,18 @@ describe('test-that-repo-can-be-cloned', () => { return runBackportViaCli( [ '--repo=backport-org/test-that-repo-can-be-cloned', - '--branch=foo', + '--branch=production', '--pr=1', `--dir=${sandboxPath}`, '--dry-run', `--accessToken=${accessToken}`, ], - { showOra: true, waitForString: 'Backporting to foo:' } + { showOra: true } ); } it('clones the repo remote repo', async () => { - const output = await run(); + const { output } = await run(); expect(output).toContain('Cloning repository from github.com'); expect(output).toMatchInlineSnapshot(` @@ -35,19 +35,29 @@ describe('test-that-repo-can-be-cloned', () => { ? Select pull request Beginning of a beautiful repo (#1) ✔ 100% Cloning repository from github.com (one-time operation) - Backporting to foo:" + Backporting to production: + - Pulling latest changes + ✔ Pulling latest changes + - Cherry-picking: Beginning of a beautiful repo (#1) + ✔ Cherry-picking: Beginning of a beautiful repo (#1) + ✔ Dry run complete" `); }); it('does not clone again on subsequent runs', async () => { - const output = await run(); + 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:" + Backporting to production: + - Pulling latest changes + ✔ Pulling latest changes + - Cherry-picking: Beginning of a beautiful repo (#1) + ✔ Cherry-picking: Beginning of a beautiful repo (#1) + ✔ Dry run complete" `); }); }); @@ -70,22 +80,18 @@ describe('test-that-repo-can-be-cloned', () => { function run() { return runBackportViaCli( [ - '--branch=foo', + '--branch=production', '--pr=1', `--dir=${backportRepo}`, '--dry-run', `--accessToken=${accessToken}`, ], - { - cwd: sourceRepo, - showOra: true, - waitForString: 'Backporting to foo:', - } + { cwd: sourceRepo, showOra: true } ); } it('clones using the local repo', async () => { - const output = await run(); + const { output } = await run(); expect(output).toEqual( expect.stringMatching( diff --git a/src/test/e2e/module/entrypoint.module.mutation.test.ts b/src/test/e2e/module/entrypoint.module.mutation.test.ts index ef5ade2a..b6dd600f 100644 --- a/src/test/e2e/module/entrypoint.module.mutation.test.ts +++ b/src/test/e2e/module/entrypoint.module.mutation.test.ts @@ -8,7 +8,7 @@ jest.unmock('find-up'); jest.unmock('del'); jest.unmock('make-dir'); -jest.setTimeout(15000); +jest.setTimeout(10_000); const accessToken = getDevAccessToken(); const octokit = new Octokit({ auth: accessToken }); diff --git a/src/test/handleUnbackportedPullRequests.private.test.ts b/src/test/handleUnbackportedPullRequests.private.test.ts index 8ee9d693..7a427d11 100644 --- a/src/test/handleUnbackportedPullRequests.private.test.ts +++ b/src/test/handleUnbackportedPullRequests.private.test.ts @@ -4,7 +4,7 @@ import { getSandboxPath, resetSandbox } from './sandbox'; const accessToken = getDevAccessToken(); jest.unmock('find-up'); -jest.setTimeout(15000); +jest.setTimeout(15_000); describe('Handle unbackported pull requests', () => { it('shows missing backports for PR number 8', async () => { @@ -50,7 +50,8 @@ describe('Handle unbackported pull requests', () => { targetBranches: ['7.x'], dir: sandboxPath, interactive: false, - publishStatusComment: false, + publishStatusCommentOnSuccess: false, + publishStatusCommentOnFailure: false, }, }); From 0cc9ad2634a604ddab09e41068b9cef2ea3c91f9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=B8ren=20Louv-Jansen?= Date: Mon, 14 Mar 2022 20:55:00 +0100 Subject: [PATCH 05/15] Fix test --- src/test/e2e/cli/entrypoint.cli.private.test.ts | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/src/test/e2e/cli/entrypoint.cli.private.test.ts b/src/test/e2e/cli/entrypoint.cli.private.test.ts index 05ad94b8..4e2e5e51 100644 --- a/src/test/e2e/cli/entrypoint.cli.private.test.ts +++ b/src/test/e2e/cli/entrypoint.cli.private.test.ts @@ -293,13 +293,22 @@ describe('entrypoint cli', () => { } ); - expect(output.replaceAll(backportDir, '')) + //@ts-expect-error + const lineToReplace = output.match( + /Conflicting files:[\s]+- (.*[\s].*)la-liga.md/ + )[1]; + + const lineWithoutBreaks = lineToReplace + .replace(/\s/g, '') + .replace(backportDir, ''); + + expect(output.replace(lineToReplace, lineWithoutBreaks)) .toMatchInlineSnapshot(` "Backporting to 7.x: The commit could not be backported due to conflicts - Please fix the conflicts in + Please fix the conflicts in /Users/sqren/dev/backport/src/test/_tmp_sandbox_/entrypoint.cli.private.test Hint: Before fixing the conflicts manually you should consider backporting the following pull requests to \\"7.x\\": - Change Barca to Braithwaite (#8) (backport missing) https://github.com/backport-org/repo-with-conflicts/pull/8 @@ -308,8 +317,7 @@ describe('entrypoint cli', () => { ? Fix the following conflicts manually: Conflicting files: - - / - la-liga.md + - /la-liga.md Press ENTER when the conflicts are resolved and files are staged (Y/n)" From fd209d59273369671b6d5b0051da4fd74d867236 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=B8ren=20Louv-Jansen?= Date: Mon, 14 Mar 2022 21:12:53 +0100 Subject: [PATCH 06/15] Fix test --- src/test/e2e/cli/entrypoint.cli.private.test.ts | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/test/e2e/cli/entrypoint.cli.private.test.ts b/src/test/e2e/cli/entrypoint.cli.private.test.ts index 4e2e5e51..0845e18a 100644 --- a/src/test/e2e/cli/entrypoint.cli.private.test.ts +++ b/src/test/e2e/cli/entrypoint.cli.private.test.ts @@ -298,17 +298,17 @@ describe('entrypoint cli', () => { /Conflicting files:[\s]+- (.*[\s].*)la-liga.md/ )[1]; - const lineWithoutBreaks = lineToReplace - .replace(/\s/g, '') - .replace(backportDir, ''); + const lineWithoutBreaks = lineToReplace.replace(/\s/g, ''); + const outputReplaced = output + .replace(lineToReplace, lineWithoutBreaks) + .replaceAll(backportDir, ''); - expect(output.replace(lineToReplace, lineWithoutBreaks)) - .toMatchInlineSnapshot(` + expect(outputReplaced).toMatchInlineSnapshot(` "Backporting to 7.x: The commit could not be backported due to conflicts - Please fix the conflicts in /Users/sqren/dev/backport/src/test/_tmp_sandbox_/entrypoint.cli.private.test + Please fix the conflicts in Hint: Before fixing the conflicts manually you should consider backporting the following pull requests to \\"7.x\\": - Change Barca to Braithwaite (#8) (backport missing) https://github.com/backport-org/repo-with-conflicts/pull/8 From 8da402b84bfa02856d9d30b5a19f87cc115ffa76 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=B8ren=20Louv-Jansen?= Date: Mon, 14 Mar 2022 21:20:37 +0100 Subject: [PATCH 07/15] Move BackportError --- src/backportRun.ts | 2 +- src/entrypoint.module.ts | 2 +- src/{errors => lib}/BackportError.ts | 2 +- src/lib/cherrypickAndCreateTargetPullRequest.ts | 2 +- src/lib/getCommits.ts | 2 +- src/lib/getTargetBranches.ts | 2 +- src/lib/git.ts | 2 +- src/lib/github/v3/createPullRequest.ts | 2 +- src/lib/github/v3/createStatusComment.test.ts | 2 +- src/lib/github/v3/createStatusComment.ts | 2 +- src/lib/github/v4/apiRequestV4.test.ts | 2 +- src/lib/github/v4/apiRequestV4.ts | 2 +- src/lib/github/v4/fetchCommits/fetchCommitByPullNumber.ts | 2 +- src/lib/github/v4/fetchCommits/fetchCommitBySha.ts | 2 +- src/lib/github/v4/fetchCommits/fetchCommitsByAuthor.ts | 2 +- .../github/v4/fetchCommits/fetchPullRequestsBySearchQuery.ts | 2 +- src/lib/github/v4/getOptionsFromGithub/getOptionsFromGithub.ts | 2 +- src/lib/github/v4/throwOnInvalidAccessToken.ts | 2 +- src/lib/setupRepo.ts | 2 +- src/options/config/globalConfig.ts | 2 +- src/options/config/readConfigFile.ts | 2 +- src/options/options.ts | 2 +- src/runSequentially.ts | 2 +- 23 files changed, 23 insertions(+), 23 deletions(-) rename src/{errors => lib}/BackportError.ts (95%) diff --git a/src/backportRun.ts b/src/backportRun.ts index 92c73614..a6bb8639 100755 --- a/src/backportRun.ts +++ b/src/backportRun.ts @@ -1,5 +1,5 @@ import chalk from 'chalk'; -import { BackportError } from './errors/BackportError'; +import { BackportError } from './lib/BackportError'; import { getLogfilePath } from './lib/env'; import { getCommits } from './lib/getCommits'; import { getGitConfigAuthor } from './lib/getGitConfigAuthor'; diff --git a/src/entrypoint.module.ts b/src/entrypoint.module.ts index de7b3642..22627b09 100644 --- a/src/entrypoint.module.ts +++ b/src/entrypoint.module.ts @@ -23,7 +23,7 @@ export type { BackportResponse, BackportSuccessResponse, } from './backportRun'; -export { BackportError } from './errors/BackportError'; +export { BackportError } from './lib/BackportError'; export type { Commit } from './lib/sourceCommit/parseSourceCommit'; export type { ConfigFileOptions } from './options/ConfigOptions'; export { fetchRemoteProjectConfig as getRemoteProjectConfig } from './lib/github/v4/fetchRemoteProjectConfig'; diff --git a/src/errors/BackportError.ts b/src/lib/BackportError.ts similarity index 95% rename from src/errors/BackportError.ts rename to src/lib/BackportError.ts index 0f2a13e6..40949e2c 100644 --- a/src/errors/BackportError.ts +++ b/src/lib/BackportError.ts @@ -1,4 +1,4 @@ -import { Commit } from '../lib/sourceCommit/parseSourceCommit'; +import { Commit } from './sourceCommit/parseSourceCommit'; type ErrorContext = | { diff --git a/src/lib/cherrypickAndCreateTargetPullRequest.ts b/src/lib/cherrypickAndCreateTargetPullRequest.ts index 190f25bd..62651d63 100644 --- a/src/lib/cherrypickAndCreateTargetPullRequest.ts +++ b/src/lib/cherrypickAndCreateTargetPullRequest.ts @@ -1,7 +1,7 @@ import chalk from 'chalk'; import { isEmpty, difference } from 'lodash'; -import { BackportError } from '../errors/BackportError'; import { ValidConfigOptions } from '../options/options'; +import { BackportError } from './BackportError'; import { spawnPromise } from './child-process-promisified'; import { getRepoPath } from './env'; import { getCommitsWithoutBackports } from './getCommitsWithoutBackports'; diff --git a/src/lib/getCommits.ts b/src/lib/getCommits.ts index db118801..f3bf0f6b 100644 --- a/src/lib/getCommits.ts +++ b/src/lib/getCommits.ts @@ -1,6 +1,6 @@ import chalk from 'chalk'; -import { BackportError } from '../errors/BackportError'; import { ValidConfigOptions } from '../options/options'; +import { BackportError } from './BackportError'; import { getFirstLine, getShortSha } from './github/commitFormatters'; import { fetchCommitByPullNumber } from './github/v4/fetchCommits/fetchCommitByPullNumber'; import { fetchCommitBySha } from './github/v4/fetchCommits/fetchCommitBySha'; diff --git a/src/lib/getTargetBranches.ts b/src/lib/getTargetBranches.ts index 348c64da..1d29bd48 100644 --- a/src/lib/getTargetBranches.ts +++ b/src/lib/getTargetBranches.ts @@ -1,10 +1,10 @@ import { isEmpty, isString } from 'lodash'; -import { BackportError } from '../errors/BackportError'; import { TargetBranchChoice, TargetBranchChoiceOrString, } from '../options/ConfigOptions'; import { ValidConfigOptions } from '../options/options'; +import { BackportError } from './BackportError'; import { promptForTargetBranches } from './prompts'; import { Commit } from './sourceCommit/parseSourceCommit'; diff --git a/src/lib/git.ts b/src/lib/git.ts index c8e66005..b31999fe 100644 --- a/src/lib/git.ts +++ b/src/lib/git.ts @@ -1,9 +1,9 @@ import { resolve as pathResolve } from 'path'; import { uniq, isEmpty, first, last } from 'lodash'; -import { BackportError } from '../errors/BackportError'; import { ora } from '../lib/ora'; import { ValidConfigOptions } from '../options/options'; import { filterNil } from '../utils/filterEmpty'; +import { BackportError } from './BackportError'; import { spawnPromise, SpawnError, diff --git a/src/lib/github/v3/createPullRequest.ts b/src/lib/github/v3/createPullRequest.ts index c6ed7b25..cd0fce62 100644 --- a/src/lib/github/v3/createPullRequest.ts +++ b/src/lib/github/v3/createPullRequest.ts @@ -1,8 +1,8 @@ import { Octokit } from '@octokit/rest'; -import { BackportError } from '../../../errors/BackportError'; import { ora } from '../../../lib/ora'; import { ValidConfigOptions } from '../../../options/options'; import { PACKAGE_VERSION } from '../../../utils/packageVersion'; +import { BackportError } from '../../BackportError'; import { logger } from '../../logger'; import { Commit } from '../../sourceCommit/parseSourceCommit'; import { getFirstLine, getShortSha } from '../commitFormatters'; diff --git a/src/lib/github/v3/createStatusComment.test.ts b/src/lib/github/v3/createStatusComment.test.ts index 380e99fe..0267a9cb 100644 --- a/src/lib/github/v3/createStatusComment.test.ts +++ b/src/lib/github/v3/createStatusComment.test.ts @@ -3,8 +3,8 @@ import { BackportResponse, BackportSuccessResponse, } from '../../../backportRun'; -import { BackportError } from '../../../errors/BackportError'; import { ValidConfigOptions } from '../../../options/options'; +import { BackportError } from '../../BackportError'; import { createStatusComment, getCommentBody } from './createStatusComment'; describe('createStatusComment', () => { diff --git a/src/lib/github/v3/createStatusComment.ts b/src/lib/github/v3/createStatusComment.ts index b0fe134a..f4f6b8fe 100644 --- a/src/lib/github/v3/createStatusComment.ts +++ b/src/lib/github/v3/createStatusComment.ts @@ -1,9 +1,9 @@ import { Octokit } from '@octokit/rest'; import { BackportResponse } from '../../../backportRun'; -import { BackportError } from '../../../errors/BackportError'; import { ValidConfigOptions } from '../../../options/options'; import { PACKAGE_VERSION } from '../../../utils/packageVersion'; import { redact } from '../../../utils/redact'; +import { BackportError } from '../../BackportError'; import { logger } from '../../logger'; import { getFirstLine } from '../commitFormatters'; diff --git a/src/lib/github/v4/apiRequestV4.test.ts b/src/lib/github/v4/apiRequestV4.test.ts index 53edafc5..15344214 100644 --- a/src/lib/github/v4/apiRequestV4.test.ts +++ b/src/lib/github/v4/apiRequestV4.test.ts @@ -1,7 +1,7 @@ import gql from 'graphql-tag'; import nock from 'nock'; -import { BackportError } from '../../../errors/BackportError'; import { mockGqlRequest } from '../../../test/nockHelpers'; +import { BackportError } from '../../BackportError'; import { apiRequestV4 } from './apiRequestV4'; describe('apiRequestV4', () => { diff --git a/src/lib/github/v4/apiRequestV4.ts b/src/lib/github/v4/apiRequestV4.ts index 56619bfe..fe0d1775 100644 --- a/src/lib/github/v4/apiRequestV4.ts +++ b/src/lib/github/v4/apiRequestV4.ts @@ -1,7 +1,7 @@ import axios, { AxiosResponse } from 'axios'; import { DocumentNode } from 'graphql'; import { print } from 'graphql/language/printer'; -import { BackportError } from '../../../errors/BackportError'; +import { BackportError } from '../../BackportError'; import { logger } from '../../logger'; interface GithubError { diff --git a/src/lib/github/v4/fetchCommits/fetchCommitByPullNumber.ts b/src/lib/github/v4/fetchCommits/fetchCommitByPullNumber.ts index 876d785d..e2fd61f4 100644 --- a/src/lib/github/v4/fetchCommits/fetchCommitByPullNumber.ts +++ b/src/lib/github/v4/fetchCommits/fetchCommitByPullNumber.ts @@ -1,6 +1,6 @@ import gql from 'graphql-tag'; -import { BackportError } from '../../../../errors/BackportError'; import { ValidConfigOptions } from '../../../../options/options'; +import { BackportError } from '../../../BackportError'; import { swallowMissingConfigFileException } from '../../../remoteConfig'; import { Commit, diff --git a/src/lib/github/v4/fetchCommits/fetchCommitBySha.ts b/src/lib/github/v4/fetchCommits/fetchCommitBySha.ts index 76c242fa..69296b1b 100644 --- a/src/lib/github/v4/fetchCommits/fetchCommitBySha.ts +++ b/src/lib/github/v4/fetchCommits/fetchCommitBySha.ts @@ -1,6 +1,6 @@ import gql from 'graphql-tag'; -import { BackportError } from '../../../../errors/BackportError'; import { ValidConfigOptions } from '../../../../options/options'; +import { BackportError } from '../../../BackportError'; import { swallowMissingConfigFileException } from '../../../remoteConfig'; import { Commit, diff --git a/src/lib/github/v4/fetchCommits/fetchCommitsByAuthor.ts b/src/lib/github/v4/fetchCommits/fetchCommitsByAuthor.ts index 642fe20b..cc3a3467 100644 --- a/src/lib/github/v4/fetchCommits/fetchCommitsByAuthor.ts +++ b/src/lib/github/v4/fetchCommits/fetchCommitsByAuthor.ts @@ -1,9 +1,9 @@ import gql from 'graphql-tag'; import { isEmpty, uniqBy, orderBy } from 'lodash'; -import { BackportError } from '../../../../errors/BackportError'; import { ValidConfigOptions } from '../../../../options/options'; import { filterNil } from '../../../../utils/filterEmpty'; import { filterUnmergedCommits } from '../../../../utils/filterUnmergedCommits'; +import { BackportError } from '../../../BackportError'; import { swallowMissingConfigFileException } from '../../../remoteConfig'; import { Commit, diff --git a/src/lib/github/v4/fetchCommits/fetchPullRequestsBySearchQuery.ts b/src/lib/github/v4/fetchCommits/fetchPullRequestsBySearchQuery.ts index f47191ab..15b808c5 100644 --- a/src/lib/github/v4/fetchCommits/fetchPullRequestsBySearchQuery.ts +++ b/src/lib/github/v4/fetchCommits/fetchPullRequestsBySearchQuery.ts @@ -1,7 +1,7 @@ import gql from 'graphql-tag'; import { isEmpty } from 'lodash'; -import { BackportError } from '../../../../errors/BackportError'; import { filterUnmergedCommits } from '../../../../utils/filterUnmergedCommits'; +import { BackportError } from '../../../BackportError'; import { swallowMissingConfigFileException } from '../../../remoteConfig'; import { Commit, diff --git a/src/lib/github/v4/getOptionsFromGithub/getOptionsFromGithub.ts b/src/lib/github/v4/getOptionsFromGithub/getOptionsFromGithub.ts index 2fd2b8b6..6b2f5e81 100644 --- a/src/lib/github/v4/getOptionsFromGithub/getOptionsFromGithub.ts +++ b/src/lib/github/v4/getOptionsFromGithub/getOptionsFromGithub.ts @@ -1,5 +1,5 @@ -import { BackportError } from '../../../../errors/BackportError'; import { ConfigFileOptions } from '../../../../options/ConfigOptions'; +import { BackportError } from '../../../BackportError'; import { getLocalConfigFileCommitDate, isLocalConfigFileUntracked, diff --git a/src/lib/github/v4/throwOnInvalidAccessToken.ts b/src/lib/github/v4/throwOnInvalidAccessToken.ts index 1272af3e..81129eed 100644 --- a/src/lib/github/v4/throwOnInvalidAccessToken.ts +++ b/src/lib/github/v4/throwOnInvalidAccessToken.ts @@ -1,6 +1,6 @@ import { isEmpty, difference } from 'lodash'; -import { BackportError } from '../../../errors/BackportError'; import { maybe } from '../../../utils/maybe'; +import { BackportError } from '../../BackportError'; import { getGlobalConfigPath } from '../../env'; import { GithubV4Exception } from './apiRequestV4'; diff --git a/src/lib/setupRepo.ts b/src/lib/setupRepo.ts index e47dbe2b..55fe11fb 100644 --- a/src/lib/setupRepo.ts +++ b/src/lib/setupRepo.ts @@ -1,6 +1,6 @@ import del = require('del'); -import { BackportError } from '../errors/BackportError'; import { ValidConfigOptions } from '../options/options'; +import { BackportError } from './BackportError'; import { getRepoPath } from './env'; import { addRemote, diff --git a/src/options/config/globalConfig.ts b/src/options/config/globalConfig.ts index 9a7feed2..4e1d2430 100644 --- a/src/options/config/globalConfig.ts +++ b/src/options/config/globalConfig.ts @@ -1,7 +1,7 @@ import { chmod, writeFile } from 'fs/promises'; import path from 'path'; import makeDir from 'make-dir'; -import { BackportError } from '../../errors/BackportError'; +import { BackportError } from '../../lib/BackportError'; import { getBackportDirPath, getGlobalConfigPath } from '../../lib/env'; import { isErrnoError } from '../../utils/isErrnoError'; import { ConfigFileOptions } from '../ConfigOptions'; diff --git a/src/options/config/readConfigFile.ts b/src/options/config/readConfigFile.ts index f8bd6f27..8184d277 100644 --- a/src/options/config/readConfigFile.ts +++ b/src/options/config/readConfigFile.ts @@ -1,6 +1,6 @@ import { readFile } from 'fs/promises'; import stripJsonComments from 'strip-json-comments'; -import { BackportError } from '../../errors/BackportError'; +import { BackportError } from '../../lib/BackportError'; import { excludeUndefined } from '../../utils/excludeUndefined'; import { ConfigFileOptions } from '../ConfigOptions'; diff --git a/src/options/options.ts b/src/options/options.ts index 8634a2ca..434e85a3 100644 --- a/src/options/options.ts +++ b/src/options/options.ts @@ -1,5 +1,5 @@ import { isEmpty } from 'lodash'; -import { BackportError } from '../errors/BackportError'; +import { BackportError } from '../lib/BackportError'; import { getGlobalConfigPath } from '../lib/env'; import { getOptionsFromGithub, diff --git a/src/runSequentially.ts b/src/runSequentially.ts index 77ff39f9..f18a69ba 100755 --- a/src/runSequentially.ts +++ b/src/runSequentially.ts @@ -1,4 +1,4 @@ -import { BackportError } from './errors/BackportError'; +import { BackportError } from './lib/BackportError'; import { cherrypickAndCreateTargetPullRequest } from './lib/cherrypickAndCreateTargetPullRequest'; import { GitConfigAuthor } from './lib/getGitConfigAuthor'; import { logger, consoleLog } from './lib/logger'; From a70ef57369dcb3a22a79073e81f0b0ddd414046f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=B8ren=20Louv-Jansen?= Date: Mon, 14 Mar 2022 21:42:55 +0100 Subject: [PATCH 08/15] Add arg to getGlobalConfigPath --- src/lib/env.ts | 5 ++++- .../getOptionsFromGithub/getOptionsFromGithub.ts | 16 ++++++++++++++-- src/lib/github/v4/throwOnInvalidAccessToken.ts | 6 +++++- src/options/config/globalConfig.ts | 6 +----- src/options/options.ts | 4 ++-- src/scripts/postinstall.ts | 2 +- src/test/e2e/cli/entrypoint.cli.private.test.ts | 9 +++++++-- 7 files changed, 34 insertions(+), 14 deletions(-) diff --git a/src/lib/env.ts b/src/lib/env.ts index 9fbae4f6..1def3f44 100644 --- a/src/lib/env.ts +++ b/src/lib/env.ts @@ -13,7 +13,10 @@ export function getLogfilePath({ logFilePath }: { logFilePath?: string }) { return path.join(homedir(), '.backport', 'backport.log'); } -export function getGlobalConfigPath() { +export function getGlobalConfigPath(globalConfigFile: string | undefined) { + if (globalConfigFile) { + return path.resolve(globalConfigFile); + } return path.join(homedir(), '.backport', 'config.json'); } diff --git a/src/lib/github/v4/getOptionsFromGithub/getOptionsFromGithub.ts b/src/lib/github/v4/getOptionsFromGithub/getOptionsFromGithub.ts index 6b2f5e81..ffd15526 100644 --- a/src/lib/github/v4/getOptionsFromGithub/getOptionsFromGithub.ts +++ b/src/lib/github/v4/getOptionsFromGithub/getOptionsFromGithub.ts @@ -29,8 +29,15 @@ export async function getOptionsFromGithub(options: { repoName: string; repoOwner: string; skipRemoteConfig?: boolean; + globalConfigFile?: string; }) { - const { accessToken, githubApiBaseUrlV4, repoName, repoOwner } = options; + const { + accessToken, + githubApiBaseUrlV4, + repoName, + repoOwner, + globalConfigFile, + } = options; let res: GithubConfigOptionsResponse; @@ -46,7 +53,12 @@ export async function getOptionsFromGithub(options: { throw e; } - throwOnInvalidAccessToken({ error: e, repoName, repoOwner }); + throwOnInvalidAccessToken({ + error: e, + repoName, + repoOwner, + globalConfigFile, + }); res = swallowMissingConfigFileException(e); } diff --git a/src/lib/github/v4/throwOnInvalidAccessToken.ts b/src/lib/github/v4/throwOnInvalidAccessToken.ts index 81129eed..a314643c 100644 --- a/src/lib/github/v4/throwOnInvalidAccessToken.ts +++ b/src/lib/github/v4/throwOnInvalidAccessToken.ts @@ -8,10 +8,12 @@ export function throwOnInvalidAccessToken({ error, repoOwner, repoName, + globalConfigFile, }: { error: GithubV4Exception; repoOwner: string; repoName: string; + globalConfigFile?: string; }) { function getSSOAuthUrl(ssoHeader?: string) { const matches = ssoHeader?.match(/url=(.*)/); @@ -70,7 +72,9 @@ export function throwOnInvalidAccessToken({ case 401: throw new BackportError( - `Please check your access token and make sure it is valid.\nConfig: ${getGlobalConfigPath()}` + `Please check your access token and make sure it is valid.\nConfig: ${getGlobalConfigPath( + globalConfigFile + )}` ); default: diff --git a/src/options/config/globalConfig.ts b/src/options/config/globalConfig.ts index 4e1d2430..199bc47e 100644 --- a/src/options/config/globalConfig.ts +++ b/src/options/config/globalConfig.ts @@ -1,5 +1,4 @@ import { chmod, writeFile } from 'fs/promises'; -import path from 'path'; import makeDir from 'make-dir'; import { BackportError } from '../../lib/BackportError'; import { getBackportDirPath, getGlobalConfigPath } from '../../lib/env'; @@ -12,10 +11,7 @@ export async function getGlobalConfig({ }: { globalConfigFile?: string; } = {}): Promise { - const globalConfigPath = globalConfigFile - ? path.resolve(globalConfigFile) - : getGlobalConfigPath(); - + const globalConfigPath = getGlobalConfigPath(globalConfigFile); await createGlobalConfigAndFolderIfNotExist(globalConfigPath); return readConfigFile(globalConfigPath); } diff --git a/src/options/options.ts b/src/options/options.ts index 434e85a3..d451897f 100644 --- a/src/options/options.ts +++ b/src/options/options.ts @@ -118,7 +118,7 @@ export async function getOptions({ } async function getRequiredOptions(combined: OptionsFromConfigAndCli) { - const { accessToken, repoName, repoOwner } = combined; + const { accessToken, repoName, repoOwner, globalConfigFile } = combined; if (accessToken && repoName && repoOwner) { return { accessToken, repoName, repoOwner }; @@ -126,7 +126,7 @@ async function getRequiredOptions(combined: OptionsFromConfigAndCli) { // require access token if (!accessToken) { - const globalConfigPath = getGlobalConfigPath(); + const globalConfigPath = getGlobalConfigPath(globalConfigFile); throw new BackportError( `Please update your config file: "${globalConfigPath}".\nIt must contain a valid "accessToken".\n\nRead more: ${GLOBAL_CONFIG_DOCS_LINK}` ); diff --git a/src/scripts/postinstall.ts b/src/scripts/postinstall.ts index 1aa9cbef..e45ba9a0 100644 --- a/src/scripts/postinstall.ts +++ b/src/scripts/postinstall.ts @@ -4,7 +4,7 @@ import { createGlobalConfigAndFolderIfNotExist } from '../options/config/globalC export async function postinstall() { try { - const globalConfigPath = getGlobalConfigPath(); + const globalConfigPath = getGlobalConfigPath(undefined); const didCreate = await createGlobalConfigAndFolderIfNotExist( globalConfigPath ); diff --git a/src/test/e2e/cli/entrypoint.cli.private.test.ts b/src/test/e2e/cli/entrypoint.cli.private.test.ts index 0845e18a..c4ce773c 100644 --- a/src/test/e2e/cli/entrypoint.cli.private.test.ts +++ b/src/test/e2e/cli/entrypoint.cli.private.test.ts @@ -336,8 +336,13 @@ describe('entrypoint cli', () => { const backportResult = JSON.parse(output) as BackportFailureResponse; expect(code).toBe(1); expect(backportResult.status).toBe('failure'); - expect(backportResult.errorMessage).toMatchInlineSnapshot(` - "Please update your config file: \\"/Users/sqren/.backport/config.json\\". + expect( + backportResult.errorMessage.replace( + configFilePath, + '' + ) + ).toMatchInlineSnapshot(` + "Please update your config file: \\"\\". It must contain a valid \\"accessToken\\". Read more: https://github.com/sqren/backport/blob/main/docs/configuration.md#global-config-backportconfigjson" From 0a127a4df8671845730da1f5fceac138bd2acae1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=B8ren=20Louv-Jansen?= Date: Mon, 14 Mar 2022 23:08:00 +0100 Subject: [PATCH 09/15] Fix test --- src/lib/env.test.ts | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/src/lib/env.test.ts b/src/lib/env.test.ts index 1cac9f20..a36bccc8 100644 --- a/src/lib/env.test.ts +++ b/src/lib/env.test.ts @@ -7,8 +7,18 @@ describe('env', () => { jest.spyOn(os, 'homedir').mockReturnValue('/myHomeDir'); }); - it('getGlobalConfigPath', () => { - expect(getGlobalConfigPath()).toBe('/myHomeDir/.backport/config.json'); + describe('getGlobalConfigPath', () => { + it('uses homedir when no argument is given', () => { + expect(getGlobalConfigPath(undefined)).toBe( + '/myHomeDir/.backport/config.json' + ); + }); + + it('uses custom config when given', () => { + expect(getGlobalConfigPath('/my/path/to/global/config.json')).toBe( + '/my/path/to/global/config.json' + ); + }); }); it('getRepoPath', () => { From 10e0ca0a4e243429cc636df65f82d46580a32ee7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=B8ren=20Louv-Jansen?= Date: Mon, 14 Mar 2022 23:31:47 +0100 Subject: [PATCH 10/15] Fix linebreaks --- src/lib/cherrypickAndCreateTargetPullRequest.ts | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/src/lib/cherrypickAndCreateTargetPullRequest.ts b/src/lib/cherrypickAndCreateTargetPullRequest.ts index 62651d63..31b9f44a 100644 --- a/src/lib/cherrypickAndCreateTargetPullRequest.ts +++ b/src/lib/cherrypickAndCreateTargetPullRequest.ts @@ -330,12 +330,9 @@ async function listConflictingAndUnstagedFiles({ )}` : ''; - const didConfirm = await confirmPrompt(`${header} - -${conflictSection} -${unstagedSection} - -Press ENTER when the conflicts are resolved and files are staged`); + const didConfirm = await confirmPrompt( + `${header}\n\n${conflictSection}\n${unstagedSection}\n\nPress ENTER when the conflicts are resolved and files are staged` + ); if (!didConfirm) { throw new BackportError({ code: 'abort-exception' }); From 80a741a8cd3fcfb04c8cad2097d72e49063190d8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=B8ren=20Louv-Jansen?= Date: Sat, 19 Mar 2022 08:35:25 +0100 Subject: [PATCH 11/15] Add `publishStatusCommentOnAbort` --- src/lib/github/v3/createStatusComment.test.ts | 11 +++++--- src/lib/github/v3/createStatusComment.ts | 27 +++++++------------ src/options/ConfigOptions.ts | 3 ++- src/options/cliArgs.ts | 1 + src/options/options.test.ts | 3 ++- src/options/options.ts | 3 ++- 6 files changed, 23 insertions(+), 25 deletions(-) diff --git a/src/lib/github/v3/createStatusComment.test.ts b/src/lib/github/v3/createStatusComment.test.ts index 0267a9cb..da79bc14 100644 --- a/src/lib/github/v3/createStatusComment.test.ts +++ b/src/lib/github/v3/createStatusComment.test.ts @@ -401,8 +401,11 @@ describe('getCommentBody', () => { } as BackportResponse, }); - it('posts a comment when `publishStatusCommentOnFailure = true`', () => { - const params = getParams({ publishStatusCommentOnFailure: true }); + it('posts a comment when `publishStatusCommentOnAbort = true`', () => { + const params = getParams({ + publishStatusCommentOnAbort: true, + publishStatusCommentOnFailure: true, + }); expect(getCommentBody(params)).toMatchInlineSnapshot(` "## ⚪ Backport skipped The pull request was not backported as there were no branches to backport to. If this is a mistake, please apply the desired version labels or run the backport tool manually. @@ -420,8 +423,8 @@ describe('getCommentBody', () => { `); }); - it('does not post a comment when `publishStatusCommentOnFailure = false`', () => { - const params = getParams({ publishStatusCommentOnFailure: false }); + it('does not post a comment when `publishStatusCommentOnAbort = false`', () => { + const params = getParams({ publishStatusCommentOnAbort: false }); expect(getCommentBody(params)).toBe(undefined); }); }); diff --git a/src/lib/github/v3/createStatusComment.ts b/src/lib/github/v3/createStatusComment.ts index f4f6b8fe..84f988cc 100644 --- a/src/lib/github/v3/createStatusComment.ts +++ b/src/lib/github/v3/createStatusComment.ts @@ -14,21 +14,7 @@ export async function createStatusComment({ options: ValidConfigOptions; backportResponse: BackportResponse; }): Promise { - const { - githubApiBaseUrlV3, - repoName, - repoOwner, - accessToken, - publishStatusCommentOnFailure, - publishStatusCommentOnSuccess, - } = options; - - if ( - (!publishStatusCommentOnFailure && !publishStatusCommentOnSuccess) || - options.dryRun - ) { - return; - } + const { githubApiBaseUrlV3, repoName, repoOwner, accessToken } = options; try { const octokit = new Octokit({ @@ -81,11 +67,11 @@ export function getCommentBody({ repoName, repoOwner, autoMerge, - publishStatusCommentOnSuccess, + publishStatusCommentOnAbort, publishStatusCommentOnFailure, + publishStatusCommentOnSuccess, } = options; - // TODO; add new cli args to specify whether to post comments for successful and failures // eg. in addition to `--noStatusComment` add `--noFailureStatusComment` and `--noSuccessStatusComment` where the former will overwrite the two latter const didAllBackportsSucceed = @@ -93,10 +79,15 @@ export function getCommentBody({ backportResponse.results.every((r) => r.status === 'success'); if ( + // don't publish on dry-run + options.dryRun || + // don't publish comment regardless if it succeeded or failed + (!publishStatusCommentOnFailure && !publishStatusCommentOnSuccess) || // dont publish comment if all backports suceeded (didAllBackportsSucceed && !publishStatusCommentOnSuccess) || // dont publish comment if some failed - (!didAllBackportsSucceed && !publishStatusCommentOnFailure) + (!didAllBackportsSucceed && !publishStatusCommentOnFailure) || + (backportResponse.status === 'aborted' && !publishStatusCommentOnAbort) ) { return; } diff --git a/src/options/ConfigOptions.ts b/src/options/ConfigOptions.ts index 50640da3..10656049 100644 --- a/src/options/ConfigOptions.ts +++ b/src/options/ConfigOptions.ts @@ -51,8 +51,9 @@ type Options = Partial<{ prFilter: string; projectConfigFile: string; // only available via cli and module options (not project or global config) prTitle: string; - publishStatusCommentOnSuccess: boolean; + publishStatusCommentOnAbort: boolean; publishStatusCommentOnFailure: boolean; + publishStatusCommentOnSuccess: boolean; pullNumber: number; repoForkOwner: string; repoName: string; diff --git a/src/options/cliArgs.ts b/src/options/cliArgs.ts index e9f2b86c..381724e7 100644 --- a/src/options/cliArgs.ts +++ b/src/options/cliArgs.ts @@ -460,6 +460,7 @@ export function getOptionsFromCliArgs(processArgs: readonly string[]) { noVerify: verify ?? noVerify, publishStatusCommentOnSuccess: noStatusComment === true ? false : undefined, publishStatusCommentOnFailure: noStatusComment === true ? false : undefined, + publishStatusCommentOnAbort: noStatusComment === true ? false : undefined, interactive: nonInteractive === true ? false : undefined, }); } diff --git a/src/options/options.test.ts b/src/options/options.test.ts index 74bd6a35..5288ec66 100644 --- a/src/options/options.test.ts +++ b/src/options/options.test.ts @@ -225,8 +225,9 @@ describe('getOptions', () => { multipleBranches: true, multipleCommits: false, noVerify: true, + publishStatusCommentOnAbort: false, publishStatusCommentOnSuccess: true, - publishStatusCommentOnFailure: true, + publishStatusCommentOnFailure: false, repoForkOwner: 'john.diller', repoName: 'kibana', repoOwner: 'elastic', diff --git a/src/options/options.ts b/src/options/options.ts index d451897f..e708223e 100644 --- a/src/options/options.ts +++ b/src/options/options.ts @@ -43,8 +43,9 @@ export const defaultConfigOptions = { multipleBranches: true, multipleCommits: false, noVerify: true, + publishStatusCommentOnAbort: false, publishStatusCommentOnSuccess: true, - publishStatusCommentOnFailure: true, + publishStatusCommentOnFailure: false, resetAuthor: false, reviewers: [] as Array, sourcePRLabels: [] as string[], From 163f2789eee5418f4e2b8c650c9e8bf86996eada Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=B8ren=20Louv-Jansen?= Date: Sat, 19 Mar 2022 08:55:10 +0100 Subject: [PATCH 12/15] Update test output --- .../e2e/cli/entrypoint.cli.private.test.ts | 38 +++++++------------ 1 file changed, 13 insertions(+), 25 deletions(-) diff --git a/src/test/e2e/cli/entrypoint.cli.private.test.ts b/src/test/e2e/cli/entrypoint.cli.private.test.ts index c4ce773c..b9b871f9 100644 --- a/src/test/e2e/cli/entrypoint.cli.private.test.ts +++ b/src/test/e2e/cli/entrypoint.cli.private.test.ts @@ -293,35 +293,23 @@ describe('entrypoint cli', () => { } ); - //@ts-expect-error - const lineToReplace = output.match( - /Conflicting files:[\s]+- (.*[\s].*)la-liga.md/ - )[1]; + // //@ts-expect-error + // const lineToReplace = output.match( + // /Conflicting files:[\s]+- (.*[\s].*)la-liga.md/ + // )[1]; + + // const lineWithoutBreaks = lineToReplace.replace(/\s/g, ''); + // const outputReplaced = output + // .replace(lineToReplace, lineWithoutBreaks) + // .replaceAll(backportDir, ''); - const lineWithoutBreaks = lineToReplace.replace(/\s/g, ''); const outputReplaced = output - .replace(lineToReplace, lineWithoutBreaks) + .replaceAll('\n', '') .replaceAll(backportDir, ''); - expect(outputReplaced).toMatchInlineSnapshot(` - "Backporting to 7.x: - - The commit could not be backported due to conflicts - - Please fix the conflicts in - Hint: Before fixing the conflicts manually you should consider backporting the following pull requests to \\"7.x\\": - - Change Barca to Braithwaite (#8) (backport missing) - https://github.com/backport-org/repo-with-conflicts/pull/8 - - - ? Fix the following conflicts manually: - - Conflicting files: - - /la-liga.md - - - Press ENTER when the conflicts are resolved and files are staged (Y/n)" - `); + expect(outputReplaced).toMatchInlineSnapshot( + `"Backporting to 7.x:The commit could not be backported due to conflictsPlease fix the conflicts in Hint: Before fixing the conflicts manually you should consider backporting the following pull requests to \\"7.x\\": - Change Barca to Braithwaite (#8) (backport missing) https://github.com/backport-org/repo-with-conflicts/pull/8? Fix the following conflicts manually:Conflicting files: - /la-liga.mdPress ENTER when the conflicts are resolved and files are staged (Y/n)"` + ); }); }); From f070859ef70702254019bff09f03ddd7ffe94716 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=B8ren=20Louv-Jansen?= Date: Sat, 19 Mar 2022 20:07:40 +0100 Subject: [PATCH 13/15] Improve didFail logic --- src/lib/github/v3/createStatusComment.test.ts | 1 - src/lib/github/v3/createStatusComment.ts | 20 +++++++++++++++---- 2 files changed, 16 insertions(+), 5 deletions(-) diff --git a/src/lib/github/v3/createStatusComment.test.ts b/src/lib/github/v3/createStatusComment.test.ts index da79bc14..fa509a8c 100644 --- a/src/lib/github/v3/createStatusComment.test.ts +++ b/src/lib/github/v3/createStatusComment.test.ts @@ -404,7 +404,6 @@ describe('getCommentBody', () => { it('posts a comment when `publishStatusCommentOnAbort = true`', () => { const params = getParams({ publishStatusCommentOnAbort: true, - publishStatusCommentOnFailure: true, }); expect(getCommentBody(params)).toMatchInlineSnapshot(` "## ⚪ Backport skipped diff --git a/src/lib/github/v3/createStatusComment.ts b/src/lib/github/v3/createStatusComment.ts index 84f988cc..1530edb8 100644 --- a/src/lib/github/v3/createStatusComment.ts +++ b/src/lib/github/v3/createStatusComment.ts @@ -78,15 +78,27 @@ export function getCommentBody({ backportResponse.status === 'success' && backportResponse.results.every((r) => r.status === 'success'); + const didBackportFail = + backportResponse.status === 'failure' || + (backportResponse.status === 'success' && + backportResponse.results.some((r) => r.status !== 'success')); + if ( // don't publish on dry-run options.dryRun || - // don't publish comment regardless if it succeeded or failed - (!publishStatusCommentOnFailure && !publishStatusCommentOnSuccess) || + // + // don't publish comment under any circumstances + (!publishStatusCommentOnFailure && + !publishStatusCommentOnSuccess && + !publishStatusCommentOnAbort) || + // // dont publish comment if all backports suceeded (didAllBackportsSucceed && !publishStatusCommentOnSuccess) || - // dont publish comment if some failed - (!didAllBackportsSucceed && !publishStatusCommentOnFailure) || + // + // dont publish comment if operation failed or all backports failed + (didBackportFail && !publishStatusCommentOnFailure) || + // + // dont publish comment if backport was aborted (backportResponse.status === 'aborted' && !publishStatusCommentOnAbort) ) { return; From 167983c170b96fa4d26f5591b7e6dcd852b87679 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=B8ren=20Louv-Jansen?= Date: Sat, 19 Mar 2022 20:09:19 +0100 Subject: [PATCH 14/15] Bump deps --- package.json | 14 +++--- yarn.lock | 132 +++++++++++++++++++++++++++++++++++---------------- 2 files changed, 99 insertions(+), 47 deletions(-) diff --git a/package.json b/package.json index 9b9ef1d3..e94f8024 100644 --- a/package.json +++ b/package.json @@ -81,7 +81,7 @@ "terminal-link": "^2.1.1", "utility-types": "^3.10.0", "winston": "^3.6.0", - "yargs": "^17.3.1", + "yargs": "^17.4.0", "yargs-parser": "^21.0.1" }, "devDependencies": { @@ -89,13 +89,13 @@ "@types/dedent": "^0.7.0", "@types/inquirer": "^8.2.0", "@types/jest": "^27.4.1", - "@types/lodash": "^4.14.179", + "@types/lodash": "^4.14.180", "@types/node": "^17.0.21", "@types/safe-json-stringify": "^1.1.2", - "@types/yargs": "^17.0.9", + "@types/yargs": "^17.0.10", "@types/yargs-parser": "^21.0.0", - "@typescript-eslint/eslint-plugin": "^5.14.0", - "@typescript-eslint/parser": "^5.14.0", + "@typescript-eslint/eslint-plugin": "^5.15.0", + "@typescript-eslint/parser": "^5.15.0", "eslint": "^8.11.0", "eslint-config-prettier": "^8.5.0", "eslint-plugin-import": "^2.25.4", @@ -105,9 +105,9 @@ "husky": "^7.0.4", "jest": "^27.5.1", "jest-snapshot-serializer-ansi": "^1.0.0", - "lint-staged": "^12.3.5", + "lint-staged": "^12.3.7", "nock": "^13.2.4", - "prettier": "^2.5.1", + "prettier": "^2.6.0", "strip-ansi": "^6.0.1", "ts-jest": "^27.1.3", "ts-node": "^10.7.0", diff --git a/yarn.lock b/yarn.lock index 98758791..bccb1746 100644 --- a/yarn.lock +++ b/yarn.lock @@ -951,10 +951,10 @@ resolved "https://registry.yarnpkg.com/@types/json5/-/json5-0.0.29.tgz#ee28707ae94e11d2b827bcbe5270bcea7f3e71ee" integrity sha1-7ihweulOEdK4J7y+UnC86n8+ce4= -"@types/lodash@^4.14.179": - version "4.14.179" - resolved "https://registry.yarnpkg.com/@types/lodash/-/lodash-4.14.179.tgz#490ec3288088c91295780237d2497a3aa9dfb5c5" - integrity sha512-uwc1x90yCKqGcIOAT6DwOSuxnrAbpkdPsUOZtwrXb4D/6wZs+6qG7QnIawDuZWg0sWpxl+ltIKCaLoMlna678w== +"@types/lodash@^4.14.180": + version "4.14.180" + resolved "https://registry.yarnpkg.com/@types/lodash/-/lodash-4.14.180.tgz#4ab7c9ddfc92ec4a887886483bc14c79fb380670" + integrity sha512-XOKXa1KIxtNXgASAnwj7cnttJxS4fksBRywK/9LzRV5YxrF80BXZIGeQSuoESQ/VkUj30Ae0+YcuHc15wJCB2g== "@types/node@*", "@types/node@^17.0.21": version "17.0.21" @@ -1014,21 +1014,21 @@ dependencies: "@types/yargs-parser" "*" -"@types/yargs@^17.0.9": - version "17.0.9" - resolved "https://registry.yarnpkg.com/@types/yargs/-/yargs-17.0.9.tgz#f1f931a4e5ae2c0134dea10f501088636a50b46a" - integrity sha512-Ci8+4/DOtkHRylcisKmVMtmVO5g7weUVCKcsu1sJvF1bn0wExTmbHmhFKj7AnEm0de800iovGhdSKzYnzbaHpg== +"@types/yargs@^17.0.10": + version "17.0.10" + resolved "https://registry.yarnpkg.com/@types/yargs/-/yargs-17.0.10.tgz#591522fce85d8739bca7b8bb90d048e4478d186a" + integrity sha512-gmEaFwpj/7f/ROdtIlci1R1VYU1J4j95m8T+Tj3iBgiBFKg1foE/PSl93bBd5T9LDXNPo8UlNN6W0qwD8O5OaA== dependencies: "@types/yargs-parser" "*" -"@typescript-eslint/eslint-plugin@^5.14.0": - version "5.14.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.14.0.tgz#5119b67152356231a0e24b998035288a9cd21335" - integrity sha512-ir0wYI4FfFUDfLcuwKzIH7sMVA+db7WYen47iRSaCGl+HMAZI9fpBwfDo45ZALD3A45ZGyHWDNLhbg8tZrMX4w== +"@typescript-eslint/eslint-plugin@^5.15.0": + version "5.15.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.15.0.tgz#c28ef7f2e688066db0b6a9d95fb74185c114fb9a" + integrity sha512-u6Db5JfF0Esn3tiAKELvoU5TpXVSkOpZ78cEGn/wXtT2RVqs2vkt4ge6N8cRCyw7YVKhmmLDbwI2pg92mlv7cA== dependencies: - "@typescript-eslint/scope-manager" "5.14.0" - "@typescript-eslint/type-utils" "5.14.0" - "@typescript-eslint/utils" "5.14.0" + "@typescript-eslint/scope-manager" "5.15.0" + "@typescript-eslint/type-utils" "5.15.0" + "@typescript-eslint/utils" "5.15.0" debug "^4.3.2" functional-red-black-tree "^1.0.1" ignore "^5.1.8" @@ -1036,14 +1036,14 @@ semver "^7.3.5" tsutils "^3.21.0" -"@typescript-eslint/parser@^5.14.0": - version "5.14.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/parser/-/parser-5.14.0.tgz#7c79f898aa3cff0ceee6f1d34eeed0f034fb9ef3" - integrity sha512-aHJN8/FuIy1Zvqk4U/gcO/fxeMKyoSv/rS46UXMXOJKVsLQ+iYPuXNbpbH7cBLcpSbmyyFbwrniLx5+kutu1pw== +"@typescript-eslint/parser@^5.15.0": + version "5.15.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/parser/-/parser-5.15.0.tgz#95f603f8fe6eca7952a99bfeef9b85992972e728" + integrity sha512-NGAYP/+RDM2sVfmKiKOCgJYPstAO40vPAgACoWPO/+yoYKSgAXIFaBKsV8P0Cc7fwKgvj27SjRNX4L7f4/jCKQ== dependencies: - "@typescript-eslint/scope-manager" "5.14.0" - "@typescript-eslint/types" "5.14.0" - "@typescript-eslint/typescript-estree" "5.14.0" + "@typescript-eslint/scope-manager" "5.15.0" + "@typescript-eslint/types" "5.15.0" + "@typescript-eslint/typescript-estree" "5.15.0" debug "^4.3.2" "@typescript-eslint/scope-manager@5.14.0": @@ -1054,12 +1054,20 @@ "@typescript-eslint/types" "5.14.0" "@typescript-eslint/visitor-keys" "5.14.0" -"@typescript-eslint/type-utils@5.14.0": - version "5.14.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/type-utils/-/type-utils-5.14.0.tgz#711f08105860b12988454e91df433567205a8f0b" - integrity sha512-d4PTJxsqaUpv8iERTDSQBKUCV7Q5yyXjqXUl3XF7Sd9ogNLuKLkxz82qxokqQ4jXdTPZudWpmNtr/JjbbvUixw== +"@typescript-eslint/scope-manager@5.15.0": + version "5.15.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/scope-manager/-/scope-manager-5.15.0.tgz#d97afab5e0abf4018d1289bd711be21676cdd0ee" + integrity sha512-EFiZcSKrHh4kWk0pZaa+YNJosvKE50EnmN4IfgjkA3bTHElPtYcd2U37QQkNTqwMCS7LXeDeZzEqnsOH8chjSg== dependencies: - "@typescript-eslint/utils" "5.14.0" + "@typescript-eslint/types" "5.15.0" + "@typescript-eslint/visitor-keys" "5.15.0" + +"@typescript-eslint/type-utils@5.15.0": + version "5.15.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/type-utils/-/type-utils-5.15.0.tgz#d2c02eb2bdf54d0a645ba3a173ceda78346cf248" + integrity sha512-KGeDoEQ7gHieLydujGEFLyLofipe9PIzfvA/41urz4hv+xVxPEbmMQonKSynZ0Ks2xDhJQ4VYjB3DnRiywvKDA== + dependencies: + "@typescript-eslint/utils" "5.15.0" debug "^4.3.2" tsutils "^3.21.0" @@ -1068,6 +1076,11 @@ resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-5.14.0.tgz#96317cf116cea4befabc0defef371a1013f8ab11" integrity sha512-BR6Y9eE9360LNnW3eEUqAg6HxS9Q35kSIs4rp4vNHRdfg0s+/PgHgskvu5DFTM7G5VKAVjuyaN476LCPrdA7Mw== +"@typescript-eslint/types@5.15.0": + version "5.15.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-5.15.0.tgz#c7bdd103843b1abae97b5518219d3e2a0d79a501" + integrity sha512-yEiTN4MDy23vvsIksrShjNwQl2vl6kJeG9YkVJXjXZnkJElzVK8nfPsWKYxcsGWG8GhurYXP4/KGj3aZAxbeOA== + "@typescript-eslint/typescript-estree@5.14.0": version "5.14.0" resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-5.14.0.tgz#78b7f7385d5b6f2748aacea5c9b7f6ae62058314" @@ -1081,7 +1094,32 @@ semver "^7.3.5" tsutils "^3.21.0" -"@typescript-eslint/utils@5.14.0", "@typescript-eslint/utils@^5.10.0": +"@typescript-eslint/typescript-estree@5.15.0": + version "5.15.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-5.15.0.tgz#81513a742a9c657587ad1ddbca88e76c6efb0aac" + integrity sha512-Hb0e3dGc35b75xLzixM3cSbG1sSbrTBQDfIScqdyvrfJZVEi4XWAT+UL/HMxEdrJNB8Yk28SKxPLtAhfCbBInA== + dependencies: + "@typescript-eslint/types" "5.15.0" + "@typescript-eslint/visitor-keys" "5.15.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.15.0": + version "5.15.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/utils/-/utils-5.15.0.tgz#468510a0974d3ced8342f37e6c662778c277f136" + integrity sha512-081rWu2IPKOgTOhHUk/QfxuFog8m4wxW43sXNOMSCdh578tGJ1PAaWPsj42LOa7pguh173tNlMigsbrHvh/mtA== + dependencies: + "@types/json-schema" "^7.0.9" + "@typescript-eslint/scope-manager" "5.15.0" + "@typescript-eslint/types" "5.15.0" + "@typescript-eslint/typescript-estree" "5.15.0" + eslint-scope "^5.1.1" + eslint-utils "^3.0.0" + +"@typescript-eslint/utils@^5.10.0": version "5.14.0" resolved "https://registry.yarnpkg.com/@typescript-eslint/utils/-/utils-5.14.0.tgz#6c8bc4f384298cbbb32b3629ba7415f9f80dc8c4" integrity sha512-EHwlII5mvUA0UsKYnVzySb/5EE/t03duUTweVy8Zqt3UQXBrpEVY144OTceFKaOe4xQXZJrkptCf7PjEBeGK4w== @@ -1101,6 +1139,14 @@ "@typescript-eslint/types" "5.14.0" eslint-visitor-keys "^3.0.0" +"@typescript-eslint/visitor-keys@5.15.0": + version "5.15.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/visitor-keys/-/visitor-keys-5.15.0.tgz#5669739fbf516df060f978be6a6dce75855a8027" + integrity sha512-+vX5FKtgvyHbmIJdxMJ2jKm9z2BIlXJiuewI8dsDYMp5LzPUcuTT78Ya5iwvQg3VqSVdmxyM8Anj1Jeq7733ZQ== + dependencies: + "@typescript-eslint/types" "5.15.0" + eslint-visitor-keys "^3.0.0" + abab@^2.0.3, abab@^2.0.5: version "2.0.5" resolved "https://registry.yarnpkg.com/abab/-/abab-2.0.5.tgz#c0b678fb32d60fc1219c784d6a826fe385aeb79a" @@ -3402,10 +3448,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.5: - version "12.3.5" - resolved "https://registry.yarnpkg.com/lint-staged/-/lint-staged-12.3.5.tgz#8048ce048c3cac12f57200a06344a54dc91c8fa9" - integrity sha512-oOH36RUs1It7b9U/C7Nl/a0sLfoIBcMB8ramiB3nuJ6brBqzsWiUAFSR5DQ3yyP/OR7XKMpijtgKl2DV1lQ3lA== +lint-staged@^12.3.7: + version "12.3.7" + resolved "https://registry.yarnpkg.com/lint-staged/-/lint-staged-12.3.7.tgz#ad0e2014302f704f9cf2c0ebdb97ac63d0f17be0" + integrity sha512-/S4D726e2GIsDVWIk1XGvheCaDm1SJRQp8efamZFWJxQMVEbOwSysp7xb49Oo73KYCdy97mIWinhlxcoNqIfIQ== dependencies: cli-truncate "^3.1.0" colorette "^2.0.16" @@ -3417,6 +3463,7 @@ lint-staged@^12.3.5: micromatch "^4.0.4" normalize-path "^3.0.0" object-inspect "^1.12.0" + pidtree "^0.5.0" string-argv "^0.3.1" supports-color "^9.2.1" yaml "^1.10.2" @@ -3892,6 +3939,11 @@ picomatch@^2.0.4, picomatch@^2.2.3: resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-2.3.1.tgz#3ba3833733646d9d3e4995946c1365a67fb07a42" integrity sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA== +pidtree@^0.5.0: + version "0.5.0" + resolved "https://registry.yarnpkg.com/pidtree/-/pidtree-0.5.0.tgz#ad5fbc1de78b8a5f99d6fbdd4f6e4eee21d1aca1" + integrity sha512-9nxspIM7OpZuhBxPg73Zvyq7j1QMPMPsGKTqRc2XOaFQauDvoNz9fM1Wdkjmeo7l9GXOZiRs97sPkuayl39wjA== + pirates@^4.0.4: version "4.0.5" resolved "https://registry.yarnpkg.com/pirates/-/pirates-4.0.5.tgz#feec352ea5c3268fb23a37c702ab1699f35a5f3b" @@ -3921,10 +3973,10 @@ prettier-linter-helpers@^1.0.0: dependencies: fast-diff "^1.1.2" -prettier@^2.5.1: - version "2.5.1" - resolved "https://registry.yarnpkg.com/prettier/-/prettier-2.5.1.tgz#fff75fa9d519c54cf0fce328c1017d94546bc56a" - integrity sha512-vBZcPRUR5MZJwoyi3ZoyQlc1rXeEck8KgeC9AwwOn+exuxLxq5toTRDTSaVrXHxelDMHy9zlicw8u66yxoSUFg== +prettier@^2.6.0: + version "2.6.0" + resolved "https://registry.yarnpkg.com/prettier/-/prettier-2.6.0.tgz#12f8f504c4d8ddb76475f441337542fa799207d4" + integrity sha512-m2FgJibYrBGGgQXNzfd0PuDGShJgRavjUoRCw1mZERIWVSXF0iLzLm+aOqTAbLnC3n6JzUhAA8uZnFVghHJ86A== pretty-format@^27.0.0, pretty-format@^27.5.1: version "27.5.1" @@ -4887,10 +4939,10 @@ yargs@^16.2.0: y18n "^5.0.5" yargs-parser "^20.2.2" -yargs@^17.3.1: - version "17.3.1" - resolved "https://registry.yarnpkg.com/yargs/-/yargs-17.3.1.tgz#da56b28f32e2fd45aefb402ed9c26f42be4c07b9" - integrity sha512-WUANQeVgjLbNsEmGk20f+nlHgOqzRFpiGWVaBrYGYIGANIIu3lWjoyi0fNlFmJkvfhCZ6BXINe7/W2O2bV4iaA== +yargs@^17.4.0: + version "17.4.0" + resolved "https://registry.yarnpkg.com/yargs/-/yargs-17.4.0.tgz#9fc9efc96bd3aa2c1240446af28499f0e7593d00" + integrity sha512-WJudfrk81yWFSOkZYpAZx4Nt7V4xp7S/uJkX0CnxovMCt1wCE8LNftPpNuF9X/u9gN5nsD7ycYtRcDf2pL3UiA== dependencies: cliui "^7.0.2" escalade "^3.1.1" From 557fbdc59afdb85b97c2969daaa2799aa9d4054c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=B8ren=20Louv-Jansen?= Date: Sat, 19 Mar 2022 23:54:19 +0100 Subject: [PATCH 15/15] Fixes --- .vscode/launch.json | 11 +-- src/lib/BackportError.ts | 6 +- .../cherrypickAndCreateTargetPullRequest.ts | 2 +- src/lib/github/v3/createStatusComment.test.ts | 70 ++++++++++++++++++- src/lib/github/v3/createStatusComment.ts | 11 +-- 5 files changed, 79 insertions(+), 21 deletions(-) diff --git a/.vscode/launch.json b/.vscode/launch.json index 2dea3e91..2dd50355 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -7,7 +7,7 @@ { "type": "node", "request": "launch", - "name": "TS-node", + "name": "Run file", "program": "${workspaceRoot}/node_modules/.bin/ts-node", "args": ["--transpile-only", "${file}"], "console": "integratedTerminal" @@ -15,15 +15,10 @@ { "type": "node", "request": "launch", - "name": "Run backport (ts-node)", + "name": "Run entrypoint.cli.ts", "program": "${workspaceRoot}/src/entrypoint.cli.ts", "runtimeArgs": ["-r", "ts-node/register/transpile-only"], - "args": [ - "--dry-run", - "--pr=12", - "--branch=7.x", - "--repo=backport-org/repo-with-conflicts" - ], + "args": ["--no-fork", "--pr=96", "--repo=backport-org/backport-demo"], "console": "integratedTerminal" }, { diff --git a/src/lib/BackportError.ts b/src/lib/BackportError.ts index 40949e2c..ffb2278b 100644 --- a/src/lib/BackportError.ts +++ b/src/lib/BackportError.ts @@ -14,7 +14,7 @@ type ErrorContext = message: string; } | { - code: 'no-branches-exception' | 'abort-exception'; + code: 'no-branches-exception' | 'abort-conflict-resolution-exception'; }; function getMessage(errorContext: ErrorContext): string { @@ -25,8 +25,8 @@ function getMessage(errorContext: ErrorContext): string { )}`; case 'no-branches-exception': return 'There are no branches to backport to. Aborting.'; - case 'abort-exception': - return 'Aborted'; + case 'abort-conflict-resolution-exception': + return 'Conflict resolution was aborted by the user'; case 'message-only-exception': return errorContext.message; } diff --git a/src/lib/cherrypickAndCreateTargetPullRequest.ts b/src/lib/cherrypickAndCreateTargetPullRequest.ts index 31b9f44a..d15b5de4 100644 --- a/src/lib/cherrypickAndCreateTargetPullRequest.ts +++ b/src/lib/cherrypickAndCreateTargetPullRequest.ts @@ -335,7 +335,7 @@ async function listConflictingAndUnstagedFiles({ ); if (!didConfirm) { - throw new BackportError({ code: 'abort-exception' }); + throw new BackportError({ code: 'abort-conflict-resolution-exception' }); } const MAX_RETRIES = 100; diff --git a/src/lib/github/v3/createStatusComment.test.ts b/src/lib/github/v3/createStatusComment.test.ts index fa509a8c..17f05913 100644 --- a/src/lib/github/v3/createStatusComment.test.ts +++ b/src/lib/github/v3/createStatusComment.test.ts @@ -383,7 +383,7 @@ describe('getCommentBody', () => { }); }); - describe('when all backports fail due to missing branches', () => { + describe('when backport was aborted due to missing branches', () => { const getParams = (opts: Partial) => ({ options: { repoName: 'kibana', @@ -427,4 +427,72 @@ describe('getCommentBody', () => { expect(getCommentBody(params)).toBe(undefined); }); }); + + describe('when backport was aborted during conflict resolution', () => { + const getParams = (opts: Partial) => ({ + options: { + interactive: true, + repoName: 'kibana', + repoOwner: 'elastic', + autoMerge: true, + backportBinary: 'node scripts/backport', + ...opts, + } as ValidConfigOptions, + pullNumber: 55, + backportResponse: { + status: 'success', + commits: [], + results: [ + { + targetBranch: 'staging', + status: 'handled-error', + error: new BackportError({ + code: 'abort-conflict-resolution-exception', + }), + }, + ], + } as BackportResponse, + }); + + it('posts a comment when `publishStatusCommentOnAbort = true`', () => { + const params = getParams({ + publishStatusCommentOnAbort: true, + }); + expect(getCommentBody(params)).toMatchInlineSnapshot(`undefined`); + }); + + it('does not post a comment when `publishStatusCommentOnAbort = false`', () => { + const params = getParams({ publishStatusCommentOnAbort: false }); + expect(getCommentBody(params)).toBe(undefined); + }); + + it('posts a comment when `publishStatusCommentOnFailure = true`', () => { + const params = getParams({ + publishStatusCommentOnFailure: true, + }); + expect(getCommentBody(params)).toMatchInlineSnapshot(` + "## 💔 All backports failed + + | Status | Branch | Result | + |:------:|:------:|:------| + |❌|staging|Conflict resolution was aborted by the user| + + ### Manual backport + To create the backport manually run: + \`\`\` + node scripts/backport --pr 55 + \`\`\` + + ### Questions ? + Please refer to the [Backport tool documentation](https://github.com/sqren/backport) + + " + `); + }); + + it('does not post a comment when `publishStatusCommentOnFailure = false`', () => { + const params = getParams({ publishStatusCommentOnFailure: false }); + expect(getCommentBody(params)).toBe(undefined); + }); + }); }); diff --git a/src/lib/github/v3/createStatusComment.ts b/src/lib/github/v3/createStatusComment.ts index 1530edb8..111986d1 100644 --- a/src/lib/github/v3/createStatusComment.ts +++ b/src/lib/github/v3/createStatusComment.ts @@ -78,10 +78,10 @@ export function getCommentBody({ backportResponse.status === 'success' && backportResponse.results.every((r) => r.status === 'success'); - const didBackportFail = + const didAllBackportsFail = backportResponse.status === 'failure' || (backportResponse.status === 'success' && - backportResponse.results.some((r) => r.status !== 'success')); + backportResponse.results.every((r) => r.status !== 'success')); if ( // don't publish on dry-run @@ -96,7 +96,7 @@ export function getCommentBody({ (didAllBackportsSucceed && !publishStatusCommentOnSuccess) || // // dont publish comment if operation failed or all backports failed - (didBackportFail && !publishStatusCommentOnFailure) || + (didAllBackportsFail && !publishStatusCommentOnFailure) || // // dont publish comment if backport was aborted (backportResponse.status === 'aborted' && !publishStatusCommentOnAbort) @@ -126,11 +126,6 @@ ${manualBackportCommand}${questionsAndLinkToBackport}${packageVersionSection}`; } const tableBody = backportResponse.results - .filter((result) => { - // only post status updates for successful backports when running in --interactive mode - // TODO: this seems duplicated from above - return !options.interactive || result.status === 'success'; - }) .map((result) => { if (result.status === 'success') { return [