@jsenv/sourcemap 1.0.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/package.json +26 -0
- package/scripts/test.mjs +15 -0
- package/src/bundle_sourcemap.js +3 -0
- package/src/error_stack_remap/detailed_message.js +18 -0
- package/src/error_stack_remap/install_error_stack_remapping.js +141 -0
- package/src/error_stack_remap/remap_call_site.js +312 -0
- package/src/error_stack_remap/remap_stack.js +220 -0
- package/src/error_stack_remap/stringify_stack.js +9 -0
- package/src/magic_source.js +52 -0
- package/src/main.js +10 -0
- package/src/original_position.js +19 -0
- package/src/require_sourcemap.js +9 -0
- package/src/sourcemap_comment.js +143 -0
- package/src/sourcemap_composition_v3.js +87 -0
- package/src/sourcemap_converter.js +24 -0
- package/src/sourcemap_url_generator.js +15 -0
- package/test/magic_source.test.js +13 -0
package/package.json
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@jsenv/sourcemap",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"license": "MIT",
|
|
5
|
+
"repository": {
|
|
6
|
+
"type": "git",
|
|
7
|
+
"url": "https://github.com/jsenv/jsenv-core",
|
|
8
|
+
"directory": "packages/utils"
|
|
9
|
+
},
|
|
10
|
+
"publishConfig": {
|
|
11
|
+
"access": "public",
|
|
12
|
+
"registry": "https://registry.npmjs.org"
|
|
13
|
+
},
|
|
14
|
+
"type": "module",
|
|
15
|
+
"exports": {
|
|
16
|
+
".": "./src/main.js"
|
|
17
|
+
},
|
|
18
|
+
"scripts": {
|
|
19
|
+
"test": "node --conditions=development ./scripts/test.mjs"
|
|
20
|
+
},
|
|
21
|
+
"dependencies": {
|
|
22
|
+
"@jsenv/urls": "1.2.5",
|
|
23
|
+
"magic-string": "0.26.2",
|
|
24
|
+
"source-map": "0.7.4"
|
|
25
|
+
}
|
|
26
|
+
}
|
package/scripts/test.mjs
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { executeTestPlan, nodeProcess } from "@jsenv/core"
|
|
2
|
+
|
|
3
|
+
await executeTestPlan({
|
|
4
|
+
rootDirectoryUrl: new URL("../", import.meta.url),
|
|
5
|
+
testPlan: {
|
|
6
|
+
"**/*.test.mjs": {
|
|
7
|
+
node: {
|
|
8
|
+
runtime: nodeProcess,
|
|
9
|
+
},
|
|
10
|
+
},
|
|
11
|
+
},
|
|
12
|
+
failFast: process.argv.includes("--workspace"),
|
|
13
|
+
completedExecutionLogMerging: process.argv.includes("--workspace"),
|
|
14
|
+
coverage: process.argv.includes("--coverage"),
|
|
15
|
+
})
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
// duplicated from @jsenv/log to avoid the dependency
|
|
2
|
+
export const createDetailedMessage = (message, details = {}) => {
|
|
3
|
+
let string = `${message}`
|
|
4
|
+
|
|
5
|
+
Object.keys(details).forEach((key) => {
|
|
6
|
+
const value = details[key]
|
|
7
|
+
string += `
|
|
8
|
+
--- ${key} ---
|
|
9
|
+
${
|
|
10
|
+
Array.isArray(value)
|
|
11
|
+
? value.join(`
|
|
12
|
+
`)
|
|
13
|
+
: value
|
|
14
|
+
}`
|
|
15
|
+
})
|
|
16
|
+
|
|
17
|
+
return string
|
|
18
|
+
}
|
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
import { createDetailedMessage } from "./detailed_message.js"
|
|
2
|
+
import { remapStack } from "./remap_stack.js"
|
|
3
|
+
import { stringifyStack } from "./stringify_stack.js"
|
|
4
|
+
|
|
5
|
+
export const installErrorStackRemapping = ({
|
|
6
|
+
fetchFile,
|
|
7
|
+
resolveFile,
|
|
8
|
+
SourceMapConsumer,
|
|
9
|
+
indent = " ",
|
|
10
|
+
}) => {
|
|
11
|
+
if (typeof fetchFile !== "function") {
|
|
12
|
+
throw new TypeError(`fetchFile must be a function, got ${fetchFile}`)
|
|
13
|
+
}
|
|
14
|
+
if (typeof SourceMapConsumer !== "function") {
|
|
15
|
+
throw new TypeError(
|
|
16
|
+
`sourceMapConsumer must be a function, got ${SourceMapConsumer}`,
|
|
17
|
+
)
|
|
18
|
+
}
|
|
19
|
+
if (typeof indent !== "string") {
|
|
20
|
+
throw new TypeError(`indent must be a string, got ${indent}`)
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
const errorRemappingCache = new WeakMap()
|
|
24
|
+
const errorRemapFailureCallbackMap = new WeakMap()
|
|
25
|
+
|
|
26
|
+
let installed = false
|
|
27
|
+
const previousPrepareStackTrace = Error.prepareStackTrace
|
|
28
|
+
const install = () => {
|
|
29
|
+
if (installed) return
|
|
30
|
+
installed = true
|
|
31
|
+
Error.prepareStackTrace = prepareStackTrace
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
const uninstall = () => {
|
|
35
|
+
if (!installed) return
|
|
36
|
+
installed = false
|
|
37
|
+
Error.prepareStackTrace = previousPrepareStackTrace
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
// ensure we do not use prepareStackTrace for thoose error
|
|
41
|
+
// otherwise we would recursively remap error stack
|
|
42
|
+
// and if the reason causing the failure is still here
|
|
43
|
+
// it would create an infinite loop
|
|
44
|
+
const readErrorStack = (error) => {
|
|
45
|
+
uninstall()
|
|
46
|
+
const stack = error.stack
|
|
47
|
+
install()
|
|
48
|
+
return stack
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
const prepareStackTrace = (error, stack) => {
|
|
52
|
+
const onFailure = (failureData) => {
|
|
53
|
+
const failureCallbackArray = errorRemapFailureCallbackMap.get(error)
|
|
54
|
+
if (failureCallbackArray) {
|
|
55
|
+
failureCallbackArray.forEach((callback) => callback(failureData))
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
const stackRemappingPromise = remapStack({
|
|
60
|
+
stack,
|
|
61
|
+
error,
|
|
62
|
+
resolveFile,
|
|
63
|
+
fetchFile: memoizeFetch(fetchFile),
|
|
64
|
+
SourceMapConsumer,
|
|
65
|
+
readErrorStack,
|
|
66
|
+
indent,
|
|
67
|
+
onFailure,
|
|
68
|
+
})
|
|
69
|
+
errorRemappingCache.set(error, stackRemappingPromise)
|
|
70
|
+
|
|
71
|
+
return stringifyStack(stack, { error, indent })
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
const getErrorOriginalStackString = async (
|
|
75
|
+
error,
|
|
76
|
+
{
|
|
77
|
+
onFailure = (message) => {
|
|
78
|
+
console.warn(message)
|
|
79
|
+
},
|
|
80
|
+
} = {},
|
|
81
|
+
) => {
|
|
82
|
+
if (onFailure) {
|
|
83
|
+
const remapFailureCallbackArray = errorRemapFailureCallbackMap.get(error)
|
|
84
|
+
if (remapFailureCallbackArray) {
|
|
85
|
+
errorRemapFailureCallbackMap.set(error, [
|
|
86
|
+
...remapFailureCallbackArray,
|
|
87
|
+
onFailure,
|
|
88
|
+
])
|
|
89
|
+
} else {
|
|
90
|
+
errorRemapFailureCallbackMap.set(error, [onFailure])
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
// ensure Error.prepareStackTrace gets triggered by reading error.stack now
|
|
95
|
+
const { stack } = error
|
|
96
|
+
const promise = errorRemappingCache.get(error)
|
|
97
|
+
|
|
98
|
+
if (promise) {
|
|
99
|
+
try {
|
|
100
|
+
const originalCallsites = await promise
|
|
101
|
+
errorRemapFailureCallbackMap.get(error)
|
|
102
|
+
|
|
103
|
+
const firstCall = originalCallsites[0]
|
|
104
|
+
if (firstCall) {
|
|
105
|
+
Object.assign(error, {
|
|
106
|
+
filename: firstCall.getFileName(),
|
|
107
|
+
lineno: firstCall.getLineNumber(),
|
|
108
|
+
columnno: firstCall.getColumnNumber(),
|
|
109
|
+
})
|
|
110
|
+
}
|
|
111
|
+
return stringifyStack(originalCallsites, { error, indent })
|
|
112
|
+
} catch (e) {
|
|
113
|
+
onFailure(
|
|
114
|
+
createDetailedMessage(`error while computing original stack.`, {
|
|
115
|
+
["stack from error while computing"]: readErrorStack(e),
|
|
116
|
+
["stack from error to remap"]: stack,
|
|
117
|
+
}),
|
|
118
|
+
)
|
|
119
|
+
return stack
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
return stack
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
install()
|
|
127
|
+
|
|
128
|
+
return { getErrorOriginalStackString, uninstall }
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
const memoizeFetch = (fetchUrl) => {
|
|
132
|
+
const urlCache = {}
|
|
133
|
+
return async (url) => {
|
|
134
|
+
if (url in urlCache) {
|
|
135
|
+
return urlCache[url]
|
|
136
|
+
}
|
|
137
|
+
const responsePromise = fetchUrl(url)
|
|
138
|
+
urlCache[url] = responsePromise
|
|
139
|
+
return responsePromise
|
|
140
|
+
}
|
|
141
|
+
}
|
|
@@ -0,0 +1,312 @@
|
|
|
1
|
+
import { startsWithWindowsDriveLetter, windowsFilePathToUrl } from "@jsenv/urls"
|
|
2
|
+
|
|
3
|
+
import { createDetailedMessage } from "./detailed_message.js"
|
|
4
|
+
|
|
5
|
+
export const remapCallSite = async (
|
|
6
|
+
callSite,
|
|
7
|
+
{ urlToSourcemapConsumer, resolveFile, readErrorStack, onFailure },
|
|
8
|
+
) => {
|
|
9
|
+
if (callSite.isNative()) {
|
|
10
|
+
return callSite
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
// Most call sites will return the source file from getFileName(), but code
|
|
14
|
+
// passed to eval() ending in "//# sourceURL=..." will return the source file
|
|
15
|
+
// from getScriptNameOrSourceURL() instead
|
|
16
|
+
const source = callSite.getFileName() || callSite.getScriptNameOrSourceURL()
|
|
17
|
+
if (source) {
|
|
18
|
+
const line = callSite.getLineNumber()
|
|
19
|
+
const column = callSite.getColumnNumber() - 1
|
|
20
|
+
const originalPosition = await remapSourcePosition({
|
|
21
|
+
source,
|
|
22
|
+
line,
|
|
23
|
+
column,
|
|
24
|
+
resolveFile,
|
|
25
|
+
urlToSourcemapConsumer,
|
|
26
|
+
readErrorStack,
|
|
27
|
+
onFailure,
|
|
28
|
+
})
|
|
29
|
+
|
|
30
|
+
const callSiteClone = cloneCallSite(callSite)
|
|
31
|
+
|
|
32
|
+
callSiteClone.getFunctionName = () =>
|
|
33
|
+
originalPosition.name || callSite.getFunctionName()
|
|
34
|
+
callSiteClone.getFileName = () => originalPosition.source
|
|
35
|
+
callSiteClone.getLineNumber = () => originalPosition.line
|
|
36
|
+
callSiteClone.getColumnNumber = () => originalPosition.column + 1
|
|
37
|
+
callSiteClone.getScriptNameOrSourceURL = () => originalPosition.source
|
|
38
|
+
|
|
39
|
+
return callSiteClone
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
// Code called using eval() needs special handling
|
|
43
|
+
if (callSite.isEval()) {
|
|
44
|
+
const origin = callSite.getEvalOrigin()
|
|
45
|
+
if (origin) {
|
|
46
|
+
const callSiteClone = cloneCallSite(callSite)
|
|
47
|
+
const originalEvalOrigin = await remapEvalOrigin(origin, {
|
|
48
|
+
resolveFile,
|
|
49
|
+
urlToSourcemapConsumer,
|
|
50
|
+
readErrorStack,
|
|
51
|
+
onFailure,
|
|
52
|
+
})
|
|
53
|
+
callSiteClone.getEvalOrigin = () => originalEvalOrigin
|
|
54
|
+
return callSiteClone
|
|
55
|
+
}
|
|
56
|
+
return callSite
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
// If we get here then we were unable to change the source position
|
|
60
|
+
return callSite
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
const cloneCallSite = (callSite) => {
|
|
64
|
+
const callSiteClone = {}
|
|
65
|
+
methods.forEach((name) => {
|
|
66
|
+
callSiteClone[name] = () => callSite[name]()
|
|
67
|
+
})
|
|
68
|
+
callSiteClone.toString = () => callSiteToFunctionCall(callSiteClone)
|
|
69
|
+
|
|
70
|
+
return callSiteClone
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
const methods = [
|
|
74
|
+
"getColumnNumber",
|
|
75
|
+
"getEvalOrigin",
|
|
76
|
+
"getFileName",
|
|
77
|
+
"getFunction",
|
|
78
|
+
"getFunctionName",
|
|
79
|
+
"getLineNumber",
|
|
80
|
+
"getMethodName",
|
|
81
|
+
"getPosition",
|
|
82
|
+
"getScriptNameOrSourceURL",
|
|
83
|
+
"getThis",
|
|
84
|
+
"getTypeName",
|
|
85
|
+
"isConstructor",
|
|
86
|
+
"isEval",
|
|
87
|
+
"isNative",
|
|
88
|
+
"isToplevel",
|
|
89
|
+
"toString",
|
|
90
|
+
]
|
|
91
|
+
|
|
92
|
+
const callSiteToFunctionCall = (callSite) => {
|
|
93
|
+
const fileLocation = callSiteToFileLocation(callSite)
|
|
94
|
+
const isConstructor = callSite.isConstructor()
|
|
95
|
+
const isMethodCall = !callSite.isToplevel() && !isConstructor
|
|
96
|
+
|
|
97
|
+
if (isMethodCall) {
|
|
98
|
+
return `${callSiteToMethodCall(callSite)} (${fileLocation})`
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
const functionName = callSite.getFunctionName()
|
|
102
|
+
if (isConstructor) {
|
|
103
|
+
return `new ${functionName || "<anonymous>"} (${fileLocation})`
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
if (functionName) {
|
|
107
|
+
return `${functionName} (${fileLocation})`
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
return `${fileLocation}`
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
const callSiteToMethodCall = (callSite) => {
|
|
114
|
+
const functionName = callSite.getFunctionName()
|
|
115
|
+
const typeName = callSiteToType(callSite)
|
|
116
|
+
|
|
117
|
+
if (!functionName) {
|
|
118
|
+
return `${typeName}.<anonymous>`
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
const methodName = callSite.getMethodName()
|
|
122
|
+
const as = generateAs({ methodName, functionName })
|
|
123
|
+
|
|
124
|
+
if (typeName && !functionName.startsWith(typeName)) {
|
|
125
|
+
return `${typeName}.${functionName}${as}`
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
return `${functionName}${as}`
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
const generateAs = ({ methodName, functionName }) => {
|
|
132
|
+
if (!methodName) return ""
|
|
133
|
+
if (
|
|
134
|
+
functionName.indexOf(`.${methodName}`) ===
|
|
135
|
+
functionName.length - methodName.length - 1
|
|
136
|
+
)
|
|
137
|
+
return ""
|
|
138
|
+
return ` [as ${methodName}]`
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
const callSiteToType = (callSite) => {
|
|
142
|
+
const typeName = callSite.getTypeName()
|
|
143
|
+
// Fixes shim to be backward compatible with Node v0 to v4
|
|
144
|
+
if (typeName === "[object Object]") {
|
|
145
|
+
return "null"
|
|
146
|
+
}
|
|
147
|
+
return typeName
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
const callSiteToFileLocation = (callSite) => {
|
|
151
|
+
if (callSite.isNative()) return "native"
|
|
152
|
+
|
|
153
|
+
const sourceFile = callSiteToSourceFile(callSite)
|
|
154
|
+
const lineNumber = callSite.getLineNumber()
|
|
155
|
+
if (lineNumber === null) {
|
|
156
|
+
return sourceFile
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
const columnNumber = callSite.getColumnNumber()
|
|
160
|
+
if (!columnNumber) {
|
|
161
|
+
return `${sourceFile}:${lineNumber}`
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
return `${sourceFile}:${lineNumber}:${columnNumber}`
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
const callSiteToSourceFile = (callSite) => {
|
|
168
|
+
const fileName = callSite.getScriptNameOrSourceURL()
|
|
169
|
+
|
|
170
|
+
if (fileName) {
|
|
171
|
+
return fileName
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
// Source code does not originate from a file and is not native, but we
|
|
175
|
+
// can still get the source position inside the source string, e.g. in
|
|
176
|
+
// an eval string.
|
|
177
|
+
if (callSite.isEval()) {
|
|
178
|
+
return `${callSite.getEvalOrigin()}, <anonymous>`
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
return "<anonymous>"
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
// Parses code generated by FormatEvalOrigin(), a function inside V8:
|
|
185
|
+
// https://code.google.com/p/v8/source/browse/trunk/src/messages.js
|
|
186
|
+
const remapEvalOrigin = async (
|
|
187
|
+
origin,
|
|
188
|
+
{ resolveFile, urlToSourcemapConsumer, onFailure },
|
|
189
|
+
) => {
|
|
190
|
+
// Most eval() calls are in this format
|
|
191
|
+
const topLevelEvalMatch = /^eval at ([^(]+) \((.+):(\d+):(\d+)\)$/.exec(
|
|
192
|
+
origin,
|
|
193
|
+
)
|
|
194
|
+
if (topLevelEvalMatch) {
|
|
195
|
+
const source = topLevelEvalMatch[2]
|
|
196
|
+
const line = Number(topLevelEvalMatch[3])
|
|
197
|
+
const column = topLevelEvalMatch[4] - 1
|
|
198
|
+
const originalPosition = await remapSourcePosition({
|
|
199
|
+
source,
|
|
200
|
+
line,
|
|
201
|
+
column,
|
|
202
|
+
resolveFile,
|
|
203
|
+
urlToSourcemapConsumer,
|
|
204
|
+
onFailure,
|
|
205
|
+
})
|
|
206
|
+
return `eval at ${topLevelEvalMatch[1]} (${originalPosition.source}:${
|
|
207
|
+
originalPosition.line
|
|
208
|
+
}:${originalPosition.column + 1})`
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
// Parse nested eval() calls using recursion
|
|
212
|
+
const nestedEvalMatch = /^eval at ([^(]+) \((.+)\)$/.exec(origin)
|
|
213
|
+
if (nestedEvalMatch) {
|
|
214
|
+
const originalEvalOrigin = await remapEvalOrigin(nestedEvalMatch[2], {
|
|
215
|
+
resolveFile,
|
|
216
|
+
urlToSourcemapConsumer,
|
|
217
|
+
onFailure,
|
|
218
|
+
})
|
|
219
|
+
return `eval at ${nestedEvalMatch[1]} (${originalEvalOrigin})`
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
// Make sure we still return useful information if we didn't find anything
|
|
223
|
+
return origin
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
const remapSourcePosition = async ({
|
|
227
|
+
source,
|
|
228
|
+
line,
|
|
229
|
+
column,
|
|
230
|
+
resolveFile,
|
|
231
|
+
urlToSourcemapConsumer,
|
|
232
|
+
readErrorStack,
|
|
233
|
+
onFailure,
|
|
234
|
+
}) => {
|
|
235
|
+
const position = { source, line, column }
|
|
236
|
+
|
|
237
|
+
const url = sourceToUrl(source, { resolveFile })
|
|
238
|
+
if (!url) return position
|
|
239
|
+
|
|
240
|
+
const sourceMapConsumer = await urlToSourcemapConsumer(url)
|
|
241
|
+
|
|
242
|
+
if (!sourceMapConsumer) return position
|
|
243
|
+
|
|
244
|
+
try {
|
|
245
|
+
const originalPosition = sourceMapConsumer.originalPositionFor(position)
|
|
246
|
+
|
|
247
|
+
// Only return the original position if a matching line was found. If no
|
|
248
|
+
// matching line is found then we return position instead, which will cause
|
|
249
|
+
// the stack trace to print the path and line for the compiled file. It is
|
|
250
|
+
// better to give a precise location in the compiled file than a vague
|
|
251
|
+
// location in the original file.
|
|
252
|
+
const originalSource = originalPosition.source
|
|
253
|
+
|
|
254
|
+
if (originalSource === null) return position
|
|
255
|
+
originalPosition.source = resolveFile(originalSource, url, {
|
|
256
|
+
type: "file-original",
|
|
257
|
+
})
|
|
258
|
+
|
|
259
|
+
return originalPosition
|
|
260
|
+
} catch (e) {
|
|
261
|
+
onFailure(
|
|
262
|
+
createDetailedMessage(`error while remapping position.`, {
|
|
263
|
+
["error stack"]: readErrorStack(e),
|
|
264
|
+
["source"]: source,
|
|
265
|
+
["line"]: line,
|
|
266
|
+
["column"]: column,
|
|
267
|
+
}),
|
|
268
|
+
)
|
|
269
|
+
return position
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
const sourceToUrl = (source, { resolveFile }) => {
|
|
274
|
+
if (startsWithScheme(source)) {
|
|
275
|
+
return source
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
// linux filesystem path
|
|
279
|
+
if (source[0] === "/") {
|
|
280
|
+
return resolveFile(source)
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
// be careful, due to babel or something like that we might receive paths like
|
|
284
|
+
// C:/directory/file.js (without backslashes we would expect on windows)
|
|
285
|
+
// In that case we consider C: is the signe we are on windows
|
|
286
|
+
// And I avoid to rely on process.platform === "win32" because this file might be executed in chrome
|
|
287
|
+
if (startsWithWindowsDriveLetter(source)) {
|
|
288
|
+
return windowsFilePathToUrl(source)
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
// I don't think we will ever encounter relative file in the stack trace
|
|
292
|
+
// but if it ever happens we are safe :)
|
|
293
|
+
if (source.slice(0, 2) === "./" || source.slice(0, 3) === "../") {
|
|
294
|
+
return resolveFile(source)
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
// we have received a "bare specifier" for the source
|
|
298
|
+
// it happens for internal/process/task_queues.js for instance
|
|
299
|
+
// if we do return resolveFile(source) it will be converted to
|
|
300
|
+
// file:///C:/project-directory/internal/process/task_queues.js in node
|
|
301
|
+
// and
|
|
302
|
+
// http://domain.com/internal/process/task_queues.js
|
|
303
|
+
// but the file will certainly be a 404
|
|
304
|
+
// and if not it won't be the right file anyway
|
|
305
|
+
// for now we assume "bare specifier" in the stack trace
|
|
306
|
+
// are internal files that are pointless to try to remap
|
|
307
|
+
return null
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
const startsWithScheme = (string) => {
|
|
311
|
+
return /^[a-zA-Z]{2,}:/.test(string)
|
|
312
|
+
}
|
|
@@ -0,0 +1,220 @@
|
|
|
1
|
+
import { DATA_URL } from "@jsenv/urls"
|
|
2
|
+
|
|
3
|
+
import { SOURCEMAP } from "../sourcemap_comment.js"
|
|
4
|
+
import { createDetailedMessage } from "./detailed_message.js"
|
|
5
|
+
import { remapCallSite } from "./remap_call_site.js"
|
|
6
|
+
|
|
7
|
+
export const remapStack = async ({
|
|
8
|
+
stack,
|
|
9
|
+
resolveFile,
|
|
10
|
+
fetchFile,
|
|
11
|
+
SourceMapConsumer,
|
|
12
|
+
readErrorStack,
|
|
13
|
+
onFailure,
|
|
14
|
+
}) => {
|
|
15
|
+
const urlToSourcemapConsumer = memoizeByFirstArgStringValue(
|
|
16
|
+
async (stackTraceFileUrl) => {
|
|
17
|
+
if (stackTraceFileUrl.startsWith("node:")) {
|
|
18
|
+
return null
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
try {
|
|
22
|
+
let text
|
|
23
|
+
try {
|
|
24
|
+
const fileResponse = await fetchFile(stackTraceFileUrl)
|
|
25
|
+
const { status } = fileResponse
|
|
26
|
+
if (status !== 200) {
|
|
27
|
+
if (status === 404) {
|
|
28
|
+
onFailure(`stack trace file not found at ${stackTraceFileUrl}`)
|
|
29
|
+
} else {
|
|
30
|
+
onFailure(
|
|
31
|
+
createDetailedMessage(
|
|
32
|
+
`unexpected response fetching stack trace file.`,
|
|
33
|
+
{
|
|
34
|
+
["response status"]: status,
|
|
35
|
+
["response text"]: fileResponse.body,
|
|
36
|
+
["stack trace file"]: stackTraceFileUrl,
|
|
37
|
+
},
|
|
38
|
+
),
|
|
39
|
+
)
|
|
40
|
+
}
|
|
41
|
+
return null
|
|
42
|
+
}
|
|
43
|
+
text = await fileResponse.text()
|
|
44
|
+
} catch (e) {
|
|
45
|
+
onFailure(
|
|
46
|
+
createDetailedMessage(`error while fetching stack trace file.`, {
|
|
47
|
+
["fetch error stack"]: readErrorStack(e),
|
|
48
|
+
["stack trace file"]: stackTraceFileUrl,
|
|
49
|
+
}),
|
|
50
|
+
)
|
|
51
|
+
|
|
52
|
+
return null
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
const jsSourcemapComment = SOURCEMAP.readComment({
|
|
56
|
+
contentType: "text/javascript",
|
|
57
|
+
content: text,
|
|
58
|
+
})
|
|
59
|
+
if (!jsSourcemapComment) {
|
|
60
|
+
return null
|
|
61
|
+
}
|
|
62
|
+
const jsSourcemapUrl = jsSourcemapComment.specifier
|
|
63
|
+
let sourcemapUrl
|
|
64
|
+
let sourcemapString
|
|
65
|
+
if (jsSourcemapUrl.startsWith("data:")) {
|
|
66
|
+
sourcemapUrl = stackTraceFileUrl
|
|
67
|
+
sourcemapString = DATA_URL.parse(jsSourcemapUrl, { as: "string" })
|
|
68
|
+
} else {
|
|
69
|
+
sourcemapUrl = resolveFile(jsSourcemapUrl, stackTraceFileUrl, {
|
|
70
|
+
type: "source-map",
|
|
71
|
+
})
|
|
72
|
+
|
|
73
|
+
try {
|
|
74
|
+
const sourcemapResponse = await fetchFile(sourcemapUrl)
|
|
75
|
+
const { status } = sourcemapResponse
|
|
76
|
+
if (status !== 200) {
|
|
77
|
+
if (status === 404) {
|
|
78
|
+
onFailure(`sourcemap file not found at ${sourcemapUrl}`)
|
|
79
|
+
} else {
|
|
80
|
+
onFailure(
|
|
81
|
+
createDetailedMessage(
|
|
82
|
+
`unexpected response for sourcemap file.`,
|
|
83
|
+
{
|
|
84
|
+
["response status"]: status,
|
|
85
|
+
["response text"]: await sourcemapResponse.text(),
|
|
86
|
+
["sourcemap url"]: sourcemapUrl,
|
|
87
|
+
},
|
|
88
|
+
),
|
|
89
|
+
)
|
|
90
|
+
}
|
|
91
|
+
return null
|
|
92
|
+
}
|
|
93
|
+
sourcemapString = await sourcemapResponse.text()
|
|
94
|
+
} catch (e) {
|
|
95
|
+
onFailure(
|
|
96
|
+
createDetailedMessage(`error while fetching sourcemap.`, {
|
|
97
|
+
["fetch error stack"]: readErrorStack(e),
|
|
98
|
+
["sourcemap url"]: sourcemapUrl,
|
|
99
|
+
}),
|
|
100
|
+
)
|
|
101
|
+
return null
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
let sourceMap
|
|
106
|
+
try {
|
|
107
|
+
sourceMap = JSON.parse(sourcemapString)
|
|
108
|
+
} catch (e) {
|
|
109
|
+
onFailure(
|
|
110
|
+
createDetailedMessage(`error while parsing sourcemap.`, {
|
|
111
|
+
["parse error stack"]: readErrorStack(e),
|
|
112
|
+
["sourcemap url"]: sourcemapUrl,
|
|
113
|
+
}),
|
|
114
|
+
)
|
|
115
|
+
return null
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
let { sourcesContent } = sourceMap
|
|
119
|
+
|
|
120
|
+
if (!sourcesContent) {
|
|
121
|
+
sourcesContent = []
|
|
122
|
+
sourceMap.sourcesContent = sourcesContent
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
let firstSourceMapSourceFailure = null
|
|
126
|
+
|
|
127
|
+
await Promise.all(
|
|
128
|
+
sourceMap.sources.map(async (source, index) => {
|
|
129
|
+
if (index in sourcesContent) return
|
|
130
|
+
|
|
131
|
+
const sourcemapSourceUrl = resolveFile(source, sourcemapUrl, {
|
|
132
|
+
type: "source",
|
|
133
|
+
})
|
|
134
|
+
try {
|
|
135
|
+
const sourceResponse = await fetchFile(sourcemapSourceUrl)
|
|
136
|
+
const { status } = sourceResponse
|
|
137
|
+
if (status !== 200) {
|
|
138
|
+
if (firstSourceMapSourceFailure) return
|
|
139
|
+
|
|
140
|
+
if (status === 404) {
|
|
141
|
+
firstSourceMapSourceFailure = createDetailedMessage(
|
|
142
|
+
`sourcemap source not found.`,
|
|
143
|
+
{
|
|
144
|
+
["sourcemap source url"]: sourcemapSourceUrl,
|
|
145
|
+
["sourcemap url"]: sourcemapUrl,
|
|
146
|
+
},
|
|
147
|
+
)
|
|
148
|
+
return
|
|
149
|
+
}
|
|
150
|
+
firstSourceMapSourceFailure = createDetailedMessage(
|
|
151
|
+
`unexpected response for sourcemap source.`,
|
|
152
|
+
{
|
|
153
|
+
["response status"]: status,
|
|
154
|
+
["response text"]: await sourceResponse.text(),
|
|
155
|
+
["sourcemap source url"]: sourcemapSourceUrl,
|
|
156
|
+
["sourcemap url"]: sourcemapUrl,
|
|
157
|
+
},
|
|
158
|
+
)
|
|
159
|
+
return
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
const sourceString = await sourceResponse.text()
|
|
163
|
+
sourcesContent[index] = sourceString
|
|
164
|
+
} catch (e) {
|
|
165
|
+
if (firstSourceMapSourceFailure) return
|
|
166
|
+
firstSourceMapSourceFailure = createDetailedMessage(
|
|
167
|
+
`error while fetching sourcemap source.`,
|
|
168
|
+
{
|
|
169
|
+
["fetch error stack"]: readErrorStack(e),
|
|
170
|
+
["sourcemap source url"]: sourcemapSourceUrl,
|
|
171
|
+
["sourcemap url"]: sourcemapUrl,
|
|
172
|
+
},
|
|
173
|
+
)
|
|
174
|
+
}
|
|
175
|
+
}),
|
|
176
|
+
)
|
|
177
|
+
|
|
178
|
+
if (firstSourceMapSourceFailure) {
|
|
179
|
+
onFailure(firstSourceMapSourceFailure)
|
|
180
|
+
return null
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
return new SourceMapConsumer(sourceMap)
|
|
184
|
+
} catch (e) {
|
|
185
|
+
onFailure(
|
|
186
|
+
createDetailedMessage(
|
|
187
|
+
`error while preparing a sourceMap consumer for a stack trace file.`,
|
|
188
|
+
{
|
|
189
|
+
["error stack"]: readErrorStack(e),
|
|
190
|
+
["stack trace file"]: stackTraceFileUrl,
|
|
191
|
+
},
|
|
192
|
+
),
|
|
193
|
+
)
|
|
194
|
+
return null
|
|
195
|
+
}
|
|
196
|
+
},
|
|
197
|
+
)
|
|
198
|
+
const originalCallsites = await Promise.all(
|
|
199
|
+
stack.map((callSite) =>
|
|
200
|
+
remapCallSite(callSite, {
|
|
201
|
+
resolveFile,
|
|
202
|
+
urlToSourcemapConsumer,
|
|
203
|
+
readErrorStack,
|
|
204
|
+
onFailure,
|
|
205
|
+
}),
|
|
206
|
+
),
|
|
207
|
+
)
|
|
208
|
+
return originalCallsites
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
const memoizeByFirstArgStringValue = (fn) => {
|
|
212
|
+
const stringValueCache = {}
|
|
213
|
+
return (firstArgValue) => {
|
|
214
|
+
if (firstArgValue in stringValueCache)
|
|
215
|
+
return stringValueCache[firstArgValue]
|
|
216
|
+
const value = fn(firstArgValue)
|
|
217
|
+
stringValueCache[firstArgValue] = value
|
|
218
|
+
return value
|
|
219
|
+
}
|
|
220
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
export const stringifyStack = (stack, { error, indent }) => {
|
|
2
|
+
const name = error.name || "Error"
|
|
3
|
+
const message = error.message || ""
|
|
4
|
+
const stackString = stack
|
|
5
|
+
.map((callSite) => `\n${indent}at ${callSite}`)
|
|
6
|
+
.join("")
|
|
7
|
+
|
|
8
|
+
return `${name}: ${message}${stackString}`
|
|
9
|
+
}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import MagicString from "magic-string"
|
|
2
|
+
|
|
3
|
+
export const createMagicSource = (content) => {
|
|
4
|
+
if (content === undefined) {
|
|
5
|
+
throw new Error("content missing")
|
|
6
|
+
}
|
|
7
|
+
const mutations = []
|
|
8
|
+
return {
|
|
9
|
+
prepend: (string) => {
|
|
10
|
+
mutations.push((magicString) => {
|
|
11
|
+
magicString.prepend(string)
|
|
12
|
+
})
|
|
13
|
+
},
|
|
14
|
+
append: (string) => {
|
|
15
|
+
mutations.push((magicString) => {
|
|
16
|
+
magicString.append(string)
|
|
17
|
+
})
|
|
18
|
+
},
|
|
19
|
+
replace: ({ start, end, replacement }) => {
|
|
20
|
+
mutations.push((magicString) => {
|
|
21
|
+
magicString.overwrite(start, end, replacement)
|
|
22
|
+
})
|
|
23
|
+
},
|
|
24
|
+
remove: ({ start, end }) => {
|
|
25
|
+
mutations.push((magicString) => {
|
|
26
|
+
magicString.remove(start, end)
|
|
27
|
+
})
|
|
28
|
+
},
|
|
29
|
+
toContentAndSourcemap: ({ source } = {}) => {
|
|
30
|
+
if (mutations.length === 0) {
|
|
31
|
+
return {
|
|
32
|
+
content,
|
|
33
|
+
sourcemap: null,
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
const magicString = new MagicString(content)
|
|
37
|
+
mutations.forEach((mutation) => {
|
|
38
|
+
mutation(magicString)
|
|
39
|
+
})
|
|
40
|
+
const code = magicString.toString()
|
|
41
|
+
const map = magicString.generateMap({
|
|
42
|
+
hires: true,
|
|
43
|
+
includeContent: true,
|
|
44
|
+
source,
|
|
45
|
+
})
|
|
46
|
+
return {
|
|
47
|
+
content: code,
|
|
48
|
+
sourcemap: map,
|
|
49
|
+
}
|
|
50
|
+
},
|
|
51
|
+
}
|
|
52
|
+
}
|
package/src/main.js
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
export { createMagicSource } from "./magic_source.js"
|
|
2
|
+
export { composeTwoSourcemaps } from "./sourcemap_composition_v3.js"
|
|
3
|
+
|
|
4
|
+
export { getOriginalPosition } from "./original_position.js"
|
|
5
|
+
export { sourcemapConverter } from "./sourcemap_converter.js"
|
|
6
|
+
export {
|
|
7
|
+
generateSourcemapFileUrl,
|
|
8
|
+
generateSourcemapDataUrl,
|
|
9
|
+
} from "./sourcemap_url_generator.js"
|
|
10
|
+
export { SOURCEMAP } from "./sourcemap_comment.js"
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { requireSourcemap } from "./require_sourcemap.js"
|
|
2
|
+
|
|
3
|
+
// https://github.com/mozilla/source-map#sourcemapconsumerprototypeoriginalpositionforgeneratedposition
|
|
4
|
+
export const getOriginalPosition = async ({
|
|
5
|
+
sourcemap,
|
|
6
|
+
line,
|
|
7
|
+
column,
|
|
8
|
+
bias,
|
|
9
|
+
}) => {
|
|
10
|
+
const { SourceMapConsumer } = requireSourcemap()
|
|
11
|
+
|
|
12
|
+
const sourceMapConsumer = await new SourceMapConsumer(sourcemap)
|
|
13
|
+
const originalPosition = sourceMapConsumer.originalPositionFor({
|
|
14
|
+
line,
|
|
15
|
+
column,
|
|
16
|
+
bias,
|
|
17
|
+
})
|
|
18
|
+
return originalPosition
|
|
19
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { createRequire } from "node:module"
|
|
2
|
+
|
|
3
|
+
const require = createRequire(import.meta.url)
|
|
4
|
+
// consider using https://github.com/7rulnik/source-map-js
|
|
5
|
+
|
|
6
|
+
export const requireSourcemap = () => {
|
|
7
|
+
const namespace = require("source-map")
|
|
8
|
+
return namespace
|
|
9
|
+
}
|
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
export const SOURCEMAP = {
|
|
2
|
+
enabledOnContentType: (contentType) => {
|
|
3
|
+
return ["text/javascript", "text/css"].includes(contentType)
|
|
4
|
+
},
|
|
5
|
+
|
|
6
|
+
readComment: ({ contentType, content }) => {
|
|
7
|
+
const read = {
|
|
8
|
+
"text/javascript": parseJavaScriptSourcemapComment,
|
|
9
|
+
"text/css": parseCssSourcemapComment,
|
|
10
|
+
}[contentType]
|
|
11
|
+
return read ? read(content) : null
|
|
12
|
+
},
|
|
13
|
+
|
|
14
|
+
writeComment: ({ contentType, content, specifier }) => {
|
|
15
|
+
const write = {
|
|
16
|
+
"text/javascript": setJavaScriptSourceMappingUrl,
|
|
17
|
+
"text/css": setCssSourceMappingUrl,
|
|
18
|
+
}[contentType]
|
|
19
|
+
return write ? write(content, specifier) : content
|
|
20
|
+
},
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
const parseJavaScriptSourcemapComment = (javaScriptSource) => {
|
|
24
|
+
let sourceMappingUrl
|
|
25
|
+
replaceSourceMappingUrl(
|
|
26
|
+
javaScriptSource,
|
|
27
|
+
javascriptSourceMappingUrlCommentRegexp,
|
|
28
|
+
(value) => {
|
|
29
|
+
sourceMappingUrl = value
|
|
30
|
+
},
|
|
31
|
+
)
|
|
32
|
+
if (!sourceMappingUrl) {
|
|
33
|
+
return null
|
|
34
|
+
}
|
|
35
|
+
return {
|
|
36
|
+
type: "sourcemap_comment",
|
|
37
|
+
subtype: "js",
|
|
38
|
+
// we assume it's on last line
|
|
39
|
+
line: javaScriptSource.split(/\r?\n/).length,
|
|
40
|
+
// ${"//#"} is to avoid static analysis to think there is a sourceMappingUrl for this file
|
|
41
|
+
column: `${"//#"} sourceMappingURL=`.length + 1,
|
|
42
|
+
specifier: sourceMappingUrl,
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
const setJavaScriptSourceMappingUrl = (
|
|
47
|
+
javaScriptSource,
|
|
48
|
+
sourceMappingFileUrl,
|
|
49
|
+
) => {
|
|
50
|
+
let replaced
|
|
51
|
+
const sourceAfterReplace = replaceSourceMappingUrl(
|
|
52
|
+
javaScriptSource,
|
|
53
|
+
javascriptSourceMappingUrlCommentRegexp,
|
|
54
|
+
() => {
|
|
55
|
+
replaced = true
|
|
56
|
+
return sourceMappingFileUrl
|
|
57
|
+
? writeJavaScriptSourceMappingURL(sourceMappingFileUrl)
|
|
58
|
+
: ""
|
|
59
|
+
},
|
|
60
|
+
)
|
|
61
|
+
if (replaced) {
|
|
62
|
+
return sourceAfterReplace
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
return sourceMappingFileUrl
|
|
66
|
+
? `${javaScriptSource}
|
|
67
|
+
${writeJavaScriptSourceMappingURL(sourceMappingFileUrl)}
|
|
68
|
+
`
|
|
69
|
+
: javaScriptSource
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
const parseCssSourcemapComment = (cssSource) => {
|
|
73
|
+
let sourceMappingUrl
|
|
74
|
+
replaceSourceMappingUrl(
|
|
75
|
+
cssSource,
|
|
76
|
+
cssSourceMappingUrlCommentRegExp,
|
|
77
|
+
(value) => {
|
|
78
|
+
sourceMappingUrl = value
|
|
79
|
+
},
|
|
80
|
+
)
|
|
81
|
+
if (!sourceMappingUrl) {
|
|
82
|
+
return null
|
|
83
|
+
}
|
|
84
|
+
return {
|
|
85
|
+
type: "sourcemap_comment",
|
|
86
|
+
subtype: "css",
|
|
87
|
+
// we assume it's on last line
|
|
88
|
+
line: cssSource.split(/\r?\n/).length - 1,
|
|
89
|
+
// ${"//*#"} is to avoid static analysis to think there is a sourceMappingUrl for this file
|
|
90
|
+
column: `${"//*#"} sourceMappingURL=`.length + 1,
|
|
91
|
+
specifier: sourceMappingUrl,
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
const setCssSourceMappingUrl = (cssSource, sourceMappingFileUrl) => {
|
|
96
|
+
let replaced
|
|
97
|
+
const sourceAfterReplace = replaceSourceMappingUrl(
|
|
98
|
+
cssSource,
|
|
99
|
+
cssSourceMappingUrlCommentRegExp,
|
|
100
|
+
() => {
|
|
101
|
+
replaced = true
|
|
102
|
+
return sourceMappingFileUrl
|
|
103
|
+
? writeCssSourceMappingUrl(sourceMappingFileUrl)
|
|
104
|
+
: ""
|
|
105
|
+
},
|
|
106
|
+
)
|
|
107
|
+
if (replaced) {
|
|
108
|
+
return sourceAfterReplace
|
|
109
|
+
}
|
|
110
|
+
return sourceMappingFileUrl
|
|
111
|
+
? `${cssSource}
|
|
112
|
+
${writeCssSourceMappingUrl(sourceMappingFileUrl)}
|
|
113
|
+
`
|
|
114
|
+
: cssSource
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
const javascriptSourceMappingUrlCommentRegexp =
|
|
118
|
+
/\/\/ ?# ?sourceMappingURL=([^\s'"]+)/g
|
|
119
|
+
const cssSourceMappingUrlCommentRegExp =
|
|
120
|
+
/\/\*# ?sourceMappingURL=([^\s'"]+) \*\//g
|
|
121
|
+
|
|
122
|
+
// ${"//#"} is to avoid a parser thinking there is a sourceMappingUrl for this file
|
|
123
|
+
const writeJavaScriptSourceMappingURL = (value) =>
|
|
124
|
+
`${"//#"} sourceMappingURL=${value}`
|
|
125
|
+
const writeCssSourceMappingUrl = (value) => `/*# sourceMappingURL=${value} */`
|
|
126
|
+
|
|
127
|
+
const replaceSourceMappingUrl = (source, regexp, callback) => {
|
|
128
|
+
let lastSourceMappingUrl
|
|
129
|
+
let matchSourceMappingUrl
|
|
130
|
+
while ((matchSourceMappingUrl = regexp.exec(source))) {
|
|
131
|
+
lastSourceMappingUrl = matchSourceMappingUrl
|
|
132
|
+
}
|
|
133
|
+
if (lastSourceMappingUrl) {
|
|
134
|
+
const index = lastSourceMappingUrl.index
|
|
135
|
+
const before = source.slice(0, index)
|
|
136
|
+
const after = source.slice(index)
|
|
137
|
+
const mappedAfter = after.replace(regexp, (match, firstGroup) => {
|
|
138
|
+
return callback(firstGroup)
|
|
139
|
+
})
|
|
140
|
+
return `${before}${mappedAfter}`
|
|
141
|
+
}
|
|
142
|
+
return source
|
|
143
|
+
}
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* https://github.com/mozilla/source-map#sourcemapgenerator
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { requireSourcemap } from "./require_sourcemap.js"
|
|
6
|
+
|
|
7
|
+
const { SourceMapConsumer, SourceMapGenerator } = requireSourcemap()
|
|
8
|
+
|
|
9
|
+
export const composeTwoSourcemaps = async (firstSourcemap, secondSourcemap) => {
|
|
10
|
+
if (!firstSourcemap && !secondSourcemap) {
|
|
11
|
+
return null
|
|
12
|
+
}
|
|
13
|
+
if (!firstSourcemap) {
|
|
14
|
+
return secondSourcemap
|
|
15
|
+
}
|
|
16
|
+
if (!secondSourcemap) {
|
|
17
|
+
return firstSourcemap
|
|
18
|
+
}
|
|
19
|
+
const sourcemapGenerator = new SourceMapGenerator()
|
|
20
|
+
const firstSourcemapConsumer = await new SourceMapConsumer(firstSourcemap)
|
|
21
|
+
const secondSourcemapConsumer = await new SourceMapConsumer(secondSourcemap)
|
|
22
|
+
const firstMappings = readMappings(firstSourcemapConsumer)
|
|
23
|
+
firstMappings.forEach((mapping) => {
|
|
24
|
+
sourcemapGenerator.addMapping(mapping)
|
|
25
|
+
})
|
|
26
|
+
const secondMappings = readMappings(secondSourcemapConsumer)
|
|
27
|
+
secondMappings.forEach((mapping) => {
|
|
28
|
+
sourcemapGenerator.addMapping(mapping)
|
|
29
|
+
})
|
|
30
|
+
const sourcemap = sourcemapGenerator.toJSON()
|
|
31
|
+
const sources = []
|
|
32
|
+
const sourcesContent = []
|
|
33
|
+
const firstSourcesContent = firstSourcemap.sourcesContent
|
|
34
|
+
const secondSourcesContent = secondSourcemap.sourcesContent
|
|
35
|
+
sourcemap.sources.forEach((source) => {
|
|
36
|
+
sources.push(source)
|
|
37
|
+
if (secondSourcesContent) {
|
|
38
|
+
const secondSourceIndex = secondSourcemap.sources.indexOf(source)
|
|
39
|
+
if (secondSourceIndex > -1) {
|
|
40
|
+
sourcesContent.push(secondSourcesContent[secondSourceIndex])
|
|
41
|
+
return
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
if (firstSourcesContent) {
|
|
45
|
+
const firstSourceIndex = firstSourcemap.sources.indexOf(source)
|
|
46
|
+
if (firstSourceIndex > -1) {
|
|
47
|
+
sourcesContent.push(firstSourcesContent[firstSourceIndex])
|
|
48
|
+
return
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
sourcesContent.push(null)
|
|
52
|
+
})
|
|
53
|
+
sourcemap.sources = sources
|
|
54
|
+
sourcemap.sourcesContent = sourcesContent
|
|
55
|
+
return sourcemap
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
const readMappings = (consumer) => {
|
|
59
|
+
const mappings = []
|
|
60
|
+
consumer.eachMapping(
|
|
61
|
+
({
|
|
62
|
+
originalColumn,
|
|
63
|
+
originalLine,
|
|
64
|
+
generatedColumn,
|
|
65
|
+
generatedLine,
|
|
66
|
+
source,
|
|
67
|
+
name,
|
|
68
|
+
}) => {
|
|
69
|
+
mappings.push({
|
|
70
|
+
original:
|
|
71
|
+
typeof originalColumn === "number"
|
|
72
|
+
? {
|
|
73
|
+
column: originalColumn,
|
|
74
|
+
line: originalLine,
|
|
75
|
+
}
|
|
76
|
+
: undefined,
|
|
77
|
+
generated: {
|
|
78
|
+
column: generatedColumn,
|
|
79
|
+
line: generatedLine,
|
|
80
|
+
},
|
|
81
|
+
source: typeof originalColumn === "number" ? source : undefined,
|
|
82
|
+
name,
|
|
83
|
+
})
|
|
84
|
+
},
|
|
85
|
+
)
|
|
86
|
+
return mappings
|
|
87
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import {
|
|
2
|
+
isFileSystemPath,
|
|
3
|
+
fileSystemPathToUrl,
|
|
4
|
+
urlToFileSystemPath,
|
|
5
|
+
} from "@jsenv/urls"
|
|
6
|
+
|
|
7
|
+
export const sourcemapConverter = {
|
|
8
|
+
toFileUrls: (sourcemap) => {
|
|
9
|
+
return {
|
|
10
|
+
...sourcemap,
|
|
11
|
+
sources: sourcemap.sources.map((source) => {
|
|
12
|
+
return isFileSystemPath(source) ? fileSystemPathToUrl(source) : source
|
|
13
|
+
}),
|
|
14
|
+
}
|
|
15
|
+
},
|
|
16
|
+
toFilePaths: (sourcemap) => {
|
|
17
|
+
return {
|
|
18
|
+
...sourcemap,
|
|
19
|
+
sources: sourcemap.sources.map((source) => {
|
|
20
|
+
return urlToFileSystemPath(source)
|
|
21
|
+
}),
|
|
22
|
+
}
|
|
23
|
+
},
|
|
24
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
export const generateSourcemapFileUrl = (url) => {
|
|
2
|
+
const urlObject = new URL(url)
|
|
3
|
+
let { origin, pathname, search, hash } = urlObject
|
|
4
|
+
// origin is "null" for "file://" urls with Node.js
|
|
5
|
+
if (origin === "null" && urlObject.href.startsWith("file:")) {
|
|
6
|
+
origin = "file://"
|
|
7
|
+
}
|
|
8
|
+
const sourcemapUrl = `${origin}${pathname}.map${search}${hash}`
|
|
9
|
+
return sourcemapUrl
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export const generateSourcemapDataUrl = (sourcemap) => {
|
|
13
|
+
const asBase64 = Buffer.from(JSON.stringify(sourcemap)).toString("base64")
|
|
14
|
+
return `data:application/json;charset=utf-8;base64,${asBase64}`
|
|
15
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { createMagicSource } from "@jsenv/sourcemap"
|
|
2
|
+
|
|
3
|
+
const content = `import { inferContextFrom, createUrlContext } from "../url_context.js";
|
|
4
|
+
// toto
|
|
5
|
+
`
|
|
6
|
+
const magic = createMagicSource(content)
|
|
7
|
+
magic.replace({
|
|
8
|
+
start: 51,
|
|
9
|
+
end: 70,
|
|
10
|
+
replacement: `"/@fs/Users/d.maillard/dev/jsenv/jsenv-core/src/internal/url_context.js"`,
|
|
11
|
+
})
|
|
12
|
+
const ret = magic.toContentAndSourcemap()
|
|
13
|
+
console.log(ret)
|