Skip to content

Commit

Permalink
✨ improve: bud error handling (#2326)
Browse files Browse the repository at this point in the history
- Improve bud error handling
- Should always convert anything thrown to `BudError`
- Include more stack trace

## Type of change

**PATCH: backwards compatible change**
  • Loading branch information
kellymears committed Jun 14, 2023
1 parent 539c2bc commit e6938cf
Show file tree
Hide file tree
Showing 15 changed files with 159 additions and 224 deletions.
142 changes: 65 additions & 77 deletions sources/@roots/bud-dashboard/src/app/error.tsx
Original file line number Diff line number Diff line change
@@ -1,148 +1,136 @@
/* eslint-disable n/no-process-env */
import type {BudHandler} from '@roots/bud-support/errors'

import cleanStack from '@roots/bud-support/clean-stack'
import {BudError, type BudHandler} from '@roots/bud-support/errors'
import figures from '@roots/bud-support/figures'
import * as Ink from '@roots/bud-support/ink'
import {Box, Text} from '@roots/bud-support/ink'
import isString from '@roots/bud-support/lodash/isString'

export type Props = React.PropsWithChildren<{
error: BudHandler
message?: string
name?: string
}>

const basePath =
process.env.PROJECT_CWD ?? process.env.INIT_CWD ?? process.cwd()

export const Error = ({error, ...props}: Props) => {
if (!error) {
return (
<Ink.Box flexDirection="column">
<Ink.Text>An unknown error has occurred.</Ink.Text>
{Object.entries(props).map(([key, value], id) => (
<Ink.Box flexDirection="column" gap={2} key={id}>
<Ink.Text>{key}</Ink.Text>
<Ink.Text>{JSON.stringify(value)}</Ink.Text>
</Ink.Box>
))}
</Ink.Box>
)
error = BudError.normalize({...props})
}

if (isString(error)) {
return (
<Ink.Box flexDirection="column" paddingTop={1}>
<Ink.Text backgroundColor="red" color="white">
<Box flexDirection="column" paddingTop={1}>
<Text backgroundColor="red" color="white">
{` Error `}
</Ink.Text>
<Ink.Box marginTop={1}>
<Ink.Text>
<Ink.Text color="red">{figures.cross}</Ink.Text>
</Text>
<Box marginTop={1}>
<Text>
<Text color="red">{figures.cross}</Text>
{` `}
{error}
</Ink.Text>
</Ink.Box>
</Ink.Box>
</Text>
</Box>
</Box>
)
}

return (
<Ink.Box flexDirection="column" paddingTop={1}>
<Ink.Text backgroundColor="red" color="white">
<Box flexDirection="column" paddingTop={1}>
<Text backgroundColor="red" color="white">
{` `}
{error.name}
{` `}
</Ink.Text>
</Text>

{error.message ? (
<Ink.Box marginTop={1}>
<Ink.Text>
<Ink.Text color="red">{figures.cross}</Ink.Text>
<Box marginTop={1}>
<Text>
<Text color="red">{figures.cross}</Text>
{` `}
{error.message}
</Ink.Text>
</Ink.Box>
</Text>
</Box>
) : null}

{error.details && (
<Ink.Box marginTop={1}>
<Ink.Text>
<Ink.Text color="blue">
<Box marginTop={1}>
<Text>
<Text color="blue">
{figures.ellipsis}
{` `}Error details{` `}
</Ink.Text>
</Text>

<Ink.Text>{error.details}</Ink.Text>
</Ink.Text>
</Ink.Box>
<Text>{error.details}</Text>
</Text>
</Box>
)}

{!error.origin && error.stack && (
<Ink.Box flexDirection="column" marginTop={1}>
<Ink.Text color="blue">
<Box flexDirection="column" marginTop={1}>
<Text color="blue">
{figures.hamburger}
{` `}Stack trace
</Ink.Text>
</Text>

<Ink.Text dimColor>
<Text dimColor>
{cleanStack(error.stack, {
basePath:
process.env.PROJECT_CWD ??
process.env.INIT_CWD ??
process.cwd(),
basePath,
pretty: true,
})
.split(`\n`)
.slice(1, 5)
.join(`\n`)}
</Ink.Text>
</Ink.Box>
})}
</Text>
</Box>
)}

{error.docs && (
<Ink.Box marginTop={1}>
<Ink.Text>
<Ink.Text color="blue">
<Box marginTop={1}>
<Text>
<Text color="blue">
{figures.arrowRight}
{` `}Documentation
</Ink.Text>
</Text>
{` `}
<Ink.Text>{error.docs.href}</Ink.Text>
</Ink.Text>
</Ink.Box>
<Text>{error.docs.href}</Text>
</Text>
</Box>
)}

