11'use strict'
22
3+ const execSync = require ( 'child_process' ) . execSync
4+
35/*
46Usage:
57
@@ -12,32 +14,33 @@ Ordinarily this is run via the gen-changelog shell script, which appends
1214the result to the changelog.
1315
1416*/
15- const execSync = require ( 'child_process' ) . execSync
16- const branch = process . argv [ 2 ] || 'origin/latest'
17- const log = execSync ( `git log --reverse --pretty='format:%h' ${ branch } ...` )
18- . toString ( )
19- . split ( / \n / )
20-
21- function printCommit ( c ) {
22- console . log ( `* [\`${ c . hash } \`](${ c . url } )` )
23- for ( const pr of c . prs ) {
24- console . log ( ` [#${ pr . number } ](${ pr . url } )` )
25- // remove the (#111) relating to this pull request from the commit message,
26- // since we manually add the link outside of the commit message
27- const msgRe = new RegExp ( `\\s*\\(#${ pr . number } \\)` , 'g' )
28- c . message = c . message . replace ( msgRe , '' )
17+
18+ const parseArgs = ( argv ) => {
19+ const result = {
20+ releaseNotes : false ,
21+ branch : 'origin/latest' ,
2922 }
30- // no need to indent this output, it's already got 2 spaces
31- console . log ( c . message )
32- // no credit for deps commits, leading spaces are important here
33- if ( ! c . message . startsWith ( ' deps' ) ) {
34- for ( const user of c . credit ) {
35- console . log ( ` ([${ user . name } ](${ user . url } ))` )
23+
24+ for ( const arg of argv ) {
25+ if ( arg === '--release-notes' ) {
26+ result . releaseNotes = true
27+ continue
3628 }
29+
30+ result . branch = arg
3731 }
32+
33+ return result
3834}
3935
4036const main = async ( ) => {
37+
38+ const { branch, releaseNotes } = parseArgs ( process . argv . slice ( 2 ) )
39+
40+ const log = execSync ( `git log --reverse --pretty='format:%h' ${ branch } ...` )
41+ . toString ( )
42+ . split ( / \n / )
43+
4144 const query = `
4245 fragment commitCredit on GitObject {
4346 ... on Commit {
@@ -75,14 +78,54 @@ const main = async () => {
7578 const response = execSync ( `gh api graphql -f query='${ query } '` ) . toString ( )
7679 const body = JSON . parse ( response )
7780
81+ const output = {
82+ Features : [ ] ,
83+ 'Bug Fixes' : [ ] ,
84+ Documentation : [ ] ,
85+ Dependencies : [ ] ,
86+ }
87+
7888 for ( const [ hash , data ] of Object . entries ( body . data . repository ) ) {
89+ if ( ! data ) {
90+ console . error ( 'no data for hash' , hash )
91+ continue
92+ }
93+
94+ const message = data . message . replace ( / ^ \s + / gm, '' ) // remove leading spaces
95+ . replace ( / ( \r ? \n ) + / gm, '\n' ) // replace multiple newlines with one
96+ . replace ( / ( [ ^ \s ] + @ \d + \. \d + \. \d + .* ) / gm, '`$1`' ) // wrap package@version in backticks
97+
98+ const lines = message . split ( '\n' )
99+ // the title is the first line of the commit, 'let' because we change it later
100+ let title = lines . shift ( )
101+ // the body is the rest of the commit with some normalization
102+ const body = lines . join ( '\n' ) // re-join our normalized commit into a string
103+ . split ( / \n ? \* / gm) // split on lines starting with a literal *
104+ . filter ( ( line ) => line . trim ( ) . length > 0 ) // remove blank lines
105+ . map ( ( line ) => {
106+ const clean = line . replace ( / \n / gm, ' ' ) // replace new lines for this bullet with spaces
107+ return clean . startsWith ( '*' ) ? clean : `* ${ clean } ` // make sure the line starts with *
108+ } )
109+ . join ( '\n' ) // re-join with new lines
110+
111+ const type = title . startsWith ( 'feat' ) ? 'Features'
112+ : title . startsWith ( 'fix' ) ? 'Bug Fixes'
113+ : title . startsWith ( 'docs' ) ? 'Documentation'
114+ : title . startsWith ( 'deps' ) ? 'Dependencies'
115+ : null
116+
117+ const prs = data . associatedPullRequests . nodes . filter ( ( pull ) => pull . merged )
118+ for ( const pr of prs ) {
119+ title = title . replace ( new RegExp ( `\\s*\\(#${ pr . number } \\)` , 'g' ) , '' )
120+ }
121+
79122 const commit = {
80123 hash : hash . slice ( 1 ) , // remove leading _
81124 url : data . url ,
82- message : data . message . replace ( / ( \r ? \n ) + / gm , '\n' ) // swap multiple new lines with one
83- . replace ( / ^ / gm , ' ' ) // add two spaces to the start of each line
84- . replace ( / ( [ ^ \s ] + @ \d + \. \d + \. \d + . * ) / g , '`$1`' ) , // wrap package @version in backticks
85- prs : data . associatedPullRequests . nodes . filter ( ( pull ) => pull . merged ) ,
125+ title ,
126+ type ,
127+ body ,
128+ prs,
86129 credit : data . authors . nodes . map ( ( author ) => {
87130 if ( author . user && author . user . login ) {
88131 return {
@@ -100,7 +143,42 @@ const main = async () => {
100143 } ) ,
101144 }
102145
103- printCommit ( commit )
146+ if ( commit . type ) {
147+ output [ commit . type ] . push ( commit )
148+ }
149+ }
150+
151+ for ( const key of Object . keys ( output ) ) {
152+ if ( output [ key ] . length > 0 ) {
153+ const groupHeading = `### ${ key } `
154+ console . group ( groupHeading )
155+ console . log ( ) // blank line after heading
156+
157+ for ( const commit of output [ key ] ) {
158+ let groupCommit = `* [\`${ commit . hash } \`](${ commit . url } )`
159+ for ( const pr of commit . prs ) {
160+ groupCommit += ` [#${ pr . number } ](${ pr . url } )`
161+ }
162+ groupCommit += ` ${ commit . title } `
163+ if ( key !== 'Dependencies' ) {
164+ for ( const user of commit . credit ) {
165+ if ( releaseNotes ) {
166+ groupCommit += ` (${ user . name } )`
167+ } else {
168+ groupCommit += ` ([${ user . name } ](${ user . url } ))`
169+ }
170+ }
171+ }
172+ console . group ( groupCommit )
173+ if ( commit . body && commit . body . length ) {
174+ console . log ( commit . body )
175+ }
176+ console . groupEnd ( groupCommit )
177+ }
178+
179+ console . log ( ) // blank line at end of group
180+ console . groupEnd ( groupHeading )
181+ }
104182 }
105183}
106184
0 commit comments