diff --git a/.gitignore b/.gitignore index 3c3629e..0a43334 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,5 @@ node_modules +# Intellij IDE project settings +.idea +# Because dogfooding +.pr-train.yml diff --git a/README.md b/README.md index 222a558..c3d5354 100644 --- a/README.md +++ b/README.md @@ -35,6 +35,23 @@ If you run with `--create-prs` again, `pr-train` will only override the Table of **Pro-tip**: If you want to udpate the ToCs in your GitHub PRs, just update the PR titles and re-run pr train with `--create-prs` - it will do the right thing. +### Draft PRs + +To create PRs in draft mode ([if your repo allows](https://docs.github.com/en/free-pro-team@latest/github/collaborating-with-issues-and-pull-requests/about-pull-requests#draft-pull-requests)), +pass the `-d` or `--draft` argument on the command line (in addition to `-c`/`--create-prs`). + +You can also configure PRs to be created in draft mode by default if you add the following section to your `.pr-train.yml` file: + +```yaml +prs: + draft-by-default: true + +trains: + # etc +``` + +Specifying this option will allow you to omit the `-d`/`--draft` parameter (though you still need to specify `-c`/`--create-prs`) when you want to create/update PRs. + ## Example with explanation You finished coding a feature and now you have a patch that is over 1000 SLOCs long. That's a big patch. As a good citizen, you want to split the diff into multiple PRs, e.g.: @@ -108,3 +125,33 @@ Unlike the sub-branches, the combined branch doesn't need to exist when you run Run `git pr-train` in your working dir when you're on any branch that belongs to a PR train. You don't have to be on the first branch, any branch will do. Use `-r/--rebase` option if you'd like to rebase branches on top of each other rather than merge (note: you will have to push with `git pr-train -pf` in that case). `git pr-train -p` will merge/rebase and push your updated changes to remote `origin` (configurable via `--remote` option). + +## No master? No problem! + +_All your base are belong to us._ - CATS + +Are you working in a repository that doesn't use `master` as the main (default) branch? +For example, newer repos use `main` instead. +Or do you have a different branch that you want all PR trains to use as a base? + +Add a section to the top of the config like so: + +```yml +prs: + main-branch-name: main + +trains: + # existing train config +``` + +### Override the base branch when creating PRs + +You can override the base branch to use when creating PRs by passing the `--base `. This takes precedence +over the main branch specified in the config file. + +e.g. `git pr-train -p -c -b feat/my-feature-base` + +## Print the PR links to the terminal + +To have the command output include a link to the PR that was created or updated, +simply add `print-urls: true` to the `prs` section of the config file. diff --git a/cfg_template.yml b/cfg_template.yml index 08646b0..3e43420 100644 --- a/cfg_template.yml +++ b/cfg_template.yml @@ -1,3 +1,15 @@ +prs: + # Uncomment this if you use a different name for your default branch (e.g. "main" instead of "master") + # main-branch-name: main + # + # Uncomment to create new PRs in draft mode by default. This will prevent them from notifying anybody + # (including CODEOWNERS) until you are ready. This option can be overridden on the command line using the + # -d/--draft or --no-draft options, which set draft mode to true or false, respectively. + # draft-by-default: true + # + # Print out the links to the terminal for the created/updated PRs (default false) + # print-urls: true + trains: # This is an example. This PR train would have 4 branches plus 1 "combined" branch to run tests etc on. # diff --git a/consts.js b/consts.js index 0d24f16..4a8a05f 100644 --- a/consts.js +++ b/consts.js @@ -1,5 +1,6 @@ module.exports = { DEFAULT_REMOTE: 'origin', + DEFAULT_BASE_BRANCH: 'master', MERGE_STEP_DELAY_MS: 1000, MERGE_STEP_DELAY_WAIT_FOR_LOCK: 2500, } diff --git a/github.js b/github.js index b3e669e..d1c207f 100644 --- a/github.js +++ b/github.js @@ -2,12 +2,15 @@ const octo = require('octonode'); const promptly = require('promptly'); const { - DEFAULT_REMOTE + DEFAULT_REMOTE, + DEFAULT_BASE_BRANCH } = require('./consts'); const fs = require('fs'); +const get = require('lodash/get'); const colors = require('colors'); const emoji = require('node-emoji'); -const simpleGit = require('simple-git/promise'); +const table = require('markdown-table'); +const width = require('string-width'); /** * @@ -31,17 +34,18 @@ async function constructPrMsg(sg, branch) { * @param {string} combinedBranch */ function constructTrainNavigation(branchToPrDict, currentBranch, combinedBranch) { - let contents = '\n\n#### PR chain:\n'; - contents = Object.keys(branchToPrDict).reduce((output, branch) => { - const maybeHandRight = branch === currentBranch ? '👉 ' : ''; - const maybeHandLeft = branch === currentBranch ? ' 👈 **YOU ARE HERE**' : ''; + let contents = '\n\n'; + let tableData = [['', 'PR', 'Description']]; + Object.keys(branchToPrDict).forEach((branch) => { + const maybeHandRight = branch === currentBranch ? '👉 ' : ' '; const combinedInfo = branch === combinedBranch ? ' **[combined branch]** ' : ' '; - output += `${maybeHandRight}#${branchToPrDict[branch].pr}${combinedInfo}(${branchToPrDict[ - branch - ].title.trim()})${maybeHandLeft}`; - return output + '\n'; - }, contents); - contents += '\n'; + const prTitle = branchToPrDict[branch].title.trim(); + const prNumber = `#${branchToPrDict[branch].pr}`; + const prInfo = `${combinedInfo}${prTitle}`.trim(); + tableData.push([maybeHandRight, prNumber, prInfo]); + }); + contents += table(tableData, { stringLength: width }) + '\n'; + contents += '\n' return contents; } @@ -67,21 +71,47 @@ function readGHKey() { * @param {string} body */ function upsertNavigationInBody(newNavigation, body) { + body = body || ''; if (body.match(//)) { return body.replace(/[^]*<\/pr-train-toc>/, newNavigation); } else { - return body + '\n' + newNavigation; + return (body ? body + '\n' : '') + newNavigation; } } +function checkAndReportInvalidBaseError(e, base) { + const { field, code } = get(e, 'body.errors[0]', {}); + if (field === 'base' && code === 'invalid') { + console.log([ + emoji.get('no_entry'), + `\n${emoji.get('confounded')} This is embarrassing. `, + `The base branch of ${base.bold} doesn't seem to exist on the remote.`, + `\nDid you forget to ${emoji.get('arrow_up')} push it?`, + ].join('')); + return true; + } + return false; +} + /** * * @param {simpleGit.SimpleGit} sg * @param {Array.} allBranches * @param {string} combinedBranch + * @param {boolean} draft * @param {string} remote + * @param {string} baseBranch + * @param {boolean} printLinks */ -async function ensurePrsExist(sg, allBranches, combinedBranch, remote = DEFAULT_REMOTE) { +async function ensurePrsExist({ + sg, + allBranches, + combinedBranch, + draft, + remote = DEFAULT_REMOTE, + baseBranch = DEFAULT_BASE_BRANCH, + printLinks = false +}) { //const allBranches = combinedBranch ? sortedBranches.concat(combinedBranch) : sortedBranches; const octoClient = octo.client(readGHKey()); // TODO: take remote name from `-r` value. @@ -108,8 +138,10 @@ async function ensurePrsExist(sg, allBranches, combinedBranch, remote = DEFAULT_ body: '', }); + const prText = draft ? 'draft PR' : 'PR'; + console.log(); - console.log('This will create (or update) PRs for the following branches:'); + console.log(`This will create (or update) ${prText}s for the following branches:`); await allBranches.reduce(async (memo, branch) => { await memo; const { @@ -124,7 +156,7 @@ async function ensurePrsExist(sg, allBranches, combinedBranch, remote = DEFAULT_ process.exit(0); } - const nickAndRepo = remoteUrl.match(/github\.com[/:](.*)\.git/)[1]; + const nickAndRepo = remoteUrl.match(/github\.com[/:](.*)/)[1].replace(/\.git$/, ''); if (!nickAndRepo) { console.log(`I could not parse your remote ${remote} repo URL`.red); process.exit(4); @@ -145,7 +177,7 @@ async function ensurePrsExist(sg, allBranches, combinedBranch, remote = DEFAULT_ title, body } = branch === combinedBranch ? getCombinedBranchPrMsg() : await constructPrMsg(sg, branch); - const base = index === 0 || branch === combinedBranch ? 'master' : allBranches[index - 1]; + const base = index === 0 || branch === combinedBranch ? baseBranch : allBranches[index - 1]; process.stdout.write(`Checking if PR for branch ${branch} already exists... `); const prs = await ghRepo.prsAsync({ head: `${nick}:${branch}`, @@ -162,12 +194,16 @@ async function ensurePrsExist(sg, allBranches, combinedBranch, remote = DEFAULT_ base, title, body, + draft, }; - process.stdout.write(`Creating PR for branch "${branch}"...`); + const baseMessage = base === baseBranch ? colors.dim(` (against ${base})`) : ''; + process.stdout.write(`Creating ${prText} for branch "${branch}"${baseMessage}...`); try { prResponse = (await ghRepo.prAsync(payload))[0]; } catch (e) { - console.error(JSON.stringify(e, null, 2)); + if (!checkAndReportInvalidBaseError(e, base)) { + console.error(JSON.stringify(e, null, 2)); + } throw e; } console.log(emoji.get('white_check_mark')); @@ -199,11 +235,12 @@ async function ensurePrsExist(sg, allBranches, combinedBranch, remote = DEFAULT_ const navigation = constructTrainNavigation(prDict, branch, combinedBranch); const newBody = upsertNavigationInBody(navigation, body); process.stdout.write(`Updating PR for branch ${branch}...`); - await ghPr.updateAsync({ + const updateResponse = await ghPr.updateAsync({ title, body: `${newBody}`, }); - console.log(emoji.get('white_check_mark')); + const prLink = get(updateResponse, '0._links.html.href', colors.yellow('Could not get URL')); + console.log(emoji.get('white_check_mark') + (printLinks ? ` (${prLink})` : '')); }, Promise.resolve()); } diff --git a/index.js b/index.js index d475573..866bd34 100755 --- a/index.js +++ b/index.js @@ -9,12 +9,18 @@ const fs = require('fs'); const yaml = require('js-yaml'); const { ensurePrsExist, readGHKey, checkGHKeyExists } = require('./github'); const colors = require('colors'); -const { DEFAULT_REMOTE, MERGE_STEP_DELAY_MS, MERGE_STEP_DELAY_WAIT_FOR_LOCK } = require('./consts'); +const { + DEFAULT_REMOTE, + DEFAULT_BASE_BRANCH, + MERGE_STEP_DELAY_MS, + MERGE_STEP_DELAY_WAIT_FOR_LOCK, +} = require('./consts'); const path = require('path'); // @ts-ignore const package = require('./package.json'); const inquirer = require('inquirer'); const shelljs = require('shelljs'); +const camelCase = require('lodash/camelCase'); const sleep = ms => new Promise(resolve => setTimeout(resolve, ms)); @@ -63,8 +69,8 @@ async function pushBranches(sg, branches, forcePush, remote = DEFAULT_REMOTE) { console.log('All changes pushed ' + emoji.get('white_check_mark')); } -async function getUnmergedBranches(sg, branches) { - const mergedBranchesOutput = await sg.raw(['branch', '--merged', 'master']); +async function getUnmergedBranches(sg, branches, baseBranch = DEFAULT_BASE_BRANCH) { + const mergedBranchesOutput = await sg.raw(['branch', '--merged', baseBranch]); const mergedBranches = mergedBranchesOutput .split('\n') .map(b => b.trim()) @@ -80,11 +86,12 @@ async function getConfigPath(sg) { /** * @typedef {string | Object.} BranchCfg * @typedef {Object.>} TrainCfg + * @typedef {{ prs?: Object, trains: Array.}} YamlCfg */ /** * @param {simpleGit.SimpleGit} sg - * @return {Promise.<{trains: Array.}>} + * @return {Promise.} */ async function loadConfig(sg) { const path = await getConfigPath(sg); @@ -128,7 +135,7 @@ function getBranchesInCurrentTrain(branchConfig) { */ function getCombinedBranch(branchConfig) { const combinedBranch = /** @type {Object} */ branchConfig.find(cfg => { - if (typeof cfg === 'string') { + if (!cfg || typeof cfg === 'string') { return false; } const branchName = Object.keys(cfg)[0]; @@ -161,7 +168,57 @@ async function handleSwitchToBranchCommand(sg, sortedBranches, combinedBranch) { process.exit(0); } +/** + * @param {YamlCfg} ymlConfig + * @param {string} path + */ +function getConfigOption(ymlConfig, path) { + const parts = path.split(/\./g); + let ptr = ymlConfig; + while (ptr && parts.length) { + const part = parts.shift(); + // cater for both kebab case and camel cased variants of key, just for developer convenience. + ptr = part in ptr ? ptr[part] : ptr[camelCase(part)]; + } + return ptr; +} + async function main() { + const sg = simpleGit(); + if (!(await sg.checkIsRepo())) { + console.log('Not a git repo'.red); + process.exit(1); + } + + // try to create or init the config first so we can read values from it + if (process.argv.includes('--init')) { + if (fs.existsSync(await getConfigPath(sg))) { + console.log('.pr-train.yml already exists'); + process.exit(1); + } + const root = path.dirname(require.main.filename); + const cfgTpl = fs.readFileSync(`${root}/cfg_template.yml`); + fs.writeFileSync(await getConfigPath(sg), cfgTpl); + console.log(`Created a ".pr-train.yml" file. Please make sure it's gitignored.`); + process.exit(0); + } + + let ymlConfig; + try { + ymlConfig = await loadConfig(sg); + } catch (e) { + if (e instanceof yaml.YAMLException) { + console.log('There seems to be an error in `.pr-train.yml`.'); + console.log(e.message); + process.exit(1); + } + console.log('`.pr-train.yml` file not found. Please run `git pr-train --init` to create one.'.red); + process.exit(1); + } + + const defaultBase = getConfigOption(ymlConfig, 'prs.main-branch-name') || DEFAULT_BASE_BRANCH; + const draftByDefault = !!getConfigOption(ymlConfig, 'prs.draft-by-default'); + program .version(package.version) .option('--init', 'Creates a .pr-train.yml file with an example configuration') @@ -169,8 +226,11 @@ async function main() { .option('-l, --list', 'List branches in current train') .option('-r, --rebase', 'Rebase branches rather than merging them') .option('-f, --force', 'Force push to remote') - .option('--push-merged', 'Push all branches (inclusing those that have already been merged into master)') + .option('--push-merged', 'Push all branches (including those that have already been merged into the base branch)') .option('--remote ', 'Set remote to push to. Defaults to "origin"') + .option('-b, --base ', `Specify the base branch to use for the first and combined PRs.`, defaultBase) + .option('-d, --draft', 'Create PRs in draft mode', draftByDefault) + .option('--no-draft', 'Do not create PRs in draft mode', !draftByDefault) .option('-c, --create-prs', 'Create GitHub PRs from your train branches'); program.on('--help', () => { @@ -199,36 +259,9 @@ async function main() { program.createPrs && checkGHKeyExists(); - const sg = simpleGit(); - if (!(await sg.checkIsRepo())) { - console.log('Not a git repo'.red); - process.exit(1); - } - - if (program.init) { - if (fs.existsSync(await getConfigPath(sg))) { - console.log('.pr-train.yml already exists'); - process.exit(1); - } - const root = path.dirname(require.main.filename); - const cfgTpl = fs.readFileSync(`${root}/cfg_template.yml`); - fs.writeFileSync(await getConfigPath(sg), cfgTpl); - console.log(`Created a ".pr-train.yml" file. Please make sure it's gitignored.`); - process.exit(0); - } + const baseBranch = program.base; // will have default value if one is not supplied - let ymlConfig; - try { - ymlConfig = await loadConfig(sg); - } catch (e) { - if (e instanceof yaml.YAMLException) { - console.log('There seems to be an error in `.pr-train.yml`.'); - console.log(e.message); - process.exit(1); - } - console.log('`.pr-train.yml` file not found. Please run `git pr-train --init` to create one.'.red); - process.exit(1); - } + const draft = program.draft != null ? program.draft : draftByDefault; const { current: currentBranch, all: allBranches } = await sg.branchLocal(); const trainCfg = await getBranchesConfigInCurrentTrain(sg, ymlConfig); @@ -273,7 +306,7 @@ async function main() { async function findAndPushBranches() { let branchesToPush = sortedTrainBranches; if (!program.pushMerged) { - branchesToPush = await getUnmergedBranches(sg, sortedTrainBranches); + branchesToPush = await getUnmergedBranches(sg, sortedTrainBranches, baseBranch); const branchDiff = difference(sortedTrainBranches, branchesToPush); if (branchDiff.length > 0) { console.log(`Not pushing already merged branches: ${branchDiff.join(', ')}`); @@ -286,7 +319,15 @@ async function main() { // the PR titles and descriptions). Just push and create the PRs. if (program.createPrs) { await findAndPushBranches(); - await ensurePrsExist(sg, sortedTrainBranches, combinedTrainBranch, program.remote); + await ensurePrsExist({ + sg, + allBranches: sortedTrainBranches, + combinedBranch: combinedTrainBranch, + remote: program.remote, + draft, + baseBranch, + printLinks: getConfigOption(ymlConfig, 'prs.print-urls'), + }); return; } @@ -309,6 +350,6 @@ async function main() { } main().catch(e => { - console.log(`${emoji.get('x')} An error occured. Was there a conflict perhaps?`.red); + console.log(`${emoji.get('x')} An error occurred. Was there a conflict perhaps?`.red); console.error('error', e); }); diff --git a/package-lock.json b/package-lock.json index 824361a..fb93b20 100644 --- a/package-lock.json +++ b/package-lock.json @@ -27,9 +27,9 @@ "integrity": "sha512-cBhpre4ma+U0T1oM5fXg7Dy1Jw7zzwv7lt/GoCpr+hDQJoYnKVPLL4dCvSEFMmQurOQvSrwT7SL/DAlhBI97RQ==" }, "ansi-regex": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz", - "integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=" + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.0.tgz", + "integrity": "sha512-bY6fj56OUQ0hU1KjFNDQuJFezqKdrAyFdIevADiqrWHwSlbmBNMHp5ak2f40Pm8JTFyM2mqxkG6ngkHO11f/lg==" }, "ansi-styles": { "version": "3.2.1", @@ -170,9 +170,9 @@ } }, "commander": { - "version": "2.15.0", - "resolved": "https://registry.npmjs.org/commander/-/commander-2.15.0.tgz", - "integrity": "sha512-7B1ilBwtYSbetCgTY1NJFg+gVpestg0fdA1MhC1Vs4ssyfSXnCAjFr+QcQM9/RedXC0EaUx1sG8Smgw2VfgKEg==" + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/commander/-/commander-3.0.2.tgz", + "integrity": "sha512-Gar0ASD4BDyKC4hl4DwHqDrmvjoxWKZigVnAbn5H1owvm4CxCPdb0HQDehwNYMJpla5+M2tPmPARzhtYuwpHow==" }, "concat-map": { "version": "0.0.1", @@ -220,6 +220,11 @@ "safer-buffer": "^2.1.0" } }, + "emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==" + }, "escape-string-regexp": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", @@ -383,6 +388,37 @@ "string-width": "^2.1.0", "strip-ansi": "^5.0.0", "through": "^2.3.6" + }, + "dependencies": { + "ansi-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz", + "integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=" + }, + "is-fullwidth-code-point": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", + "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=" + }, + "string-width": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-2.1.1.tgz", + "integrity": "sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw==", + "requires": { + "is-fullwidth-code-point": "^2.0.0", + "strip-ansi": "^4.0.0" + }, + "dependencies": { + "strip-ansi": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz", + "integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=", + "requires": { + "ansi-regex": "^3.0.0" + } + } + } + } } }, "interpret": { @@ -391,9 +427,9 @@ "integrity": "sha512-mT34yGKMNceBQUoVn7iCDKDntA7SC6gycMAWzGx1z/CMCTV7b2AAtXlo3nRyHZ1FelRkQbQjprHSYGwzLtkVbw==" }, "is-fullwidth-code-point": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", - "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=" + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==" }, "is-promise": { "version": "2.1.0", @@ -471,6 +507,14 @@ "resolved": "https://registry.npmjs.org/lodash.toarray/-/lodash.toarray-4.4.0.tgz", "integrity": "sha1-JMS/zWsvuji/0FlNsRedjptlZWE=" }, + "markdown-table": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/markdown-table/-/markdown-table-2.0.0.tgz", + "integrity": "sha512-Ezda85ToJUBhM6WGaG6veasyym+Tbs3cMAw/ZhOPqXiYsr0jgocBV3j3nx+4lk47plLlIqjwuTm/ywVI+zjJ/A==", + "requires": { + "repeat-string": "^1.0.0" + } + }, "mime-db": { "version": "1.35.0", "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.35.0.tgz", @@ -620,6 +664,11 @@ "resolve": "^1.1.6" } }, + "repeat-string": { + "version": "1.6.1", + "resolved": "https://registry.npmjs.org/repeat-string/-/repeat-string-1.6.1.tgz", + "integrity": "sha1-jcrkcOHIirwtYA//Sndihtp15jc=" + }, "request": { "version": "2.87.0", "resolved": "https://registry.npmjs.org/request/-/request-2.87.0.tgz", @@ -735,20 +784,21 @@ } }, "string-width": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-2.1.1.tgz", - "integrity": "sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw==", + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.0.tgz", + "integrity": "sha512-zUz5JD+tgqtuDjMhwIg5uFVV3dtqZ9yQJlZVfq4I01/K5Paj5UHj7VyrQOJvzawSVlKpObApbfD0Ed6yJc+1eg==", "requires": { - "is-fullwidth-code-point": "^2.0.0", - "strip-ansi": "^4.0.0" + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.0" }, "dependencies": { "strip-ansi": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz", - "integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=", + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.0.tgz", + "integrity": "sha512-AuvKTrTfQNYNIctbR1K/YGTR1756GycPsg7b9bdV9Duqur4gv6aKqHXah67Z8ImS7WEz5QVcOtlfW2rZEugt6w==", "requires": { - "ansi-regex": "^3.0.0" + "ansi-regex": "^5.0.0" } } } diff --git a/package.json b/package.json index cb656ac..08a24dd 100644 --- a/package.json +++ b/package.json @@ -20,19 +20,21 @@ }, "dependencies": { "colors": "^1.2.1", - "commander": "^2.15.0", + "commander": "^3.0.2", "figlet": "^1.2.0", "inquirer": "^6.2.1", "js-yaml": "^3.13.1", "lodash": "^4.17.15", "lodash.difference": "^4.5.0", "lodash.sortby": "^4.7.0", + "markdown-table": "^2.0.0", "node-emoji": "^1.8.1", "octonode": "^0.9.3", "progress": "^2.0.0", "promptly": "^3.0.3", "shelljs": "^0.8.3", - "simple-git": "^1.102.0" + "simple-git": "^1.102.0", + "string-width": "^4.2.0" }, "devDependencies": { "@types/js-yaml": "^3.11.2"