2022-12-19 17:09:22 +01:00
import * as core from '@actions/core' ;
import * as github from '@actions/github' ;
2023-06-21 05:49:43 -04:00
import * as pluginRetry from '@octokit/plugin-retry' ;
2023-08-02 06:13:14 +02:00
import * as api from './api' ;
2023-10-04 11:23:34 +02:00
import isEqual from 'lodash.isequal' ;
2023-08-02 06:13:14 +02:00
import { getInputs } from './get-inputs' ;
2021-06-03 16:15:47 -04:00
2023-10-24 12:11:31 +02:00
import { BaseMatchConfig , MatchConfig } from './api/get-label-configs' ;
2023-01-28 15:58:00 -05:00
2023-10-24 12:11:31 +02:00
import { checkAllChangedFiles , checkAnyChangedFiles } from './changedFiles' ;
2023-03-24 21:08:59 -04:00
2023-10-24 12:11:31 +02:00
import { checkAnyBranch , checkAllBranch } from './branch' ;
2021-06-03 16:15:47 -04:00
2021-06-21 11:39:52 -04:00
type ClientType = ReturnType < typeof github.getOctokit >;
2021-06-03 16:15:47 -04:00
2023-06-21 05:49:43 -04:00
// GitHub Issues cannot have more than 100 labels
const GITHUB_MAX_LABELS = 100 ;
2023-05-11 16:37:58 -04:00
2023-08-02 06:13:14 +02:00
export const run = () =>
labeler (). catch ( error => {
core . error ( error );
core . setFailed ( error . message );
});
2021-06-03 16:15:47 -04:00
2023-08-02 06:13:14 +02:00
async function labeler() {
const { token , configPath , syncLabels , dot , prNumbers } = getInputs ();
2021-06-03 16:15:47 -04:00
2023-08-02 06:13:14 +02:00
if ( ! prNumbers . length ) {
core . warning ( 'Could not get pull request number(s), exiting' );
return ;
}
2021-06-03 16:15:47 -04:00
2023-08-02 06:13:14 +02:00
const client : ClientType = github . getOctokit ( token , {}, pluginRetry . retry );
2021-06-03 16:15:47 -04:00
2023-09-25 15:28:47 +02:00
const pullRequests = api . getPullRequests ( client , prNumbers );
for await ( const pullRequest of pullRequests ) {
2023-10-24 12:11:31 +02:00
const labelConfigs : Map < string , MatchConfig [] > = await api . getLabelConfigs (
2021-06-03 16:15:47 -04:00
client ,
configPath
);
2023-08-02 06:13:14 +02:00
const preexistingLabels = pullRequest . data . labels . map ( l => l . name );
const allLabels : Set < string > = new Set < string >( preexistingLabels );
2021-06-03 16:15:47 -04:00
2023-02-19 16:46:14 -05:00
for ( const [ label , configs ] of labelConfigs . entries ()) {
2021-06-03 16:15:47 -04:00
core . debug ( `processing ${ label } ` );
2023-10-24 12:11:31 +02:00
if ( checkMatchConfigs ( pullRequest . changedFiles , configs , dot )) {
2023-08-02 06:13:14 +02:00
allLabels . add ( label );
} else if ( syncLabels ) {
allLabels . delete ( label );
2021-06-03 16:15:47 -04:00
}
}
2023-08-02 06:13:14 +02:00
const labelsToAdd = [... allLabels ]. slice ( 0 , GITHUB_MAX_LABELS );
const excessLabels = [... allLabels ]. slice ( GITHUB_MAX_LABELS );
let newLabels : string [] = [];
try {
2023-10-04 11:23:34 +02:00
if ( ! isEqual ( labelsToAdd , preexistingLabels )) {
2023-08-02 06:13:14 +02:00
await api . setLabels ( client , pullRequest . number , labelsToAdd );
newLabels = labelsToAdd . filter (
label => ! preexistingLabels . includes ( label )
);
}
} catch ( error : any ) {
if (
error . name !== 'HttpError' ||
error . message !== 'Resource not accessible by integration'
) {
throw error ;
}
core . warning (
`The action requires write permission to add labels to pull requests. For more information please refer to the action documentation: https://github.com/actions/labeler#permissions` ,
{
title : ` ${ process . env [ 'GITHUB_ACTION_REPOSITORY' ] } running under ' ${ github . context . eventName } ' is misconfigured`
}
);
core . setFailed ( error . message );
2021-06-03 16:15:47 -04:00
return ;
}
2023-08-02 06:13:14 +02:00
core . setOutput ( 'new-labels' , newLabels . join ( ',' ));
core . setOutput ( 'all-labels' , labelsToAdd . join ( ',' ));
2021-06-03 16:15:47 -04:00
2023-08-02 06:13:14 +02:00
if ( excessLabels . length ) {
2023-06-30 12:09:04 +02:00
core . warning (
2023-08-02 06:13:14 +02:00
`Maximum of ${ GITHUB_MAX_LABELS } labels allowed. Excess labels: ${ excessLabels . join (
', '
) } ` ,
{ title : 'Label limit for a PR exceeded' }
2021-06-03 16:15:47 -04:00
);
}
}
}
2023-03-18 15:15:42 -04:00
export function checkMatchConfigs (
2021-06-03 16:15:47 -04:00
changedFiles : string [],
2023-10-24 12:11:31 +02:00
matchConfigs : MatchConfig [],
2022-02-04 13:26:54 +00:00
dot : boolean
2021-06-03 16:15:47 -04:00
) : boolean {
2023-03-23 09:08:08 -04:00
for ( const config of matchConfigs ) {
core . debug ( ` checking config ${ JSON . stringify ( config ) } ` );
2023-10-24 12:11:31 +02:00
if ( ! checkMatch ( changedFiles , config , dot )) {
2021-06-03 16:15:47 -04:00
return false ;
}
}
return true ;
}
2022-02-04 13:26:54 +00:00
function checkMatch (
changedFiles : string [],
matchConfig : MatchConfig ,
dot : boolean
) : boolean {
2023-03-20 17:39:57 -04:00
if ( ! Object . keys ( matchConfig ). length ) {
2023-03-27 16:15:03 -04:00
core . debug ( ` no "any" or "all" patterns to check` );
2023-03-20 17:39:57 -04:00
return false ;
}
2023-03-24 21:08:59 -04:00
if ( matchConfig . all ) {
2023-10-24 12:11:31 +02:00
if ( ! checkAll ( matchConfig . all , changedFiles , dot )) {
2021-06-03 16:15:47 -04:00
return false ;
}
}
2023-03-24 21:08:59 -04:00
if ( matchConfig . any ) {
2023-10-24 12:11:31 +02:00
if ( ! checkAny ( matchConfig . any , changedFiles , dot )) {
2021-06-03 16:15:47 -04:00
return false ;
}
}
return true ;
}
2023-03-24 21:08:59 -04:00
// equivalent to "Array.some()" but expanded for debugging and clarity
export function checkAny (
matchConfigs : BaseMatchConfig [],
2023-10-24 12:11:31 +02:00
changedFiles : string [],
dot : boolean
2023-03-24 21:08:59 -04:00
) : boolean {
core . debug ( ` checking "any" patterns` );
2023-05-17 07:05:19 -04:00
if (
! matchConfigs . length ||
! matchConfigs . some ( configOption => Object . keys ( configOption ). length )
) {
2023-03-24 22:29:41 -04:00
core . debug ( ` no "any" patterns to check` );
return false ;
}
2023-03-24 21:08:59 -04:00
for ( const matchConfig of matchConfigs ) {
if ( matchConfig . baseBranch ) {
2023-03-24 21:44:26 -04:00
if ( checkAnyBranch ( matchConfig . baseBranch , 'base' )) {
2023-09-20 13:43:39 +02:00
core . debug ( ` "any" patterns matched` );
2023-03-24 21:08:59 -04:00
return true ;
}
}
2023-03-24 21:44:26 -04:00
if ( matchConfig . changedFiles ) {
2023-10-24 12:11:31 +02:00
if ( checkAnyChangedFiles ( changedFiles , matchConfig . changedFiles , dot )) {
2023-09-20 13:43:39 +02:00
core . debug ( ` "any" patterns matched` );
2023-03-24 21:44:26 -04:00
return true ;
}
}
2023-03-24 21:08:59 -04:00
if ( matchConfig . headBranch ) {
2023-03-24 21:44:26 -04:00
if ( checkAnyBranch ( matchConfig . headBranch , 'head' )) {
2023-09-20 13:43:39 +02:00
core . debug ( ` "any" patterns matched` );
2023-03-24 21:08:59 -04:00
return true ;
}
2023-01-28 17:53:41 -05:00
}
}
2023-03-24 21:08:59 -04:00
core . debug ( ` "any" patterns did not match any configs` );
return false ;
}
// equivalent to "Array.every()" but expanded for debugging and clarity
export function checkAll (
matchConfigs : BaseMatchConfig [],
2023-10-24 12:11:31 +02:00
changedFiles : string [],
dot : boolean
2023-03-24 21:08:59 -04:00
) : boolean {
2023-03-27 16:52:42 -04:00
core . debug ( ` checking "all" patterns` );
2023-05-15 10:45:56 -04:00
if (
! matchConfigs . length ||
2023-05-17 07:05:19 -04:00
! matchConfigs . some ( configOption => Object . keys ( configOption ). length )
2023-05-15 10:45:56 -04:00
) {
2023-03-24 22:29:41 -04:00
core . debug ( ` no "all" patterns to check` );
return false ;
}
2023-03-24 21:08:59 -04:00
for ( const matchConfig of matchConfigs ) {
if ( matchConfig . baseBranch ) {
2023-03-24 21:44:26 -04:00
if ( ! checkAllBranch ( matchConfig . baseBranch , 'base' )) {
2023-09-20 13:43:39 +02:00
core . debug ( ` "all" patterns did not match` );
2023-03-24 21:08:59 -04:00
return false ;
}
}
2023-03-24 21:44:26 -04:00
if ( matchConfig . changedFiles ) {
2023-05-11 17:02:05 -04:00
if ( ! changedFiles . length ) {
core . debug ( ` no files to check "changed-files" patterns against` );
return false ;
}
2023-10-24 12:11:31 +02:00
if ( ! checkAllChangedFiles ( changedFiles , matchConfig . changedFiles , dot )) {
2023-09-20 13:43:39 +02:00
core . debug ( ` "all" patterns did not match` );
2023-03-25 17:42:17 -04:00
return false ;
2023-03-24 21:44:26 -04:00
}
}
2023-03-24 21:08:59 -04:00
if ( matchConfig . headBranch ) {
2023-03-24 21:44:26 -04:00
if ( ! checkAllBranch ( matchConfig . headBranch , 'head' )) {
2023-09-20 13:43:39 +02:00
core . debug ( ` "all" patterns did not match` );
2023-03-24 21:08:59 -04:00
return false ;
}
2023-01-28 17:53:41 -05:00
}
}
2023-03-27 16:52:42 -04:00
core . debug ( ` "all" patterns matched all configs` );
2023-03-15 05:52:04 -04:00
return true ;
2021-06-03 16:15:47 -04:00
}