@@ -40,6 +40,7 @@ async function main() {
4040 const github = new Octokit ( { auth : token } ) ;
4141 for ( let repo of reposToBeChecked ) {
4242 await runLockClosedAction ( github , repo ) ;
43+ await closeStaleDraftPrs ( github , repo ) ;
4344 }
4445 } catch ( error : any ) {
4546 // TODO(josephperrott): properly set typings for error.
@@ -53,6 +54,129 @@ async function main() {
5354 }
5455}
5556
57+ async function closeStaleDraftPrs ( github : Octokit , repo : string ) : Promise < void > {
58+ const days = 28 ;
59+ const message =
60+ "This draft PR is being closed as has been stale for 4 weeks and has seen no activity from you. If you'd like to see this change land, please open a new pull request with these changes. Thank you for being an Angular contributor!" ;
61+
62+ const threshold = new Date ( ) ;
63+ threshold . setDate ( threshold . getDate ( ) - days ) ;
64+ const thresholdIso = threshold . toISOString ( ) ;
65+ const thresholdDateString = thresholdIso . split ( 'T' ) [ 0 ] ;
66+
67+ const repositoryName = `angular/${ repo } ` ;
68+ const query = `repo:${ repositoryName } +is:pr+is:draft+is:open+created:<${ thresholdDateString } +sort:updated-asc` ;
69+ console . info ( 'Stale Draft PR Query: ' + query ) ;
70+
71+ let closeCount = 0 ;
72+ const prResponse = await github . search . issuesAndPullRequests ( {
73+ q : query ,
74+ per_page : 100 ,
75+ } ) ;
76+
77+ console . info ( `Stale Draft PR Query found ${ prResponse . data . total_count } items` ) ;
78+
79+ if ( ! prResponse . data . items . length ) {
80+ console . info ( `No draft PRs to close` ) ;
81+ return ;
82+ }
83+
84+ console . info ( `Attempting to close up to ${ prResponse . data . items . length } draft PR(s)` ) ;
85+ core . startGroup ( 'Closing stale draft PRs' ) ;
86+
87+ for ( const item of prResponse . data . items ) {
88+ if ( ! item . pull_request ) continue ;
89+
90+ try {
91+ const pr = await github . pulls . get ( {
92+ owner : 'angular' ,
93+ repo,
94+ pull_number : item . number ,
95+ } ) ;
96+
97+ if ( ! pr . data . draft || pr . data . state !== 'open' ) {
98+ console . info ( `Skipping PR #${ item . number } , no longer an open draft` ) ;
99+ continue ;
100+ }
101+
102+ if ( new Date ( pr . data . updated_at ) < threshold ) {
103+ console . info (
104+ `PR #${ item . number } has had no activity from anyone since ${ pr . data . updated_at } , closing immediately.` ,
105+ ) ;
106+ } else {
107+ const author = pr . data . user . login ;
108+
109+ const commit = await github . repos . getCommit ( {
110+ owner : 'angular' ,
111+ repo,
112+ ref : pr . data . head . sha ,
113+ } ) ;
114+
115+ const commitDate = new Date (
116+ commit . data . commit . author ?. date || commit . data . commit . committer ?. date || 0 ,
117+ ) ;
118+ if ( commitDate > threshold ) {
119+ console . info ( `Skipping PR #${ item . number } , recent commit found from ${ commitDate } ` ) ;
120+ continue ;
121+ }
122+
123+ const comments = await github . issues . listComments ( {
124+ owner : 'angular' ,
125+ repo,
126+ issue_number : item . number ,
127+ since : thresholdIso ,
128+ per_page : 100 ,
129+ } ) ;
130+
131+ if ( comments . data . some ( ( c ) => c . user ?. login === author ) ) {
132+ console . info ( `Skipping PR #${ item . number } , recent issue comment found from author` ) ;
133+ continue ;
134+ }
135+
136+ const reviewComments = await github . pulls . listReviewComments ( {
137+ owner : 'angular' ,
138+ repo,
139+ pull_number : item . number ,
140+ since : thresholdIso ,
141+ per_page : 100 ,
142+ } ) ;
143+
144+ if ( reviewComments . data . some ( ( c ) => c . user ?. login === author ) ) {
145+ console . info ( `Skipping PR #${ item . number } , recent review comment found from author` ) ;
146+ continue ;
147+ }
148+ }
149+
150+ console . info ( `Closing stale draft PR #${ item . number } ` ) ;
151+
152+ await github . issues . createComment ( {
153+ owner : 'angular' ,
154+ repo,
155+ issue_number : item . number ,
156+ body : message ,
157+ } ) ;
158+
159+ await github . pulls . update ( {
160+ owner : 'angular' ,
161+ repo,
162+ pull_number : item . number ,
163+ state : 'closed' ,
164+ } ) ;
165+
166+ await setTimeoutPromise ( 250 ) ;
167+ ++ closeCount ;
168+ } catch ( error : any ) {
169+ core . warning ( `Unable to close draft PR angular/${ repo } #${ item . number } : ${ error . message } ` ) ;
170+ if ( typeof error . request === 'object' ) {
171+ core . error ( JSON . stringify ( error . request , null , 2 ) ) ;
172+ }
173+ }
174+ }
175+
176+ core . endGroup ( ) ;
177+ console . info ( `Closed ${ closeCount } stale draft PR(s)` ) ;
178+ }
179+
56180async function runLockClosedAction ( github : Octokit , repo : string ) : Promise < void > {
57181 // NOTE: `days` and `message` must not be changed without dev-rel and dev-infra concurrence
58182
0 commit comments