11'use strict'
22
3+ const execSync = require ( 'child_process' ) . execSync
4+
35/*
46Usage:
57
@@ -12,32 +14,32 @@ 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+ const { branch, releaseNotes } = parseArgs ( process . argv . slice ( 2 ) )
38+
39+ const log = execSync ( `git log --reverse --pretty='format:%h' ${ branch } ...` )
40+ . toString ( )
41+ . split ( / \n / )
42+
4143 const query = `
4244 fragment commitCredit on GitObject {
4345 ... on Commit {
@@ -75,14 +77,54 @@ const main = async () => {
7577 const response = execSync ( `gh api graphql -f query='${ query } '` ) . toString ( )
7678 const body = JSON . parse ( response )
7779
80+ const output = {
81+ Features : [ ] ,
82+ 'Bug Fixes' : [ ] ,
83+ Documentation : [ ] ,
84+ Dependencies : [ ] ,
85+ }
86+
7887 for ( const [ hash , data ] of Object . entries ( body . data . repository ) ) {
88+ if ( ! data ) {
89+ console . error ( 'no data for hash' , hash )
90+ continue
91+ }
92+
93+ const message = data . message . replace ( / ^ \s + / gm, '' ) // remove leading spaces
94+ . replace ( / ( \r ? \n ) + / gm, '\n' ) // replace multiple newlines with one
95+ . replace ( / ( [ ^ \s ] + @ \d + \. \d + \. \d + .* ) / gm, '`$1`' ) // wrap package@version in backticks
96+
97+ const lines = message . split ( '\n' )
98+ // the title is the first line of the commit, 'let' because we change it later
99+ let title = lines . shift ( )
100+ // the body is the rest of the commit with some normalization
101+ const body = lines . join ( '\n' ) // re-join our normalized commit into a string
102+ . split ( / \n ? \* / gm) // split on lines starting with a literal *
103+ . filter ( ( line ) => line . trim ( ) . length > 0 ) // remove blank lines
104+ . map ( ( line ) => {
105+ const clean = line . replace ( / \n / gm, ' ' ) // replace new lines for this bullet with spaces
106+ return clean . startsWith ( '*' ) ? clean : `* ${ clean } ` // make sure the line starts with *
107+ } )
108+ . join ( '\n' ) // re-join with new lines
109+
110+ const type = title . startsWith ( 'feat' ) ? 'Features'
111+ : title . startsWith ( 'fix' ) ? 'Bug Fixes'
112+ : title . startsWith ( 'docs' ) ? 'Documentation'
113+ : title . startsWith ( 'deps' ) ? 'Dependencies'
114+ : null
115+
116+ const prs = data . associatedPullRequests . nodes . filter ( ( pull ) => pull . merged )
117+ for ( const pr of prs ) {
118+ title = title . replace ( new RegExp ( `\\s*\\(#${ pr . number } \\)` , 'g' ) , '' )
119+ }
120+
79121 const commit = {
80122 hash : hash . slice ( 1 ) , // remove leading _
81123 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 ) ,
124+ title ,
125+ type ,
126+ body ,
127+ prs,
86128 credit : data . authors . nodes . map ( ( author ) => {
87129 if ( author . user && author . user . login ) {
88130 return {
@@ -100,7 +142,42 @@ const main = async () => {
100142 } ) ,
101143 }
102144
103- printCommit ( commit )
145+ if ( commit . type ) {
146+ output [ commit . type ] . push ( commit )
147+ }
148+ }
149+
150+ for ( const key of Object . keys ( output ) ) {
151+ if ( output [ key ] . length > 0 ) {
152+ const groupHeading = `### ${ key } `
153+ console . group ( groupHeading )
154+ console . log ( ) // blank line after heading
155+
156+ for ( const commit of output [ key ] ) {
157+ let groupCommit = `* [\`${ commit . hash } \`](${ commit . url } )`
158+ for ( const pr of commit . prs ) {
159+ groupCommit += ` [#${ pr . number } ](${ pr . url } )`
160+ }
161+ groupCommit += ` ${ commit . title } `
162+ if ( key !== 'Dependencies' ) {
163+ for ( const user of commit . credit ) {
164+ if ( releaseNotes ) {
165+ groupCommit += ` (${ user . name } )`
166+ } else {
167+ groupCommit += ` ([${ user . name } ](${ user . url } ))`
168+ }
169+ }
170+ }
171+ console . group ( groupCommit )
172+ if ( commit . body && commit . body . length ) {
173+ console . log ( commit . body )
174+ }
175+ console . groupEnd ( groupCommit )
176+ }
177+
178+ console . log ( ) // blank line at end of group
179+ console . groupEnd ( groupHeading )
180+ }
104181 }
105182}
106183
0 commit comments