Files
2023-10-10 15:24:37 -04:00

344 lines
8.6 KiB
TypeScript

import * as github from "@actions/github";
import * as core from "@actions/core";
import { readFileSync } from "fs";
import { join } from "path";
import https from "node:https";
const context = github.context;
let octokit: ReturnType<typeof github.getOctokit>;
type ConfigType = {
LABEL: {
name: string;
color: string;
};
CHECKS: {
ignoreLabels: string[];
regexp: string;
regexpFlags: string;
prefixes: string[];
alwaysPassCI: boolean;
};
MESSAGES: {
success: string;
failure: string;
notice: string;
};
};
async function titleCheckFailed({
config: { MESSAGES, LABEL, CHECKS },
}: {
config: ConfigType;
}) {
try {
if (MESSAGES.notice.length) {
core.notice(MESSAGES.notice);
}
await addLabel({ name: LABEL.name });
if (CHECKS.alwaysPassCI) {
core.info(MESSAGES.failure);
} else {
core.setFailed(MESSAGES.failure);
}
} catch (error) {
core.info(error);
if (CHECKS.alwaysPassCI) {
core.info(`Failed to add label (${LABEL.name}) to PR`);
} else {
core.setFailed(`Failed to add label (${LABEL.name}) to PR`);
}
}
}
async function createLabel({
label: { name, color },
}: {
label: ConfigType["LABEL"];
}) {
if (name === "") {
return;
}
try {
core.info(`Creating label (${name})...`);
let createResponse = await octokit.rest.issues.createLabel({
owner: context.repo.owner,
repo: context.repo.repo,
name: name,
color: color,
});
core.info(`Created label (${name}) - ${createResponse.status}`);
} catch (error) {
// Might not always be due to label's existence
core.info(`Label (${name}) already created.`);
}
}
async function addLabel({ name }: { name: string }) {
if (name === "") {
return;
}
core.info(`Adding label (${name}) to PR...`);
const addLabelResponse = await octokit.rest.issues.addLabels({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: context.issue.number,
labels: [name],
});
core.info(`Added label (${name}) to PR - ${addLabelResponse.status}`);
}
async function removeLabel({
labels,
name,
}: {
name: string;
labels: ConfigType["LABEL"][];
}) {
if (name === "") {
return;
}
try {
if (
!labels
.map((label) => label.name.toLowerCase())
.includes(name.toLowerCase())
) {
return;
}
core.info("No formatting necessary. Removing label...");
let removeLabelResponse = await octokit.rest.issues.removeLabel({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: context.issue.number,
name: name,
});
core.info(`Removed label - ${removeLabelResponse.status}`);
} catch (error) {
core.info(`Failed to remove label (${name}) from PR: ${error}`);
}
}
function downloadJSON(url: URL): Promise<ConfigType> {
return new Promise<ConfigType>((resolve, reject) => {
const req = https.request(url, (res) => {
let responseBody = "";
res.on("data", (d) => {
responseBody += d;
});
res.on("end", () => {
resolve(JSON.parse(responseBody.toString()));
});
});
req.on("error", (e) => {
reject(e);
});
req.end();
});
}
async function getJSON({
configPath,
localConfigPath,
remoteConfigURL,
GitHubConfigOwner,
GitHubConfigRepo,
GitHubConfigPath,
GitHubConfigRef,
GitHubConfigToken,
}: {
configPath: string;
localConfigPath?: string;
remoteConfigURL?: string;
GitHubConfigOwner?: string;
GitHubConfigRepo?: string;
GitHubConfigPath?: string;
GitHubConfigRef?: string;
GitHubConfigToken?: string;
}): Promise<ConfigType> {
if (localConfigPath) {
core.info(`Using local config file ${localConfigPath}`);
const data = readFileSync(join(process.cwd(), localConfigPath));
return JSON.parse(data.toString());
}
if (remoteConfigURL) {
core.info(`Using remote config file ${remoteConfigURL}`);
const url = new URL(remoteConfigURL);
return await downloadJSON(url);
}
const is_same_repo =
(!GitHubConfigOwner || GitHubConfigOwner === context.repo.owner) &&
(!GitHubConfigRepo || GitHubConfigRepo === context.repo.repo);
core.info(
`Using config file ${GitHubConfigPath || configPath} from repo ${
GitHubConfigOwner || context.repo.owner
}/${GitHubConfigRepo || context.repo.repo} [ref: ${
GitHubConfigRef ||
(is_same_repo ? context.sha : "latest commit on the default branch")
}]`
);
let _octokit = octokit;
if (GitHubConfigToken) {
_octokit = github.getOctokit(GitHubConfigToken);
}
const response = await _octokit.rest.repos.getContent({
owner: GitHubConfigOwner || context.repo.owner,
repo: GitHubConfigRepo || context.repo.repo,
path: GitHubConfigPath || configPath,
ref: GitHubConfigRef || (is_same_repo ? context.sha : undefined),
});
return JSON.parse(
Buffer.from(
(response.data as any).content,
(response.data as any).encoding
).toString()
);
}
async function handleOctokitError({
passOnOctokitError,
error,
}: {
passOnOctokitError: boolean;
error: Error;
}) {
core.info(`Octokit Error - ${error}`);
if (passOnOctokitError) {
core.info("Passing CI regardless");
} else {
core.setFailed("Failing CI test");
}
}
const run = async ({
configPath,
localConfigPath,
remoteConfigURL,
GitHubConfigOwner,
GitHubConfigRepo,
GitHubConfigPath,
GitHubConfigRef,
GitHubConfigToken,
}: {
configPath: string;
localConfigPath: string;
remoteConfigURL?: string;
GitHubConfigOwner?: string;
GitHubConfigRepo?: string;
GitHubConfigPath?: string;
GitHubConfigRef?: string;
GitHubConfigToken?: string;
}) => {
if (!context || !context.payload || !context.payload.pull_request) {
return;
}
try {
const title = context.payload.pull_request.title;
const labels = context.payload.pull_request.labels;
let config: ConfigType;
try {
config = await getJSON({
configPath,
localConfigPath,
remoteConfigURL,
GitHubConfigOwner,
GitHubConfigRepo,
GitHubConfigPath,
GitHubConfigRef,
GitHubConfigToken,
});
} catch (e) {
core.setFailed(
`Couldn't retrieve or parse the config file specified - ${e}`
);
return;
}
let { CHECKS, LABEL, MESSAGES } = config;
LABEL = LABEL || ({} as ConfigType["LABEL"]);
LABEL.name = LABEL.name || "";
LABEL.color = LABEL.color || "eee";
CHECKS.ignoreLabels = CHECKS.ignoreLabels || [];
MESSAGES = MESSAGES || ({} as ConfigType["MESSAGES"]);
MESSAGES.success = MESSAGES.success || "All OK";
MESSAGES.failure = MESSAGES.failure || "Failing CI test";
MESSAGES.notice = MESSAGES.notice || "";
for (let i = 0; i < labels.length; i++) {
for (let j = 0; j < CHECKS.ignoreLabels.length; j++) {
if (labels[i].name == CHECKS.ignoreLabels[j]) {
core.info(`Ignoring Title Check for label - ${labels[i].name}`);
removeLabel({ labels, name: LABEL.name });
return;
}
}
}
await createLabel({ label: LABEL });
if (CHECKS.prefixes && CHECKS.prefixes.length) {
for (let i = 0; i < CHECKS.prefixes.length; i++) {
if (title.startsWith(CHECKS.prefixes[i])) {
removeLabel({ labels, name: LABEL.name });
core.info(MESSAGES.success);
return;
}
}
}
if (CHECKS.regexp) {
let re = new RegExp(CHECKS.regexp, CHECKS.regexpFlags || "");
if (re.test(title)) {
removeLabel({ labels, name: LABEL.name });
core.info(MESSAGES.success);
return;
}
}
await titleCheckFailed({ config: { LABEL, CHECKS, MESSAGES } });
} catch (error) {
core.info(error);
}
};
const configPath = core.getInput("configuration_path")!;
const localConfigPath = core.getInput("local_configuration_path");
const remoteConfigURL = core.getInput("remote_configuration_url");
const GitHubConfigOwner = core.getInput("github_configuration_owner");
const GitHubConfigRepo = core.getInput("github_configuration_repo");
const GitHubConfigPath = core.getInput("github_configuration_path");
const GitHubConfigRef = core.getInput("github_configuration_ref");
const GitHubConfigToken = core.getInput("github_configuration_token");
const passOnOctokitError = core.getInput("pass_on_octokit_error") === "true";
const token = core.getInput("GITHUB_TOKEN");
try {
octokit = github.getOctokit(token);
run({
configPath,
localConfigPath,
remoteConfigURL,
GitHubConfigOwner,
GitHubConfigRepo,
GitHubConfigPath,
GitHubConfigRef,
GitHubConfigToken,
});
} catch (e) {
handleOctokitError({ passOnOctokitError, error: e });
}