Merge pull request #11 from mshick/typescript

build(typescript): refactoring project with typescript and test setup
This commit is contained in:
Michael Shick 2020-05-21 13:19:15 -04:00 committed by GitHub
commit 81c7cad365
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
13 changed files with 4956 additions and 25713 deletions

14
.editorconfig Normal file
View 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
View file

@ -0,0 +1,2 @@
*.js
!/.github

View file

@ -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
View file

@ -1,3 +1,8 @@
# Ignore test runner output
__tests__/runner/*
!__tests__/runner/.gitkeep
node_modules/ node_modules/
# Editors # Editors

8
.prettierrc.json Normal file
View file

@ -0,0 +1,8 @@
{
"singleQuote": true,
"trailingComma": "all",
"bracketSpacing": false,
"printWidth": 120,
"tabWidth": 2,
"semi": false
}

View 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
View 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

File diff suppressed because one or more lines are too long

100
index.js
View file

@ -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
View 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

File diff suppressed because it is too large Load diff

View file

@ -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
View 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"]
}