Files
labeler/src/labeler.ts
T

228 lines
6.0 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-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-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
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);
});
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
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) {
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
);
}
}
}
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 {
if (!Object.keys(matchConfig).length) {
2023-03-27 16:15:03 -04:00
core.debug(` no "any" or "all" patterns to check`);
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')) {
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)) {
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')) {
core.debug(` "any" patterns matched`);
2023-03-24 21:08:59 -04:00
return true;
}
}
}
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')) {
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) {
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)) {
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')) {
core.debug(` "all" patterns did not match`);
2023-03-24 21:08:59 -04:00
return false;
}
}
}
2023-03-27 16:52:42 -04:00
core.debug(` "all" patterns matched all configs`);
return true;
2021-06-03 16:15:47 -04:00
}