From 1c5932fc39fcd7b321aaccf63eb108cb3e858d8b Mon Sep 17 00:00:00 2001 From: "James M. Greene" Date: Wed, 27 Mar 2024 22:29:17 -0500 Subject: [PATCH] Add error-utils helper and tests --- package-lock.json | 27 +++++++++++++++++++++++++++ package.json | 1 + src/error-utils.js | 24 ++++++++++++++++++++++++ src/error-utils.test.js | 38 ++++++++++++++++++++++++++++++++++++++ 4 files changed, 90 insertions(+) create mode 100644 src/error-utils.js create mode 100644 src/error-utils.test.js diff --git a/package-lock.json b/package-lock.json index c6a1cd5..7592e30 100644 --- a/package-lock.json +++ b/package-lock.json @@ -11,6 +11,7 @@ "dependencies": { "@actions/core": "^1.10.1", "@actions/github": "^6.0.0", + "error-stack-parser": "^2.1.4", "espree": "^9.6.1" }, "devDependencies": { @@ -2793,6 +2794,14 @@ "is-arrayish": "^0.2.1" } }, + "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/es-abstract": { "version": "1.21.1", "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.21.1.tgz", @@ -6145,6 +6154,11 @@ "node": ">=10" } }, + "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/stop-iteration-iterator": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/stop-iteration-iterator/-/stop-iteration-iterator-1.0.0.tgz", @@ -8805,6 +8819,14 @@ "is-arrayish": "^0.2.1" } }, + "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" + } + }, "es-abstract": { "version": "1.21.1", "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.21.1.tgz", @@ -11242,6 +11264,11 @@ "escape-string-regexp": "^2.0.0" } }, + "stackframe": { + "version": "1.3.4", + "resolved": "https://registry.npmjs.org/stackframe/-/stackframe-1.3.4.tgz", + "integrity": "sha512-oeVtt7eWQS+Na6F//S4kJ2K2VbRlS9D43mAlMyVpVWovy9o+jfgH8O9agzANzaiLjclA0oYzUXEM4PurhSUChw==" + }, "stop-iteration-iterator": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/stop-iteration-iterator/-/stop-iteration-iterator-1.0.0.tgz", diff --git a/package.json b/package.json index 86121e5..5554788 100644 --- a/package.json +++ b/package.json @@ -26,6 +26,7 @@ "dependencies": { "@actions/core": "^1.10.1", "@actions/github": "^6.0.0", + "error-stack-parser": "^2.1.4", "espree": "^9.6.1" }, "devDependencies": { diff --git a/src/error-utils.js b/src/error-utils.js new file mode 100644 index 0000000..6ad98fb --- /dev/null +++ b/src/error-utils.js @@ -0,0 +1,24 @@ +const ErrorStackParser = require('error-stack-parser') + +// Convert an Error's stack into `@actions/core` toolkit AnnotationProperties: +// https://github.com/actions/toolkit/blob/ef77c9d60bdb03700d7758b0d04b88446e72a896/packages/core/src/core.ts#L36-L71 +function convertErrorToAnnotationProperties(error, title = error.name) { + if (!(error instanceof Error)) { + throw new TypeError('error must be an instance of Error') + } + + const stack = ErrorStackParser.parse(error) + const firstFrame = stack && stack.length > 0 ? stack[0] : null + if (!firstFrame) { + throw new Error('Error stack is empty or unparseable') + } + + return { + title, + file: firstFrame.fileName, + startLine: firstFrame.lineNumber, + startColumn: firstFrame.columnNumber + } +} + +module.exports = { convertErrorToAnnotationProperties } diff --git a/src/error-utils.test.js b/src/error-utils.test.js new file mode 100644 index 0000000..29eadd4 --- /dev/null +++ b/src/error-utils.test.js @@ -0,0 +1,38 @@ +const { convertErrorToAnnotationProperties } = require('./error-utils') + +describe('error-utils', () => { + describe('convertErrorToAnnotationProperties', () => { + it('throws a TypeError if the first argument is not an Error instance', () => { + expect(() => convertErrorToAnnotationProperties('not an Error')).toThrow( + TypeError, + 'error must be an instance of Error' + ) + }) + + it('throws an Error if the first argument is an Error instance without a parseable stack', () => { + const error = new Error('Test error') + error.stack = '' + expect(() => convertErrorToAnnotationProperties(error)).toThrow(Error, 'Error stack is empty or unparseable') + }) + + it('returns an AnnotationProperties-compatible object', () => { + const result = convertErrorToAnnotationProperties(new TypeError('Test error')) + expect(result).toEqual({ + title: 'TypeError', + file: __filename, + startLine: expect.any(Number), + startColumn: expect.any(Number) + }) + }) + + it('returns an AnnotationProperties-compatible object with a custom title', () => { + const result = convertErrorToAnnotationProperties(new TypeError('Test error'), 'custom title') + expect(result).toEqual({ + title: 'custom title', + file: __filename, + startLine: expect.any(Number), + startColumn: expect.any(Number) + }) + }) + }) +})