11import * as github from '@actions/github' ;
2+ import * as core from '@actions/core' ;
3+ import { args } from './input' ;
4+
5+ export const labelActions = [ 'add' , 'remove' , 'close' ] as const ;
6+
7+ type Element < T extends unknown [ ] > = T extends readonly ( infer ElementType ) [ ] ? ElementType : never ;
8+ export type Issue = Element < Awaited < ReturnType < typeof getIssues > > > ;
9+ export type Timeline = Awaited < ReturnType < typeof getIssueLabelTimeline > > ;
210
311export async function getIssues ( labels : string [ ] , token : string ) {
412 const octokit = github . getOctokit ( token ) ;
@@ -9,3 +17,80 @@ export async function getIssues(labels: string[], token: string) {
917 labels : labels . join ( ) ,
1018 } ) ;
1119}
20+
21+ export async function processIssues ( issues : Issue [ ] , args : args ) {
22+ issues . forEach ( async issue => {
23+ const timeline = await getIssueLabelTimeline ( issue . number , args . token ) ;
24+ // Enumerate labels in issue and check if each matches our action list
25+ issue . labels . forEach ( label => {
26+ const issueLabel = typeof label === 'string' ? label : label . name ;
27+ if ( issueLabel ) {
28+ if ( args . expirationLabelMap ) {
29+ // These are labels that we apply if an issue hasn't been updated in a specified timeframe
30+ args . expirationLabelMap . forEach ( async lam => {
31+ const sourceLabelList = lam . split ( ':' ) [ 0 ] . split ( ',' ) ;
32+ const configuredAction = lam . split ( ':' ) [ 1 ] ;
33+ const configuredTime = parseInt ( lam . split ( ':' ) [ 2 ] ) ;
34+
35+ if ( sourceLabelList . includes ( issueLabel ) && issueDateCompare ( issue . updated_at , configuredTime ) ) {
36+ // Issue contains label specified and configured time has elapsed
37+ switch ( configuredAction ) {
38+ case 'add' :
39+ await addLabelToIssue ( issue . number , lam . split ( ':' ) [ 3 ] ) ;
40+ break ;
41+ case 'remove' :
42+ await removeLabelFromIssue ( issue . number , lam . split ( ':' ) [ 3 ] ) ;
43+ break ;
44+ case 'close' :
45+ await closeIssue ( issue . number ) ;
46+ break ;
47+ default :
48+ core . error ( `Unknown action ${ configuredAction } for issue #${ issue . number } , doing nothing` ) ;
49+ }
50+ }
51+ } ) ;
52+ }
53+ if ( args . updateRemoveLabels ) {
54+ // These are labels that need removed if an issue has been updated after they were applied
55+ args . updateRemoveLabels . forEach ( async removeMe => {
56+ if ( Date . parse ( issue . updated_at ) > getIssueLabelDate ( timeline , removeMe ) ) {
57+ removeLabelFromIssue ( issue . number , removeMe ) ;
58+ }
59+ } ) ;
60+ }
61+ }
62+ } ) ;
63+ } ) ;
64+ }
65+
66+ async function getIssueLabelTimeline ( issueNumber : number , token : string ) {
67+ const octokit = github . getOctokit ( token ) ;
68+ return (
69+ await octokit . paginate ( octokit . rest . issues . listEventsForTimeline , {
70+ owner : github . context . repo . owner ,
71+ repo : github . context . repo . repo ,
72+ issue_number : issueNumber ,
73+ } )
74+ ) . filter ( event => event . event === 'labeled' ) ;
75+ }
76+
77+ function getIssueLabelDate ( timeline : Timeline , label : string ) {
78+ // Return when the label was last applied
79+ return timeline . reduce ( ( p , c ) => {
80+ if ( c . updated_at && c . label ?. name === label ) {
81+ if ( Date . parse ( c . updated_at ) > p ) {
82+ return Date . parse ( c . updated_at ) ;
83+ } else {
84+ return p ;
85+ }
86+ } else {
87+ return p ;
88+ }
89+ } , 0 ) ;
90+ }
91+
92+ function issueDateCompare ( issueDate : string , configuredDays : number ) {
93+ const d = new Date ( Date . parse ( issueDate ) ) ;
94+ d . setDate ( d . getDate ( ) + configuredDays ) ;
95+ return d . valueOf ( ) < Date . now ( ) ;
96+ }
0 commit comments