diff --git a/src/commands/set-shas.yml b/src/commands/set-shas.yml index ce2ae0a..ca5c971 100755 --- a/src/commands/set-shas.yml +++ b/src/commands/set-shas.yml @@ -25,6 +25,12 @@ parameters: description: | By default, only workflows with CircleCI status of "success" will be detected for the main branch. Enable this option to also detect workflows with CircleCI status of "on_hold" in case your workflow requires manual approvals. + allow-not-run-workflow: + type: boolean + default: false + description: | + By default, only workflows with CircleCI status of "success" will be detected for the main branch. + Enable this option to also detect workflows with CircleCI status of "not_run" in case your workflow requires manual approvals. skip-branch-filter: type: boolean default: false @@ -39,6 +45,7 @@ steps: PARAM_ERROR_ON_NO_SUCCESSFUL_WORKFLOW: <> PARAM_WORKFLOW_NAME: <> PARAM_ALLOW_ON_HOLD: <> + PARMA_ALLOW_NOT_RUN: <> PARAM_SKIP_BRANCH_FILTER: <> PARAM_SCRIPT: <> name: Derives SHAs for base and head for use in `nx affected` commands diff --git a/src/scripts/find-successful-workflow.js b/src/scripts/find-successful-workflow.js index 528e683..ff755a7 100644 --- a/src/scripts/find-successful-workflow.js +++ b/src/scripts/find-successful-workflow.js @@ -1,14 +1,15 @@ #!/usr/bin/env node -const { execSync } = require('child_process'); -const https = require('https'); +const { execSync } = require("child_process"); +const https = require("https"); const buildUrl = process.argv[2]; const branchName = process.argv[3]; const mainBranchName = process.env.MAIN_BRANCH_NAME || process.argv[4]; -const errorOnNoSuccessfulWorkflow = process.argv[5] === '1'; -const allowOnHoldWorkflow = process.argv[6] === '1'; -const skipBranchFilter = process.argv[7] === '1'; +const errorOnNoSuccessfulWorkflow = process.argv[5] === "1"; +const allowOnHoldWorkflow = process.argv[6] === "1"; +const skipBranchFilter = process.argv[7] === "1"; const workflowName = process.argv[8]; +const allowNotRunWorkflow = process.argv[9] == "1"; const circleToken = process.env.CIRCLE_API_TOKEN; const [, host, project] = buildUrl.match(/https?:\/\/([^\/]+)\/(.*)\/\d+/); @@ -16,10 +17,15 @@ const [, host, project] = buildUrl.match(/https?:\/\/([^\/]+)\/(.*)\/\d+/); let BASE_SHA; (async () => { if (branchName !== mainBranchName) { - BASE_SHA = execSync(`git merge-base origin/${mainBranchName} HEAD`, { encoding: 'utf-8' }); + BASE_SHA = execSync(`git merge-base origin/${mainBranchName} HEAD`, { + encoding: "utf-8", + }); } else { try { - BASE_SHA = await findSuccessfulCommit(skipBranchFilter ? undefined : mainBranchName, workflowName); + BASE_SHA = await findSuccessfulCommit( + skipBranchFilter ? undefined : mainBranchName, + workflowName + ); } catch (e) { process.stderr.write(e.message); if (errorOnNoSuccessfulWorkflow) { @@ -31,7 +37,9 @@ This might be a temporary issue with their API or a misconfiguration of the CIRC We are therefore defaulting to use HEAD~1 on 'origin/${mainBranchName}'. NOTE: You can instead make this a hard error by settting 'error-on-no-successful-workflow' on the step in your workflow.\n\n`); - BASE_SHA = execSync(`git rev-parse origin/${mainBranchName}~1`, { encoding: 'utf-8' }); + BASE_SHA = execSync(`git rev-parse origin/${mainBranchName}~1`, { + encoding: "utf-8", + }); } } @@ -51,7 +59,9 @@ WARNING: Unable to find a successful workflow run on 'origin/${mainBranchName}'. We are therefore defaulting to use HEAD~1 on 'origin/${mainBranchName}'. NOTE: You can instead make this a hard error by settting 'error-on-no-successful-workflow' on the step in your workflow.\n\n`); - BASE_SHA = execSync(`git rev-parse origin/${mainBranchName}~1`, { encoding: 'utf-8' }); + BASE_SHA = execSync(`git rev-parse origin/${mainBranchName}~1`, { + encoding: "utf-8", + }); } } else { process.stdout.write(` @@ -69,15 +79,18 @@ async function findSuccessfulCommit(branch, workflowName) { let foundSHA; do { - const fullParams = params.concat(nextPage ? [`page-token=${nextPage}`] : []).join('&'); - const { next_page_token, sha } = await getJson(`${url}${fullParams}`) - .then(async ({ next_page_token, items }) => { + const fullParams = params + .concat(nextPage ? [`page-token=${nextPage}`] : []) + .join("&"); + const { next_page_token, sha } = await getJson(`${url}${fullParams}`).then( + async ({ next_page_token, items }) => { const pipeline = await findSuccessfulPipeline(items, workflowName); return { next_page_token, - sha: pipeline ? pipeline.vcs.revision : void 0 + sha: pipeline ? pipeline.vcs.revision : void 0, }; - }); + } + ); foundSHA = sha; nextPage = next_page_token; @@ -88,9 +101,11 @@ async function findSuccessfulCommit(branch, workflowName) { async function findSuccessfulPipeline(pipelines, workflowName) { for (const pipeline of pipelines) { - if (!pipeline.errors.length - && commitExists(pipeline.vcs.revision) - && await isWorkflowSuccessful(pipeline.id, workflowName)) { + if ( + !pipeline.errors.length && + commitExists(pipeline.vcs.revision) && + (await isWorkflowSuccessful(pipeline.id, workflowName)) + ) { return pipeline; } } @@ -99,7 +114,7 @@ async function findSuccessfulPipeline(pipelines, workflowName) { function commitExists(commitSha) { try { - execSync(`git cat-file -e ${commitSha}`, { stdio: ['pipe', 'pipe', null] }); + execSync(`git cat-file -e ${commitSha}`, { stdio: ["pipe", "pipe", null] }); return true; } catch { return false; @@ -108,11 +123,28 @@ function commitExists(commitSha) { async function isWorkflowSuccessful(pipelineId, workflowName) { if (!workflowName) { - return getJson(`https://${host}/api/v2/pipeline/${pipelineId}/workflow`) - .then(({ items }) => items.every(item => (item.status === 'success') || (allowOnHoldWorkflow && item.status === 'on_hold'))); + return getJson( + `https://${host}/api/v2/pipeline/${pipelineId}/workflow` + ).then(({ items }) => + items.every( + (item) => + item.status === "success" || + (allowOnHoldWorkflow && item.status === "on_hold") || + (allowNotRunWorkflow && item.status === "not_run") + ) + ); } else { - return getJson(`https://${host}/api/v2/pipeline/${pipelineId}/workflow`) - .then(({ items }) => items.some(item => ((item.status === 'success') || (allowOnHoldWorkflow && item.status === 'on_hold')) && item.name === workflowName)); + return getJson( + `https://${host}/api/v2/pipeline/${pipelineId}/workflow` + ).then(({ items }) => + items.some( + (item) => + (item.status === "success" || + (allowOnHoldWorkflow && item.status === "on_hold") || + (allowNotRunWorkflow && item.status === "not_run")) && + item.name === workflowName + ) + ); } } @@ -122,34 +154,46 @@ async function getJson(url) { if (circleToken) { options.headers = { - 'Circle-Token': circleToken - } + "Circle-Token": circleToken, + }; } - https.get(url, options, (res) => { - let data = []; - - res.on('data', chunk => { - data.push(chunk); - }); - - res.on('end', () => { - const response = Buffer.concat(data).toString(); - try { - const responseJSON = JSON.parse(response); - resolve(responseJSON); - } catch (e) { - if (response.includes('Project not found')) { - reject(new Error(`Error: Project not found.\nIf you are using a private repo, make sure the CIRCLE_API_TOKEN is set.\n\n${response}`)); - } else { - reject(e) + https + .get(url, options, (res) => { + let data = []; + + res.on("data", (chunk) => { + data.push(chunk); + }); + + res.on("end", () => { + const response = Buffer.concat(data).toString(); + try { + const responseJSON = JSON.parse(response); + resolve(responseJSON); + } catch (e) { + if (response.includes("Project not found")) { + reject( + new Error( + `Error: Project not found.\nIf you are using a private repo, make sure the CIRCLE_API_TOKEN is set.\n\n${response}` + ) + ); + } else { + reject(e); + } } - } - }); - }).on('error', error => reject( - circleToken - ? new Error(`Error: Pipeline fetching failed.\nCheck if you set the correct user CIRCLE_API_TOKEN.\n\n${error.toString()}`) - : new Error(`Error: Pipeline fetching failed.\nIf this is private repo you will need to set CIRCLE_API_TOKEN\n\n${error.toString()}`) - )); + }); + }) + .on("error", (error) => + reject( + circleToken + ? new Error( + `Error: Pipeline fetching failed.\nCheck if you set the correct user CIRCLE_API_TOKEN.\n\n${error.toString()}` + ) + : new Error( + `Error: Pipeline fetching failed.\nIf this is private repo you will need to set CIRCLE_API_TOKEN\n\n${error.toString()}` + ) + ) + ); }); } diff --git a/src/scripts/set-shas.sh b/src/scripts/set-shas.sh index 5f7e841..b2ddd42 100644 --- a/src/scripts/set-shas.sh +++ b/src/scripts/set-shas.sh @@ -6,7 +6,7 @@ if [ -z "$CIRCLE_BRANCH" ]; then else TARGET_BRANCH=$CIRCLE_BRANCH fi -RESPONSE=$(node index.js $CIRCLE_BUILD_URL $TARGET_BRANCH $PARAM_MAIN_BRANCH $PARAM_ERROR_ON_NO_SUCCESSFUL_WORKFLOW $PARAM_ALLOW_ON_HOLD $PARAM_SKIP_BRANCH_FILTER $PARAM_WORKFLOW_NAME) +RESPONSE=$(node index.js $CIRCLE_BUILD_URL $TARGET_BRANCH $PARAM_MAIN_BRANCH $PARAM_ERROR_ON_NO_SUCCESSFUL_WORKFLOW $PARAM_ALLOW_ON_HOLD $PARAM_SKIP_BRANCH_FILTER $PARAM_WORKFLOW_NAME $PARMA_ALLOW_NOT_RUN) echo "$RESPONSE" BASE_SHA=$(echo "$RESPONSE" | grep 'Commit:' | sed 's/.*Commit: //') HEAD_SHA=$(git rev-parse HEAD)