Compare commits

...

4 Commits

6 changed files with 162 additions and 14 deletions

View File

@ -49,6 +49,22 @@ const testEnvVars = {
const UUID = '9b1deb4d-3b7d-4bad-9bdd-2b0d7b3dcb6d'
const DELIMITER = `ghadelimiter_${UUID}`
function extractErrorMetadata(error: Error): {
file: string | undefined
line: string | undefined
column: string | undefined
} {
const stackLines = error.stack?.split(os.EOL) || []
const firstTraceLine = stackLines[1]
const match = firstTraceLine.match(/at (?:.*) \((.*):(\d+):(\d+)\)/) || []
const [, file, line, column] = match
return {
file,
line,
column
}
}
describe('@actions/core', () => {
beforeAll(() => {
const filePath = path.join(__dirname, `test`)
@ -379,9 +395,14 @@ describe('@actions/core', () => {
it('setFailed handles Error', () => {
const message = 'this is my error message'
core.setFailed(new Error(message))
const error = new Error(message)
core.setFailed(error)
expect(process.exitCode).toBe(core.ExitCode.Failure)
assertWriteCalls([`::error::Error: ${message}${os.EOL}`])
const {file, line, column} = extractErrorMetadata(error)
assertWriteCalls([
`::error title=Error,file=${file},line=${line},col=${column}::Error: ${message}${os.EOL}`
])
})
it('error sets the correct error message', () => {
@ -396,11 +417,21 @@ describe('@actions/core', () => {
it('error handles an error object', () => {
const message = 'this is my error message'
core.error(new Error(message))
const error = new Error(message)
core.error(error)
const {file, line, column} = extractErrorMetadata(error)
assertWriteCalls([
`::error title=Error,file=${file},line=${line},col=${column}::Error: ${message}${os.EOL}`
])
})
it('error handles an error object and an empty properties', () => {
const message = 'this is my error message'
core.error(new Error(message), {})
assertWriteCalls([`::error::Error: ${message}${os.EOL}`])
})
it('error handles parameters correctly', () => {
it('error handles custom properties correctly', () => {
const message = 'this is my error message'
core.error(new Error(message), {
title: 'A title',
@ -427,11 +458,21 @@ describe('@actions/core', () => {
it('warning handles an error object', () => {
const message = 'this is my error message'
core.warning(new Error(message))
const error = new Error(message)
core.warning(error)
const {file, line, column} = extractErrorMetadata(error)
assertWriteCalls([
`::warning title=Error,file=${file},line=${line},col=${column}::Error: ${message}${os.EOL}`
])
})
it('warning handles an error object and an empty properties', () => {
const message = 'this is my error message'
core.warning(new Error(message), {})
assertWriteCalls([`::warning::Error: ${message}${os.EOL}`])
})
it('warning handles parameters correctly', () => {
it('warning handles custom properties correctly', () => {
const message = 'this is my error message'
core.warning(new Error(message), {
title: 'A title',
@ -458,11 +499,21 @@ describe('@actions/core', () => {
it('notice handles an error object', () => {
const message = 'this is my error message'
core.notice(new Error(message))
const error = new Error(message)
core.notice(error)
const {file, line, column} = extractErrorMetadata(error)
assertWriteCalls([
`::notice title=Error,file=${file},line=${line},col=${column}::Error: ${message}${os.EOL}`
])
})
it('notice handles an error object and an empty properties', () => {
const message = 'this is my error message'
core.notice(new Error(message), {})
assertWriteCalls([`::notice::Error: ${message}${os.EOL}`])
})
it('notice handles parameters correctly', () => {
it('notice handles custom properties correctly', () => {
const message = 'this is my error message'
core.notice(new Error(message), {
title: 'A title',

View File

@ -0,0 +1,26 @@
import {toAnnotationProperties} from '../src/utils'
describe('@actions/core/src/utils', () => {
describe('.toAnnotationProperties', () => {
it('extracts title only from Error instance without a parseable stack', () => {
const error = new TypeError('Test error')
error.stack = ''
expect(toAnnotationProperties(error)).toEqual({
title: 'TypeError',
file: undefined,
startLine: undefined,
startColumn: undefined
})
})
it('extracts AnnotationProperties from Error instance', () => {
const error = new ReferenceError('Test error')
expect(toAnnotationProperties(error)).toEqual({
title: 'ReferenceError',
file: expect.stringMatching(/utils\.test\.ts$/),
startLine: expect.any(Number),
startColumn: expect.any(Number)
})
})
})
})

View File

@ -11,6 +11,7 @@
"dependencies": {
"@actions/exec": "^1.1.1",
"@actions/http-client": "^2.0.1",
"error-stack-parser": "^2.1.4",
"uuid": "^8.3.2"
},
"devDependencies": {
@ -51,6 +52,19 @@
"integrity": "sha512-c/I8ZRb51j+pYGAu5CrFMRxqZ2ke4y2grEBO5AUjgSkSk+qT2Ea+OdWElz/OiMf5MNpn2b17kuVBwZLQJXzihw==",
"dev": true
},
"node_modules/error-stack-parser": {
"version": "2.1.4",
"resolved": "https://registry.npmjs.org/error-stack-parser/-/error-stack-parser-2.1.4.tgz",
"integrity": "sha512-Sk5V6wVazPhq5MhpO+AUxJn5x7XSXGl1R93Vn7i+zS15KDVxQijejNCrz8340/2bgLBjR9GtEG8ZVKONDjcqGQ==",
"dependencies": {
"stackframe": "^1.3.4"
}
},
"node_modules/stackframe": {
"version": "1.3.4",
"resolved": "https://registry.npmjs.org/stackframe/-/stackframe-1.3.4.tgz",
"integrity": "sha512-oeVtt7eWQS+Na6F//S4kJ2K2VbRlS9D43mAlMyVpVWovy9o+jfgH8O9agzANzaiLjclA0oYzUXEM4PurhSUChw=="
},
"node_modules/tunnel": {
"version": "0.0.6",
"resolved": "https://registry.npmjs.org/tunnel/-/tunnel-0.0.6.tgz",
@ -102,6 +116,19 @@
"integrity": "sha512-c/I8ZRb51j+pYGAu5CrFMRxqZ2ke4y2grEBO5AUjgSkSk+qT2Ea+OdWElz/OiMf5MNpn2b17kuVBwZLQJXzihw==",
"dev": true
},
"error-stack-parser": {
"version": "2.1.4",
"resolved": "https://registry.npmjs.org/error-stack-parser/-/error-stack-parser-2.1.4.tgz",
"integrity": "sha512-Sk5V6wVazPhq5MhpO+AUxJn5x7XSXGl1R93Vn7i+zS15KDVxQijejNCrz8340/2bgLBjR9GtEG8ZVKONDjcqGQ==",
"requires": {
"stackframe": "^1.3.4"
}
},
"stackframe": {
"version": "1.3.4",
"resolved": "https://registry.npmjs.org/stackframe/-/stackframe-1.3.4.tgz",
"integrity": "sha512-oeVtt7eWQS+Na6F//S4kJ2K2VbRlS9D43mAlMyVpVWovy9o+jfgH8O9agzANzaiLjclA0oYzUXEM4PurhSUChw=="
},
"tunnel": {
"version": "0.0.6",
"resolved": "https://registry.npmjs.org/tunnel/-/tunnel-0.0.6.tgz",

View File

@ -38,10 +38,11 @@
"dependencies": {
"@actions/exec": "^1.1.1",
"@actions/http-client": "^2.0.1",
"error-stack-parser": "^2.1.4",
"uuid": "^8.3.2"
},
"devDependencies": {
"@types/node": "^12.0.2",
"@types/uuid": "^8.3.4"
}
}
}

View File

@ -1,7 +1,10 @@
import {issue, issueCommand} from './command'
import {issueFileCommand, prepareKeyValueMessage} from './file-command'
import {toCommandProperties, toCommandValue} from './utils'
import {
toAnnotationProperties,
toCommandProperties,
toCommandValue
} from './utils'
import * as os from 'os'
import * as path from 'path'
@ -242,6 +245,21 @@ export function debug(message: string): void {
issueCommand('debug', {}, message)
}
function defaultAnnotationPropertes(
message: string | Error,
properties: AnnotationProperties | undefined = undefined
): AnnotationProperties {
// If no properties are provided, try to extract them from the Error instance
if (properties === undefined) {
if (message instanceof Error) {
properties = toAnnotationProperties(message)
} else {
properties = {}
}
}
return properties
}
/**
* Adds an error issue
* @param message error issue message. Errors will be converted to string via toString()
@ -249,8 +267,10 @@ export function debug(message: string): void {
*/
export function error(
message: string | Error,
properties: AnnotationProperties = {}
properties: AnnotationProperties | undefined = undefined
): void {
properties = defaultAnnotationPropertes(message, properties)
issueCommand(
'error',
toCommandProperties(properties),
@ -265,8 +285,10 @@ export function error(
*/
export function warning(
message: string | Error,
properties: AnnotationProperties = {}
properties: AnnotationProperties | undefined = undefined
): void {
properties = defaultAnnotationPropertes(message, properties)
issueCommand(
'warning',
toCommandProperties(properties),
@ -281,8 +303,10 @@ export function warning(
*/
export function notice(
message: string | Error,
properties: AnnotationProperties = {}
properties: AnnotationProperties | undefined = undefined
): void {
properties = defaultAnnotationPropertes(message, properties)
issueCommand(
'notice',
toCommandProperties(properties),

View File

@ -3,6 +3,7 @@
import {AnnotationProperties} from './core'
import {CommandProperties} from './command'
import ErrorStackParser from 'error-stack-parser'
/**
* Sanitizes an input into a string so it can be passed into issueCommand safely
@ -39,3 +40,21 @@ export function toCommandProperties(
endColumn: annotationProperties.endColumn
}
}
export function toAnnotationProperties(error: Error): AnnotationProperties {
let firstFrame
try {
const stack = ErrorStackParser.parse(error)
firstFrame = stack?.[0]
} catch (parseError) {
// If we can't parse the stack, we'll just skip it
}
return {
title: error.name,
file: firstFrame?.fileName,
startLine: firstFrame?.lineNumber,
startColumn: firstFrame?.columnNumber
}
}