/
error.ts
97 lines (88 loc) · 2.58 KB
/
error.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
import chalk from 'chalk'
import { RollupError } from 'rollup'
import { ViteDevServer } from '../..'
import { Connect } from 'types/connect'
import { pad } from '../../utils'
import strip from 'strip-ansi'
import { ErrorPayload } from 'types/hmrPayload'
export function prepareError(err: Error | RollupError): ErrorPayload['err'] {
// only copy the information we need and avoid serializing unnecessary
// properties, since some errors may attach full objects (e.g. PostCSS)
return {
message: strip(err.message),
stack: strip(cleanStack(err.stack || '')),
id: (err as RollupError).id,
frame: strip((err as RollupError).frame || ''),
plugin: (err as RollupError).plugin,
pluginCode: (err as RollupError).pluginCode,
loc: (err as RollupError).loc
}
}
export function buildErrorMessage(
err: RollupError,
args: string[] = [],
includeStack = true
): string {
if (err.plugin) args.push(` Plugin: ${chalk.magenta(err.plugin)}`)
if (err.id) args.push(` File: ${chalk.cyan(err.id)}`)
if (err.frame) args.push(chalk.yellow(pad(err.frame)))
if (includeStack && err.stack) args.push(pad(cleanStack(err.stack)))
return args.join('\n')
}
function cleanStack(stack: string) {
return stack
.split(/\n/g)
.filter((l) => /^\s*at/.test(l))
.join('\n')
}
export function errorMiddleware(
server: ViteDevServer,
allowNext = false
): Connect.ErrorHandleFunction {
// note the 4 args must be kept for connect to treat this as error middleware
// Keep the named function. The name is visible in debug logs via `DEBUG=connect:dispatcher ...`
return function viteErrorMiddleware(err: RollupError, _req, res, next) {
const msg = buildErrorMessage(err, [
chalk.red(`Internal server error: ${err.message}`)
])
server.config.logger.error(msg, {
clear: true,
timestamp: true
})
server.ws.send({
type: 'error',
err: prepareError(err)
})
if (allowNext) {
next()
} else {
if (err instanceof AccessRestrictedError) {
res.statusCode = 403
res.write(renderErrorHTML(err.message))
res.end()
}
res.statusCode = 500
res.end()
}
}
}
export class AccessRestrictedError extends Error {
constructor(msg: string) {
super(msg)
}
}
export function renderErrorHTML(msg: string): string {
// to have syntax highlighting and autocompletion in IDE
const html = String.raw
return html`
<body>
<h1>403 Restricted</h1>
<p>${msg.replace(/\n/g, '<br/>')}</p>
<style>
body {
padding: 1em 2em;
}
</style>
</body>
`
}