-
Notifications
You must be signed in to change notification settings - Fork 139
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Split up GitHub context functions, provide fork info
- Loading branch information
1 parent
b20f60f
commit 168fd66
Showing
9 changed files
with
412 additions
and
223 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file was deleted.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,68 @@ | ||
const { name: actionName } = require("../../package"); | ||
const { log } = require("../utils/action"); | ||
const request = require("../utils/request"); | ||
const { capitalizeFirstLetter } = require("../utils/string"); | ||
|
||
/** | ||
* Creates a new check on GitHub which annotates the relevant commit with linting errors | ||
* @param {string} linterName - Name of the linter for which a check should be created | ||
* @param {string} sha - SHA of the commit which should be annotated | ||
* @param {import('./context').GithubContext} context - Information about the GitHub repository and | ||
* action trigger event | ||
* @param {{isSuccess: boolean, warning: [], error: []}} lintResult - Parsed lint result | ||
* @param {string} summary - Summary for the GitHub check | ||
*/ | ||
async function createCheck(linterName, sha, context, lintResult, summary) { | ||
let annotations = []; | ||
for (const level of ["warning", "error"]) { | ||
annotations = [ | ||
...annotations, | ||
...lintResult[level].map(result => ({ | ||
path: result.path, | ||
start_line: result.firstLine, | ||
end_line: result.lastLine, | ||
annotation_level: level === "warning" ? "warning" : "failure", | ||
message: result.message, | ||
})), | ||
]; | ||
} | ||
|
||
// Only use the first 50 annotations (limit for a single API request) | ||
if (annotations.length > 50) { | ||
log( | ||
`There are more than 50 errors/warnings from ${linterName}. Annotations are created for the first 50 issues only.`, | ||
); | ||
annotations = annotations.slice(0, 50); | ||
} | ||
|
||
const body = { | ||
name: linterName, | ||
head_sha: sha, | ||
conclusion: lintResult.isSuccess ? "success" : "failure", | ||
output: { | ||
title: capitalizeFirstLetter(summary), | ||
summary: `${linterName} found ${summary}`, | ||
annotations, | ||
}, | ||
}; | ||
try { | ||
log(`Creating GitHub check with ${annotations.length} annotations for ${linterName}…`); | ||
await request(`https://api.github.com/repos/${context.repository.repoName}/check-runs`, { | ||
method: "POST", | ||
headers: { | ||
"Content-Type": "application/json", | ||
// "Accept" header is required to access Checks API during preview period | ||
Accept: "application/vnd.github.antiope-preview+json", | ||
Authorization: `Bearer ${context.token}`, | ||
"User-Agent": actionName, | ||
}, | ||
body, | ||
}); | ||
log(`${linterName} check created successfully`); | ||
} catch (err) { | ||
log(err, "error"); | ||
throw new Error(`Error trying to create GitHub check for ${linterName}: ${err.message}`); | ||
} | ||
} | ||
|
||
module.exports = { createCheck }; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,116 @@ | ||
const { readFileSync } = require("fs"); | ||
|
||
const { name: actionName } = require("../../package"); | ||
const { getEnv, getInput } = require("../utils/action"); | ||
|
||
/** | ||
* GitHub Actions workflow's environment variables | ||
* @typedef {{actor: string, eventName: string, eventPath: string, token: string, workspace: | ||
* string}} ActionEnv | ||
*/ | ||
|
||
/** | ||
* Information about the GitHub repository and its fork (if it exists) | ||
* @typedef {{repoName: string, forkName: string, hasFork: boolean}} GithubRepository | ||
*/ | ||
|
||
/** | ||
* Information about the GitHub repository and action trigger event | ||
* @typedef {{actor: string, branch: string, event: object, eventName: string, repository: | ||
* GithubRepository, token: string, workspace: string}} GithubContext | ||
*/ | ||
|
||
/** | ||
* Returns the GitHub Actions workflow's environment variables | ||
* @returns {ActionEnv} GitHub Actions workflow's environment variables | ||
*/ | ||
function parseActionEnv() { | ||
return { | ||
// Information provided by environment | ||
actor: getEnv("github_actor", true), | ||
eventName: getEnv("github_event_name", true), | ||
eventPath: getEnv("github_event_path", true), | ||
workspace: getEnv("github_workspace", true), | ||
|
||
// Information provided by action user | ||
token: getInput("github_token", true), | ||
}; | ||
} | ||
|
||
/** | ||
* Parse `event.json` file (file with the complete webhook event payload, automatically provided by | ||
* GitHub) | ||
* @param {string} eventPath - Path to the `event.json` file | ||
* @returns {object} - Webhook event payload | ||
*/ | ||
function parseEnvFile(eventPath) { | ||
const eventBuffer = readFileSync(eventPath); | ||
return JSON.parse(eventBuffer); | ||
} | ||
|
||
/** | ||
* Parses the name of the current branch from the GitHub webhook event | ||
* @param {string} eventName - GitHub event type | ||
* @param {object} event - GitHub webhook event payload | ||
* @returns {string} - Branch name | ||
*/ | ||
function parseBranch(eventName, event) { | ||
if (eventName === "push") { | ||
return event.ref.substring(11); // Remove "refs/heads/" from start of string | ||
} | ||
if (eventName === "pull_request") { | ||
return event.pull_request.head.ref; | ||
} | ||
throw Error(`${actionName} does not support "${eventName}" GitHub events`); | ||
} | ||
|
||
/** | ||
* Parses the name of the current repository and determines whether it has a corresponding fork. | ||
* Fork detection is only supported for the "pull_request" event | ||
* @param {string} eventName - GitHub event type | ||
* @param {object} event - GitHub webhook event payload | ||
* @returns {GithubRepository} - Information about the GitHub repository and its fork (if it exists) | ||
*/ | ||
function parseRepository(eventName, event) { | ||
const repoName = event.repository.full_name; | ||
let forkName; | ||
if (eventName === "pull_request") { | ||
// "pull_request" events are triggered on the repository where the PR is made. The PR branch can | ||
// be on the same repository (`forkRepository` is set to `null`) or on a fork (`forkRepository` | ||
// is defined) | ||
const headRepoName = event.pull_request.head.repo.full_name; | ||
forkName = repoName === headRepoName ? undefined : headRepoName; | ||
} | ||
return { | ||
repoName, | ||
forkName, | ||
hasFork: forkName != null && forkName !== repoName, | ||
}; | ||
} | ||
|
||
/** | ||
* Returns information about the GitHub repository and action trigger event | ||
* @returns {GithubContext} context - Information about the GitHub repository and action trigger | ||
* event | ||
*/ | ||
function getContext() { | ||
const { actor, eventName, eventPath, token, workspace } = parseActionEnv(); | ||
const event = parseEnvFile(eventPath); | ||
return { | ||
actor, | ||
branch: parseBranch(eventName, event), | ||
event, | ||
eventName, | ||
repository: parseRepository(eventName, event), | ||
token, | ||
workspace, | ||
}; | ||
} | ||
|
||
module.exports = { | ||
getContext, | ||
parseActionEnv, | ||
parseBranch, | ||
parseEnvFile, | ||
parseRepository, | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,48 @@ | ||
const { createCheck } = require("../../src/github/api"); | ||
const { | ||
EVENT_NAME, | ||
EVENT_PATH, | ||
FORK_REPOSITORY, | ||
REPOSITORY, | ||
REPOSITORY_DIR, | ||
TOKEN, | ||
USERNAME, | ||
} = require("./test-constants"); | ||
|
||
jest.mock("../../src/utils/request", () => | ||
// eslint-disable-next-line global-require | ||
jest.fn().mockReturnValue(require("./api-responses/check-runs.json")), | ||
); | ||
|
||
describe("createCheck()", () => { | ||
const LINT_RESULT = { | ||
isSuccess: true, | ||
warning: [], | ||
error: [], | ||
}; | ||
const context = { | ||
actor: USERNAME, | ||
event: {}, | ||
eventName: EVENT_NAME, | ||
eventPath: EVENT_PATH, | ||
repository: { | ||
repoName: REPOSITORY, | ||
forkName: FORK_REPOSITORY, | ||
hasFork: false, | ||
}, | ||
token: TOKEN, | ||
workspace: REPOSITORY_DIR, | ||
}; | ||
|
||
test("mocked request should be successful", async () => { | ||
await expect( | ||
createCheck("check-name", "sha", context, LINT_RESULT, "summary"), | ||
).resolves.toEqual(undefined); | ||
}); | ||
|
||
test("mocked request should fail when no lint results are provided", async () => { | ||
await expect(createCheck("check-name", "sha", context, null, "summary")).rejects.toEqual( | ||
expect.any(Error), | ||
); | ||
}); | ||
}); |
Oops, something went wrong.