@jsenv/core 27.5.1 → 27.6.0
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/autoreload.js +7 -0
- package/dist/js/html_supervisor_installer.js +524 -344
- package/dist/main.js +179 -95
- package/package.json +3 -2
- package/src/dev/start_dev_server.js +4 -0
- package/src/omega/kitchen.js +13 -15
- package/src/omega/omega_server.js +4 -0
- package/src/omega/server/file_service.js +9 -66
- package/src/omega/url_graph/url_graph_load.js +0 -2
- package/src/omega/url_graph/url_info_transformations.js +27 -0
- package/src/omega/url_graph.js +1 -0
- package/src/plugins/autoreload/client/autoreload.js +7 -0
- package/src/plugins/html_supervisor/client/error_formatter.js +413 -0
- package/src/plugins/html_supervisor/client/error_overlay.js +62 -272
- package/src/plugins/html_supervisor/client/error_site_remap.js +85 -0
- package/src/plugins/html_supervisor/client/html_supervisor_installer.js +26 -77
- package/src/plugins/html_supervisor/jsenv_plugin_html_supervisor.js +103 -14
- package/src/test/execute_test_plan.js +1 -2
|
@@ -34,17 +34,11 @@ export const createFileService = ({
|
|
|
34
34
|
cooldownBetweenFileEvents,
|
|
35
35
|
explorer,
|
|
36
36
|
sourcemaps,
|
|
37
|
+
sourcemapsSourcesProtocol,
|
|
38
|
+
sourcemapsSourcesContent,
|
|
37
39
|
writeGeneratedFiles,
|
|
38
40
|
}) => {
|
|
39
41
|
const jsenvDirectoryUrl = new URL(".jsenv/", rootDirectoryUrl).href
|
|
40
|
-
const onErrorWhileServingFileCallbacks = []
|
|
41
|
-
const onErrorWhileServingFile = (data) => {
|
|
42
|
-
onErrorWhileServingFileCallbacks.forEach(
|
|
43
|
-
(onErrorWhileServingFileCallback) => {
|
|
44
|
-
onErrorWhileServingFileCallback(data)
|
|
45
|
-
},
|
|
46
|
-
)
|
|
47
|
-
}
|
|
48
42
|
|
|
49
43
|
const clientFileChangeCallbackList = []
|
|
50
44
|
const clientFilesPruneCallbackList = []
|
|
@@ -113,6 +107,9 @@ export const createFileService = ({
|
|
|
113
107
|
rootDirectoryUrl,
|
|
114
108
|
scenario,
|
|
115
109
|
runtimeCompat,
|
|
110
|
+
clientRuntimeCompat: {
|
|
111
|
+
[runtimeName]: runtimeVersion,
|
|
112
|
+
},
|
|
116
113
|
urlGraph,
|
|
117
114
|
plugins: [
|
|
118
115
|
...plugins,
|
|
@@ -134,6 +131,8 @@ export const createFileService = ({
|
|
|
134
131
|
}),
|
|
135
132
|
],
|
|
136
133
|
sourcemaps,
|
|
134
|
+
sourcemapsSourcesProtocol,
|
|
135
|
+
sourcemapsSourcesContent,
|
|
137
136
|
writeGeneratedFiles,
|
|
138
137
|
})
|
|
139
138
|
serverStopCallbacks.push(() => {
|
|
@@ -170,28 +169,6 @@ export const createFileService = ({
|
|
|
170
169
|
},
|
|
171
170
|
})
|
|
172
171
|
})
|
|
173
|
-
onErrorWhileServingFileCallbacks.push((data) => {
|
|
174
|
-
serverEventsDispatcher.dispatchToRoomsMatching(
|
|
175
|
-
{
|
|
176
|
-
type: "error_while_serving_file",
|
|
177
|
-
data,
|
|
178
|
-
},
|
|
179
|
-
(room) => {
|
|
180
|
-
// send only to page depending on this file
|
|
181
|
-
const errorFileUrl = data.url
|
|
182
|
-
const roomEntryPointUrl = new URL(
|
|
183
|
-
room.request.ressource.slice(1),
|
|
184
|
-
rootDirectoryUrl,
|
|
185
|
-
).href
|
|
186
|
-
const isErrorRelatedToEntryPoint = Boolean(
|
|
187
|
-
urlGraph.findDependent(errorFileUrl, (dependentUrlInfo) => {
|
|
188
|
-
return dependentUrlInfo.url === roomEntryPointUrl
|
|
189
|
-
}),
|
|
190
|
-
)
|
|
191
|
-
return isErrorRelatedToEntryPoint
|
|
192
|
-
},
|
|
193
|
-
)
|
|
194
|
-
})
|
|
195
172
|
// "unshift" because serve must come first to catch event source client request
|
|
196
173
|
kitchen.pluginController.unshiftPlugin({
|
|
197
174
|
name: "jsenv:provide_server_events",
|
|
@@ -286,6 +263,7 @@ export const createFileService = ({
|
|
|
286
263
|
!urlInfo.isInline &&
|
|
287
264
|
urlInfo.type !== "sourcemap"
|
|
288
265
|
) {
|
|
266
|
+
urlInfo.error = null
|
|
289
267
|
urlInfo.sourcemap = null
|
|
290
268
|
urlInfo.sourcemapReference = null
|
|
291
269
|
urlInfo.content = null
|
|
@@ -298,9 +276,6 @@ export const createFileService = ({
|
|
|
298
276
|
await kitchen.cook(urlInfo, {
|
|
299
277
|
request,
|
|
300
278
|
reference,
|
|
301
|
-
clientRuntimeCompat: {
|
|
302
|
-
[runtimeName]: runtimeVersion,
|
|
303
|
-
},
|
|
304
279
|
outDirectoryUrl:
|
|
305
280
|
scenario === "dev"
|
|
306
281
|
? `${rootDirectoryUrl}.jsenv/${runtimeName}@${runtimeVersion}/`
|
|
@@ -333,18 +308,9 @@ export const createFileService = ({
|
|
|
333
308
|
)
|
|
334
309
|
return response
|
|
335
310
|
} catch (e) {
|
|
311
|
+
urlInfo.error = e
|
|
336
312
|
const code = e.code
|
|
337
313
|
if (code === "PARSE_ERROR") {
|
|
338
|
-
onErrorWhileServingFile({
|
|
339
|
-
requestedRessource: request.ressource,
|
|
340
|
-
code: "PARSE_ERROR",
|
|
341
|
-
message: e.reason,
|
|
342
|
-
url: e.url,
|
|
343
|
-
traceUrl: e.traceUrl,
|
|
344
|
-
traceLine: e.traceLine,
|
|
345
|
-
traceColumn: e.traceColumn,
|
|
346
|
-
traceMessage: e.traceMessage,
|
|
347
|
-
})
|
|
348
314
|
return {
|
|
349
315
|
url: reference.url,
|
|
350
316
|
status: 200, // let the browser re-throw the syntax error
|
|
@@ -375,19 +341,6 @@ export const createFileService = ({
|
|
|
375
341
|
}
|
|
376
342
|
}
|
|
377
343
|
if (code === "NOT_FOUND") {
|
|
378
|
-
onErrorWhileServingFile({
|
|
379
|
-
requestedRessource: request.ressource,
|
|
380
|
-
isFaviconAutoRequest:
|
|
381
|
-
request.ressource === "/favicon.ico" &&
|
|
382
|
-
reference.type === "http_request",
|
|
383
|
-
code: "NOT_FOUND",
|
|
384
|
-
message: e.reason,
|
|
385
|
-
url: e.url,
|
|
386
|
-
traceUrl: e.traceUrl,
|
|
387
|
-
traceLine: e.traceLine,
|
|
388
|
-
traceColumn: e.traceColumn,
|
|
389
|
-
traceMessage: e.traceMessage,
|
|
390
|
-
})
|
|
391
344
|
return {
|
|
392
345
|
url: reference.url,
|
|
393
346
|
status: 404,
|
|
@@ -395,16 +348,6 @@ export const createFileService = ({
|
|
|
395
348
|
statusMessage: e.message,
|
|
396
349
|
}
|
|
397
350
|
}
|
|
398
|
-
onErrorWhileServingFile({
|
|
399
|
-
requestedRessource: request.ressource,
|
|
400
|
-
code: "UNEXPECTED",
|
|
401
|
-
stack: e.stack,
|
|
402
|
-
url: e.url,
|
|
403
|
-
traceUrl: e.traceUrl,
|
|
404
|
-
traceLine: e.traceLine,
|
|
405
|
-
traceColumn: e.traceColumn,
|
|
406
|
-
traceMessage: e.traceMessage,
|
|
407
|
-
})
|
|
408
351
|
return {
|
|
409
352
|
url: reference.url,
|
|
410
353
|
status: 500,
|
|
@@ -7,7 +7,6 @@ export const loadUrlGraph = async ({
|
|
|
7
7
|
startLoading,
|
|
8
8
|
writeGeneratedFiles,
|
|
9
9
|
outDirectoryUrl,
|
|
10
|
-
clientRuntimeCompat,
|
|
11
10
|
}) => {
|
|
12
11
|
if (writeGeneratedFiles && outDirectoryUrl) {
|
|
13
12
|
await ensureEmptyDirectory(outDirectoryUrl)
|
|
@@ -19,7 +18,6 @@ export const loadUrlGraph = async ({
|
|
|
19
18
|
if (promiseFromData) return promiseFromData
|
|
20
19
|
const promise = _cook(urlInfo, {
|
|
21
20
|
outDirectoryUrl,
|
|
22
|
-
clientRuntimeCompat,
|
|
23
21
|
...context,
|
|
24
22
|
})
|
|
25
23
|
promises.push(promise)
|
|
@@ -11,12 +11,29 @@ import {
|
|
|
11
11
|
export const createUrlInfoTransformer = ({
|
|
12
12
|
logger,
|
|
13
13
|
sourcemaps,
|
|
14
|
+
sourcemapsSourcesProtocol,
|
|
14
15
|
sourcemapsSourcesContent,
|
|
15
16
|
sourcemapsRelativeSources,
|
|
16
17
|
urlGraph,
|
|
18
|
+
clientRuntimeCompat,
|
|
17
19
|
injectSourcemapPlaceholder,
|
|
18
20
|
foundSourcemap,
|
|
19
21
|
}) => {
|
|
22
|
+
const runtimeNames = Object.keys(clientRuntimeCompat)
|
|
23
|
+
const chromeAsSingleRuntime =
|
|
24
|
+
runtimeNames.length === 1 && runtimeNames[0] === "chrome"
|
|
25
|
+
if (sourcemapsSourcesProtocol === undefined) {
|
|
26
|
+
sourcemapsSourcesProtocol = "file:///"
|
|
27
|
+
}
|
|
28
|
+
if (sourcemapsSourcesContent === undefined) {
|
|
29
|
+
if (chromeAsSingleRuntime && sourcemapsSourcesProtocol === "file:///") {
|
|
30
|
+
// chrome is able to fetch source when referenced with "file:"
|
|
31
|
+
sourcemapsSourcesContent = false
|
|
32
|
+
} else {
|
|
33
|
+
sourcemapsSourcesContent = true
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
20
37
|
const sourcemapsEnabled =
|
|
21
38
|
sourcemaps === "inline" ||
|
|
22
39
|
sourcemaps === "file" ||
|
|
@@ -172,6 +189,16 @@ export const createUrlInfoTransformer = ({
|
|
|
172
189
|
return sourceRelative || "."
|
|
173
190
|
})
|
|
174
191
|
}
|
|
192
|
+
if (sourcemapsSourcesProtocol !== "file:///") {
|
|
193
|
+
sourcemap.sources = sourcemap.sources.map((source) => {
|
|
194
|
+
if (source.startsWith("file:///")) {
|
|
195
|
+
return `${sourcemapsSourcesProtocol}${source.slice(
|
|
196
|
+
"file:///".length,
|
|
197
|
+
)}`
|
|
198
|
+
}
|
|
199
|
+
return source
|
|
200
|
+
})
|
|
201
|
+
}
|
|
175
202
|
sourcemapUrlInfo.content = JSON.stringify(sourcemap, null, " ")
|
|
176
203
|
if (!urlInfo.sourcemapIsWrong) {
|
|
177
204
|
if (sourcemaps === "inline") {
|
package/src/omega/url_graph.js
CHANGED
|
@@ -15,6 +15,7 @@ const reloader = {
|
|
|
15
15
|
isAutoreloadEnabled,
|
|
16
16
|
setAutoreloadPreference,
|
|
17
17
|
status: "idle",
|
|
18
|
+
currentExecution: null,
|
|
18
19
|
onstatuschange: () => {},
|
|
19
20
|
setStatus: (status) => {
|
|
20
21
|
reloader.status = status
|
|
@@ -49,6 +50,7 @@ const reloader = {
|
|
|
49
50
|
promise.then(
|
|
50
51
|
() => {
|
|
51
52
|
onApplied(reloadMessage)
|
|
53
|
+
reloader.currentExecution = null
|
|
52
54
|
},
|
|
53
55
|
(e) => {
|
|
54
56
|
reloader.setStatus("failed")
|
|
@@ -61,6 +63,7 @@ const reloader = {
|
|
|
61
63
|
`[jsenv] Hot reload failed after ${reloadMessage.reason}.
|
|
62
64
|
This could be due to syntax errors or importing non-existent modules (see errors in console)`,
|
|
63
65
|
)
|
|
66
|
+
reloader.currentExecution = null
|
|
64
67
|
},
|
|
65
68
|
)
|
|
66
69
|
}
|
|
@@ -146,6 +149,10 @@ const applyHotReload = async ({ hotInstructions }) => {
|
|
|
146
149
|
await urlHotMeta.disposeCallback()
|
|
147
150
|
}
|
|
148
151
|
console.log(`importing js module`)
|
|
152
|
+
reloader.currentExecution = {
|
|
153
|
+
type: "dynamic_import",
|
|
154
|
+
url: urlToFetch,
|
|
155
|
+
}
|
|
149
156
|
const namespace = await reloadJsImport(urlToFetch)
|
|
150
157
|
if (urlHotMeta.acceptCallback) {
|
|
151
158
|
await urlHotMeta.acceptCallback(namespace)
|
|
@@ -0,0 +1,413 @@
|
|
|
1
|
+
export const formatError = (
|
|
2
|
+
error,
|
|
3
|
+
{ rootDirectoryUrl, errorBaseUrl, openInEditor, url, line, column },
|
|
4
|
+
) => {
|
|
5
|
+
let { message, stack } = normalizeErrorParts(error)
|
|
6
|
+
let errorDetailsPromiseReference = { current: null }
|
|
7
|
+
let tip = `Reported by the browser while executing <code>${window.location.pathname}${window.location.search}</code>.`
|
|
8
|
+
let errorUrlSite
|
|
9
|
+
|
|
10
|
+
const errorMeta = extractErrorMeta(error, { url, line, column })
|
|
11
|
+
|
|
12
|
+
const resolveUrlSite = ({ url, line, column }) => {
|
|
13
|
+
const inlineUrlMatch = url.match(
|
|
14
|
+
/@L([0-9]+)C([0-9]+)\-L([0-9]+)C([0-9]+)(\.[\w]+)$/,
|
|
15
|
+
)
|
|
16
|
+
if (inlineUrlMatch) {
|
|
17
|
+
const htmlUrl = url.slice(0, inlineUrlMatch.index)
|
|
18
|
+
const tagLineStart = parseInt(inlineUrlMatch[1])
|
|
19
|
+
const tagColumnStart = parseInt(inlineUrlMatch[2])
|
|
20
|
+
const tagLineEnd = parseInt(inlineUrlMatch[3])
|
|
21
|
+
const tagColumnEnd = parseInt(inlineUrlMatch[4])
|
|
22
|
+
const extension = inlineUrlMatch[5]
|
|
23
|
+
url = htmlUrl
|
|
24
|
+
line = tagLineStart + (line === undefined ? 0 : parseInt(line))
|
|
25
|
+
// stackTrace formatted by V8 (chrome)
|
|
26
|
+
if (Error.captureStackTrace) {
|
|
27
|
+
line--
|
|
28
|
+
}
|
|
29
|
+
if (errorMeta.type === "dynamic_import_syntax_error") {
|
|
30
|
+
// syntax error on inline script need line-1 for some reason
|
|
31
|
+
if (Error.captureStackTrace) {
|
|
32
|
+
line--
|
|
33
|
+
} else {
|
|
34
|
+
// firefox and safari need line-2
|
|
35
|
+
line -= 2
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
column = tagColumnStart + (column === undefined ? 0 : parseInt(column))
|
|
39
|
+
const fileUrl = resolveFileUrl(url)
|
|
40
|
+
return {
|
|
41
|
+
isInline: true,
|
|
42
|
+
originalUrl: `${fileUrl}@L${tagLineStart}C${tagColumnStart}-L${tagLineEnd}C${tagColumnEnd}${extension}`,
|
|
43
|
+
url: fileUrl,
|
|
44
|
+
line,
|
|
45
|
+
column,
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
return {
|
|
49
|
+
isInline: false,
|
|
50
|
+
url: resolveFileUrl(url),
|
|
51
|
+
line,
|
|
52
|
+
column,
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
const resolveFileUrl = (url) => {
|
|
57
|
+
let urlObject = new URL(url)
|
|
58
|
+
if (urlObject.origin === window.origin) {
|
|
59
|
+
urlObject = new URL(
|
|
60
|
+
`${urlObject.pathname.slice(1)}${urlObject.search}`,
|
|
61
|
+
rootDirectoryUrl,
|
|
62
|
+
)
|
|
63
|
+
}
|
|
64
|
+
if (urlObject.href.startsWith("file:")) {
|
|
65
|
+
const atFsIndex = urlObject.pathname.indexOf("/@fs/")
|
|
66
|
+
if (atFsIndex > -1) {
|
|
67
|
+
const afterAtFs = urlObject.pathname.slice(atFsIndex + "/@fs/".length)
|
|
68
|
+
return new URL(afterAtFs, "file:///").href
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
return urlObject.href
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
const generateClickableText = (text) => {
|
|
75
|
+
const textWithHtmlLinks = makeLinksClickable(text, {
|
|
76
|
+
createLink: (url, { line, column }) => {
|
|
77
|
+
const urlSite = resolveUrlSite({ url, line, column })
|
|
78
|
+
if (!errorUrlSite && text === stack) {
|
|
79
|
+
onErrorLocated(urlSite, "error.stack")
|
|
80
|
+
}
|
|
81
|
+
if (errorBaseUrl) {
|
|
82
|
+
if (urlSite.url.startsWith(rootDirectoryUrl)) {
|
|
83
|
+
urlSite.url = `${errorBaseUrl}${urlSite.url.slice(
|
|
84
|
+
rootDirectoryUrl.length,
|
|
85
|
+
)}`
|
|
86
|
+
} else {
|
|
87
|
+
urlSite.url = "file:///mocked_for_snapshots"
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
const urlWithLineAndColumn = formatUrlWithLineAndColumn(urlSite)
|
|
91
|
+
return {
|
|
92
|
+
href:
|
|
93
|
+
url.startsWith("file:") && openInEditor
|
|
94
|
+
? `javascript:window.fetch('/__open_in_editor__/${urlWithLineAndColumn}')`
|
|
95
|
+
: urlSite.url,
|
|
96
|
+
text: urlWithLineAndColumn,
|
|
97
|
+
}
|
|
98
|
+
},
|
|
99
|
+
})
|
|
100
|
+
return textWithHtmlLinks
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
const formatErrorText = ({ message, stack, codeFrame }) => {
|
|
104
|
+
let text
|
|
105
|
+
if (message && stack) {
|
|
106
|
+
text = `${generateClickableText(message)}\n${generateClickableText(
|
|
107
|
+
stack,
|
|
108
|
+
)}`
|
|
109
|
+
} else if (stack) {
|
|
110
|
+
text = generateClickableText(stack)
|
|
111
|
+
} else {
|
|
112
|
+
text = generateClickableText(message)
|
|
113
|
+
}
|
|
114
|
+
if (codeFrame) {
|
|
115
|
+
text += `\n\n${generateClickableText(codeFrame)}`
|
|
116
|
+
}
|
|
117
|
+
return text
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
const onErrorLocated = (urlSite) => {
|
|
121
|
+
errorUrlSite = urlSite
|
|
122
|
+
errorDetailsPromiseReference.current = (async () => {
|
|
123
|
+
if (errorMeta.type === "dynamic_import_fetch_error") {
|
|
124
|
+
const response = await window.fetch(
|
|
125
|
+
`/__get_error_cause__/${
|
|
126
|
+
urlSite.isInline ? urlSite.originalUrl : urlSite.url
|
|
127
|
+
}`,
|
|
128
|
+
)
|
|
129
|
+
if (response.status !== 200) {
|
|
130
|
+
return null
|
|
131
|
+
}
|
|
132
|
+
const causeInfo = await response.json()
|
|
133
|
+
if (!causeInfo) {
|
|
134
|
+
return null
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
const causeText =
|
|
138
|
+
causeInfo.code === "NOT_FOUND"
|
|
139
|
+
? formatErrorText({
|
|
140
|
+
message: causeInfo.reason,
|
|
141
|
+
stack: causeInfo.codeFrame,
|
|
142
|
+
})
|
|
143
|
+
: formatErrorText({
|
|
144
|
+
message: causeInfo.stack,
|
|
145
|
+
stack: causeInfo.codeFrame,
|
|
146
|
+
})
|
|
147
|
+
return {
|
|
148
|
+
cause: causeText,
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
if (urlSite.line !== undefined) {
|
|
152
|
+
const response = await window.fetch(
|
|
153
|
+
`/__get_code_frame__/${formatUrlWithLineAndColumn(urlSite)}`,
|
|
154
|
+
)
|
|
155
|
+
const codeFrame = await response.text()
|
|
156
|
+
return {
|
|
157
|
+
codeFrame: formatErrorText({ message: codeFrame }),
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
return null
|
|
161
|
+
})()
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
// error.stack is more reliable than url/line/column reported on window error events
|
|
165
|
+
// so use it only when error.stack is not available
|
|
166
|
+
if (
|
|
167
|
+
url &&
|
|
168
|
+
!stack &&
|
|
169
|
+
// ignore window.reportError() it gives no valuable info
|
|
170
|
+
!url.endsWith("html_supervisor_installer.js")
|
|
171
|
+
) {
|
|
172
|
+
onErrorLocated(resolveUrlSite({ url, line, column }))
|
|
173
|
+
} else if (errorMeta.url) {
|
|
174
|
+
onErrorLocated(resolveUrlSite(errorMeta))
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
return {
|
|
178
|
+
theme:
|
|
179
|
+
error && error.cause && error.cause.code === "PARSE_ERROR"
|
|
180
|
+
? "light"
|
|
181
|
+
: "dark",
|
|
182
|
+
title: "An error occured",
|
|
183
|
+
text: formatErrorText({ message, stack }),
|
|
184
|
+
tip: `${tip}
|
|
185
|
+
<br />
|
|
186
|
+
Click outside to close.`,
|
|
187
|
+
errorDetailsPromise: errorDetailsPromiseReference.current,
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
const extractErrorMeta = (error, { line }) => {
|
|
192
|
+
if (!error) {
|
|
193
|
+
return {}
|
|
194
|
+
}
|
|
195
|
+
const { message } = error
|
|
196
|
+
if (!message) {
|
|
197
|
+
return {}
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
export_missing: {
|
|
201
|
+
// chrome
|
|
202
|
+
if (message.includes("does not provide an export named")) {
|
|
203
|
+
return {
|
|
204
|
+
type: "dynamic_import_export_missing",
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
// firefox
|
|
208
|
+
if (message.startsWith("import not found:")) {
|
|
209
|
+
return {
|
|
210
|
+
type: "dynamic_import_export_missing",
|
|
211
|
+
browser: "firefox",
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
// safari
|
|
215
|
+
if (message.startsWith("import binding name")) {
|
|
216
|
+
return {
|
|
217
|
+
type: "dynamic_import_export_missing",
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
js_syntax_error: {
|
|
223
|
+
if (error.name === "SyntaxError" && typeof line === "number") {
|
|
224
|
+
return {
|
|
225
|
+
type: "dynamic_import_syntax_error",
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
fetch_error: {
|
|
231
|
+
// chrome
|
|
232
|
+
if (message.startsWith("Failed to fetch dynamically imported module: ")) {
|
|
233
|
+
const url = error.message.slice(
|
|
234
|
+
"Failed to fetch dynamically imported module: ".length,
|
|
235
|
+
)
|
|
236
|
+
return {
|
|
237
|
+
type: "dynamic_import_fetch_error",
|
|
238
|
+
url,
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
// firefox
|
|
242
|
+
if (message === "error loading dynamically imported module") {
|
|
243
|
+
return {
|
|
244
|
+
type: "dynamic_import_fetch_error",
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
// safari
|
|
248
|
+
if (message === "Importing a module script failed.") {
|
|
249
|
+
return {
|
|
250
|
+
type: "dynamic_import_fetch_error",
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
return {}
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
const formatUrlWithLineAndColumn = ({ url, line, column }) => {
|
|
259
|
+
return line === undefined && column === undefined
|
|
260
|
+
? url
|
|
261
|
+
: column === undefined
|
|
262
|
+
? `${url}:${line}`
|
|
263
|
+
: `${url}:${line}:${column}`
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
const normalizeErrorParts = (error) => {
|
|
267
|
+
if (error === undefined) {
|
|
268
|
+
return {
|
|
269
|
+
message: "undefined",
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
if (error === null) {
|
|
273
|
+
return {
|
|
274
|
+
message: "null",
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
if (typeof error === "string") {
|
|
278
|
+
return {
|
|
279
|
+
message: error,
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
if (error instanceof Error) {
|
|
283
|
+
if (error.name === "SyntaxError") {
|
|
284
|
+
return {
|
|
285
|
+
message: error.message,
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
if (error.cause && error.cause.code === "PARSE_ERROR") {
|
|
289
|
+
if (error.messageHTML) {
|
|
290
|
+
return {
|
|
291
|
+
message: error.messageHTML,
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
return {
|
|
295
|
+
message: error.message,
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
// stackTrace formatted by V8
|
|
299
|
+
if (Error.captureStackTrace) {
|
|
300
|
+
return {
|
|
301
|
+
message: error.message,
|
|
302
|
+
stack: getErrorStackWithoutErrorMessage(error),
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
return {
|
|
306
|
+
message: error.message,
|
|
307
|
+
stack: error.stack ? ` ${error.stack}` : null,
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
if (typeof error === "object") {
|
|
311
|
+
return error
|
|
312
|
+
}
|
|
313
|
+
return {
|
|
314
|
+
message: JSON.stringify(error),
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
const getErrorStackWithoutErrorMessage = (error) => {
|
|
319
|
+
let stack = error.stack
|
|
320
|
+
const messageInStack = `${error.name}: ${error.message}`
|
|
321
|
+
if (stack.startsWith(messageInStack)) {
|
|
322
|
+
stack = stack.slice(messageInStack.length)
|
|
323
|
+
}
|
|
324
|
+
const nextLineIndex = stack.indexOf("\n")
|
|
325
|
+
if (nextLineIndex > -1) {
|
|
326
|
+
stack = stack.slice(nextLineIndex + 1)
|
|
327
|
+
}
|
|
328
|
+
return stack
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
const makeLinksClickable = (string, { createLink = (url) => url }) => {
|
|
332
|
+
// normalize line breaks
|
|
333
|
+
string = string.replace(/\n/g, "\n")
|
|
334
|
+
string = escapeHtml(string)
|
|
335
|
+
// render links
|
|
336
|
+
string = stringToStringWithLink(string, {
|
|
337
|
+
transform: (url, { line, column }) => {
|
|
338
|
+
const { href, text } = createLink(url, { line, column })
|
|
339
|
+
return link({ href, text })
|
|
340
|
+
},
|
|
341
|
+
})
|
|
342
|
+
return string
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
const escapeHtml = (string) => {
|
|
346
|
+
return string
|
|
347
|
+
.replace(/&/g, "&")
|
|
348
|
+
.replace(/</g, "<")
|
|
349
|
+
.replace(/>/g, ">")
|
|
350
|
+
.replace(/"/g, """)
|
|
351
|
+
.replace(/'/g, "'")
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
// `Error: yo
|
|
355
|
+
// at Object.execute (http://127.0.0.1:57300/build/src/__test__/file-throw.js:9:13)
|
|
356
|
+
// at doExec (http://127.0.0.1:3000/src/__test__/file-throw.js:452:38)
|
|
357
|
+
// at postOrderExec (http://127.0.0.1:3000/src/__test__/file-throw.js:448:16)
|
|
358
|
+
// at http://127.0.0.1:3000/src/__test__/file-throw.js:399:18`.replace(/(?:https?|ftp|file):\/\/(.*+)$/gm, (...args) => {
|
|
359
|
+
// debugger
|
|
360
|
+
// })
|
|
361
|
+
const stringToStringWithLink = (
|
|
362
|
+
source,
|
|
363
|
+
{
|
|
364
|
+
transform = (url) => {
|
|
365
|
+
return {
|
|
366
|
+
href: url,
|
|
367
|
+
text: url,
|
|
368
|
+
}
|
|
369
|
+
},
|
|
370
|
+
} = {},
|
|
371
|
+
) => {
|
|
372
|
+
return source.replace(/(?:https?|ftp|file):\/\/\S+/gm, (match) => {
|
|
373
|
+
let linkHTML = ""
|
|
374
|
+
|
|
375
|
+
const lastChar = match[match.length - 1]
|
|
376
|
+
|
|
377
|
+
// hotfix because our url regex sucks a bit
|
|
378
|
+
const endsWithSeparationChar = lastChar === ")" || lastChar === ":"
|
|
379
|
+
if (endsWithSeparationChar) {
|
|
380
|
+
match = match.slice(0, -1)
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
const lineAndColumnPattern = /:([0-9]+):([0-9]+)$/
|
|
384
|
+
const lineAndColumMatch = match.match(lineAndColumnPattern)
|
|
385
|
+
if (lineAndColumMatch) {
|
|
386
|
+
const lineAndColumnString = lineAndColumMatch[0]
|
|
387
|
+
const lineNumber = lineAndColumMatch[1]
|
|
388
|
+
const columnNumber = lineAndColumMatch[2]
|
|
389
|
+
linkHTML = transform(match.slice(0, -lineAndColumnString.length), {
|
|
390
|
+
line: lineNumber,
|
|
391
|
+
column: columnNumber,
|
|
392
|
+
})
|
|
393
|
+
} else {
|
|
394
|
+
const linePattern = /:([0-9]+)$/
|
|
395
|
+
const lineMatch = match.match(linePattern)
|
|
396
|
+
if (lineMatch) {
|
|
397
|
+
const lineString = lineMatch[0]
|
|
398
|
+
const lineNumber = lineMatch[1]
|
|
399
|
+
linkHTML = transform(match.slice(0, -lineString.length), {
|
|
400
|
+
line: lineNumber,
|
|
401
|
+
})
|
|
402
|
+
} else {
|
|
403
|
+
linkHTML = transform(match, {})
|
|
404
|
+
}
|
|
405
|
+
}
|
|
406
|
+
if (endsWithSeparationChar) {
|
|
407
|
+
return `${linkHTML}${lastChar}`
|
|
408
|
+
}
|
|
409
|
+
return linkHTML
|
|
410
|
+
})
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
const link = ({ href, text = href }) => `<a href="${href}">${text}</a>`
|