11#!/usr/bin/env node
22
3- // TODO(vojta): pre-commit hook for validating messages
4- // TODO(vojta): report errors, currently Q silence everything which really sucks
5-
63'use strict' ;
74
8- var child = require ( 'child_process' ) ;
95var fs = require ( 'fs' ) ;
10- var util = require ( 'util' ) ;
11- var q = require ( 'qq' ) ;
12-
13- var GIT_LOG_CMD = 'git log --grep="%s" -E --format=%s %s..HEAD' ;
14- var GIT_TAG_CMD = 'git describe --tags --abbrev=0' ;
15-
16- var HEADER_TPL = '<a name="%s"></a>\n# %s (%s)\n\n' ;
17- var LINK_ISSUE = '[#%s](https://github.com/angular/angular.js/issues/%s)' ;
18- var LINK_COMMIT = '[%s](https://github.com/angular/angular.js/commit/%s)' ;
19-
20- var EMPTY_COMPONENT = '$$' ;
21-
22-
23- var warn = function ( ) {
24- console . log ( 'WARNING:' , util . format . apply ( null , arguments ) ) ;
25- } ;
26-
27-
28- var parseRawCommit = function ( raw ) {
29- if ( ! raw ) return null ;
30-
31- var lines = raw . split ( '\n' ) ;
32- var msg = { } , match ;
33-
34- msg . hash = lines . shift ( ) ;
35- msg . subject = lines . shift ( ) ;
36- msg . closes = [ ] ;
37- msg . breaks = [ ] ;
38-
39- lines . forEach ( function ( line ) {
40- match = line . match ( / (?: C l o s e s | F i x e s ) \s # ( \d + ) / ) ;
41- if ( match ) msg . closes . push ( parseInt ( match [ 1 ] ) ) ;
42- } ) ;
43-
44- match = raw . match ( / B R E A K I N G C H A N G E : ( [ \s \S ] * ) / ) ;
45- if ( match ) {
46- msg . breaking = match [ 1 ] ;
47- }
48-
6+ var cl = require ( 'conventional-changelog' ) ;
497
50- msg . body = lines . join ( '\n' ) ;
51- match = msg . subject . match ( / ^ ( .* ) \( ( .* ) \) \: \s ( .* ) $ / ) ;
8+ var changelogFile = 'CHANGELOG.md' ;
529
53- if ( ! match || ! match [ 1 ] || ! match [ 3 ] ) {
54- warn ( 'Incorrect message: %s %s' , msg . hash , msg . subject ) ;
55- msg . type = 'other' ;
56- msg . component = 'other' ;
57- } else {
58- msg . type = match [ 1 ] ;
59- msg . component = match [ 2 ] ;
60- msg . subject = match [ 3 ] ;
61- }
62-
63- return msg ;
64- } ;
65-
66-
67- var linkToIssue = function ( issue ) {
68- return util . format ( LINK_ISSUE , issue , issue ) ;
69- } ;
70-
71-
72- var linkToCommit = function ( hash ) {
73- return util . format ( LINK_COMMIT , hash . substr ( 0 , 8 ) , hash ) ;
10+ var config = {
11+ file : changelogFile ,
12+ repository : 'https://github.com/angular/angular' ,
13+ version : require ( '../../package.json' ) . version
7414} ;
7515
76-
77- var currentDate = function ( ) {
78- var now = new Date ( ) ;
79- var pad = function ( i ) {
80- return ( '0' + i ) . substr ( - 2 ) ;
81- } ;
82-
83- return util . format ( '%d-%s-%s' , now . getFullYear ( ) , pad ( now . getMonth ( ) + 1 ) , pad ( now . getDate ( ) ) ) ;
84- } ;
85-
86-
87- var printSection = function ( stream , title , section , printCommitLinks ) {
88- printCommitLinks = printCommitLinks === undefined ? true : printCommitLinks ;
89- var components = Object . getOwnPropertyNames ( section ) . sort ( ) ;
90- var buffer = '' ;
91- var sectionIsEmpty = true ;
92-
93- var write = function ( str ) {
94- buffer += str ;
95- sectionIsEmpty = false ;
96- }
97-
98- components . forEach ( function ( name ) {
99- var prefix = '-' ;
100- var nested = section [ name ] . length > 1 ;
101-
102- if ( name !== EMPTY_COMPONENT ) {
103- if ( nested ) {
104- write ( util . format ( '- **%s:**\n' , name ) ) ;
105- prefix = ' -' ;
106- } else {
107- prefix = util . format ( '- **%s:**' , name ) ;
108- }
109- }
110-
111- section [ name ] . forEach ( function ( commit ) {
112- if ( printCommitLinks ) {
113- write ( util . format ( '%s %s\n (%s' , prefix , commit . subject , linkToCommit ( commit . hash ) ) ) ;
114- if ( commit . closes . length ) {
115- write ( ',\n ' + commit . closes . map ( linkToIssue ) . join ( ', ' ) ) ;
116- }
117- write ( ')\n' ) ;
118- } else {
119- write ( util . format ( '%s %s\n' , prefix , commit . subject ) ) ;
120- }
121- } ) ;
122- } ) ;
123-
124- if ( sectionIsEmpty ) {
125- // Nothing in this section. Skip.
16+ cl ( config , function ( err , log ) {
17+ if ( err ) {
18+ console . error ( 'Failed to generate changelog: ' + err ) ;
12619 return ;
12720 }
12821
129- stream . write ( util . format ( '\n## %s\n\n' , title ) ) ;
130- stream . write ( buffer ) ;
131- stream . write ( '\n' ) ;
132- } ;
133-
134-
135- var readGitLog = function ( grep , from ) {
136- var deferred = q . defer ( ) ;
137-
138- // TODO(vojta): if it's slow, use spawn and stream it instead
139- child . exec ( util . format ( GIT_LOG_CMD , grep , '%H%n%s%n%b%n==END==' , from ) , function ( code , stdout , stderr ) {
140- var commits = [ ] ;
141-
142- stdout . split ( '\n==END==\n' ) . forEach ( function ( rawCommit ) {
143- var commit = parseRawCommit ( rawCommit ) ;
144- if ( commit ) commits . push ( commit ) ;
145- } ) ;
146-
147- deferred . resolve ( commits ) ;
148- } ) ;
149-
150- return deferred . promise ;
151- } ;
152-
153-
154- var writeChangelog = function ( stream , commits , version ) {
155- var sections = {
156- fix : { } ,
157- feat : { } ,
158- perf : { } ,
159- breaks : { } ,
160- other : { }
161- } ;
162-
163- sections . breaks [ EMPTY_COMPONENT ] = [ ] ;
164-
165- commits . forEach ( function ( commit ) {
166- var section = sections [ commit . type ] ;
167- var component = commit . component || EMPTY_COMPONENT ;
168-
169- if ( section ) {
170- section [ component ] = section [ component ] || [ ] ;
171- section [ component ] . push ( commit ) ;
172- }
173-
174- if ( commit . breaking ) {
175- sections . breaks [ component ] = sections . breaks [ component ] || [ ] ;
176- sections . breaks [ component ] . push ( {
177- subject : util . format ( "due to %s,\n %s" , linkToCommit ( commit . hash ) , commit . breaking ) ,
178- hash : commit . hash ,
179- closes : [ ]
180- } ) ;
181- }
182- } ) ;
183-
184- stream . write ( util . format ( HEADER_TPL , version , version , currentDate ( ) ) ) ;
185- printSection ( stream , 'Features' , sections . feat ) ;
186- printSection ( stream , 'Performance Improvements' , sections . perf ) ;
187- printSection ( stream , 'Breaking Changes' , sections . breaks , false ) ;
188- printSection ( stream , 'Other (malformed commit messages)' , sections . other ) ;
189- } ;
190-
191-
192- var getPreviousTag = function ( ) {
193- var deferred = q . defer ( ) ;
194- child . exec ( GIT_TAG_CMD , function ( code , stdout , stderr ) {
195- if ( code ) deferred . reject ( 'Cannot get the previous tag.' ) ;
196- else deferred . resolve ( stdout . replace ( '\n' , '' ) ) ;
197- } ) ;
198- return deferred . promise ;
199- } ;
200-
201-
202- var generate = function ( version , file ) {
203-
204- getPreviousTag ( ) . then ( function ( tag ) {
205- console . log ( 'Reading git log since' , tag ) ;
206- readGitLog ( '^fix|^feat|^perf|BREAKING' , tag ) . then ( function ( commits ) {
207- console . log ( 'Parsed' , commits . length , 'commits' ) ;
208- console . log ( 'Generating changelog to' , file || 'stdout' , '(' , version , ')' ) ;
209- writeChangelog ( file ? fs . createWriteStream ( file ) : process . stdout , commits , version ) ;
210- } ) ;
211- } ) ;
212- } ;
213-
214-
215- // publish for testing
216- exports . parseRawCommit = parseRawCommit ;
217- exports . printSection = printSection ;
218-
219- // hacky start if not run by jasmine :-D
220- if ( process . argv . join ( '' ) . indexOf ( 'jasmine-node' ) === - 1 ) {
221- generate ( process . argv [ 2 ] , process . argv [ 3 ] ) ;
222- }
22+ fs . writeFileSync ( changelogFile , log ) ;
23+ } ) ;
0 commit comments