@usebruno/js 0.45.1 → 0.46.1
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 +5 -4
- package/src/bru.js +110 -18
- package/src/bruno-request.js +21 -19
- package/src/index.js +4 -1
- package/src/runtime/script-runtime.js +107 -62
- package/src/runtime/test-runtime.js +8 -3
- package/src/sandbox/node-vm/console.js +102 -0
- package/src/sandbox/node-vm/index.js +68 -8
- package/src/sandbox/node-vm/utils.js +15 -0
- package/src/sandbox/quickjs/index.js +6 -21
- package/src/sandbox/quickjs/shims/bru.js +108 -3
- package/src/sandbox/quickjs/shims/bruno-request.js +12 -0
- package/src/sandbox/quickjs/shims/console.js +97 -5
- package/src/sandbox/quickjs/shims/lib/axios.js +26 -16
- package/src/sandbox/quickjs/shims/lib/axios.spec.js +495 -0
- package/src/test.js +6 -4
- package/src/utils/error-formatter.js +426 -0
- package/src/utils/error-formatter.spec.js +388 -0
- package/src/utils/sandbox.js +64 -0
- package/src/utils.js +27 -6
|
@@ -0,0 +1,426 @@
|
|
|
1
|
+
const fs = require('fs');
|
|
2
|
+
const YAML = require('yaml');
|
|
3
|
+
const { NODEVM_SCRIPT_WRAPPER_OFFSET, QUICKJS_SCRIPT_WRAPPER_OFFSET } = require('./sandbox');
|
|
4
|
+
|
|
5
|
+
const DEFAULT_CONTEXT_LINES = 5;
|
|
6
|
+
const ALLOWED_SOURCE_EXTENSIONS = ['.bru', '.yml', '.yaml'];
|
|
7
|
+
|
|
8
|
+
const isAllowedSourceFile = (filePath) =>
|
|
9
|
+
typeof filePath === 'string' && ALLOWED_SOURCE_EXTENSIONS.some((ext) => filePath.endsWith(ext));
|
|
10
|
+
|
|
11
|
+
const SCRIPT_TYPES = Object.freeze({
|
|
12
|
+
PRE_REQUEST: 'pre-request',
|
|
13
|
+
POST_RESPONSE: 'post-response',
|
|
14
|
+
TEST: 'test'
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
// Bruno script types → OpenCollection YAML script types
|
|
18
|
+
const SCRIPT_TYPE_TO_YML = {
|
|
19
|
+
[SCRIPT_TYPES.PRE_REQUEST]: 'before-request',
|
|
20
|
+
[SCRIPT_TYPES.POST_RESPONSE]: 'after-response',
|
|
21
|
+
[SCRIPT_TYPES.TEST]: 'tests'
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
const readFile = (filePath, cache = null) => {
|
|
25
|
+
if (cache?.has(filePath)) return cache.get(filePath);
|
|
26
|
+
try {
|
|
27
|
+
const content = fs.readFileSync(filePath, 'utf-8').replace(/\r\n/g, '\n');
|
|
28
|
+
if (cache) cache.set(filePath, content);
|
|
29
|
+
return content;
|
|
30
|
+
} catch {
|
|
31
|
+
return null;
|
|
32
|
+
}
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
const BLOCK_PATTERNS = {
|
|
36
|
+
[SCRIPT_TYPES.PRE_REQUEST]: /^script:pre-request\s*\{/,
|
|
37
|
+
[SCRIPT_TYPES.POST_RESPONSE]: /^script:post-response\s*\{/,
|
|
38
|
+
[SCRIPT_TYPES.TEST]: /^tests\s*\{/
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
/** Find the 1-indexed line where a script block's content starts in a .bru file */
|
|
42
|
+
const findScriptBlockStartLine = (filePath, scriptType, cache = null) => {
|
|
43
|
+
if (!filePath.endsWith('.bru')) return null;
|
|
44
|
+
|
|
45
|
+
const cacheKey = `bru:${filePath}:${scriptType}`;
|
|
46
|
+
if (cache?.has(cacheKey)) return cache.get(cacheKey);
|
|
47
|
+
|
|
48
|
+
const content = readFile(filePath, cache);
|
|
49
|
+
if (!content) return null;
|
|
50
|
+
|
|
51
|
+
const pattern = BLOCK_PATTERNS[scriptType];
|
|
52
|
+
if (!pattern) return null;
|
|
53
|
+
|
|
54
|
+
const lines = content.split('\n');
|
|
55
|
+
let result = null;
|
|
56
|
+
for (let i = 0; i < lines.length; i++) {
|
|
57
|
+
if (pattern.test(lines[i])) {
|
|
58
|
+
result = i + 2; // +1 for 1-indexing, +1 for line after opening brace
|
|
59
|
+
break;
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
if (cache) cache.set(cacheKey, result);
|
|
64
|
+
return result;
|
|
65
|
+
};
|
|
66
|
+
|
|
67
|
+
/** Find the 1-indexed line where a script block's content starts in a .yml file */
|
|
68
|
+
const findYmlScriptBlockStartLine = (filePath, scriptType, cache = null) => {
|
|
69
|
+
if (!filePath.endsWith('.yml') && !filePath.endsWith('.yaml')) return null;
|
|
70
|
+
|
|
71
|
+
const cacheKey = `yml:${filePath}:${scriptType}`;
|
|
72
|
+
if (cache?.has(cacheKey)) return cache.get(cacheKey);
|
|
73
|
+
|
|
74
|
+
const content = readFile(filePath, cache);
|
|
75
|
+
if (!content) return null;
|
|
76
|
+
|
|
77
|
+
const ymlType = SCRIPT_TYPE_TO_YML[scriptType];
|
|
78
|
+
if (!ymlType) return null;
|
|
79
|
+
|
|
80
|
+
let result = null;
|
|
81
|
+
try {
|
|
82
|
+
const lineCounter = new YAML.LineCounter();
|
|
83
|
+
const doc = YAML.parseDocument(content, { lineCounter });
|
|
84
|
+
|
|
85
|
+
// Request yml files use runtime.scripts, collection/folder yml files use request.scripts
|
|
86
|
+
const scriptPaths = [['runtime', 'scripts'], ['request', 'scripts']];
|
|
87
|
+
for (const scriptPath of scriptPaths) {
|
|
88
|
+
const scripts = doc.getIn(scriptPath, true);
|
|
89
|
+
if (YAML.isSeq(scripts)) {
|
|
90
|
+
for (const item of scripts.items) {
|
|
91
|
+
if (!YAML.isMap(item)) continue;
|
|
92
|
+
if (item.get('type') === ymlType) {
|
|
93
|
+
const codeNode = item.get('code', true);
|
|
94
|
+
if (codeNode && codeNode.range) {
|
|
95
|
+
result = lineCounter.linePos(codeNode.range[0]).line + 1;
|
|
96
|
+
break;
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
if (result) break;
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
} catch {
|
|
104
|
+
// invalid YAML
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
if (cache) cache.set(cacheKey, result);
|
|
108
|
+
return result;
|
|
109
|
+
};
|
|
110
|
+
|
|
111
|
+
/** Adjust a runtime-reported line number to the actual line in the .bru/.yml file */
|
|
112
|
+
const adjustLineNumber = (filePath, reportedLine, isQuickJS, scriptType = null, cache = null, scriptMetadata = null) => {
|
|
113
|
+
const isBruFile = filePath.endsWith('.bru');
|
|
114
|
+
const isYmlFile = filePath.endsWith('.yml') || filePath.endsWith('.yaml');
|
|
115
|
+
|
|
116
|
+
if (!isBruFile && !isYmlFile) {
|
|
117
|
+
return reportedLine;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
const wrapperOffset = isQuickJS ? QUICKJS_SCRIPT_WRAPPER_OFFSET : NODEVM_SCRIPT_WRAPPER_OFFSET;
|
|
121
|
+
const scriptRelativeLine = reportedLine - wrapperOffset;
|
|
122
|
+
|
|
123
|
+
if (scriptRelativeLine < 1) return reportedLine;
|
|
124
|
+
|
|
125
|
+
// Use metadata if available to correctly map line numbers in combined scripts
|
|
126
|
+
if (scriptType && scriptMetadata) {
|
|
127
|
+
const { requestStartLine, requestEndLine } = scriptMetadata;
|
|
128
|
+
if (requestStartLine != null && requestEndLine != null) {
|
|
129
|
+
if (scriptRelativeLine >= requestStartLine && scriptRelativeLine <= requestEndLine) {
|
|
130
|
+
// Error is within the request script segment
|
|
131
|
+
const blockStartLine = isBruFile
|
|
132
|
+
? findScriptBlockStartLine(filePath, scriptType, cache)
|
|
133
|
+
: findYmlScriptBlockStartLine(filePath, scriptType, cache);
|
|
134
|
+
|
|
135
|
+
if (blockStartLine) {
|
|
136
|
+
return blockStartLine + (scriptRelativeLine - requestStartLine) - 1;
|
|
137
|
+
}
|
|
138
|
+
} else {
|
|
139
|
+
// Error is in a collection/folder-level script
|
|
140
|
+
// Cannot map to the request .bru/.yml file, return null to skip source context.
|
|
141
|
+
return null;
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
// No segment metadata, map script-relative line to file line via block start.
|
|
147
|
+
if (scriptType) {
|
|
148
|
+
const blockStartLine = isBruFile
|
|
149
|
+
? findScriptBlockStartLine(filePath, scriptType, cache)
|
|
150
|
+
: findYmlScriptBlockStartLine(filePath, scriptType, cache);
|
|
151
|
+
|
|
152
|
+
if (blockStartLine) {
|
|
153
|
+
return blockStartLine + scriptRelativeLine - 1;
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
return scriptRelativeLine;
|
|
158
|
+
};
|
|
159
|
+
|
|
160
|
+
/**
|
|
161
|
+
* Resolve an error in a collection/folder script segment to its source file and line.
|
|
162
|
+
* Uses the segments array in metadata to find which segment the error falls in,
|
|
163
|
+
* then maps to the actual line in that segment's source file.
|
|
164
|
+
*/
|
|
165
|
+
const resolveSegmentError = (parsed, metadata, scriptType, cache) => {
|
|
166
|
+
if (!metadata?.segments?.length || !parsed) return null;
|
|
167
|
+
|
|
168
|
+
const wrapperOffset = parsed.isQuickJS ? QUICKJS_SCRIPT_WRAPPER_OFFSET : NODEVM_SCRIPT_WRAPPER_OFFSET;
|
|
169
|
+
const scriptRelativeLine = parsed.line - wrapperOffset;
|
|
170
|
+
if (scriptRelativeLine < 1) return null;
|
|
171
|
+
|
|
172
|
+
for (const segment of metadata.segments) {
|
|
173
|
+
if (scriptRelativeLine >= segment.startLine && scriptRelativeLine <= segment.endLine) {
|
|
174
|
+
const isBru = segment.filePath.endsWith('.bru');
|
|
175
|
+
const isYml = segment.filePath.endsWith('.yml') || segment.filePath.endsWith('.yaml');
|
|
176
|
+
if (!isBru && !isYml) return null;
|
|
177
|
+
|
|
178
|
+
const blockStartLine = isBru
|
|
179
|
+
? findScriptBlockStartLine(segment.filePath, scriptType, cache)
|
|
180
|
+
: findYmlScriptBlockStartLine(segment.filePath, scriptType, cache);
|
|
181
|
+
if (!blockStartLine) return null;
|
|
182
|
+
|
|
183
|
+
return {
|
|
184
|
+
line: blockStartLine + (scriptRelativeLine - segment.startLine) - 1,
|
|
185
|
+
filePath: segment.filePath,
|
|
186
|
+
displayPath: segment.displayPath
|
|
187
|
+
};
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
return null;
|
|
191
|
+
};
|
|
192
|
+
|
|
193
|
+
/** Extract file path, line, column, and runtime type from a single stack trace line */
|
|
194
|
+
const matchStackFrame = (line) => {
|
|
195
|
+
// QuickJS: "at (/path/to/file.bru:11)" or "at <anonymous> (/path/to/file.bru:11)"
|
|
196
|
+
const quickjsMatch = line.match(/at (?:<[^>]+>\s*)?\(((?:[A-Za-z]:)?[^:]+):(\d+)(?::(\d+))?\)/);
|
|
197
|
+
if (quickjsMatch && (quickjsMatch[1].includes('/') || quickjsMatch[1].includes('\\'))) {
|
|
198
|
+
return {
|
|
199
|
+
filePath: quickjsMatch[1],
|
|
200
|
+
line: parseInt(quickjsMatch[2], 10),
|
|
201
|
+
column: quickjsMatch[3] ? parseInt(quickjsMatch[3], 10) : null,
|
|
202
|
+
isQuickJS: true
|
|
203
|
+
};
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
// Node VM: "at /path/to/file.bru:11:5" or "at Object.<anonymous> (/path/to/file.bru:11:5)"
|
|
207
|
+
const nodeMatch = line.match(/at (?:.*?\()?((?:[A-Za-z]:)?[^:]+):(\d+)(?::(\d+))?\)?/);
|
|
208
|
+
if (nodeMatch && (nodeMatch[1].includes('/') || nodeMatch[1].includes('\\'))) {
|
|
209
|
+
return {
|
|
210
|
+
filePath: nodeMatch[1],
|
|
211
|
+
line: parseInt(nodeMatch[2], 10),
|
|
212
|
+
column: nodeMatch[3] ? parseInt(nodeMatch[3], 10) : null,
|
|
213
|
+
isQuickJS: false
|
|
214
|
+
};
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
return null;
|
|
218
|
+
};
|
|
219
|
+
|
|
220
|
+
/** Parse the first stack frame to extract file path, line, and column */
|
|
221
|
+
const parseStackTrace = (stack) => {
|
|
222
|
+
if (!stack) return null;
|
|
223
|
+
|
|
224
|
+
for (const line of stack.split('\n')) {
|
|
225
|
+
const match = matchStackFrame(line);
|
|
226
|
+
if (match) return match;
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
return null;
|
|
230
|
+
};
|
|
231
|
+
|
|
232
|
+
const parseErrorLocation = (error) => {
|
|
233
|
+
if (error.__callSites?.length > 0) {
|
|
234
|
+
const first = error.__callSites[0];
|
|
235
|
+
return {
|
|
236
|
+
filePath: first.filePath,
|
|
237
|
+
line: first.line,
|
|
238
|
+
column: first.column,
|
|
239
|
+
isQuickJS: false
|
|
240
|
+
};
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
/* falls back to string parsing */
|
|
244
|
+
const parsed = parseStackTrace(error.stack);
|
|
245
|
+
if (parsed && error.__isQuickJS) {
|
|
246
|
+
parsed.isQuickJS = true;
|
|
247
|
+
}
|
|
248
|
+
return parsed;
|
|
249
|
+
};
|
|
250
|
+
|
|
251
|
+
/** Read source file and extract context lines around the error location */
|
|
252
|
+
const getSourceContext = (filePath, errorLine, contextLines = DEFAULT_CONTEXT_LINES, cache = null) => {
|
|
253
|
+
const content = readFile(filePath, cache);
|
|
254
|
+
if (!content) return null;
|
|
255
|
+
|
|
256
|
+
const lines = content.split('\n');
|
|
257
|
+
const startLine = Math.max(1, errorLine - contextLines);
|
|
258
|
+
const endLine = Math.min(lines.length, errorLine + contextLines);
|
|
259
|
+
|
|
260
|
+
const contextLinesArray = [];
|
|
261
|
+
for (let i = startLine; i <= endLine; i++) {
|
|
262
|
+
contextLinesArray.push({
|
|
263
|
+
lineNumber: i,
|
|
264
|
+
content: lines[i - 1],
|
|
265
|
+
isError: i === errorLine
|
|
266
|
+
});
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
return { lines: contextLinesArray, startLine, errorLine };
|
|
270
|
+
};
|
|
271
|
+
|
|
272
|
+
/** Build adjusted stack trace string from structured CallSite data */
|
|
273
|
+
const buildStackFromCallSites = (callSites, scriptType = null, cache = null, scriptMetadata = null) => {
|
|
274
|
+
return callSites.map((site) => {
|
|
275
|
+
const adjusted = adjustLineNumber(site.filePath, site.line, false, scriptType, cache, scriptMetadata);
|
|
276
|
+
let fileToUse = site.filePath;
|
|
277
|
+
let lineToUse = adjusted !== null ? adjusted : site.line;
|
|
278
|
+
|
|
279
|
+
// Try segment resolution for collection/folder frames
|
|
280
|
+
if (adjusted === null && scriptMetadata?.segments) {
|
|
281
|
+
const parsed = { line: site.line, isQuickJS: false };
|
|
282
|
+
const resolved = resolveSegmentError(parsed, scriptMetadata, scriptType, cache);
|
|
283
|
+
if (resolved) {
|
|
284
|
+
fileToUse = resolved.filePath;
|
|
285
|
+
lineToUse = resolved.line;
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
const loc = site.column ? `${fileToUse}:${lineToUse}:${site.column}` : `${fileToUse}:${lineToUse}`;
|
|
290
|
+
const name = site.functionName ? `${site.functionName} (${loc})` : loc;
|
|
291
|
+
return ` at ${name}`;
|
|
292
|
+
}).join('\n');
|
|
293
|
+
};
|
|
294
|
+
|
|
295
|
+
/** Adjust all line numbers in a stack trace string */
|
|
296
|
+
const adjustStackTrace = (stack, scriptType = null, cache = null, scriptMetadata = null, forceQuickJS = false) => {
|
|
297
|
+
if (!stack) return stack;
|
|
298
|
+
|
|
299
|
+
return stack.split('\n').map((line) => {
|
|
300
|
+
const match = matchStackFrame(line);
|
|
301
|
+
if (!match) return line;
|
|
302
|
+
|
|
303
|
+
const isQuickJS = forceQuickJS || match.isQuickJS;
|
|
304
|
+
const adjusted = adjustLineNumber(match.filePath, match.line, isQuickJS, scriptType, cache, scriptMetadata);
|
|
305
|
+
|
|
306
|
+
// Try segment resolution for collection/folder frames
|
|
307
|
+
if (adjusted === null && scriptMetadata?.segments) {
|
|
308
|
+
const parsed = { line: match.line, isQuickJS };
|
|
309
|
+
const resolved = resolveSegmentError(parsed, scriptMetadata, scriptType, cache);
|
|
310
|
+
if (resolved) {
|
|
311
|
+
const suffix = match.isQuickJS ? ')' : '';
|
|
312
|
+
return match.column !== null
|
|
313
|
+
? line.replace(`${match.filePath}:${match.line}:${match.column}${suffix}`, `${resolved.filePath}:${resolved.line}:${match.column}${suffix}`)
|
|
314
|
+
: line.replace(`${match.filePath}:${match.line}${suffix}`, `${resolved.filePath}:${resolved.line}${suffix}`);
|
|
315
|
+
}
|
|
316
|
+
return line;
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
if (adjusted === null || adjusted === match.line) return line;
|
|
320
|
+
|
|
321
|
+
const suffix = match.isQuickJS ? ')' : '';
|
|
322
|
+
return match.column !== null
|
|
323
|
+
? line.replace(`:${match.line}:${match.column}${suffix}`, `:${adjusted}:${match.column}${suffix}`)
|
|
324
|
+
: line.replace(`:${match.line}${suffix}`, `:${adjusted}${suffix}`);
|
|
325
|
+
}).join('\n');
|
|
326
|
+
};
|
|
327
|
+
|
|
328
|
+
/** Resolve original error name from wrapped errors (QuickJS cause / Node VM ScriptError) */
|
|
329
|
+
const getErrorTypeName = (error) => {
|
|
330
|
+
return error.cause?.name || error.originalError?.name || error.name || error.constructor?.name || 'Error';
|
|
331
|
+
};
|
|
332
|
+
|
|
333
|
+
/** Format an error with source context and adjusted line numbers */
|
|
334
|
+
const formatErrorWithContext = (error, relativeFilePath = null, scriptType = null, contextLines = DEFAULT_CONTEXT_LINES, scriptMetadata = null) => {
|
|
335
|
+
if (!error) return '';
|
|
336
|
+
|
|
337
|
+
const cache = new Map();
|
|
338
|
+
// Use metadata from error object if available, otherwise use passed parameter
|
|
339
|
+
const metadata = error.scriptMetadata || scriptMetadata;
|
|
340
|
+
|
|
341
|
+
const parsed = parseErrorLocation(error);
|
|
342
|
+
if (!parsed) {
|
|
343
|
+
return `${error.message}\n${error.stack || ''}`;
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
const { filePath } = parsed;
|
|
347
|
+
const adjustedLine = adjustLineNumber(filePath, parsed.line, parsed.isQuickJS, scriptType, cache, metadata);
|
|
348
|
+
|
|
349
|
+
// adjustedLine === null means the error is in a collection/folder script
|
|
350
|
+
// resolve to the collection/folder source file using segment metadata
|
|
351
|
+
let segmentResult = null;
|
|
352
|
+
if (adjustedLine === null) {
|
|
353
|
+
segmentResult = resolveSegmentError(parsed, metadata, scriptType, cache);
|
|
354
|
+
if (!segmentResult) {
|
|
355
|
+
// Fallback: no segment resolution possible, show message + stack only
|
|
356
|
+
const errorType = getErrorTypeName(error);
|
|
357
|
+
const parts = [`${errorType}: ${error.message}`];
|
|
358
|
+
if (error.__callSites?.length > 0) {
|
|
359
|
+
parts.push(buildStackFromCallSites(error.__callSites, scriptType, cache, metadata));
|
|
360
|
+
} else if (error.stack) {
|
|
361
|
+
const stackLines = error.stack.split('\n').slice(1);
|
|
362
|
+
for (const stackLine of stackLines) {
|
|
363
|
+
parts.push(` ${stackLine.trim()}`);
|
|
364
|
+
}
|
|
365
|
+
}
|
|
366
|
+
return parts.join('\n');
|
|
367
|
+
}
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
const sourceFile = segmentResult ? segmentResult.filePath : filePath;
|
|
371
|
+
const sourceLine = segmentResult ? segmentResult.line : adjustedLine;
|
|
372
|
+
const context = isAllowedSourceFile(sourceFile) ? getSourceContext(sourceFile, sourceLine, contextLines, cache) : null;
|
|
373
|
+
|
|
374
|
+
if (!context) {
|
|
375
|
+
return `${error.message}\n${error.stack || ''}`;
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
const displayPath = segmentResult ? segmentResult.displayPath : (relativeFilePath || filePath);
|
|
379
|
+
const lines = [];
|
|
380
|
+
|
|
381
|
+
lines.push(`File: ${displayPath}`);
|
|
382
|
+
lines.push('');
|
|
383
|
+
|
|
384
|
+
const maxLineNumber = context.lines[context.lines.length - 1].lineNumber;
|
|
385
|
+
const lineNumberWidth = String(maxLineNumber).length;
|
|
386
|
+
|
|
387
|
+
for (const lineInfo of context.lines) {
|
|
388
|
+
const lineNum = String(lineInfo.lineNumber).padStart(lineNumberWidth, ' ');
|
|
389
|
+
const prefix = lineInfo.isError ? '>' : ' ';
|
|
390
|
+
|
|
391
|
+
lines.push(`${prefix} ${lineNum} | ${lineInfo.content}`);
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
lines.push('');
|
|
395
|
+
|
|
396
|
+
const errorType = getErrorTypeName(error);
|
|
397
|
+
lines.push(`${errorType}: ${error.message}`);
|
|
398
|
+
|
|
399
|
+
if (error.__callSites?.length > 0) {
|
|
400
|
+
lines.push(buildStackFromCallSites(error.__callSites, scriptType, cache, metadata));
|
|
401
|
+
} else {
|
|
402
|
+
const stackToDisplay = adjustStackTrace(error.stack, scriptType, cache, metadata, parsed.isQuickJS);
|
|
403
|
+
const userStackLines = stackToDisplay.split('\n').slice(1);
|
|
404
|
+
for (const stackLine of userStackLines) {
|
|
405
|
+
lines.push(` ${stackLine.trim()}`);
|
|
406
|
+
}
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
return lines.join('\n');
|
|
410
|
+
};
|
|
411
|
+
|
|
412
|
+
module.exports = {
|
|
413
|
+
SCRIPT_TYPES,
|
|
414
|
+
DEFAULT_CONTEXT_LINES,
|
|
415
|
+
parseStackTrace,
|
|
416
|
+
parseErrorLocation,
|
|
417
|
+
buildStackFromCallSites,
|
|
418
|
+
getSourceContext,
|
|
419
|
+
formatErrorWithContext,
|
|
420
|
+
adjustLineNumber,
|
|
421
|
+
resolveSegmentError,
|
|
422
|
+
findScriptBlockStartLine,
|
|
423
|
+
findYmlScriptBlockStartLine,
|
|
424
|
+
adjustStackTrace,
|
|
425
|
+
getErrorTypeName
|
|
426
|
+
};
|