@@ -3,9 +3,11 @@ import {createReadStream, createWriteStream, readFileSync} from 'fs';
3
3
import { prompt } from 'inquirer' ;
4
4
import { join } from 'path' ;
5
5
import { Readable } from 'stream' ;
6
+ import { releasePackages } from './release-output/release-packages' ;
6
7
7
8
// These imports lack type definitions.
8
9
const conventionalChangelog = require ( 'conventional-changelog' ) ;
10
+ const changelogCompare = require ( 'conventional-changelog-writer/lib/util' ) ;
9
11
const merge2 = require ( 'merge2' ) ;
10
12
11
13
/** Prompts for a changelog release name and prepends the new changelog. */
@@ -21,11 +23,16 @@ export async function promptAndGenerateChangelog(changelogPath: string) {
21
23
*/
22
24
export async function prependChangelogFromLatestTag ( changelogPath : string , releaseName : string ) {
23
25
const outputStream : Readable = conventionalChangelog (
24
- /* core options */ { preset : 'angular' } ,
25
- /* context options */ { title : releaseName } ,
26
- /* raw-commits options */ null ,
27
- /* commit parser options */ null ,
28
- /* writer options */ createChangelogWriterOptions ( changelogPath ) ) ;
26
+ /* core options */ { preset : 'angular' } ,
27
+ /* context options */ { title : releaseName } ,
28
+ /* raw-commits options */ null ,
29
+ /* commit parser options */ {
30
+ // Expansion of the convention-changelog-angular preset to extract the package
31
+ // name from the commit message.
32
+ headerPattern : / ^ ( \w * ) (?: \( (?: ( [ ^ / ] + ) \/ ) ? ( .* ) \) ) ? : ( .* ) $ / ,
33
+ headerCorrespondence : [ 'type' , 'package' , 'scope' , 'subject' ] ,
34
+ } ,
35
+ /* writer options */ createChangelogWriterOptions ( changelogPath ) ) ;
29
36
30
37
// Stream for reading the existing changelog. This is necessary because we want to
31
38
// actually prepend the new changelog to the existing one.
@@ -41,19 +48,20 @@ export async function prependChangelogFromLatestTag(changelogPath: string, relea
41
48
// read and write from the same source which causes the content to be thrown off.
42
49
previousChangelogStream . on ( 'end' , ( ) => {
43
50
mergedCompleteChangelog . pipe ( createWriteStream ( changelogPath ) )
44
- . once ( 'error' , ( error : any ) => reject ( error ) )
45
- . once ( 'finish' , ( ) => resolve ( ) ) ;
51
+ . once ( 'error' , ( error : any ) => reject ( error ) )
52
+ . once ( 'finish' , ( ) => resolve ( ) ) ;
46
53
} ) ;
47
54
} ) ;
48
55
}
49
56
50
57
/** Prompts the terminal for a changelog release name. */
51
58
export async function promptChangelogReleaseName ( ) : Promise < string > {
52
59
return ( await prompt < { releaseName : string } > ( {
53
- type : 'text' ,
54
- name : 'releaseName' ,
55
- message : 'What should be the name of the release?'
56
- } ) ) . releaseName ;
60
+ type : 'text' ,
61
+ name : 'releaseName' ,
62
+ message : 'What should be the name of the release?'
63
+ } ) )
64
+ . releaseName ;
57
65
}
58
66
59
67
/**
@@ -68,45 +76,92 @@ export async function promptChangelogReleaseName(): Promise<string> {
68
76
*/
69
77
function createChangelogWriterOptions ( changelogPath : string ) {
70
78
const existingChangelogContent = readFileSync ( changelogPath , 'utf8' ) ;
79
+ const commitSortFunction = changelogCompare . functionify ( [ 'type' , 'scope' , 'subject' ] ) ;
71
80
72
81
return {
82
+ // Overwrite the changelog templates so that we can render the commits grouped
83
+ // by package names.
84
+ mainTemplate : readFileSync ( join ( __dirname , 'changelog-root-template.hbs' ) , 'utf8' ) ,
85
+ commitPartial : readFileSync ( join ( __dirname , 'changelog-commit-template.hbs' ) , 'utf8' ) ,
86
+
73
87
// Specify a writer option that can be used to modify the content of a new changelog section.
74
88
// See: conventional-changelog/tree/master/packages/conventional-changelog-writer
75
89
finalizeContext : ( context : any ) => {
76
- context . commitGroups = context . commitGroups . filter ( ( group : any ) => {
77
- group . commits = group . commits . filter ( ( commit : any ) => {
78
-
79
- // Commits that change things for "cdk-experimental" or "material-experimental" will also
80
- // show up in the changelog by default. We don't want to show these in the changelog.
81
- if ( commit . scope && commit . scope . includes ( 'experimental' ) ) {
82
- console . log ( yellow ( ` ↺ Skipping experimental: "${ bold ( commit . header ) } "` ) ) ;
83
- return false ;
84
- }
90
+ const packageGroups : { [ packageName : string ] : any [ ] } = { } ;
85
91
92
+ context . commitGroups . forEach ( ( group : any ) => {
93
+ group . commits . forEach ( ( commit : any ) => {
86
94
// Filter out duplicate commits. Note that we cannot compare the SHA because the commits
87
95
// will have a different SHA if they are being cherry-picked into a different branch.
88
96
if ( existingChangelogContent . includes ( commit . subject ) ) {
89
97
console . log ( yellow ( ` ↺ Skipping duplicate: "${ bold ( commit . header ) } "` ) ) ;
90
98
return false ;
91
99
}
92
- return true ;
100
+
101
+ // Commits which just specify a scope that refers to a package but do not follow
102
+ // the commit format that is parsed by the conventional-changelog-parser, can be
103
+ // still resolved to their package from the scope. This handles the case where
104
+ // a commit targets the whole package and does not specify a specific scope.
105
+ // e.g. "refactor(material-experimental): support strictness flags".
106
+ if ( ! commit . package && commit . scope ) {
107
+ const matchingPackage = releasePackages . find ( pkgName => pkgName === commit . scope ) ;
108
+ if ( matchingPackage ) {
109
+ commit . scope = null ;
110
+ commit . package = matchingPackage ;
111
+ }
112
+ }
113
+
114
+ // TODO(devversion): once we formalize the commit message format and
115
+ // require specifying the "material" package explicitly, we can remove
116
+ // the fallback to the "material" package.
117
+ const packageName = commit . package || 'material' ;
118
+ const { color, title} = getTitleAndColorOfTypeLabel ( commit . type ) ;
119
+
120
+ if ( ! packageGroups [ packageName ] ) {
121
+ packageGroups [ packageName ] = [ ] ;
122
+ }
123
+
124
+ packageGroups [ packageName ] . push ( {
125
+ typeDescription : title ,
126
+ typeImageUrl : `https://img.shields.io/badge/-${ title } -${ color } ` ,
127
+ ...commit
128
+ } ) ;
93
129
} ) ;
130
+ } ) ;
94
131
95
- // Filter out commit groups which don't have any commits. Commit groups will become
96
- // empty if we filter out all duplicated commits.
97
- return group . commits . length ;
132
+ context . packageGroups = Object . keys ( packageGroups ) . sort ( ) . map ( pkgName => {
133
+ return {
134
+ title : pkgName ,
135
+ commits : packageGroups [ pkgName ] . sort ( commitSortFunction ) ,
136
+ } ;
98
137
} ) ;
99
138
100
139
return context ;
101
140
}
102
141
} ;
103
142
}
104
143
144
+ /** Gets the title and color from a commit type label. */
145
+ function getTitleAndColorOfTypeLabel ( typeLabel : string ) : { title : string , color : string } {
146
+ if ( typeLabel === `Features` ) {
147
+ return { title : 'feature' , color : 'green' } ;
148
+ } else if ( typeLabel === `Bug Fixes` ) {
149
+ return { title : 'bug fix' , color : 'orange' } ;
150
+ } else if ( typeLabel === `Performance Improvements` ) {
151
+ return { title : 'performance' , color : 'blue' } ;
152
+ } else if ( typeLabel === `Reverts` ) {
153
+ return { title : 'revert' , color : 'grey' } ;
154
+ } else if ( typeLabel === `Documentation` ) {
155
+ return { title : 'docs' , color : 'darkgreen' } ;
156
+ } else if ( typeLabel === `refactor` ) {
157
+ return { title : 'refactor' , color : 'lightgrey' } ;
158
+ }
159
+ return { title : typeLabel , color : 'yellow' } ;
160
+ }
161
+
105
162
/** Entry-point for generating the changelog when called through the CLI. */
106
163
if ( require . main === module ) {
107
164
promptAndGenerateChangelog ( join ( __dirname , '../../CHANGELOG.md' ) ) . then ( ( ) => {
108
165
console . log ( green ( ' ✓ Successfully updated the changelog.' ) ) ;
109
166
} ) ;
110
167
}
111
-
112
-
0 commit comments