From 6888a24080a9c40771cd7873535e59771c5caa01 Mon Sep 17 00:00:00 2001 From: mauriziovitale Date: Wed, 6 Mar 2024 14:03:55 +0000 Subject: [PATCH] Handle multiple types for advanced scenarios --- README.md | 1 + action.yml | 2 +- dist/index.js | 49 ++++++++++++++----- find-successful-workflow.ts | 95 ++++++++++++++++++++++++------------- 4 files changed, 100 insertions(+), 47 deletions(-) diff --git a/README.md b/README.md index e0441af..5fa014b 100644 --- a/README.md +++ b/README.md @@ -91,6 +91,7 @@ jobs: last-successful-event: "" # The path where your repository is. This is only required for cases where the repository code is checked out or moved to a specific path. + # The property can also contain several types separated by comma i.e. schedule,workflow_dispatch # # Default: . working-directory: "" diff --git a/action.yml b/action.yml index b6690d4..6736cc7 100644 --- a/action.yml +++ b/action.yml @@ -12,7 +12,7 @@ inputs: description: "By default, if no successful workflow is found on the main branch to determine the SHA, we will log a warning and use HEAD~1. Enable this option to error and exit instead." default: "false" last-successful-event: - description: "The type of event to check for the last successful commit corresponding to that workflow-id, e.g. push, pull_request, release etc" + description: "The type of event to check for the last successful commit corresponding to that workflow-id, e.g. push or pull_request or release. The property can also contain several types separated by a comma. E.g. schedule,workflow_dispatch." default: "push" working-directory: description: "The directory where your repository is located" diff --git a/dist/index.js b/dist/index.js index 974c09b..ec64489 100644 --- a/dist/index.js +++ b/dist/index.js @@ -37971,18 +37971,43 @@ function findSuccessfulCommit(workflow_id, run_id, owner, repo, branch, lastSucc process.stdout.write("\n"); process.stdout.write(`Workflow Id not provided. Using workflow '${workflow_id}'\n`); } - // fetch all workflow runs on a given repo/branch/workflow with push and success - const shas = yield octokit - .request(`GET /repos/${owner}/${repo}/actions/workflows/${workflow_id}/runs`, { - owner, - repo, - // on non-push workflow runs we do not have branch property - branch: lastSuccessfulEvent !== "push" ? undefined : branch, - workflow_id, - event: lastSuccessfulEvent, - status: "success", - }) - .then(({ data: { workflow_runs } }) => workflow_runs.map((run) => run.head_sha)); + let shas = []; + // if there are several events separated by comma + if (lastSuccessfulEvent.includes(",")) { + // find the greatest workflow id among the types and retrieve the sha + const events = lastSuccessfulEvent.split(","); + const workflowIdMap = new Map(); + for (const event of events) { + const workflowRun = yield octokit + .request(`GET /repos/${owner}/${repo}/actions/workflows/${workflow_id}/runs`, { + owner, + repo, + // on non-push workflow runs we do not have branch property + branch: lastSuccessfulEvent !== "push" ? undefined : branch, + event, + workflow_id, + }) + .then(({ data }) => { + return data.workflow_runs[0]; + }); + workflowIdMap.set(workflowRun.id, workflowRun.head_sha); + } + shas.push(workflowIdMap.get(Math.max(...workflowIdMap.keys()))); + } + else { + // fetch all workflow runs on a given repo/branch/workflow with push and success + shas = yield octokit + .request(`GET /repos/${owner}/${repo}/actions/workflows/${workflow_id}/runs`, { + owner, + repo, + // on non-push workflow runs we do not have branch property + branch: lastSuccessfulEvent !== "push" ? undefined : branch, + workflow_id, + event: lastSuccessfulEvent, + status: "success", + }) + .then(({ data: { workflow_runs } }) => workflow_runs.map((run) => run.head_sha)); + } return yield findExistingCommit(octokit, branch, shas); }); } diff --git a/find-successful-workflow.ts b/find-successful-workflow.ts index 788cbcc..5abd962 100644 --- a/find-successful-workflow.ts +++ b/find-successful-workflow.ts @@ -29,7 +29,7 @@ let BASE_SHA: string; } else { process.stdout.write("\n"); process.stdout.write( - `WARNING: Working directory '${workingDirectory}' doesn't exist.\n` + `WARNING: Working directory '${workingDirectory}' doesn't exist.\n`, ); } } @@ -49,7 +49,7 @@ let BASE_SHA: string; const baseResult = spawnSync( "git", ["merge-base", `origin/${mainBranchName}`, mergeBaseRef], - { encoding: "utf-8" } + { encoding: "utf-8" }, ); BASE_SHA = baseResult.stdout; } catch (e) { @@ -64,7 +64,7 @@ let BASE_SHA: string; owner, repo, mainBranchName, - lastSuccessfulEvent + lastSuccessfulEvent, ); } catch (e) { core.setFailed(e.message); @@ -76,27 +76,27 @@ let BASE_SHA: string; reportFailure(mainBranchName); return; } else { - process.stdout.write( "\n"); + process.stdout.write("\n"); process.stdout.write( - `WARNING: Unable to find a successful workflow run on 'origin/${mainBranchName}', or the latest successful workflow was connected to a commit which no longer exists on that branch (e.g. if that branch was rebased)\n` + `WARNING: Unable to find a successful workflow run on 'origin/${mainBranchName}', or the latest successful workflow was connected to a commit which no longer exists on that branch (e.g. if that branch was rebased)\n`, ); process.stdout.write( - `We are therefore defaulting to use HEAD~1 on 'origin/${mainBranchName}'\n` + `We are therefore defaulting to use HEAD~1 on 'origin/${mainBranchName}'\n`, ); process.stdout.write("\n"); process.stdout.write( - `NOTE: You can instead make this a hard error by setting 'error-on-no-successful-workflow' on the action in your workflow.\n` + `NOTE: You can instead make this a hard error by setting 'error-on-no-successful-workflow' on the action in your workflow.\n`, ); process.stdout.write("\n"); const commitCountOutput = spawnSync( "git", ["rev-list", "--count", `origin/${mainBranchName}`], - { encoding: "utf-8" } + { encoding: "utf-8" }, ).stdout; const commitCount = parseInt( stripNewLineEndings(commitCountOutput), - 10 + 10, ); const LAST_COMMIT_CMD = `origin/${mainBranchName}${ @@ -111,7 +111,7 @@ let BASE_SHA: string; } else { process.stdout.write("\n"); process.stdout.write( - `Found the last successful workflow run on 'origin/${mainBranchName}'\n` + `Found the last successful workflow run on 'origin/${mainBranchName}'\n`, ); process.stdout.write(`Commit: ${BASE_SHA}\n`); } @@ -148,7 +148,7 @@ async function findSuccessfulCommit( owner: string, repo: string, branch: string, - lastSuccessfulEvent: string + lastSuccessfulEvent: string, ): Promise { const octokit = new ProxifiedClient(); if (!workflow_id) { @@ -162,26 +162,53 @@ async function findSuccessfulCommit( .then(({ data: { workflow_id } }) => workflow_id); process.stdout.write("\n"); process.stdout.write( - `Workflow Id not provided. Using workflow '${workflow_id}'\n` + `Workflow Id not provided. Using workflow '${workflow_id}'\n`, ); } - // fetch all workflow runs on a given repo/branch/workflow with push and success - const shas = await octokit - .request( - `GET /repos/${owner}/${repo}/actions/workflows/${workflow_id}/runs`, - { - owner, - repo, - // on non-push workflow runs we do not have branch property - branch: lastSuccessfulEvent !== "push" ? undefined : branch, - workflow_id, - event: lastSuccessfulEvent, - status: "success", - } - ) - .then(({ data: { workflow_runs } }) => - workflow_runs.map((run: { head_sha: any }) => run.head_sha) - ); + let shas = []; + // if there are several events separated by comma + if (lastSuccessfulEvent.includes(",")) { + // find the greatest workflow id among the types and retrieve the sha + const events = lastSuccessfulEvent.split(","); + const workflowIdMap = new Map(); + for (const event of events) { + const workflowRun = await octokit + .request( + `GET /repos/${owner}/${repo}/actions/workflows/${workflow_id}/runs`, + { + owner, + repo, + // on non-push workflow runs we do not have branch property + branch: lastSuccessfulEvent !== "push" ? undefined : branch, + event, + workflow_id, + }, + ) + .then(({ data }) => { + return data.workflow_runs[0]; + }); + workflowIdMap.set(workflowRun.id, workflowRun.head_sha); + } + shas.push(workflowIdMap.get(Math.max(...workflowIdMap.keys()))); + } else { + // fetch all workflow runs on a given repo/branch/workflow with push and success + shas = await octokit + .request( + `GET /repos/${owner}/${repo}/actions/workflows/${workflow_id}/runs`, + { + owner, + repo, + // on non-push workflow runs we do not have branch property + branch: lastSuccessfulEvent !== "push" ? undefined : branch, + workflow_id, + event: lastSuccessfulEvent, + status: "success", + }, + ) + .then(({ data: { workflow_runs } }) => + workflow_runs.map((run: { head_sha: any }) => run.head_sha), + ); + } return await findExistingCommit(octokit, branch, shas); } @@ -198,7 +225,7 @@ async function findMergeBaseRef(): Promise { function findMergeQueuePr(): string { const { head_ref, base_sha } = github.context.payload.merge_group; const result = new RegExp( - `^refs/heads/gh-readonly-queue/${mainBranchName}/pr-(\\d+)-${base_sha}$` + `^refs/heads/gh-readonly-queue/${mainBranchName}/pr-(\\d+)-${base_sha}$`, ).exec(head_ref); return result ? result.at(1) : undefined; } @@ -213,7 +240,7 @@ async function findMergeQueueBranch(): Promise { const octokit = new ProxifiedClient(); const result = await octokit.request( `GET /repos/${owner}/${repo}/pulls/${pull_number}`, - { owner, repo, pull_number: +pull_number } + { owner, repo, pull_number: +pull_number }, ); return result.data.head.ref; } @@ -224,7 +251,7 @@ async function findMergeQueueBranch(): Promise { async function findExistingCommit( octokit: Octokit, branchName: string, - shas: string[] + shas: string[], ): Promise { for (const commitSha of shas) { if (await commitExists(octokit, branchName, commitSha)) { @@ -240,7 +267,7 @@ async function findExistingCommit( async function commitExists( octokit: Octokit, branchName: string, - commitSha: string + commitSha: string, ): Promise { try { spawnSync("git", ["cat-file", "-e", commitSha], { @@ -263,7 +290,7 @@ async function commitExists( }); return commits.data.some( - (commit: { sha: string }) => commit.sha === commitSha + (commit: { sha: string }) => commit.sha === commitSha, ); } catch { return false;