From dfad8be704753a467ff3c51bef14c0c277a6d56b Mon Sep 17 00:00:00 2001 From: Ben Challis Date: Sun, 28 Apr 2024 03:02:22 +0100 Subject: [PATCH] Refactor into separate concerns --- dist/index.js | 339 +++++++++++++++++++++++++++---------- src/colors.ts | 7 - src/display.ts | 68 ++++++++ src/fetch-check-runs.ts | 28 +++ src/index.ts | 109 ++---------- src/inputs.ts | 30 ++++ src/relevant-check-runs.ts | 41 +++++ 7 files changed, 433 insertions(+), 189 deletions(-) delete mode 100644 src/colors.ts create mode 100644 src/display.ts create mode 100644 src/fetch-check-runs.ts create mode 100644 src/inputs.ts create mode 100644 src/relevant-check-runs.ts diff --git a/dist/index.js b/dist/index.js index 4b6bab9..6064925 100644 --- a/dist/index.js +++ b/dist/index.js @@ -29188,33 +29188,152 @@ function wrappy (fn, cb) { /***/ }), -/***/ 2100: +/***/ 653: /***/ ((__unused_webpack_module, exports) => { "use strict"; Object.defineProperty(exports, "__esModule", ({ value: true })); -exports.colors = void 0; -exports.colors = { +exports.delay = void 0; +async function delay(secs) { + return new Promise((resolve) => setTimeout(resolve, secs * 1000)); +} +exports.delay = delay; + + +/***/ }), + +/***/ 9517: +/***/ (function(__unused_webpack_module, exports, __nccwpck_require__) { + +"use strict"; + +var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { + if (k2 === undefined) k2 = k; + var desc = Object.getOwnPropertyDescriptor(m, k); + if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { + desc = { enumerable: true, get: function() { return m[k]; } }; + } + Object.defineProperty(o, k2, desc); +}) : (function(o, m, k, k2) { + if (k2 === undefined) k2 = k; + o[k2] = m[k]; +})); +var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { + Object.defineProperty(o, "default", { enumerable: true, value: v }); +}) : function(o, v) { + o["default"] = v; +}); +var __importStar = (this && this.__importStar) || function (mod) { + if (mod && mod.__esModule) return mod; + var result = {}; + if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k); + __setModuleDefault(result, mod); + return result; +}; +Object.defineProperty(exports, "__esModule", ({ value: true })); +exports.Display = void 0; +const core = __importStar(__nccwpck_require__(2186)); +const colors = { reset: "\x1b[0m", red: "\x1b[31m", green: "\x1b[32m", }; +const logAsGroup = (name, lines) => { + if (lines.length > 0) { + core.startGroup(name); + for (const item of lines) { + console.info(item); + } + core.endGroup(); + } +}; +const logCheckRuns = (icon, color, runs) => { + if (runs.length > 0) { + logAsGroup(`${icon} ${color}${runs.length}${colors.reset}`, runs); + } +}; +exports.Display = { + timedOut: () => { + console.info(""); + console.info(`⏰ ${colors.red}Timed out!${colors.reset}`); + }, + delaying: (seconds) => { + console.info(`🦥 Inspecting again in ${seconds}s...`); + }, + overallFailure: () => { + console.info(""); + console.info(`❗ ${colors.red}Failure!${colors.reset}`); + }, + overallSuccess: () => { + console.info(""); + console.info(`🚀 ${colors.green}Success!${colors.reset}`); + }, + startingIteration: () => { + console.info(""); + }, + ignoredCheckNames: (ignoredCheckNames) => { + if (ignoredCheckNames.size > 0) { + logAsGroup("Ignored check names", [...ignoredCheckNames]); + } + }, + relevantCheckRuns: (checkRuns) => { + logCheckRuns("✅", colors.green, checkRuns.succeeded); + logCheckRuns("❌", colors.red, checkRuns.failed); + logCheckRuns("⏳", colors.reset, checkRuns.pending); + }, +}; /***/ }), -/***/ 653: -/***/ ((__unused_webpack_module, exports) => { +/***/ 8896: +/***/ (function(__unused_webpack_module, exports, __nccwpck_require__) { "use strict"; +var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { + if (k2 === undefined) k2 = k; + var desc = Object.getOwnPropertyDescriptor(m, k); + if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { + desc = { enumerable: true, get: function() { return m[k]; } }; + } + Object.defineProperty(o, k2, desc); +}) : (function(o, m, k, k2) { + if (k2 === undefined) k2 = k; + o[k2] = m[k]; +})); +var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { + Object.defineProperty(o, "default", { enumerable: true, value: v }); +}) : function(o, v) { + o["default"] = v; +}); +var __importStar = (this && this.__importStar) || function (mod) { + if (mod && mod.__esModule) return mod; + var result = {}; + if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k); + __setModuleDefault(result, mod); + return result; +}; Object.defineProperty(exports, "__esModule", ({ value: true })); -exports.delay = void 0; -async function delay(secs) { - return new Promise((resolve) => setTimeout(resolve, secs * 1000)); -} -exports.delay = delay; +exports.fetchCheckRuns = void 0; +const github = __importStar(__nccwpck_require__(5438)); +const inputs_1 = __nccwpck_require__(7063); +const relevant_check_runs_1 = __nccwpck_require__(3695); +const octokit = github.getOctokit(inputs_1.inputs.token); +const fetchCheckRuns = async () => { + const iterator = octokit.paginate.iterator(octokit.rest.checks.listForRef, { + ...github.context.repo, + ref: inputs_1.inputs.ref, + per_page: 100, + }); + let runs = []; + for await (const { data } of iterator) { + runs = runs.concat(data); + } + return new relevant_check_runs_1.RelevantCheckRuns(runs.filter((run) => run.name !== inputs_1.inputs.name && !inputs_1.inputs.ignored.has(run.name))); +}; +exports.fetchCheckRuns = fetchCheckRuns; /***/ }), @@ -29249,104 +29368,146 @@ var __importStar = (this && this.__importStar) || function (mod) { }; Object.defineProperty(exports, "__esModule", ({ value: true })); const core = __importStar(__nccwpck_require__(2186)); -const github = __importStar(__nccwpck_require__(5438)); -const colors_1 = __nccwpck_require__(2100); const delay_1 = __nccwpck_require__(653); +const fetch_check_runs_1 = __nccwpck_require__(8896); +const inputs_1 = __nccwpck_require__(7063); +const display_1 = __nccwpck_require__(9517); const startTime = new Date(); -const inputs = { - name: core.getInput("name"), - interval: Number(core.getInput("interval")), - timeout: Number(core.getInput("timeout")), - ref: core.getInput("ref"), - ignored: new Set(core.getMultilineInput("ignored")), -}; -const octokit = github.getOctokit(core.getInput("token", { required: true })); -const failureConclusions = ["failure", "cancelled", "timed_out"]; const shouldTimeOut = () => { const executionTime = Math.round((new Date().getTime() - startTime.getTime()) / 1000); - return executionTime > inputs.timeout; -}; -const timedOut = () => { - console.info(""); - console.info(`⏰ ${colors_1.colors.red}Timed out!${colors_1.colors.reset}`); - core.setFailed("Timed out waiting on check runs to all be successful."); -}; -const logGroup = (name, items) => { - if (items.length > 0) { - core.startGroup(name); - for (const item of items) { - console.info(item); - } - core.endGroup(); - } -}; -const logCheckRuns = (icon, color, runs) => { - if (runs.length > 0) { - logGroup(`${icon} ${color}${runs.length}${colors_1.colors.reset}`, runs); - } + return executionTime > inputs_1.inputs.timeout; }; -if (inputs.ignored.size > 0) { - logGroup("Ignored check names", [...inputs.ignored]); -} +display_1.Display.ignoredCheckNames(inputs_1.inputs.ignored); const waitForCheckRuns = async () => { while (!shouldTimeOut()) { - console.info(""); - let checks = []; - const iterator = octokit.paginate.iterator(octokit.rest.checks.listForRef, { - owner: github.context.repo.owner, - repo: github.context.repo.repo, - ref: inputs.ref, - per_page: 100, - }); - for await (const { data } of iterator) { - checks = checks.concat(data); - } - checks = checks.filter((v) => v.name !== inputs.name && !inputs.ignored.has(v.name)); - core.debug(`Found a total of ${checks.length} relevant check runs`); - if (checks.length === 0) { - console.info(`Slothing, verifying again in ${inputs.interval}s...`); - await (0, delay_1.delay)(inputs.interval); + display_1.Display.startingIteration(); + const checkRuns = await (0, fetch_check_runs_1.fetchCheckRuns)(); + if (checkRuns.total() === 0) { + display_1.Display.delaying(inputs_1.inputs.interval); + await (0, delay_1.delay)(inputs_1.inputs.interval); continue; } - const pending = []; - const failures = []; - const successful = []; - for (const check of checks) { - if (!check.conclusion) { - pending.push(check.name); - } - else if (failureConclusions.includes(check.conclusion)) { - failures.push(check.name); - } - else { - successful.push(check.name); - } - } - for (const runs of [successful, failures, pending]) { - runs.sort(); - } - logCheckRuns("✅", colors_1.colors.green, successful); - logCheckRuns("❌", colors_1.colors.red, failures); - logCheckRuns("⏳", colors_1.colors.reset, pending); - if (failures.length > 0) { - console.info(""); - console.info(`❗ ${colors_1.colors.red}Failure!${colors_1.colors.reset}`); + display_1.Display.relevantCheckRuns(checkRuns); + if (checkRuns.isOverallFailure()) { + display_1.Display.overallFailure(); core.setFailed("A check run failed."); return; } - if (pending.length === 0) { - console.info(""); - console.info(`🚀 ${colors_1.colors.green}Success!${colors_1.colors.reset}`); + if (checkRuns.isOverallSuccess()) { + display_1.Display.overallSuccess(); return; } - console.info(`Slothing, verifying again in ${inputs.interval}s...`); - await (0, delay_1.delay)(inputs.interval); + display_1.Display.delaying(inputs_1.inputs.interval); + await (0, delay_1.delay)(inputs_1.inputs.interval); } - timedOut(); + display_1.Display.timedOut(); + core.setFailed("Timed out waiting on check runs to all be successful."); }; waitForCheckRuns(); +/***/ }), + +/***/ 7063: +/***/ (function(__unused_webpack_module, exports, __nccwpck_require__) { + +"use strict"; + +var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { + if (k2 === undefined) k2 = k; + var desc = Object.getOwnPropertyDescriptor(m, k); + if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { + desc = { enumerable: true, get: function() { return m[k]; } }; + } + Object.defineProperty(o, k2, desc); +}) : (function(o, m, k, k2) { + if (k2 === undefined) k2 = k; + o[k2] = m[k]; +})); +var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { + Object.defineProperty(o, "default", { enumerable: true, value: v }); +}) : function(o, v) { + o["default"] = v; +}); +var __importStar = (this && this.__importStar) || function (mod) { + if (mod && mod.__esModule) return mod; + var result = {}; + if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k); + __setModuleDefault(result, mod); + return result; +}; +Object.defineProperty(exports, "__esModule", ({ value: true })); +exports.inputs = void 0; +const core = __importStar(__nccwpck_require__(2186)); +const interval = Number(core.getInput("interval")); +if (!Number.isInteger(interval)) { + throw new Error("Invalid interval"); +} +if (interval < 1) { + throw new Error("Interval must be greater than 0"); +} +const timeout = Number(core.getInput("timeout")); +if (!Number.isInteger(timeout)) { + throw new Error("Invalid timeout"); +} +if (timeout < 1) { + throw new Error("Timeout must be greater than 0"); +} +exports.inputs = { + token: core.getInput("token", { required: true }), + name: core.getInput("name"), + interval, + timeout, + ref: core.getInput("ref"), + ignored: new Set(core.getMultilineInput("ignored")), +}; + + +/***/ }), + +/***/ 3695: +/***/ ((__unused_webpack_module, exports) => { + +"use strict"; + +Object.defineProperty(exports, "__esModule", ({ value: true })); +exports.RelevantCheckRuns = void 0; +const failureConclusions = ["failure", "cancelled", "timed_out"]; +class RelevantCheckRuns { + pending = []; + failed = []; + succeeded = []; + constructor(all) { + for (const run of all) { + if (!run.conclusion) { + this.pending.push(run.name); + } + else if (failureConclusions.includes(run.conclusion)) { + this.failed.push(run.name); + } + else { + this.succeeded.push(run.name); + } + } + this.pending.sort(); + this.failed.sort(); + this.succeeded.sort(); + } + isOverallFailure() { + return this.failed.length > 0; + } + isOverallSuccess() { + return (!this.isOverallFailure() && + this.pending.length === 0 && + this.succeeded.length > 0); + } + total() { + return this.pending.length + this.failed.length + this.succeeded.length; + } +} +exports.RelevantCheckRuns = RelevantCheckRuns; + + /***/ }), /***/ 9491: diff --git a/src/colors.ts b/src/colors.ts deleted file mode 100644 index 9800e7a..0000000 --- a/src/colors.ts +++ /dev/null @@ -1,7 +0,0 @@ -export const colors = { - reset: "\x1b[0m", - red: "\x1b[31m", - green: "\x1b[32m", -} as const; - -export type Color = (typeof colors)[keyof typeof colors]; diff --git a/src/display.ts b/src/display.ts new file mode 100644 index 0000000..66aa679 --- /dev/null +++ b/src/display.ts @@ -0,0 +1,68 @@ +import * as core from "@actions/core"; +import { RelevantCheckRuns } from "./relevant-check-runs"; +import { CheckRun } from "./fetch-check-runs"; + +const colors = { + reset: "\x1b[0m", + red: "\x1b[31m", + green: "\x1b[32m", +} as const; + +type Color = (typeof colors)[keyof typeof colors]; + +const logAsGroup = (name: string, lines: string[]): void => { + if (lines.length > 0) { + core.startGroup(name); + for (const item of lines) { + console.info(item); + } + core.endGroup(); + } +}; + +const logCheckRuns = ( + icon: string, + color: Color, + runs: CheckRun["name"][], +): void => { + if (runs.length > 0) { + logAsGroup(`${icon} ${color}${runs.length}${colors.reset}`, runs); + } +}; + +export const Display = { + timedOut: () => { + console.info(""); + console.info(`⏰ ${colors.red}Timed out!${colors.reset}`); + }, + + delaying: (seconds: number) => { + console.info(`🦥 Inspecting again in ${seconds}s...`); + }, + + overallFailure: () => { + console.info(""); + console.info(`❗ ${colors.red}Failure!${colors.reset}`); + }, + + overallSuccess: () => { + console.info(""); + console.info(`🚀 ${colors.green}Success!${colors.reset}`); + }, + + startingIteration: () => { + console.info(""); + }, + + ignoredCheckNames: (ignoredCheckNames: Set) => { + if (ignoredCheckNames.size > 0) { + logAsGroup("Ignored check names", [...ignoredCheckNames]); + } + }, + + relevantCheckRuns: (checkRuns: RelevantCheckRuns) => { + logCheckRuns("✅", colors.green, checkRuns.succeeded); + logCheckRuns("❌", colors.red, checkRuns.failed); + logCheckRuns("⏳", colors.reset, checkRuns.pending); + }, +}; diff --git a/src/fetch-check-runs.ts b/src/fetch-check-runs.ts new file mode 100644 index 0000000..4115bde --- /dev/null +++ b/src/fetch-check-runs.ts @@ -0,0 +1,28 @@ +import * as github from "@actions/github"; +import type { components } from "@octokit/openapi-types"; +import { inputs } from "./inputs"; +import { RelevantCheckRuns } from "./relevant-check-runs"; + +export type CheckRun = components["schemas"]["check-run"]; + +const octokit = github.getOctokit(inputs.token); + +export const fetchCheckRuns = async (): Promise => { + const iterator = octokit.paginate.iterator(octokit.rest.checks.listForRef, { + ...github.context.repo, + ref: inputs.ref, + per_page: 100, + }); + + let runs: CheckRun[] = []; + + for await (const { data } of iterator) { + runs = runs.concat(data); + } + + return new RelevantCheckRuns( + runs.filter( + (run) => run.name !== inputs.name && !inputs.ignored.has(run.name), + ), + ); +}; diff --git a/src/index.ts b/src/index.ts index f188d2a..2cb3228 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,26 +1,11 @@ import * as core from "@actions/core"; -import * as github from "@actions/github"; -import type { RestEndpointMethodTypes } from "@octokit/plugin-rest-endpoint-methods/dist-types/generated/parameters-and-response-types"; -import { Color, colors } from "./colors"; import { delay } from "./delay"; - -type GitHubCheckRuns = - RestEndpointMethodTypes["checks"]["listForRef"]["response"]["data"]["check_runs"]; +import { fetchCheckRuns } from "./fetch-check-runs"; +import { inputs } from "./inputs"; +import { Display } from "./display"; const startTime = new Date(); -const inputs = { - name: core.getInput("name"), - interval: Number(core.getInput("interval")), - timeout: Number(core.getInput("timeout")), - ref: core.getInput("ref"), - ignored: new Set(core.getMultilineInput("ignored")), -}; - -const octokit = github.getOctokit(core.getInput("token", { required: true })); - -const failureConclusions = ["failure", "cancelled", "timed_out"]; - const shouldTimeOut = (): boolean => { const executionTime = Math.round( (new Date().getTime() - startTime.getTime()) / 1000, @@ -28,101 +13,39 @@ const shouldTimeOut = (): boolean => { return executionTime > inputs.timeout; }; -const timedOut = (): void => { - console.info(""); - console.info(`⏰ ${colors.red}Timed out!${colors.reset}`); - core.setFailed("Timed out waiting on check runs to all be successful."); -}; - -const logGroup = (name: string, items: string[]): void => { - if (items.length > 0) { - core.startGroup(name); - for (const item of items) { - console.info(item); - } - core.endGroup(); - } -}; - -const logCheckRuns = (icon: string, color: Color, runs: string[]): void => { - if (runs.length > 0) { - logGroup(`${icon} ${color}${runs.length}${colors.reset}`, runs); - } -}; - -if (inputs.ignored.size > 0) { - logGroup("Ignored check names", [...inputs.ignored]); -} +Display.ignoredCheckNames(inputs.ignored); const waitForCheckRuns = async (): Promise => { while (!shouldTimeOut()) { - console.info(""); - - let checks: GitHubCheckRuns = []; - - const iterator = octokit.paginate.iterator(octokit.rest.checks.listForRef, { - owner: github.context.repo.owner, - repo: github.context.repo.repo, - ref: inputs.ref, - per_page: 100, - }); - - for await (const { data } of iterator) { - checks = checks.concat(data); - } - - checks = checks.filter( - (v) => v.name !== inputs.name && !inputs.ignored.has(v.name), - ); + Display.startingIteration(); - core.debug(`Found a total of ${checks.length} relevant check runs`); + const checkRuns = await fetchCheckRuns(); - if (checks.length === 0) { - console.info(`Slothing, verifying again in ${inputs.interval}s...`); + if (checkRuns.total() === 0) { + Display.delaying(inputs.interval); await delay(inputs.interval); continue; } - const pending: string[] = []; - const failures: string[] = []; - const successful: string[] = []; + Display.relevantCheckRuns(checkRuns); - for (const check of checks) { - if (!check.conclusion) { - pending.push(check.name); - } else if (failureConclusions.includes(check.conclusion)) { - failures.push(check.name); - } else { - successful.push(check.name); - } - } - - for (const runs of [successful, failures, pending]) { - runs.sort(); - } - - logCheckRuns("✅", colors.green, successful); - logCheckRuns("❌", colors.red, failures); - logCheckRuns("⏳", colors.reset, pending); - - if (failures.length > 0) { - console.info(""); - console.info(`❗ ${colors.red}Failure!${colors.reset}`); + if (checkRuns.isOverallFailure()) { + Display.overallFailure(); core.setFailed("A check run failed."); return; } - if (pending.length === 0) { - console.info(""); - console.info(`🚀 ${colors.green}Success!${colors.reset}`); + if (checkRuns.isOverallSuccess()) { + Display.overallSuccess(); return; } - console.info(`Slothing, verifying again in ${inputs.interval}s...`); + Display.delaying(inputs.interval); await delay(inputs.interval); } - timedOut(); + Display.timedOut(); + core.setFailed("Timed out waiting on check runs to all be successful."); }; waitForCheckRuns(); diff --git a/src/inputs.ts b/src/inputs.ts new file mode 100644 index 0000000..6fe629c --- /dev/null +++ b/src/inputs.ts @@ -0,0 +1,30 @@ +import * as core from "@actions/core"; + +const interval = Number(core.getInput("interval")); + +if (!Number.isInteger(interval)) { + throw new Error("Invalid interval"); +} + +if (interval < 1) { + throw new Error("Interval must be greater than 0"); +} + +const timeout = Number(core.getInput("timeout")); + +if (!Number.isInteger(timeout)) { + throw new Error("Invalid timeout"); +} + +if (timeout < 1) { + throw new Error("Timeout must be greater than 0"); +} + +export const inputs = { + token: core.getInput("token", { required: true }), + name: core.getInput("name"), + interval, + timeout, + ref: core.getInput("ref"), + ignored: new Set(core.getMultilineInput("ignored")), +} as const; diff --git a/src/relevant-check-runs.ts b/src/relevant-check-runs.ts new file mode 100644 index 0000000..6af146f --- /dev/null +++ b/src/relevant-check-runs.ts @@ -0,0 +1,41 @@ +import { CheckRun } from "./fetch-check-runs"; + +const failureConclusions = ["failure", "cancelled", "timed_out"]; + +export class RelevantCheckRuns { + readonly pending: CheckRun["name"][] = []; + readonly failed: CheckRun["name"][] = []; + readonly succeeded: CheckRun["name"][] = []; + + constructor(all: CheckRun[]) { + for (const run of all) { + if (!run.conclusion) { + this.pending.push(run.name); + } else if (failureConclusions.includes(run.conclusion)) { + this.failed.push(run.name); + } else { + this.succeeded.push(run.name); + } + } + + this.pending.sort(); + this.failed.sort(); + this.succeeded.sort(); + } + + isOverallFailure(): boolean { + return this.failed.length > 0; + } + + isOverallSuccess(): boolean { + return ( + !this.isOverallFailure() && + this.pending.length === 0 && + this.succeeded.length > 0 + ); + } + + total(): number { + return this.pending.length + this.failed.length + this.succeeded.length; + } +}