@@ -13,8 +13,9 @@ import { Octokit } from '@octokit/core';
13
13
import { bold , cyan , dim } from 'chalk' ;
14
14
import { Messages , SfdxError } from '@salesforce/core' ;
15
15
import { exec } from 'shelljs' ;
16
+ import * as semver from 'semver' ;
16
17
import { CLI } from '../../types' ;
17
- import { NpmPackage , parseAliasedPackageName } from '../../package' ;
18
+ import { NpmPackage , parseAliasedPackageName , parseAliasedPackageVersion } from '../../package' ;
18
19
19
20
Messages . importMessagesDirectory ( __dirname ) ;
20
21
const messages = Messages . loadMessages ( '@salesforce/plugin-release-management' , 'cli.releasenotes' ) ;
@@ -31,6 +32,18 @@ export type Change = {
31
32
32
33
export type ChangesByPlugin = Record < string , Change [ ] > ;
33
34
35
+ type Differences = {
36
+ removed : Record < string , string > ;
37
+ added : Record < string , string > ;
38
+ upgraded : Record < string , string > ;
39
+ downgraded : Record < string , string > ;
40
+ unchanged : Record < string , string > ;
41
+ } ;
42
+
43
+ function isNotEmpty ( obj : Record < string , unknown > ) : boolean {
44
+ return Object . keys ( obj ) . length > 0 ;
45
+ }
46
+
34
47
export default class ReleaseNotes extends SfdxCommand {
35
48
public static readonly description = messages . getMessage ( 'description' ) ;
36
49
public static readonly examples = messages . getMessage ( 'examples' ) . split ( os . EOL ) ;
@@ -59,15 +72,52 @@ export default class ReleaseNotes extends SfdxCommand {
59
72
this . octokit = new Octokit ( { auth } ) ;
60
73
const cli = ensure < CLI > ( this . flags . cli ) ;
61
74
const fullName = cli === CLI . SF ? '@salesforce/cli' : 'sfdx-cli' ;
62
- const npmPackage = this . getNpmPackage ( fullName , this . flags . since ?? 'latest-rc' ) ;
63
- const publishDate = npmPackage . time [ npmPackage . version ] ;
64
- const plugins = this . normalizePlugins ( npmPackage ) ;
75
+
76
+ const npmPackage = this . getNpmPackage ( fullName , this . flags . since ?? 'latest' ) ;
77
+ const latestrc = this . getNpmPackage ( fullName , 'latest-rc' ) ;
78
+
79
+ const oldPlugins = this . normalizePlugins ( npmPackage ) ;
80
+ const newPlugins = this . normalizePlugins ( latestrc ) ;
81
+
82
+ const differences = this . findDifferences ( oldPlugins , newPlugins ) ;
83
+
84
+ if ( isNotEmpty ( differences . upgraded ) ) {
85
+ this . ux . styledHeader ( 'Upgraded Plugins' ) ;
86
+ for ( const [ plugin , version ] of Object . entries ( differences . upgraded ) ) {
87
+ this . ux . log ( `• ${ plugin } ${ oldPlugins [ plugin ] } => ${ version } ` ) ;
88
+ }
89
+ }
90
+
91
+ if ( isNotEmpty ( differences . downgraded ) ) {
92
+ this . ux . styledHeader ( 'Downgraded Plugins' ) ;
93
+ for ( const [ plugin , version ] of Object . entries ( differences . downgraded ) ) {
94
+ this . ux . log ( `• ${ plugin } ${ version } => ${ oldPlugins [ plugin ] } ` ) ;
95
+ }
96
+ }
97
+
98
+ if ( isNotEmpty ( differences . added ) ) {
99
+ this . ux . styledHeader ( 'Added Plugins' ) ;
100
+ for ( const [ plugin , version ] of Object . entries ( differences . added ) ) {
101
+ this . ux . log ( `• ${ plugin } ${ version } ` ) ;
102
+ }
103
+ }
104
+
105
+ if ( isNotEmpty ( differences . removed ) ) {
106
+ this . ux . styledHeader ( 'Removed Plugins' ) ;
107
+ for ( const [ plugin , version ] of Object . entries ( differences . removed ) ) {
108
+ this . ux . log ( `• ${ plugin } ${ version } ` ) ;
109
+ }
110
+ }
111
+
65
112
const changesByPlugin : ChangesByPlugin = { } ;
66
- for ( const plugin of plugins ) {
113
+ for ( const [ plugin ] of Object . entries ( differences . upgraded ) ) {
114
+ const pkg = this . getNpmPackage ( plugin , oldPlugins [ plugin ] ) ;
115
+ const publishDate = pkg . time [ pkg . version ] ;
67
116
const changes = await this . getPullsForPlugin ( plugin , publishDate ) ;
68
117
if ( changes . length ) changesByPlugin [ plugin ] = changes ;
69
118
}
70
119
120
+ this . ux . log ( ) ;
71
121
if ( this . flags . markdown ) {
72
122
this . logChangesMarkdown ( changesByPlugin ) ;
73
123
} else {
@@ -82,17 +132,41 @@ export default class ReleaseNotes extends SfdxCommand {
82
132
return JSON . parse ( result . stdout ) as NpmPackage ;
83
133
}
84
134
85
- private normalizePlugins ( npmPackage : NpmPackage ) : string [ ] {
135
+ private normalizePlugins ( npmPackage : NpmPackage ) : Record < string , string > {
86
136
const plugins = npmPackage . oclif ?. plugins ?? [ ] ;
87
- const normalized = plugins
88
- . filter ( ( p ) => ! p . startsWith ( '@oclif' ) )
89
- . map ( ( p ) => {
90
- if ( npmPackage . dependencies [ p ] . startsWith ( 'npm:' ) ) {
91
- return parseAliasedPackageName ( npmPackage . dependencies [ p ] ) ;
92
- }
93
- return p ;
94
- } ) ;
95
- return [ npmPackage . name , ...normalized ] ;
137
+ const normalized = { [ npmPackage . name ] : npmPackage . version } ;
138
+ plugins . forEach ( ( p ) => {
139
+ if ( npmPackage . dependencies [ p ] . startsWith ( 'npm:' ) ) {
140
+ const name = parseAliasedPackageName ( npmPackage . dependencies [ p ] ) ;
141
+ const version = parseAliasedPackageVersion ( npmPackage . dependencies [ p ] ) ;
142
+ normalized [ name ] = version ;
143
+ } else {
144
+ normalized [ p ] = npmPackage . dependencies [ p ] ;
145
+ }
146
+ } ) ;
147
+
148
+ return normalized ;
149
+ }
150
+
151
+ private findDifferences ( oldPlugins : Record < string , string > , newPlugins : Record < string , string > ) : Differences {
152
+ const removed = { } ;
153
+ const added = { } ;
154
+ const upgraded = { } ;
155
+ const downgraded = { } ;
156
+ const unchanged = { } ;
157
+
158
+ for ( const [ name , version ] of Object . entries ( oldPlugins ) ) {
159
+ if ( ! newPlugins [ name ] ) removed [ name ] = version ;
160
+ }
161
+
162
+ for ( const [ name , version ] of Object . entries ( newPlugins ) ) {
163
+ if ( ! oldPlugins [ name ] ) added [ name ] = version ;
164
+ else if ( semver . gt ( version , oldPlugins [ name ] ) ) upgraded [ name ] = version ;
165
+ else if ( semver . lt ( version , oldPlugins [ name ] ) ) downgraded [ name ] = version ;
166
+ else unchanged [ name ] = version ;
167
+ }
168
+
169
+ return { removed, added, upgraded, downgraded, unchanged } ;
96
170
}
97
171
98
172
private async getNameOfUser ( username : string ) : Promise < string > {
@@ -115,6 +189,7 @@ export default class ReleaseNotes extends SfdxCommand {
115
189
owner,
116
190
repo,
117
191
state : 'closed' ,
192
+ base : 'main' ,
118
193
// eslint-disable-next-line camelcase
119
194
per_page : 100 ,
120
195
} ) ;
0 commit comments