From 32121a23c504b045c9ce073f9289b37accd38732 Mon Sep 17 00:00:00 2001 From: Florian Pichler Date: Wed, 5 Sep 2018 23:00:01 +0200 Subject: [PATCH 1/2] Feature: Allow changelogs to start and end anywhere in range This also improves how changelogs are handled in the interactive interface and how the command line params work. Re #1 --- README.md | 10 ++++++ experiments.js | 16 ---------- lib/git/last-tag.js | 13 -------- lib/git/tags.js | 49 ---------------------------- lib/git/version-tags.js | 18 +++++++++++ package.json | 4 --- src/create-changelog.js | 17 +++------- src/defaults.js | 71 +++++++++++++++++++++++++++++++++++++++++ src/program.js | 20 +++++------- src/questions.js | 56 ++++++++++++++++++++++++++++++++ src/settings.js | 69 ++------------------------------------- 11 files changed, 170 insertions(+), 173 deletions(-) delete mode 100644 experiments.js delete mode 100644 lib/git/last-tag.js delete mode 100644 lib/git/tags.js create mode 100644 lib/git/version-tags.js create mode 100644 src/defaults.js diff --git a/README.md b/README.md index cc03e84..41f4e0e 100644 --- a/README.md +++ b/README.md @@ -9,6 +9,7 @@ And - Batch update GitHub labels + ## Installation If you have Node.js and Yarn installed, you can use install via: @@ -18,11 +19,20 @@ yarn global add anfema/work ``` +## Usage + +### Changelogs + +`-l` `--changelog` accepts a revision range, ie. `1.0.0` or `1.0.0..2.0.0` or `..HEAD` to limit the requested and rendered pull requests to that range. The output can be piped as well. For example `work -c | pbcopy` can be used to create the changelog and send it straight to your pasteboard on macOS. + + + ## Configuration This tool will ask for any required configuration. If you want to adjust default settings, you can add a JSON object to `.config/work` to adjust settings as necessary. Note: This should not be the case for anfema repositories. + ## Credits The changelog feature is more based on than inspired by [lerna-changelog](https://github.com/lerna/lerna-changelog) which didn't quite match what we needed. It's a great project to look at and learn. diff --git a/experiments.js b/experiments.js deleted file mode 100644 index 4ce4200..0000000 --- a/experiments.js +++ /dev/null @@ -1,16 +0,0 @@ -const createChangelog = require('./src/create-changelog.js'); -const { owner, repo } = require('./lib/git/github-info.js'); - -(async () => { - await createChangelog({ - tagFrom: '1.0.0', - owner, - repo, - changelog: { - 'New Features & Improvements': ['Enhancement', 'Feature'], - 'Behind the scenes': ['Refactor'], - 'Fixed Bugs': ['Bug'], - 'Other changes': ['Chore'], - }, - }); -})(); diff --git a/lib/git/last-tag.js b/lib/git/last-tag.js deleted file mode 100644 index 2c719a4..0000000 --- a/lib/git/last-tag.js +++ /dev/null @@ -1,13 +0,0 @@ -const execa = require('execa'); - -module.exports = async () => { - try { - const { stdout } = await execa('git', ['describe', '--abbrev=0', '--tags']); - - return stdout; - } catch (err) { - console.log(`Can't select last tag.`, err); - } - - return null; -}; diff --git a/lib/git/tags.js b/lib/git/tags.js deleted file mode 100644 index 99e3921..0000000 --- a/lib/git/tags.js +++ /dev/null @@ -1,49 +0,0 @@ -const execa = require('execa'); -const split2 = require('split2'); -const through2 = require('through2'); -const semver = require('semver'); -const getStream = require('get-stream'); - -const tagsRegex = /commit\s(.*)\s\(tag:\s*(.+?)[,\)]/gi; - -function repositoryTags(options = { cwd: '.' }) { - options.maxBuffer = options.maxBuffer || Infinity; - - return execa('git', ['log', '--decorate', '--no-color'], options) - .stdout.pipe(split2()) - .pipe( - through2.obj(function transform(line, enc, cb) { - const match = tagsRegex.exec(line); - - if (match !== null) { - const [, hash, tag] = match; - - // eslint-disable-next-line no-invalid-this - this.push({ - tag: semver.clean(tag), - hash, - }); - } - - cb(); - }) - ); -} - -const allTags = async (options = { cwd: '.' }) => { - const tagsStream = repositoryTags(options); - const tags = await getStream.array(tagsStream); - - return tags; -}; - -const lastTag = async (options = { cwd: '.' }) => { - const tags = await allTags(options); - - return tags[0]; -}; - -module.exports = { - all: allTags, - last: lastTag, -}; diff --git a/lib/git/version-tags.js b/lib/git/version-tags.js new file mode 100644 index 0000000..7009c8f --- /dev/null +++ b/lib/git/version-tags.js @@ -0,0 +1,18 @@ +const execa = require('execa'); +const semver = require('semver'); + +module.exports = async () => { + try { + const { stdout: localTags } = await execa('git', ['tag']); + + return localTags + .split('\n') + .map(semver.clean) + .filter(semver.valid) + .sort(semver.rcompare); + } catch (err) { + console.log(`Can't list version tags`, err); + } + + return null; +}; diff --git a/package.json b/package.json index 420b6e8..f724655 100644 --- a/package.json +++ b/package.json @@ -15,12 +15,8 @@ "commander": "^2.17.1", "conf": "^2.0.0", "execa": "~1.0.0", - "get-stream": "~4.0.0", - "git-all-tags": "~1.0.0", "git-commits": "~1.3.0", - "git-latest-semver-tag": "~1.0.2", "git-repo-name": "~0.6.0", - "git-semver-tags": "~2.0.0", "git-username": "~1.0.0", "inquirer": "~6.2.0", "lodash": "^4.17.10", diff --git a/src/create-changelog.js b/src/create-changelog.js index 26f5934..535e38c 100644 --- a/src/create-changelog.js +++ b/src/create-changelog.js @@ -5,7 +5,6 @@ const semver = require('semver'); const octokit = require('../lib/octokit.js'); const getCommits = require('../lib/git/get-commits.js'); -const lastTag = require('../lib/git/last-tag.js'); const findPrId = require('../lib/git/find-pr-id.js'); const render = require('../lib/changelog/render.js'); @@ -16,19 +15,13 @@ const createChangelog = async ({ repo, changelog: changelogCategories, } = {}) => { - try { - if (!tagFrom) { - tagFrom = await lastTag(); - } - } catch (err) { - console.log(`No tags found; can't create Changelog.`, err); - - return; - } - try { console.log( - chalk.green(`Rendering changelog from ${tagFrom}${tagTo ? ` to ${tagTo}` : ''}:`) + chalk.green( + `Rendering changelog from ${tagFrom ? tagFrom : 'Initial'}${ + tagTo ? ` to ${tagTo}` : '' + }:` + ) ); let commits = await getCommits({ from: tagFrom, to: tagTo || '' }); diff --git a/src/defaults.js b/src/defaults.js new file mode 100644 index 0000000..35a5da6 --- /dev/null +++ b/src/defaults.js @@ -0,0 +1,71 @@ +module.exports = { + branchFormat: '[USERNAME]/[ISSUEID]-[DESCRIPTION]', + defaultBranch: 'develop', + + cwd: process.cwd(), + + prefixes: { + feature: 'Features', + bugfix: 'Fixed Bugs', + refactor: 'Improved Code', + test: 'Improved Tests', + chore: 'Miscellaneous', + }, + + changelog: { + 'New Features & Improvements': ['Enhancement', 'Feature'], + 'Fixed Bugs': ['Bug'], + 'Behind the scenes': ['Refactor'], + 'Other changes': ['Chore'], + }, + + tagFrom: '', + tagTo: 'HEAD', + + // As of 2018-09-04 + githubLabelstoDelete: [ + 'bug', + 'duplicate', + 'enhancement', + 'good first issue', + 'help wanted', + 'invalid', + 'question', + 'wontfix', + ], + + defaultLabels: [ + // Workflow columns + { name: 'Status: To Do', color: '#73D0FF' }, + { name: 'Status: Blocked', color: '#363E4A' }, + { name: 'Status: In Progress', color: '#95E6CB' }, + { name: 'Status: Has PR', color: '#BAE67E' }, + { name: 'Status: To Test', color: '#FFC44C' }, + + // Types + { name: 'Bug', color: '#EE0701' }, + { name: 'Change Request', color: '#D4BFFF' }, + { name: 'Chore', color: '#E3EFF8' }, + { name: 'Enhancement', color: '#0052CC' }, + { name: 'Needs Design', color: '#363E4A' }, + { name: 'Needs Discussion', color: '#363E4A' }, + { name: 'Refactor', color: '#E3EFF8' }, + { name: 'Test', color: '#CCC9C2' }, + + // Priority + { name: 'Priority: Low', color: '#667380' }, + { name: 'Priority: !!!', color: '#FFA759' }, + + // Resolved states + { name: 'Closed: Duplicate', color: '#FFFFFF' }, + { name: 'Closed: Invalid', color: '#FFFFFF' }, + { name: 'Closed: Wontfix', color: '#FFFFFF' }, + + // Managed by CodeTree + { name: 'codetree-epic', color: '#FBF4EF' }, + { name: 'size: 1', color: '#FBF4EF' }, + { name: 'size: 2', color: '#FBF4EF' }, + { name: 'size: 4', color: '#FBF4EF' }, + { name: 'size: 8', color: '#FBF4EF' }, + ], +}; diff --git a/src/program.js b/src/program.js index 79e854d..d45ae84 100644 --- a/src/program.js +++ b/src/program.js @@ -1,11 +1,12 @@ const program = require('commander'); const pkg = require('../package.json'); +const defaults = require('./defaults.js'); program .version(pkg.version) .usage('[options]') .option('-i, --issue [n]', 'Begin work on a specific issue') - .option('-c, --changelog [t..t]', 'Create changelog') + .option('-c, --changelog [t..t]', 'Render a changelog from Pull Requests') .option('--sync-labels', 'Sync custom default labels to GitHub') .option('--remove-labels', 'Remove GitHub default labels') .option('--reset-config', 'Clears any configuration made (token, username, etc)') @@ -36,23 +37,18 @@ if (program.resetConfig) { args.task = 'reset-config'; } -if (program.changelog) { +if (program.changelog === true || typeof program.changelog === 'string') { args.task = 'changelog'; - let command = program.changelog; + if (typeof program.changelog === 'string') { + const [tagFrom, tagTo] = program.changelog.split('..'); - command = command === true ? '' : command; - - const range = command.split('..'); - const [tagFrom, tagTo] = range; - - if (tagFrom) { args.tagFrom = tagFrom; - } - - if (tagTo) { args.tagTo = tagTo; } + + args.tagFrom = args.tagFrom || defaults.tagFrom; + args.tagTo = args.tagTo || defaults.tagTo; } module.exports = args; diff --git a/src/questions.js b/src/questions.js index 7a581ee..48ef02a 100644 --- a/src/questions.js +++ b/src/questions.js @@ -3,10 +3,12 @@ const chalk = require('chalk'); const inquirer = require('inquirer'); const termSize = require('term-size'); +const defaults = require('./defaults.js'); const config = require('./config.js'); const settings = require('./settings.js'); const octokit = require('../lib/octokit.js'); const githubLogin = require('../lib/github/get-login.js'); +const versionTags = require('../lib/git/version-tags.js'); const ask = async ({ owner, repo }, program) => { if (!owner || !repo) { @@ -210,6 +212,60 @@ const ask = async ({ owner, repo }, program) => { results = Object.assign(results, answers); } + if (results.task === 'changelog') { + const versions = await versionTags(); + + const answers = await inquirer.prompt([ + { + name: 'tagFrom', + message: 'From where should the changelog start?', + type: 'list', + pageSize: Math.max(termSize().rows - 7, 5), + default: versions[0] || defaults.tagFrom, + choices: async () => { + return [ + ...versions, + { + name: 'Initial commit', + value: defaults.tagFrom, + short: 'Initial', + }, + ]; + }, + when() { + return program.tagFrom === undefined; + }, + }, + { + name: 'tagTo', + message: 'Where should the changelog end?', + type: 'list', + pageSize: Math.max(termSize().rows - 8, 5), + default: defaults.tagTo, + choices: async currentAnswers => { + const remainingVersions = versions.slice( + 0, + versions.indexOf(currentAnswers.tagFrom) + ); + + return [ + { + name: 'Current Head', + value: defaults.tagTo, + short: 'HEAD', + }, + ...remainingVersions, + ]; + }, + when() { + return program.tagTo === undefined; + }, + }, + ]); + + results = Object.assign(results, answers); + } + results = Object.assign({}, program, results); return results; diff --git a/src/settings.js b/src/settings.js index 196553b..20a255c 100644 --- a/src/settings.js +++ b/src/settings.js @@ -1,72 +1,7 @@ const rc = require('rc'); -const config = rc('work', { - branchFormat: '[USERNAME]/[ISSUEID]-[DESCRIPTION]', - defaultBranch: 'develop', +const defaults = require('./defaults.js'); - cwd: process.cwd(), - - prefixes: { - feature: 'Features', - bugfix: 'Fixed Bugs', - refactor: 'Improved Code', - test: 'Improved Tests', - chore: 'Miscellaneous', - }, - - changelog: { - 'New Features & Improvements': ['Enhancement', 'Feature'], - 'Behind the scenes': ['Refactor'], - 'Fixed Bugs': ['Bug'], - 'Other changes': ['Chore'], - }, - - // As of 2018-09-04 - githubLabelstoDelete: [ - 'bug', - 'duplicate', - 'enhancement', - 'good first issue', - 'help wanted', - 'invalid', - 'question', - 'wontfix', - ], - - defaultLabels: [ - // Workflow columns - { name: 'Status: To Do', color: '#73D0FF' }, - { name: 'Status: Blocked', color: '#363E4A' }, - { name: 'Status: In Progress', color: '#95E6CB' }, - { name: 'Status: Has PR', color: '#BAE67E' }, - { name: 'Status: To Test', color: '#FFC44C' }, - - // Types - { name: 'Bug', color: '#EE0701' }, - { name: 'Change Request', color: '#D4BFFF' }, - { name: 'Chore', color: '#E3EFF8' }, - { name: 'Enhancement', color: '#0052CC' }, - { name: 'Needs Design', color: '#363E4A' }, - { name: 'Needs Discussion', color: '#363E4A' }, - { name: 'Refactor', color: '#E3EFF8' }, - { name: 'Test', color: '#CCC9C2' }, - - // Priority - { name: 'Priority: Low', color: '#667380' }, - { name: 'Priority: !!!', color: '#FFA759' }, - - // Resolved states - { name: 'Closed: Duplicate', color: '#FFFFFF' }, - { name: 'Closed: Invalid', color: '#FFFFFF' }, - { name: 'Closed: Wontfix', color: '#FFFFFF' }, - - // Managed by CodeTree - { name: 'codetree-epic', color: '#FBF4EF' }, - { name: 'size: 1', color: '#FBF4EF' }, - { name: 'size: 2', color: '#FBF4EF' }, - { name: 'size: 4', color: '#FBF4EF' }, - { name: 'size: 8', color: '#FBF4EF' }, - ], -}); +const config = rc('work', defaults); module.exports = config; From acec31f9fc0fe1d034d9635ef2cfe07151c473ad Mon Sep 17 00:00:00 2001 From: Florian Pichler Date: Wed, 5 Sep 2018 23:03:30 +0200 Subject: [PATCH 2/2] Bugfix: Ensure work branches are created from develop Re #2 --- src/create-branch.js | 1 + 1 file changed, 1 insertion(+) diff --git a/src/create-branch.js b/src/create-branch.js index 9fdcab9..e261920 100644 --- a/src/create-branch.js +++ b/src/create-branch.js @@ -85,6 +85,7 @@ module.exports = async ({ owner, repo, issue: number, username, branchFormat, de } try { + await execa('git', ['checkout', 'develop']); await execa('git', ['checkout', '-b', branchName]); await execa('git', ['push', '--set-upstream']); } catch (err) {