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-03-23 10:45:05 +01:00
import { Minimatch } from 'minimatch' ;
2023-08-02 06:13:14 +02:00
import * as api from './api' ;
import { isListEqual , printPattern } from './utils' ;
import { getInputs } from './get-inputs' ;
2021-06-03 16:15:47 -04:00
interface MatchConfig {
all? : string [];
any ?: string [];
}
type StringOrMatchConfig = string | MatchConfig ;
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-08-02 06:13:14 +02:00
export const run = () =>
labeler (). catch ( error => {
core . error ( error );
core . setFailed ( error . message );
});
async function labeler() {
const { token , configPath , syncLabels , dot , prNumbers } = getInputs ();
if ( ! prNumbers . length ) {
core . warning ( 'Could not get pull request number(s), exiting' );
return ;
}
const client : ClientType = github . getOctokit ( token , {}, pluginRetry . retry );
2023-09-25 15:28:47 +02:00
const pullRequests = api . getPullRequests ( client , prNumbers );
for await ( const pullRequest of pullRequests ) {
2023-08-02 06:13:14 +02:00
const labelGlobs : Map < string , StringOrMatchConfig [] > =
await api . getLabelGlobs ( client , configPath );
const preexistingLabels = pullRequest . data . labels . map ( l => l . name );
const allLabels : Set < string > = new Set < string >( preexistingLabels );
for ( const [ label , globs ] of labelGlobs . entries ()) {
core . debug ( `processing ${ label } ` );
if ( checkGlobs ( pullRequest . changedFiles , globs , dot )) {
allLabels . add ( label );
} else if ( syncLabels ) {
allLabels . delete ( label );
}
}
const labelsToAdd = [... allLabels ]. slice ( 0 , GITHUB_MAX_LABELS );
const excessLabels = [... allLabels ]. slice ( GITHUB_MAX_LABELS );
let newLabels : string [] = [];
try {
if ( ! isListEqual ( labelsToAdd , preexistingLabels )) {
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
);
}
}
}
function toMatchConfig ( config : StringOrMatchConfig ) : MatchConfig {
2022-12-19 17:09:22 +01:00
if ( typeof config === 'string' ) {
2021-06-03 16:15:47 -04:00
return {
2022-12-19 17:09:22 +01:00
any : [ config ]
2021-06-03 16:15:47 -04:00
};
}
return config ;
}
export function checkGlobs (
changedFiles : string [],
2022-02-04 13:26:54 +00:00
globs : StringOrMatchConfig [],
dot : boolean
2021-06-03 16:15:47 -04:00
) : boolean {
for ( const glob of globs ) {
core . debug ( ` checking pattern ${ JSON . stringify ( glob ) } ` );
const matchConfig = toMatchConfig ( glob );
2022-02-04 13:26:54 +00:00
if ( checkMatch ( changedFiles , matchConfig , dot )) {
2021-06-03 16:15:47 -04:00
return true ;
}
}
return false ;
}
2023-03-23 10:45:05 +01:00
function isMatch ( changedFile : string , matchers : Minimatch []) : boolean {
2021-06-03 16:15:47 -04:00
core . debug ( ` matching patterns against file ${ changedFile } ` );
for ( const matcher of matchers ) {
core . debug ( ` - ${ printPattern ( matcher ) } ` );
if ( ! matcher . match ( changedFile )) {
core . debug ( ` ${ printPattern ( matcher ) } did not match` );
return false ;
}
}
core . debug ( ` all patterns matched` );
return true ;
}
// equivalent to "Array.some()" but expanded for debugging and clarity
2022-02-04 13:26:54 +00:00
function checkAny (
changedFiles : string [],
globs : string [],
dot : boolean
) : boolean {
2023-01-16 17:36:24 +00:00
const matchers = globs . map ( g => new Minimatch ( g , { dot }));
2021-06-03 16:15:47 -04:00
core . debug ( ` checking "any" patterns` );
for ( const changedFile of changedFiles ) {
if ( isMatch ( changedFile , matchers )) {
core . debug ( ` "any" patterns matched against ${ changedFile } ` );
return true ;
}
}
core . debug ( ` "any" patterns did not match any files` );
return false ;
}
// equivalent to "Array.every()" but expanded for debugging and clarity
2022-02-04 13:26:54 +00:00
function checkAll (
changedFiles : string [],
globs : string [],
dot : boolean
) : boolean {
2023-05-31 01:44:56 +01:00
const matchers = globs . map ( g => new Minimatch ( g , { dot }));
2021-06-03 16:15:47 -04:00
core . debug ( ` checking "all" patterns` );
for ( const changedFile of changedFiles ) {
if ( ! isMatch ( changedFile , matchers )) {
core . debug ( ` "all" patterns did not match against ${ changedFile } ` );
return false ;
}
}
core . debug ( ` "all" patterns matched all files` );
return true ;
}
2022-02-04 13:26:54 +00:00
function checkMatch (
changedFiles : string [],
matchConfig : MatchConfig ,
dot : boolean
) : boolean {
2021-06-03 16:15:47 -04:00
if ( matchConfig . all !== undefined ) {
2022-02-04 13:26:54 +00:00
if ( ! checkAll ( changedFiles , matchConfig . all , dot )) {
2021-06-03 16:15:47 -04:00
return false ;
}
}
if ( matchConfig . any !== undefined ) {
2022-02-04 13:26:54 +00:00
if ( ! checkAny ( changedFiles , matchConfig . any , dot )) {
2021-06-03 16:15:47 -04:00
return false ;
}
}
return true ;
}