From ec7da651adf2f5435f88801ca6d6e7823088452f Mon Sep 17 00:00:00 2001 From: Prince Gupta Date: Mon, 16 Jan 2023 19:17:56 +0530 Subject: [PATCH] Issue #559, #193 --- .github/workflows/test-action-pr-comment.yml | 52 ++++++++++++++++++ package.json | 2 +- src/git.js | 5 +- src/github/api.js | 50 ++++++++++++++++- src/github/context.js | 56 ++++++++++++++++---- src/index.js | 14 +++-- 6 files changed, 160 insertions(+), 19 deletions(-) create mode 100644 .github/workflows/test-action-pr-comment.yml diff --git a/.github/workflows/test-action-pr-comment.yml b/.github/workflows/test-action-pr-comment.yml new file mode 100644 index 00000000..950109c4 --- /dev/null +++ b/.github/workflows/test-action-pr-comment.yml @@ -0,0 +1,52 @@ +name: Test Action (PR Comment) + +on: + issue_comment: + types: + - created + +permissions: + checks: write + contents: read + +jobs: + test: + name: Run action + if: github.event.issue.pull_request && contains(github.event.comment.body, 'run-lint') + runs-on: ${{ matrix.os }} + strategy: + fail-fast: false + matrix: + os: [ubuntu-latest, windows-latest, macos-latest] + + steps: + - name: Clone git repo + uses: actions/checkout@v3 + + - name: Checkout Pull Request + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: | + PR_URL="${{ github.event.issue.pull_request.url }}" + PR_NUM=${PR_URL##*/} + hub pr checkout $PR_NUM + - name: Set up Node.js + uses: actions/setup-node@v3 + with: + node-version-file: ".nvmrc" + cache: "yarn" + + - name: Install dependencies + run: yarn install + + - name: Build action + run: yarn build + + - name: Run linters + uses: ./ + with: + continue_on_error: false + eslint: true + prettier: true + prettier_extensions: "css,html,js,json,jsx,less,md,scss,ts,tsx,vue,yaml,yml" + neutral_check_on_warning: true \ No newline at end of file diff --git a/package.json b/package.json index 4f99e30a..9b60dfa9 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "lint-action", - "version": "2.2.0", + "version": "2.3.0", "description": "GitHub Action for detecting and fixing linting errors", "repository": "github:wearerequired/lint-action", "license": "MIT", diff --git a/src/git.js b/src/git.js index a67cb312..37376b3c 100644 --- a/src/git.js +++ b/src/git.js @@ -33,8 +33,9 @@ function checkOutRemoteBranch(context) { // Switch to remote branch core.info(`Switching to the "${context.branch}" branch`); - run(`git branch --force ${context.branch} --track ${remote}/${context.branch}`); - run(`git checkout ${context.branch}`); + // run(`git branch --force ${context.branch} --track ${remote}/${context.branch}`); + run(`git reset --hard ${remote}/${context.branch}`); + run(`git checkout -- ${context.branch}`); } /** diff --git a/src/github/api.js b/src/github/api.js index c28b7a36..addb8b42 100644 --- a/src/github/api.js +++ b/src/github/api.js @@ -99,4 +99,52 @@ async function createCheck(linterName, sha, context, lintResult, neutralCheckOnW } } -module.exports = { createCheck }; +/** + * Fetches a Pull Request on GitHub + * @param {string} repository - The Github Repository with user name ex: wearerequired/master + * @param {string} pullRequestNumber - SHA of the commit which should be annotated + * @param {string} token - github token for authentication + * @returns {object} pull request information + */ +async function fetchPullRequest(repository, pullRequestNumber, token) { + try { + core.info(`fetchPullRequest for owner ${repository}`); + const response = await request( + `${process.env.GITHUB_API_URL}/repos/${repository}/pulls/${pullRequestNumber}`, + { + method: "GET", + headers: { + "Content-Type": "application/json", + Accept: "application/json", + Authorization: `Bearer ${token}`, + "User-Agent": actionName, + }, + }, + ); + core.info( + `fetchPullRequest completed successfully for owner:${repository} pull:${pullRequestNumber}`, + ); + return response.data; + } catch (err) { + let errorMessage = err.message; + if (err.data) { + try { + const errorData = JSON.parse(err.data); + if (errorData.message) { + errorMessage += `. ${errorData.message}`; + } + if (errorData.documentation_url) { + errorMessage += ` ${errorData.documentation_url}`; + } + } catch (e) { + // Ignore + } + } + core.error(errorMessage); + throw new Error( + `Error while trying to fetch PR for owner:${repository} pull:${pullRequestNumber}`, + ); + } +} + +module.exports = { createCheck, fetchPullRequest }; diff --git a/src/github/context.js b/src/github/context.js index 0f19c694..9d198992 100644 --- a/src/github/context.js +++ b/src/github/context.js @@ -4,6 +4,7 @@ const core = require("@actions/core"); const { name: actionName } = require("../../package.json"); const { getEnv } = require("../utils/action"); +const { fetchPullRequest } = require("./api"); /** * GitHub Actions workflow's environment variables @@ -69,14 +70,23 @@ function parseEnvFile(eventPath) { * 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 + * @param {object | undefined} pullRequest - pull request payload associated to event * @returns {string} - Branch name */ -function parseBranch(eventName, event) { +function parseBranch(eventName, event, pullRequest) { if (eventName === "push" || eventName === "workflow_dispatch") { return event.ref.substring(11); // Remove "refs/heads/" from start of string } if (eventName === "pull_request" || eventName === "pull_request_target") { - return event.pull_request.head.ref; + return pullRequest.head.ref; + } + if (eventName === "issue_comment") { + if (event.issue.pull_request) { + return pullRequest.head.ref; + } + throw Error( + `${actionName} does not support issue_comment event that is not associated to a PR`, + ); } throw Error(`${actionName} does not support "${eventName}" GitHub events`); } @@ -86,20 +96,26 @@ function parseBranch(eventName, event) { * Fork detection is only supported for the "pull_request" event * @param {string} eventName - GitHub event type * @param {object} event - GitHub webhook event payload + * @param {object | undefined} pullRequest - pull request payload associated to event * @returns {GithubRepository} - Information about the GitHub repository and its fork (if it exists) */ -function parseRepository(eventName, event) { +function parseRepository(eventName, event, pullRequest) { const repoName = event.repository.full_name; const cloneUrl = event.repository.clone_url; let forkName; let forkCloneUrl; - if (eventName === "pull_request" || eventName === "pull_request_target") { + + if ( + eventName === "pull_request" || + eventName === "pull_request_target" || + (eventName === "issue_comment" && event.issue.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; + const headRepoName = pullRequest.head.repo.full_name; forkName = repoName === headRepoName ? undefined : headRepoName; - const headForkCloneUrl = event.pull_request.head.repo.clone_url; + const headForkCloneUrl = pullRequest.head.repo.clone_url; forkCloneUrl = cloneUrl === headForkCloneUrl ? undefined : headForkCloneUrl; } return { @@ -111,20 +127,38 @@ function parseRepository(eventName, event) { }; } +/** + * 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 + * @param {string} token - GitHub token + * @returns {Promise} - The payload corresponding to the pull request + */ +async function parsePullRequest(eventName, event, token) { + if (eventName === "pull_request" || eventName === "pull_request_target") { + return event.pull_request; + } + if (eventName === "issue_comment" && event.issue.pull_request) { + return fetchPullRequest(event.repository.full_name, event.issue.number, token); + } + return undefined; +} + /** * Returns information about the GitHub repository and action trigger event - * @returns {GithubContext} context - Information about the GitHub repository and action trigger - * event + * @returns {Promise} context - Information about the GitHub repository + * and action trigger event */ -function getContext() { +async function getContext() { const { actor, eventName, eventPath, token, workspace } = parseActionEnv(); const event = parseEnvFile(eventPath); + const pullRequest = await parsePullRequest(eventName, event, token); return { actor, - branch: parseBranch(eventName, event), + branch: await parseBranch(eventName, event, pullRequest), event, eventName, - repository: parseRepository(eventName, event), + repository: parseRepository(eventName, event, pullRequest), token, workspace, }; diff --git a/src/index.js b/src/index.js index ee488adb..2617df69 100644 --- a/src/index.js +++ b/src/index.js @@ -13,7 +13,7 @@ const { getSummary } = require("./utils/lint-result"); * Parses the action configuration and runs all enabled linters on matching files */ async function runAction() { - const context = getContext(); + const context = await getContext(); const autoFix = core.getInput("auto_fix") === "true"; const commit = core.getInput("commit") === "true"; const skipVerification = core.getInput("git_no_verify") === "true"; @@ -24,10 +24,16 @@ async function runAction() { const checkName = core.getInput("check_name", { required: true }); const neutralCheckOnWarning = core.getInput("neutral_check_on_warning") === "true"; const isPullRequest = - context.eventName === "pull_request" || context.eventName === "pull_request_target"; + context.eventName === "pull_request" || + context.eventName === "pull_request_target" || + (context.eventName === "issue_comment" && context.event.issue.pull_request); // If on a PR from fork: Display messages regarding action limitations - if (context.eventName === "pull_request" && context.repository.hasFork) { + if ( + (context.eventName === "pull_request" || + (context.eventName === "issue_comment" && context.event.issue.pull_request)) && + context.repository.hasFork + ) { core.error( "This action does not have permission to create annotations on forks. You may want to run it only on `pull_request_target` events with checks permissions set to write. See https://docs.github.com/en/actions/learn-github-actions/workflow-syntax-for-github-actions#permissions for details.", ); @@ -155,4 +161,4 @@ async function runAction() { runAction().catch((error) => { core.debug(error.stack || "No error stack trace"); core.setFailed(error.message); -}); +}); \ No newline at end of file