Skip to content

Commit

Permalink
fix: ensure resolved BASE_SHA was not disconnected from branch (#132)
Browse files Browse the repository at this point in the history
JamesHenry authored Nov 19, 2023

Unverified

This commit is not signed, but one or more authors requires that any commit attributed to them is signed.
1 parent fb2e040 commit 46af9ed
Showing 6 changed files with 628 additions and 167 deletions.
8 changes: 6 additions & 2 deletions .husky/pre-commit
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
#!/bin/sh
#!/usr/bin/env sh
. "$(dirname -- "$0")/_/husky.sh"

npx --no-install lint-staged --allow-empty

node tools/pre-commit.js

node tools/pre-commit.js
36 changes: 22 additions & 14 deletions README.md
Original file line number Diff line number Diff line change
@@ -19,6 +19,7 @@ width="100%" alt="Nx - Smart, Extensible Build Framework"></p>
**.github/workflows/ci.yml**

<!-- start example-usage -->

```yaml
# ... more CI config ...

@@ -40,7 +41,7 @@ jobs:
# ===========================================================================
- name: Derive appropriate SHAs for base and head for `nx affected` commands
uses: nrwl/nx-set-shas@v4

- run: |
echo "BASE: ${{ env.NX_BASE }}"
echo "HEAD: ${{ env.NX_HEAD }}"
@@ -51,75 +52,81 @@ jobs:
- name: Derive appropriate SHAs for base and head for `nx affected` commands
id: setSHAs
uses: nrwl/nx-set-shas@v4

- run: |
echo "BASE: ${{ steps.setSHAs.outputs.base }}"
echo "HEAD: ${{ steps.setSHAs.outputs.head }}"
# ... more CI config ...
```

<!-- end example-usage -->

## Configuration Options

<!-- start configuration-options -->

```yaml
- uses: nrwl/nx-set-shas@v4
with:
# The "main" branch of your repository (the base branch which you target with PRs).
# Common names for this branch include main and master.
#
# Default: main
main-branch-name: ''
main-branch-name: ""

# Applies the derived SHAs for base and head as NX_BASE and NX_HEAD environment variables within the current Job.
#
# Default: true
set-environment-variables-for-job: ''
set-environment-variables-for-job: ""

# By default, if no successful workflow run 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
error-on-no-successful-workflow: ''
error-on-no-successful-workflow: ""

# The type of event to check for the last successful commit corresponding to that workflow-id, e.g. push, pull_request, release etc.
#
# Default: push
last-successful-event: ''
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.
#
# Default: .
working-directory: ''
working-directory: ""

# The ID of the github action workflow to check for successful run or the name of the file name containing the workflow.
# The ID of the github action workflow to check for successful run or the name of the file name containing the workflow.
# E.g. 'ci.yml'. If not provided, current workflow id will be used
#
workflow-id: ''
workflow-id: ""
```
<!-- end configuration-options -->
## Permissions in v2+
This Action uses Github API to find the last successful workflow run. If your `GITHUB_TOKEN` has restrictions set please ensure you override them for the workflow to enable read access to `actions` and `contents`:

<!-- start permissions-in-v2 -->

```yaml
jobs:
myjob:
runs-on: ubuntu-latest
name: My Job
permissions:
contents: 'read'
actions: 'read'
contents: "read"
actions: "read"
```

<!-- end permissions-in-v2 -->

## Self-hosted runners

This Action supports usage of your own self-hosted runners, but since it uses GitHub APIs you will need to grant it explicit access rights:

<!-- self-hosted runners -->

```yaml
# ... more CI config ...
@@ -149,6 +156,7 @@ jobs:
# ... more CI config ...
```

<!-- end self-hosted runners -->

## Background
@@ -157,7 +165,6 @@ When we run the `affected` command on [Nx](https://nx.dev/), we can specify 2 gi

This makes it easy to set up a CI system that scales well with the continuous growth of your repository, as you add more and more projects.


### Problem

Figuring out what these two git commits are might not be as simple as it seems.
@@ -174,8 +181,9 @@ Conceptually, what we want is to use the absolute latest commit on the `master`
The commits therefore can't just be `HEAD` and `HEAD~1`. If a few deployments fail one after another, that means that we're accumulating a list of affected projects that are not getting deployed. Anytime we retry the deployment, we want to include **every commit since the last time we deployed successfully**. That way we ensure we don't accidentally skip deploying a project that has changed.

This action enables you to find:
* Commit SHA from which PR originated (in the case of `pull_request`)
* Commit SHA of the last successful CI run

- Commit SHA from which PR originated (in the case of `pull_request`)
- Commit SHA of the last successful CI run

## License

113 changes: 63 additions & 50 deletions dist/index.js
Original file line number Diff line number Diff line change
@@ -37853,21 +37853,21 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
});
};
Object.defineProperty(exports, "__esModule", ({ value: true }));
const action_1 = __nccwpck_require__(1231);
const core = __nccwpck_require__(2186);
const github = __nccwpck_require__(5438);
const action_1 = __nccwpck_require__(1231);
const child_process_1 = __nccwpck_require__(2081);
const fs_1 = __nccwpck_require__(7147);
const https_proxy_agent_1 = __nccwpck_require__(7219);
const proxy_from_env_1 = __nccwpck_require__(3329);
const { runId, repo: { repo, owner }, eventName } = github.context;
const { runId, repo: { repo, owner }, eventName, } = github.context;
process.env.GITHUB_TOKEN = process.argv[2];
const mainBranchName = process.argv[3];
const errorOnNoSuccessfulWorkflow = process.argv[4];
const lastSuccessfulEvent = process.argv[5];
const workingDirectory = process.argv[6];
const workflowId = process.argv[7];
const defaultWorkingDirectory = '.';
const defaultWorkingDirectory = ".";
const ProxifiedClient = action_1.Octokit.plugin(proxyPlugin);
let BASE_SHA;
(() => __awaiter(void 0, void 0, void 0, function* () {
@@ -37876,17 +37876,20 @@ let BASE_SHA;
process.chdir(workingDirectory);
}
else {
process.stdout.write('\n');
process.stdout.write("\n");
process.stdout.write(`WARNING: Working directory '${workingDirectory}' doesn't exist.\n`);
}
}
const headResult = (0, child_process_1.spawnSync)('git', ['rev-parse', 'HEAD'], { encoding: 'utf-8' });
const headResult = (0, child_process_1.spawnSync)("git", ["rev-parse", "HEAD"], {
encoding: "utf-8",
});
const HEAD_SHA = headResult.stdout;
if ((['pull_request', 'pull_request_target'].includes(eventName) && !github.context.payload.pull_request.merged) ||
eventName == 'merge_group') {
if ((["pull_request", "pull_request_target"].includes(eventName) &&
!github.context.payload.pull_request.merged) ||
eventName == "merge_group") {
try {
const mergeBaseRef = yield findMergeBaseRef();
const baseResult = (0, child_process_1.spawnSync)('git', ['merge-base', `origin/${mainBranchName}`, mergeBaseRef], { encoding: 'utf-8' });
const baseResult = (0, child_process_1.spawnSync)("git", ["merge-base", `origin/${mainBranchName}`, mergeBaseRef], { encoding: "utf-8" });
BASE_SHA = baseResult.stdout;
}
catch (e) {
@@ -37903,32 +37906,35 @@ let BASE_SHA;
return;
}
if (!BASE_SHA) {
if (errorOnNoSuccessfulWorkflow === 'true') {
if (errorOnNoSuccessfulWorkflow === "true") {
reportFailure(mainBranchName);
return;
}
else {
process.stdout.write('\n');
process.stdout.write(`WARNING: Unable to find a successful workflow run on 'origin/${mainBranchName}'\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`);
process.stdout.write(`We are therefore defaulting to use HEAD~1 on 'origin/${mainBranchName}'\n`);
process.stdout.write('\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`);
const commitCountOutput = (0, child_process_1.spawnSync)('git', ['rev-list', '--count', `origin/${mainBranchName}`], { encoding: 'utf-8' }).stdout;
process.stdout.write("\n");
const commitCountOutput = (0, child_process_1.spawnSync)("git", ["rev-list", "--count", `origin/${mainBranchName}`], { encoding: "utf-8" }).stdout;
const commitCount = parseInt(stripNewLineEndings(commitCountOutput), 10);
const LAST_COMMIT_CMD = `origin/${mainBranchName}${commitCount > 1 ? '~1' : ''}`;
const baseRes = (0, child_process_1.spawnSync)('git', ['rev-parse', LAST_COMMIT_CMD], { encoding: 'utf-8' });
const LAST_COMMIT_CMD = `origin/${mainBranchName}${commitCount > 1 ? "~1" : ""}`;
const baseRes = (0, child_process_1.spawnSync)("git", ["rev-parse", LAST_COMMIT_CMD], {
encoding: "utf-8",
});
BASE_SHA = baseRes.stdout;
core.setOutput('noPreviousBuild', 'true');
core.setOutput("noPreviousBuild", "true");
}
}
else {
process.stdout.write('\n');
process.stdout.write("\n");
process.stdout.write(`Found the last successful workflow run on 'origin/${mainBranchName}'\n`);
process.stdout.write(`Commit: ${BASE_SHA}\n`);
}
}
core.setOutput('base', stripNewLineEndings(BASE_SHA));
core.setOutput('head', stripNewLineEndings(HEAD_SHA));
core.setOutput("base", stripNewLineEndings(BASE_SHA));
core.setOutput("head", stripNewLineEndings(HEAD_SHA));
}))();
function reportFailure(branchName) {
core.setFailed(`
@@ -37940,7 +37946,7 @@ function reportFailure(branchName) {
- If no, then you might have changed your git history and those commits no longer exist.`);
}
function proxyPlugin(octokit) {
octokit.hook.before('request', options => {
octokit.hook.before("request", (options) => {
const proxy = (0, proxy_from_env_1.getProxyForUrl)(options.baseUrl);
if (proxy) {
options.request.agent = new https_proxy_agent_1.HttpsProxyAgent(proxy);
@@ -37949,47 +37955,45 @@ function proxyPlugin(octokit) {
}
/**
* Find last successful workflow run on the repo
* @param {string?} workflow_id
* @param {number} run_id
* @param {string} owner
* @param {string} repo
* @param {string} branch
* @returns
*/
function findSuccessfulCommit(workflow_id, run_id, owner, repo, branch, lastSuccessfulEvent) {
return __awaiter(this, void 0, void 0, function* () {
const octokit = new ProxifiedClient();
if (!workflow_id) {
workflow_id = yield octokit.request(`GET /repos/${owner}/${repo}/actions/runs/${run_id}`, {
workflow_id = yield octokit
.request(`GET /repos/${owner}/${repo}/actions/runs/${run_id}`, {
owner,
repo,
branch,
run_id
}).then(({ data: { workflow_id } }) => workflow_id);
process.stdout.write('\n');
run_id,
})
.then(({ data: { workflow_id } }) => workflow_id);
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`, {
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,
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(shas);
status: "success",
})
.then(({ data: { workflow_runs } }) => workflow_runs.map((run) => run.head_sha));
return yield findExistingCommit(octokit, branch, shas);
});
}
function findMergeBaseRef() {
return __awaiter(this, void 0, void 0, function* () {
if (eventName == 'merge_group') {
if (eventName == "merge_group") {
const mergeQueueBranch = yield findMergeQueueBranch();
return `origin/${mergeQueueBranch}`;
}
else {
return 'HEAD';
return "HEAD";
}
});
}
@@ -38002,9 +38006,9 @@ function findMergeQueueBranch() {
return __awaiter(this, void 0, void 0, function* () {
const pull_number = findMergeQueuePr();
if (!pull_number) {
throw new Error('Failed to determine PR number');
throw new Error("Failed to determine PR number");
}
process.stdout.write('\n');
process.stdout.write("\n");
process.stdout.write(`Found PR #${pull_number} from merge queue branch\n`);
const octokit = new ProxifiedClient();
const result = yield octokit.request(`GET /repos/${owner}/${repo}/pulls/${pull_number}`, { owner, repo, pull_number: +pull_number });
@@ -38013,13 +38017,11 @@ function findMergeQueueBranch() {
}
/**
* Get first existing commit
* @param {string[]} commit_shas
* @returns {string?}
*/
function findExistingCommit(shas) {
function findExistingCommit(octokit, branchName, shas) {
return __awaiter(this, void 0, void 0, function* () {
for (const commitSha of shas) {
if (yield commitExists(commitSha)) {
if (yield commitExists(octokit, branchName, commitSha)) {
return commitSha;
}
}
@@ -38028,14 +38030,26 @@ function findExistingCommit(shas) {
}
/**
* Check if given commit is valid
* @param {string} commitSha
* @returns {boolean}
*/
function commitExists(commitSha) {
function commitExists(octokit, branchName, commitSha) {
return __awaiter(this, void 0, void 0, function* () {
try {
(0, child_process_1.spawnSync)('git', ['cat-file', '-e', commitSha], { stdio: ['pipe', 'pipe', null] });
return true;
(0, child_process_1.spawnSync)("git", ["cat-file", "-e", commitSha], {
stdio: ["pipe", "pipe", null],
});
// Check the commit exists in general
yield octokit.request("GET /repos/{owner}/{repo}/commits/{commit_sha}", {
owner,
repo,
commit_sha: commitSha,
});
// Check the commit exists on the expected main branch (it will not in the case of a rebased main branch)
const commits = yield octokit.request("GET /repos/{owner}/{repo}/commits", {
owner,
repo,
sha: branchName,
});
return commits.data.some((commit) => commit.sha === commitSha);
}
catch (_a) {
return false;
@@ -38044,10 +38058,9 @@ function commitExists(commitSha) {
}
/**
* Strips LF line endings from given string
* @param {string} string
*/
function stripNewLineEndings(string) {
return string.replace('\n', '');
return string.replace("\n", "");
}


260 changes: 166 additions & 94 deletions find-successful-workflow.ts
Original file line number Diff line number Diff line change
@@ -1,92 +1,126 @@
import { Octokit } from '@octokit/action';
import * as core from '@actions/core';
import * as github from '@actions/github';
import { spawnSync } from 'child_process';
import { existsSync } from 'fs';
import { HttpsProxyAgent } from 'https-proxy-agent'
import { getProxyForUrl } from 'proxy-from-env';

const { runId, repo: { repo, owner }, eventName } = github.context;
import * as core from "@actions/core";
import * as github from "@actions/github";
import { Octokit } from "@octokit/action";
import { spawnSync } from "child_process";
import { existsSync } from "fs";
import { HttpsProxyAgent } from "https-proxy-agent";
import { getProxyForUrl } from "proxy-from-env";

const {
runId,
repo: { repo, owner },
eventName,
} = github.context;
process.env.GITHUB_TOKEN = process.argv[2];
const mainBranchName = process.argv[3];
const errorOnNoSuccessfulWorkflow = process.argv[4];
const lastSuccessfulEvent = process.argv[5];
const workingDirectory = process.argv[6];
const workflowId = process.argv[7];
const defaultWorkingDirectory = '.';


const defaultWorkingDirectory = ".";

const ProxifiedClient = Octokit.plugin(
proxyPlugin
);
const ProxifiedClient = Octokit.plugin(proxyPlugin);

let BASE_SHA;
let BASE_SHA: string;
(async () => {
if (workingDirectory !== defaultWorkingDirectory) {
if (existsSync(workingDirectory)) {
process.chdir(workingDirectory);
} else {
process.stdout.write('\n');
process.stdout.write(`WARNING: Working directory '${workingDirectory}' doesn't exist.\n`);
process.stdout.write("\n");
process.stdout.write(
`WARNING: Working directory '${workingDirectory}' doesn't exist.\n`
);
}
}

const headResult = spawnSync('git', ['rev-parse', 'HEAD'], { encoding: 'utf-8' });
const headResult = spawnSync("git", ["rev-parse", "HEAD"], {
encoding: "utf-8",
});
const HEAD_SHA = headResult.stdout;


if (
(['pull_request', 'pull_request_target'].includes(eventName) && !github.context.payload.pull_request.merged) ||
eventName == 'merge_group'
(["pull_request", "pull_request_target"].includes(eventName) &&
!github.context.payload.pull_request.merged) ||
eventName == "merge_group"
) {
try {
const mergeBaseRef = await findMergeBaseRef();
const baseResult = spawnSync('git', ['merge-base', `origin/${mainBranchName}`, mergeBaseRef], { encoding: 'utf-8' });
const baseResult = spawnSync(
"git",
["merge-base", `origin/${mainBranchName}`, mergeBaseRef],
{ encoding: "utf-8" }
);
BASE_SHA = baseResult.stdout;
} catch (e) {
core.setFailed(e.message);
return;
}
} else {
try {
BASE_SHA = await findSuccessfulCommit(workflowId, runId, owner, repo, mainBranchName, lastSuccessfulEvent);
BASE_SHA = await findSuccessfulCommit(
workflowId,
runId,
owner,
repo,
mainBranchName,
lastSuccessfulEvent
);
} catch (e) {
core.setFailed(e.message);
return;
}

if (!BASE_SHA) {
if (errorOnNoSuccessfulWorkflow === 'true') {
if (errorOnNoSuccessfulWorkflow === "true") {
reportFailure(mainBranchName);
return;
} else {
process.stdout.write('\n');
process.stdout.write(`WARNING: Unable to find a successful workflow run on 'origin/${mainBranchName}'\n`);
process.stdout.write(`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`);
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`
);
process.stdout.write(
`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`
);
process.stdout.write("\n");

const commitCountOutput = spawnSync('git', ['rev-list', '--count', `origin/${mainBranchName}`], { encoding: 'utf-8' }).stdout;
const commitCount = parseInt(stripNewLineEndings(commitCountOutput), 10);
const commitCountOutput = spawnSync(
"git",
["rev-list", "--count", `origin/${mainBranchName}`],
{ encoding: "utf-8" }
).stdout;
const commitCount = parseInt(
stripNewLineEndings(commitCountOutput),
10
);

const LAST_COMMIT_CMD = `origin/${mainBranchName}${commitCount > 1 ? '~1' : ''}`
const baseRes = spawnSync('git', ['rev-parse', LAST_COMMIT_CMD], { encoding: 'utf-8' });
const LAST_COMMIT_CMD = `origin/${mainBranchName}${
commitCount > 1 ? "~1" : ""
}`;
const baseRes = spawnSync("git", ["rev-parse", LAST_COMMIT_CMD], {
encoding: "utf-8",
});
BASE_SHA = baseRes.stdout;
core.setOutput('noPreviousBuild', 'true');
core.setOutput("noPreviousBuild", "true");
}
} else {
process.stdout.write('\n');
process.stdout.write(`Found the last successful workflow run on 'origin/${mainBranchName}'\n`);
process.stdout.write("\n");
process.stdout.write(
`Found the last successful workflow run on 'origin/${mainBranchName}'\n`
);
process.stdout.write(`Commit: ${BASE_SHA}\n`);
}

}
core.setOutput('base', stripNewLineEndings(BASE_SHA));
core.setOutput('head', stripNewLineEndings(HEAD_SHA));
core.setOutput("base", stripNewLineEndings(BASE_SHA));
core.setOutput("head", stripNewLineEndings(HEAD_SHA));
})();

function reportFailure(branchName) {
function reportFailure(branchName: string): void {
core.setFailed(`
Unable to find a successful workflow run on 'origin/${branchName}'
NOTE: You have set 'error-on-no-successful-workflow' on the action so this is a hard error.
@@ -96,85 +130,104 @@ function reportFailure(branchName) {
- If no, then you might have changed your git history and those commits no longer exist.`);
}

function proxyPlugin(octokit: Octokit) {
octokit.hook.before('request', options => {
const proxy: URL = getProxyForUrl(options.baseUrl)
function proxyPlugin(octokit: Octokit): void {
octokit.hook.before("request", (options) => {
const proxy: URL = getProxyForUrl(options.baseUrl);
if (proxy) {
options.request.agent = new HttpsProxyAgent(proxy)
options.request.agent = new HttpsProxyAgent(proxy);
}
})
});
}

/**
* Find last successful workflow run on the repo
* @param {string?} workflow_id
* @param {number} run_id
* @param {string} owner
* @param {string} repo
* @param {string} branch
* @returns
*/
async function findSuccessfulCommit(workflow_id, run_id, owner, repo, branch, lastSuccessfulEvent) {
async function findSuccessfulCommit(
workflow_id: string | undefined,
run_id: number,
owner: string,
repo: string,
branch: string,
lastSuccessfulEvent: string
): Promise<string | undefined> {
const octokit = new ProxifiedClient();
if (!workflow_id) {
workflow_id = await octokit.request(`GET /repos/${owner}/${repo}/actions/runs/${run_id}`, {
owner,
repo,
branch,
run_id
}).then(({ data: { workflow_id } }) => workflow_id);
process.stdout.write('\n');
process.stdout.write(`Workflow Id not provided. Using workflow '${workflow_id}'\n`);
workflow_id = await octokit
.request(`GET /repos/${owner}/${repo}/actions/runs/${run_id}`, {
owner,
repo,
branch,
run_id,
})
.then(({ data: { workflow_id } }) => workflow_id);
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 = 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 => run.head_sha));

return await findExistingCommit(shas);
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)
);

return await findExistingCommit(octokit, branch, shas);
}

async function findMergeBaseRef() {
if (eventName == 'merge_group') {
async function findMergeBaseRef(): Promise<string> {
if (eventName == "merge_group") {
const mergeQueueBranch = await findMergeQueueBranch();
return `origin/${mergeQueueBranch}`;
} else {
return 'HEAD'
return "HEAD";
}
}

function findMergeQueuePr() {
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}$`).exec(head_ref);
const result = new RegExp(
`^refs/heads/gh-readonly-queue/${mainBranchName}/pr-(\\d+)-${base_sha}$`
).exec(head_ref);
return result ? result.at(1) : undefined;
}

async function findMergeQueueBranch() {
async function findMergeQueueBranch(): Promise<string> {
const pull_number = findMergeQueuePr();
if (!pull_number) {
throw new Error('Failed to determine PR number')
throw new Error("Failed to determine PR number");
}
process.stdout.write('\n');
process.stdout.write("\n");
process.stdout.write(`Found PR #${pull_number} from merge queue branch\n`);
const octokit = new ProxifiedClient();
const result = await octokit.request(`GET /repos/${owner}/${repo}/pulls/${pull_number}`, { owner, repo, pull_number: +pull_number });
const result = await octokit.request(
`GET /repos/${owner}/${repo}/pulls/${pull_number}`,
{ owner, repo, pull_number: +pull_number }
);
return result.data.head.ref;
}

/**
* Get first existing commit
* @param {string[]} commit_shas
* @returns {string?}
*/
async function findExistingCommit(shas) {
async function findExistingCommit(
octokit: Octokit,
branchName: string,
shas: string[]
): Promise<string | undefined> {
for (const commitSha of shas) {
if (await commitExists(commitSha)) {
if (await commitExists(octokit, branchName, commitSha)) {
return commitSha;
}
}
@@ -183,23 +236,42 @@ async function findExistingCommit(shas) {

/**
* Check if given commit is valid
* @param {string} commitSha
* @returns {boolean}
*/
async function commitExists(commitSha) {
async function commitExists(
octokit: Octokit,
branchName: string,
commitSha: string
): Promise<boolean> {
try {
spawnSync('git', ['cat-file', '-e', commitSha], { stdio: ['pipe', 'pipe', null] });
return true;
spawnSync("git", ["cat-file", "-e", commitSha], {
stdio: ["pipe", "pipe", null],
});

// Check the commit exists in general
await octokit.request("GET /repos/{owner}/{repo}/commits/{commit_sha}", {
owner,
repo,
commit_sha: commitSha,
});

// Check the commit exists on the expected main branch (it will not in the case of a rebased main branch)
const commits = await octokit.request("GET /repos/{owner}/{repo}/commits", {
owner,
repo,
sha: branchName,
});

return commits.data.some(
(commit: { sha: string }) => commit.sha === commitSha
);
} catch {
return false;
}
}

/**
* Strips LF line endings from given string
* @param {string} string
*/
function stripNewLineEndings(string) {
return string.replace('\n', '');
function stripNewLineEndings(string: string): string {
return string.replace("\n", "");
}

16 changes: 14 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"private": true,
"version": "4.0.1",
"version": "4.0.2",
"license": "MIT",
"description": "This package.json is here purely to control the version of the Action, in combination with https://github.com/JamesHenry/publish-shell-action",
"scripts": {
@@ -13,6 +13,11 @@
"engines": {
"node": ">=18"
},
"volta": {
"node": "20.9.0",
"yarn": "1.22.21"
},
"packageManager": "yarn@1.22.21",
"homepage": "https://github.com/nrwl/nx-set-shas#readme",
"dependencies": {
"@actions/core": "^1.10.0",
@@ -22,11 +27,18 @@
"proxy-from-env": "1.1.0"
},
"devDependencies": {
"@types/node": "^20.5.9",
"@types/node": "^20.9.2",
"@vercel/ncc": "^0.36.1",
"chalk": "^4.1.2",
"husky": "^8.0.1",
"is-ci": "^3.0.1",
"lint-staged": "15.1.0",
"prettier": "^3.1.0",
"typescript": "^5.2.0"
},
"lint-staged": {
"*.{ts,json,yml,md}": [
"npx prettier --write"
]
}
}
362 changes: 357 additions & 5 deletions yarn.lock

Large diffs are not rendered by default.

0 comments on commit 46af9ed

Please sign in to comment.