@@ -70,32 +70,82 @@ function parseFromRevArg(rawArgs: ReadonlyArray<string>): string | null {
7070 ) ;
7171}
7272
73+ function readVersionFromPackageJSONAtRef ( ref : string ) : string {
74+ const packageJSONAtRef = git ( ) . catFile ( 'blob' , `${ ref } :package.json` ) ;
75+ return JSON . parse ( packageJSONAtRef ) . version ;
76+ }
77+
78+ function resolveChangelogRangeConfig (
79+ workingTreeVersion : string ,
80+ fromRev : string | null ,
81+ ) : {
82+ title : string ;
83+ rangeStart : string ;
84+ rangeEnd : string ;
85+ } {
86+ const workingTreeReleaseTag = `v${ workingTreeVersion } ` ;
87+
88+ // packageJSON in the working tree can differ from HEAD:package.json during
89+ // release:prepare after npm version updates files but before committing.
90+ // Supported scenario 1: release preparation not started
91+ // - working-tree version tag exists
92+ // - HEAD version older than or equal to working-tree version, must also exist
93+ if ( git ( ) . tagExists ( workingTreeReleaseTag ) ) {
94+ return {
95+ title : 'Unreleased' ,
96+ rangeStart : fromRev ?? workingTreeReleaseTag ,
97+ rangeEnd : 'HEAD' ,
98+ } ;
99+ }
100+
101+ const headVersion = readVersionFromPackageJSONAtRef ( 'HEAD' ) ;
102+ const headReleaseTag = `v${ headVersion } ` ;
103+
104+ // Supported scenario 2: release preparation started
105+ // - working-tree version tag not yet created
106+ // - HEAD version tag exists
107+ if ( git ( ) . tagExists ( headReleaseTag ) ) {
108+ return {
109+ title : workingTreeReleaseTag ,
110+ rangeStart : fromRev ?? headReleaseTag ,
111+ rangeEnd : 'HEAD' ,
112+ } ;
113+ }
114+
115+ // Supported scenario 3:
116+ // - release preparation committed
117+ // - working-tree version tag equal to HEAD version tag, both not yet created
118+ // - HEAD~1 version tag exists
119+ const parentVersion = readVersionFromPackageJSONAtRef ( 'HEAD~1' ) ;
120+ const parentTag = `v${ parentVersion } ` ;
121+ const parentTagExists = git ( ) . tagExists ( parentTag ) ;
122+ if ( workingTreeReleaseTag === headReleaseTag && parentTagExists ) {
123+ console . warn ( `Release committed, should already contain this changelog!` ) ;
124+
125+ return {
126+ title : workingTreeReleaseTag ,
127+ rangeStart : fromRev ?? parentTag ,
128+ rangeEnd : 'HEAD~1' ,
129+ } ;
130+ }
131+
132+ throw new Error (
133+ 'Unable to determine changelog range. One of the following scenarios must be true:\n' +
134+ `1) HEAD/working-tree release tags exist, i.e. release preparation not started.\n` +
135+ `2) HEAD release tag exists, but working-tree release tag not yet created, i.e. release preparation started, not yet committed.\n` +
136+ `3) HEAD/working-tree release tags not yet created, i.e. release preparation committed, not yet released, no additional commits on branch.` ,
137+ ) ;
138+ }
139+
73140async function genChangeLog ( ) : Promise < string > {
74- const { version } = packageJSON ;
75- const releaseTag = `v${ version } ` ;
141+ const workingTreeVersion = packageJSON . version ;
76142 const fromRev = parseFromRevArg ( process . argv . slice ( 2 ) ) ;
77- const releaseTagExists = git ( ) . tagExists ( releaseTag ) ;
78-
79- let tag : string | null ;
80- let baseRef : string ;
81- let endRef : string ;
82- if ( releaseTagExists ) {
83- tag = null ;
84- baseRef = fromRev ?? releaseTag ;
85- endRef = 'HEAD' ;
86- } else {
87- tag = releaseTag ;
88- if ( fromRev != null ) {
89- baseRef = fromRev ;
90- } else {
91- const parentPackageJSON = git ( ) . catFile ( 'blob' , 'HEAD~1:package.json' ) ;
92- const parentVersion = JSON . parse ( parentPackageJSON ) . version ;
93- baseRef = `v${ parentVersion } ` ;
94- }
95- endRef = 'HEAD~1' ;
96- }
143+ const { title, rangeStart, rangeEnd } = resolveChangelogRangeConfig (
144+ workingTreeVersion ,
145+ fromRev ,
146+ ) ;
97147
98- const commitsRange = `${ baseRef } ..${ endRef } ` ;
148+ const commitsRange = `${ rangeStart } ..${ rangeEnd } ` ;
99149 const commitsList = git ( ) . revList ( '--reverse' , commitsRange ) ;
100150
101151 const allPRs = await getPRsInfo ( commitsList ) ;
@@ -139,7 +189,7 @@ async function genChangeLog(): Promise<string> {
139189 throw new Error ( validationIssues . join ( '\n\n' ) ) ;
140190 }
141191
142- let changelog = `## ${ tag ?? 'Unreleased' } (${ date } )\n` ;
192+ let changelog = `## ${ title } (${ date } )\n` ;
143193 for ( const [ label , config ] of Object . entries ( labelsConfig ) ) {
144194 const prs = byLabel [ label ] ;
145195 if ( prs != null ) {
0 commit comments