backport
Version:
A CLI tool that automates the process of backporting commits
432 lines • 15 kB
JavaScript
"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.getRuntimeArguments = exports.getOptionsFromCliArgs = void 0;
const yargs_1 = __importDefault(require("yargs"));
const yargs_parser_1 = __importDefault(require("yargs-parser"));
const excludeUndefined_1 = require("../utils/excludeUndefined");
const options_1 = require("./options");
function getOptionsFromCliArgs(processArgs) {
const yargsInstance = (0, yargs_1.default)(processArgs)
.strict()
.parserConfiguration({
'strip-dashed': true,
'strip-aliased': true,
'boolean-negation': false,
})
.usage('$0 [args]')
.wrap(Math.max(100, Math.min(120, yargs_1.default.terminalWidth())))
.option('accessToken', {
alias: 'accesstoken',
description: 'Github access token',
type: 'string',
})
// show users own commits
.option('all', {
description: 'List all commits',
alias: 'a',
type: 'boolean',
conflicts: 'author',
})
.option('assignee', {
description: 'Add assignees to the target pull request',
alias: 'assign',
type: 'array',
string: true,
conflicts: ['autoAssign'],
})
.option('autoAssign', {
description: 'Auto assign the target pull request to yourself',
type: 'boolean',
conflicts: ['assignees'],
})
.option('autoMerge', {
description: 'Enable auto-merge for created pull requests',
type: 'boolean',
})
.option('autoMergeMethod', {
description: 'Sets auto-merge method when using --auto-merge. Default: merge',
type: 'string',
choices: ['merge', 'rebase', 'squash'],
})
.option('author', {
description: 'Show commits by a specific user',
alias: 'author',
type: 'string',
conflicts: 'all',
})
.option('cherrypickRef', {
description: 'Append commit message with "(cherry picked from commit...)',
type: 'boolean',
conflicts: ['noCherrypickRef'],
})
.option('commitConflicts', {
description: 'Commit conflicts instead of aborting. Only takes effect in `non-interactive` mode. Defaults to false',
type: 'boolean',
})
.option('cwd', {
hidden: true,
description: 'Path to source repo',
type: 'string',
})
.option('projectConfigFile', {
alias: 'config',
description: 'Path to project config',
type: 'string',
})
.option('globalConfigFile', {
description: 'Path to global config',
type: 'string',
})
.option('dateSince', {
alias: 'since',
description: 'ISO-8601 date for filtering commits',
type: 'string',
coerce: (since) => {
if (since) {
return new Date(since).toISOString();
}
},
})
.option('dateUntil', {
alias: 'until',
description: 'ISO-8601 date for filtering commits',
type: 'string',
coerce: (until) => {
if (until) {
return new Date(until).toISOString();
}
},
})
.option('dir', {
description: 'Path to temporary backport repo',
type: 'string',
})
.option('details', {
description: 'Show details about each commit',
type: 'boolean',
})
.option('draft', {
description: 'Publish pull request as draft',
type: 'boolean',
})
.option('dryRun', {
description: 'Run backport locally without pushing to Github',
type: 'boolean',
})
.option('editor', {
description: 'Editor to be opened during conflict resolution',
type: 'string',
})
.option('skipRemoteConfig', {
description: 'Use local .backportrc.json config instead of loading from Github',
type: 'boolean',
})
// push target branch to {repoForkOwner}/{repoName}
.option('fork', {
description: 'Create backports in fork or origin repo. Defaults to true',
type: 'boolean',
conflicts: ['noFork'],
})
.option('gitHostname', {
hidden: true,
description: 'Hostname for Github',
type: 'string',
})
.option('githubApiBaseUrlV3', {
hidden: true,
description: `Base url for Github's REST (v3) API`,
type: 'string',
})
.option('githubApiBaseUrlV4', {
hidden: true,
description: `Base url for Github's GraphQL (v4) API`,
type: 'string',
})
.option('gitAuthorName', {
description: `Set commit author name`,
type: 'string',
})
.option('gitAuthorEmail', {
description: `Set commit author email`,
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`,
type: 'string',
})
.option('ls', {
description: 'List commits instead of backporting them',
type: 'boolean',
})
.option('mainline', {
description: 'Parent id of merge commit. Defaults to 1 when supplied without arguments',
type: 'number',
coerce: (mainline) => {
if (mainline === undefined) {
// return 1 if `--mainline` is given without a value
// return undefined if --mainline is not supplied at all
return processArgs.includes('--mainline') ? 1 : undefined;
}
// use specified mainline parent
if (Number.isInteger(mainline)) {
return mainline;
}
// Invalid value provided
throw new Error(`--mainline must be an integer. Received: ${mainline}`);
},
})
.option('signoff', {
description: 'Pass the --signoff option to the cherry-pick command',
type: 'boolean',
alias: ['s'],
})
// display 10 commits to pick from
.option('maxNumber', {
description: 'Number of commits to choose from',
alias: ['number', 'n'],
type: 'number',
})
// cli-only
.option('multiple', {
description: 'Select multiple branches/commits',
type: 'boolean',
conflicts: ['multipleBranches', 'multipleCommits'],
})
// allow picking multiple target branches
.option('multipleBranches', {
description: 'Backport to multiple branches',
type: 'boolean',
conflicts: ['multiple'],
})
// allow picking multiple commits
.option('multipleCommits', {
description: 'Backport multiple commits',
type: 'boolean',
conflicts: ['multiple'],
})
.option('noCherrypickRef', {
description: 'Do not append commit message with "(cherry picked from commit...)"',
type: 'boolean',
conflicts: ['cherrypickRef'],
})
// cli-only
// negation of `publishStatusCommentOnSuccess` and `publishStatusCommentOnFailure`
.option('noStatusComment', {
description: "Don't publish status comment to Github",
type: 'boolean',
})
.option('noVerify', {
description: 'Bypass the pre-commit and commit-msg hooks',
type: 'boolean',
})
.option('noFork', {
description: 'Create backports in the origin repo',
type: 'boolean',
conflicts: ['fork', 'repoForkOwner'],
})
.option('noTelemetry', {
description: 'Disable telemetry',
type: 'boolean',
})
.option('onlyMissing', {
description: 'Only list commits with missing or unmerged backports',
type: 'boolean',
})
.option('path', {
description: 'Only list commits touching files under the specified path',
alias: 'p',
type: 'array',
string: true,
})
.option('prDescription', {
description: 'Description to be added to pull request',
alias: 'description',
type: 'string',
})
.option('prTitle', {
description: 'Title of pull request',
alias: 'title',
type: 'string',
})
.option('prFilter', {
conflicts: ['pullNumber', 'sha', 'path'],
description: `Filter source pull requests by a query`,
type: 'string',
})
.option('pullNumber', {
conflicts: ['sha', 'prFilter'],
description: 'Pull request to backport',
alias: 'pr',
type: 'number',
})
.option('resetAuthor', {
description: 'Set yourself as commit author',
type: 'boolean',
})
.option('reviewer', {
description: 'Add reviewer to the target PR',
type: 'array',
string: true,
})
.option('repoForkOwner', {
description: 'The owner of the fork where the backport branch is pushed. Defaults to the currently authenticated user',
type: 'string',
conflicts: ['noFork'],
})
.option('repoOwner', {
hidden: true,
description: 'Repository owner',
type: 'string',
conflicts: ['repo'],
})
.option('repoName', {
hidden: true,
description: 'Repository name',
type: 'string',
conflicts: ['repo'],
})
.option('repo', {
description: 'Repo owner and name',
alias: 'upstream',
type: 'string',
conflicts: ['repoName', 'repoOwner'],
})
.option('sha', {
conflicts: ['pullNumber', 'prFilter'],
description: 'Commit sha to backport',
alias: 'commit',
type: 'string',
})
.option('noUnmergedBackportsHelp', {
description: 'Do not list the unmerged backports in PR comment',
type: 'boolean',
})
.option('sourceBranch', {
description: `Specify a non-default branch (normally "master") to backport from`,
type: 'string',
})
.option('sourcePRLabel', {
description: 'Add labels to the source (original) PR',
alias: 'sourcePrLabel',
type: 'array',
string: true,
})
.option('copySourcePRLabels', {
description: 'Copy labels from source PR to the target PR',
alias: 'copySourcePrLabels',
type: 'boolean',
})
.option('copySourcePRReviewers', {
description: 'Copy reviewers from the source PR to the target PR',
alias: ['copySourcePrReviewers', 'addOriginalReviewers'],
type: 'boolean',
})
.option('targetBranch', {
description: 'Branch(es) to backport to',
alias: ['branch', 'b'],
type: 'array',
string: true, // ensure `6.0` is not coerced to `6`
})
.option('targetBranchChoice', {
description: 'List branches to backport to',
type: 'array',
string: true,
})
.option('targetPRLabel', {
description: 'Add labels to the target (backport) PR',
alias: ['label', 'l'],
type: 'array',
string: true,
})
.option('backportBranchName', {
description: 'Name template to use for the branch name of the backport',
type: 'string',
})
// cli-only
.option('verify', {
description: `Opposite of no-verify`,
type: 'boolean',
})
.alias('version', 'v')
.help()
.exitProcess(!processArgs.includes('--noExitProcess'))
.epilogue('For bugs, feature requests or questions: https://github.com/sorenlouv/backport/issues\nOr contact me directly: https://twitter.com/sorenlouv')
// don't kill process upon error
// and don't log error to console
.fail((msg, err) => {
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
if (err) {
throw err;
}
throw new Error(msg);
});
const {
/* eslint-disable @typescript-eslint/no-unused-vars */
$0, _,
/* eslint-enable @typescript-eslint/no-unused-vars */
multiple, multipleBranches, multipleCommits, all,
// shorthands
repo,
// filters
author,
// negations
noCherrypickRef, noFork, noStatusComment, noVerify, verify, nonInteractive, noTelemetry,
// array types (should be renamed to plural form)
assignee, path, reviewer, sourcePRLabel, targetBranch, targetBranchChoice, targetPRLabel, ...restOptions } = yargsInstance.parseSync();
const [repoOwner, repoName] = repo?.split('/') ?? [
restOptions.repoOwner,
restOptions.repoName,
];
return (0, excludeUndefined_1.excludeUndefined)({
...restOptions,
// repoName and repoOwner
repoOwner,
repoName,
// filters
author: all ? null : author,
// `multiple` is a cli-only flag to override `multipleBranches` and `multipleCommits`
multipleBranches: multiple ?? multipleBranches,
multipleCommits: multiple ?? multipleCommits,
// rename array types to plural
assignees: assignee,
commitPaths: path,
reviewers: reviewer,
sourcePRLabels: sourcePRLabel,
targetBranchChoices: targetBranchChoice,
targetBranches: targetBranch,
targetPRLabels: targetPRLabel,
// negations (cli-only flags)
cherrypickRef: noCherrypickRef === true ? false : restOptions.cherrypickRef,
fork: noFork === true ? false : restOptions.fork,
noVerify: verify ?? noVerify,
publishStatusCommentOnSuccess: noStatusComment === true ? false : undefined,
publishStatusCommentOnFailure: noStatusComment === true ? false : undefined,
publishStatusCommentOnAbort: noStatusComment === true ? false : undefined,
interactive: nonInteractive === true ? false : undefined,
telemetry: noTelemetry === true ? false : undefined,
});
}
exports.getOptionsFromCliArgs = getOptionsFromCliArgs;
function getRuntimeArguments(processArgs, optionsFromModule) {
const { nonInteractive, json, logFilePath, ls } = (0, yargs_parser_1.default)(processArgs);
const base = { ...options_1.defaultConfigOptions, ...optionsFromModule };
return {
interactive: nonInteractive === undefined && json === undefined
? base.interactive
: !(nonInteractive === true || json === true),
logFilePath,
ls,
};
}
exports.getRuntimeArguments = getRuntimeArguments;
//# sourceMappingURL=cliArgs.js.map