22// Maintains a single rolling RC discussion per upcoming release (vX.Y.Z)
33// - Creates the discussion once (if missing)
44// - Adds a comment for each RC (rc.1, rc.2, ...)
5- // - Skips for stable releases
5+ // - Finalizes the thread on stable release
66
77const GQL_URL = 'https://api.github.com/graphql' ;
88
@@ -54,12 +54,12 @@ async function main() {
5454 const repoFull = process . env . GITHUB_REPOSITORY ;
5555 const nextVersion = process . argv [ 2 ] || process . env . SEMREL_NEXT_VERSION ;
5656 if ( ! token || ! repoFull || ! nextVersion ) return ;
57- if ( ! isRc ( nextVersion ) ) return ; // RCs only
5857 const [ owner , name ] = repoFull . split ( '/' ) ;
5958
6059 // Derive base version without rc suffix
6160 const baseVersion = nextVersion . replace ( / - r c \. .* $ / , '' ) ;
6261 const title = `Release Candidate: v${ baseVersion } ` ;
62+ const today = new Date ( ) . toISOString ( ) . slice ( 0 , 10 ) ;
6363
6464 // Determine last stable tag for proper compare link and cumulative notes
6565 let lastStableTag = '' ;
@@ -96,6 +96,16 @@ async function main() {
9696 const compareUrl = lastStableTag
9797 ? `https://github.com/${ owner } /${ name } /compare/${ lastStableTag } ...v${ nextVersion } `
9898 : '' ;
99+ // Try to find previous RC for delta link
100+ let prevRcTag = '' ;
101+ try {
102+ const tags = run ( 'git tag --sort=-v:refname' ) . split ( '\n' ) ;
103+ const rcTags = tags . filter ( ( t ) => t . startsWith ( `v${ baseVersion } -rc.` ) ) ;
104+ const currentRcNum = Number ( ( nextVersion . match ( / - r c \. ( \d + ) $ / ) || [ ] ) [ 1 ] || '0' ) ;
105+ const prevTarget = `v${ baseVersion } -rc.${ currentRcNum - 1 } ` ;
106+ if ( currentRcNum > 1 && rcTags . includes ( prevTarget ) ) prevRcTag = prevTarget ;
107+ } catch { }
108+ const deltaUrl = prevRcTag ? `https://github.com/${ owner } /${ name } /compare/${ prevRcTag } ...v${ nextVersion } ` : '' ;
99109
100110 // Try to fetch auto-generated release notes from GitHub API (cumulative from last stable)
101111 let generatedNotes = '' ;
@@ -113,25 +123,47 @@ async function main() {
113123 generatedNotes = body ?. body || '' ;
114124 } catch { }
115125
116- const note = `Updated to v${ nextVersion } . Release: ${ releaseUrl } ${ compareUrl ? ` | Full changelog : ${ compareUrl } ` : '' } ` ;
126+ const note = `Released ${ today } : v${ nextVersion } — ${ releaseUrl } ${ deltaUrl ? ` | Delta since ${ prevRcTag } : ${ deltaUrl } ` : '' } ${ compareUrl ? ` | Since last stable : ${ compareUrl } ` : '' } ` ;
117127
118- const bodyTemplate = ( v ) => {
128+
129+ const bodyTemplateRc = ( v ) => {
119130 return [
120131 `Tracking thread for the upcoming release v${ baseVersion } .` ,
121132 '' ,
122- `Latest RC: v${ v } ` ,
123- `Release: ${ releaseUrl } ` ,
124- compareUrl ? `Full changelog: ${ compareUrl } ` : undefined ,
133+ `Date: ${ today } ` ,
134+ '' ,
135+ `## Latest build` ,
136+ `- RC: v${ v } ` ,
137+ `- Release: ${ releaseUrl } ` ,
138+ compareUrl ? `- Since last stable: ${ compareUrl } ` : undefined ,
139+ deltaUrl ? `- Delta since previous RC (${ prevRcTag } ): ${ deltaUrl } ` : undefined ,
125140 '' ,
141+ `## Notes` ,
142+ `Feedback welcome — report issues here: https://github.com/${ owner } /${ name } /issues/new?title=${ encodeURIComponent (
143+ `RC v${ v } feedback:`
144+ ) } `,
126145 generatedNotes
127- ? `<details><summary>Auto-generated release notes</summary>\n\n${ generatedNotes } \n\n</details>`
146+ ? `\n <details><summary>Auto-generated release notes</summary>\n\n${ generatedNotes } \n\n</details>`
128147 : undefined ,
129148 ]
130149 . filter ( Boolean )
131150 . join ( '\n' ) ;
132151 } ;
133152
134- if ( ! existing ) {
153+ const bodyTemplateStable = ( v , finalDeltaUrl ) => {
154+ return [
155+ `Status: Released v${ v } on ${ today } .` ,
156+ '' ,
157+ `## Links` ,
158+ `- Stable: https://github.com/${ owner } /${ name } /releases/tag/v${ v } ` ,
159+ finalDeltaUrl ? `- Delta from last RC: ${ finalDeltaUrl } ` : undefined ,
160+ lastStableTag ? `- Since previous stable: https://github.com/${ owner } /${ name } /compare/${ lastStableTag } ...v${ v } ` : undefined ,
161+ ]
162+ . filter ( Boolean )
163+ . join ( '\n' ) ;
164+ } ;
165+
166+ if ( isRc ( nextVersion ) && ! existing ) {
135167 // Create the rolling RC discussion once
136168 await gqlFetch (
137169 token ,
@@ -140,31 +172,66 @@ async function main() {
140172 discussion { id number url }
141173 }
142174 }` ,
143- { repoId : repo . id , catId : category . id , title, body : bodyTemplate ( nextVersion ) }
175+ { repoId : repo . id , catId : category . id , title, body : bodyTemplateRc ( nextVersion ) }
144176 ) ;
145177 // No further comment needed on first creation
146178 return ;
147179 }
148180
149- // Update the discussion body with latest RC details
150- await gqlFetch (
151- token ,
152- `mutation UpdateDiscussion($id: ID!, $body: String!) {
153- updateDiscussion(input: { discussionId: $id, body: $body }) { discussion { id url } }
154- }` ,
155- { id : existing . id , body : bodyTemplate ( nextVersion ) }
156- ) ;
181+ if ( isRc ( nextVersion ) && existing ) {
182+ // Update the discussion body with latest RC details
183+ await gqlFetch (
184+ token ,
185+ `mutation UpdateDiscussion($id: ID!, $body: String!) {
186+ updateDiscussion(input: { discussionId: $id, body: $body }) { discussion { id url } }
187+ }` ,
188+ { id : existing . id , body : bodyTemplateRc ( nextVersion ) }
189+ ) ;
190+ // Add a comment to the existing rolling thread
191+ await gqlFetch (
192+ token ,
193+ `mutation AddComment($discId: ID!, $body: String!) {
194+ addDiscussionComment(input: { discussionId: $discId, body: $body }) {
195+ comment { url }
196+ }
197+ }` ,
198+ { discId : existing . id , body : note }
199+ ) ;
200+ return ;
201+ }
157202
158- // Add a comment to the existing rolling thread
159- await gqlFetch (
160- token ,
161- `mutation AddComment($discId: ID!, $body: String!) {
162- addDiscussionComment(input: { discussionId: $discId, body: $body }) {
163- comment { url }
164- }
165- }` ,
166- { discId : existing . id , body : note }
167- ) ;
203+ // Stable release: finalize thread (if exists)
204+ if ( ! isRc ( nextVersion ) && existing ) {
205+ // Compute delta from last RC to stable
206+ let lastRcTag = '' ;
207+ try {
208+ const tags = run ( 'git tag --sort=-v:refname' ) . split ( '\n' ) ;
209+ const rcTags = tags . filter ( ( t ) => t . startsWith ( `v${ baseVersion } -rc.` ) ) ;
210+ lastRcTag = rcTags [ 0 ] || '' ;
211+ } catch { }
212+ const finalDeltaUrl = lastRcTag
213+ ? `https://github.com/${ owner } /${ name } /compare/${ lastRcTag } ...v${ nextVersion } `
214+ : '' ;
215+
216+ // Update body to show Released status and links
217+ await gqlFetch (
218+ token ,
219+ `mutation UpdateDiscussion($id: ID!, $body: String!) {
220+ updateDiscussion(input: { discussionId: $id, body: $body }) { discussion { id url } }
221+ }` ,
222+ { id : existing . id , body : bodyTemplateStable ( nextVersion , finalDeltaUrl ) }
223+ ) ;
224+ // Post final comment
225+ await gqlFetch (
226+ token ,
227+ `mutation AddComment($discId: ID!, $body: String!) {
228+ addDiscussionComment(input: { discussionId: $discId, body: $body }) {
229+ comment { url }
230+ }
231+ }` ,
232+ { discId : existing . id , body : `Stable released ${ today } : v${ nextVersion } — https://github.com/${ owner } /${ name } /releases/tag/v${ nextVersion } ${ finalDeltaUrl ? ` | Delta from last RC: ${ finalDeltaUrl } ` : '' } ` }
233+ ) ;
234+ }
168235}
169236
170237main ( ) . catch ( ( ) => {
0 commit comments