{error.issues && (
<Ink.Box marginTop={1}>
<Ink.Text>
<Ink.Text color="blue">
<Box marginTop={1}>
<Text>
<Text color="blue">
{figures.arrowRight}
{` `}
Issues
</Ink.Text>
</Text>
{` `}
<Ink.Text>{error.issues.href}</Ink.Text>
</Ink.Text>
</Ink.Box>
<Text>{error.issues.href}</Text>
</Text>
</Box>
)}

{error.origin && (
<Ink.Box>
<Box>
<Error error={error.origin} />
</Ink.Box>
</Box>
)}

{error.file && (
<Ink.Box marginTop={1}>
<Ink.Text color="blue">
<Box marginTop={1}>
<Text color="blue">
{figures.info}
{` `}See file{` `}
</Ink.Text>
<Ink.Text>{error.file.path}</Ink.Text>
</Ink.Box>
</Text>
<Text>{error.file.path}</Text>
</Box>
)}
</Ink.Box>
</Box>
)
}

export const Message = ({children}: React.PropsWithChildren<{}>) => (
<Ink.Box flexDirection="column">
<Ink.Text>{children}</Ink.Text>
</Ink.Box>
<Box flexDirection="column">
<Text>{children}</Text>
</Box>
)
18 changes: 13 additions & 5 deletions sources/@roots/bud-framework/src/lifecycle/bootstrap.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import chalk from '@roots/bud-support/chalk'
import {BudError} from '@roots/bud-support/errors'
import figures from '@roots/bud-support/figures'
import camelCase from '@roots/bud-support/lodash/camelCase'
import isFunction from '@roots/bud-support/lodash/isFunction'
Expand Down Expand Up @@ -146,11 +147,14 @@ export const bootstrap = async function (this: Bud) {

this[`fs`] = new FS(() => this)
this[`module`] = new Module(() => this)

await Promise.all(
[...this.context.services]
.filter(filterServices(this))
.map(instantiateServices(this)),
)
).catch(error => {
throw BudError.normalize(error)
})

this.hooks
.fromMap({
Expand Down Expand Up @@ -238,9 +242,13 @@ export const bootstrap = async function (this: Bud) {
* Checksums
*/
this.after(async bud => {
await bud.fs.write(bud.module.cacheLocation, {
resolutions: bud.module.resolved,
version: bud.context.bud.version,
})
await bud.fs
.write(bud.module.cacheLocation, {
resolutions: bud.module.resolved,
version: bud.context.bud.version,
})
.catch(error => {
throw new Error(error)
})
})
}
37 changes: 24 additions & 13 deletions sources/@roots/bud-hooks/src/event/event.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@ import type {Bud} from '@roots/bud-framework'
import type {Events, EventsStore} from '@roots/bud-framework/registry'

import {bind} from '@roots/bud-support/decorators/bind'
import {BudError} from '@roots/bud-support/errors'
import logger from '@roots/bud-support/logger'

import {Hooks} from '../base/base.js'

Expand All @@ -25,19 +27,29 @@ export class EventHooks extends Hooks<EventsStore> {

await Promise.all(
actions.map(async (action, iteration) => {
try {
this.app.hooks.logger.await(
`executing callback ${iteration + 1}/${actions.length}`,
)
await action(value as any)
this.app.hooks.logger.success(
`executing callback ${iteration + 1}/${actions.length}`,
)
} catch (e) {
this.catch(e, id, iteration)
}
this.app.hooks.logger.await(
`executing ${id} callback (${iteration + 1}/${actions.length})`,
)

await action(value as any)
.catch(error => {
this.app.hooks.logger.error(
`error executing ${id} callback (${iteration + 1}/${
actions.length
})`,
)
throw BudError.normalize(error)
})
.then(() => {
logger.success(
`executed ${id} callback (${iteration + 1}/${
actions.length
})`,
)
})
}),
)

this.app.hooks.logger.timeEnd(id)

return this.app
Expand All @@ -52,8 +64,7 @@ export class EventHooks extends Hooks<EventsStore> {

input.map((value, iteration) => {
this.app.hooks.logger.info(
`registered event callback for`,
id,
`registered ${id} callback`,
`(${iteration + 1}/${input.length})`,
)

Expand Down
2 changes: 0 additions & 2 deletions sources/@roots/bud/src/cli/commands/bud.build.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -136,8 +136,6 @@ export default class BudBuildCommand extends BudCommand {
*/
public override async execute() {
await this.makeBud()
await this.healthcheck(this)
await this.bud?.run()
await this.bud?.dashboard?.renderQueuedMessages()
}
}
1 change: 0 additions & 1 deletion sources/@roots/bud/src/cli/commands/bud.clean.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -141,7 +141,6 @@ export default class BudCleanCommand extends BudCommand {
*/
public override async execute() {
await this.makeBud()
await this.healthcheck(this)

const cleanAll =
!this.outputPositional &&
Expand Down
Loading

0 comments on commit e6938cf

Please sign in to comment.