1- import { spawnSync } from 'node:child_process' ;
1+ import { extractFailureSnippet , extractRunIdFromUrl , isFailingCheck , parseAvailableFields } from './repair-harness/ghHelpers' ;
2+ import { fetchChecks , resolvePr , runGh } from './repair-harness/github' ;
23
3- type JsonRecord = Record < string , unknown > ;
4-
5- const FAILURE_CONCLUSIONS = new Set ( [ 'failure' , 'cancelled' , 'timed_out' , 'action_required' ] ) ;
6- const FAILURE_STATES = new Set ( [ 'failure' , 'error' , 'cancelled' , 'timed_out' , 'action_required' ] ) ;
7- const FAILURE_BUCKETS = new Set ( [ 'fail' ] ) ;
8- const FAILURE_MARKERS = [ 'error' , 'fail' , 'failed' , 'traceback' , 'exception' , 'assert' , 'panic' , 'fatal' , 'timeout' ] ;
9-
10- export function parseAvailableFields ( text : string ) : string [ ] {
11- const match = text . match ( / A v a i l a b l e f i e l d s : \s * ( [ \s \S ] + ) / i) ;
12- if ( ! match ) return [ ] ;
13- return match [ 1 ]
14- . split ( '\n' )
15- . map ( ( line ) => line . trim ( ) )
16- . filter ( ( line ) => / ^ [ a - z A - Z ] [ \w - ] * $ / . test ( line ) ) ;
17- }
18-
19- export function isFailingCheck ( check : JsonRecord ) : boolean {
20- const conclusion = String ( check . conclusion ?? '' ) . toLowerCase ( ) ;
21- if ( FAILURE_CONCLUSIONS . has ( conclusion ) ) return true ;
22- const state = String ( ( check . state ?? check . status ?? '' ) ) . toLowerCase ( ) ;
23- if ( FAILURE_STATES . has ( state ) ) return true ;
24- const bucket = String ( check . bucket ?? '' ) . toLowerCase ( ) ;
25- return FAILURE_BUCKETS . has ( bucket ) ;
26- }
27-
28- export function extractRunIdFromUrl ( url : string ) : string | null {
29- const match = url . match ( / \/ a c t i o n s \/ r u n s \/ ( \d + ) / ) ;
30- return match ?. [ 1 ] ?? null ;
31- }
32-
33- export function extractFailureSnippet ( logText : string , maxLines = 120 , context = 20 ) : string {
34- const lines = logText . split ( / \r ? \n / ) ;
35- const failureIndex = lines . findIndex ( ( line ) => {
36- const normalized = line . toLowerCase ( ) ;
37- return FAILURE_MARKERS . some ( ( marker ) => normalized . includes ( marker ) ) ;
38- } ) ;
39- if ( failureIndex < 0 ) {
40- return lines . slice ( - maxLines ) . join ( '\n' ) . trim ( ) ;
41- }
42- const start = Math . max ( 0 , failureIndex - context ) ;
43- const end = Math . min ( lines . length , start + maxLines ) ;
44- return lines . slice ( start , end ) . join ( '\n' ) . trim ( ) ;
45- }
4+ export { extractFailureSnippet , extractRunIdFromUrl , isFailingCheck , parseAvailableFields } from './repair-harness/ghHelpers' ;
465
47- function runGh ( args : string [ ] , options : { cwd ?: string ; allowFailure ?: boolean } = { } ) {
48- const result = spawnSync ( 'gh' , args , {
49- cwd : options . cwd ,
50- encoding : 'utf8' ,
51- } ) ;
52- if ( result . status !== 0 && ! options . allowFailure ) {
53- const message = [ result . stderr , result . stdout ] . filter ( Boolean ) . join ( '\n' ) . trim ( ) ;
54- throw new Error ( message || `gh ${ args . join ( ' ' ) } failed` ) ;
55- }
56- return result ;
57- }
6+ type JsonRecord = Record < string , unknown > ;
587
598function parseArgs ( argv : string [ ] ) {
609 const parsed : { pr ?: string ; json : boolean ; repo : string } = {
@@ -71,29 +20,6 @@ function parseArgs(argv: string[]) {
7120 return parsed ;
7221}
7322
74- function resolvePr ( pr : string | undefined , repo : string ) : string {
75- if ( pr ) return pr ;
76- const result = runGh ( [ 'pr' , 'view' , '--json' , 'number,url' ] , { cwd : repo } ) ;
77- const data = JSON . parse ( result . stdout ) as { number ?: number ; url ?: string } ;
78- if ( ! data . number ) throw new Error ( 'Unable to resolve current branch PR.' ) ;
79- return String ( data . number ) ;
80- }
81-
82- function fetchChecks ( pr : string , repo : string ) : JsonRecord [ ] {
83- const primaryFields = [ 'name' , 'state' , 'conclusion' , 'detailsUrl' , 'startedAt' , 'completedAt' ] ;
84- let result = runGh ( [ 'pr' , 'checks' , pr , '--json' , primaryFields . join ( ',' ) ] , { cwd : repo , allowFailure : true } ) ;
85- if ( result . status !== 0 ) {
86- const availableFields = parseAvailableFields ( `${ result . stderr } \n${ result . stdout } ` ) ;
87- const fallbackFields = [ 'name' , 'state' , 'bucket' , 'link' , 'workflow' , 'startedAt' , 'completedAt' ] ;
88- const selectedFields = fallbackFields . filter ( ( field ) => availableFields . includes ( field ) ) ;
89- if ( selectedFields . length === 0 ) {
90- throw new Error ( [ result . stderr , result . stdout ] . filter ( Boolean ) . join ( '\n' ) . trim ( ) || 'gh pr checks failed' ) ;
91- }
92- result = runGh ( [ 'pr' , 'checks' , pr , '--json' , selectedFields . join ( ',' ) ] , { cwd : repo } ) ;
93- }
94- return JSON . parse ( result . stdout ) as JsonRecord [ ] ;
95- }
96-
9723function inspectRun ( runId : string , repo : string ) {
9824 const metadataResult = runGh (
9925 [ 'run' , 'view' , runId , '--json' , 'name,workflowName,conclusion,status,url,event,headBranch,headSha' ] ,
0 commit comments