@@ -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,121 @@ 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:created-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+ const author = pr . data . user . login ;
103+
104+ const commit = await github . repos . getCommit ( {
105+ owner : 'angular' ,
106+ repo,
107+ ref : pr . data . head . sha ,
108+ } ) ;
109+
110+ const commitDate = new Date (
111+ commit . data . commit . author ?. date || commit . data . commit . committer ?. date || 0 ,
112+ ) ;
113+ if ( commitDate > threshold ) {
114+ console . info ( `Skipping PR #${ item . number } , recent commit found from ${ commitDate } ` ) ;
115+ continue ;
116+ }
117+
118+ const comments = await github . issues . listComments ( {
119+ owner : 'angular' ,
120+ repo,
121+ issue_number : item . number ,
122+ since : thresholdIso ,
123+ } ) ;
124+
125+ if ( comments . data . some ( ( c ) => c . user ?. login === author ) ) {
126+ console . info ( `Skipping PR #${ item . number } , recent issue comment found from author` ) ;
127+ continue ;
128+ }
129+
130+ const reviewComments = await github . pulls . listReviewComments ( {
131+ owner : 'angular' ,
132+ repo,
133+ pull_number : item . number ,
134+ since : thresholdIso ,
135+ } ) ;
136+
137+ if ( reviewComments . data . some ( ( c ) => c . user ?. login === author ) ) {
138+ console . info ( `Skipping PR #${ item . number } , recent review comment found from author` ) ;
139+ continue ;
140+ }
141+
142+ console . info ( `Closing stale draft PR #${ item . number } ` ) ;
143+
144+ await github . issues . createComment ( {
145+ owner : 'angular' ,
146+ repo,
147+ issue_number : item . number ,
148+ body : message ,
149+ } ) ;
150+
151+ await github . pulls . update ( {
152+ owner : 'angular' ,
153+ repo,
154+ pull_number : item . number ,
155+ state : 'closed' ,
156+ } ) ;
157+
158+ await setTimeoutPromise ( 250 ) ;
159+ ++ closeCount ;
160+ } catch ( error : any ) {
161+ core . warning ( `Unable to close draft PR angular/${ repo } #${ item . number } : ${ error . message } ` ) ;
162+ if ( typeof error . request === 'object' ) {
163+ core . error ( JSON . stringify ( error . request , null , 2 ) ) ;
164+ }
165+ }
166+ }
167+
168+ core . endGroup ( ) ;
169+ console . info ( `Closed ${ closeCount } stale draft PR(s)` ) ;
170+ }
171+
56172async function runLockClosedAction ( github : Octokit , repo : string ) : Promise < void > {
57173 // NOTE: `days` and `message` must not be changed without dev-rel and dev-infra concurrence
58174
0 commit comments