diff --git a/packages/build/src/error/parse/normalize.js b/packages/build/src/error/parse/normalize.js new file mode 100644 index 0000000000..1c0a88e499 --- /dev/null +++ b/packages/build/src/error/parse/normalize.js @@ -0,0 +1,21 @@ +// Ensure error is an `Error` instance +const normalizeError = function(error) { + if (error instanceof Array) { + return normalizeArray(error) + } + + if (error instanceof Error && typeof error.message === 'string' && typeof error.stack === 'string') { + return error + } + + return new Error(String(error)) +} + +// Some libraries throw arrays of Errors +const normalizeArray = function(errorArray) { + const [error, ...errors] = errorArray.map(normalizeError) + error.errors = errors + return error +} + +module.exports = { normalizeError } diff --git a/packages/build/src/error/parse/parse.js b/packages/build/src/error/parse/parse.js index e70d5a11fd..8a8827064a 100644 --- a/packages/build/src/error/parse/parse.js +++ b/packages/build/src/error/parse/parse.js @@ -2,6 +2,7 @@ const { getErrorInfo } = require('../info') const { getTypeInfo } = require('../type') const { getLocationInfo } = require('./location') +const { normalizeError } = require('./normalize') const { getPluginInfo } = require('./plugin') const { getErrorProps } = require('./properties') const { getStackInfo } = require('./stack') @@ -61,14 +62,6 @@ const parseErrorInfo = function(error) { } } -const normalizeError = function(error) { - if (error instanceof Error && typeof error.message === 'string' && typeof error.stack === 'string') { - return error - } - - return new Error(String(error)) -} - // Retrieve title to print in logs const getTitle = function(title, errorInfo) { if (typeof title !== 'function') { diff --git a/packages/build/src/plugins/child/error.js b/packages/build/src/plugins/child/error.js index f48f8778f6..6d0602fb99 100644 --- a/packages/build/src/plugins/child/error.js +++ b/packages/build/src/plugins/child/error.js @@ -3,11 +3,13 @@ const logProcessErrors = require('log-process-errors') const { errorToJson } = require('../../error/build') const { isBuildError } = require('../../error/info') +const { normalizeError } = require('../../error/parse/normalize') const { sendEventToParent } = require('../ipc') // Handle any top-level error and communicate it back to parent const handleError = async function(error) { - const errorPayload = errorToJson(error, { type: 'pluginInternal' }) + const errorA = normalizeError(error) + const errorPayload = errorToJson(errorA, { type: 'pluginInternal' }) await sendEventToParent('error', errorPayload) } diff --git a/packages/build/tests/error/fixtures/exception_array/manifest.yml b/packages/build/tests/error/fixtures/exception_array/manifest.yml new file mode 100644 index 0000000000..a3512f0259 --- /dev/null +++ b/packages/build/tests/error/fixtures/exception_array/manifest.yml @@ -0,0 +1,2 @@ +name: test +inputs: [] diff --git a/packages/build/tests/error/fixtures/exception_array/netlify.toml b/packages/build/tests/error/fixtures/exception_array/netlify.toml new file mode 100644 index 0000000000..81b0ce8bb1 --- /dev/null +++ b/packages/build/tests/error/fixtures/exception_array/netlify.toml @@ -0,0 +1,2 @@ +[[plugins]] +package = "./plugin" diff --git a/packages/build/tests/error/fixtures/exception_array/plugin.js b/packages/build/tests/error/fixtures/exception_array/plugin.js new file mode 100644 index 0000000000..96f23893a6 --- /dev/null +++ b/packages/build/tests/error/fixtures/exception_array/plugin.js @@ -0,0 +1,5 @@ +module.exports = { + async onPreBuild() { + throw [new Error('test'), new Error('testTwo')] + }, +} diff --git a/packages/build/tests/error/fixtures/exception_string/manifest.yml b/packages/build/tests/error/fixtures/exception_string/manifest.yml new file mode 100644 index 0000000000..a3512f0259 --- /dev/null +++ b/packages/build/tests/error/fixtures/exception_string/manifest.yml @@ -0,0 +1,2 @@ +name: test +inputs: [] diff --git a/packages/build/tests/error/fixtures/exception_string/netlify.toml b/packages/build/tests/error/fixtures/exception_string/netlify.toml new file mode 100644 index 0000000000..81b0ce8bb1 --- /dev/null +++ b/packages/build/tests/error/fixtures/exception_string/netlify.toml @@ -0,0 +1,2 @@ +[[plugins]] +package = "./plugin" diff --git a/packages/build/tests/error/fixtures/exception_string/plugin.js b/packages/build/tests/error/fixtures/exception_string/plugin.js new file mode 100644 index 0000000000..b0803cd819 --- /dev/null +++ b/packages/build/tests/error/fixtures/exception_string/plugin.js @@ -0,0 +1,5 @@ +module.exports = { + async onPreBuild() { + throw 'test' + }, +} diff --git a/packages/build/tests/error/snapshots/tests.js.md b/packages/build/tests/error/snapshots/tests.js.md index 9780cf5b24..4942482c41 100644 --- a/packages/build/tests/error/snapshots/tests.js.md +++ b/packages/build/tests/error/snapshots/tests.js.md @@ -3415,6 +3415,133 @@ Generated by [AVA](https://ava.li). ## exception +> Snapshot 1 + + `␊ + ┌─────────────────────────────┐␊ + │ Netlify Build │␊ + └─────────────────────────────┘␊ + ␊ + > Version␊ + @netlify/build 1.0.0␊ + ␊ + > Flags␊ + debug: true␊ + repositoryRoot: /file/path␊ + ␊ + > Current directory␊ + /file/path␊ + ␊ + > Config file␊ + /file/path␊ + ␊ + > Resolved config␊ + plugins:␊ + - inputs: {}␊ + origin: config␊ + package: /file/path␊ + ␊ + > Context␊ + production␊ + ␊ + > Loading plugins␊ + - /file/path from netlify.toml␊ + ␊ + ┌─────────────────────────────────────┐␊ + │ 1. onPreBuild command from /file/path │␊ + └─────────────────────────────────────┘␊ + ␊ + ␊ + ┌──────────────────────────────────┐␊ + │ Plugin "/file/path" internal error │␊ + └──────────────────────────────────┘␊ + ␊ + Error message␊ + Error: test␊ + ␊ + Plugin details␊ + Package: /file/path␊ + Version: 1.0.0␊ + Repository: git+https://github.com/netlify/build.git␊ + Report issues: https://github.com/netlify/build/issues␊ + ␊ + Error location␊ + In "onPreBuild" event in "/file/path" from netlify.toml␊ + STACK TRACE␊ + ␊ + Resolved config␊ + plugins:␊ + - inputs: {}␊ + origin: config␊ + package: /file/path` + +## exception that are arrays + +> Snapshot 1 + + `␊ + ┌─────────────────────────────┐␊ + │ Netlify Build │␊ + └─────────────────────────────┘␊ + ␊ + > Version␊ + @netlify/build 1.0.0␊ + ␊ + > Flags␊ + debug: true␊ + repositoryRoot: /file/path␊ + ␊ + > Current directory␊ + /file/path␊ + ␊ + > Config file␊ + /file/path␊ + ␊ + > Resolved config␊ + plugins:␊ + - inputs: {}␊ + origin: config␊ + package: /file/path␊ + ␊ + > Context␊ + production␊ + ␊ + > Loading plugins␊ + - /file/path from netlify.toml␊ + ␊ + ┌─────────────────────────────────────┐␊ + │ 1. onPreBuild command from /file/path │␊ + └─────────────────────────────────────┘␊ + ␊ + ␊ + ┌──────────────────────────────────┐␊ + │ Plugin "/file/path" internal error │␊ + └──────────────────────────────────┘␊ + ␊ + Error message␊ + Error: test␊ + ␊ + Plugin details␊ + Package: /file/path␊ + Version: 1.0.0␊ + Repository: git+https://github.com/netlify/build.git␊ + Report issues: https://github.com/netlify/build/issues␊ + ␊ + Error location␊ + In "onPreBuild" event in "/file/path" from netlify.toml␊ + STACK TRACE␊ + ␊ + Error properties␊ + { errors: [ {} ] }␊ + ␊ + Resolved config␊ + plugins:␊ + - inputs: {}␊ + origin: config␊ + package: /file/path` + +## exception that are strings + > Snapshot 1 `␊ diff --git a/packages/build/tests/error/snapshots/tests.js.snap b/packages/build/tests/error/snapshots/tests.js.snap index e84b0c8ace..c21e4fa9d0 100644 Binary files a/packages/build/tests/error/snapshots/tests.js.snap and b/packages/build/tests/error/snapshots/tests.js.snap differ diff --git a/packages/build/tests/error/tests.js b/packages/build/tests/error/tests.js index c045a7917b..b6e63775c1 100644 --- a/packages/build/tests/error/tests.js +++ b/packages/build/tests/error/tests.js @@ -86,6 +86,14 @@ test('exception with circular references', async t => { await runFixture(t, 'exception_circular') }) +test('exception that are strings', async t => { + await runFixture(t, 'exception_string') +}) + +test('exception that are arrays', async t => { + await runFixture(t, 'exception_array') +}) + test('Clean stack traces of build.command', async t => { const { returnValue } = await runFixture(t, 'build_command', { snapshot: false, normalize: false }) const count = getStackLinesCount(returnValue)