diff --git a/docs/configuration.md b/docs/configuration.md index 23c0035e..9edd760d 100644 --- a/docs/configuration.md +++ b/docs/configuration.md @@ -93,7 +93,7 @@ Config: List of target branches the user can select interactively. The array can contain branch names as strings or objects that also contains the field `checked` which indicates whether the branch should be pre-selected. It is useful to pre-select branches you often backport to. -CLI: `--branch 6.1 --branch 6.0` +CLI: `target-branch-choice ` Config: @@ -117,13 +117,15 @@ Config: Default: `false` -CLI: `--all` +CLI: `--all`, `-a` #### `branchLabelMapping` Pre-select target branch choices based on the source PR labels. -Example: +CLI: N/A + +Config: ```json { @@ -211,6 +213,22 @@ When backporting a merge commit the parent id must be specified. This is directl - Defaults to 1 when no parent id is given: `backport --mainline` - Specifying parent id: `backport --mainline 2` +#### maxNumber + +Number of commits that will be listed for the user to choose from. + +Default: 10 + +CLI: `--max-number `, `--number `, `-n ` + +Config: + +```json +{ + "maxNumber": 20 +} +``` + #### `multipleCommits` `true`: you will be able to select multiple commits to backport. You will use `` to select, and `` to confirm you selection. @@ -227,17 +245,31 @@ Default: `false` Default: `true` +#### path + +Only list commits touching files under the specified path + +CLI: `--path `, `-p ` + +Config: + +```json +{ + "path": "my/folder" +} +``` + #### `prTitle` -Pull request title pattern. +Title for the target pull request Template values: - `{targetBranch}`: Branch the backport PR will be targeting -- `{commitMessages}`: Multiple commits will be concatenated and separated by pipes (`|`). +- `{commitMessages}`: Message of backported commit. For multiple commits the messages will be separated by pipes (`|`). Default: `"[{targetBranch}] {commitMessages}"` -CLI: `--pr-title "{commitMessages} backport for {targetBranch}"` +CLI: `--pr-title ""`, `--title "<title>"` Config: @@ -249,7 +281,7 @@ Config: #### `prDescription` -Text that will be appended to the pull request description. +Text that will be appended to the description of the target pull request For people who often need to add the same description to PRs they can create a bash alias: @@ -257,7 +289,7 @@ For people who often need to add the same description to PRs they can create a b alias backport-skip-ci='backport --prDescription "[skip-ci]"' ``` -CLI: `--pr-description "skip-ci"` +CLI: `--pr-description "<text>"`, `--description "<text>"` Config: @@ -267,6 +299,38 @@ Config: } ``` +#### `prFilter` + +Filter source pull requests by any [Github query](https://help.github.com/en/github/searching-for-information-on-github/understanding-the-search-syntax). Text with whitespace [must contain escaped quotes](https://help.github.com/en/github/searching-for-information-on-github/understanding-the-search-syntax#use-quotation-marks-for-queries-with-whitespace). + +CLI: `--pr-filter "<query>"` + +Config: + +```json +{ + "prFilter": "label: \"Backport Needed\"" +} +``` + +#### `pullNumber` + +Backport a pull request by specifying its number + +CLI: `--pull-number "<number>"`, `--pr "<number>"` + +#### `resetAuthor` + +Change the author of the backported commit to the current user + +CLI: `--reset-author` + +#### `sha` + +Backport a commit by specifying its commit sha + +CLI: `--sha "<sha>"`, `--commit "<sha>"` + #### `sourceBranch` By default the list of commits will be sourced from the repository's default branch (mostly "master"). Use `sourceBranch` to list and backport commits from other branches than the default. @@ -287,7 +351,7 @@ Config: Labels that will be added to the source (original) pull request. This can be useful if you, at a later time, want to find the PRs that were already backported. -CLI: `--source-pr-labels was-backported` +CLI: `--source-pr-labels <label>` Config: @@ -297,11 +361,25 @@ Config: } ``` +#### `targetBranches` + +Overrides `targetBranchChoices` so instead of displaying a prompt with target branches to choose from, the selected commit(s) will be backported directly to the branches defined in `targetBranches` + +CLI: `--target-branches <branch>`, `--branch <branch>`, `-b <branch>` + +Config: + +```json +{ + "targetBranches": ["7.x", "7.7"] +} +``` + #### `targetPRLabels` Labels that will be added to the target (backport) pull request. This can be useful if you, at a later time, want to find the backport PRs. -CLI: `--labels backport --labels apm-team` +CLI: `--target-pr-labels <label>`, `-l <label>` Config: diff --git a/jest.config.js b/jest.config.js index 61ba171e..c89f5710 100644 --- a/jest.config.js +++ b/jest.config.js @@ -3,6 +3,10 @@ module.exports = { setupFiles: ['./src/test/setupFiles/automatic-mocks.ts'], preset: 'ts-jest', testRegex: '(test|src)/.*test.ts$', + + // exclude private tests that requires credentials and can therefore not run on CI for external contributors + modulePathIgnorePatterns: ['.*.private.test.ts$'], + moduleFileExtensions: ['ts', 'js', 'json'], globals: { 'ts-jest': { diff --git a/jest.config.private.js b/jest.config.private.js new file mode 100644 index 00000000..d8abf3b4 --- /dev/null +++ b/jest.config.private.js @@ -0,0 +1,10 @@ +// eslint-disable-next-line @typescript-eslint/no-var-requires +const config = require('./jest.config'); + +module.exports = { + ...config, + + // only include (private) tests that cannot run on CI because they require credentials and thus exclude external contributors + testRegex: ['.*.private.test.ts$'], + modulePathIgnorePatterns: [], +}; diff --git a/package.json b/package.json index 488bf899..e32037d9 100644 --- a/package.json +++ b/package.json @@ -31,6 +31,8 @@ "postinstall": "test -f ./dist/scripts/runPostinstall.js && node ./dist/scripts/runPostinstall.js || echo 'Dist folder missing'", "prepublishOnly": "tsc --project ./tsconfig.prod.json", "test": "jest", + "test-private": "jest --config ./jest.config.private.js", + "test-all": "yarn test && yarn test-private", "start": "ts-node --transpile-only ./src/index.ts" }, "lint-staged": { diff --git a/src/options/cliArgs.test.ts b/src/options/cliArgs.test.ts index b2dab8d1..7f2f5cc0 100644 --- a/src/options/cliArgs.test.ts +++ b/src/options/cliArgs.test.ts @@ -11,7 +11,6 @@ describe('getOptionsFromCliArgs', () => { githubApiBaseUrlV3: 'https://api.github.com', githubApiBaseUrlV4: 'https://api.github.com/graphql', maxNumber: 10, - multiple: false, multipleBranches: true, multipleCommits: false, noVerify: true, @@ -46,7 +45,6 @@ describe('getOptionsFromCliArgs', () => { githubApiBaseUrlV3: 'https://api.github.com', githubApiBaseUrlV4: 'https://api.github.com/graphql', maxNumber: 10, - multiple: false, multipleBranches: true, multipleCommits: false, noVerify: true, diff --git a/src/options/cliArgs.ts b/src/options/cliArgs.ts index 43acce9a..bfccab04 100644 --- a/src/options/cliArgs.ts +++ b/src/options/cliArgs.ts @@ -1,6 +1,15 @@ +import isString from 'lodash.isstring'; import yargs from 'yargs'; import { OptionsFromConfigFiles } from './config/config'; +type Maybe<T> = T | undefined; +type BranchLabelMapping = Record<string, string> | undefined; +type BranchChoiceRaw = string | BranchChoice; +export interface BranchChoice { + name: string; + checked?: boolean; +} + export type OptionsFromCliArgs = ReturnType<typeof getOptionsFromCliArgs>; export function getOptionsFromCliArgs( configOptions: OptionsFromConfigFiles, @@ -14,61 +23,70 @@ export function getOptionsFromCliArgs( }) .usage('$0 [args]') .wrap(Math.max(100, Math.min(120, yargs.terminalWidth()))) + .option('accessToken', { + default: configOptions.accessToken as Maybe<string>, alias: 'accesstoken', description: 'Github access token', type: 'string', }) + + // show users own commits .option('all', { - default: configOptions.all, + default: (configOptions.all ?? false) as boolean, description: 'List all commits', alias: 'a', type: 'boolean', }) + .option('author', { - default: configOptions.author, + default: configOptions.author as Maybe<string>, description: 'Show commits by specific author', type: 'string', }) - .option('maxNumber', { - default: configOptions.maxNumber, - description: 'Number of commits to choose from', - alias: ['number', 'n'], - type: 'number', - }) + .option('dryRun', { default: false, description: 'Perform backport without pushing to Github', type: 'boolean', }) + .option('editor', { - default: configOptions.editor, + default: configOptions.editor as Maybe<string>, description: 'Editor to be opened during conflict resolution', type: 'string', }) + + // push target branch to {username}/{repoName} .option('fork', { - default: configOptions.fork, + default: (configOptions.fork ?? true) as boolean, description: 'Create backports in fork or origin repo', type: 'boolean', }) + .option('gitHostname', { hidden: true, - default: configOptions.gitHostname, + default: (configOptions.gitHostname ?? 'github.com') as string, description: 'Hostname for Github', type: 'string', }) + .option('githubApiBaseUrlV3', { hidden: true, - default: configOptions.githubApiBaseUrlV3, + default: (configOptions.githubApiBaseUrlV3 ?? + 'https://api.github.com') as string, description: `Base url for Github's REST (v3) API`, type: 'string', }) + .option('githubApiBaseUrlV4', { hidden: true, - default: configOptions.githubApiBaseUrlV4, + default: (configOptions.githubApiBaseUrlV4 ?? + 'https://api.github.com/graphql') as string, description: `Base url for Github's GraphQL (v4) API`, type: 'string', }) + .option('mainline', { description: 'Parent id of merge commit. Defaults to 1 when supplied without arguments', @@ -88,107 +106,153 @@ export function getOptionsFromCliArgs( throw new Error(`--mainline must be an integer. Received: ${mainline}`); }, }) + + // display 10 commits to pick from + .option('maxNumber', { + default: (configOptions.maxNumber ?? 10) as number, + description: 'Number of commits to choose from', + alias: ['number', 'n'], + type: 'number', + }) + + // cli-only .option('multiple', { - default: configOptions.multiple, description: 'Select multiple branches/commits', type: 'boolean', }) - .option('multipleCommits', { - default: configOptions.multipleCommits, - description: 'Backport multiple commits', - type: 'boolean', - }) + + // allow picking multiple target branches .option('multipleBranches', { - default: configOptions.multipleBranches, + default: (configOptions.multipleBranches ?? true) as boolean, description: 'Backport to multiple branches', type: 'boolean', }) + + // allow picking multiple commits + .option('multipleCommits', { + default: (configOptions.multipleCommits ?? false) as boolean, + description: 'Backport multiple commits', + type: 'boolean', + }) + .option('noVerify', { - default: configOptions.noVerify, + default: (configOptions.noVerify ?? true) as boolean, description: 'Bypasses the pre-commit and commit-msg hooks', type: 'boolean', }) + .option('path', { - default: configOptions.path, + default: configOptions.path as Maybe<string>, description: 'Only list commits touching files under the specified path', alias: 'p', type: 'string', }) + .option('prTitle', { - default: configOptions.prTitle, + default: (configOptions.prTitle ?? + '[{targetBranch}] {commitMessages}') as string, description: 'Title of pull request', alias: 'title', type: 'string', }) + .option('prDescription', { - default: configOptions.prDescription, + default: configOptions.prDescription as Maybe<string>, description: 'Description to be added to pull request', alias: 'description', type: 'string', }) + + .option('prFilter', { + default: configOptions.prFilter as Maybe<string>, + conflicts: ['pullNumber', 'sha'], + description: `Filter source pull requests by a query`, + type: 'string', + }) + + // cli-only .option('pullNumber', { - conflicts: ['sha', 'sourcePRsFilter'], + conflicts: ['sha', 'prFilter'], description: 'Pull request to backport', - type: 'number', alias: 'pr', + type: 'number', }) + + // cli-only .option('resetAuthor', { default: false, description: 'Set yourself as commit author', type: 'boolean', }) + + // cli-only .option('sha', { - conflicts: ['pullNumber', 'sourcePRsFilter'], + conflicts: ['pullNumber', 'prFilter'], description: 'Commit sha to backport', - type: 'string', alias: 'commit', - }) - .option('sourcePRLabels', { - default: configOptions.sourcePRLabels, - description: 'Add labels to the source (original) PR', - type: 'array', - alias: 'sourcePRLabel', - }) - .option('sourcePRsFilter', { - conflicts: ['pullNumber', 'sha'], - // default: configOptions.githubApiBaseUrlV4, - description: `Filter source pull requests by a query`, - alias: 'pr-filter', type: 'string', }) + .option('sourceBranch', { - default: configOptions.sourceBranch, + default: configOptions.sourceBranch as Maybe<string>, description: `List commits to backport from another branch than master`, type: 'string', }) - .option('verify', { - description: `Opposite of no-verify`, - type: 'boolean', + + .option('sourcePRLabels', { + default: (configOptions.sourcePRLabels ?? []) as string[], + description: 'Add labels to the source (original) PR', + alias: 'sourcePRLabel', + type: 'array', }) + .option('targetBranches', { - default: [] as string[], + default: (configOptions.targetBranches || []) as string[], description: 'Branch(es) to backport to', - type: 'array', alias: ['targetBranch', 'branch', 'b'], + type: 'array', string: true, // ensure `6.0` is not coerced to `6` }) + + .option('targetBranchChoices', { + // backwards-compatability: `branches` was renamed `targetBranchChoices` + default: (configOptions.targetBranchChoices ?? + configOptions.branches ?? + []) as BranchChoiceRaw[], + description: 'List branches to backport to', + alias: 'targetBranchChoice', + type: 'array', + }) + .option('targetPRLabels', { - default: configOptions.targetPRLabels, + // backwards-compatability: `labels` was renamed `targetPRLabels` + default: (configOptions.targetPRLabels ?? + configOptions.labels ?? + []) as string[], description: 'Add labels to the target (backport) PR', alias: ['labels', 'label', 'l'], type: 'array', }) + + // cli-only + .option('verify', { + description: `Opposite of no-verify`, + type: 'boolean', + }) + .option('upstream', { - default: configOptions.upstream, + default: configOptions.upstream as Maybe<string>, description: 'Name of repository', alias: 'up', type: 'string', }) + .option('username', { - default: configOptions.username, + default: configOptions.username as Maybe<string>, description: 'Github username', type: 'string', }) + .option('verbose', { default: false, description: 'Show additional debug information', @@ -202,15 +266,41 @@ export function getOptionsFromCliArgs( ).argv; // eslint-disable-next-line @typescript-eslint/no-unused-vars, no-unused-vars - const { $0, _, verify, ...rest } = cliArgs; + const { $0, _, verify, multiple, ...rest } = cliArgs; return { ...rest, - accessToken: cliArgs.accessToken || configOptions.accessToken, // accessToken should not be displayed in yargs help menu - branchLabelMapping: configOptions.branchLabelMapping, // not available as cli argument - multipleBranches: cliArgs.multipleBranches || cliArgs.multiple, - multipleCommits: cliArgs.multipleCommits || cliArgs.multiple, - noVerify: verify ?? rest.noVerify, // `verify` is a cli-only flag to flip the default of `no-verify` - targetBranchChoices: configOptions.targetBranchChoices, // not available as cli argument + + // `branchLabelMapping` is not available as cli argument + branchLabelMapping: configOptions.branchLabelMapping as BranchLabelMapping, + + // `multiple` is a cli-only flag to override `multipleBranches` and `multipleCommits` + multipleBranches: multiple ?? cliArgs.multipleBranches, + multipleCommits: multiple ?? cliArgs.multipleCommits, + + // `verify` is a cli-only flag to flip the default of `no-verify` + noVerify: verify ?? rest.noVerify, + + // convert from array of primitives to array of object + targetBranchChoices: getTargetBranchChoicesAsObject( + rest.targetBranchChoices + ), }; } + +// in the config `branches` can either be a string or an object. +// We need to transform it so that it is always treated as an object troughout the application +function getTargetBranchChoicesAsObject( + targetBranchChoices: BranchChoiceRaw[] +): BranchChoice[] { + return targetBranchChoices.map((choice) => { + if (isString(choice)) { + return { + name: choice, + checked: false, + }; + } + + return choice; + }); +} diff --git a/src/options/config/config.test.ts b/src/options/config/config.test.ts index 2cc0ee93..78715d78 100644 --- a/src/options/config/config.test.ts +++ b/src/options/config/config.test.ts @@ -11,23 +11,7 @@ describe('getOptionsFromConfigFiles', () => { it('should return default config values', () => { expect(res).toEqual({ accessToken: 'myAccessToken', - all: false, - fork: true, - gitHostname: 'github.com', - githubApiBaseUrlV3: 'https://api.github.com', - githubApiBaseUrlV4: 'https://api.github.com/graphql', - maxNumber: 10, - multiple: false, - multipleBranches: true, - multipleCommits: false, - noVerify: true, - prTitle: '[{targetBranch}] {commitMessages}', - sourcePRLabels: [], - targetBranchChoices: [ - { checked: false, name: '6.0' }, - { checked: false, name: '5.9' }, - ], - targetPRLabels: [], + targetBranchChoices: ['6.0', '5.9'], upstream: 'elastic/backport-demo', username: 'sqren', }); diff --git a/src/options/config/config.ts b/src/options/config/config.ts index d33174c9..49ade51a 100644 --- a/src/options/config/config.ts +++ b/src/options/config/config.ts @@ -1,5 +1,3 @@ -import isString from 'lodash.isstring'; -import { Config } from '../../types/Config'; import { PromiseReturnType } from '../../types/PromiseReturnType'; import { getGlobalConfig } from './globalConfig'; import { getProjectConfig } from './projectConfig'; @@ -12,67 +10,6 @@ export async function getOptionsFromConfigFiles() { getProjectConfig(), getGlobalConfig(), ]); - - const { - // backwards-compatability: `branches` was renamed `targetBranchChoices` - targetBranchChoices, - branches, - - // backwards-compatability: `labels` was renamed `targetPRLabels` - targetPRLabels, - labels, - - // global and project config combined - ...combinedConfig - } = { - ...globalConfig, - ...projectConfig, - }; - - return { - // defaults - all: false, // show users own commits - fork: true, // push target branch to {username}/{repoName} - gitHostname: 'github.com', - githubApiBaseUrlV3: 'https://api.github.com', - githubApiBaseUrlV4: 'https://api.github.com/graphql', - maxNumber: 10, // display 10 commits to pick from - multiple: false, - multipleBranches: true, // allow user to pick multiple target branches - multipleCommits: false, // only let user pick a single commit - noVerify: true, - prTitle: '[{targetBranch}] {commitMessages}', - sourcePRLabels: [] as string[] | never[], - targetBranchChoices: getTargetBranchChoicesAsObject( - // backwards-compatability: `branches` was renamed `targetBranchChoices` - targetBranchChoices || branches - ), - - // backwards-compatability: `labels` was renamed `targetPRLabels` - targetPRLabels: targetPRLabels || labels || ([] as string[]), - - // merge defaults with config values - ...combinedConfig, - }; -} - -// in the config `branches` can either be a string or an object. -// We need to transform it so that it is always treated as an object troughout the application -function getTargetBranchChoicesAsObject( - targetBranchChoices?: Config['targetBranchChoices'] -) { - if (!targetBranchChoices) { - return; - } - - return targetBranchChoices.map((choice) => { - if (isString(choice)) { - return { - name: choice, - checked: false, - }; - } - - return choice; - }); + // global and project config combined + return { ...globalConfig, ...projectConfig }; } diff --git a/src/options/config/readConfigFile.ts b/src/options/config/readConfigFile.ts index efeeea77..d3aa3fa7 100644 --- a/src/options/config/readConfigFile.ts +++ b/src/options/config/readConfigFile.ts @@ -1,14 +1,13 @@ import stripJsonComments from 'strip-json-comments'; import { HandledError } from '../../services/HandledError'; import { readFile } from '../../services/fs-promisified'; -import { Config } from '../../types/Config'; export async function readConfigFile(filepath: string) { const fileContents = await readFile(filepath, 'utf8'); const configWithoutComments = stripJsonComments(fileContents); try { - return JSON.parse(configWithoutComments) as Config; + return JSON.parse(configWithoutComments) as Record<string, unknown>; } catch (e) { throw new HandledError( `"${filepath}" contains invalid JSON:\n\n${fileContents}\n\nTry validating the file on https://jsonlint.com/` diff --git a/src/options/options.test.ts b/src/options/options.test.ts index b086293a..ccb9844b 100644 --- a/src/options/options.test.ts +++ b/src/options/options.test.ts @@ -81,7 +81,6 @@ describe('getOptions', () => { githubApiBaseUrlV3: 'https://api.github.com', githubApiBaseUrlV4: 'https://api.github.com/graphql', maxNumber: 10, - multiple: false, multipleBranches: true, multipleCommits: false, noVerify: true, @@ -117,7 +116,6 @@ describe('validateRequiredOptions', () => { githubApiBaseUrlV4: 'https://api.github.com/graphql', mainline: undefined, maxNumber: 10, - multiple: false, multipleBranches: true, multipleCommits: false, noVerify: true, @@ -129,7 +127,7 @@ describe('validateRequiredOptions', () => { sha: undefined, sourceBranch: 'mySourceBranch', sourcePRLabels: [], - sourcePRsFilter: undefined, + prFilter: undefined, targetBranchChoices: [], targetBranches: ['branchA'], targetPRLabels: [], diff --git a/src/runWithOptions.test.ts b/src/runWithOptions.test.ts index 99c252cb..642f2a5a 100644 --- a/src/runWithOptions.test.ts +++ b/src/runWithOptions.test.ts @@ -32,7 +32,6 @@ describe('runWithOptions', () => { githubApiBaseUrlV4: 'https://api.github.com/graphql', mainline: undefined, maxNumber: 10, - multiple: false, multipleBranches: false, multipleCommits: false, noVerify: true, @@ -46,7 +45,7 @@ describe('runWithOptions', () => { sha: undefined, sourceBranch: 'mySourceBranch', sourcePRLabels: [], - sourcePRsFilter: undefined, + prFilter: undefined, targetBranches: [], targetBranchChoices: [ { name: '6.x' }, diff --git a/src/services/github/v4/fetchPullRequestBySearchQuery.test.ts b/src/services/github/v4/fetchPullRequestBySearchQuery.test.ts index f43793c9..68b4b52e 100644 --- a/src/services/github/v4/fetchPullRequestBySearchQuery.test.ts +++ b/src/services/github/v4/fetchPullRequestBySearchQuery.test.ts @@ -1,15 +1,19 @@ import axios from 'axios'; import { BackportOptions } from '../../../options/options'; +import { PromiseReturnType } from '../../../types/PromiseReturnType'; +import { SpyHelper } from '../../../types/SpyHelper'; import { fetchPullRequestBySearchQuery } from './fetchPullRequestBySearchQuery'; import { fetchPullRequestBySearchQueryMock } from './mocks/fetchPullRequestBySearchQueryMock'; describe('fetchPullRequestBySearchQuery', () => { - it('s', async () => { - const spy = jest.spyOn(axios, 'post').mockResolvedValueOnce({ + let spy: SpyHelper<typeof axios.post>; + let res: PromiseReturnType<typeof fetchPullRequestBySearchQuery>; + beforeEach(async () => { + spy = jest.spyOn(axios, 'post').mockResolvedValueOnce({ data: fetchPullRequestBySearchQueryMock, }); - const res = await fetchPullRequestBySearchQuery({ + res = await fetchPullRequestBySearchQuery({ accessToken: 'myAccessToken', all: false, author: 'sqren', @@ -18,10 +22,20 @@ describe('fetchPullRequestBySearchQuery', () => { repoName: 'kibana', repoOwner: 'elastic', sourceBranch: 'master', - sourcePRsFilter: 'label:Team:apm', + prFilter: 'label:Team:apm', } as BackportOptions); + }); + it('should make request with correct variables', () => { expect(spy).toHaveBeenCalledTimes(1); + expect(spy.mock.calls[0][1].variables).toEqual({ + first: 10, + query: + 'type:pr is:merged sort:updated-desc repo:elastic/kibana author:sqren label:Team:apm base:master', + }); + }); + + it('should return correct response', async () => { expect(res).toMatchInlineSnapshot(` Array [ Object { diff --git a/src/services/github/v4/fetchPullRequestBySearchQuery.ts b/src/services/github/v4/fetchPullRequestBySearchQuery.ts index e29fb824..dc75cf2f 100644 --- a/src/services/github/v4/fetchPullRequestBySearchQuery.ts +++ b/src/services/github/v4/fetchPullRequestBySearchQuery.ts @@ -20,7 +20,7 @@ export async function fetchPullRequestBySearchQuery( repoName, repoOwner, sourceBranch, - sourcePRsFilter, + prFilter, } = options; const query = /* GraphQL */ ` query getPulLRequestBySearchQuery($query: String!, $first: Int!) { @@ -44,7 +44,7 @@ export async function fetchPullRequestBySearchQuery( `; const authorFilter = all ? '' : `author:${author}`; - const searchQuery = `type:pr is:merged sort:updated-desc repo:${repoOwner}/${repoName} ${authorFilter} ${sourcePRsFilter} base:${sourceBranch}`; + const searchQuery = `type:pr is:merged sort:updated-desc repo:${repoOwner}/${repoName} ${authorFilter} ${prFilter} base:${sourceBranch}`; const spinner = ora('Loading pull requests...').start(); let res: DataResponse; try { @@ -93,8 +93,8 @@ export async function fetchPullRequestBySearchQuery( // terminate if not commits were found if (isEmpty(commits)) { const errorText = options.all - ? `There are no pull requests matching the filter "${sourcePRsFilter}"` - : `There are no commits by "${options.author}" matching the filter "${sourcePRsFilter}". Try with \`--all\` for commits by all users or \`--author=<username>\` for commits from a specific user`; + ? `There are no pull requests matching the filter "${prFilter}"` + : `There are no commits by "${options.author}" matching the filter "${prFilter}". Try with \`--all\` for commits by all users or \`--author=<username>\` for commits from a specific user`; throw new HandledError(errorText); } diff --git a/src/services/prompts.ts b/src/services/prompts.ts index 25de1002..881d883e 100644 --- a/src/services/prompts.ts +++ b/src/services/prompts.ts @@ -5,8 +5,8 @@ import inquirer, { ConfirmQuestion, } from 'inquirer'; import isEmpty from 'lodash.isempty'; +import { BranchChoice } from '../options/cliArgs'; import { CommitChoice } from '../types/Commit'; -import { BranchChoice } from '../types/Config'; import { getShortSha } from './github/commitFormatters'; type Question = CheckboxQuestion | ListQuestion | ConfirmQuestion; diff --git a/src/test/integration/__snapshots__/integration.test.ts.snap b/src/test/integration/__snapshots__/integration.test.ts.snap index 3bd73a89..661d8548 100644 --- a/src/test/integration/__snapshots__/integration.test.ts.snap +++ b/src/test/integration/__snapshots__/integration.test.ts.snap @@ -185,12 +185,12 @@ Array [ "githubApiBaseUrlV3": "https://api.github.com", "githubApiBaseUrlV4": "https://api.github.com/graphql", "maxNumber": 10, - "multiple": false, "multipleBranches": true, "multipleCommits": false, "noVerify": true, "path": undefined, "prDescription": undefined, + "prFilter": undefined, "prTitle": "[{targetBranch}] {commitMessages}", "repoName": "backport-demo", "repoOwner": "elastic", diff --git a/src/test/yargs.test.ts b/src/test/yargs.private.test.ts similarity index 97% rename from src/test/yargs.test.ts rename to src/test/yargs.private.test.ts index 1159f9af..681561e6 100644 --- a/src/test/yargs.test.ts +++ b/src/test/yargs.private.test.ts @@ -48,7 +48,7 @@ describe('yargs', () => { it('should return error when branch is missing', () => { const res = runBackport( - `--upstream foo --username ${username} --accessToken ${accessToken}` + `--upstream foo --username ${username} --accessToken ${accessToken}` ); expect(res).toMatchInlineSnapshot(` "You must specify a target branch @@ -62,7 +62,7 @@ describe('yargs', () => { it('should return error when upstream is missing', () => { const res = runBackport( - `--branch foo --username ${username} --accessToken ${accessToken}` + `--branch foo --username ${username} --accessToken ${accessToken}` ); expect(res).toMatchInlineSnapshot(` "You must specify a valid Github repository diff --git a/src/types/Config.d.ts b/src/types/Config.d.ts deleted file mode 100644 index f88abe8c..00000000 --- a/src/types/Config.d.ts +++ /dev/null @@ -1,39 +0,0 @@ -export interface BranchChoice { - name: string; - checked?: boolean; -} - -export interface Config { - // global config - accessToken?: string; - username?: string; - editor?: string; - - // project config - branchLabelMapping?: Record<string, string>; - fork?: boolean; - noVerify?: boolean; - targetBranchChoices?: (string | BranchChoice)[]; - upstream?: string; - - // both - all?: boolean; - author?: string; - gitHostname?: string; - githubApiBaseUrlV3?: string; - githubApiBaseUrlV4?: string; - maxNumber?: number; - multiple?: boolean; - multipleBranches?: boolean; - multipleCommits?: boolean; - path?: string; - prDescription?: string; - prTitle?: string; - sourceBranch?: string; - sourcePRLabels?: string[]; - targetPRLabels?: string[]; - - // deprecated - kept for backwards compatability - branches?: (string | BranchChoice)[]; - labels?: string[]; -} diff --git a/src/ui/cherrypickAndCreatePullRequest.test.ts b/src/ui/cherrypickAndCreatePullRequest.test.ts index c69d23ce..72f16d1c 100644 --- a/src/ui/cherrypickAndCreatePullRequest.test.ts +++ b/src/ui/cherrypickAndCreatePullRequest.test.ts @@ -52,7 +52,7 @@ describe('cherrypickAndCreateTargetPullRequest', () => { repoOwner: 'elastic', username: 'sqren', sourceBranch: 'myDefaultSourceBranch', - sourcePRLabels: [], + sourcePRLabels: [] as string[], } as BackportOptions; const commits: CommitSelected[] = [ @@ -149,7 +149,7 @@ describe('cherrypickAndCreateTargetPullRequest', () => { repoName: 'kibana', repoOwner: 'elastic', username: 'sqren', - sourcePRLabels: [], + sourcePRLabels: [] as string[], } as BackportOptions; await cherrypickAndCreateTargetPullRequest({ @@ -209,7 +209,7 @@ describe('cherrypickAndCreateTargetPullRequest', () => { repoOwner: 'elastic', username: 'sqren', sourceBranch: 'myDefaultSourceBranch', - sourcePRLabels: [], + sourcePRLabels: [] as string[], } as BackportOptions; const res = await runTimersUntilResolved(() => diff --git a/src/ui/getCommits.ts b/src/ui/getCommits.ts index 65b34681..ac17a415 100644 --- a/src/ui/getCommits.ts +++ b/src/ui/getCommits.ts @@ -19,7 +19,7 @@ export async function getCommits(options: BackportOptions) { ]; } - if (options.sourcePRsFilter) { + if (options.prFilter) { const commitChoices = await fetchPullRequestBySearchQuery(options); return promptForCommits({ diff --git a/src/ui/getTargetBranches.test.ts b/src/ui/getTargetBranches.test.ts index ef847521..fd2bf52a 100644 --- a/src/ui/getTargetBranches.test.ts +++ b/src/ui/getTargetBranches.test.ts @@ -1,6 +1,6 @@ +import { BranchChoice } from '../options/cliArgs'; import { BackportOptions } from '../options/options'; import * as prompts from '../services/prompts'; -import { BranchChoice } from '../types/Config'; import { SpyHelper } from '../types/SpyHelper'; import { getTargetBranches } from './getTargetBranches'; diff --git a/src/ui/getTargetBranches.ts b/src/ui/getTargetBranches.ts index f3079858..f65dcb24 100644 --- a/src/ui/getTargetBranches.ts +++ b/src/ui/getTargetBranches.ts @@ -10,17 +10,18 @@ export function getTargetBranches( options: BackportOptions, commits: CommitSelected[] ) { - // target branches specified via cli + // target branches already specified (in contrast to letting the user choose from a list) if (!isEmpty(options.targetBranches)) { return options.targetBranches; } - // combine target branches from all commits + // combine target branches from commits that were selected for backporting const selectedTargetBranches = flatMap( commits, (commit) => commit.selectedTargetBranches ).filter(filterEmpty); + // list the target branch choices (in contrast to automatically backporting to specific branches) return promptForTargetBranches({ targetBranchChoices: getTargetBranchChoices( options,