mirror of
https://github.com/mshick/add-pr-comment.git
synced 2025-12-31 22:29:45 +11:00
Merge pull request #11 from mshick/typescript
build(typescript): refactoring project with typescript and test setup
This commit is contained in:
commit
81c7cad365
13 changed files with 4956 additions and 25713 deletions
14
.editorconfig
Normal file
14
.editorconfig
Normal file
|
|
@ -0,0 +1,14 @@
|
||||||
|
# editorconfig.org
|
||||||
|
|
||||||
|
root = true
|
||||||
|
|
||||||
|
[*]
|
||||||
|
indent_style = space
|
||||||
|
indent_size = 2
|
||||||
|
end_of_line = lf
|
||||||
|
charset = utf-8
|
||||||
|
trim_trailing_whitespace = true
|
||||||
|
insert_final_newline = true
|
||||||
|
|
||||||
|
[*.md]
|
||||||
|
trim_trailing_whitespace = false
|
||||||
2
.eslintignore
Normal file
2
.eslintignore
Normal file
|
|
@ -0,0 +1,2 @@
|
||||||
|
*.js
|
||||||
|
!/.github
|
||||||
|
|
@ -1,17 +1,40 @@
|
||||||
{
|
{
|
||||||
"env": {
|
"parser": "@typescript-eslint/parser",
|
||||||
"commonjs": true,
|
"plugins": ["@typescript-eslint", "prettier"],
|
||||||
"es6": true,
|
"extends": [
|
||||||
"node": true
|
"eslint:recommended",
|
||||||
|
"plugin:@typescript-eslint/recommended",
|
||||||
|
"prettier/@typescript-eslint",
|
||||||
|
"plugin:prettier/recommended"
|
||||||
|
],
|
||||||
|
"rules": {
|
||||||
|
"prettier/prettier": [
|
||||||
|
"error",
|
||||||
|
{
|
||||||
|
"singleQuote": true,
|
||||||
|
"trailingComma": "all",
|
||||||
|
"bracketSpacing": false,
|
||||||
|
"printWidth": 120,
|
||||||
|
"tabWidth": 2,
|
||||||
|
"semi": false
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"camelcase": "off",
|
||||||
|
"@typescript-eslint/camelcase": [
|
||||||
|
"error",
|
||||||
|
{
|
||||||
|
"properties": "never"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"@typescript-eslint/no-non-null-assertion": "off"
|
||||||
},
|
},
|
||||||
"extends": "eslint:recommended",
|
"env": {
|
||||||
"globals": {
|
"node": true,
|
||||||
"Atomics": "readonly",
|
"jest": true,
|
||||||
"SharedArrayBuffer": "readonly"
|
"es6": true
|
||||||
},
|
},
|
||||||
"parserOptions": {
|
"parserOptions": {
|
||||||
"ecmaVersion": 2018
|
"ecmaVersion": 2018,
|
||||||
},
|
"sourceType": "module"
|
||||||
"rules": {
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
5
.gitignore
vendored
5
.gitignore
vendored
|
|
@ -1,3 +1,8 @@
|
||||||
|
|
||||||
|
# Ignore test runner output
|
||||||
|
__tests__/runner/*
|
||||||
|
!__tests__/runner/.gitkeep
|
||||||
|
|
||||||
node_modules/
|
node_modules/
|
||||||
|
|
||||||
# Editors
|
# Editors
|
||||||
|
|
|
||||||
8
.prettierrc.json
Normal file
8
.prettierrc.json
Normal file
|
|
@ -0,0 +1,8 @@
|
||||||
|
{
|
||||||
|
"singleQuote": true,
|
||||||
|
"trailingComma": "all",
|
||||||
|
"bracketSpacing": false,
|
||||||
|
"printWidth": 120,
|
||||||
|
"tabWidth": 2,
|
||||||
|
"semi": false
|
||||||
|
}
|
||||||
41
__tests__/add-pr-comment.test.ts
Normal file
41
__tests__/add-pr-comment.test.ts
Normal file
|
|
@ -0,0 +1,41 @@
|
||||||
|
import * as core from '@actions/core'
|
||||||
|
import * as github from '@actions/github'
|
||||||
|
import {WebhookPayload} from '@actions/github/lib/interfaces'
|
||||||
|
import run from '../add-pr-comment'
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
jest.resetModules()
|
||||||
|
jest.spyOn(core, 'getInput').mockImplementation((name: string): string => {
|
||||||
|
switch (name) {
|
||||||
|
case 'message':
|
||||||
|
return 'hello world'
|
||||||
|
case 'repo-token':
|
||||||
|
return '12345'
|
||||||
|
case 'allow-repeats':
|
||||||
|
return 'false'
|
||||||
|
default:
|
||||||
|
return ''
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
// https://developer.github.com/webhooks/event-payloads/#issues
|
||||||
|
github.context.payload = {
|
||||||
|
action: 'created',
|
||||||
|
issue: {
|
||||||
|
number: 1,
|
||||||
|
},
|
||||||
|
comment: {
|
||||||
|
id: 1,
|
||||||
|
user: {
|
||||||
|
login: 'monalisa',
|
||||||
|
},
|
||||||
|
body: 'Honk',
|
||||||
|
},
|
||||||
|
} as WebhookPayload
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('add-pr-comment action', () => {
|
||||||
|
it('runs', async () => {
|
||||||
|
await expect(run()).resolves.not.toThrow()
|
||||||
|
})
|
||||||
|
})
|
||||||
143
add-pr-comment.ts
Normal file
143
add-pr-comment.ts
Normal file
|
|
@ -0,0 +1,143 @@
|
||||||
|
import * as core from '@actions/core'
|
||||||
|
import * as github from '@actions/github'
|
||||||
|
import {HttpClient} from '@actions/http-client'
|
||||||
|
import {Endpoints, RequestHeaders} from '@octokit/types'
|
||||||
|
import {Octokit} from '@octokit/rest'
|
||||||
|
|
||||||
|
type ListCommitPullsResponse = Endpoints['GET /repos/:owner/:repo/commits/:commit_sha/pulls']['response']
|
||||||
|
|
||||||
|
interface AddPrCommentInputs {
|
||||||
|
message: string
|
||||||
|
repoToken: string
|
||||||
|
allowRepeats: boolean
|
||||||
|
}
|
||||||
|
|
||||||
|
interface ListCommitPullsParams {
|
||||||
|
repoToken: string
|
||||||
|
owner: string
|
||||||
|
repo: string
|
||||||
|
commitSha: string
|
||||||
|
}
|
||||||
|
|
||||||
|
const listCommitPulls = async (params: ListCommitPullsParams): Promise<ListCommitPullsResponse | null> => {
|
||||||
|
const {repoToken, owner, repo, commitSha} = params
|
||||||
|
|
||||||
|
const http = new HttpClient('http-client-add-pr-comment')
|
||||||
|
|
||||||
|
const additionalHeaders: RequestHeaders = {
|
||||||
|
accept: 'application/vnd.github.groot-preview+json',
|
||||||
|
authorization: `token ${repoToken}`,
|
||||||
|
}
|
||||||
|
|
||||||
|
const body = await http.getJson<ListCommitPullsResponse>(
|
||||||
|
`https://api.github.com/repos/${owner}/${repo}/commits/${commitSha}/pulls`,
|
||||||
|
additionalHeaders,
|
||||||
|
)
|
||||||
|
|
||||||
|
return body.result
|
||||||
|
}
|
||||||
|
|
||||||
|
const getIssueNumberFromCommitPullsList = (commitPullsList: ListCommitPullsResponse): number | null =>
|
||||||
|
commitPullsList.data.length ? commitPullsList.data[0].number : null
|
||||||
|
|
||||||
|
const isMessagePresent = (
|
||||||
|
message: AddPrCommentInputs['message'],
|
||||||
|
comments: Octokit.IssuesListCommentsResponse,
|
||||||
|
): boolean => {
|
||||||
|
const cleanRe = new RegExp('\\R|\\s', 'g')
|
||||||
|
const messageClean = message.replace(cleanRe, '')
|
||||||
|
|
||||||
|
return comments.some(
|
||||||
|
({user, body}) =>
|
||||||
|
// First find candidate bot messages to avoid extra processing
|
||||||
|
user.login === 'github-actions[bot]' && body.replace(cleanRe, '') === messageClean,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
const getInputs = (): AddPrCommentInputs => {
|
||||||
|
return {
|
||||||
|
message: core.getInput('message'),
|
||||||
|
repoToken: core.getInput('repo-token'),
|
||||||
|
allowRepeats: Boolean(core.getInput('allow-repeats') === 'true'),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const run = async (): Promise<void> => {
|
||||||
|
try {
|
||||||
|
const {message, repoToken, allowRepeats} = getInputs()
|
||||||
|
|
||||||
|
core.debug(`input message: ${message}`)
|
||||||
|
core.debug(`input allow-repeats: ${allowRepeats}`)
|
||||||
|
|
||||||
|
const {
|
||||||
|
payload: {pull_request: pullRequest, repository},
|
||||||
|
sha: commitSha,
|
||||||
|
} = github.context
|
||||||
|
|
||||||
|
if (!repository) {
|
||||||
|
core.info('unable to determine repository from request type')
|
||||||
|
core.setOutput('comment-created', 'false')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
const {full_name: repoFullName} = repository!
|
||||||
|
const [owner, repo] = repoFullName!.split('/')
|
||||||
|
|
||||||
|
let issueNumber
|
||||||
|
|
||||||
|
if (pullRequest && pullRequest.number) {
|
||||||
|
issueNumber = pullRequest.number
|
||||||
|
} else {
|
||||||
|
// If this is not a pull request, attempt to find a PR matching the sha
|
||||||
|
const commitPullsList = await listCommitPulls({repoToken, owner, repo, commitSha})
|
||||||
|
issueNumber = commitPullsList && getIssueNumberFromCommitPullsList(commitPullsList)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!issueNumber) {
|
||||||
|
core.info('this action only works on pull_request events or other commits associated with a pull')
|
||||||
|
core.setOutput('comment-created', 'false')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
const octokit = new github.GitHub(repoToken)
|
||||||
|
|
||||||
|
let shouldCreateComment = true
|
||||||
|
|
||||||
|
if (!allowRepeats) {
|
||||||
|
core.debug('repeat comments are disallowed, checking for existing')
|
||||||
|
|
||||||
|
const {data: comments} = await octokit.issues.listComments({
|
||||||
|
owner,
|
||||||
|
repo,
|
||||||
|
issue_number: issueNumber,
|
||||||
|
})
|
||||||
|
|
||||||
|
if (isMessagePresent(message, comments)) {
|
||||||
|
core.info('the issue already contains an identical message')
|
||||||
|
shouldCreateComment = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (shouldCreateComment) {
|
||||||
|
await octokit.issues.createComment({
|
||||||
|
owner,
|
||||||
|
repo,
|
||||||
|
issue_number: issueNumber,
|
||||||
|
body: message,
|
||||||
|
})
|
||||||
|
|
||||||
|
core.setOutput('comment-created', 'true')
|
||||||
|
} else {
|
||||||
|
core.setOutput('comment-created', 'false')
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
core.setFailed(error.message)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Don't auto-execute in the test environment
|
||||||
|
if (process.env['NODE_ENV'] !== 'test') {
|
||||||
|
run()
|
||||||
|
}
|
||||||
|
|
||||||
|
export default run
|
||||||
25403
dist/index.js
vendored
25403
dist/index.js
vendored
File diff suppressed because one or more lines are too long
100
index.js
100
index.js
|
|
@ -1,100 +0,0 @@
|
||||||
const core = require("@actions/core");
|
|
||||||
const github = require("@actions/github");
|
|
||||||
const { HttpClient, Headers } = require("@actions/http-client");
|
|
||||||
|
|
||||||
const previewHeader = "application/vnd.github.groot-preview+json";
|
|
||||||
|
|
||||||
const getPulls = async (repoToken, repo, commitSha) => {
|
|
||||||
const http = new HttpClient("http-client-add-pr-comment");
|
|
||||||
|
|
||||||
const additionalHeaders = {
|
|
||||||
[Headers.Accept]: previewHeader,
|
|
||||||
[Headers.Authorization]: `token ${repoToken}`,
|
|
||||||
};
|
|
||||||
|
|
||||||
const body = await http.getJson(
|
|
||||||
`https://api.github.com/repos/${repo}/commits/${commitSha}/pulls`,
|
|
||||||
additionalHeaders
|
|
||||||
);
|
|
||||||
|
|
||||||
return body.result;
|
|
||||||
};
|
|
||||||
|
|
||||||
async function run() {
|
|
||||||
try {
|
|
||||||
const message = core.getInput("message");
|
|
||||||
const repoToken = core.getInput("repo-token");
|
|
||||||
const allowRepeats = Boolean(core.getInput("allow-repeats") === "true");
|
|
||||||
|
|
||||||
core.debug(`input message: ${message}`);
|
|
||||||
core.debug(`input allow-repeats: ${allowRepeats}`);
|
|
||||||
|
|
||||||
const {
|
|
||||||
payload: { pull_request: pullRequest, repository },
|
|
||||||
sha: commitSha,
|
|
||||||
} = github.context;
|
|
||||||
|
|
||||||
const { full_name: repoFullName } = repository;
|
|
||||||
|
|
||||||
let issueNumber;
|
|
||||||
|
|
||||||
if (pullRequest && pullRequest.number) {
|
|
||||||
issueNumber = pullRequest.number;
|
|
||||||
} else {
|
|
||||||
// If this is not a pull request, attempt to find a PR matching the sha
|
|
||||||
const pulls = await getPulls(repoToken, repoFullName, commitSha);
|
|
||||||
issueNumber = pulls.length ? pulls[0].number : null;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!issueNumber) {
|
|
||||||
core.info(
|
|
||||||
"this action only works on pull_request events or other commits associated with a pull"
|
|
||||||
);
|
|
||||||
core.setOutput("comment-created", "false");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const [owner, repo] = repoFullName.split("/");
|
|
||||||
|
|
||||||
const octokit = new github.GitHub(repoToken);
|
|
||||||
|
|
||||||
if (allowRepeats === false) {
|
|
||||||
core.debug("repeat comments are disallowed, checking for existing");
|
|
||||||
|
|
||||||
const { data: comments } = await octokit.issues.listComments({
|
|
||||||
owner,
|
|
||||||
repo,
|
|
||||||
issue_number: issueNumber,
|
|
||||||
});
|
|
||||||
|
|
||||||
const spacesRe = new RegExp("\\R|\\s", "g");
|
|
||||||
const messageClean = message.replace(spacesRe, "");
|
|
||||||
|
|
||||||
const commentExists = comments.some(
|
|
||||||
(c) =>
|
|
||||||
// First find candidate bot messages to avoid extra processing(
|
|
||||||
c.user.login === "github-actions[bot]" &&
|
|
||||||
c.body.replace(spacesRe, "") === messageClean
|
|
||||||
);
|
|
||||||
|
|
||||||
if (commentExists) {
|
|
||||||
core.info("the issue already contains this message");
|
|
||||||
core.setOutput("comment-created", "false");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
await octokit.issues.createComment({
|
|
||||||
owner,
|
|
||||||
repo,
|
|
||||||
issue_number: issueNumber,
|
|
||||||
body: message,
|
|
||||||
});
|
|
||||||
|
|
||||||
core.setOutput("comment-created", "true");
|
|
||||||
} catch (error) {
|
|
||||||
core.setFailed(error.message);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
run();
|
|
||||||
18
jest.config.js
Normal file
18
jest.config.js
Normal file
|
|
@ -0,0 +1,18 @@
|
||||||
|
const processStdoutWrite = process.stdout.write.bind(process.stdout)
|
||||||
|
process.stdout.write = (str, encoding, cb) => {
|
||||||
|
if (!str.match(/^##/)) {
|
||||||
|
return processStdoutWrite(str, encoding, cb)
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
clearMocks: true,
|
||||||
|
moduleFileExtensions: ['js', 'ts'],
|
||||||
|
testEnvironment: 'node',
|
||||||
|
testMatch: ['**/*.test.ts'],
|
||||||
|
transform: {
|
||||||
|
'^.+\\.ts$': 'ts-jest',
|
||||||
|
},
|
||||||
|
verbose: true,
|
||||||
|
}
|
||||||
4819
package-lock.json
generated
4819
package-lock.json
generated
File diff suppressed because it is too large
Load diff
24
package.json
24
package.json
|
|
@ -2,11 +2,12 @@
|
||||||
"name": "@mshick/add-pr-comment",
|
"name": "@mshick/add-pr-comment",
|
||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"description": "A GitHub Action which adds a comment to a Pull Request Issue.",
|
"description": "A GitHub Action which adds a comment to a Pull Request Issue.",
|
||||||
"main": "index.js",
|
"main": "dist/index.js",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"lint": "eslint index.js",
|
"build": "tsc --noEmit && ncc build add-pr-comment.ts -o dist -m",
|
||||||
"package": "ncc build index.js -o dist",
|
"lint": "eslint . --ext .ts",
|
||||||
"test": "eslint index.js"
|
"test": "tsc --noEmit && jest",
|
||||||
|
"clean": "rm -rf node_modules dist package-lock.json __tests__/runner/**/*"
|
||||||
},
|
},
|
||||||
"repository": {
|
"repository": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
|
|
@ -32,7 +33,18 @@
|
||||||
"@actions/http-client": "^1.0.8"
|
"@actions/http-client": "^1.0.8"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@zeit/ncc": "^0.22.1",
|
"@octokit/types": "^4.0.1",
|
||||||
"eslint": "^6.8.0"
|
"@types/jest": "^25.2.1",
|
||||||
|
"@types/node": "^13.11.1",
|
||||||
|
"@typescript-eslint/eslint-plugin": "^2.27.0",
|
||||||
|
"@typescript-eslint/parser": "^2.27.0",
|
||||||
|
"@zeit/ncc": "^0.22.0",
|
||||||
|
"eslint": "^6.8.0",
|
||||||
|
"eslint-config-prettier": "^6.10.1",
|
||||||
|
"eslint-plugin-prettier": "^3.1.2",
|
||||||
|
"jest": "^25.3.0",
|
||||||
|
"prettier": "^2.0.4",
|
||||||
|
"ts-jest": "^25.3.1",
|
||||||
|
"typescript": "^3.8.3"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
17
tsconfig.json
Normal file
17
tsconfig.json
Normal file
|
|
@ -0,0 +1,17 @@
|
||||||
|
{
|
||||||
|
"compilerOptions": {
|
||||||
|
"module": "commonjs",
|
||||||
|
"target": "esnext",
|
||||||
|
"lib": ["es2015", "es2017"],
|
||||||
|
"strict": true,
|
||||||
|
"esModuleInterop": true,
|
||||||
|
"skipLibCheck": true,
|
||||||
|
"noUnusedLocals": true,
|
||||||
|
"noUnusedParameters": true,
|
||||||
|
"noImplicitAny": true,
|
||||||
|
"removeComments": false,
|
||||||
|
"preserveConstEnums": true
|
||||||
|
},
|
||||||
|
"include": ["**/*.ts"],
|
||||||
|
"exclude": ["node_modules"]
|
||||||
|
}
|
||||||
Loading…
Add table
Add a link
Reference in a new issue