@@ -11,14 +11,27 @@ import { commitExists, git, emptyTreeName, revParse } from "./git.js";
11
11
import { GitNotes } from "./git-notes.js" ;
12
12
import { GitGitGadget , IGitGitGadgetOptions } from "./gitgitgadget.js" ;
13
13
import { getConfig } from "./gitgitgadget-config.js" ;
14
- import { GitHubGlue , IGitHubUser , IPRComment , IPRCommit , IPullRequestInfo , RequestError } from "./github-glue.js" ;
14
+ import {
15
+ ConclusionType ,
16
+ GitHubGlue ,
17
+ IGitHubUser ,
18
+ IPRComment ,
19
+ IPRCommit ,
20
+ IPullRequestInfo ,
21
+ RequestError ,
22
+ } from "./github-glue.js" ;
15
23
import { toPrettyJSON } from "./json-util.js" ;
16
24
import { MailArchiveGitHelper } from "./mail-archive-helper.js" ;
17
25
import { MailCommitMapping } from "./mail-commit-mapping.js" ;
18
26
import { IMailMetadata } from "./mail-metadata.js" ;
19
27
import { IPatchSeriesMetadata } from "./patch-series-metadata.js" ;
20
28
import { IConfig , getExternalConfig , setConfig } from "./project-config.js" ;
21
- import { getPullRequestKeyFromURL , pullRequestKey } from "./pullRequestKey.js" ;
29
+ import {
30
+ getPullRequestCommentKeyFromURL ,
31
+ getPullRequestKeyFromURL ,
32
+ getPullRequestOrCommentKeyFromURL ,
33
+ pullRequestKey ,
34
+ } from "./pullRequestKey.js" ;
22
35
import { ISMTPOptions } from "./send-mail.js" ;
23
36
import { fileURLToPath } from "url" ;
24
37
@@ -89,24 +102,8 @@ export class CIHelper {
89
102
needsMailingListMirror ?: boolean ;
90
103
needsUpstreamBranches ?: boolean ;
91
104
needsMailToCommitNotes ?: boolean ;
105
+ createOrUpdateCheckRun ?: boolean | "post" ;
92
106
} ) : Promise < void > {
93
- // help dugite realize where `git` is...
94
- const gitExecutable = os . type ( ) === "Windows_NT" ? "git.exe" : "git" ;
95
- const stripSuffix = `bin${ path . sep } ${ gitExecutable } ` ;
96
- for ( const gitPath of ( process . env . PATH || "/" )
97
- . split ( path . delimiter )
98
- . map ( ( p ) => path . normalize ( `${ p } ${ path . sep } ${ gitExecutable } ` ) )
99
- // eslint-disable-next-line security/detect-non-literal-fs-filename
100
- . filter ( ( p ) => p . endsWith ( `${ path . sep } ${ stripSuffix } ` ) && fs . existsSync ( p ) ) ) {
101
- process . env . LOCAL_GIT_DIRECTORY = gitPath . substring ( 0 , gitPath . length - stripSuffix . length ) ;
102
- // need to override GIT_EXEC_PATH, so that Dugite can find the `git-remote-https` executable,
103
- // see https://github.com/desktop/dugite/blob/v2.7.1/lib/git-environment.ts#L44-L64
104
- // Also: We cannot use `await git(["--exec-path"]);` because that would use Dugite, which would
105
- // override `GIT_EXEC_PATH` and then `git --exec-path` would report _that_...
106
- process . env . GIT_EXEC_PATH = spawnSync ( gitPath , [ "--exec-path" ] ) . stdout . toString ( "utf-8" ) . trimEnd ( ) ;
107
- break ;
108
- }
109
-
110
107
// configure the Git committer information
111
108
process . env . GIT_CONFIG_PARAMETERS = [
112
109
process . env . GIT_CONFIG_PARAMETERS ,
@@ -138,6 +135,27 @@ export class CIHelper {
138
135
// Ignore, for now
139
136
}
140
137
138
+ if ( setupOptions ?. createOrUpdateCheckRun ) {
139
+ return await this . createOrUpdateCheckRun ( setupOptions . createOrUpdateCheckRun === "post" ) ;
140
+ }
141
+
142
+ // help dugite realize where `git` is...
143
+ const gitExecutable = os . type ( ) === "Windows_NT" ? "git.exe" : "git" ;
144
+ const stripSuffix = `bin${ path . sep } ${ gitExecutable } ` ;
145
+ for ( const gitPath of ( process . env . PATH || "/" )
146
+ . split ( path . delimiter )
147
+ . map ( ( p ) => path . normalize ( `${ p } ${ path . sep } ${ gitExecutable } ` ) )
148
+ // eslint-disable-next-line security/detect-non-literal-fs-filename
149
+ . filter ( ( p ) => p . endsWith ( `${ path . sep } ${ stripSuffix } ` ) && fs . existsSync ( p ) ) ) {
150
+ process . env . LOCAL_GIT_DIRECTORY = gitPath . substring ( 0 , gitPath . length - stripSuffix . length ) ;
151
+ // need to override GIT_EXEC_PATH, so that Dugite can find the `git-remote-https` executable,
152
+ // see https://github.com/desktop/dugite/blob/v2.7.1/lib/git-environment.ts#L44-L64
153
+ // Also: We cannot use `await git(["--exec-path"]);` because that would use Dugite, which would
154
+ // override `GIT_EXEC_PATH` and then `git --exec-path` would report _that_...
155
+ process . env . GIT_EXEC_PATH = spawnSync ( gitPath , [ "--exec-path" ] ) . stdout . toString ( "utf-8" ) . trimEnd ( ) ;
156
+ break ;
157
+ }
158
+
141
159
// eslint-disable-next-line security/detect-non-literal-fs-filename
142
160
if ( ! fs . existsSync ( this . workDir ) ) await git ( [ "init" , "--bare" , "--initial-branch" , "unused" , this . workDir ] ) ;
143
161
for ( const [ key , value ] of [
@@ -269,24 +287,79 @@ export class CIHelper {
269
287
}
270
288
}
271
289
272
- public parsePRCommentURLInput ( ) : { owner : string ; repo : string ; prNumber : number ; commentId : number } {
273
- const prCommentUrl = core . getInput ( "pr-comment-url" ) ;
274
- const [ , owner , repo , prNumber , commentId ] =
275
- prCommentUrl . match ( / ^ h t t p s : \/ \/ g i t h u b \. c o m \/ ( [ ^ / ] + ) \/ ( [ ^ / ] + ) \/ p u l l \/ ( \d + ) # i s s u e c o m m e n t - ( \d + ) $ / ) || [ ] ;
276
- if ( ! this . config . app . installedOn . includes ( owner ) || repo !== this . config . repo . name ) {
277
- throw new Error ( `Invalid PR comment URL: ${ prCommentUrl } ` ) ;
290
+ protected async createOrUpdateCheckRun ( runPost : boolean ) : Promise < void > {
291
+ type CheckRunParameters = {
292
+ owner : string ;
293
+ repo : string ;
294
+ pull_number : number ;
295
+ check_run_id ?: number ;
296
+ name : string ;
297
+ output ?: {
298
+ title : string ;
299
+ summary : string ;
300
+ text ?: string ;
301
+ } ;
302
+ details_url ?: string ;
303
+ conclusion ?: ConclusionType ;
304
+ job_status ?: ConclusionType ;
305
+ } ;
306
+ const params = JSON . parse ( core . getState ( "check-run" ) || "{}" ) as CheckRunParameters ;
307
+
308
+ const validateCheckRunParameters = ( ) => {
309
+ const result = typia . createValidate < CheckRunParameters > ( ) ( params ) ;
310
+ if ( ! result . success ) {
311
+ throw new Error (
312
+ `Invalid check-run state:\n- ${ result . errors
313
+ . map ( ( e ) => `${ e . path } (value: ${ e . value } , expected: ${ e . expected } ): ${ e . description } ` )
314
+ . join ( "\n- " ) } `,
315
+ ) ;
316
+ }
317
+ } ;
318
+ if ( Object . keys ( params ) . length ) validateCheckRunParameters ( ) ;
319
+
320
+ [ "pr-url" , "check-run-id" , "name" , "title" , "summary" , "text" , "details-url" , "conclusion" , "job-status" ]
321
+ . map ( ( name ) => [ name . replaceAll ( "-" , "_" ) , core . getInput ( name ) ] as const )
322
+ . forEach ( ( [ key , value ] ) => {
323
+ if ( ! value ) return ;
324
+ if ( key === "pr_url" ) Object . assign ( params , getPullRequestOrCommentKeyFromURL ( value ) ) ;
325
+ else if ( key === "check_run_id" ) params . check_run_id = Number . parseInt ( value , 10 ) ;
326
+ else if ( key === "title" || key === "summary" || key === "text" ) {
327
+ if ( ! params . output ) Object . assign ( params , { output : { } } ) ;
328
+ ( params . output as { [ key : string ] : string } ) [ key ] = value ;
329
+ } else ( params as unknown as { [ key : string ] : string } ) [ key ] = value ;
330
+ } ) ;
331
+ validateCheckRunParameters ( ) ;
332
+
333
+ if ( runPost ) {
334
+ if ( ! params . check_run_id ) {
335
+ core . info ( "No Check Run ID found in state; doing nothing" ) ;
336
+ return ;
337
+ }
338
+ if ( ! params . conclusion ) {
339
+ Object . assign ( params , { conclusion : params . job_status } ) ;
340
+ validateCheckRunParameters ( ) ;
341
+ }
342
+ }
343
+
344
+ if ( params . check_run_id === undefined ) {
345
+ ( { id : params . check_run_id } = await this . github . createCheckRun ( params ) ) ;
346
+ core . setOutput ( "check-run-id" , params . check_run_id ) ;
347
+ } else {
348
+ await this . github . updateCheckRun ( {
349
+ ...params ,
350
+ // needed to pacify TypeScript's concerns about the ID being potentially undefined
351
+ check_run_id : params . check_run_id ,
352
+ } ) ;
278
353
}
279
- return { owner , repo , prNumber : parseInt ( prNumber , 10 ) , commentId : parseInt ( commentId , 10 ) } ;
354
+ core . exportVariable ( "STATE_check-run" , JSON . stringify ( params ) ) ;
280
355
}
281
356
282
- public parsePRURLInput ( ) : { owner : string ; repo : string ; prNumber : number } {
283
- const prUrl = core . getInput ( "pr-url" ) ;
357
+ public parsePRCommentURLInput ( ) : { owner : string ; repo : string ; pull_number : number ; comment_id : number } {
358
+ return getPullRequestCommentKeyFromURL ( core . getInput ( "pr-comment-url" ) ) ;
359
+ }
284
360
285
- const [ , owner , repo , prNumber ] = prUrl . match ( / ^ h t t p s : \/ \/ g i t h u b \. c o m \/ ( [ ^ / ] + ) \/ ( [ ^ / ] + ) \/ p u l l \/ ( \d + ) $ / ) || [ ] ;
286
- if ( ! this . config . app . installedOn . includes ( owner ) || repo !== this . config . repo . name ) {
287
- throw new Error ( `Invalid PR URL: ${ prUrl } ` ) ;
288
- }
289
- return { owner, repo, prNumber : parseInt ( prNumber , 10 ) } ;
361
+ public parsePRURLInput ( ) : { owner : string ; repo : string ; pull_number : number } {
362
+ return getPullRequestCommentKeyFromURL ( core . getInput ( "pr-url" ) ) ;
290
363
}
291
364
292
365
public setAccessToken ( repositoryOwner : string , token : string ) : void {
0 commit comments