Files
labeler/src/labeler.ts
T

196 lines
5.2 KiB
TypeScript
Raw Normal View History

import * as core from '@actions/core';
import * as github from '@actions/github';
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;
type ClientType = ReturnType<typeof github.getOctokit>;
2021-06-03 16:15:47 -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);
for await (const pullRequest of api.getChangedPullRequests(
client,
prNumbers
)) {
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) {
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 {
if (typeof config === 'string') {
2021-06-03 16:15:47 -04:00
return {
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 {
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;
}