From d879cd5bc8f21aed8fe140c58e9b33a8148797da Mon Sep 17 00:00:00 2001 From: Kenichi Kamiya Date: Sun, 14 Apr 2024 15:40:16 +0900 Subject: [PATCH] Add an option to mark whether the validation is required or not for wait-list items (#762) Resolves #760 --- .github/workflows/itself.yml | 4 ++++ CHANGELOG.md | 3 +++ README.md | 9 ++++++--- dist/index.js | 29 ++++++++++++++++++++--------- src/github-api.ts | 34 ++++++++++++++++++++++++---------- src/main.ts | 6 +++--- 6 files changed, 60 insertions(+), 25 deletions(-) diff --git a/.github/workflows/itself.yml b/.github/workflows/itself.yml index fa8a9210..fa8319f4 100644 --- a/.github/workflows/itself.yml +++ b/.github/workflows/itself.yml @@ -77,6 +77,10 @@ jobs: { "workflowFile": "merge-bot-pr.yml", "jobName": "dependabot" + }, + { + "workflowFile": "THERE_ARE_NO_FILES_AS_THIS.yml", + "optional": true } ] skip-list: diff --git a/CHANGELOG.md b/CHANGELOG.md index 4d14b394..600ae03b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,9 +2,12 @@ This file only records notable changes. Not synchronized with all releases and tags. +- main - not yet released + - Add option to disable validations for `wait-list` and not found the checkRun - v3.0.0 - Wait other jobs which defined in same workflow by default: [#754](https://github.com/kachick/wait-other-jobs/issues/754)\ You can change this behavior with new option `skip-same-workflow: 'true'` + - Validate if the checkRun for the `wait-list` specified name is not found: [#760](https://github.com/kachick/wait-other-jobs/issues/760) - v2.0.2 - Allow some neutral patterns: [93299c](https://github.com/kachick/wait-other-jobs/commit/93299c2fa22fd463db31668eba54b34b58270696) - v2.0.0 diff --git a/README.md b/README.md index 0925ae54..50c23692 100644 --- a/README.md +++ b/README.md @@ -35,8 +35,10 @@ with: early-exit: 'false' # default 'true' skip-same-workflow: 'true' # default 'false' # lists should be given with JSON formatted array, do not specify both wait-list and skip-list - # - Each item should have a "workflowFile" field, and they can optionally have a "jobName" field - # - If no jobName is specified, all the jobs in the workflow will be targeted + # - Each item should have a "workflowFile" field, and they can optionally have a "jobName" field. + # - If no jobName is specified, all the jobs in the workflow will be targeted. + # - wait-list: If the checkRun for the specified name is not found, this action raise errors by default. + # You can disable this validation `optional: true`. wait-list: | [ { @@ -44,7 +46,8 @@ with: "jobName": "test" }, { - "workflowFile": "release.yml" + "workflowFile": "release.yml", + "optional": true } ] skip-list: | diff --git a/dist/index.js b/dist/index.js index 2d134675..567daa60 100644 --- a/dist/index.js +++ b/dist/index.js @@ -28150,7 +28150,11 @@ var FilterCondition = z.object({ workflowFile: z.string().endsWith(".yml"), jobName: z.string().min(1).optional() }); -var FilterConditions = z.array(FilterCondition); +var WaitFilterCondition = FilterCondition.extend( + { optional: z.boolean().optional().default(false) } +); +var SkipFilterConditions = z.array(FilterCondition); +var WaitFilterConditions = z.array(WaitFilterCondition); async function getCheckRunSummaries(token, trigger) { const octokit = new PaginatableOctokit({ auth: token }); const { repository: { object: { checkSuites } } } = await octokit.graphql.paginate( @@ -28247,11 +28251,21 @@ async function fetchOtherRunStatus(token, trigger, waitList, skipList, shouldSki const others = summaries.filter((summary) => !(summary.isSameWorkflow && trigger.jobName === summary.jobName)); let filtered = others.filter((summary) => !(summary.isSameWorkflow && shouldSkipSameWorkflow)); if (waitList.length > 0) { + const seeker = waitList.map((condition) => ({ ...condition, found: false })); filtered = filtered.filter( - (summary) => waitList.some( - (target) => target.workflowFile === summary.workflowPath && (target.jobName ? target.jobName === summary.jobName : true) - ) + (summary) => seeker.some((target) => { + if (target.workflowFile === summary.workflowPath && (target.jobName ? target.jobName === summary.jobName : true)) { + target.found = true; + return true; + } else { + return false; + } + }) ); + const unmatches = seeker.filter((result) => !result.found && !result.optional); + if (unmatches.length > 0) { + throw new Error(`Failed to meet some runs on your specified wait-list: ${JSON.stringify(unmatches)}`); + } } if (skipList.length > 0) { filtered = filtered.filter( @@ -28260,9 +28274,6 @@ async function fetchOtherRunStatus(token, trigger, waitList, skipList, shouldSki ) ); } - if (filtered.length === 0) { - throw new Error("No targets found except wait-other-jobs itself"); - } const completed = filtered.filter((summary) => summary.runStatus === "COMPLETED"); const progress = completed.length === filtered.length ? "done" : "in_progress"; const conclusion = completed.every((summary) => summary.acceptable) ? "acceptable" : "bad"; @@ -28366,8 +28377,8 @@ async function run() { (0, import_core2.getInput)("attempt-limits", { required: true, trimWhitespace: true }), 10 ); - const waitList = FilterConditions.parse(JSON.parse((0, import_core2.getInput)("wait-list", { required: true }))); - const skipList = FilterConditions.parse(JSON.parse((0, import_core2.getInput)("skip-list", { required: true }))); + const waitList = WaitFilterConditions.parse(JSON.parse((0, import_core2.getInput)("wait-list", { required: true }))); + const skipList = SkipFilterConditions.parse(JSON.parse((0, import_core2.getInput)("skip-list", { required: true }))); if (waitList.length > 0 && skipList.length > 0) { (0, import_core2.error)("Do not specify both wait-list and skip-list"); (0, import_core2.setFailed)("Specified both list"); diff --git a/src/github-api.ts b/src/github-api.ts index 5b7d84d9..913d8b1c 100644 --- a/src/github-api.ts +++ b/src/github-api.ts @@ -10,7 +10,11 @@ const FilterCondition = z.object({ workflowFile: z.string().endsWith('.yml'), jobName: (z.string().min(1)).optional(), }); -export const FilterConditions = z.array(FilterCondition); +const WaitFilterCondition = FilterCondition.extend( + { optional: z.boolean().optional().default(false) }, +); +export const SkipFilterConditions = z.array(FilterCondition); +export const WaitFilterConditions = z.array(WaitFilterCondition); interface Trigger { owner: string; @@ -152,8 +156,8 @@ interface Report { export async function fetchOtherRunStatus( token: string, trigger: Trigger, - waitList: z.infer, - skipList: z.infer, + waitList: z.infer, + skipList: z.infer, shouldSkipSameWorkflow: boolean, ): Promise { if (waitList.length > 0 && skipList.length > 0) { @@ -165,11 +169,25 @@ export async function fetchOtherRunStatus( let filtered = others.filter((summary) => !(summary.isSameWorkflow && shouldSkipSameWorkflow)); if (waitList.length > 0) { + const seeker = waitList.map((condition) => ({ ...condition, found: false })); filtered = filtered.filter((summary) => - waitList.some((target) => - target.workflowFile === summary.workflowPath && (target.jobName ? (target.jobName === summary.jobName) : true) - ) + seeker.some((target) => { + if ( + target.workflowFile === summary.workflowPath && (target.jobName ? (target.jobName === summary.jobName) : true) + ) { + target.found = true; + return true; + } else { + return false; + } + }) ); + + const unmatches = seeker.filter((result) => (!(result.found)) && (!(result.optional))); + + if (unmatches.length > 0) { + throw new Error(`Failed to meet some runs on your specified wait-list: ${JSON.stringify(unmatches)}`); + } } if (skipList.length > 0) { filtered = filtered.filter((summary) => @@ -179,10 +197,6 @@ export async function fetchOtherRunStatus( ); } - if (filtered.length === 0) { - throw new Error('No targets found except wait-other-jobs itself'); - } - const completed = filtered.filter((summary) => summary.runStatus === 'COMPLETED'); const progress: Report['progress'] = completed.length === filtered.length diff --git a/src/main.ts b/src/main.ts index 5b353dc3..b261fea9 100644 --- a/src/main.ts +++ b/src/main.ts @@ -16,7 +16,7 @@ const errorMessage = (body: string) => (`${styles.red.open}${body}${styles.red.c const succeededMessage = (body: string) => (`${styles.green.open}${body}${styles.green.close}`); const colorize = (body: string, ok: boolean) => (ok ? succeededMessage(body) : errorMessage(body)); -import { FilterConditions, fetchOtherRunStatus } from './github-api.js'; +import { SkipFilterConditions, WaitFilterConditions, fetchOtherRunStatus } from './github-api.js'; import { readableDuration, wait, isRetryMethod, retryMethods, getIdleMilliseconds } from './wait.js'; async function run(): Promise { @@ -73,8 +73,8 @@ async function run(): Promise { getInput('attempt-limits', { required: true, trimWhitespace: true }), 10, ); - const waitList = FilterConditions.parse(JSON.parse(getInput('wait-list', { required: true }))); - const skipList = FilterConditions.parse(JSON.parse(getInput('skip-list', { required: true }))); + const waitList = WaitFilterConditions.parse(JSON.parse(getInput('wait-list', { required: true }))); + const skipList = SkipFilterConditions.parse(JSON.parse(getInput('skip-list', { required: true }))); if (waitList.length > 0 && skipList.length > 0) { error('Do not specify both wait-list and skip-list'); setFailed('Specified both list');