Migrate to TypeScript to avoid silent errors and improve debugging

This commit is contained in:
Pedro Pombeiro 2019-01-17 18:00:26 +01:00
parent 772c3dc438
commit aaed828003
No known key found for this signature in database
GPG Key ID: A65DEB11E4BBC647
13 changed files with 965 additions and 861 deletions

14
.vscode/launch.json vendored
View File

@ -6,15 +6,11 @@
"configurations": [ "configurations": [
{ {
"type": "node", "type": "node",
"request": "launch", "request": "attach",
"name": "Debug", "name": "Attach to Process",
"program": "${workspaceFolder}/node_modules/probot/bin/probot-run.js", "restart": true,
"args": [ "protocol": "inspector",
"${workspaceFolder}/index.js" "port": 9229
],
"env": {
"DEBUG": "true"
}
} }
] ]
} }

3
.vscode/settings.json vendored Normal file
View File

@ -0,0 +1,3 @@
{
"typescript.tsdk": "node_modules/typescript/lib"
}

203
index.js
View File

@ -1,203 +0,0 @@
// Checks API example
// See: https://developer.github.com/v3/checks/ to learn more
const pendingChecks = []
const checkDependencies = require('./lib/dependency-check')
const Humanize = require('humanize-plus')
module.exports = app => {
app.on(['check_suite.requested'], checkSuiteRequested)
app.on(['check_run.rerequested'], checkRunRerequested)
async function checkSuiteRequested(context) {
if (context.isBot) {
return
}
return await checkSuiteAsync(context, context.payload.check_suite)
}
async function checkRunRerequested(context) {
if (context.isBot) {
return
}
const { check_suite } = context.payload.check_run
return await checkSuiteAsync(context, check_suite)
}
async function checkSuiteAsync(context, check_suite) {
const { head_branch, head_sha } = check_suite
// Probot API note: context.repo() => {username: 'hiimbex', repo: 'testing-things'}
const check = context.repo({
name: 'packages-check-bot',
head_branch: head_branch,
head_sha: head_sha,
started_at: (new Date()).toISOString()
})
try {
if (pendingChecks[head_sha]) {
// Already running, ignore
return
}
context.log.info(`checking ${context.payload.repository.full_name}#${head_branch} (${head_sha}) (check_suite.id #${check_suite.id})
Pull requests: ${Humanize.oxford(check_suite.pull_requests.map(pr => pr.url), 5)}`)
check.status = 'in_progress'
check.output = {
title: 'package.json check',
summary: 'Checking any new/updated dependencies...'
}
if (context.payload.action === 'rerequested') {
pendingChecks[head_sha] = { ...check, check_run_id: context.payload.check_run.id }
queueCheckAsync(context, check_suite)
return
} else {
pendingChecks[head_sha] = { ...check }
const createResponse = await context.github.checks.create(check)
context.log.debug(`create checks status: ${createResponse.status}`)
pendingChecks[head_sha] = { ...check, check_run_id: createResponse.data.id }
queueCheckAsync(context, check_suite)
return createResponse
}
} catch (e) {
context.log.error(e)
// Report error back to GitHub
check.status = 'completed'
check.conclusion = 'cancelled'
check.completed_at = (new Date()).toISOString()
check.output = {
title: 'package.json check',
summary: e.message
}
return context.github.checks.create(check)
}
}
// For more information on building apps:
// https://probot.github.io/docs/
// To get your app running against GitHub, see:
// https://probot.github.io/docs/development/
}
const timeout = ms => new Promise(res => setTimeout(res, ms))
async function queueCheckAsync(context, check_suite) {
try {
const { before, head_sha } = check_suite
const compareResponse = await context.github.repos.compareCommits(context.repo({
base: before,
head: head_sha
}))
context.log.debug(`compare commits status: ${compareResponse.status}, ${compareResponse.data.files.length} file(s)`)
let check = pendingChecks[head_sha]
let checkedDepCount = 0
let packageJsonFilenames = []
const packageFilenameRegex = /^package\.json(.orig)?$/g
check.output.annotations = undefined
for (const file of compareResponse.data.files) {
switch (file.status) {
case 'added':
case 'modified':
if (packageFilenameRegex.test(file.filename)) {
packageJsonFilenames.push(file.filename)
checkedDepCount += await checkPackageFileAsync(check, context, file, head_sha)
}
break
}
}
check.status = 'completed'
check.completed_at = (new Date()).toISOString()
if (!check.output.annotations) {
check.conclusion = 'neutral'
check.output.summary = 'No changes to dependencies'
} else if (check.output.annotations.length === 0) {
check.conclusion = 'success'
check.output.summary = 'All dependencies are good!'
} else {
check.conclusion = 'failure'
const warnings = check.output.annotations.filter(a => a.annotation_level === 'warning').length
const failures = check.output.annotations.filter(a => a.annotation_level === 'failure').length
const uniqueProblemDependencies = [...new Set(check.output.annotations.map(a => a.dependency))]
check.output.summary = `Checked ${checkedDepCount} ${Humanize.pluralize(checkedDepCount, 'dependency', 'dependencies')} in ${Humanize.oxford(packageJsonFilenames.map(f => `\`${f}\``), 3)}.
${Humanize.boundedNumber(failures, 10)} ${Humanize.pluralize(failures, 'failure')}, ${Humanize.boundedNumber(warnings, 10)} ${Humanize.pluralize(warnings, 'warning')} in ${Humanize.oxford(uniqueProblemDependencies.map(f => `\`${f}\``), 3)} need your attention!`
}
// Remove helper data from annotation objects
const annotations = check.output.annotations
for (const annotation of annotations) {
delete annotation['dependency']
}
for (let annotationIndex = 0; annotationIndex < annotations.length; annotationIndex += 50) {
const annotationsSlice = annotations.length > 50 ? annotations.slice(annotationIndex, annotationIndex + 50) : annotations
check.output.annotations = annotationsSlice
for (let attempts = 3; attempts >= 0; ) {
try {
const updateResponse = await context.github.checks.update({
owner: check.owner,
repo: check.repo,
check_run_id: check.check_run_id,
name: check.name,
//details_url: check.details_url,
external_id: check.external_id,
started_at: check.started_at,
status: check.status,
conclusion: check.conclusion,
completed_at: check.completed_at,
output: check.output
})
context.log.debug(`update checks status: ${updateResponse.status}`)
break
} catch (error) {
if (--attempts <= 0) {
throw error
}
context.log.warn(`error while updating check run, will try again in 30 seconds: ${error.message}`)
await timeout(30000)
}
}
}
check.output.annotations = annotations
delete pendingChecks[head_sha]
} catch (error) {
context.log.error(error)
// This function isn't usually awaited for, so there's no point in rethrowing
}
}
async function checkPackageFileAsync(check, context, file, head_sha) {
const contentsResponse = await context.github.repos.getContents(context.repo({
path: file.filename,
ref: head_sha
}))
context.log.debug(`get contents response: ${contentsResponse.status}`)
if (contentsResponse.status >= 300) {
throw new `HTTP error ${contentsResponse.status} (${contentsResponse.statusText}) fetching ${file.filename}`
}
const contents = Buffer.from(contentsResponse.data.content, 'base64').toString('utf8')
const contentsJSON = JSON.parse(contents)
let dependencyCount = 0
dependencyCount += checkDependencies(contents, contentsJSON.dependencies, file, check)
dependencyCount += checkDependencies(contents, contentsJSON.devDependencies, file, check)
dependencyCount += checkDependencies(contents, contentsJSON.optionalDependencies, file, check)
return dependencyCount
}

8
jest.config.js Normal file
View File

@ -0,0 +1,8 @@
module.exports = {
roots: ['<rootDir>/src/', '<rootDir>/test/'],
transform: {
'^.+\\.tsx?$': 'ts-jest'
},
testRegex: '(/__tests__/.*|\\.(test|spec))\\.[tj]sx?$',
moduleFileExtensions: ['ts', 'tsx', 'js', 'jsx', 'json', 'node']
}

View File

@ -1,105 +0,0 @@
module.exports = checkDependencies
function checkDependencies(contents, dependencies, file, check) {
if (!dependencies) {
return 0
}
const urlRegex = /^(http:\/\/|https:\/\/|git\+http:\/\/|git\+https:\/\/|ssh:\/\/|git\+ssh:\/\/|github:)([a-zA-Z0-9_\-./]+)(#(.*))?$/gm
const requiredProtocol = 'git+https://'
let dependencyCount = 0
for (const dependency in dependencies) {
if (dependencies.hasOwnProperty(dependency)) {
++dependencyCount
const url = dependencies[dependency]
const match = urlRegex.exec(url)
if (!match) {
continue
}
const protocol = match[1]
const address = match[2]
const tag = match.length > 4 ? match[4] : null
const { line } = findLineColumn(contents, contents.indexOf(url))
const optimalAddress = address.endsWith('.git') ? address : address.concat('.git')
const optimalTag = isTag(tag) ? tag : '#<release-tag>'
const suggestedUrl = `${requiredProtocol}${optimalAddress}${optimalTag}`
const annotationSource = {
check: check,
dependency: dependency,
file: file,
line: line
}
if (protocol !== requiredProtocol) {
createAnnotation(annotationSource, suggestedUrl, {
annotation_level: 'warning',
title: `Found protocol ${protocol} being used in dependency`,
message: `Protocol should be ${requiredProtocol}.`
})
}
if (protocol !== 'github:' && !address.endsWith('.git')) {
createAnnotation(annotationSource, suggestedUrl, {
annotation_level: 'warning',
title: 'Address should end with .git for consistency.',
message: 'Android builds have been known to fail when dependency addresses don\'t end with .git.'
})
}
if (!tag) {
createAnnotation(annotationSource, suggestedUrl, {
annotation_level: 'failure',
title: 'Dependency is not locked with a tag/release.',
message: `${url} is not a deterministic dependency locator.\r\nIf the branch advances, it will be impossible to rebuild the same output in the future.`
})
} else if (!isTag(tag)) {
createAnnotation(annotationSource, suggestedUrl, {
annotation_level: 'failure',
title: 'Dependency is locked with a branch, instead of a tag/release.',
message: `${url} is not a deterministic dependency locator.\r\nIf the branch advances, it will be impossible to rebuild the same output in the future.`
})
}
}
}
return dependencyCount
}
function findLineColumn (contents, index) {
const lines = contents.split('\n')
const line = contents.substr(0, index).split('\n').length
const startOfLineIndex = (() => {
const x = lines.slice(0)
x.splice(line - 1)
return x.join('\n').length + (x.length > 0)
})()
const col = index - startOfLineIndex
return { line, col }
}
function createAnnotation(annotationSource, suggestedUrl, annotation) {
const { check, dependency, file, line } = annotationSource
if (!check.output.annotations) {
check.output.annotations = []
}
annotation.message = annotation.message.concat(`\r\n\r\nSuggested URL: ${suggestedUrl}`)
check.output.annotations.push({
...annotation,
dependency: dependency,
path: file.filename,
start_line: line,
end_line: line,
raw_details: `{suggestedUrl: ${suggestedUrl}}`
})
}
function isTag(tag) {
// TODO: We need to check the actual repo to see if it is a branch or a tag
return tag && tag !== 'master' && tag !== 'develop'
}

6
nodemon.json Normal file
View File

@ -0,0 +1,6 @@
{
"ignore": ["**/*.test.ts", "**/*.spec.ts", ".git", "node_modules"],
"watch": ["src"],
"exec": "yarn _start-dev",
"ext": "ts"
}

View File

@ -1,52 +1,68 @@
{ {
"name": "packages-check-bot",
"version": "1.0.0",
"description": "checks changes to packages.json to ensure that URL schemes match intended pattern and that forks are referenced with a tag, instead of a branch.",
"author": "Pedro Pombeiro <pombeirp@users.noreply.github.com> (https://status.im)", "author": "Pedro Pombeiro <pombeirp@users.noreply.github.com> (https://status.im)",
"license": "ISC",
"repository": "https://github.com/status-im/packages-check-bot.git",
"homepage": "https://github.com/status-im/packages-check-bot",
"bugs": "https://github.com/status-im/packages-check-bot/issues", "bugs": "https://github.com/status-im/packages-check-bot/issues",
"dependencies": {
"@types/humanize-plus": "^1.8.0",
"@types/nock": "^9.3.0",
"humanize-plus": "^1.8.2",
"nock": "^10.0.0",
"probot": "^7.2.0"
},
"description": "checks changes to packages.json to ensure that URL schemes match intended pattern and that forks are referenced with a tag, instead of a branch.",
"devDependencies": {
"@types/jest": "^23.1.5",
"@types/node": "^10.12.18",
"eslint-plugin-typescript": "^0.12.0",
"jest": "^23.4.0",
"nodemon": "^1.18.9",
"smee-client": "^1.0.2",
"standard": "^10.0.3",
"ts-jest": "^23.0.0",
"tslint": "^5.12.1",
"typescript": "3.0.1",
"typescript-eslint-parser": "^18.0.0",
"typescript-tslint-plugin": "^0.2.1"
},
"engines": {
"node": ">= 8.3.0"
},
"homepage": "https://github.com/status-im/packages-check-bot",
"jest": {
"testEnvironment": "node"
},
"keywords": [ "keywords": [
"probot", "probot",
"github", "github",
"probot-app" "probot-app"
], ],
"scripts": { "license": "ISC",
"dev": "nodemon", "name": "packages-check-bot",
"start": "probot run ./index.js",
"lint": "standard --fix",
"test": "jest && standard",
"test:watch": "jest --watch --notify --notifyMode=change --coverage"
},
"dependencies": {
"humanize-plus": "^1.8.2",
"probot": "^7.2.0"
},
"devDependencies": {
"eslint": "^5.12.0",
"jest": "^22.4.3",
"nock": "^10.0.0",
"nodemon": "^1.17.2",
"smee-client": "^1.0.2",
"standard": "^10.0.3"
},
"engines": {
"node": ">= 8.3.0"
},
"standard": {
"env": [
"jest"
]
},
"nodemonConfig": { "nodemonConfig": {
"exec": "npm start", "exec": "npm start",
"watch": [ "watch": [
".env", ".env",
"." "./lib"
] ]
}, },
"jest": { "repository": "https://github.com/status-im/packages-check-bot.git",
"testEnvironment": "node" "scripts": {
} "build": "tsc -p tsconfig.json",
"postinstall": "yarn build",
"dev": "./node_modules/nodemon/bin/nodemon.js",
"_start-dev": "./scripts/predebug.sh; yarn build && node --inspect ./node_modules/probot/bin/probot-run.js ./lib/index.js",
"lint": "standard **/*.ts --fix",
"start": "probot run ./lib/index.js",
"test": "jest && standard **/*.ts",
"test:watch": "jest --watch --notify --notifyMode=change --coverage"
},
"standard": {
"env": [
"jest"
],
"parser": "typescript-eslint-parser",
"plugins": [
"typescript"
]
},
"version": "1.0.0"
} }

7
scripts/predebug.sh Executable file
View File

@ -0,0 +1,7 @@
#!/bin/bash
APP_PORT=3000
APP_PID="$(lsof -i :${APP_PORT} | awk 'NR!=1 {print $2}' | sort -u | tr '\r\n' ' ')"
if [ ! -z "$APP_PID" ]; then
kill $APP_PID
fi

161
src/dependency-check.ts Normal file
View File

@ -0,0 +1,161 @@
export class AnnotationResult {
title?: string
message: string
annotationLevel: 'notice' | 'warning' | 'failure'
dependency: Dependency
path: string
startLine: number
endLine: number
rawDetails?: string
constructor (title: string,
message: string,
annotationLevel: "notice" | "warning" | "failure",
dependency: Dependency,
path: string,
startLine: number,
endLine: number,
rawDetails: string) {
this.title = title
this.message = message
this.annotationLevel = annotationLevel
this.dependency = dependency
this.path = path
this.startLine = startLine
this.endLine = endLine
this.rawDetails = rawDetails
}
}
export class AnalysisResult {
checkedDependencyCount!: number
annotations!: AnnotationResult[]
constructor () {
this.checkedDependencyCount = 0
this.annotations = []
}
}
export class Dependency {
name!: string
url!: string
constructor (name: string, url: string) {
this.name = name
this.url = url
}
}
type annotationSource = {
dependency: Dependency,
filename: string,
line: number
}
export function getDependenciesFromJSON (dependenciesJSON: any): Dependency[] {
const dependencies: Dependency[] = []
for (const name in dependenciesJSON) {
if (dependenciesJSON.hasOwnProperty(name)) {
dependencies.push(new Dependency(name, dependenciesJSON[name]))
}
}
return dependencies
}
export function checkDependencies (contents: string, dependencies: Dependency[], filename: string, result: AnalysisResult) {
if (!dependencies || dependencies.length === 0) {
return
}
const urlRegex = /^(http:\/\/|https:\/\/|git\+http:\/\/|git\+https:\/\/|ssh:\/\/|git\+ssh:\/\/|github:)([a-zA-Z0-9_\-./]+)(#(.*))?$/gm
const requiredProtocol = 'git+https://'
result.checkedDependencyCount += dependencies.length
for (const dependency of dependencies) {
const url = dependency.url
const match = urlRegex.exec(url)
if (!match) {
continue
}
const protocol = match[1]
const address = match[2]
const tag = match.length > 4 ? match[4] : ''
const { line } = findLineColumn(contents, contents.indexOf(url))
const optimalAddress = address.endsWith('.git') ? address : address.concat('.git')
const optimalTag = isTag(tag) ? tag : '#<release-tag>'
const suggestedUrl = `${requiredProtocol}${optimalAddress}${optimalTag}`
const annotationSource: annotationSource = {
dependency: dependency,
filename: filename,
line: line
}
if (protocol !== requiredProtocol) {
result.annotations.push(createAnnotation(annotationSource, suggestedUrl, 'warning',
`Found protocol ${protocol} being used in dependency`,
`Protocol should be ${requiredProtocol}.`))
}
if (protocol !== 'github:' && !address.endsWith('.git')) {
result.annotations.push(createAnnotation(annotationSource, suggestedUrl, 'warning',
'Address should end with .git for consistency.',
'Android builds have been known to fail when dependency addresses don\'t end with .git.'
))
}
if (!tag) {
result.annotations.push(createAnnotation(annotationSource, suggestedUrl, 'failure',
'Dependency is not locked with a tag/release.',
`${url} is not a deterministic dependency locator.\r\nIf the branch advances, it will be impossible to rebuild the same output in the future.`
))
} else if (!isTag(tag)) {
result.annotations.push(createAnnotation(annotationSource, suggestedUrl, 'failure',
'Dependency is locked with a branch, instead of a tag/release.',
`${url} is not a deterministic dependency locator.\r\nIf the branch advances, it will be impossible to rebuild the same output in the future.`
))
}
}
}
function findLineColumn (contents: string, index: number) {
const lines = contents.split('\n')
const line = contents.substr(0, index).split('\n').length
const startOfLineIndex = (() => {
const x = lines.slice(0)
x.splice(line - 1)
return x.join('\n').length + (x.length > 0 ? 1 : 0)
})()
const col = index - startOfLineIndex
return { line, col }
}
function createAnnotation (
annotationSource: annotationSource,
suggestedUrl: string,
annotationLevel: "notice" | "warning" | "failure",
title: string,
message: string): AnnotationResult {
const { dependency, filename, line } = annotationSource
return new AnnotationResult(
title,
message.concat(`\r\n\r\nSuggested URL: ${suggestedUrl}`),
annotationLevel,
dependency,
filename,
line,
line,
`{suggestedUrl: ${suggestedUrl}}`
)
}
function isTag (tag: string): boolean {
// TODO: We need to check the actual repo to see if it is a branch or a tag
return tag !== '' && tag !== 'master' && tag !== 'develop'
}

215
src/index.ts Normal file
View File

@ -0,0 +1,215 @@
// Checks API example
// See: https://developer.github.com/v3/checks/ to learn more
import { Application, Context } from 'probot' // eslint-disable-line no-unused-vars
import Octokit from '@octokit/rest'
import Humanize from 'humanize-plus'
import { checkDependencies, getDependenciesFromJSON, AnalysisResult, AnnotationResult } from './dependency-check'
const pendingChecks: any = []
export = (app: Application) => {
app.on(['check_suite.requested'], async (context) => { await checkSuiteAsync(context, context.payload.check_suite) })
app.on(['check_run.rerequested'], async (context) => {
const { check_suite } = context.payload.check_run
await checkSuiteAsync(context, check_suite)
})
async function checkSuiteAsync (context: Context, checkSuite: Octokit.ChecksCreateSuiteResponse): Promise<Octokit.Response<Octokit.ChecksCreateResponse>> {
const { head_branch: headBranch, head_sha: headSHA } = checkSuite
// Probot API note: context.repo() => {username: 'hiimbex', repo: 'testing-things'}
const check: Octokit.ChecksCreateParams = context.repo({
name: 'packages-check-bot',
head_branch: headBranch,
head_sha: headSHA,
started_at: (new Date()).toISOString()
})
try {
context.log.info(`checking ${context.payload.repository.full_name}#${headBranch} (${headSHA}) (check_suite.id #${checkSuite.id})
Pull requests: ${Humanize.oxford(checkSuite.pull_requests.map(pr => pr.url), 5)}`)
check.status = 'in_progress'
check.output = {
title: 'package.json check',
summary: 'Checking any new/updated dependencies...'
}
const alreadyQueued = pendingChecks[headSHA]
if (context.payload.action === 'rerequested') {
if (!alreadyQueued) {
pendingChecks[headSHA] = { ...check, check_run_id: context.payload.check_run.id }
queueCheckAsync(context, checkSuite)
}
const createResponse = await context.github.checks.create(check)
context.log.debug(`create checks status: ${createResponse.status}`)
return createResponse
} else {
if (!alreadyQueued) {
pendingChecks[headSHA] = { ...check }
}
const createResponse = await context.github.checks.create(check)
context.log.debug(`create checks status: ${createResponse.status}`)
if (!alreadyQueued) {
pendingChecks[headSHA] = { ...check, check_run_id: createResponse.data.id }
queueCheckAsync(context, checkSuite)
}
return createResponse
}
} catch (e) {
context.log.error(e)
// Report error back to GitHub
check.status = 'completed'
check.conclusion = 'cancelled'
check.completed_at = (new Date()).toISOString()
check.output = {
title: 'package.json check',
summary: e.message
}
return context.github.checks.create(check)
}
}
// For more information on building apps:
// https://probot.github.io/docs/
// To get your app running against GitHub, see:
// https://probot.github.io/docs/development/
}
const timeout = (ms: number) => new Promise(resolve => setTimeout(resolve, ms))
async function queueCheckAsync (context: Context, checkSuite: Octokit.ChecksCreateSuiteResponse) {
try {
const { before, head_sha } = checkSuite
const compareResponse = await context.github.repos.compareCommits(context.repo({
base: before,
head: head_sha
}))
context.log.debug(`compare commits status: ${compareResponse.status}, ${compareResponse.data.files.length} file(s)`)
let check = pendingChecks[head_sha]
let checkedDepCount = 0
let packageJsonFilenames = []
const packageFilenameRegex = /^package\.json(.orig)?$/g
check.output.annotations = undefined
let analysisResult = new AnalysisResult()
for (const file of compareResponse.data.files) {
switch (file.status) {
case 'added':
case 'modified':
if (packageFilenameRegex.test(file.filename)) {
packageJsonFilenames.push(file.filename)
checkedDepCount += await checkPackageFileAsync(analysisResult, context, file.filename, head_sha)
}
break
}
}
check.status = 'completed'
check.completed_at = (new Date()).toISOString()
if (analysisResult.checkedDependencyCount === 0) {
check.conclusion = 'neutral'
check.output.summary = 'No changes to dependencies'
} else if (analysisResult.annotations.length === 0) {
check.conclusion = 'success'
check.output.summary = 'All dependencies are good!'
} else {
check.conclusion = 'failure'
const warnings = analysisResult.annotations.filter(a => a.annotationLevel === 'warning').length
const failures = analysisResult.annotations.filter(a => a.annotationLevel === 'failure').length
const uniqueProblemDependencies = [...new Set(analysisResult.annotations.map(a => a.dependency))]
check.output.summary = `Checked ${checkedDepCount} ${Humanize.pluralize(checkedDepCount, 'dependency', 'dependencies')} in ${Humanize.oxford(packageJsonFilenames.map(f => `\`${f}\``), 3)}.
${Humanize.boundedNumber(failures, 10)} ${Humanize.pluralize(failures, 'failure')}, ${Humanize.boundedNumber(warnings, 10)} ${Humanize.pluralize(warnings, 'warning')} in ${Humanize.oxford(uniqueProblemDependencies.map(f => `\`${f}\``), 3)} need your attention!`
}
for (let annotationIndex = 0; annotationIndex < analysisResult.annotations.length; annotationIndex += 50) {
const annotationsSlice = analysisResult.annotations.length > 50 ? analysisResult.annotations.slice(annotationIndex, annotationIndex + 50) : analysisResult.annotations
convertAnnotationResults(check, annotationsSlice)
for (let attempts = 3; attempts >= 0;) {
try {
const updateResponse = await context.github.checks.update({
owner: check.owner,
repo: check.repo,
check_run_id: check.check_run_id,
name: check.name,
//details_url: check.details_url,
external_id: check.external_id,
started_at: check.started_at,
status: check.status,
conclusion: check.conclusion,
completed_at: check.completed_at,
output: check.output
})
context.log.debug(`update checks status: ${updateResponse.status}`)
break
} catch (error) {
if (--attempts <= 0) {
throw error
}
context.log.warn(`error while updating check run, will try again in 30 seconds: ${error.message}`)
await timeout(30000)
}
}
}
delete pendingChecks[head_sha]
} catch (error) {
context.log.error(error)
// This function isn't usually awaited for, so there's no point in rethrowing
}
}
async function checkPackageFileAsync (analysisResult: AnalysisResult, context: Context, filename: string, headSHA: string) {
const contentsResponse: any = await context.github.repos.getContents(context.repo({
path: filename,
ref: headSHA
}))
context.log.debug(`get contents response: ${contentsResponse.status}`)
if (contentsResponse.status >= 300) {
throw new Error(`HTTP error ${contentsResponse.status} (${contentsResponse.statusText}) fetching ${filename}`)
}
const contents = Buffer.from(contentsResponse.data.content, 'base64').toString('utf8')
const contentsJSON = JSON.parse(contents)
checkDependencies(contents, getDependenciesFromJSON(contentsJSON.dependencies), filename, analysisResult)
checkDependencies(contents, getDependenciesFromJSON(contentsJSON.devDependencies), filename, analysisResult)
checkDependencies(contents, getDependenciesFromJSON(contentsJSON.optionalDependencies), filename, analysisResult)
return analysisResult.checkedDependencyCount
}
function convertAnnotationResults (check: Octokit.ChecksUpdateParams, annotationsSlice: AnnotationResult[]) {
if (!check.output) {
const output: Octokit.ChecksUpdateParamsOutput = {
summary: ''
}
check.output = output
}
check.output.annotations = []
for (const annotationResult of annotationsSlice) {
const annotation: Octokit.ChecksUpdateParamsOutputAnnotations = {
path: annotationResult.path,
start_line: annotationResult.startLine,
end_line: annotationResult.endLine,
annotation_level: annotationResult.annotationLevel,
message: annotationResult.message,
title: annotationResult.title,
raw_details: annotationResult.rawDetails
}
check.output.annotations.push(annotation)
}
}

View File

@ -1,15 +1,18 @@
const nock = require('nock') // You can import your modules
// import index from '../src/index'
import nock from 'nock'
// Requiring our app implementation // Requiring our app implementation
const myProbotApp = require('..') import myProbotApp from '../src'
const { Probot } = require('probot') import { Probot } from 'probot'
// Requiring our fixtures // Requiring our fixtures
const checkSuitePayload = require('./fixtures/check_suite.requested') import checkSuitePayload from './fixtures/check_suite.requested.json'
const checkRunSuccess = require('./fixtures/check_run.created') import checkRunSuccess from './fixtures/check_run.created.json'
nock.disableNetConnect() nock.disableNetConnect()
describe('My Probot app', () => { describe('My Probot app', () => {
let probot let probot: any
beforeEach(() => { beforeEach(() => {
probot = new Probot({}) probot = new Probot({})
@ -41,5 +44,8 @@ describe('My Probot app', () => {
// For more information about testing with Jest see: // For more information about testing with Jest see:
// https://facebook.github.io/jest/ // https://facebook.github.io/jest/
// For more information about using TypeScript in your tests, Jest recommends:
// https://github.com/kulshekhar/ts-jest
// For more information about testing with Nock see: // For more information about testing with Nock see:
// https://github.com/nock/nock // https://github.com/nock/nock

31
tsconfig.json Normal file
View File

@ -0,0 +1,31 @@
{
"compilerOptions": {
"allowJs": false,
"lib": ["es2015", "es2017", "es2018"],
"module": "commonjs",
"moduleResolution": "node",
"target": "es5",
"noImplicitReturns": true,
"noFallthroughCasesInSwitch": true,
"noUnusedLocals": false,
"pretty": true,
"strict": true,
"sourceMap": true,
"outDir": "./lib",
"skipLibCheck": true,
"noImplicitAny": true,
"esModuleInterop": true,
"declaration": true,
"resolveJsonModule": true,
"downlevelIteration": true,
"plugins": [
{
"name": "typescript-tslint-plugin"
}
]
},
"include": [
"src/**/*"
],
"compileOnSave": false
}

965
yarn.lock

File diff suppressed because it is too large Load Diff