@jsenv/core 27.5.1 → 27.5.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/js/html_supervisor_installer.js +334 -267
- package/dist/main.js +57 -14
- package/package.json +2 -1
- package/src/plugins/html_supervisor/client/error_formatter.js +300 -0
- package/src/plugins/html_supervisor/client/error_overlay.js +39 -268
- package/src/plugins/html_supervisor/client/html_supervisor_installer.js +5 -8
- package/src/plugins/html_supervisor/jsenv_plugin_html_supervisor.js +53 -15
|
@@ -1,33 +1,43 @@
|
|
|
1
|
+
import { formatError } from "./error_formatter.js"
|
|
2
|
+
|
|
1
3
|
const JSENV_ERROR_OVERLAY_TAGNAME = "jsenv-error-overlay"
|
|
2
4
|
|
|
3
5
|
export const displayErrorInDocument = (
|
|
4
6
|
error,
|
|
5
7
|
{
|
|
6
8
|
rootDirectoryUrl,
|
|
9
|
+
errorBaseUrl,
|
|
7
10
|
openInEditor,
|
|
8
11
|
url,
|
|
9
12
|
line,
|
|
10
13
|
column,
|
|
14
|
+
codeFrame,
|
|
11
15
|
reportedBy,
|
|
12
16
|
requestedRessource,
|
|
13
17
|
},
|
|
14
18
|
) => {
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
+
const { theme, title, text, codeFramePromise, tip } = formatError(error, {
|
|
20
|
+
rootDirectoryUrl,
|
|
21
|
+
errorBaseUrl,
|
|
22
|
+
openInEditor,
|
|
19
23
|
url,
|
|
20
24
|
line,
|
|
21
25
|
column,
|
|
26
|
+
codeFrame,
|
|
22
27
|
reportedBy,
|
|
23
28
|
requestedRessource,
|
|
24
29
|
})
|
|
30
|
+
|
|
25
31
|
let jsenvErrorOverlay = new JsenvErrorOverlay({
|
|
26
32
|
theme,
|
|
27
33
|
title,
|
|
28
|
-
text
|
|
34
|
+
text,
|
|
35
|
+
codeFramePromise,
|
|
29
36
|
tip,
|
|
30
37
|
})
|
|
38
|
+
document.querySelectorAll(JSENV_ERROR_OVERLAY_TAGNAME).forEach((node) => {
|
|
39
|
+
node.parentNode.removeChild(node)
|
|
40
|
+
})
|
|
31
41
|
document.body.appendChild(jsenvErrorOverlay)
|
|
32
42
|
const removeErrorOverlay = () => {
|
|
33
43
|
if (jsenvErrorOverlay && jsenvErrorOverlay.parentNode) {
|
|
@@ -45,29 +55,24 @@ export const displayErrorInDocument = (
|
|
|
45
55
|
return removeErrorOverlay
|
|
46
56
|
}
|
|
47
57
|
|
|
48
|
-
const createErrorText = ({
|
|
49
|
-
rootDirectoryUrl,
|
|
50
|
-
openInEditor,
|
|
51
|
-
message,
|
|
52
|
-
stack,
|
|
53
|
-
}) => {
|
|
54
|
-
if (message && stack) {
|
|
55
|
-
return `${replaceLinks(message, {
|
|
56
|
-
rootDirectoryUrl,
|
|
57
|
-
openInEditor,
|
|
58
|
-
})}\n${replaceLinks(stack, { rootDirectoryUrl, openInEditor })}`
|
|
59
|
-
}
|
|
60
|
-
if (stack) {
|
|
61
|
-
return replaceLinks(stack, { rootDirectoryUrl, openInEditor })
|
|
62
|
-
}
|
|
63
|
-
return replaceLinks(message, { rootDirectoryUrl, openInEditor })
|
|
64
|
-
}
|
|
65
|
-
|
|
66
58
|
class JsenvErrorOverlay extends HTMLElement {
|
|
67
|
-
constructor({ theme, title, text, tip }) {
|
|
59
|
+
constructor({ theme, title, text, codeFramePromise, tip }) {
|
|
68
60
|
super()
|
|
69
61
|
this.root = this.attachShadow({ mode: "open" })
|
|
70
|
-
this.root.innerHTML =
|
|
62
|
+
this.root.innerHTML = `
|
|
63
|
+
<style>
|
|
64
|
+
${overlayCSS}
|
|
65
|
+
</style>
|
|
66
|
+
<div class="backdrop"></div>
|
|
67
|
+
<div class="overlay" data-theme=${theme}>
|
|
68
|
+
<h1 class="title">
|
|
69
|
+
${title}
|
|
70
|
+
</h1>
|
|
71
|
+
<pre class="text">${text}</pre>
|
|
72
|
+
<div class="tip">
|
|
73
|
+
${tip}
|
|
74
|
+
</div>
|
|
75
|
+
</div>`
|
|
71
76
|
this.root.querySelector(".backdrop").onclick = () => {
|
|
72
77
|
if (!this.parentNode) {
|
|
73
78
|
// not in document anymore
|
|
@@ -76,10 +81,13 @@ class JsenvErrorOverlay extends HTMLElement {
|
|
|
76
81
|
this.root.querySelector(".backdrop").onclick = null
|
|
77
82
|
this.parentNode.removeChild(this)
|
|
78
83
|
}
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
84
|
+
if (codeFramePromise) {
|
|
85
|
+
codeFramePromise.then((codeFrame) => {
|
|
86
|
+
if (this.parentNode) {
|
|
87
|
+
this.root.querySelector(".text").innerHTML += `\n\n${codeFrame}`
|
|
88
|
+
}
|
|
89
|
+
})
|
|
90
|
+
}
|
|
83
91
|
}
|
|
84
92
|
}
|
|
85
93
|
|
|
@@ -87,8 +95,7 @@ if (customElements && !customElements.get(JSENV_ERROR_OVERLAY_TAGNAME)) {
|
|
|
87
95
|
customElements.define(JSENV_ERROR_OVERLAY_TAGNAME, JsenvErrorOverlay)
|
|
88
96
|
}
|
|
89
97
|
|
|
90
|
-
const
|
|
91
|
-
<style>
|
|
98
|
+
const overlayCSS = `
|
|
92
99
|
:host {
|
|
93
100
|
position: fixed;
|
|
94
101
|
z-index: 99999;
|
|
@@ -162,240 +169,4 @@ pre {
|
|
|
162
169
|
|
|
163
170
|
pre a {
|
|
164
171
|
color: inherit;
|
|
165
|
-
}
|
|
166
|
-
</style>
|
|
167
|
-
<div class="backdrop"></div>
|
|
168
|
-
<div class="overlay">
|
|
169
|
-
<h1 class="title"></h1>
|
|
170
|
-
<pre class="text"></pre>
|
|
171
|
-
<div class="tip"></div>
|
|
172
|
-
</div>
|
|
173
|
-
`
|
|
174
|
-
|
|
175
|
-
const parseErrorInfo = (error) => {
|
|
176
|
-
if (error === undefined) {
|
|
177
|
-
return {
|
|
178
|
-
message: "undefined",
|
|
179
|
-
}
|
|
180
|
-
}
|
|
181
|
-
if (error === null) {
|
|
182
|
-
return {
|
|
183
|
-
message: "null",
|
|
184
|
-
}
|
|
185
|
-
}
|
|
186
|
-
if (typeof error === "string") {
|
|
187
|
-
return {
|
|
188
|
-
message: error,
|
|
189
|
-
}
|
|
190
|
-
}
|
|
191
|
-
if (error instanceof Error) {
|
|
192
|
-
if (error.name === "SyntaxError") {
|
|
193
|
-
return {
|
|
194
|
-
message: error.message,
|
|
195
|
-
}
|
|
196
|
-
}
|
|
197
|
-
if (error.cause && error.cause.code === "PARSE_ERROR") {
|
|
198
|
-
if (error.messageHTML) {
|
|
199
|
-
return {
|
|
200
|
-
message: error.messageHTML,
|
|
201
|
-
}
|
|
202
|
-
}
|
|
203
|
-
return {
|
|
204
|
-
message: error.message,
|
|
205
|
-
}
|
|
206
|
-
}
|
|
207
|
-
// stackTrace formatted by V8
|
|
208
|
-
if (Error.captureStackTrace) {
|
|
209
|
-
return {
|
|
210
|
-
message: error.message,
|
|
211
|
-
stack: getErrorStackWithoutErrorMessage(error),
|
|
212
|
-
}
|
|
213
|
-
}
|
|
214
|
-
return {
|
|
215
|
-
message: error.message,
|
|
216
|
-
stack: error.stack ? ` ${error.stack}` : null,
|
|
217
|
-
}
|
|
218
|
-
}
|
|
219
|
-
if (typeof error === "object") {
|
|
220
|
-
return error
|
|
221
|
-
}
|
|
222
|
-
return {
|
|
223
|
-
message: JSON.stringify(error),
|
|
224
|
-
}
|
|
225
|
-
}
|
|
226
|
-
|
|
227
|
-
const getErrorStackWithoutErrorMessage = (error) => {
|
|
228
|
-
let stack = error.stack
|
|
229
|
-
const messageInStack = `${error.name}: ${error.message}`
|
|
230
|
-
if (stack.startsWith(messageInStack)) {
|
|
231
|
-
stack = stack.slice(messageInStack.length)
|
|
232
|
-
}
|
|
233
|
-
const nextLineIndex = stack.indexOf("\n")
|
|
234
|
-
if (nextLineIndex > -1) {
|
|
235
|
-
stack = stack.slice(nextLineIndex + 1)
|
|
236
|
-
}
|
|
237
|
-
return stack
|
|
238
|
-
}
|
|
239
|
-
|
|
240
|
-
const errorToHTML = (
|
|
241
|
-
error,
|
|
242
|
-
{ url, line, column, reportedBy, requestedRessource },
|
|
243
|
-
) => {
|
|
244
|
-
let { message, stack } = parseErrorInfo(error)
|
|
245
|
-
if (url) {
|
|
246
|
-
if (!stack || (error && error.name === "SyntaxError")) {
|
|
247
|
-
stack = ` at ${appendLineAndColumn(url, { line, column })}`
|
|
248
|
-
}
|
|
249
|
-
}
|
|
250
|
-
let tip = formatTip({ reportedBy, requestedRessource })
|
|
251
|
-
return {
|
|
252
|
-
theme:
|
|
253
|
-
error && error.cause && error.cause.code === "PARSE_ERROR"
|
|
254
|
-
? "light"
|
|
255
|
-
: "dark",
|
|
256
|
-
title: "An error occured",
|
|
257
|
-
message,
|
|
258
|
-
stack,
|
|
259
|
-
tip: `${tip}
|
|
260
|
-
<br />
|
|
261
|
-
Click outside to close.`,
|
|
262
|
-
}
|
|
263
|
-
}
|
|
264
|
-
|
|
265
|
-
const formatTip = ({ reportedBy, requestedRessource }) => {
|
|
266
|
-
if (reportedBy === "browser") {
|
|
267
|
-
return `Reported by the browser while executing <code>${window.location.pathname}${window.location.search}</code>.`
|
|
268
|
-
}
|
|
269
|
-
return `Reported by the server while serving <code>${requestedRessource}</code>`
|
|
270
|
-
}
|
|
271
|
-
|
|
272
|
-
const replaceLinks = (string, { rootDirectoryUrl, openInEditor }) => {
|
|
273
|
-
// normalize line breaks
|
|
274
|
-
string = string.replace(/\n/g, "\n")
|
|
275
|
-
string = escapeHtml(string)
|
|
276
|
-
// render links
|
|
277
|
-
string = stringToStringWithLink(string, {
|
|
278
|
-
transform: (url, { line, column }) => {
|
|
279
|
-
const urlObject = new URL(url)
|
|
280
|
-
|
|
281
|
-
const onFileUrl = (fileUrlObject) => {
|
|
282
|
-
const atFsIndex = fileUrlObject.pathname.indexOf("/@fs/")
|
|
283
|
-
let fileUrl
|
|
284
|
-
if (atFsIndex > -1) {
|
|
285
|
-
const afterAtFs = fileUrlObject.pathname.slice(
|
|
286
|
-
atFsIndex + "/@fs/".length,
|
|
287
|
-
)
|
|
288
|
-
fileUrl = new URL(afterAtFs, "file:///").href
|
|
289
|
-
} else {
|
|
290
|
-
fileUrl = fileUrlObject.href
|
|
291
|
-
}
|
|
292
|
-
fileUrl = appendLineAndColumn(fileUrl, {
|
|
293
|
-
line,
|
|
294
|
-
column,
|
|
295
|
-
})
|
|
296
|
-
return link({
|
|
297
|
-
href: openInEditor
|
|
298
|
-
? `javascript:window.fetch('/__open_in_editor__/${fileUrl}')`
|
|
299
|
-
: fileUrl,
|
|
300
|
-
text: fileUrl,
|
|
301
|
-
})
|
|
302
|
-
}
|
|
303
|
-
|
|
304
|
-
if (urlObject.origin === window.origin) {
|
|
305
|
-
const fileUrlObject = new URL(
|
|
306
|
-
`${urlObject.pathname.slice(1)}${urlObject.search}`,
|
|
307
|
-
rootDirectoryUrl,
|
|
308
|
-
)
|
|
309
|
-
return onFileUrl(fileUrlObject)
|
|
310
|
-
}
|
|
311
|
-
if (urlObject.href.startsWith("file:")) {
|
|
312
|
-
return onFileUrl(urlObject)
|
|
313
|
-
}
|
|
314
|
-
return link({
|
|
315
|
-
href: url,
|
|
316
|
-
text: appendLineAndColumn(url, { line, column }),
|
|
317
|
-
})
|
|
318
|
-
},
|
|
319
|
-
})
|
|
320
|
-
return string
|
|
321
|
-
}
|
|
322
|
-
|
|
323
|
-
const escapeHtml = (string) => {
|
|
324
|
-
return string
|
|
325
|
-
.replace(/&/g, "&")
|
|
326
|
-
.replace(/</g, "<")
|
|
327
|
-
.replace(/>/g, ">")
|
|
328
|
-
.replace(/"/g, """)
|
|
329
|
-
.replace(/'/g, "'")
|
|
330
|
-
}
|
|
331
|
-
|
|
332
|
-
const appendLineAndColumn = (url, { line, column }) => {
|
|
333
|
-
if (line !== undefined && column !== undefined) {
|
|
334
|
-
return `${url}:${line}:${column}`
|
|
335
|
-
}
|
|
336
|
-
if (line !== undefined) {
|
|
337
|
-
return `${url}:${line}`
|
|
338
|
-
}
|
|
339
|
-
return url
|
|
340
|
-
}
|
|
341
|
-
|
|
342
|
-
// `Error: yo
|
|
343
|
-
// at Object.execute (http://127.0.0.1:57300/build/src/__test__/file-throw.js:9:13)
|
|
344
|
-
// at doExec (http://127.0.0.1:3000/src/__test__/file-throw.js:452:38)
|
|
345
|
-
// at postOrderExec (http://127.0.0.1:3000/src/__test__/file-throw.js:448:16)
|
|
346
|
-
// at http://127.0.0.1:3000/src/__test__/file-throw.js:399:18`.replace(/(?:https?|ftp|file):\/\/(.*+)$/gm, (...args) => {
|
|
347
|
-
// debugger
|
|
348
|
-
// })
|
|
349
|
-
const stringToStringWithLink = (
|
|
350
|
-
source,
|
|
351
|
-
{
|
|
352
|
-
transform = (url) => {
|
|
353
|
-
return {
|
|
354
|
-
href: url,
|
|
355
|
-
text: url,
|
|
356
|
-
}
|
|
357
|
-
},
|
|
358
|
-
} = {},
|
|
359
|
-
) => {
|
|
360
|
-
return source.replace(/(?:https?|ftp|file):\/\/\S+/gm, (match) => {
|
|
361
|
-
let linkHTML = ""
|
|
362
|
-
|
|
363
|
-
const lastChar = match[match.length - 1]
|
|
364
|
-
|
|
365
|
-
// hotfix because our url regex sucks a bit
|
|
366
|
-
const endsWithSeparationChar = lastChar === ")" || lastChar === ":"
|
|
367
|
-
if (endsWithSeparationChar) {
|
|
368
|
-
match = match.slice(0, -1)
|
|
369
|
-
}
|
|
370
|
-
|
|
371
|
-
const lineAndColumnPattern = /:([0-9]+):([0-9]+)$/
|
|
372
|
-
const lineAndColumMatch = match.match(lineAndColumnPattern)
|
|
373
|
-
if (lineAndColumMatch) {
|
|
374
|
-
const lineAndColumnString = lineAndColumMatch[0]
|
|
375
|
-
const lineNumber = lineAndColumMatch[1]
|
|
376
|
-
const columnNumber = lineAndColumMatch[2]
|
|
377
|
-
linkHTML = transform(match.slice(0, -lineAndColumnString.length), {
|
|
378
|
-
line: lineNumber,
|
|
379
|
-
column: columnNumber,
|
|
380
|
-
})
|
|
381
|
-
} else {
|
|
382
|
-
const linePattern = /:([0-9]+)$/
|
|
383
|
-
const lineMatch = match.match(linePattern)
|
|
384
|
-
if (lineMatch) {
|
|
385
|
-
const lineString = lineMatch[0]
|
|
386
|
-
const lineNumber = lineMatch[1]
|
|
387
|
-
linkHTML = transform(match.slice(0, -lineString.length), {
|
|
388
|
-
line: lineNumber,
|
|
389
|
-
})
|
|
390
|
-
} else {
|
|
391
|
-
linkHTML = transform(match, {})
|
|
392
|
-
}
|
|
393
|
-
}
|
|
394
|
-
if (endsWithSeparationChar) {
|
|
395
|
-
return `${linkHTML}${lastChar}`
|
|
396
|
-
}
|
|
397
|
-
return linkHTML
|
|
398
|
-
})
|
|
399
|
-
}
|
|
400
|
-
|
|
401
|
-
const link = ({ href, text = href }) => `<a href="${href}">${text}</a>`
|
|
172
|
+
}`
|
|
@@ -11,6 +11,7 @@ export const installHtmlSupervisor = ({
|
|
|
11
11
|
logs,
|
|
12
12
|
measurePerf,
|
|
13
13
|
errorOverlay,
|
|
14
|
+
errorBaseUrl,
|
|
14
15
|
openInEditor,
|
|
15
16
|
}) => {
|
|
16
17
|
const errorTransformer = null // could implement error stack remapping if needed
|
|
@@ -227,6 +228,7 @@ export const installHtmlSupervisor = ({
|
|
|
227
228
|
const { error } = errorEvent
|
|
228
229
|
displayErrorInDocument(error, {
|
|
229
230
|
rootDirectoryUrl,
|
|
231
|
+
errorBaseUrl,
|
|
230
232
|
openInEditor,
|
|
231
233
|
url: errorEvent.filename,
|
|
232
234
|
line: errorEvent.lineno,
|
|
@@ -276,21 +278,16 @@ export const installHtmlSupervisor = ({
|
|
|
276
278
|
displayErrorInDocument(
|
|
277
279
|
{
|
|
278
280
|
message,
|
|
279
|
-
stack
|
|
280
|
-
stack && traceMessage
|
|
281
|
-
? `${stack}\n\n${traceMessage}`
|
|
282
|
-
: stack
|
|
283
|
-
? stack
|
|
284
|
-
: traceMessage
|
|
285
|
-
? `\n${traceMessage}`
|
|
286
|
-
: "",
|
|
281
|
+
stack,
|
|
287
282
|
},
|
|
288
283
|
{
|
|
289
284
|
rootDirectoryUrl,
|
|
285
|
+
errorBaseUrl,
|
|
290
286
|
openInEditor,
|
|
291
287
|
url: traceUrl,
|
|
292
288
|
line: traceLine,
|
|
293
289
|
column: traceColumn,
|
|
290
|
+
codeFrame: traceMessage,
|
|
294
291
|
reportedBy: "server",
|
|
295
292
|
requestedRessource,
|
|
296
293
|
},
|
|
@@ -19,7 +19,7 @@ import {
|
|
|
19
19
|
removeHtmlNodeText,
|
|
20
20
|
setHtmlNodeText,
|
|
21
21
|
} from "@jsenv/ast"
|
|
22
|
-
import { generateInlineContentUrl } from "@jsenv/urls"
|
|
22
|
+
import { generateInlineContentUrl, stringifyUrlSite } from "@jsenv/urls"
|
|
23
23
|
|
|
24
24
|
import { requireFromJsenv } from "@jsenv/core/src/require_from_jsenv.js"
|
|
25
25
|
|
|
@@ -28,6 +28,7 @@ export const jsenvPluginHtmlSupervisor = ({
|
|
|
28
28
|
measurePerf = false,
|
|
29
29
|
errorOverlay = true,
|
|
30
30
|
openInEditor = true,
|
|
31
|
+
errorBaseUrl,
|
|
31
32
|
}) => {
|
|
32
33
|
const htmlSupervisorSetupFileUrl = new URL(
|
|
33
34
|
"./client/html_supervisor_setup.js?js_classic",
|
|
@@ -45,24 +46,60 @@ export const jsenvPluginHtmlSupervisor = ({
|
|
|
45
46
|
dev: true,
|
|
46
47
|
test: true,
|
|
47
48
|
},
|
|
48
|
-
serve: (request) => {
|
|
49
|
-
if (
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
49
|
+
serve: (request, context) => {
|
|
50
|
+
if (request.ressource.startsWith("/__open_in_editor__/")) {
|
|
51
|
+
const file = request.ressource.slice("/__open_in_editor__/".length)
|
|
52
|
+
if (!file) {
|
|
53
|
+
return {
|
|
54
|
+
status: 400,
|
|
55
|
+
body: "Missing file in url",
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
const launch = requireFromJsenv("launch-editor")
|
|
59
|
+
launch(fileURLToPath(file), () => {
|
|
60
|
+
// ignore error for now
|
|
61
|
+
})
|
|
54
62
|
return {
|
|
55
|
-
status:
|
|
56
|
-
|
|
63
|
+
status: 200,
|
|
64
|
+
headers: {
|
|
65
|
+
"cache-control": "no-store",
|
|
66
|
+
},
|
|
57
67
|
}
|
|
58
68
|
}
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
69
|
+
if (request.ressource.startsWith("/__get_code_frame__/")) {
|
|
70
|
+
const url = request.ressource.slice("/__get_code_frame__/".length)
|
|
71
|
+
const match = url.match(/:([0-9]+):([0-9]+)$/)
|
|
72
|
+
if (!match) {
|
|
73
|
+
return {
|
|
74
|
+
status: 400,
|
|
75
|
+
body: "Missing line and column in url",
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
const file = url.slice(0, match.index)
|
|
79
|
+
const line = parseInt(match[1])
|
|
80
|
+
const column = parseInt(match[2])
|
|
81
|
+
const urlInfo = context.urlGraph.getUrlInfo(file)
|
|
82
|
+
if (!urlInfo) {
|
|
83
|
+
return {
|
|
84
|
+
status: 404,
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
const codeFrame = stringifyUrlSite({
|
|
88
|
+
url: file,
|
|
89
|
+
line,
|
|
90
|
+
column,
|
|
91
|
+
content: urlInfo.originalContent,
|
|
92
|
+
})
|
|
93
|
+
return {
|
|
94
|
+
status: 200,
|
|
95
|
+
headers: {
|
|
96
|
+
"content-type": "text/plain",
|
|
97
|
+
"content-length": Buffer.byteLength(codeFrame),
|
|
98
|
+
},
|
|
99
|
+
body: codeFrame,
|
|
100
|
+
}
|
|
65
101
|
}
|
|
102
|
+
return null
|
|
66
103
|
},
|
|
67
104
|
transformUrlContent: {
|
|
68
105
|
html: ({ url, content }, context) => {
|
|
@@ -173,6 +210,7 @@ export const jsenvPluginHtmlSupervisor = ({
|
|
|
173
210
|
installHtmlSupervisor(${JSON.stringify(
|
|
174
211
|
{
|
|
175
212
|
rootDirectoryUrl: context.rootDirectoryUrl,
|
|
213
|
+
errorBaseUrl,
|
|
176
214
|
logs,
|
|
177
215
|
measurePerf,
|
|
178
216
|
errorOverlay,
|