@@ -8,6 +8,39 @@ import { latest, oldestSupported } from '#src/versions/lib/enterprise-server-rel
88import { getContents } from '#src/workflows/git-utils.ts'
99import github from '#src/workflows/github.ts'
1010
11+ interface ReleaseDates {
12+ [ releaseNumber : string ] : {
13+ start : string
14+ end : string
15+ prp ?: string
16+ feature_freeze ?: string
17+ code_freeze ?: string
18+ release_candidate ?: string
19+ }
20+ }
21+
22+ interface ReleaseTemplate {
23+ content : string
24+ issue ?: {
25+ number : number
26+ html_url : string
27+ }
28+ }
29+
30+ interface ReleaseTemplates {
31+ [ templateName : string ] : ReleaseTemplate
32+ }
33+
34+ interface ReleaseTemplateContext {
35+ [ key : string ] : string
36+ }
37+
38+ interface IssueSearchOpts {
39+ labels ?: string [ ]
40+ searchQuery ?: string
41+ titleMatch ?: string
42+ }
43+
1144// Required by github() to authenticate
1245if ( ! process . env . GITHUB_TOKEN ) {
1346 throw new Error ( 'Error! You must have a GITHUB_TOKEN set in an .env file to run this script.' )
@@ -99,7 +132,7 @@ async function createReleaseIssue() {
99132
100133 // Only open an issue if today is within 30 days before
101134 // the release candidate date
102- if ( getNumberDaysUntilMilestone ( rcDate ) > 30 ) {
135+ if ( getNumberDaysUntilMilestone ( rcDate || '' ) > 30 ) {
103136 console . log (
104137 `The ${ releaseNumber } release candidate is not until ${ rcDate } ! An issue will be opened 30 days prior to the release candidate date.` ,
105138 )
@@ -134,11 +167,18 @@ async function createReleaseIssue() {
134167 releaseTemplateContext ,
135168 )
136169 await addRepoLabels ( repo , labels )
137- await updateIssue ( repo , template . issue . number , title , body , labels )
170+ await updateIssue ( repo , template . issue ! . number , title , body , labels )
138171 }
139172}
140173
141- async function createIssue ( fullRepo , title , body , labels , releaseNumber , releaseType ) {
174+ async function createIssue (
175+ fullRepo : string ,
176+ title : string ,
177+ body : string ,
178+ labels : string [ ] ,
179+ releaseNumber : string ,
180+ releaseType : string ,
181+ ) {
142182 const [ owner , repo ] = fullRepo . split ( '/' )
143183 if ( ! owner || ! repo ) throw new Error ( 'Please provide a valid repo name in the format owner/repo' )
144184 let issue
@@ -150,7 +190,7 @@ async function createIssue(fullRepo, title, body, labels, releaseNumber, release
150190 body,
151191 labels,
152192 } )
153- } catch ( error ) {
193+ } catch ( error : any ) {
154194 console . log ( `#ERROR# ${ error } \n🛑 There was an error creating the issue.` )
155195 throw error
156196 }
@@ -163,7 +203,13 @@ async function createIssue(fullRepo, title, body, labels, releaseNumber, release
163203 return issue
164204}
165205
166- async function updateIssue ( fullRepo , issueNumber , title , body , labels ) {
206+ async function updateIssue (
207+ fullRepo : string ,
208+ issueNumber : number ,
209+ title : string ,
210+ body : string ,
211+ labels : string [ ] ,
212+ ) {
167213 const [ owner , repo ] = fullRepo . split ( '/' )
168214 if ( ! owner || ! repo ) throw new Error ( 'Please provide a valid repo name in the format owner/repo' )
169215
@@ -177,7 +223,7 @@ async function updateIssue(fullRepo, issueNumber, title, body, labels) {
177223 body,
178224 labels,
179225 } )
180- } catch ( error ) {
226+ } catch ( error : any ) {
181227 console . log (
182228 `#ERROR# ${ error } \n🛑 There was an error updating issue ${ issueNumber } in ${ fullRepo } .` ,
183229 )
@@ -188,17 +234,17 @@ async function updateIssue(fullRepo, issueNumber, title, body, labels) {
188234 }
189235}
190236
191- async function addRepoLabels ( fullRepo , labels ) {
237+ async function addRepoLabels ( fullRepo : string , labels : string [ ] ) {
192238 const [ owner , repo ] = fullRepo . split ( '/' )
193- const labelsToAdd = [ ]
239+ const labelsToAdd : string [ ] = [ ]
194240 for ( const name of labels ) {
195241 try {
196242 await octokit . request ( 'GET /repos/{owner}/{repo}/labels/{name}' , {
197243 owner,
198244 repo,
199245 name,
200246 } )
201- } catch ( error ) {
247+ } catch ( error : any ) {
202248 if ( error . status === 404 ) {
203249 labelsToAdd . push ( name )
204250 } else {
@@ -214,65 +260,78 @@ async function addRepoLabels(fullRepo, labels) {
214260 repo,
215261 name,
216262 } )
217- } catch ( error ) {
263+ } catch ( error : any ) {
218264 console . log ( `#ERROR# ${ error } \n🛑 There was an error adding the label ${ name } .` )
219265 throw error
220266 }
221267 }
222268}
223269
224- function getReleaseTemplates ( ) {
270+ function getReleaseTemplates ( ) : ReleaseTemplates {
225271 const templateFiles = walk ( 'src/ghes-releases/lib/release-templates' , {
226272 includeBasePath : true ,
227273 directories : false ,
228274 globs : [ '**/*.md' ] ,
229275 ignore : [ '**/README.md' ] ,
230276 } )
231- const releaseTemplates = { }
277+ const releaseTemplates : ReleaseTemplates = { }
232278 for ( const file of templateFiles ) {
233279 releaseTemplates [ basename ( file , '.md' ) ] = { content : readFileSync ( file , 'utf8' ) }
234280 }
235281 return releaseTemplates
236282}
237283
238- function getReleaseTemplateContext ( releaseNumber , releaseInfo , releaseTemplates ) {
239- const context = {
284+ function getReleaseTemplateContext (
285+ releaseNumber : string ,
286+ releaseInfo : ReleaseDates [ string ] ,
287+ releaseTemplates : ReleaseTemplates ,
288+ ) : ReleaseTemplateContext {
289+ const context : ReleaseTemplateContext = {
240290 'release-number' : releaseNumber ,
241291 'release-target-date' : releaseInfo . start ,
242- 'release-prp' : releaseInfo . prp ,
243- 'release-feature-freeze-date' : releaseInfo . feature_freeze ,
244- 'release-code-freeze-date' : releaseInfo . code_freeze ,
245- 'release-rc-target-date' : releaseInfo . release_candidate ,
292+ 'release-prp' : releaseInfo . prp || '' ,
293+ 'release-feature-freeze-date' : releaseInfo . feature_freeze || '' ,
294+ 'release-code-freeze-date' : releaseInfo . code_freeze || '' ,
295+ 'release-rc-target-date' : releaseInfo . release_candidate || '' ,
246296 }
247297 // Add a context variable for each issue url
248298 for ( const [ templateName , template ] of Object . entries ( releaseTemplates ) ) {
249- context [ `${ templateName } -url` ] = template . issue . html_url
299+ if ( template . issue ) {
300+ context [ `${ templateName } -url` ] = template . issue . html_url
301+ }
250302 }
251303
252304 // Create a context variable for each of the
253305 // 7 days before release-rc-target-date
254- const rcTargetDate = new Date ( releaseInfo . release_candidate ) . getTime ( )
255- for ( let i = 1 ; i <= 7 ; i ++ ) {
256- const day = i
257- const milliSecondsBefore = day * 24 * 60 * 60 * 1000
258- const rcDateBefore = new Date ( rcTargetDate - milliSecondsBefore ) . toISOString ( ) . slice ( 0 , 10 )
259- context [ `release-rc-target-date-minus-${ day } ` ] = rcDateBefore
306+ if ( releaseInfo . release_candidate ) {
307+ const rcTargetDate = new Date ( releaseInfo . release_candidate ) . getTime ( )
308+ for ( let i = 1 ; i <= 7 ; i ++ ) {
309+ const day = i
310+ const milliSecondsBefore = day * 24 * 60 * 60 * 1000
311+ const rcDateBefore = new Date ( rcTargetDate - milliSecondsBefore ) . toISOString ( ) . slice ( 0 , 10 )
312+ context [ `release-rc-target-date-minus-${ day } ` ] = rcDateBefore
313+ }
260314 }
261315 return context
262316}
263317
264- async function getRenderedTemplate ( templateContent , releaseTemplateContext ) {
318+ async function getRenderedTemplate (
319+ templateContent : string ,
320+ releaseTemplateContext : ReleaseTemplateContext ,
321+ ) {
265322 const { content, data } = matter ( templateContent )
266323 const title = await liquid . parseAndRender ( data . title , releaseTemplateContext )
267324 const body = await liquid . parseAndRender ( content , releaseTemplateContext )
268325 const labels = await Promise . all (
269- data . labels . map ( async ( label ) => await liquid . parseAndRender ( label , releaseTemplateContext ) ) ,
326+ data . labels . map (
327+ async ( label : string ) => await liquid . parseAndRender ( label , releaseTemplateContext ) ,
328+ ) ,
270329 )
271330
272331 return { title, body, labels }
273332}
274333
275- function getNumberDaysUntilMilestone ( milestoneDate ) {
334+ function getNumberDaysUntilMilestone ( milestoneDate : string ) : number {
276335 const today = new Date ( ) . toISOString ( ) . slice ( 0 , 10 )
277336 const nextMilestoneDateTime = new Date ( milestoneDate ) . getTime ( )
278337 const todayTime = new Date ( today ) . getTime ( )
@@ -281,7 +340,7 @@ function getNumberDaysUntilMilestone(milestoneDate) {
281340 return Math . floor ( differenceInMilliseconds / ( 1000 * 60 * 60 * 24 ) )
282341}
283342
284- function getNextReleaseNumber ( releaseDates ) {
343+ function getNextReleaseNumber ( releaseDates : ReleaseDates ) : string {
285344 const indexOfLatest = Object . keys ( releaseDates ) . indexOf ( latest )
286345 const indexOfNext = indexOfLatest + 1
287346 return Object . keys ( releaseDates ) [ indexOfNext ]
@@ -292,9 +351,9 @@ function getNextReleaseNumber(releaseDates) {
292351// labels: ['enterprise deprecation', 'ghes 3.0']
293352// titleMatch: 'GHES 3.0'
294353async function isExistingIssue (
295- repo ,
296- opts = { labels : undefined , searchQuery : undefined , titleMatch : undefined } ,
297- ) {
354+ repo : string ,
355+ opts : IssueSearchOpts = { labels : undefined , searchQuery : undefined , titleMatch : undefined } ,
356+ ) : Promise < boolean > {
298357 const { labels, searchQuery, titleMatch } = opts
299358 const labelQuery = labels && labels . map ( ( label ) => `label:"${ encodeURI ( label ) } "` ) . join ( '+' )
300359 let query = encodeURIComponent ( 'is:issue ' + `repo:${ repo } ` )
@@ -314,21 +373,21 @@ async function isExistingIssue(
314373 console . log ( `Issue ${ issue . html_url } already exists for this release.` )
315374 return true
316375 }
317- return
376+ return false
318377 }
319378 }
320379
321380 const issueExists = ! ! issues . data . items . length
322381 if ( issueExists ) {
323382 console . log (
324- `Issue ${ issues . data . items . map ( ( item ) => item . html_url ) } already exists for this release.` ,
383+ `Issue ${ issues . data . items . map ( ( item : { html_url : string } ) => item . html_url ) } already exists for this release.` ,
325384 )
326385 }
327386 return issueExists
328387}
329388
330- async function getReleaseDates ( ) {
331- let rawDates = [ ]
389+ async function getReleaseDates ( ) : Promise < ReleaseDates > {
390+ let rawDates : ReleaseDates = { }
332391 try {
333392 rawDates = JSON . parse (
334393 await getContents ( 'github' , 'enterprise-releases' , 'master' , 'releases.json' ) ,
0 commit comments