Skip to content

Add error statuses #1271

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
May 14, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 12 additions & 13 deletions packages/build/src/core/commands.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,9 @@ const execa = require('execa')
const pReduce = require('p-reduce')

const { setEnvChanges } = require('../env/changes.js')
const { cancelBuild } = require('../error/cancel')
const { addErrorInfo, getErrorInfo } = require('../error/info')
const { reportBuildError } = require('../error/monitor/report')
const { serializeErrorStatus } = require('../error/parse/serialize_status')
const { logCommand, logBuildCommandStart, logCommandSuccess, logPluginError } = require('../log/main')
const { pipeOutput, unpipeOutput } = require('../log/stream')
const { startTimer, endTimer } = require('../log/timer')
Expand Down Expand Up @@ -58,7 +58,7 @@ const isSuccessCommand = function({ event }) {
// list of `failedPlugins` (that ran `utils.build.failPlugin()`).
// If an error arises, runs `onError` events.
// Runs `onEnd` events at the end, whether an error was thrown or not.
const runCommands = async function({ commands, configPath, buildDir, nodePath, childEnv, errorMonitor, api }) {
const runCommands = async function({ commands, configPath, buildDir, nodePath, childEnv, errorMonitor }) {
const { index: commandsCount, error: errorA, statuses: statusesB } = await pReduce(
commands,
async (
Expand All @@ -81,7 +81,6 @@ const runCommands = async function({ commands, configPath, buildDir, nodePath, c
envChanges,
statuses,
errorMonitor,
api,
error,
failedPlugins,
},
Expand Down Expand Up @@ -116,7 +115,6 @@ const runCommand = async function({
envChanges,
statuses,
errorMonitor,
api,
error,
failedPlugins,
}) {
Expand Down Expand Up @@ -147,7 +145,7 @@ const runCommand = async function({
const newValues =
newError === undefined
? handleCommandSuccess({ event, package, newEnvChanges, newStatus, methodTimer })
: await handleCommandError({ newError, errorMonitor, api })
: await handleCommandError({ newError, errorMonitor })
return { ...newValues, newIndex: index + 1 }
}

Expand Down Expand Up @@ -257,20 +255,21 @@ const handleCommandSuccess = function({ event, package, newEnvChanges, newStatus
// handlers of the same type have been triggered before propagating
// - if `utils.build.failPlugin()` was used, print an error and skip next event
// handlers of that plugin. But do not stop build.
const handleCommandError = async function({ newError, errorMonitor, api }) {
const handleCommandError = async function({ newError, errorMonitor }) {
const { type, location: { package } = {} } = getErrorInfo(newError)
const newStatus = serializeErrorStatus(newError)

if (type === 'failPlugin') {
logPluginError(newError)
await reportBuildError(newError, errorMonitor)
return { failedPlugin: [package] }
return handleFailPlugin({ newStatus, package, newError, errorMonitor })
}

if (type === 'cancelBuild') {
await cancelBuild(api)
}
return { newError, newStatus }
}

return { newError }
const handleFailPlugin = async function({ newStatus, package, newError, errorMonitor }) {
logPluginError(newError)
await reportBuildError(newError, errorMonitor)
return { failedPlugin: [package], newStatus }
}

module.exports = { getCommands, isSuccessCommand, runCommands }
7 changes: 2 additions & 5 deletions packages/build/src/core/main.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ setColorLevel()
require('../error/process')

const { getChildEnv } = require('../env/main')
const { maybeCancelBuild } = require('../error/cancel')
const { removeErrorColors } = require('../error/colors')
const { reportBuildError } = require('../error/monitor/report')
const { startErrorMonitor } = require('../error/monitor/start')
Expand Down Expand Up @@ -79,7 +80,6 @@ const build = async function(flags) {
branch,
mode,
errorMonitor,
api,
})

if (dry) {
Expand All @@ -97,6 +97,7 @@ const build = async function(flags) {
await trackBuildComplete({ commandsCount, netlifyConfig, duration, siteInfo, mode })
return true
} catch (error) {
await maybeCancelBuild(error, api)
await logOldCliVersionError(mode)
throw error
}
Expand All @@ -122,7 +123,6 @@ const buildRun = async function({
branch,
mode,
errorMonitor,
api,
}) {
const utilsData = await startUtils(buildDir)
const childEnv = await getChildEnv({ netlifyConfig, buildDir, context, branch, siteInfo, mode })
Expand All @@ -142,7 +142,6 @@ const buildRun = async function({
dry,
constants,
errorMonitor,
api,
})
} finally {
await stopPlugins(childProcesses)
Expand All @@ -162,7 +161,6 @@ const executeCommands = async function({
dry,
constants,
errorMonitor,
api,
}) {
const pluginsCommands = await loadPlugins({
pluginsOptions,
Expand All @@ -187,7 +185,6 @@ const executeCommands = async function({
nodePath,
childEnv,
errorMonitor,
api,
})
}

Expand Down
12 changes: 10 additions & 2 deletions packages/build/src/error/cancel.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,21 @@ const {
env: { DEPLOY_ID },
} = require('process')

const { getErrorInfo } = require('./info')

// Cancel builds, for example when a plugin uses `utils.build.cancelBuild()`
const cancelBuild = async function(api) {
const maybeCancelBuild = async function(error, api) {
if (api === undefined || !DEPLOY_ID) {
return
}

const { type } = getErrorInfo(error)

if (type !== 'cancelBuild') {
return
}

await api.cancelSiteDeploy({ deploy_id: DEPLOY_ID })
}

module.exports = { cancelBuild }
module.exports = { maybeCancelBuild }
10 changes: 6 additions & 4 deletions packages/build/src/error/parse/parse.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,14 @@ const { getErrorProps } = require('./properties')
const { getStackInfo } = require('./stack')

// Parse all error information into a normalized sets of properties
const parseError = function(error) {
const parseError = function({ error, colors }) {
const {
message,
stack,
errorProps,
errorInfo,
errorInfo: { location = {}, plugin = {} },
state,
title,
isSuccess,
stackType,
Expand All @@ -28,20 +29,21 @@ const parseError = function(error) {

const pluginInfo = getPluginInfo(plugin, location)
const locationInfo = getLocationInfo({ stack: stackA, location, locationType })
const errorPropsA = getErrorProps(errorProps, showErrorProps)
return { title: titleA, message: messageA, pluginInfo, locationInfo, errorProps: errorPropsA, isSuccess }
const errorPropsA = getErrorProps({ errorProps, showErrorProps, colors })
return { state, title: titleA, message: messageA, pluginInfo, locationInfo, errorProps: errorPropsA, isSuccess }
}

// Parse error instance into all the basic properties containing information
const parseErrorInfo = function(error) {
const { message, stack, ...errorProps } = normalizeError(error)
const errorInfo = getErrorInfo(errorProps)
const { title, isSuccess, stackType, locationType, showErrorProps, rawStack } = getTypeInfo(errorInfo)
const { state, title, isSuccess, stackType, locationType, showErrorProps, rawStack } = getTypeInfo(errorInfo)
return {
message,
stack,
errorProps,
errorInfo,
state,
title,
isSuccess,
stackType,
Expand Down
4 changes: 2 additions & 2 deletions packages/build/src/error/parse/properties.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,14 @@ const { omit } = require('../../utils/omit')
const { INFO_SYM } = require('../info')

// In uncaught exceptions, print error static properties
const getErrorProps = function(errorProps, showErrorProps) {
const getErrorProps = function({ errorProps, showErrorProps, colors }) {
const errorPropsA = omit(errorProps, CLEANED_ERROR_PROPS)

if (!showErrorProps || Object.keys(errorPropsA).length === 0) {
return
}

return inspect(errorPropsA)
return inspect(errorPropsA, { colors })
}

// Remove error static properties that should not be logged
Expand Down
2 changes: 1 addition & 1 deletion packages/build/src/error/parse/serialize_log.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ const { parseError } = require('./parse')

// Serialize an error object into a title|body string to print in logs
const serializeLogError = function(error) {
const { title, message, pluginInfo, locationInfo, errorProps, isSuccess } = parseError(error)
const { title, message, pluginInfo, locationInfo, errorProps, isSuccess } = parseError({ error, colors: true })
const body = getBody({ message, pluginInfo, locationInfo, errorProps, isSuccess })
return { title, body, isSuccess }
}
Expand Down
28 changes: 28 additions & 0 deletions packages/build/src/error/parse/serialize_status.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
const { parseError } = require('./parse')

// Serialize an error object to `statuses` properties
const serializeErrorStatus = function(error) {
const { state, title, message, locationInfo, errorProps } = parseError({ error, colors: false })
const text = getText({ locationInfo, errorProps })
return { state, title, summary: message, text }
}

const getText = function({ locationInfo, errorProps }) {
const parts = [locationInfo, getErrorProps(errorProps)].filter(Boolean)

if (parts.length === 0) {
return
}

return parts.join('\n\n')
}

const getErrorProps = function(errorProps) {
if (errorProps === undefined) {
return
}

return `Error properties:\n${errorProps}`
}

module.exports = { serializeErrorStatus }
10 changes: 9 additions & 1 deletion packages/build/src/error/type.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
// Retrieve error-type specific information
const getTypeInfo = function({ type }) {
const typeA = TYPES[type] === undefined ? DEFAULT_TYPE : type
return { type: typeA, ...TYPES[typeA] }
return { type: typeA, state: DEFAULT_STATE, ...TYPES[typeA] }
}

// List of error types, and their related properties
Expand All @@ -20,6 +20,8 @@ const getTypeInfo = function({ type }) {
// - `message`: printed as is, but taken from `error.message`.
// Used when `error.stack` is not being correct due to the error being
// passed between different processes.
// Related to error statuses:
// - `state`: error status state. Defaults to `failed_build`
// Related to Bugsnag:
// - `group`: main title shown in Bugsnag. Also used to group errors together
// in Bugsnag, combined with `error.message`.
Expand Down Expand Up @@ -67,6 +69,7 @@ const TYPES = {
stackType: 'stack',
locationType: 'buildFail',
severity: 'info',
state: 'failed_plugin',
},

// Plugin called `utils.build.cancelBuild()`
Expand All @@ -76,6 +79,7 @@ const TYPES = {
locationType: 'buildFail',
isSuccess: true,
severity: 'info',
state: 'canceled_plugin',
},

// Plugin has an invalid shape
Expand Down Expand Up @@ -129,7 +133,11 @@ const TYPES = {
severity: 'error',
},
}

// When no error type matches, it's an uncaught exception, i.e. a bug
const DEFAULT_TYPE = 'exception'

// When no `state` is provided
const DEFAULT_STATE = 'failed_build'

module.exports = { getTypeInfo }