From c34d085263744ceecf875b0d8f57af67dcb19ac8 Mon Sep 17 00:00:00 2001 From: Steve Mao Date: Thu, 2 Apr 2015 20:03:24 +1100 Subject: [PATCH] feat: use new api of `references` and `notes` Per https://github.com/stevemao/conventional-commits-parser/commit/29bdfe171a876a2e547f0c5940c6d680b7cf2306 and https://github.com/stevemao/conventional-commits-parser/commit/0923ed500867d5dcfa7c3dbad850d50a0eb14a6f. It is possible to reference an issue in a different repository. BREAKING CHANGES: The upstream must use the new api of `references` and `notes`. `closes` now becomes `references` The `notes` object is no longer a key-value object but an array of note object, such as ```js { title: 'BREAKING AMEND', text: 'some breaking change' } ``` --- README.md | 32 +++++++------- index.js | 13 +++--- lib/util.js | 54 ++++++++++++------------ templates/commit.hbs | 2 +- templates/footer.hbs | 2 +- templates/template.hbs | 4 +- test/fixtures/commits.ldjson | 2 +- test/index.spec.js | 53 ++++++++++++++--------- test/partial.commit.spec.js | 31 ++++++++++++-- test/partial.footer.spec.js | 4 +- test/template.spec.js | 2 +- test/util.spec.js | 82 +++++++++++++++++++++--------------- 12 files changed, 166 insertions(+), 115 deletions(-) diff --git a/README.md b/README.md index b48fedd..0ae6688 100644 --- a/README.md +++ b/README.md @@ -26,12 +26,12 @@ The header has a special format that includes a **type**, a **scope** (optional) ### footer -The footer should contain any information about **Important Notes** (optional) and is also the place to reference GitHub issues that this commit **Closes** (optional). +The footer should contain any information about **Important Notes** (optional) and is also the place to reference GitHub issues that this commit **references** (optional). ``` - + ``` [More details](CONVENTIONS.md) @@ -56,22 +56,22 @@ It expects an object mode upstream and the object should look something like thi { hash: '9b1aff905b638aa274a5fc8f88662df446d374bd', header: 'feat(scope): broadcast $destroy event on scope destruction', - body: '', - footer: 'Closes #1', - notes: {}, - closes: [ 1 ], type: 'feat', scope: 'scope', - subject: 'broadcast $destroy event on scope destruction' } + subject: 'broadcast $destroy event on scope destruction', + body: null, + footer: 'Closes #1', + notes: [], + references: [ { action: 'Closes', repository: null, issue: '1', raw: '#1' } ] } { hash: '13f31602f396bc269076ab4d389cfd8ca94b20ba', header: 'feat(ng-list): Allow custom separator', - body: 'bla bla bla', - footer: 'BREAKING CHANGE: some breaking change', - notes: { 'BREAKING CHANGE': 'some breaking change' }, - closes: [], type: 'feat', scope: 'ng-list', - subject: 'Allow custom separator' } + subject: 'Allow custom separator', + body: 'bla bla bla', + footer: 'BREAKING CHANGE: some breaking change', + notes: [ { title: 'BREAKING CHANGE', text: 'some breaking change' } ], + references: [] } Each chunk should be a commit. Json object is also **valid**. @@ -164,11 +164,11 @@ Replace with new values in each commit. Type: `object` Default: `{ 'BREAKING CHANGE': 'BREAKING CHANGES' }` -Replace with new group names. If group name is not present here, it will be ignored. +Replace with new group titles. If a note's title is not in this mapping, the note will be ignored. ##### commitGroupsCompareFn -Type: `function` Default: lexicographical order on `name` field. +Type: `function` Default: lexicographical order on `title` field. A compare function used to sort commit groups. @@ -180,7 +180,7 @@ A compare function used to sort commits. ##### noteGroupsCompareFn -Type : `function` Default: lexicographical order on `name` field. +Type : `function` Default: lexicographical order on `title` field. A compare function used to sort note groups. @@ -258,7 +258,7 @@ It works with [Line Delimited JSON](http://en.wikipedia.org/wiki/Line_Delimited_ If you have commits.ldjson ```js -{"hash":"9b1aff905b638aa274a5fc8f88662df446d374bd","header":"feat(ngMessages): provide support for dynamic message resolution","body":"Prior to this fix it was impossible to apply a binding to a the ngMessage directive to represent the name of the error.","footer":"BREAKING CHANGE: The `ngMessagesInclude` attribute is now its own directive and that must be placed as a **child** element within the element with the ngMessages directive.\nCloses #10036\nCloses #9338","notes":{"BREAKING CHANGE":"The `ngMessagesInclude` attribute is now its own directive and that must be placed as a **child** element within the element with the ngMessages directive."},"closes":[10036,9338],"type":"feat","scope":"ngMessages","subject":"provide support for dynamic message resolution"} +{"hash":"9b1aff905b638aa274a5fc8f88662df446d374bd","header":"feat(ngMessages): provide support for dynamic message resolution","type":"feat","scope":"ngMessages","subject":"provide support for dynamic message resolution","body":"Prior to this fix it was impossible to apply a binding to a the ngMessage directive to represent the name of the error.","footer":"BREAKING CHANGE: The `ngMessagesInclude` attribute is now its own directive and that must be placed as a **child** element within the element with the ngMessages directive.\nCloses #10036\nCloses #9338","notes":[{"title":"BREAKING CHANGE","text":"The `ngMessagesInclude` attribute is now its own directive and that must be placed as a **child** element within the element with the ngMessages directive."}],"references":[{"action":"Closes","repository":null,"issue":"10036","raw":"#10036"},{"action":"Closes","repository":null,"issue":"9338","raw":"#9338"}]} ``` And you run diff --git a/index.js b/index.js index a5b8640..0e48798 100644 --- a/index.js +++ b/index.js @@ -13,7 +13,7 @@ function conventionalCommitsTemplate(version, context, options) { var stream; var commits = []; - var allNotes = []; + var notes = []; var isPatch = semver.patch(version) !== 0; context = _.extend({ @@ -42,9 +42,9 @@ function conventionalCommitsTemplate(version, context, options) { noteGroups: { 'BREAKING CHANGE': 'BREAKING CHANGES' }, - commitGroupsCompareFn: util.getCompareFunction('name'), + commitGroupsCompareFn: util.getCompareFunction('title'), commitsCompareFn: util.getCompareFunction('scope'), - noteGroupsCompareFn: util.getCompareFunction('name'), + noteGroupsCompareFn: util.getCompareFunction('title'), notesCompareFn: util.getCompareFunction(), mainTemplate: fs.readFileSync(__dirname + '/templates/template.hbs', 'utf-8'), headerPartial: fs.readFileSync(__dirname + '/templates/header.hbs', 'utf-8'), @@ -56,11 +56,14 @@ function conventionalCommitsTemplate(version, context, options) { var commit = util.processCommit(chunk, options.hashLength, options.replacements); commits.push(commit); - allNotes.push(commit.notes); + notes = notes.concat(commit.notes); + cb(); }, function(cb) { var compiled = util.compileTemplates(options); - context = _.merge(context, util.getExtraContext(commits, allNotes, options)); + + context = _.merge(context, util.getExtraContext(commits, notes, options)); + this.push(compiled(context)); cb(); }); diff --git a/lib/util.js b/lib/util.js index 7e511d2..8a3d14e 100644 --- a/lib/util.js +++ b/lib/util.js @@ -24,16 +24,16 @@ function getCommitGroups(groupBy, commits, groupsCompareFn, commitsCompareFn) { return commit[groupBy] || ''; }); - _.forEach(commitGroupsObj, function(commits, name) { - if (name === '') { - name = false; + _.forEach(commitGroupsObj, function(commits, title) { + if (title === '') { + title = false; } if (commitsCompareFn) { commits.sort(commitsCompareFn); } commitGroups.push({ - name: name, + title: title, commits: commits }); }); @@ -45,38 +45,36 @@ function getCommitGroups(groupBy, commits, groupsCompareFn, commitsCompareFn) { return commitGroups; } -function getNoteGroups(allNotes, noteGroups, noteGroupsCompareFn, notesCompareFn) { +function getNoteGroups(notes, noteGroups, noteGroupsCompareFn, notesCompareFn) { noteGroups = noteGroups || {}; - var reGroups = []; - _.forEach(allNotes, function(notes) { - _.forEach(notes, function(note, name) { - name = noteGroups[name]; - if (name) { - var titleExists = false; - _.forEach(reGroups, function(group) { - if (group.name === name) { - titleExists = true; - group.notes.push(note); - return false; - } - }); - - if (!titleExists) { - reGroups.push({ - name: name, - notes: [note] - }); + var retGroups = []; + _.forEach(notes, function(note) { + var title = noteGroups[note.title]; + if (title) { + var titleExists = false; + _.forEach(retGroups, function(group) { + if (group.title === title) { + titleExists = true; + group.notes.push(note.text); + return false; } + }); + + if (!titleExists) { + retGroups.push({ + title: title, + notes: [note.text] + }); } - }); + } }); - reGroups.sort(noteGroupsCompareFn); - _.forEach(reGroups, function(group) { + retGroups.sort(noteGroupsCompareFn); + _.forEach(retGroups, function(group) { group.notes.sort(notesCompareFn); }); - return reGroups; + return retGroups; } function processCommit(chunk, hashLength, replacements) { diff --git a/templates/commit.hbs b/templates/commit.hbs index 342d43d..8133a1b 100644 --- a/templates/commit.hbs +++ b/templates/commit.hbs @@ -2,4 +2,4 @@ {{~!-- commit hash --}} {{#if @root.linkReferences}}([{{hash}}][{{@root.host}}/{{@root.repository}}/{{@root.commit}}/{{hash}}]){{else}}{{hash~}}{{/if}} -{{~!-- commit close --}}{{#if closes}}, closes{{~#each closes}} {{#if @root.linkReferences}}[#{{this}}]({{@root.host}}/{{@root.repository}}/{{@root.issue}}/{{this}}){{else}}#{{this}}{{/if}}{{/each}}{{/if}} +{{~!-- commit references --}}{{#if references}}, closes{{~#each references}} {{#if @root.linkReferences}}[{{#if this.repository}}{{this.repository}}{{/if}}#{{this.issue}}]({{@root.host}}/{{#if this.repository}}{{this.repository}}{{else}}{{@root.repository}}{{/if}}/{{@root.issue}}/{{this.issue}}){{else}}{{#if this.repository}}{{this.repository}}{{/if}}#{{this.issue}}{{/if}}{{/each}}{{/if}} diff --git a/templates/footer.hbs b/templates/footer.hbs index bcfe86f..8e6a294 100644 --- a/templates/footer.hbs +++ b/templates/footer.hbs @@ -1,5 +1,5 @@ {{#each noteGroups}} -### {{name}} +### {{title}} {{#each notes}} * {{this}} {{/each}} diff --git a/templates/template.hbs b/templates/template.hbs index 3b0cc82..40c5ebe 100644 --- a/templates/template.hbs +++ b/templates/template.hbs @@ -3,8 +3,8 @@ {{#if commitGroups}} {{#each commitGroups}} -{{#if name}} -### {{name}} +{{#if title}} +### {{title}} {{/if}} {{#each commits}} diff --git a/test/fixtures/commits.ldjson b/test/fixtures/commits.ldjson index a79860d..1278cf8 100644 --- a/test/fixtures/commits.ldjson +++ b/test/fixtures/commits.ldjson @@ -1 +1 @@ -{"hash":"9b1aff905b638aa274a5fc8f88662df446d374bd","header":"feat(ngMessages): provide support for dynamic message resolution","body":"Prior to this fix it was impossible to apply a binding to a the ngMessage directive to represent the name of the error.","footer":"BREAKING CHANGE: The `ngMessagesInclude` attribute is now its own directive and that must be placed as a **child** element within the element with the ngMessages directive.\nCloses #10036\nCloses #9338","notes":{"BREAKING CHANGE":"The `ngMessagesInclude` attribute is now its own directive and that must be placed as a **child** element within the element with the ngMessages directive."},"closes":[10036,9338],"type":"feat","scope":"ngMessages","subject":"provide support for dynamic message resolution"} \ No newline at end of file +{"hash":"9b1aff905b638aa274a5fc8f88662df446d374bd","header":"feat(ngMessages): provide support for dynamic message resolution","type":"feat","scope":"ngMessages","subject":"provide support for dynamic message resolution","body":"Prior to this fix it was impossible to apply a binding to a the ngMessage directive to represent the name of the error.","footer":"BREAKING CHANGE: The `ngMessagesInclude` attribute is now its own directive and that must be placed as a **child** element within the element with the ngMessages directive.\nCloses #10036\nCloses #9338","notes":[{"title":"BREAKING CHANGE","text":"The `ngMessagesInclude` attribute is now its own directive and that must be placed as a **child** element within the element with the ngMessages directive."}],"references":[{"action":"Closes","repository":null,"issue":"10036","raw":"#10036"},{"action":"Closes","repository":null,"issue":"9338","raw":"#9338"}]} \ No newline at end of file diff --git a/test/index.spec.js b/test/index.spec.js index c80caa7..811364d 100644 --- a/test/index.spec.js +++ b/test/index.spec.js @@ -10,28 +10,45 @@ describe('conventionalCommitsTemplate', function() { upstream.write({ hash: '9b1aff905b638aa274a5fc8f88662df446d374bd', header: 'feat(scope): broadcast $destroy event on scope destruction', - body: 'BREAKING NEWS: breaking news', - footer: 'Closes #1', - notes: { - 'BREAKING NEWS': 'breaking news' - }, - closes: [1, 2, 3], type: 'feat', scope: 'scope', - subject: 'broadcast $destroy event on scope destruction' + subject: 'broadcast $destroy event on scope destruction', + body: null, + footer: 'Closes #1', + notes: [{ + title: 'BREAKING NEWS', + text: 'breaking news' + }], + references: [{ + action: 'Closes', + repository: null, + issue: '1', + raw: '#1' + }, { + action: 'Closes', + repository: null, + issue: '2', + raw: '#2' + }, { + action: 'Closes', + repository: null, + issue: '3', + raw: '#3' + }] }); upstream.write({ hash: '13f31602f396bc269076ab4d389cfd8ca94b20ba', header: 'feat(ng-list): Allow custom separator', - body: 'bla bla bla', - footer: 'BREAKING CHANGE: some breaking change', - notes: { - 'BREAKING CHANGE': 'some breaking change' - }, - closes: [], type: 'feat', scope: 'ng-list', - subject: 'Allow custom separator' + subject: 'Allow custom separator', + body: 'bla bla bla', + footer: 'BREAKING CHANGE: some breaking change', + notes: [{ + title: 'BREAKING CHANGE', + text: 'some breaking change' + }], + references: [] }); upstream.end(); @@ -40,9 +57,7 @@ describe('conventionalCommitsTemplate', function() { describe('link', function() { it('should link if host, repository, commit and issue are not truthy', function(done) { - var upstream = getStream(); - - upstream + getStream() .pipe(conventionalCommitsTemplate('0.0.1', { title: 'this is a title', host: 'https://github.com', @@ -59,9 +74,7 @@ describe('conventionalCommitsTemplate', function() { describe('version', function() { it('should not error with a valid version', function(done) { - var upstream = getStream(); - - upstream + getStream() .pipe(conventionalCommitsTemplate('0.0.1')) .on('error', function(err) { done(err); diff --git a/test/partial.commit.spec.js b/test/partial.commit.spec.js index b8a226a..5677506 100644 --- a/test/partial.commit.spec.js +++ b/test/partial.commit.spec.js @@ -39,18 +39,41 @@ describe('partial.commit', function() { expect(log).to.equal('* **my scope:** my subject ([hash][www.myhost.com/a/b/my commits/hash])\n'); }); - it('should generate commit if `closes` is truthy and `linkReferences` is falsy', function() { - templateContext.closes = [1, 2, 3]; + it('should generate commit if `references` is truthy and `linkReferences` is falsy', function() { + templateContext.references = [{ + issue: 1 + }, { + issue: 2 + }, { + issue: 3 + }]; var log = Handlebars.compile(template)(templateContext); expect(log).to.equal('* **my scope:** my subject hash, closes #1 #2 #3\n'); }); - it('should generate commit if `closes` is truthy and `linkReferences` is truthy', function() { + it('should generate commit if `references` is truthy and `linkReferences` is truthy', function() { templateContext.linkReferences = true; - templateContext.closes = [1, 2, 3]; + templateContext.references = [{ + issue: 1 + }, { + issue: 2 + }, { + issue: 3 + }]; var log = Handlebars.compile(template)(templateContext); expect(log).to.equal('* **my scope:** my subject ([hash][www.myhost.com/a/b/my commits/hash]), closes [#1](www.myhost.com/a/b/my issue/1) [#2](www.myhost.com/a/b/my issue/2) [#3](www.myhost.com/a/b/my issue/3)\n'); }); + + it('should reference an issue in a different repository', function() { + templateContext.linkReferences = true; + templateContext.references = [{ + repository: 'c/d', + issue: 1 + }]; + var log = Handlebars.compile(template)(templateContext); + + expect(log).to.equal('* **my scope:** my subject ([hash][www.myhost.com/a/b/my commits/hash]), closes [c/d#1](www.myhost.com/c/d/my issue/1)\n'); + }); }); diff --git a/test/partial.footer.spec.js b/test/partial.footer.spec.js index 31f9678..3dcb9c8 100644 --- a/test/partial.footer.spec.js +++ b/test/partial.footer.spec.js @@ -16,10 +16,10 @@ before(function(done) { beforeEach(function() { templateContext = { noteGroups: [{ - name: 'my name', + title: 'my name', notes: ['my note 1', 'my note 2'] }, { - name: 'my other name', + title: 'my other name', notes: ['my note 3', 'my note 4'] }] }; diff --git a/test/template.spec.js b/test/template.spec.js index 6f8160f..633b039 100644 --- a/test/template.spec.js +++ b/test/template.spec.js @@ -38,7 +38,7 @@ describe('template', function() { it('should generate template if `commitGroups` is truthy and `name` is truthy', function() { templateContext.commitGroups = [{ - name: 'my name', + title: 'my name', commits: [1, 2] }]; var log = Handlebars.compile(template)(templateContext); diff --git a/test/util.spec.js b/test/util.spec.js index bcc234c..beef663 100644 --- a/test/util.spec.js +++ b/test/util.spec.js @@ -47,7 +47,7 @@ describe('util', function() { var commitGroups = util.getCommitGroups('groupBy', commits); expect(commitGroups).to.eql([{ - name: 'A', + title: 'A', commits: [{ groupBy: 'A', content: 'this is A' @@ -56,7 +56,7 @@ describe('util', function() { content: 'this is another A' }] }, { - name: 'Big B', + title: 'Big B', commits: [{ groupBy: 'Big B', content: 'this is B and its a bit longer' @@ -76,14 +76,14 @@ describe('util', function() { var commitGroups = util.getCommitGroups('groupBy', commits); expect(commitGroups).to.eql([{ - name: false, + title: false, commits: [{ content: 'this is A' }, { content: 'this is another A' }] }, { - name: 'Big B', + title: 'Big B', commits: [{ groupBy: 'Big B', content: 'this is B and its a bit longer' @@ -93,23 +93,23 @@ describe('util', function() { it('should group and sort groups', function() { var commitGroups = util.getCommitGroups('groupBy', commits, function(a, b) { - if (a.name.length < b.name.length) { + if (a.title.length < b.title.length) { return 1; } - if (a.name.length > b.name.length) { + if (a.title.length > b.title.length) { return -1; } return 0; }); expect(commitGroups).to.eql([{ - name: 'Big B', + title: 'Big B', commits: [{ groupBy: 'Big B', content: 'this is B and its a bit longer' }] }, { - name: 'A', + title: 'A', commits: [{ groupBy: 'A', content: 'this is A' @@ -132,7 +132,7 @@ describe('util', function() { }); expect(commitGroups).to.eql([{ - name: 'A', + title: 'A', commits: [{ groupBy: 'A', content: 'this is another A' @@ -141,7 +141,7 @@ describe('util', function() { content: 'this is A' }] }, { - name: 'Big B', + title: 'Big B', commits: [{ groupBy: 'Big B', content: 'this is B and its a bit longer' @@ -152,13 +152,20 @@ describe('util', function() { describe('getNoteGroups', function() { var notes = [{ - A: 'this is A and its a bit longer', - B: 'this is B', - C: 'this is C' + title: 'A', + text: 'this is A and its a bit longer' }, { - A: 'this is another A' + title: 'B', + text: 'this is B' }, { - B: 'this is another B' + title: 'C', + text: 'this is C' + }, { + title: 'A', + text: 'this is another A' + }, { + title: 'B', + text: 'this is another B' }]; var noteGroupsMapping = { A: 'Big A', @@ -169,10 +176,10 @@ describe('util', function() { var noteGroups = util.getNoteGroups(notes, noteGroupsMapping); expect(noteGroups).to.eql([{ - name: 'Big A', + title: 'Big A', notes: ['this is A and its a bit longer', 'this is another A'] }, { - name: 'Big B+', + title: 'Big B+', notes: ['this is B', 'this is another B'] }]); }); @@ -185,20 +192,20 @@ describe('util', function() { it('should group and sort groups', function() { var noteGroups = util.getNoteGroups(notes, noteGroupsMapping, function(a, b) { - if (a.name.length < b.name.length) { + if (a.title.length < b.title.length) { return 1; } - if (a.name.length > b.name.length) { + if (a.title.length > b.title.length) { return -1; } return 0; }); expect(noteGroups).to.eql([{ - name: 'Big B+', + title: 'Big B+', notes: ['this is B', 'this is another B'] }, { - name: 'Big A', + title: 'Big A', notes: ['this is A and its a bit longer', 'this is another A'] }]); }); @@ -215,10 +222,10 @@ describe('util', function() { }); expect(noteGroups).to.eql([{ - name: 'Big A', + title: 'Big A', notes: ['this is A and its a bit longer', 'this is another A'] }, { - name: 'Big B+', + title: 'Big B+', notes: ['this is another B', 'this is B'] }]); }); @@ -300,13 +307,20 @@ describe('util', function() { content: 'this is B and its a bit longer' }]; var notes = [{ - A: 'this is A and its a bit longer', - B: 'this is B', - C: 'this is C' + title: 'A', + text: 'this is A and its a bit longer' + }, { + title: 'B', + text: 'this is B' + }, { + title: 'C', + text: 'this is C' }, { - A: 'this is another A' + title: 'A', + text: 'this is another A' }, { - B: 'this is another B' + title: 'B', + text: 'this is another B' }]; var noteGroupsMapping = { A: 'Big A', @@ -320,7 +334,7 @@ describe('util', function() { expect(extra).to.eql({ commitGroups: [{ - name: false, + title: false, commits: [{ content: 'this is A' }, { @@ -331,13 +345,13 @@ describe('util', function() { }] }], noteGroups: [{ - name: 'Big A', + title: 'Big A', notes: [ 'this is A and its a bit longer', 'this is another A' ] }, { - name: 'Big B+', + title: 'Big B+', notes: [ 'this is B', 'this is another B' @@ -353,14 +367,14 @@ describe('util', function() { expect(extra).to.eql({ commitGroups: [{ - name: false, + title: false, commits: [{ content: 'this is A' }, { content: 'this is another A' }] }, { - name: 'Big B', + title: 'Big B', commits: [{ content: 'this is B and its a bit longer', groupBy: 'Big B' @@ -377,7 +391,7 @@ describe('util', function() { expect(extra).to.eql({ commitGroups: [{ - name: false, + title: false, commits: [{ content: 'this is A' }, {