diff --git a/README.md b/README.md index e8b98b6..3c38792 100644 --- a/README.md +++ b/README.md @@ -117,6 +117,41 @@ 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). +* `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 + +Example for +```json5 +{ + "nlm": { + "emoji": { + "set": { + "refactor": "đŸ”Ĩ" // will overwrite the existing setting for "refactor" type + } + } + } +} +``` + +The default emojis for the commit types are: +```json5 +{ + "feat": "✨", + "fix": "🐛", + "perf": "⚡", + "refactor": "đŸ“Ļī¸", + "chore": "â™ģī¸", + "build": "👷", + "revert": "↩ī¸", + "docs": "📝", + "style": "🎨", + "test": "✅", + "ci": "💚", + "breaking": "đŸ’Ĩ" // this emoji will be set before the "Breaking Change" section +} +``` If there's no file named `LICENSE` in the repository, `nlm` won't attempt to add the headers. diff --git a/lib/steps/changelog.js b/lib/steps/changelog.js index e2246a9..2af134a 100644 --- a/lib/steps/changelog.js +++ b/lib/steps/changelog.js @@ -36,6 +36,26 @@ const Github = require('../github/client'); const parseRepository = require('../github/parse-repository'); +const emojiMaps = new Map([ + [ + 'default', + { + breaking: 'đŸ’Ĩ', + feat: '✨', + fix: '🐛', + perf: '⚡', + refactor: 'đŸ“Ļī¸', + chore: 'â™ģī¸', + build: '👷', + revert: '↩ī¸', + docs: '📝', + style: '🎨', + test: '✅', + ci: '💚', + }, + ], +]); + function addPullRequestCommits(pkg, commits, pr) { const github = Github.forRepository(pkg.repository); return Promise.all([ @@ -92,11 +112,16 @@ function removeInvalidPRs(prs) { }); prs.length = filtered.length; Object.assign(prs, filtered); + return { ...prs, ...filtered, length: filtered.length }; } async function generateChangeLog(cwd, pkg, options) { const repoInfo = parseRepository(pkg.repository); - const { commits } = options; + const { commits, emoji = {} } = options; + let emojiSet = {}; + if (!emoji.skip) { + emojiSet = { ...emojiMaps.get('default'), ...(emoji.set || {}) }; + } const prs = commits.filter(c => c.type === 'pr'); function getCommitLink(commit) { @@ -118,15 +143,15 @@ async function generateChangeLog(cwd, pkg, options) { function prependBreakingChanges(changelog) { const breaking = flatten(commits.map(extractBreakingChanges)); if (!breaking.length) return changelog; - return [ - '#### Breaking Changes', - '', - breaking.map(formatBreakingChange).join('\n\n'), - '', - '#### Commits', - '', - changelog, - ].join('\n'); + return `#### ${ + !emoji.skip ? `${emojiSet['breaking']} ` : '' + }Breaking Changes + +${breaking.map(formatBreakingChange).join('\n\n')} + +#### Commits + +${changelog}`; } function formatReference(ref) { @@ -142,7 +167,9 @@ async function generateChangeLog(cwd, pkg, options) { let subject; if (commit.type) { - subject = `**${commit.type}:** ${commit.subject}`; + subject = `${!emoji.skip ? `${emojiSet[commit.type]} ` : ''}**${ + commit.type + }:** ${commit.subject}`; } else { subject = commit.header; } diff --git a/test/steps/changelog.test.js b/test/steps/changelog.test.js index 9d0cf33..4fee243 100644 --- a/test/steps/changelog.test.js +++ b/test/steps/changelog.test.js @@ -118,12 +118,19 @@ function withFakeGithub() { } describe('generateChangeLog', () => { + const defaultOptions = { + emoji: { + skip: true, + }, + }; + it('can create an empty changelog', async () => { const pkg = { repository: 'usr/proj', }; const commits = []; const options = { + ...defaultOptions, commits, }; @@ -171,6 +178,7 @@ describe('generateChangeLog', () => { }, ]; const options = { + ...defaultOptions, commits, }; const href0 = `https://github.com/usr/proj/commit/${commits[0].sha}`; @@ -209,6 +217,7 @@ describe('generateChangeLog', () => { }, ]; const options = { + ...defaultOptions, commits, }; const href0 = `https://github.com/usr/proj/commit/${commits[0].sha}`; @@ -248,6 +257,7 @@ describe('generateChangeLog', () => { }, ]; const options = { + ...defaultOptions, commits, }; const href0 = `https://github.com/usr/proj/commit/${commits[0].sha}`; @@ -322,6 +332,7 @@ describe('generateChangeLog', () => { }, ]; const options = { + ...defaultOptions, commits, }; const href0 = `https://github.com/usr/proj/commit/${commits[0].sha}`; @@ -330,7 +341,8 @@ describe('generateChangeLog', () => { const href3 = `https://github.com/usr/proj/commit/${commits[3].sha}`; const changelog = await generateChangeLog(null, pkg, options); - assert.equal( + assert.strictEqual( + changelog, [ '#### Breaking Changes', '', @@ -356,152 +368,232 @@ describe('generateChangeLog', () => { `* [\`2234567\`](${href1}) **feat:** Do more things`, `* [\`2234567\`](${href2}) **feat:** Do more things`, `* [\`2234567\`](${href3}) **feat:** Do more things`, - ].join('\n'), - changelog + ].join('\n') ); }); - describe('pull request commits', () => { - const httpCalls = withFakeGithub(); + describe('PRs', () => { const pkg = { repository: 'http://127.0.0.1:3000/usr/proj', }; - const commits = [ - { - sha: '1234567890123456789012345678901234567890', - type: 'fix', - subject: 'Stop doing the wrong thing', - }, - { - sha: '2234567890123456789012345678901234567890', - type: 'feat', - subject: 'Do more things', - }, - { - sha: '3234567890123456789012345678901234567890', - type: 'pr', - subject: 'Merge PR #1', - pullId: '1', - }, - ]; - const options = { - commits, - }; - let changelog; + describe('pull request commits', () => { + const httpCalls = withFakeGithub(); + const commits = [ + { + sha: '1234567890123456789012345678901234567890', + type: 'fix', + subject: 'Stop doing the wrong thing', + }, + { + sha: '2234567890123456789012345678901234567890', + type: 'feat', + subject: 'Do more things', + }, + { + sha: '3234567890123456789012345678901234567890', + type: 'pr', + subject: 'Merge PR #1', + pullId: '1', + }, + ]; + const options = { + ...defaultOptions, + commits, + }; + let changelog; - before('generateChangeLog', async () => { - changelog = await generateChangeLog(null, pkg, options); - }); + before('generateChangeLog', async () => { + changelog = await generateChangeLog(null, pkg, options); + }); - it('calls out to github to get PR info', () => { - assert.strictEqual(httpCalls.length, 2); - }); + it('calls out to github to get PR info', () => { + assert.strictEqual(httpCalls.length, 2); + }); - it('groups commits by pull request', () => { - assert.ok(changelog.includes('* PR #1 Title')); - assert.ok(changelog.includes(' - [`1234567`]')); + it('groups commits by pull request', () => { + assert.ok(changelog.includes('* PR #1 Title')); + assert.ok(changelog.includes(' - [`1234567`]')); + }); }); - }); - describe('with an invalid PR', () => { - const httpCalls = withFakeGithub(); - const pkg = { - repository: 'http://127.0.0.1:3000/usr/proj', - }; - const commits = [ - { - sha: '1234567890123456789012345678901234567890', - type: 'fix', - subject: 'Stop doing the wrong thing', - }, - { - sha: '2234567890123456789012345678901234567890', - type: 'feat', - subject: 'Do more things', - }, - { - sha: '3234567890123456789012345678901234567890', - type: 'pr', - subject: 'Merge PR #2', - pullId: '2', - }, - ]; - const sloppyCommits = commits.map(commit => { - if (commit.type === 'pr') return commit; - return { - sha: commit.sha, - header: commit.subject, + describe('with an invalid PR', () => { + const httpCalls = withFakeGithub(); + const commits = [ + { + sha: '1234567890123456789012345678901234567890', + type: 'fix', + subject: 'Stop doing the wrong thing', + }, + { + sha: '2234567890123456789012345678901234567890', + type: 'feat', + subject: 'Do more things', + }, + { + sha: '3234567890123456789012345678901234567890', + type: 'pr', + subject: 'Merge PR #2', + pullId: '2', + }, + ]; + const sloppyCommits = commits.map(commit => { + if (commit.type === 'pr') return commit; + return { + sha: commit.sha, + header: commit.subject, + }; + }); + const options = { + ...defaultOptions, + commits, }; - }); - const options = { - commits, - }; - let changelog = null; - let sloppyChangelog = null; + let changelog = null; + let sloppyChangelog = null; - before('generateChangeLog', async () => { - changelog = await generateChangeLog(null, pkg, options); - }); + before('generateChangeLog', async () => { + changelog = await generateChangeLog(null, pkg, options); + }); - before('generateSloppyChangeLog', async () => { - sloppyChangelog = await generateChangeLog(null, pkg, { - commits: sloppyCommits, + before('generateSloppyChangeLog', async () => { + sloppyChangelog = await generateChangeLog(null, pkg, { + ...defaultOptions, + commits: sloppyCommits, + }); }); - }); - it('calls out to github to get PR info', () => { - assert.strictEqual(httpCalls.length, 4); - }); + it('calls out to github to get PR info', () => { + assert.strictEqual(httpCalls.length, 4); + }); - it('ignores the PR', () => { - assert.ok(!changelog.includes('* PR #2 Title')); - assert.ok(changelog.includes('* [`1234567`]')); + it('ignores the PR', () => { + assert.ok(!changelog.includes('* PR #2 Title')); + assert.ok(changelog.includes('* [`1234567`]')); + }); + + it('handles poorly formatted commit messages too', () => { + assert.ok(sloppyChangelog.includes(') Stop doing the wrong thing\n')); + assert.ok(sloppyChangelog.includes(') Do more things')); + }); }); - it('handles poorly formatted commit messages too', () => { - assert.ok(sloppyChangelog.includes(') Stop doing the wrong thing\n')); - assert.ok(sloppyChangelog.includes(') Do more things')); + describe('with a missing PR', () => { + const httpCalls = withFakeGithub(); + const commits = [ + { + sha: '1234567890123456789012345678901234567890', + type: 'fix', + subject: 'Stop doing the wrong thing', + }, + { + sha: '2234567890123456789012345678901234567890', + type: 'feat', + subject: 'Do more things', + }, + { + sha: '3234567890123456789012345678901234567890', + type: 'pr', + subject: 'Merge PR #3', + pullId: '3', + }, + ]; + const options = { + ...defaultOptions, + commits, + }; + let changelog = null; + + before('generateChangeLog', async () => { + changelog = await generateChangeLog(null, pkg, options); + }); + + it('calls out to github to get PR info', () => { + assert.strictEqual(httpCalls.length, 2); + }); + + it('ignores the PR', () => { + assert.ok(changelog.includes('* [`1234567`]')); + }); }); }); - describe('with a missing PR', () => { - const httpCalls = withFakeGithub(); + describe('emoji', () => { const pkg = { repository: 'http://127.0.0.1:3000/usr/proj', }; - const commits = [ - { - sha: '1234567890123456789012345678901234567890', - type: 'fix', - subject: 'Stop doing the wrong thing', - }, - { - sha: '2234567890123456789012345678901234567890', - type: 'feat', - subject: 'Do more things', - }, - { - sha: '3234567890123456789012345678901234567890', - type: 'pr', - subject: 'Merge PR #3', - pullId: '3', - }, + const defaultCommit = { + sha: '2234567890123456789012345678901234567890', + 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:**' }, ]; + const commits = cases.map(({ type }) => ({ type, ...defaultCommit })); const options = { commits, }; - let changelog = null; + let changelog; - before('generateChangeLog', async () => { + 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('calls out to github to get PR info', () => { - assert.strictEqual(httpCalls.length, 2); + it('allows custom emojis with emoji.set config', async () => { + changelog = await generateChangeLog(null, pkg, { + ...options, + emoji: { + set: { + feat: '🚀', + }, + }, + }); + 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, + }, + }); + const notExpected = 'đŸ“Ļī¸'; + + assert.ok( + !changelog.includes(notExpected), + `should not include ${notExpected}` + ); }); - it('ignores the PR', () => { - assert.ok(changelog.includes('* [`1234567`]')); + it('adds emoji to breaking changes', async () => { + changelog = await generateChangeLog(null, pkg, { + commits: [ + { + type: 'fix', + sha: '2234567890123456789012345678901234567890', + subject: 'something', + notes: [{ title: 'BREAKING CHANGE', text: 'foo' }], + }, + ], + }); + const expected = '#### đŸ’Ĩ Breaking Changes'; + + assert.ok(changelog.includes(expected), `should include ${expected}`); }); }); });