From 69ba93e9b047e3de0dcfeed60eae6eceecae1d25 Mon Sep 17 00:00:00 2001 From: Andreas Richter Date: Mon, 2 Nov 2020 19:23:40 +0100 Subject: [PATCH 1/9] refactor(changelog): change version headline --- lib/commands/changelog.js | 18 +++++------ lib/commands/release.js | 8 ++--- lib/git/commits.js | 15 +++++++--- lib/steps/changelog.js | 10 +++++-- lib/steps/pending-changes.js | 8 ++--- lib/steps/version-commit.js | 5 +++- package-lock.json | 50 +++++-------------------------- package.json | 2 +- test/git/commits.test.js | 1 + test/steps/version-commit.test.js | 6 +++- 10 files changed, 51 insertions(+), 72 deletions(-) diff --git a/lib/commands/changelog.js b/lib/commands/changelog.js index deceac2..4cfde29 100644 --- a/lib/commands/changelog.js +++ b/lib/commands/changelog.js @@ -38,22 +38,18 @@ const getPendingChanges = require('../steps/pending-changes'); const generateChangeLog = require('../steps/changelog'); -async function showChangelog(cwd, pkg, options) { - function ensureVersionTag() { - return ensureTag(cwd, `v${pkg.version}`); - } - - function runTask(task) { - return task(cwd, pkg, options); - } +function ensureVersionTag(cwd, pkg) { + return ensureTag(cwd, `v${pkg.version}`); +} +async function showChangelog(cwd, pkg, options) { function printChangelog() { process.stdout.write(`${options.changelog}\n`); } - const tasks = [ensureVersionTag, getPendingChanges, generateChangeLog]; - - for (const task of tasks) await runTask(task); + for (const task of [ensureVersionTag, getPendingChanges, generateChangeLog]) { + await task(cwd, pkg, options); + } return printChangelog(); } diff --git a/lib/commands/release.js b/lib/commands/release.js index 19e17a7..72dc37a 100644 --- a/lib/commands/release.js +++ b/lib/commands/release.js @@ -84,10 +84,6 @@ function release(cwd, pkg, options) { publishToNpm, ]; - function runTask(task) { - return task(cwd, pkg, options); - } - async function runPublishTasks() { if (options.pr) { debug('Never publishing from a PR'); @@ -109,7 +105,9 @@ function release(cwd, pkg, options) { publishTasks = [publishToNpm]; } - for (const task of publishTasks) await runTask(task); + for (const task of publishTasks) { + await task(cwd, pkg, options); + } return null; } diff --git a/lib/git/commits.js b/lib/git/commits.js index 1ea9e86..94af8d2 100644 --- a/lib/git/commits.js +++ b/lib/git/commits.js @@ -86,6 +86,11 @@ function parseCommit(commit) { return { ...data, sha: sha || data.sha, parentSha }; } +/** + * + * @param {{type: string, header: string}} commit + * @return {*|boolean} + */ function isNotRandomMerge(commit) { // DotCI adds these :( return commit.type || `${commit.header}`.indexOf('Merge ') !== 0; @@ -111,6 +116,10 @@ function gracefulEmptyState(error) { throw error; } +/** + * @param {string} fromRevision + * @return {string|*[]} + */ function createRange(fromRevision) { if (fromRevision && fromRevision !== 'v0.0.0') { return `${fromRevision}..HEAD`; @@ -119,15 +128,13 @@ function createRange(fromRevision) { return []; } -function getCommits(directory, fromRevision) { +function getCommits(cwd, fromRevision) { return run( 'git', ['log', '--reverse', '--topo-order', GIT_LOG_FORMAT].concat( createRange(fromRevision) ), - { - cwd: directory, - } + { cwd } ).then(parseLogOutput, gracefulEmptyState); } diff --git a/lib/steps/changelog.js b/lib/steps/changelog.js index e0eb4d9..1c234db 100644 --- a/lib/steps/changelog.js +++ b/lib/steps/changelog.js @@ -192,22 +192,28 @@ ${changelog}`; */ function formatCommits(orphans) { + const orphansCommits = orphans.map(formatCommit); const changes = prs .map(formatPR) - .concat(orphans.map(formatCommit)) + .concat(orphansCommits) .map(line => { return `* ${line}`; }); return changes.join('\n'); } - for (const pr of prs) await addPullRequestCommits(pkg, commits, pr); + for (const pr of prs) { + await addPullRequestCommits(pkg, commits, pr); + } removeInvalidPRs(prs); + let changelog = removePRCommits(commits, prs); changelog = formatCommits(changelog); changelog = prependBreakingChanges(changelog); + options.changelog = changelog; + return changelog; } diff --git a/lib/steps/pending-changes.js b/lib/steps/pending-changes.js index 81f5f33..f646f86 100644 --- a/lib/steps/pending-changes.js +++ b/lib/steps/pending-changes.js @@ -86,11 +86,11 @@ function normalizeReferences(meta, commit) { return commit; } -function getPendingChanges(cwd, pkg, options) { +async function getPendingChanges(cwd, pkg, options) { const meta = parseRepository(pkg.repository); - return getCommits(cwd, `v${pkg.version}`).then(commits => { - options.commits = commits.map(normalizeReferences.bind(null, meta)); - }); + const commits = await getCommits(cwd, `v${pkg.version}`); + + options.commits = commits.map(normalizeReferences.bind(null, meta)); } module.exports = getPendingChanges; diff --git a/lib/steps/version-commit.js b/lib/steps/version-commit.js index 85cca18..51f772a 100644 --- a/lib/steps/version-commit.js +++ b/lib/steps/version-commit.js @@ -103,15 +103,18 @@ function createVersionCommit(cwd, pkg, options) { changeLogContent = ''; } - changeLogContent = `### ${options.nextVersion} - ${getCurrentDate()}\n\n${ + changeLogContent = `### v${options.nextVersion} (${getCurrentDate()})\n\n${ options.changelog }${changeLogContent}`; fs.writeFileSync(changeLogFile, `${changeLogContent.trim()}\n`); + const packageJsonFile = path.join(cwd, 'package.json'); pkg.version = options.nextVersion; fs.writeFileSync(packageJsonFile, `${JSON.stringify(pkg, null, 2)}\n`); + const files = ['CHANGELOG.md', 'package.json']; updatePackageLockVersion(cwd, pkg.version, files); + return addFiles(cwd, files) .then(commit.bind(null, cwd, `v${pkg.version}`)) .then(getHEAD.bind(null, cwd)) diff --git a/package-lock.json b/package-lock.json index 9f8e9c4..984294a 100644 --- a/package-lock.json +++ b/package-lock.json @@ -11,7 +11,7 @@ "conventional-commits-parser": "^3.1.0", "debug": "^4.2.0", "glob": "^7.1.6", - "gofer": "^5.0.3", + "gofer": "^5.0.4", "minimist": "^1.2.5", "rc": "^1.2.8", "semver": "^7.3.2" @@ -20,7 +20,6 @@ "nlm": "bin/nlm.js" }, "devDependencies": { - "assertive": "^5.0.2", "eslint": "^7.12.1", "eslint-config-groupon": "^10.0.1", "eslint-plugin-import": "^2.22.1", @@ -631,20 +630,6 @@ "node": ">=0.10.0" } }, - "node_modules/assertive": { - "version": "5.0.2", - "resolved": "https://registry.npmjs.org/assertive/-/assertive-5.0.2.tgz", - "integrity": "sha512-zvQkb694hAx3YC8f+eDqDoRmvrm/NkIbfS7EVUFiAmi6tXuJzoE4n2k/Y1UmTiCY3D2ePi9KL7TxJUx935/eaA==", - "dev": true, - "dependencies": { - "lodash.isequal": "^4.5.0" - }, - "engines": { - "node": ">=10.3.0", - "npm": "^6.1.0", - "yarn": "0.0.0" - } - }, "node_modules/astral-regex": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/astral-regex/-/astral-regex-1.0.0.tgz", @@ -1818,9 +1803,9 @@ } }, "node_modules/gofer": { - "version": "5.0.3", - "resolved": "https://registry.npmjs.org/gofer/-/gofer-5.0.3.tgz", - "integrity": "sha512-pDq3OhpopYNJCynBKPm6DThwdeHS6bWU5Yrv3/FEUSRyTbqw5mxpEOi03X2Ik9iU04EqCu1dwAiJhXruanm8Eg==", + "version": "5.0.4", + "resolved": "https://registry.npmjs.org/gofer/-/gofer-5.0.4.tgz", + "integrity": "sha512-0aPot92f+jjgxzgKFChWUEaofhES/QLq7JQV0BUbg016YMegPsr8nwWxuSstlY0Ske24b1b12NMwbeokM2nU/A==", "dependencies": { "debug": "^4.2.0", "lodash.isobjectlike": "^4.0.0", @@ -2469,12 +2454,6 @@ "integrity": "sha1-+wMJF/hqMTTlvJvsDWngAT3f7bI=", "dev": true }, - "node_modules/lodash.isequal": { - "version": "4.5.0", - "resolved": "https://registry.npmjs.org/lodash.isequal/-/lodash.isequal-4.5.0.tgz", - "integrity": "sha1-QVxEePK8wwEgwizhDtMib30+GOA=", - "dev": true - }, "node_modules/lodash.isobjectlike": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/lodash.isobjectlike/-/lodash.isobjectlike-4.0.0.tgz", @@ -5130,15 +5109,6 @@ "resolved": "https://registry.npmjs.org/arrify/-/arrify-1.0.1.tgz", "integrity": "sha1-iYUI2iIm84DfkEcoRWhJwVAaSw0=" }, - "assertive": { - "version": "5.0.2", - "resolved": "https://registry.npmjs.org/assertive/-/assertive-5.0.2.tgz", - "integrity": "sha512-zvQkb694hAx3YC8f+eDqDoRmvrm/NkIbfS7EVUFiAmi6tXuJzoE4n2k/Y1UmTiCY3D2ePi9KL7TxJUx935/eaA==", - "dev": true, - "requires": { - "lodash.isequal": "^4.5.0" - } - }, "astral-regex": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/astral-regex/-/astral-regex-1.0.0.tgz", @@ -6020,9 +5990,9 @@ } }, "gofer": { - "version": "5.0.3", - "resolved": "https://registry.npmjs.org/gofer/-/gofer-5.0.3.tgz", - "integrity": "sha512-pDq3OhpopYNJCynBKPm6DThwdeHS6bWU5Yrv3/FEUSRyTbqw5mxpEOi03X2Ik9iU04EqCu1dwAiJhXruanm8Eg==", + "version": "5.0.4", + "resolved": "https://registry.npmjs.org/gofer/-/gofer-5.0.4.tgz", + "integrity": "sha512-0aPot92f+jjgxzgKFChWUEaofhES/QLq7JQV0BUbg016YMegPsr8nwWxuSstlY0Ske24b1b12NMwbeokM2nU/A==", "requires": { "debug": "^4.2.0", "lodash.isobjectlike": "^4.0.0", @@ -6496,12 +6466,6 @@ "integrity": "sha1-+wMJF/hqMTTlvJvsDWngAT3f7bI=", "dev": true }, - "lodash.isequal": { - "version": "4.5.0", - "resolved": "https://registry.npmjs.org/lodash.isequal/-/lodash.isequal-4.5.0.tgz", - "integrity": "sha1-QVxEePK8wwEgwizhDtMib30+GOA=", - "dev": true - }, "lodash.isobjectlike": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/lodash.isobjectlike/-/lodash.isobjectlike-4.0.0.tgz", diff --git a/package.json b/package.json index 57f2679..343ec35 100644 --- a/package.json +++ b/package.json @@ -38,7 +38,7 @@ "conventional-commits-parser": "^3.1.0", "debug": "^4.2.0", "glob": "^7.1.6", - "gofer": "^5.0.3", + "gofer": "^5.0.4", "minimist": "^1.2.5", "rc": "^1.2.8", "semver": "^7.3.2" diff --git a/test/git/commits.test.js b/test/git/commits.test.js index 6232818..cdec5bd 100644 --- a/test/git/commits.test.js +++ b/test/git/commits.test.js @@ -151,6 +151,7 @@ describe('getCommits', () => { assertIssue('Jira', expected); }); }); + describe('with multiple commits', () => { const dirname = withFixture('multiple-commits'); let allCommits = null; diff --git a/test/steps/version-commit.test.js b/test/steps/version-commit.test.js index e4ac72d..398802c 100644 --- a/test/steps/version-commit.test.js +++ b/test/steps/version-commit.test.js @@ -66,9 +66,11 @@ describe('createVersionCommit', () => { before(resetVars); afterEach(resetVars); + describe('with no package-lock.json', () => { const dirname = withFixture('multiple-commits'); let currentDate; + before('commits with the original author', done => { execFile('git', ['show'], { cwd: dirname }, (err, stdout) => { if (err) return done(err); @@ -98,7 +100,7 @@ describe('createVersionCommit', () => { .readFileSync(`${dirname}/CHANGELOG.md`, 'utf8') .split('\n'); - assert.strictEqual(version, `### 1.0.0 - ${currentDate}`); + assert.strictEqual(version, `### v1.0.0 (${currentDate})`); assert.strictEqual(commit1, '* New stuff'); assert.strictEqual(commit2, '* Interesting features'); }); @@ -112,6 +114,7 @@ describe('createVersionCommit', () => { }); }); }); + describe('using package-lock.json', () => { const dirname = withFixture('with-plock'); @@ -136,6 +139,7 @@ describe('createVersionCommit', () => { describe('with unused package-lock.json', () => { const dirname = withFixture('with-bogus-plock'); + before('create version commit', () => createVersionCommit(dirname, pkg, options) ); From 63bcf4e892bc20a2c47ecaa7ee9d2b02eb5a492b Mon Sep 17 00:00:00 2001 From: Andreas Richter Date: Mon, 2 Nov 2020 23:25:00 +0100 Subject: [PATCH 2/9] feat: categorize changelog --- lib/steps/changelog.js | 159 +++++++++++++++++++---------------- test/steps/changelog.test.js | 1 + 2 files changed, 86 insertions(+), 74 deletions(-) diff --git a/lib/steps/changelog.js b/lib/steps/changelog.js index 1c234db..72e84a0 100644 --- a/lib/steps/changelog.js +++ b/lib/steps/changelog.js @@ -114,93 +114,104 @@ function removeInvalidPRs(prs) { Object.assign(prs, filtered); } -async function generateChangeLog(cwd, pkg, options) { - const repoInfo = parseRepository(pkg.repository); - const { commits, emoji = {} } = options; - let emojiSet = {}; - if (!emoji.skip) { - emojiSet = { ...emojiMaps.get('default'), ...(emoji.set || {}) }; +function formatReferences(refs) { + if (!refs || refs.length === 0) { + return ''; } - const prs = commits.filter(c => c.type === 'pr'); - function getCommitLink(commit) { - const abbr = commit.sha.substr(0, 7); - const href = [ - repoInfo.htmlBase, - repoInfo.username, - repoInfo.repository, - 'commit', - commit.sha, - ].join('/'); - return `[\`${abbr}\`](${href})`; + refs = refs.map(ref => { + return `[${ref.prefix}${ref.issue}](${ref.href})`; + }); + + return ` - see: ${refs.join(', ')}`; +} + +function getCommitLink(repoInfo, commit) { + const abbr = commit.sha.substr(0, 7); + const href = [ + repoInfo.htmlBase, + repoInfo.username, + repoInfo.repository, + 'commit', + commit.sha, + ].join('/'); + return `[\`${abbr}\`](${href})`; +} + +function formatBreakingChange(change, repoInfo) { + return `${change.text}\n\n*See: ${getCommitLink(repoInfo, change.commit)}*`; +} + +function prependBreakingChanges(data, commits, pkg, options) { + const { emoji } = options; + const breaking = flatten(commits.map(extractBreakingChanges)); + if (!breaking.length) { + return data; } - function formatBreakingChange(change) { - return `${change.text}\n\n*See: ${getCommitLink(change.commit)}*`; + let emojiSet = {}; + if (!emoji.skip) { + emojiSet = { ...emojiMaps.get('default'), ...(emoji.set || {}) }; } - function prependBreakingChanges(changelog) { - const breaking = flatten(commits.map(extractBreakingChanges)); - if (!breaking.length) return changelog; - return `#### ${ - !emoji.skip ? `${emojiSet['breaking']} ` : '' - }Breaking Changes + const repoInfo = parseRepository(pkg.repository); + const breakingChanges = breaking + .map(change => formatBreakingChange(change, repoInfo)) + .join('\n\n'); + + return [ + `#### ${!emoji.skip ? `${emojiSet['breaking']} ` : ''}Breaking Changes -${breaking.map(formatBreakingChange).join('\n\n')} +${breakingChanges} #### Commits +`, + ].concat(data); +} -${changelog}`; - } +function formatCommit(commit) { + let subject; - function formatReference(ref) { - return `[${ref.prefix}${ref.issue}](${ref.href})`; + if (commit.type) { + subject = `${commit.type}: ${commit.subject}`; + } else { + subject = commit.header; } - function formatReferences(refs) { - if (!refs || refs.length === 0) return ''; - return ` - see: ${refs.map(formatReference).join(', ')}`; - } + return `${getCommitLink(commit)} ${subject}${formatReferences( + commit.references + )}`; +} - function formatCommit(commit) { - let subject; +function formatPR(pr, options) { + const { nlmOptions = {} } = options; + const changes = + nlmOptions.changelog && nlmOptions.changelog.verbose + ? pr.commits.map(formatCommit).map(line => { + return ` - ${line}`; + }) + : []; - if (commit.type) { - subject = `${!emoji.skip ? `${emojiSet[commit.type]} ` : ''}**${ - commit.type - }:** ${commit.subject}`; - } else { - subject = commit.header; - } + const titleLine = `[#${pr.pullId}](${pr.href}) ${pr.title} ([@${pr.author.name}](${pr.author.href})) `; - return `${getCommitLink(commit)} ${subject}${formatReferences( - commit.references - )}`; - } + return [titleLine].concat(changes).join('\n'); +} - function formatPR(pr) { - const changes = pr.commits.map(formatCommit).map(line => { - return ` - ${line}`; +function formatCommits(rawPRs, rawCommits, options) { + const orphansCommits = rawCommits.map(formatCommit); + const changes = rawPRs + .map(pr => formatPR(pr, options)) + .concat(orphansCommits) + .map(line => { + return `* ${line}`; }); - const titleLine = `${pr.title} - **[@${pr.author.name}](${pr.author.href})** [#${pr.pullId}](${pr.href})`; - return [titleLine].concat(changes).join('\n'); - } - /* - * * {prTitle} - @{author} #{pullId} - * - {sha} {type}: {message} - * * {sha} {type}: {message} - */ - - function formatCommits(orphans) { - const orphansCommits = orphans.map(formatCommit); - const changes = prs - .map(formatPR) - .concat(orphansCommits) - .map(line => { - return `* ${line}`; - }); - return changes.join('\n'); - } + return changes; +} + +async function generateChangeLog(cwd, pkg, options) { + const { commits } = options; + + const prs = commits.filter(c => c.type === 'pr'); for (const pr of prs) { await addPullRequestCommits(pkg, commits, pr); @@ -208,13 +219,13 @@ ${changelog}`; removeInvalidPRs(prs); - let changelog = removePRCommits(commits, prs); - changelog = formatCommits(changelog); - changelog = prependBreakingChanges(changelog); + const cleanedCommits = removePRCommits(commits, prs); + const data = formatCommits(prs, cleanedCommits, options); + const rawChangelog = prependBreakingChanges(data, commits, pkg, options); - options.changelog = changelog; + options.changelog = rawChangelog.join('\n'); - return changelog; + return options.changelog; } module.exports = generateChangeLog; diff --git a/test/steps/changelog.test.js b/test/steps/changelog.test.js index 4b361ef..5751a0d 100644 --- a/test/steps/changelog.test.js +++ b/test/steps/changelog.test.js @@ -379,6 +379,7 @@ describe('generateChangeLog', () => { const pkg = { repository: 'http://127.0.0.1:3000/usr/proj', }; + describe('pull request commits', () => { const httpCalls = withFakeGithub(); const commits = [ From ea699851e7e4d75e9a64af4212f48aae92c4dfdd Mon Sep 17 00:00:00 2001 From: Andreas Richter Date: Tue, 3 Nov 2020 22:25:03 +0100 Subject: [PATCH 3/9] feat(changelog): categorize & group PRs & commits --- lib/cli.js | 4 +- lib/steps/changelog.js | 214 +++++++++++++---- test/steps/changelog.test.js | 450 ++++++++++++++++++++++------------- 3 files changed, 466 insertions(+), 202 deletions(-) diff --git a/lib/cli.js b/lib/cli.js index 3391402..5d49946 100644 --- a/lib/cli.js +++ b/lib/cli.js @@ -106,5 +106,7 @@ if (argv.version) { // eslint-disable-next-line import/no-dynamic-require const pkg = require(packageJsonFile); - command(cwd, pkg, { ...argv, ...pkg.nlm }).catch(prettyPrintErrorAndExit); + command(cwd, pkg, { ...argv, ...pkg.nlm, nlmOptions: { ...pkg.nlm } }).catch( + prettyPrintErrorAndExit + ); } diff --git a/lib/steps/changelog.js b/lib/steps/changelog.js index 72e84a0..fcf50da 100644 --- a/lib/steps/changelog.js +++ b/lib/steps/changelog.js @@ -36,6 +36,8 @@ const Github = require('../github/client'); const parseRepository = require('../github/parse-repository'); +const DEP_REGEXP = /[@\w\/-_.]+[@\s]v?\d+[0-9x.]+/im; + const emojiMaps = new Map([ [ 'default', @@ -54,10 +56,128 @@ const emojiMaps = new Map([ ci: '💚', }, ], + [ + 'babel', + { + breaking: 'đŸ’Ĩ', + feat: '🚀', + fix: '🐛', + perf: '⚡', + refactor: 'đŸ“Ļī¸', + revert: '↩ī¸', + docs: '📝', + style: '💅', + deps: 'đŸ”ŧ', + internal: '🏡', + }, + ], ]); +function getTypeCategory(type, options) { + const { nlmOptions = {} } = options; + const { emoji: emojiOpts = {} } = nlmOptions; + + let descr = ''; + const headlineLevel = '####'; + + switch (type) { + case 'breaking': + descr = 'Breaking Changes'; + break; + + case 'feat': + descr = 'New Features'; + break; + + case 'perf': + descr = 'Performance Improvements'; + break; + + case 'refactor': + descr = 'Code Refactoring'; + break; + + case 'fix': + descr = 'Bug Fixes'; + break; + + case 'dep': + descr = 'Dependencies'; + break; + + case 'revert': + descr = 'Reverts'; + break; + + case 'docs': + descr = 'Documentation'; + break; + + case 'style': + descr = 'Polish'; + break; + + default: + descr = 'Internal'; + } + + const emojiSet = { ...emojiMaps.get('babel'), ...(emojiOpts.set || {}) }; + const emoji = !emojiOpts.skip ? emojiSet[type] || emojiSet['internal'] : ''; + + return (emoji ? [headlineLevel, emoji, descr] : [headlineLevel, descr]).join( + ' ' + ); +} + +function sortByType(data) { + if (!data.length) { + return []; + } + + const sorted = []; + [ + 'breaking', + 'feat', + 'perf', + 'refactor', + 'fix', + 'dep', + 'revert', + 'style', + 'docs', + ].forEach(type => { + data = data.reduce((acc, entry) => { + if (entry[0] === type) { + sorted.push(entry); + } else { + acc.push(entry); + } + return acc; + }, []); + }); + + return sorted.concat(data); +} + +function getType(title) { + const match = title.match(/^(\w+):/); + let type = match && match[1]; + if (findDependency(title)) { + type = 'dep'; + } + return type || 'internal'; +} + +function findDependency(str) { + return str.match(DEP_REGEXP); +} + function addPullRequestCommits(pkg, commits, pr) { const github = Github.forRepository(pkg.repository); + + pr.commits = null; + pr.shas = null; + return Promise.all([ github.pull.get(pr.pullId), github.pull.commits(pr.pullId), @@ -69,13 +189,11 @@ function addPullRequestCommits(pkg, commits, pr) { }; pr.href = info.html_url; pr.title = info.title || info.header; - const shas = (pr.shas = prCommits.map(c => c.sha)); - pr.commits = commits.filter(commit => shas.includes(commit.sha)); + pr.shas = prCommits.map(c => c.sha); + pr.commits = commits.filter(commit => pr.shas.includes(commit.sha)); }) .catch(err => { if (err.statusCode !== 404) throw err; // If the PR doesn't exist, handle it gracefully. - - pr.commits = pr.shas = null; }); } @@ -83,7 +201,7 @@ function flatten(arrs) { return [].concat.apply([], arrs); } -function removePRCommits(commits, prs) { +function removePRCommits(prs, commits) { const prShas = flatten(prs.map(pr => pr.shas)); return commits.filter( commit => commit.type !== 'pr' && !prShas.includes(commit.sha) @@ -126,7 +244,7 @@ function formatReferences(refs) { return ` - see: ${refs.join(', ')}`; } -function getCommitLink(repoInfo, commit) { +function getCommitLink(commit, repoInfo) { const abbr = commit.sha.substr(0, 7); const href = [ repoInfo.htmlBase, @@ -139,37 +257,23 @@ function getCommitLink(repoInfo, commit) { } function formatBreakingChange(change, repoInfo) { - return `${change.text}\n\n*See: ${getCommitLink(repoInfo, change.commit)}*`; + return `${change.text}\n\n*See: ${getCommitLink(change.commit, repoInfo)}*`; } -function prependBreakingChanges(data, commits, pkg, options) { - const { emoji } = options; +function getBreakingChanges(commits, options, repoInfo) { const breaking = flatten(commits.map(extractBreakingChanges)); if (!breaking.length) { - return data; - } - - let emojiSet = {}; - if (!emoji.skip) { - emojiSet = { ...emojiMaps.get('default'), ...(emoji.set || {}) }; + return []; } - const repoInfo = parseRepository(pkg.repository); const breakingChanges = breaking .map(change => formatBreakingChange(change, repoInfo)) .join('\n\n'); - return [ - `#### ${!emoji.skip ? `${emojiSet['breaking']} ` : ''}Breaking Changes - -${breakingChanges} - -#### Commits -`, - ].concat(data); + return [['breaking', breakingChanges]]; } -function formatCommit(commit) { +function formatCommit(commit, repoInfo) { let subject; if (commit.type) { @@ -178,17 +282,17 @@ function formatCommit(commit) { subject = commit.header; } - return `${getCommitLink(commit)} ${subject}${formatReferences( + return `${getCommitLink(commit, repoInfo)} ${subject}${formatReferences( commit.references )}`; } -function formatPR(pr, options) { +function formatPR(pr, options, repoInfo) { const { nlmOptions = {} } = options; const changes = nlmOptions.changelog && nlmOptions.changelog.verbose - ? pr.commits.map(formatCommit).map(line => { - return ` - ${line}`; + ? pr.commits.map(commit => { + return ` - ${formatCommit(commit, repoInfo)}`; }) : []; @@ -197,35 +301,63 @@ function formatPR(pr, options) { return [titleLine].concat(changes).join('\n'); } -function formatCommits(rawPRs, rawCommits, options) { - const orphansCommits = rawCommits.map(formatCommit); - const changes = rawPRs - .map(pr => formatPR(pr, options)) +function format(rawPRs, rawCommits, options, repoInfo) { + const orphansCommits = rawCommits.map(commit => [ + getType(`${commit.type}: ${commit.subject}`), + formatCommit(commit, repoInfo), + ]); + + return rawPRs + .map(pr => [getType(pr.title), formatPR(pr, options, repoInfo)]) .concat(orphansCommits) - .map(line => { - return `* ${line}`; - }); - return changes; + .map(([type, line]) => [type, `* ${line}`]); +} + +function mergeChangelog(data, options) { + const changelog = new Map(); + const sorted = sortByType(data); + + for (const [type, entry] of sorted) { + const category = getTypeCategory(type, options); + if (!changelog.has(category)) { + changelog.set(category, [entry]); + } else { + changelog.set(category, changelog.get(category).concat(entry)); + } + } + return [...changelog] + .map(([headline, entries]) => `${headline}\n\n${entries.join('\n')}`) + .join('\n\n'); } async function generateChangeLog(cwd, pkg, options) { const { commits } = options; + const repoInfo = parseRepository(pkg.repository); const prs = commits.filter(c => c.type === 'pr'); + // step1: fetch PR commits data from GH & match with commits for (const pr of prs) { await addPullRequestCommits(pkg, commits, pr); } + // step2: remove PRs without commits removeInvalidPRs(prs); - const cleanedCommits = removePRCommits(commits, prs); - const data = formatCommits(prs, cleanedCommits, options); - const rawChangelog = prependBreakingChanges(data, commits, pkg, options); + // step3: remove commits of type `pr` + const cleanedCommits = removePRCommits(prs, commits); + + // step4: generate PRs / commits changelog entries + const data = format(prs, cleanedCommits, options, repoInfo); + + // step5: scan commits for breaking changes + const breakingChanges = getBreakingChanges(commits, options, repoInfo); - options.changelog = rawChangelog.join('\n'); + // step6: build changelog + options.changelog = mergeChangelog(breakingChanges.concat(data), options); return options.changelog; } +generateChangeLog.emojiMaps = emojiMaps; module.exports = generateChangeLog; diff --git a/test/steps/changelog.test.js b/test/steps/changelog.test.js index 5751a0d..8777308 100644 --- a/test/steps/changelog.test.js +++ b/test/steps/changelog.test.js @@ -46,7 +46,7 @@ function responseByUrl(url) { html_url: 'http://pr-1-user', }, html_url: 'http://pr-1', - title: 'PR #1 Title', + title: 'feat: PR #1 Title', }); break; @@ -120,10 +120,22 @@ function withFakeGithub() { return httpCalls; } +function assertEntries(changelog, expected) { + const lines = Array.isArray(changelog) ? changelog : changelog.split('\n'); + + return expected.map(entry => { + const index = lines.findIndex(i => i === entry); + assert.ok(index !== -1, expected); + return index; + }); +} + describe('generateChangeLog', () => { const defaultOptions = { - emoji: { - skip: true, + nlmOptions: { + emoji: { + skip: true, + }, }, }; @@ -188,19 +200,16 @@ describe('generateChangeLog', () => { const href1 = `https://github.com/usr/proj/commit/${commits[1].sha}`; const changelog = await generateChangeLog(null, pkg, options); + + const expectedEntries = [ + `* [\`1234567\`](${href0}) fix: Stop doing the wrong thing - see: [fo/ba#7](https://gitub.com/fo/ba/issues/7)`, + `* [\`2234567\`](${href1}) feat: Do more things - see: [THING-2010](https://example.com/browse/THING-7), [#44](https://github.com/usr/proj/issues/44)`, + ]; + const indices = assertEntries(changelog, expectedEntries); const lines = changelog.split('\n'); - assert.strictEqual( - lines[0], - `* [\`1234567\`](${href0}) **fix:** Stop doing the wrong thing - see: ` + - `[fo/ba#7](https://gitub.com/fo/ba/issues/7)` - ); - assert.strictEqual( - lines[1], - `* [\`2234567\`](${href1}) **feat:** Do more things - see: ` + - `[THING-2010](https://example.com/browse/THING-7), ` + - `[#44](https://github.com/usr/proj/issues/44)` - ); + assert.strictEqual(lines[indices[0] - 2], '#### Bug Fixes'); + assert.strictEqual(lines[indices[1] - 2], '#### New Features'); }); it('can create a changelog for two commits', async () => { @@ -228,13 +237,11 @@ describe('generateChangeLog', () => { const changelog = await generateChangeLog(null, pkg, options); - assert.strictEqual( - changelog, - [ - `* [\`1234567\`](${href0}) **fix:** Stop doing the wrong thing`, - `* [\`2234567\`](${href1}) **feat:** Do more things`, - ].join('\n') - ); + const expectedEntries = [ + `* [\`1234567\`](${href0}) fix: Stop doing the wrong thing`, + `* [\`2234567\`](${href1}) feat: Do more things`, + ]; + assertEntries(changelog, expectedEntries); }); it('puts breaking changes ahead of everything else', async () => { @@ -266,121 +273,85 @@ describe('generateChangeLog', () => { const href0 = `https://github.com/usr/proj/commit/${commits[0].sha}`; const href1 = `https://github.com/usr/proj/commit/${commits[1].sha}`; + const expected = [ + '#### Breaking Changes', + '', + 'The interface of this library changed in some way.', + '', + `*See: [\`2234567\`](${href1})*`, + '', + ].join('\n'); + + const expectedEntries = [ + `* [\`1234567\`](${href0}) fix: Stop doing the wrong thing`, + `* [\`2234567\`](${href1}) feat: Do more things`, + ]; + const changelog = await generateChangeLog(null, pkg, options); - assert.strictEqual( - changelog, - [ - '#### Breaking Changes', - '', - 'The interface of this library changed in some way.', - '', - `*See: [\`2234567\`](${href1})*`, - '', - '#### Commits', - '', - `* [\`1234567\`](${href0}) **fix:** Stop doing the wrong thing`, - `* [\`2234567\`](${href1}) **feat:** Do more things`, - ].join('\n') - ); + + assert.ok(changelog.startsWith(expected)); + assertEntries(changelog, expectedEntries); }); it('handles variants typings of BREAKING CHANGE', async () => { const pkg = { repository: 'usr/proj', }; - const commits = [ + const defaultCommit = { + sha: '2234567890123456789012345678901234567890', + type: 'feat', + subject: 'Do more things', + }; + const testCases = [ { - sha: '2234567890123456789012345678901234567890', - type: 'feat', - subject: 'Do more things', - notes: [ - { - title: 'BREAKING CHANGE', - text: 'without a colon', - }, - ], + title: 'BREAKING CHANGE', + text: 'without a colon', }, { - sha: '2234567890123456789012345678901234567891', - type: 'feat', - subject: 'Do more things', - notes: [ - { - title: 'BREAKING CHANGE:', - text: 'with a colon', - }, - ], + title: 'BREAKING CHANGE:', + text: 'with a colon', }, { - sha: '2234567890123456789012345678901234567892', - type: 'feat', - subject: 'Do more things', - notes: [ - { - title: 'BREAKING CHANGES', - text: 'plural', - }, - ], + title: 'BREAKING CHANGES', + text: 'plural', }, { - sha: '2234567890123456789012345678901234567893', - type: 'feat', - subject: 'Do more things', - notes: [ - { - title: 'BREAKING CHANGES:', - text: 'plural with colon', - }, - ], + title: 'BREAKING CHANGES:', + text: 'plural with colon', }, ]; - const options = { - ...defaultOptions, - commits, - }; - const href0 = `https://github.com/usr/proj/commit/${commits[0].sha}`; - const href1 = `https://github.com/usr/proj/commit/${commits[1].sha}`; - const href2 = `https://github.com/usr/proj/commit/${commits[2].sha}`; - const href3 = `https://github.com/usr/proj/commit/${commits[3].sha}`; - const changelog = await generateChangeLog(null, pkg, options); - assert.strictEqual( - changelog, - [ + for (const commitNote of testCases) { + const commit = { ...defaultCommit, notes: [commitNote] }; + const href0 = `https://github.com/usr/proj/commit/${commit.sha}`; + + const expected = [ '#### Breaking Changes', '', - 'without a colon', + commit.notes.map(note => note.text).join('\n'), '', `*See: [\`2234567\`](${href0})*`, '', - 'with a colon', - '', - `*See: [\`2234567\`](${href1})*`, - '', - 'plural', - '', - `*See: [\`2234567\`](${href2})*`, - '', - 'plural with colon', - '', - `*See: [\`2234567\`](${href3})*`, - '', - '#### Commits', - '', - `* [\`2234567\`](${href0}) **feat:** Do more things`, - `* [\`2234567\`](${href1}) **feat:** Do more things`, - `* [\`2234567\`](${href2}) **feat:** Do more things`, - `* [\`2234567\`](${href3}) **feat:** Do more things`, - ].join('\n') - ); + ].join('\n'); + + const changelog = await generateChangeLog(null, pkg, { + ...defaultOptions, + commits: [commit], + }); + + assert.ok(changelog.startsWith(expected)); + + assertEntries(changelog, [ + `* [\`2234567\`](${href0}) feat: Do more things`, + ]); + } }); describe('PRs', () => { - const pkg = { - repository: 'http://127.0.0.1:3000/usr/proj', - }; - describe('pull request commits', () => { + const pkg = { + repository: 'http://127.0.0.1:3000/usr/proj', + }; const httpCalls = withFakeGithub(); const commits = [ { @@ -414,13 +385,49 @@ describe('generateChangeLog', () => { assert.strictEqual(httpCalls.length, 2); }); - it('groups commits by pull request', () => { - assert.ok(changelog.includes('* PR #1 Title')); - assert.ok(changelog.includes(' - [`1234567`]')); + it('omits commits by pull request by default', async () => { + changelog = await generateChangeLog(null, pkg, options); + + assert.ok(changelog.includes('[#1](http://pr-1) feat: PR #1 Title')); + assert.ok( + !changelog.includes(' - [`1234567`] fix: Stop doing the wrong thing') + ); + }); + + describe('with changelog.verbose flag', () => { + const verboseOpts = { + nlmOptions: { changelog: { verbose: true } }, + }; + it('groups commits by pull request with changelog.verbose flag', async () => { + changelog = await generateChangeLog(null, pkg, { + ...options, + ...verboseOpts, + }); + + assert.ok(changelog.includes('[#1](http://pr-1) feat: PR #1 Title')); + assert.ok(changelog.includes(' - [`1234567`]')); + }); + + it("doesn't add emojis to pull commits", async () => { + changelog = await generateChangeLog(null, pkg, { + ...options, + nlmOptions: { + changelog: { verbose: true }, + emoji: { + skip: false, + }, + }, + }); + + assert.ok(changelog.includes(') fix: Stop')); + }); }); }); - describe('with an invalid PR', () => { + describe('with a PR w/o associated commits)', () => { + const pkg = { + repository: 'http://127.0.0.1:3000/usr/proj', + }; const httpCalls = withFakeGithub(); const commits = [ { @@ -440,12 +447,15 @@ describe('generateChangeLog', () => { pullId: '2', }, ]; + + // sloppyCommits lack "type" property const sloppyCommits = commits.map(commit => { - if (commit.type === 'pr') return commit; - return { - sha: commit.sha, - header: commit.subject, - }; + return commit.type === 'pr' + ? commit + : { + sha: commit.sha, + header: commit.subject, + }; }); const options = { ...defaultOptions, @@ -469,8 +479,8 @@ describe('generateChangeLog', () => { assert.strictEqual(httpCalls.length, 4); }); - it('ignores the PR', () => { - assert.ok(!changelog.includes('* PR #2 Title')); + it('ignores the PR w/o associated commits', () => { + assert.ok(!changelog.includes('* [#2] PR #2 Title')); assert.ok(changelog.includes('* [`1234567`]')); }); @@ -478,9 +488,19 @@ describe('generateChangeLog', () => { assert.ok(sloppyChangelog.includes(') Stop doing the wrong thing\n')); assert.ok(sloppyChangelog.includes(') Do more things')); }); + + it('categorizes commits w/o angular prefix as "Internal"', () => { + assert.ok( + sloppyChangelog.startsWith('#### Internal'), + 'sets "Internal" category' + ); + }); }); - describe('with a missing PR', () => { + describe('with a PR missing in GH', () => { + const pkg = { + repository: 'http://127.0.0.1:3000/usr/proj', + }; const httpCalls = withFakeGithub(); const commits = [ { @@ -494,6 +514,7 @@ describe('generateChangeLog', () => { subject: 'Do more things', }, { + // this PR is missing - see missing mocks at the beginning of this file sha: '3234567890123456789012345678901234567890', type: 'pr', subject: 'Merge PR #3', @@ -515,12 +536,136 @@ describe('generateChangeLog', () => { }); it('ignores the PR', () => { - assert.ok(changelog.includes('* [`1234567`]')); + assert.ok(!changelog.includes('Merge PR #3')); + }); + }); + + describe('categorization', () => { + withFakeGithub(); + const pkg = { + repository: 'usr/proj', + }; + const defaultCommit = { + sha: '1234567890123456789012345678901234567890', + subject: 'Stop doing the wrong thing', + }; + const headlineLevel = '####'; + + const testCases = [ + { headline: 'New Features', prefixes: ['feat'] }, + { headline: 'Code Refactoring', prefixes: ['refactor'] }, + { headline: 'Bug Fixes', prefixes: ['fix'] }, + { headline: 'Performance Improvements', prefixes: ['perf'] }, + { headline: 'Dependencies', prefixes: ['dep'] }, + { headline: 'Documentation', prefixes: ['docs'] }, + { headline: 'Polish', prefixes: ['style'] }, + { headline: 'Reverts', prefixes: ['revert'] }, + { + headline: 'Internal', + prefixes: ['ci', 'test', 'build', 'chore'], + }, + ]; + + const href0 = `https://github.com/usr/proj/commit/${defaultCommit.sha}`; + + it('sorts commits into according categories', async () => { + for (const { headline, prefixes } of testCases) { + for (const prefix of prefixes) { + const options = { + nlmOptions: { + emoji: { + skip: true, + }, + }, + commits: [{ ...defaultCommit, type: prefix }], + }; + + const expectedEntries = [ + `* [\`1234567\`](${href0}) ${prefix}: Stop doing the wrong thing`, + ]; + + const changelog = await generateChangeLog(null, pkg, options); + const lines = changelog.split('\n'); + + const indices = assertEntries(changelog, expectedEntries); + assert.strictEqual( + lines[indices[0] - 2], + `${headlineLevel} ${headline}` + ); + } + } + }); + + it('adds category emojis to each category', async () => { + const emojiMaps = generateChangeLog.emojiMaps.get('babel'); + for (const { headline, prefixes } of testCases) { + for (const prefix of prefixes) { + const options = { + commits: [{ ...defaultCommit, type: prefix }], + }; + + const expectedEntries = [ + `* [\`1234567\`](${href0}) ${prefix}: Stop doing the wrong thing`, + ]; + + const changelog = await generateChangeLog(null, pkg, options); + const lines = changelog.split('\n'); + + const indices = assertEntries(changelog, expectedEntries); + assert.strictEqual( + lines[indices[0] - 2], + `${headlineLevel} ${ + emojiMaps[prefix] || emojiMaps['internal'] + } ${headline}` + ); + } + } + }); + + it('identifies dependency updates in commit subject and groups them into "Dependencies" category', async () => { + const subjectCases = [ + 'update @grpn/create@23.0', + 'update @grpn/create@23.0.0', + 'update @grpn/create@23.0.x update `@grpn/create@23.0.x`', + 'update @grpn/cr.eate@23.x', + 'update create@23.x', + 'update cre.ate@23', + 'update cre.ate@v23', + 'update cre.ate v23', + 'update cre.ate 23', + 'update `cre.ate 23`', + ]; + + for (const subject of subjectCases) { + const commits = [ + { + sha: '1234567890123456789012345678901234567890', + type: 'fix', + subject, + }, + ]; + const options = { + ...defaultOptions, + commits, + }; + const href = `https://github.com/usr/proj/commit/${commits[0].sha}`; + const expectedEntries = [`* [\`1234567\`](${href}) fix: ${subject}`]; + + const changelog = await generateChangeLog( + null, + pkg, + options + ).then(res => res.split('\n')); + + const index = assertEntries(changelog, expectedEntries); + assert.strictEqual(changelog[index - 2], '#### Dependencies'); + } }); }); }); - describe('emoji', () => { + describe('Emojis', () => { + let changelog; const pkg = { repository: 'http://127.0.0.1:3000/usr/proj', }; @@ -529,51 +674,36 @@ describe('generateChangeLog', () => { subject: 'something', }; const cases = [ - { type: 'feat', expected: '✨ **feat:**' }, - { type: 'fix', expected: '🐛 **fix:**' }, - { type: 'refactor', expected: 'đŸ“Ļī¸ **refactor:**' }, - { type: 'docs', expected: '📝 **docs:**' }, - { type: 'revert', expected: '↩ī¸ **revert:**' }, - { type: 'style', expected: '🎨 **style:**' }, - { type: 'build', expected: '👷 **build:**' }, - { type: 'ci', expected: '💚 **ci:**' }, - { type: 'test', expected: '✅ **test:**' }, - { type: 'perf', expected: '⚡ **perf:**' }, - { type: 'chore', expected: 'â™ģī¸ **chore:**' }, + { type: 'feat' }, + { type: 'fix' }, + { type: 'refactor' }, + { type: 'docs' }, + { type: 'revert' }, + { type: 'style' }, + { type: 'build' }, + { type: 'ci' }, + { type: 'test' }, + { type: 'perf' }, + { type: 'chore' }, ]; const commits = cases.map(({ type }) => ({ type, ...defaultCommit })); const options = { commits, - }; - let changelog; - - it('sets emojis for all commit types', async () => { - changelog = await generateChangeLog(null, pkg, options); - - cases.forEach(({ expected }) => { - assert.ok(changelog.includes(expected), `should include ${expected}`); - }); - }); - - it('allows custom emojis with emoji.set config', async () => { - changelog = await generateChangeLog(null, pkg, { - ...options, - emoji: { - set: { - feat: '🚀', - }, + nlmOptions: { + changelog: { + verbose: true, }, - }); - const expected = '🚀 **feat:**'; - - assert.ok(changelog.includes(expected), `should include ${expected}`); - }); + }, + }; it('disables emojis with emoji.skip config', async () => { changelog = await generateChangeLog(null, pkg, { ...options, - emoji: { - skip: true, + nlmOptions: { + ...options.nlmOptions, + emoji: { + skip: true, + }, }, }); const notExpected = 'đŸ“Ļī¸'; From 0bc5730a454cb30f3bf774de45918e6ce0a43e10 Mon Sep 17 00:00:00 2001 From: Andreas Richter Date: Tue, 3 Nov 2020 23:15:31 +0100 Subject: [PATCH 4/9] docs(changelog): add JSDOC typings --- lib/steps/changelog.js | 139 ++++++++++++++++++++++++++++++++++++++--- 1 file changed, 131 insertions(+), 8 deletions(-) diff --git a/lib/steps/changelog.js b/lib/steps/changelog.js index fcf50da..ddcfb57 100644 --- a/lib/steps/changelog.js +++ b/lib/steps/changelog.js @@ -32,6 +32,40 @@ 'use strict'; +/** + * @typedef PR + * @property {number} pullId + * @property {{name: string, href: string}} author + * @property {string} href + * @property {string} title + * @property {string[]} shas + * @property {Commit[]} commits + */ + +/** + * @typedef Commit + * @extends PR + * @property {string} type + * @property {string} header + * @property {string} subject + * @property {string} sha + * @property {{title: string, text:string}[]} notes + * @property {{prefix: string, issue: number, href: string}[]} references + */ + +/** + * @typedef RepoInfo + * @property {string} htmlBase + * @property {string} username + * @property {string} repository + */ + +/** + * @typedef NlmOptions + * @property {{skip?: boolean, set: {[key: string]: string}}} emoji + * @property {{verbose?: boolean}} changelog + */ + const Github = require('../github/client'); const parseRepository = require('../github/parse-repository'); @@ -73,11 +107,16 @@ const emojiMaps = new Map([ ], ]); +/** + * @param {string} type + * @param {{nlmOptions?: NlmOptions}} options + * @return {string} + */ function getTypeCategory(type, options) { const { nlmOptions = {} } = options; const { emoji: emojiOpts = {} } = nlmOptions; - let descr = ''; + let descr; const headlineLevel = '####'; switch (type) { @@ -129,6 +168,10 @@ function getTypeCategory(type, options) { ); } +/** + * @param {*[]} data + * @return {*[]} + */ function sortByType(data) { if (!data.length) { return []; @@ -159,6 +202,10 @@ function sortByType(data) { return sorted.concat(data); } +/** + * @param {string} title + * @return {string} + */ function getType(title) { const match = title.match(/^(\w+):/); let type = match && match[1]; @@ -168,13 +215,22 @@ function getType(title) { return type || 'internal'; } +/** + * @param {String} str + * @return {RegExpMatchArray|null} + */ function findDependency(str) { return str.match(DEP_REGEXP); } -function addPullRequestCommits(pkg, commits, pr) { - const github = Github.forRepository(pkg.repository); - +/** + * + * @param {Object} github + * @param {Commit[]} commits + * @param {PR} pr + * @return {Promise} + */ +function addPullRequestCommits(github, commits, pr) { pr.commits = null; pr.shas = null; @@ -197,10 +253,19 @@ function addPullRequestCommits(pkg, commits, pr) { }); } +/** + * @param {*[]} arrs + * @return {*[]} + */ function flatten(arrs) { return [].concat.apply([], arrs); } +/** + * @param {PR[]} prs + * @param {Commit[]} commits + * @return {*[]|Commit[]} + */ function removePRCommits(prs, commits) { const prShas = flatten(prs.map(pr => pr.shas)); return commits.filter( @@ -208,8 +273,12 @@ function removePRCommits(prs, commits) { ); } +/** + * @param {Commit} commit + * @return {*[]|{commit: Commit, text: string}[]} + */ function extractBreakingChanges(commit) { - if (!commit.notes || !commit.notes.length) { + if (!(commit.notes && commit.notes.length)) { return []; } @@ -223,6 +292,9 @@ function extractBreakingChanges(commit) { }); } +/** + * @param {PR[]} prs + */ function removeInvalidPRs(prs) { // Warning: We're doing something evil here and mutate the input array. const filtered = prs.filter(pr => { @@ -232,18 +304,27 @@ function removeInvalidPRs(prs) { Object.assign(prs, filtered); } +/** + * @param {{prefix: string, issue: number, href: string}[]} refs + * @return {string} + */ function formatReferences(refs) { if (!refs || refs.length === 0) { return ''; } - refs = refs.map(ref => { + const references = refs.map(ref => { return `[${ref.prefix}${ref.issue}](${ref.href})`; }); - return ` - see: ${refs.join(', ')}`; + return ` - see: ${references.join(', ')}`; } +/** + * @param {Commit} commit + * @param {RepoInfo} repoInfo + * @return {string} + */ function getCommitLink(commit, repoInfo) { const abbr = commit.sha.substr(0, 7); const href = [ @@ -256,10 +337,21 @@ function getCommitLink(commit, repoInfo) { return `[\`${abbr}\`](${href})`; } +/** + * @param {{text: string, commit: Commit}} change + * @param {RepoInfo} repoInfo + * @return {string} + */ function formatBreakingChange(change, repoInfo) { return `${change.text}\n\n*See: ${getCommitLink(change.commit, repoInfo)}*`; } +/** + * @param {Commit[]} commits + * @param {{nlmOptions?: NlmOptions}} options}} options + * @param {RepoInfo} repoInfo + * @return {*[]|(string)[][]} + */ function getBreakingChanges(commits, options, repoInfo) { const breaking = flatten(commits.map(extractBreakingChanges)); if (!breaking.length) { @@ -273,6 +365,11 @@ function getBreakingChanges(commits, options, repoInfo) { return [['breaking', breakingChanges]]; } +/** + * @param {Commit} commit + * @param {RepoInfo} repoInfo + * @return {string} + */ function formatCommit(commit, repoInfo) { let subject; @@ -287,6 +384,12 @@ function formatCommit(commit, repoInfo) { )}`; } +/** + * @param {PR} pr + * @param {{nlmOptions?: NlmOptions}} options + * @param {RepoInfo} repoInfo + * @return {string} + */ function formatPR(pr, options, repoInfo) { const { nlmOptions = {} } = options; const changes = @@ -301,6 +404,13 @@ function formatPR(pr, options, repoInfo) { return [titleLine].concat(changes).join('\n'); } +/** + * @param {PR[]} rawPRs + * @param {Commit[]} rawCommits + * @param {{nlmOptions?: NlmOptions}} options + * @param {RepoInfo} repoInfo + * @return {*[]|[string, string][]} + */ function format(rawPRs, rawCommits, options, repoInfo) { const orphansCommits = rawCommits.map(commit => [ getType(`${commit.type}: ${commit.subject}`), @@ -313,6 +423,11 @@ function format(rawPRs, rawCommits, options, repoInfo) { .map(([type, line]) => [type, `* ${line}`]); } +/** + * @param {[string, string][]|*[]} data + * @param {{nlmOptions?: NlmOptions}} options + * @return {string} + */ function mergeChangelog(data, options) { const changelog = new Map(); const sorted = sortByType(data); @@ -330,15 +445,23 @@ function mergeChangelog(data, options) { .join('\n\n'); } +/** + * @param {*} cwd + * @param {{repository: string|{url: string}}} pkg + * @param {{commits: Commit[], nlmOptions?: NlmOptions, changelog?: string}} options + * @return {Promise} + */ async function generateChangeLog(cwd, pkg, options) { const { commits } = options; const repoInfo = parseRepository(pkg.repository); + const github = Github.forRepository(pkg.repository); + /** @type {PR[]} */ const prs = commits.filter(c => c.type === 'pr'); // step1: fetch PR commits data from GH & match with commits for (const pr of prs) { - await addPullRequestCommits(pkg, commits, pr); + await addPullRequestCommits(github, commits, pr); } // step2: remove PRs without commits From 2173cf8252defe0d4b8b5793948bec608d658704 Mon Sep 17 00:00:00 2001 From: Andreas Richter Date: Wed, 4 Nov 2020 00:34:16 +0100 Subject: [PATCH 5/9] feat(changelog): add changelog.omit option to omit commit types --- README.md | 30 ++++++++++++++++++++++++++---- lib/steps/changelog.js | 12 ++++++++++-- test/steps/changelog.test.js | 33 +++++++++++++++++++++++++++++++++ 3 files changed, 69 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index 4a0cabc..8a3bdd6 100644 --- a/README.md +++ b/README.md @@ -126,9 +126,28 @@ Hook | Description `prepare` | Called when the release is about to be prepared. This is before updating files such as package.json, CHANGELOG.md and pushing a commit. It provides a reference to the **next version** number via the environment variable **NLM_NEXT_VERSION**. +```ts +interface NlmOptions { + acceptInvalidCommits?: boolean; + changelog: { + omit?: string[], + verbose?: boolean + }; + deprecated?: boolean; + emoji?: { + skip?: boolean + set?: {[type: string]: string} + }; + license?: { + files?: string[], + exclude?: string[] + } +} +``` -* `license.files`: List of files and/or directories to add license headers to. -* `license.exclude`: List of files to exclude that would otherwise be included. `nlm` will always exclude +* `license`: + * `files`: List of files and/or directories to add license headers to. + * `exclude`: List of files to exclude that would otherwise be included. `nlm` will always exclude anything in `node_modules`. * `acceptInvalidCommits`: Accept commit messages even if they can't be parsed. It's highly discouraged to use this option. @@ -136,10 +155,13 @@ anything in `node_modules`. * `deprecated`: String (may be empty) describing reason this package has been deprecated. To deprecate a package, set it to a descriptive reason. To "un-deprecate" a package, set it to an empty string (can then be later deleted). +* `changelog`: + * `omit`: Array of types, which will be omitted from the changelog. + * `verbose`: Display PR's commits. Default: `false` * `emoji`: Configure changelog emoji setting logic - * `emoji.skip`: deactivates emoji in changelog. Default: `null` - * `emoji.set`: Custom emojis map, which will overwrite the default one + * `skip`: deactivates emoji in changelog. Default: `null` + * `set`: Custom emojis map, which will overwrite the default one Example for ```json5 diff --git a/lib/steps/changelog.js b/lib/steps/changelog.js index ddcfb57..37529a3 100644 --- a/lib/steps/changelog.js +++ b/lib/steps/changelog.js @@ -62,8 +62,8 @@ /** * @typedef NlmOptions - * @property {{skip?: boolean, set: {[key: string]: string}}} emoji - * @property {{verbose?: boolean}} changelog + * @property {{skip?: boolean, set: {[type: string]: string}}} emoji + * @property {{verbose?: boolean, omit?: string[]}} changelog */ const Github = require('../github/client'); @@ -429,17 +429,25 @@ function format(rawPRs, rawCommits, options, repoInfo) { * @return {string} */ function mergeChangelog(data, options) { + const { nlmOptions = {} } = options; const changelog = new Map(); const sorted = sortByType(data); for (const [type, entry] of sorted) { const category = getTypeCategory(type, options); + + // filter out types + if (nlmOptions.changelog && nlmOptions.changelog.omit) { + if (nlmOptions.changelog.omit.includes(type)) continue; + } + if (!changelog.has(category)) { changelog.set(category, [entry]); } else { changelog.set(category, changelog.get(category).concat(entry)); } } + return [...changelog] .map(([headline, entries]) => `${headline}\n\n${entries.join('\n')}`) .join('\n\n'); diff --git a/test/steps/changelog.test.js b/test/steps/changelog.test.js index 8777308..b60b0fc 100644 --- a/test/steps/changelog.test.js +++ b/test/steps/changelog.test.js @@ -244,6 +244,39 @@ describe('generateChangeLog', () => { assertEntries(changelog, expectedEntries); }); + it('omits commit types from changelog when specificed in nlm changelog options', async () => { + const pkg = { + repository: 'usr/proj', + }; + const commits = [ + { + sha: '1234567890123456789012345678901234567890', + type: 'fix', + subject: 'Stop doing the wrong thing', + }, + { + sha: '2234567890123456789012345678901234567890', + type: 'feat', + subject: 'Do more things', + }, + ]; + const options = { + commits, + nlmOptions: { + changelog: { + omit: ['fix'], + }, + emoji: { + skip: true, + }, + }, + }; + + const changelog = await generateChangeLog(null, pkg, options); + + assert.ok(!changelog.includes('[`1234567`]')); + }); + it('puts breaking changes ahead of everything else', async () => { const pkg = { repository: 'usr/proj', From b3b4dab1b59e4bef151c1fe75e5a2677f6842ba2 Mon Sep 17 00:00:00 2001 From: Andreas Richter Date: Wed, 4 Nov 2020 09:45:20 +0100 Subject: [PATCH 6/9] feat(changelog): highlight dependency updates in commit subject --- lib/steps/changelog.js | 8 +- package-lock.json | 374 +++++++++++++++++------------------ package.json | 3 +- test/steps/changelog.test.js | 36 ++-- 4 files changed, 215 insertions(+), 206 deletions(-) diff --git a/lib/steps/changelog.js b/lib/steps/changelog.js index 37529a3..b9df96d 100644 --- a/lib/steps/changelog.js +++ b/lib/steps/changelog.js @@ -66,11 +66,13 @@ * @property {{verbose?: boolean, omit?: string[]}} changelog */ +const replaceAll = require('string.prototype.replaceall'); + const Github = require('../github/client'); const parseRepository = require('../github/parse-repository'); -const DEP_REGEXP = /[@\w\/-_.]+[@\s]v?\d+[0-9x.]+/im; +const DEP_REGEXP = /(`)?([@\w\/-_.]+[@\s]v?\d+[0-9x.]+)(`)?/gim; const emojiMaps = new Map([ [ @@ -379,6 +381,10 @@ function formatCommit(commit, repoInfo) { subject = commit.header; } + if (findDependency(subject)) { + subject = replaceAll(subject, DEP_REGEXP, '`$2`'); + } + return `${getCommitLink(commit, repoInfo)} ${subject}${formatReferences( commit.references )}`; diff --git a/package-lock.json b/package-lock.json index 984294a..a5712e6 100644 --- a/package-lock.json +++ b/package-lock.json @@ -14,7 +14,8 @@ "gofer": "^5.0.4", "minimist": "^1.2.5", "rc": "^1.2.8", - "semver": "^7.3.2" + "semver": "^7.3.2", + "string.prototype.replaceall": "^1.0.3" }, "bin": { "nlm": "bin/nlm.js" @@ -83,12 +84,12 @@ } }, "node_modules/@babel/generator": { - "version": "7.12.1", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.12.1.tgz", - "integrity": "sha512-DB+6rafIdc9o72Yc3/Ph5h+6hUjeOp66pF0naQBgUFFuPqzQwIlPTm3xZR7YNvduIMtkDIj2t21LSQwnbCrXvg==", + "version": "7.12.5", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.12.5.tgz", + "integrity": "sha512-m16TQQJ8hPt7E+OS/XVQg/7U184MLXtvuGbCdA7na61vha+ImkyyNM/9DDA0unYCVZn3ZOhng+qz48/KBOT96A==", "dev": true, "dependencies": { - "@babel/types": "^7.12.1", + "@babel/types": "^7.12.5", "jsesc": "^2.5.1", "source-map": "^0.5.0" } @@ -123,12 +124,12 @@ } }, "node_modules/@babel/helper-module-imports": { - "version": "7.12.1", - "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.12.1.tgz", - "integrity": "sha512-ZeC1TlMSvikvJNy1v/wPIazCu3NdOwgYZLIkmIyAsGhqkNpiDoQQRmaCK8YP4Pq3GPTLPV9WXaPCJKvx06JxKA==", + "version": "7.12.5", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.12.5.tgz", + "integrity": "sha512-SR713Ogqg6++uexFRORf/+nPXMmWIn80TALu0uaFb+iQIUoR7bOC7zBWyzBs5b3tBBJXuyD0cRu1F15GyzjOWA==", "dev": true, "dependencies": { - "@babel/types": "^7.12.1" + "@babel/types": "^7.12.5" } }, "node_modules/@babel/helper-module-transforms": { @@ -158,15 +159,15 @@ } }, "node_modules/@babel/helper-replace-supers": { - "version": "7.12.1", - "resolved": "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.12.1.tgz", - "integrity": "sha512-zJjTvtNJnCFsCXVi5rUInstLd/EIVNmIKA1Q9ynESmMBWPWd+7sdR+G4/wdu+Mppfep0XLyG2m7EBPvjCeFyrw==", + "version": "7.12.5", + "resolved": "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.12.5.tgz", + "integrity": "sha512-5YILoed0ZyIpF4gKcpZitEnXEJ9UoDRki1Ey6xz46rxOzfNMAhVIJMoune1hmPVxh40LRv1+oafz7UsWX+vyWA==", "dev": true, "dependencies": { "@babel/helper-member-expression-to-functions": "^7.12.1", "@babel/helper-optimise-call-expression": "^7.10.4", - "@babel/traverse": "^7.12.1", - "@babel/types": "^7.12.1" + "@babel/traverse": "^7.12.5", + "@babel/types": "^7.12.5" } }, "node_modules/@babel/helper-simple-access": { @@ -193,14 +194,14 @@ "integrity": "sha512-3U9y+43hz7ZM+rzG24Qe2mufW5KhvFg/NhnNph+i9mgCtdTCtMJuI1TMkrIUiK7Ix4PYlRF9I5dhqaLYA/ADXw==" }, "node_modules/@babel/helpers": { - "version": "7.12.1", - "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.12.1.tgz", - "integrity": "sha512-9JoDSBGoWtmbay98efmT2+mySkwjzeFeAL9BuWNoVQpkPFQF8SIIFUfY5os9u8wVzglzoiPRSW7cuJmBDUt43g==", + "version": "7.12.5", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.12.5.tgz", + "integrity": "sha512-lgKGMQlKqA8meJqKsW6rUnc4MdUk35Ln0ATDqdM1a/UpARODdI4j5Y5lVfUScnSNkJcdCRAaWkspykNoFg9sJA==", "dev": true, "dependencies": { "@babel/template": "^7.10.4", - "@babel/traverse": "^7.12.1", - "@babel/types": "^7.12.1" + "@babel/traverse": "^7.12.5", + "@babel/types": "^7.12.5" } }, "node_modules/@babel/highlight": { @@ -278,9 +279,9 @@ } }, "node_modules/@babel/parser": { - "version": "7.12.3", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.12.3.tgz", - "integrity": "sha512-kFsOS0IbsuhO5ojF8Hc8z/8vEIOkylVBrjiZUbLTE3XFe0Qi+uu6HjzQixkFaqr0ZPAMZcBVxEwmsnsLPZ2Xsw==", + "version": "7.12.5", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.12.5.tgz", + "integrity": "sha512-FVM6RZQ0mn2KCf1VUED7KepYeUWoVShczewOCfm3nzoBybaih51h+sYVVGthW9M6lPByEPTQf+xm27PBdlpwmQ==", "dev": true, "bin": { "parser": "bin/babel-parser.js" @@ -301,17 +302,17 @@ } }, "node_modules/@babel/traverse": { - "version": "7.12.1", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.12.1.tgz", - "integrity": "sha512-MA3WPoRt1ZHo2ZmoGKNqi20YnPt0B1S0GTZEPhhd+hw2KGUzBlHuVunj6K4sNuK+reEvyiPwtp0cpaqLzJDmAw==", + "version": "7.12.5", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.12.5.tgz", + "integrity": "sha512-xa15FbQnias7z9a62LwYAA5SZZPkHIXpd42C6uW68o8uTuua96FHZy1y61Va5P/i83FAAcMpW8+A/QayntzuqA==", "dev": true, "dependencies": { "@babel/code-frame": "^7.10.4", - "@babel/generator": "^7.12.1", + "@babel/generator": "^7.12.5", "@babel/helper-function-name": "^7.10.4", "@babel/helper-split-export-declaration": "^7.11.0", - "@babel/parser": "^7.12.1", - "@babel/types": "^7.12.1", + "@babel/parser": "^7.12.5", + "@babel/types": "^7.12.5", "debug": "^4.1.0", "globals": "^11.1.0", "lodash": "^4.17.19" @@ -327,9 +328,9 @@ } }, "node_modules/@babel/types": { - "version": "7.12.1", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.12.1.tgz", - "integrity": "sha512-BzSY3NJBKM4kyatSOWh3D/JJ2O3CVzBybHWxtgxnggaxEuaSTTDqeiSb/xk9lrkw2Tbqyivw5ZU4rT+EfznQsA==", + "version": "7.12.5", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.12.5.tgz", + "integrity": "sha512-gyTcvz7JFa4V45C0Zklv//GmFOAal5fL23OWpBLqc4nZ4Yrz67s4kCNwSK1Gu0MXGTU8mRY3zJYtacLdKXlzig==", "dev": true, "dependencies": { "@babel/helper-validator-identifier": "^7.10.4", @@ -695,6 +696,18 @@ "node": ">=8" } }, + "node_modules/call-bind": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.0.tgz", + "integrity": "sha512-AEXsYIyyDY3MCzbwdhzG3Jx1R0J2wetQyUynn6dYHAO+bg8l1k7jwZtRv4ryryFs7EP+NDlikJlVe59jr0cM2w==", + "dependencies": { + "function-bind": "^1.1.1", + "get-intrinsic": "^1.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/callsites": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", @@ -967,7 +980,6 @@ "version": "1.1.3", "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.1.3.tgz", "integrity": "sha512-3MqfYKj2lLzdMSf8ZIZE/V+Zuy+BgD6f164e8K2w7dgnpKArBDerGYpM46IYYcjnkdPNMjPk9A6VFB8+3SKlXQ==", - "dev": true, "dependencies": { "object-keys": "^1.0.12" }, @@ -1026,7 +1038,6 @@ "version": "1.17.7", "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.17.7.tgz", "integrity": "sha512-VBl/gnfcJ7OercKA9MVaegWsBHFjV492syMudcnQZvt/Dw8ezpcOHYZXa/J96O8vx+g4x65YKhxOwDUh63aS5g==", - "dev": true, "dependencies": { "es-to-primitive": "^1.2.1", "function-bind": "^1.1.1", @@ -1051,7 +1062,6 @@ "version": "1.2.1", "resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.2.1.tgz", "integrity": "sha512-QCOllgZJtaUo9miYBcLChTUaHNjJF3PYs1VidD7AwiEj1kYxKeQTctLAezAOH5ZKRH0g2IgPn6KwB4IT8iRpvA==", - "dev": true, "dependencies": { "is-callable": "^1.1.4", "is-date-object": "^1.0.1", @@ -1680,9 +1690,9 @@ } }, "node_modules/fromentries": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/fromentries/-/fromentries-1.3.0.tgz", - "integrity": "sha512-+pKvlQHvpxxSTF+tWZ4DjxD0Sz4G26EjAP4z7D2k8VLJ19hrLbSgaQLx/u2mVQn7hiA2s/3DyutOyFwTuDsRgA==", + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/fromentries/-/fromentries-1.3.1.tgz", + "integrity": "sha512-w4t/zm2J+uAcrpeKyW0VmYiIs3aqe/xKQ+2qwazVNZSCklQHhaVjk6XzKw5GtImq5thgL0IVRjGRAOastb08RQ==", "dev": true, "funding": [ { @@ -1730,9 +1740,9 @@ "dev": true }, "node_modules/gensync": { - "version": "1.0.0-beta.1", - "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.1.tgz", - "integrity": "sha512-r8EC6NO1sngH/zdD9fiRDLdcgnbayXah+mLgManTaIZJqEC1MZstmnox8KpnI2/fxQwrp5OpCOYWLp4rBl4Jcg==", + "version": "1.0.0-beta.2", + "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", + "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==", "dev": true, "engines": { "node": ">=6.9.0" @@ -1747,6 +1757,19 @@ "node": "6.* || 8.* || >= 10.*" } }, + "node_modules/get-intrinsic": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.0.1.tgz", + "integrity": "sha512-ZnWP+AmS1VUaLgTRy47+zKtjTxz+0xMpx3I52i+aalBK1QP19ggLF3Db89KJX7kjfOfP2eoa01qc++GwPgufPg==", + "dependencies": { + "function-bind": "^1.1.1", + "has": "^1.0.3", + "has-symbols": "^1.0.1" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/get-package-type": { "version": "0.1.0", "resolved": "https://registry.npmjs.org/get-package-type/-/get-package-type-0.1.0.tgz", @@ -1864,7 +1887,6 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.1.tgz", "integrity": "sha512-PLcsoqu++dmEIZB+6totNFKq/7Do+Z0u4oT0zKOJNl3lYK6vGwwu2hjHs+68OEZbTjiUE9bgOABXbP/GvrS0Kg==", - "dev": true, "engines": { "node": ">= 0.4" }, @@ -1918,9 +1940,9 @@ } }, "node_modules/import-fresh": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.2.1.tgz", - "integrity": "sha512-6e1q1cnWP2RXD9/keSkxHScg508CdXqXWgWBaETNhyuBFz+kUZlKboh+ISK+bU++DmbHimVBrOz/zzPe0sZ3sQ==", + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.2.2.tgz", + "integrity": "sha512-cTPNrlvJT6twpYy+YmKUKrTSjWFs3bjYjAhCwm+z4EOCubZxAuO+hHpRN64TqjEaYSHs7tJAE0w1CKMGmsG/lw==", "dev": true, "dependencies": { "parent-module": "^1.0.0", @@ -1990,7 +2012,6 @@ "version": "1.2.2", "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.2.tgz", "integrity": "sha512-dnMqspv5nU3LoewK2N/y7KLtxtakvTuaCsU9FU50/QDmdbHNy/4/JuRtMHqRU22o3q+W89YQndQEeCVwK+3qrA==", - "dev": true, "engines": { "node": ">= 0.4" }, @@ -2013,7 +2034,6 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.0.2.tgz", "integrity": "sha512-USlDT524woQ08aoZFzh3/Z6ch9Y/EWXEHQ/AaRN0SkKq4t2Jw2R2339tSXmwuVoY7LLlBCbOIlx2myP/L5zk0g==", - "dev": true, "engines": { "node": ">= 0.4" }, @@ -2055,7 +2075,6 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/is-negative-zero/-/is-negative-zero-2.0.0.tgz", "integrity": "sha1-lVOxIbD6wohp2p7UWeIMdUN4hGE=", - "dev": true, "engines": { "node": ">= 0.4" } @@ -2081,7 +2100,6 @@ "version": "1.1.1", "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.1.1.tgz", "integrity": "sha512-1+QkEcxiLlB7VEyFtyBg94e08OAsvq7FUBgApTq/w2ymCLyKJgDPsybBENVtA7XCQEgEXxKPonG+mvYRxh/LIg==", - "dev": true, "dependencies": { "has-symbols": "^1.0.1" }, @@ -2117,7 +2135,6 @@ "version": "1.0.3", "resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.0.3.tgz", "integrity": "sha512-OwijhaRSgqvhm/0ZdAcXNZt9lYdKFpcRDT5ULUuYXPoT794UNOdU+gpT6Rzo7b4V2HUl/op6GqY894AZwv9faQ==", - "dev": true, "dependencies": { "has-symbols": "^1.0.1" }, @@ -2714,9 +2731,9 @@ } }, "node_modules/mocha": { - "version": "8.2.0", - "resolved": "https://registry.npmjs.org/mocha/-/mocha-8.2.0.tgz", - "integrity": "sha512-lEWEMq2LMfNJMKeuEwb5UELi+OgFDollXaytR5ggQcHpzG3NP/R7rvixAvF+9/lLsTWhWG+4yD2M70GsM06nxw==", + "version": "8.2.1", + "resolved": "https://registry.npmjs.org/mocha/-/mocha-8.2.1.tgz", + "integrity": "sha512-cuLBVfyFfFqbNR0uUKbDGXKGk+UDFe6aR4os78XIrMQpZl/nv7JYHcvP5MFIAb374b2zFXsdgEGwmzMtP0Xg8w==", "dev": true, "dependencies": { "@ungap/promise-all-settled": "1.1.2", @@ -3027,7 +3044,6 @@ "version": "1.8.0", "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.8.0.tgz", "integrity": "sha512-jLdtEOB112fORuypAyl/50VRVIBIdVQOSUUGQHzJ4xBSbit81zRarz7GThkEFZy1RceYrWYcPcBFPQwHyAc1gA==", - "dev": true, "funding": { "url": "https://github.com/sponsors/ljharb" } @@ -3036,19 +3052,17 @@ "version": "1.1.1", "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz", "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==", - "dev": true, "engines": { "node": ">= 0.4" } }, "node_modules/object.assign": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.1.tgz", - "integrity": "sha512-VT/cxmx5yaoHSOTSyrCygIDFco+RsibY2NM0a4RdEeY/4KgqezwFtK1yr3U67xYhqJSlASm2pKhLVzPj2lr4bA==", - "dev": true, + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.2.tgz", + "integrity": "sha512-ixT2L5THXsApyiUPYKmW+2EHpXXe5Ii3M+f4e+aJFAHao5amFRW6J0OO6c/LU8Be47utCx2GL89hxGB6XSmKuQ==", "dependencies": { + "call-bind": "^1.0.0", "define-properties": "^1.1.3", - "es-abstract": "^1.18.0-next.0", "has-symbols": "^1.0.1", "object-keys": "^1.1.1" }, @@ -3059,32 +3073,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/object.assign/node_modules/es-abstract": { - "version": "1.18.0-next.1", - "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.18.0-next.1.tgz", - "integrity": "sha512-I4UGspA0wpZXWENrdA0uHbnhte683t3qT/1VFH9aX2dA5PPSf6QW5HHXf5HImaqPmjXaVeVk4RGWnaylmV7uAA==", - "dev": true, - "dependencies": { - "es-to-primitive": "^1.2.1", - "function-bind": "^1.1.1", - "has": "^1.0.3", - "has-symbols": "^1.0.1", - "is-callable": "^1.2.2", - "is-negative-zero": "^2.0.0", - "is-regex": "^1.1.1", - "object-inspect": "^1.8.0", - "object-keys": "^1.1.1", - "object.assign": "^4.1.1", - "string.prototype.trimend": "^1.0.1", - "string.prototype.trimstart": "^1.0.1" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, "node_modules/object.values": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/object.values/-/object.values-1.1.1.tgz", @@ -3950,11 +3938,25 @@ "node": ">=6" } }, + "node_modules/string.prototype.replaceall": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/string.prototype.replaceall/-/string.prototype.replaceall-1.0.3.tgz", + "integrity": "sha512-GF8JS9jtHSDkIsVMsYBPR4dItwaU6xOSPsMcRGTAbBr12ZDfyKMtgxdC2HDFbsMogGel29pmwxioJoXeu9ztIg==", + "dependencies": { + "define-properties": "^1.1.3", + "es-abstract": "^1.17.0-next.1", + "function-bind": "^1.1.1", + "has-symbols": "^1.0.1", + "is-regex": "^1.0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/string.prototype.trimend": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/string.prototype.trimend/-/string.prototype.trimend-1.0.2.tgz", "integrity": "sha512-8oAG/hi14Z4nOVP0z6mdiVZ/wqjDtWSLygMigTzAb+7aPEDTleeFf+WrF+alzecxIRkckkJVn+dTlwzJXORATw==", - "dev": true, "dependencies": { "define-properties": "^1.1.3", "es-abstract": "^1.18.0-next.1" @@ -3967,7 +3969,6 @@ "version": "1.18.0-next.1", "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.18.0-next.1.tgz", "integrity": "sha512-I4UGspA0wpZXWENrdA0uHbnhte683t3qT/1VFH9aX2dA5PPSf6QW5HHXf5HImaqPmjXaVeVk4RGWnaylmV7uAA==", - "dev": true, "dependencies": { "es-to-primitive": "^1.2.1", "function-bind": "^1.1.1", @@ -3993,7 +3994,6 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/string.prototype.trimstart/-/string.prototype.trimstart-1.0.2.tgz", "integrity": "sha512-7F6CdBTl5zyu30BJFdzSTlSlLPwODC23Od+iLoVH8X6+3fvDPPuBVVj9iaB1GOsSTSIgVfsfm27R2FGrAPznWg==", - "dev": true, "dependencies": { "define-properties": "^1.1.3", "es-abstract": "^1.18.0-next.1" @@ -4006,7 +4006,6 @@ "version": "1.18.0-next.1", "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.18.0-next.1.tgz", "integrity": "sha512-I4UGspA0wpZXWENrdA0uHbnhte683t3qT/1VFH9aX2dA5PPSf6QW5HHXf5HImaqPmjXaVeVk4RGWnaylmV7uAA==", - "dev": true, "dependencies": { "es-to-primitive": "^1.2.1", "function-bind": "^1.1.1", @@ -4265,9 +4264,9 @@ } }, "node_modules/v8-compile-cache": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/v8-compile-cache/-/v8-compile-cache-2.1.1.tgz", - "integrity": "sha512-8OQ9CL+VWyt3JStj7HX7/ciTL2V3Rl1Wf5OL+SNTm0yK1KvtReVulksyeRnCANHHuUxHlQig+JJDlUhBt1NQDQ==", + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/v8-compile-cache/-/v8-compile-cache-2.2.0.tgz", + "integrity": "sha512-gTpR5XQNKFwOd4clxfnhaqvfqMpqEwr4tOtCyz4MtYZX2JYhfr1JvBFKdS+7K/9rfpZR3VLX+YWBbKoxCgS43Q==", "dev": true }, "node_modules/validate-npm-package-license": { @@ -4509,9 +4508,9 @@ } }, "node_modules/yargs-unparser/node_modules/camelcase": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.1.0.tgz", - "integrity": "sha512-WCMml9ivU60+8rEJgELlFp1gxFcEGxwYleE3bziHEDeqsqAWGHdimB7beBFGjLzVNgPGyDsfgXLQEYMpmIFnVQ==", + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.2.0.tgz", + "integrity": "sha512-c7wVvbw3f37nuobQNtgsgG9POC9qMbNuMQmTCqZv23b6MIz0fcYpBiOlv9gEN/hdLdnZTDQhg6e9Dq5M1vKvfg==", "dev": true, "engines": { "node": ">=10" @@ -4655,12 +4654,12 @@ } }, "@babel/generator": { - "version": "7.12.1", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.12.1.tgz", - "integrity": "sha512-DB+6rafIdc9o72Yc3/Ph5h+6hUjeOp66pF0naQBgUFFuPqzQwIlPTm3xZR7YNvduIMtkDIj2t21LSQwnbCrXvg==", + "version": "7.12.5", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.12.5.tgz", + "integrity": "sha512-m16TQQJ8hPt7E+OS/XVQg/7U184MLXtvuGbCdA7na61vha+ImkyyNM/9DDA0unYCVZn3ZOhng+qz48/KBOT96A==", "dev": true, "requires": { - "@babel/types": "^7.12.1", + "@babel/types": "^7.12.5", "jsesc": "^2.5.1", "source-map": "^0.5.0" } @@ -4695,12 +4694,12 @@ } }, "@babel/helper-module-imports": { - "version": "7.12.1", - "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.12.1.tgz", - "integrity": "sha512-ZeC1TlMSvikvJNy1v/wPIazCu3NdOwgYZLIkmIyAsGhqkNpiDoQQRmaCK8YP4Pq3GPTLPV9WXaPCJKvx06JxKA==", + "version": "7.12.5", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.12.5.tgz", + "integrity": "sha512-SR713Ogqg6++uexFRORf/+nPXMmWIn80TALu0uaFb+iQIUoR7bOC7zBWyzBs5b3tBBJXuyD0cRu1F15GyzjOWA==", "dev": true, "requires": { - "@babel/types": "^7.12.1" + "@babel/types": "^7.12.5" } }, "@babel/helper-module-transforms": { @@ -4730,15 +4729,15 @@ } }, "@babel/helper-replace-supers": { - "version": "7.12.1", - "resolved": "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.12.1.tgz", - "integrity": "sha512-zJjTvtNJnCFsCXVi5rUInstLd/EIVNmIKA1Q9ynESmMBWPWd+7sdR+G4/wdu+Mppfep0XLyG2m7EBPvjCeFyrw==", + "version": "7.12.5", + "resolved": "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.12.5.tgz", + "integrity": "sha512-5YILoed0ZyIpF4gKcpZitEnXEJ9UoDRki1Ey6xz46rxOzfNMAhVIJMoune1hmPVxh40LRv1+oafz7UsWX+vyWA==", "dev": true, "requires": { "@babel/helper-member-expression-to-functions": "^7.12.1", "@babel/helper-optimise-call-expression": "^7.10.4", - "@babel/traverse": "^7.12.1", - "@babel/types": "^7.12.1" + "@babel/traverse": "^7.12.5", + "@babel/types": "^7.12.5" } }, "@babel/helper-simple-access": { @@ -4765,14 +4764,14 @@ "integrity": "sha512-3U9y+43hz7ZM+rzG24Qe2mufW5KhvFg/NhnNph+i9mgCtdTCtMJuI1TMkrIUiK7Ix4PYlRF9I5dhqaLYA/ADXw==" }, "@babel/helpers": { - "version": "7.12.1", - "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.12.1.tgz", - "integrity": "sha512-9JoDSBGoWtmbay98efmT2+mySkwjzeFeAL9BuWNoVQpkPFQF8SIIFUfY5os9u8wVzglzoiPRSW7cuJmBDUt43g==", + "version": "7.12.5", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.12.5.tgz", + "integrity": "sha512-lgKGMQlKqA8meJqKsW6rUnc4MdUk35Ln0ATDqdM1a/UpARODdI4j5Y5lVfUScnSNkJcdCRAaWkspykNoFg9sJA==", "dev": true, "requires": { "@babel/template": "^7.10.4", - "@babel/traverse": "^7.12.1", - "@babel/types": "^7.12.1" + "@babel/traverse": "^7.12.5", + "@babel/types": "^7.12.5" } }, "@babel/highlight": { @@ -4837,9 +4836,9 @@ } }, "@babel/parser": { - "version": "7.12.3", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.12.3.tgz", - "integrity": "sha512-kFsOS0IbsuhO5ojF8Hc8z/8vEIOkylVBrjiZUbLTE3XFe0Qi+uu6HjzQixkFaqr0ZPAMZcBVxEwmsnsLPZ2Xsw==", + "version": "7.12.5", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.12.5.tgz", + "integrity": "sha512-FVM6RZQ0mn2KCf1VUED7KepYeUWoVShczewOCfm3nzoBybaih51h+sYVVGthW9M6lPByEPTQf+xm27PBdlpwmQ==", "dev": true }, "@babel/template": { @@ -4854,17 +4853,17 @@ } }, "@babel/traverse": { - "version": "7.12.1", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.12.1.tgz", - "integrity": "sha512-MA3WPoRt1ZHo2ZmoGKNqi20YnPt0B1S0GTZEPhhd+hw2KGUzBlHuVunj6K4sNuK+reEvyiPwtp0cpaqLzJDmAw==", + "version": "7.12.5", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.12.5.tgz", + "integrity": "sha512-xa15FbQnias7z9a62LwYAA5SZZPkHIXpd42C6uW68o8uTuua96FHZy1y61Va5P/i83FAAcMpW8+A/QayntzuqA==", "dev": true, "requires": { "@babel/code-frame": "^7.10.4", - "@babel/generator": "^7.12.1", + "@babel/generator": "^7.12.5", "@babel/helper-function-name": "^7.10.4", "@babel/helper-split-export-declaration": "^7.11.0", - "@babel/parser": "^7.12.1", - "@babel/types": "^7.12.1", + "@babel/parser": "^7.12.5", + "@babel/types": "^7.12.5", "debug": "^4.1.0", "globals": "^11.1.0", "lodash": "^4.17.19" @@ -4879,9 +4878,9 @@ } }, "@babel/types": { - "version": "7.12.1", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.12.1.tgz", - "integrity": "sha512-BzSY3NJBKM4kyatSOWh3D/JJ2O3CVzBybHWxtgxnggaxEuaSTTDqeiSb/xk9lrkw2Tbqyivw5ZU4rT+EfznQsA==", + "version": "7.12.5", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.12.5.tgz", + "integrity": "sha512-gyTcvz7JFa4V45C0Zklv//GmFOAal5fL23OWpBLqc4nZ4Yrz67s4kCNwSK1Gu0MXGTU8mRY3zJYtacLdKXlzig==", "dev": true, "requires": { "@babel/helper-validator-identifier": "^7.10.4", @@ -5162,6 +5161,15 @@ "write-file-atomic": "^3.0.0" } }, + "call-bind": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.0.tgz", + "integrity": "sha512-AEXsYIyyDY3MCzbwdhzG3Jx1R0J2wetQyUynn6dYHAO+bg8l1k7jwZtRv4ryryFs7EP+NDlikJlVe59jr0cM2w==", + "requires": { + "function-bind": "^1.1.1", + "get-intrinsic": "^1.0.0" + } + }, "callsites": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", @@ -5367,7 +5375,6 @@ "version": "1.1.3", "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.1.3.tgz", "integrity": "sha512-3MqfYKj2lLzdMSf8ZIZE/V+Zuy+BgD6f164e8K2w7dgnpKArBDerGYpM46IYYcjnkdPNMjPk9A6VFB8+3SKlXQ==", - "dev": true, "requires": { "object-keys": "^1.0.12" } @@ -5414,7 +5421,6 @@ "version": "1.17.7", "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.17.7.tgz", "integrity": "sha512-VBl/gnfcJ7OercKA9MVaegWsBHFjV492syMudcnQZvt/Dw8ezpcOHYZXa/J96O8vx+g4x65YKhxOwDUh63aS5g==", - "dev": true, "requires": { "es-to-primitive": "^1.2.1", "function-bind": "^1.1.1", @@ -5433,7 +5439,6 @@ "version": "1.2.1", "resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.2.1.tgz", "integrity": "sha512-QCOllgZJtaUo9miYBcLChTUaHNjJF3PYs1VidD7AwiEj1kYxKeQTctLAezAOH5ZKRH0g2IgPn6KwB4IT8iRpvA==", - "dev": true, "requires": { "is-callable": "^1.1.4", "is-date-object": "^1.0.1", @@ -5912,9 +5917,9 @@ } }, "fromentries": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/fromentries/-/fromentries-1.3.0.tgz", - "integrity": "sha512-+pKvlQHvpxxSTF+tWZ4DjxD0Sz4G26EjAP4z7D2k8VLJ19hrLbSgaQLx/u2mVQn7hiA2s/3DyutOyFwTuDsRgA==", + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/fromentries/-/fromentries-1.3.1.tgz", + "integrity": "sha512-w4t/zm2J+uAcrpeKyW0VmYiIs3aqe/xKQ+2qwazVNZSCklQHhaVjk6XzKw5GtImq5thgL0IVRjGRAOastb08RQ==", "dev": true }, "fs.realpath": { @@ -5941,9 +5946,9 @@ "dev": true }, "gensync": { - "version": "1.0.0-beta.1", - "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.1.tgz", - "integrity": "sha512-r8EC6NO1sngH/zdD9fiRDLdcgnbayXah+mLgManTaIZJqEC1MZstmnox8KpnI2/fxQwrp5OpCOYWLp4rBl4Jcg==", + "version": "1.0.0-beta.2", + "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", + "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==", "dev": true }, "get-caller-file": { @@ -5952,6 +5957,16 @@ "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", "dev": true }, + "get-intrinsic": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.0.1.tgz", + "integrity": "sha512-ZnWP+AmS1VUaLgTRy47+zKtjTxz+0xMpx3I52i+aalBK1QP19ggLF3Db89KJX7kjfOfP2eoa01qc++GwPgufPg==", + "requires": { + "function-bind": "^1.1.1", + "has": "^1.0.3", + "has-symbols": "^1.0.1" + } + }, "get-package-type": { "version": "0.1.0", "resolved": "https://registry.npmjs.org/get-package-type/-/get-package-type-0.1.0.tgz", @@ -6035,8 +6050,7 @@ "has-symbols": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.1.tgz", - "integrity": "sha512-PLcsoqu++dmEIZB+6totNFKq/7Do+Z0u4oT0zKOJNl3lYK6vGwwu2hjHs+68OEZbTjiUE9bgOABXbP/GvrS0Kg==", - "dev": true + "integrity": "sha512-PLcsoqu++dmEIZB+6totNFKq/7Do+Z0u4oT0zKOJNl3lYK6vGwwu2hjHs+68OEZbTjiUE9bgOABXbP/GvrS0Kg==" }, "hasha": { "version": "5.2.2", @@ -6072,9 +6086,9 @@ "dev": true }, "import-fresh": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.2.1.tgz", - "integrity": "sha512-6e1q1cnWP2RXD9/keSkxHScg508CdXqXWgWBaETNhyuBFz+kUZlKboh+ISK+bU++DmbHimVBrOz/zzPe0sZ3sQ==", + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.2.2.tgz", + "integrity": "sha512-cTPNrlvJT6twpYy+YmKUKrTSjWFs3bjYjAhCwm+z4EOCubZxAuO+hHpRN64TqjEaYSHs7tJAE0w1CKMGmsG/lw==", "dev": true, "requires": { "parent-module": "^1.0.0", @@ -6128,8 +6142,7 @@ "is-callable": { "version": "1.2.2", "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.2.tgz", - "integrity": "sha512-dnMqspv5nU3LoewK2N/y7KLtxtakvTuaCsU9FU50/QDmdbHNy/4/JuRtMHqRU22o3q+W89YQndQEeCVwK+3qrA==", - "dev": true + "integrity": "sha512-dnMqspv5nU3LoewK2N/y7KLtxtakvTuaCsU9FU50/QDmdbHNy/4/JuRtMHqRU22o3q+W89YQndQEeCVwK+3qrA==" }, "is-core-module": { "version": "2.0.0", @@ -6142,8 +6155,7 @@ "is-date-object": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.0.2.tgz", - "integrity": "sha512-USlDT524woQ08aoZFzh3/Z6ch9Y/EWXEHQ/AaRN0SkKq4t2Jw2R2339tSXmwuVoY7LLlBCbOIlx2myP/L5zk0g==", - "dev": true + "integrity": "sha512-USlDT524woQ08aoZFzh3/Z6ch9Y/EWXEHQ/AaRN0SkKq4t2Jw2R2339tSXmwuVoY7LLlBCbOIlx2myP/L5zk0g==" }, "is-extglob": { "version": "2.1.1", @@ -6169,8 +6181,7 @@ "is-negative-zero": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/is-negative-zero/-/is-negative-zero-2.0.0.tgz", - "integrity": "sha1-lVOxIbD6wohp2p7UWeIMdUN4hGE=", - "dev": true + "integrity": "sha1-lVOxIbD6wohp2p7UWeIMdUN4hGE=" }, "is-number": { "version": "7.0.0", @@ -6187,7 +6198,6 @@ "version": "1.1.1", "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.1.1.tgz", "integrity": "sha512-1+QkEcxiLlB7VEyFtyBg94e08OAsvq7FUBgApTq/w2ymCLyKJgDPsybBENVtA7XCQEgEXxKPonG+mvYRxh/LIg==", - "dev": true, "requires": { "has-symbols": "^1.0.1" } @@ -6208,7 +6218,6 @@ "version": "1.0.3", "resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.0.3.tgz", "integrity": "sha512-OwijhaRSgqvhm/0ZdAcXNZt9lYdKFpcRDT5ULUuYXPoT794UNOdU+gpT6Rzo7b4V2HUl/op6GqY894AZwv9faQ==", - "dev": true, "requires": { "has-symbols": "^1.0.1" } @@ -6659,9 +6668,9 @@ } }, "mocha": { - "version": "8.2.0", - "resolved": "https://registry.npmjs.org/mocha/-/mocha-8.2.0.tgz", - "integrity": "sha512-lEWEMq2LMfNJMKeuEwb5UELi+OgFDollXaytR5ggQcHpzG3NP/R7rvixAvF+9/lLsTWhWG+4yD2M70GsM06nxw==", + "version": "8.2.1", + "resolved": "https://registry.npmjs.org/mocha/-/mocha-8.2.1.tgz", + "integrity": "sha512-cuLBVfyFfFqbNR0uUKbDGXKGk+UDFe6aR4os78XIrMQpZl/nv7JYHcvP5MFIAb374b2zFXsdgEGwmzMtP0Xg8w==", "dev": true, "requires": { "@ungap/promise-all-settled": "1.1.2", @@ -6909,47 +6918,22 @@ "object-inspect": { "version": "1.8.0", "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.8.0.tgz", - "integrity": "sha512-jLdtEOB112fORuypAyl/50VRVIBIdVQOSUUGQHzJ4xBSbit81zRarz7GThkEFZy1RceYrWYcPcBFPQwHyAc1gA==", - "dev": true + "integrity": "sha512-jLdtEOB112fORuypAyl/50VRVIBIdVQOSUUGQHzJ4xBSbit81zRarz7GThkEFZy1RceYrWYcPcBFPQwHyAc1gA==" }, "object-keys": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz", - "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==", - "dev": true + "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==" }, "object.assign": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.1.tgz", - "integrity": "sha512-VT/cxmx5yaoHSOTSyrCygIDFco+RsibY2NM0a4RdEeY/4KgqezwFtK1yr3U67xYhqJSlASm2pKhLVzPj2lr4bA==", - "dev": true, + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.2.tgz", + "integrity": "sha512-ixT2L5THXsApyiUPYKmW+2EHpXXe5Ii3M+f4e+aJFAHao5amFRW6J0OO6c/LU8Be47utCx2GL89hxGB6XSmKuQ==", "requires": { + "call-bind": "^1.0.0", "define-properties": "^1.1.3", - "es-abstract": "^1.18.0-next.0", "has-symbols": "^1.0.1", "object-keys": "^1.1.1" - }, - "dependencies": { - "es-abstract": { - "version": "1.18.0-next.1", - "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.18.0-next.1.tgz", - "integrity": "sha512-I4UGspA0wpZXWENrdA0uHbnhte683t3qT/1VFH9aX2dA5PPSf6QW5HHXf5HImaqPmjXaVeVk4RGWnaylmV7uAA==", - "dev": true, - "requires": { - "es-to-primitive": "^1.2.1", - "function-bind": "^1.1.1", - "has": "^1.0.3", - "has-symbols": "^1.0.1", - "is-callable": "^1.2.2", - "is-negative-zero": "^2.0.0", - "is-regex": "^1.1.1", - "object-inspect": "^1.8.0", - "object-keys": "^1.1.1", - "object.assign": "^4.1.1", - "string.prototype.trimend": "^1.0.1", - "string.prototype.trimstart": "^1.0.1" - } - } } }, "object.values": { @@ -7618,11 +7602,22 @@ } } }, + "string.prototype.replaceall": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/string.prototype.replaceall/-/string.prototype.replaceall-1.0.3.tgz", + "integrity": "sha512-GF8JS9jtHSDkIsVMsYBPR4dItwaU6xOSPsMcRGTAbBr12ZDfyKMtgxdC2HDFbsMogGel29pmwxioJoXeu9ztIg==", + "requires": { + "define-properties": "^1.1.3", + "es-abstract": "^1.17.0-next.1", + "function-bind": "^1.1.1", + "has-symbols": "^1.0.1", + "is-regex": "^1.0.4" + } + }, "string.prototype.trimend": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/string.prototype.trimend/-/string.prototype.trimend-1.0.2.tgz", "integrity": "sha512-8oAG/hi14Z4nOVP0z6mdiVZ/wqjDtWSLygMigTzAb+7aPEDTleeFf+WrF+alzecxIRkckkJVn+dTlwzJXORATw==", - "dev": true, "requires": { "define-properties": "^1.1.3", "es-abstract": "^1.18.0-next.1" @@ -7632,7 +7627,6 @@ "version": "1.18.0-next.1", "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.18.0-next.1.tgz", "integrity": "sha512-I4UGspA0wpZXWENrdA0uHbnhte683t3qT/1VFH9aX2dA5PPSf6QW5HHXf5HImaqPmjXaVeVk4RGWnaylmV7uAA==", - "dev": true, "requires": { "es-to-primitive": "^1.2.1", "function-bind": "^1.1.1", @@ -7654,7 +7648,6 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/string.prototype.trimstart/-/string.prototype.trimstart-1.0.2.tgz", "integrity": "sha512-7F6CdBTl5zyu30BJFdzSTlSlLPwODC23Od+iLoVH8X6+3fvDPPuBVVj9iaB1GOsSTSIgVfsfm27R2FGrAPznWg==", - "dev": true, "requires": { "define-properties": "^1.1.3", "es-abstract": "^1.18.0-next.1" @@ -7664,7 +7657,6 @@ "version": "1.18.0-next.1", "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.18.0-next.1.tgz", "integrity": "sha512-I4UGspA0wpZXWENrdA0uHbnhte683t3qT/1VFH9aX2dA5PPSf6QW5HHXf5HImaqPmjXaVeVk4RGWnaylmV7uAA==", - "dev": true, "requires": { "es-to-primitive": "^1.2.1", "function-bind": "^1.1.1", @@ -7867,9 +7859,9 @@ "dev": true }, "v8-compile-cache": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/v8-compile-cache/-/v8-compile-cache-2.1.1.tgz", - "integrity": "sha512-8OQ9CL+VWyt3JStj7HX7/ciTL2V3Rl1Wf5OL+SNTm0yK1KvtReVulksyeRnCANHHuUxHlQig+JJDlUhBt1NQDQ==", + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/v8-compile-cache/-/v8-compile-cache-2.2.0.tgz", + "integrity": "sha512-gTpR5XQNKFwOd4clxfnhaqvfqMpqEwr4tOtCyz4MtYZX2JYhfr1JvBFKdS+7K/9rfpZR3VLX+YWBbKoxCgS43Q==", "dev": true }, "validate-npm-package-license": { @@ -8128,9 +8120,9 @@ }, "dependencies": { "camelcase": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.1.0.tgz", - "integrity": "sha512-WCMml9ivU60+8rEJgELlFp1gxFcEGxwYleE3bziHEDeqsqAWGHdimB7beBFGjLzVNgPGyDsfgXLQEYMpmIFnVQ==", + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.2.0.tgz", + "integrity": "sha512-c7wVvbw3f37nuobQNtgsgG9POC9qMbNuMQmTCqZv23b6MIz0fcYpBiOlv9gEN/hdLdnZTDQhg6e9Dq5M1vKvfg==", "dev": true }, "decamelize": { diff --git a/package.json b/package.json index 343ec35..6424b4c 100644 --- a/package.json +++ b/package.json @@ -41,7 +41,8 @@ "gofer": "^5.0.4", "minimist": "^1.2.5", "rc": "^1.2.8", - "semver": "^7.3.2" + "semver": "^7.3.2", + "string.prototype.replaceall": "^1.0.3" }, "devDependencies": { "eslint": "^7.12.1", diff --git a/test/steps/changelog.test.js b/test/steps/changelog.test.js index b60b0fc..e25e566 100644 --- a/test/steps/changelog.test.js +++ b/test/steps/changelog.test.js @@ -655,18 +655,22 @@ describe('generateChangeLog', () => { } }); - it('identifies dependency updates in commit subject and groups them into "Dependencies" category', async () => { + it('identifies & highlights dependency updates in commit subject and groups them into "Dependencies" category', async () => { + function removeBackticks(str) { + return str.replace(/`/g, ''); + } + const subjectCases = [ - 'update @grpn/create@23.0', - 'update @grpn/create@23.0.0', - 'update @grpn/create@23.0.x update `@grpn/create@23.0.x`', - 'update @grpn/cr.eate@23.x', - 'update create@23.x', - 'update cre.ate@23', - 'update cre.ate@v23', - 'update cre.ate v23', - 'update cre.ate 23', - 'update `cre.ate 23`', + '@grpn/create@23.0', + '@grpn/create@23.0.0', + ['@grpn/create@23.0.x', '`create@23.0.x`', '`create@23x`'], // multiple packages in subject + '@grpn/cr.eate@23.x', + 'create@23.x', + 'cre.ate@23', + 'cre.ate@v23', + 'cre.ate v23', + 'cre.ate 23', + '`cre.ate 23`', ]; for (const subject of subjectCases) { @@ -674,7 +678,7 @@ describe('generateChangeLog', () => { { sha: '1234567890123456789012345678901234567890', type: 'fix', - subject, + subject: Array.isArray(subject) ? subject.join(' ') : subject, }, ]; const options = { @@ -682,7 +686,13 @@ describe('generateChangeLog', () => { commits, }; const href = `https://github.com/usr/proj/commit/${commits[0].sha}`; - const expectedEntries = [`* [\`1234567\`](${href}) fix: ${subject}`]; + const expectedEntries = [ + `* [\`1234567\`](${href}) fix: \`${ + Array.isArray(subject) + ? subject.map(removeBackticks).join('` `') + : removeBackticks(subject) + }\``, + ]; const changelog = await generateChangeLog( null, From 21b17e32ff510b11ba6b9b397ea9abf75929105a Mon Sep 17 00:00:00 2001 From: Andreas Richter Date: Wed, 4 Nov 2020 09:58:37 +0100 Subject: [PATCH 7/9] refactor(emoji): change default emoji map & update documentation --- README.md | 16 ++++--- lib/steps/changelog.js | 26 +++--------- test/steps/changelog.test.js | 82 ++++++++++++++++++++++-------------- 3 files changed, 66 insertions(+), 58 deletions(-) diff --git a/README.md b/README.md index 8a3bdd6..2e556dc 100644 --- a/README.md +++ b/README.md @@ -179,18 +179,20 @@ Example for The default emojis for the commit types are: ```json5 { - "feat": "✨", + + "breaking": "đŸ’Ĩ", + "feat": "🚀", "fix": "🐛", "perf": "⚡", "refactor": "đŸ“Ļī¸", - "chore": "â™ģī¸", - "build": "👷", "revert": "↩ī¸", "docs": "📝", - "style": "🎨", - "test": "✅", - "ci": "💚", - "breaking": "đŸ’Ĩ" // this emoji will be set before the "Breaking Change" section + "style": "💅", + + // internal types + "deps": "đŸ”ŧ", // will be set when dependencies are found in PR commit subject + "internal": "🏡", // will be set for types: "chore", "build", "test", "ci" or commits without type + } ``` diff --git a/lib/steps/changelog.js b/lib/steps/changelog.js index b9df96d..a3be9ac 100644 --- a/lib/steps/changelog.js +++ b/lib/steps/changelog.js @@ -78,31 +78,17 @@ const emojiMaps = new Map([ [ 'default', { - breaking: 'đŸ’Ĩ', - feat: '✨', - fix: '🐛', - perf: '⚡', - refactor: 'đŸ“Ļī¸', - chore: 'â™ģī¸', - build: '👷', - revert: '↩ī¸', - docs: '📝', - style: '🎨', - test: '✅', - ci: '💚', - }, - ], - [ - 'babel', - { + // angular types: breaking: 'đŸ’Ĩ', feat: '🚀', - fix: '🐛', perf: '⚡', refactor: 'đŸ“Ļī¸', + fix: '🐛', revert: '↩ī¸', - docs: '📝', style: '💅', + docs: '📝', + + // internal types: deps: 'đŸ”ŧ', internal: '🏡', }, @@ -162,7 +148,7 @@ function getTypeCategory(type, options) { descr = 'Internal'; } - const emojiSet = { ...emojiMaps.get('babel'), ...(emojiOpts.set || {}) }; + const emojiSet = { ...emojiMaps.get('default'), ...(emojiOpts.set || {}) }; const emoji = !emojiOpts.skip ? emojiSet[type] || emojiSet['internal'] : ''; return (emoji ? [headlineLevel, emoji, descr] : [headlineLevel, descr]).join( diff --git a/test/steps/changelog.test.js b/test/steps/changelog.test.js index e25e566..0336f6c 100644 --- a/test/steps/changelog.test.js +++ b/test/steps/changelog.test.js @@ -629,42 +629,16 @@ describe('generateChangeLog', () => { } }); - it('adds category emojis to each category', async () => { - const emojiMaps = generateChangeLog.emojiMaps.get('babel'); - for (const { headline, prefixes } of testCases) { - for (const prefix of prefixes) { - const options = { - commits: [{ ...defaultCommit, type: prefix }], - }; - - const expectedEntries = [ - `* [\`1234567\`](${href0}) ${prefix}: Stop doing the wrong thing`, - ]; - - const changelog = await generateChangeLog(null, pkg, options); - const lines = changelog.split('\n'); - - const indices = assertEntries(changelog, expectedEntries); - assert.strictEqual( - lines[indices[0] - 2], - `${headlineLevel} ${ - emojiMaps[prefix] || emojiMaps['internal'] - } ${headline}` - ); - } - } - }); - it('identifies & highlights dependency updates in commit subject and groups them into "Dependencies" category', async () => { function removeBackticks(str) { return str.replace(/`/g, ''); } const subjectCases = [ - '@grpn/create@23.0', - '@grpn/create@23.0.0', - ['@grpn/create@23.0.x', '`create@23.0.x`', '`create@23x`'], // multiple packages in subject - '@grpn/cr.eate@23.x', + '@scope/create@23.0', + '@scope/create@23.0.0', + ['@scope/create@23.0.x', '`create@23.0.x`', '`create@23x`'], // multiple packages in subject + '@scope/cr.eate@23.x', 'create@23.x', 'cre.ate@23', 'cre.ate@v23', @@ -713,7 +687,7 @@ describe('generateChangeLog', () => { repository: 'http://127.0.0.1:3000/usr/proj', }; const defaultCommit = { - sha: '2234567890123456789012345678901234567890', + sha: '1234567890123456789012345678901234567890', subject: 'something', }; const cases = [ @@ -739,6 +713,52 @@ describe('generateChangeLog', () => { }, }; + it('adds category emojis to each category', async () => { + const testCases = [ + { headline: 'New Features', prefixes: ['feat'] }, + { headline: 'Code Refactoring', prefixes: ['refactor'] }, + { headline: 'Bug Fixes', prefixes: ['fix'] }, + { headline: 'Performance Improvements', prefixes: ['perf'] }, + { headline: 'Dependencies', prefixes: ['dep'] }, + { headline: 'Documentation', prefixes: ['docs'] }, + { headline: 'Polish', prefixes: ['style'] }, + { headline: 'Reverts', prefixes: ['revert'] }, + { + headline: 'Internal', + prefixes: ['ci', 'test', 'build', 'chore'], + }, + ]; + + const headlineLevel = '####'; + const href0 = `https://github.com/usr/proj/commit/${defaultCommit.sha}`; + + const emojiMaps = generateChangeLog.emojiMaps.get('default'); + for (const { headline, prefixes } of testCases) { + for (const prefix of prefixes) { + const expectedEntries = [ + `* [\`1234567\`](${href0}) ${prefix}: something`, + ]; + + changelog = await generateChangeLog( + null, + { repository: 'usr/proj' }, + { + commits: [{ ...defaultCommit, type: prefix }], + } + ); + const lines = changelog.split('\n'); + + const indices = assertEntries(changelog, expectedEntries); + assert.strictEqual( + lines[indices[0] - 2], + `${headlineLevel} ${ + emojiMaps[prefix] || emojiMaps['internal'] + } ${headline}` + ); + } + } + }); + it('disables emojis with emoji.skip config', async () => { changelog = await generateChangeLog(null, pkg, { ...options, From d1e7889cc8775cc68da3af7ca2c31b1472b69fe0 Mon Sep 17 00:00:00 2001 From: Andreas Richter Date: Wed, 4 Nov 2020 13:43:53 +0100 Subject: [PATCH 8/9] revert: unnecessary changes --- lib/commands/changelog.js | 18 +++++++++++------- lib/commands/release.js | 8 +++++--- lib/git/commits.js | 15 ++++----------- lib/steps/pending-changes.js | 8 ++++---- test/git/commits.test.js | 1 - 5 files changed, 24 insertions(+), 26 deletions(-) diff --git a/lib/commands/changelog.js b/lib/commands/changelog.js index 4cfde29..deceac2 100644 --- a/lib/commands/changelog.js +++ b/lib/commands/changelog.js @@ -38,18 +38,22 @@ const getPendingChanges = require('../steps/pending-changes'); const generateChangeLog = require('../steps/changelog'); -function ensureVersionTag(cwd, pkg) { - return ensureTag(cwd, `v${pkg.version}`); -} - async function showChangelog(cwd, pkg, options) { + function ensureVersionTag() { + return ensureTag(cwd, `v${pkg.version}`); + } + + function runTask(task) { + return task(cwd, pkg, options); + } + function printChangelog() { process.stdout.write(`${options.changelog}\n`); } - for (const task of [ensureVersionTag, getPendingChanges, generateChangeLog]) { - await task(cwd, pkg, options); - } + const tasks = [ensureVersionTag, getPendingChanges, generateChangeLog]; + + for (const task of tasks) await runTask(task); return printChangelog(); } diff --git a/lib/commands/release.js b/lib/commands/release.js index 72dc37a..19e17a7 100644 --- a/lib/commands/release.js +++ b/lib/commands/release.js @@ -84,6 +84,10 @@ function release(cwd, pkg, options) { publishToNpm, ]; + function runTask(task) { + return task(cwd, pkg, options); + } + async function runPublishTasks() { if (options.pr) { debug('Never publishing from a PR'); @@ -105,9 +109,7 @@ function release(cwd, pkg, options) { publishTasks = [publishToNpm]; } - for (const task of publishTasks) { - await task(cwd, pkg, options); - } + for (const task of publishTasks) await runTask(task); return null; } diff --git a/lib/git/commits.js b/lib/git/commits.js index 94af8d2..1ea9e86 100644 --- a/lib/git/commits.js +++ b/lib/git/commits.js @@ -86,11 +86,6 @@ function parseCommit(commit) { return { ...data, sha: sha || data.sha, parentSha }; } -/** - * - * @param {{type: string, header: string}} commit - * @return {*|boolean} - */ function isNotRandomMerge(commit) { // DotCI adds these :( return commit.type || `${commit.header}`.indexOf('Merge ') !== 0; @@ -116,10 +111,6 @@ function gracefulEmptyState(error) { throw error; } -/** - * @param {string} fromRevision - * @return {string|*[]} - */ function createRange(fromRevision) { if (fromRevision && fromRevision !== 'v0.0.0') { return `${fromRevision}..HEAD`; @@ -128,13 +119,15 @@ function createRange(fromRevision) { return []; } -function getCommits(cwd, fromRevision) { +function getCommits(directory, fromRevision) { return run( 'git', ['log', '--reverse', '--topo-order', GIT_LOG_FORMAT].concat( createRange(fromRevision) ), - { cwd } + { + cwd: directory, + } ).then(parseLogOutput, gracefulEmptyState); } diff --git a/lib/steps/pending-changes.js b/lib/steps/pending-changes.js index f646f86..81f5f33 100644 --- a/lib/steps/pending-changes.js +++ b/lib/steps/pending-changes.js @@ -86,11 +86,11 @@ function normalizeReferences(meta, commit) { return commit; } -async function getPendingChanges(cwd, pkg, options) { +function getPendingChanges(cwd, pkg, options) { const meta = parseRepository(pkg.repository); - const commits = await getCommits(cwd, `v${pkg.version}`); - - options.commits = commits.map(normalizeReferences.bind(null, meta)); + return getCommits(cwd, `v${pkg.version}`).then(commits => { + options.commits = commits.map(normalizeReferences.bind(null, meta)); + }); } module.exports = getPendingChanges; diff --git a/test/git/commits.test.js b/test/git/commits.test.js index cdec5bd..6232818 100644 --- a/test/git/commits.test.js +++ b/test/git/commits.test.js @@ -151,7 +151,6 @@ describe('getCommits', () => { assertIssue('Jira', expected); }); }); - describe('with multiple commits', () => { const dirname = withFixture('multiple-commits'); let allCommits = null; From d188cbdf766a8ae23d316fe32e4bbbde5912a6f1 Mon Sep 17 00:00:00 2001 From: Andreas Richter Date: Thu, 5 Nov 2020 12:24:36 +0100 Subject: [PATCH 9/9] fix(verify): remove () from message --- lib/commands/verify.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/commands/verify.js b/lib/commands/verify.js index 48a1d09..ece915e 100644 --- a/lib/commands/verify.js +++ b/lib/commands/verify.js @@ -107,9 +107,9 @@ function verify(cwd, pkg, options) { const { releaseType } = options; const nextUpdate = releaseType !== 'none' - ? `${pkg.version} -> ${semver.inc(pkg.version, releaseType)}` + ? `(${pkg.version} -> ${semver.inc(pkg.version, releaseType)})` : ''; - console.log('[nlm] Changes are %j (%s)', releaseType, nextUpdate); + console.log('[nlm] Changes are %j %s', releaseType, nextUpdate); } const verifyTasks = [