156 lines
5.9 KiB
JavaScript
156 lines
5.9 KiB
JavaScript
"use strict";
|
|
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
};
|
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
exports.getSourceMapFromLocalFile = exports.getSourceMapFromRemoteFile = exports.symbolicateStackFrame = exports.symbolicateFromSources = exports.symbolicateStackTraceFromRemoteFrames = exports.getSourceMapFromRemoteUrl = void 0;
|
|
const fs_1 = require("fs");
|
|
const path_1 = __importDefault(require("path"));
|
|
const source_map_1 = require("source-map");
|
|
const read_file_1 = require("./assets/read-file");
|
|
const truthy_1 = require("./truthy");
|
|
function extractSourceMapUrl(fileContents) {
|
|
const regex = /\/\/[#@] ?sourceMappingURL=([^\s'"]+)\s*$/gm;
|
|
let match = null;
|
|
for (;;) {
|
|
const next = regex.exec(fileContents);
|
|
if (next === null || next === undefined) {
|
|
break;
|
|
}
|
|
match = next;
|
|
}
|
|
if (!(match === null || match === void 0 ? void 0 : match[1])) {
|
|
return null;
|
|
}
|
|
return match[1].toString();
|
|
}
|
|
const getSourceMapFromRemoteUrl = async (url) => {
|
|
if (!url.endsWith('.js.map')) {
|
|
return Promise.reject(new Error(`The URL ${url} does not seem to be a valid source map URL.`));
|
|
}
|
|
const obj = await fetchUrl(url);
|
|
return new source_map_1.SourceMapConsumer(obj);
|
|
};
|
|
exports.getSourceMapFromRemoteUrl = getSourceMapFromRemoteUrl;
|
|
const getSourceMap = (filePath, fileContents, type) => {
|
|
const sm = extractSourceMapUrl(fileContents);
|
|
if (sm === null) {
|
|
return Promise.resolve(null);
|
|
}
|
|
if (sm.indexOf('data:') === 0) {
|
|
const base64 = /^data:application\/json;([\w=:"-]+;)*base64,/;
|
|
const match2 = sm.match(base64);
|
|
if (!match2) {
|
|
throw new Error('Sorry, non-base64 inline source-map encoding is not supported.');
|
|
}
|
|
const converted = window.atob(sm.substring(match2[0].length));
|
|
try {
|
|
const sourceMapConsumer = new source_map_1.SourceMapConsumer(JSON.parse(converted));
|
|
return Promise.resolve(sourceMapConsumer);
|
|
}
|
|
catch (_a) {
|
|
return Promise.resolve(null);
|
|
}
|
|
}
|
|
if (type === 'local') {
|
|
// Find adjacent file: bundle.js -> bundle.js.map
|
|
const newFilePath = path_1.default.join(path_1.default.dirname(filePath), sm);
|
|
return Promise.resolve(new source_map_1.SourceMapConsumer((0, fs_1.readFileSync)(newFilePath, 'utf8')));
|
|
}
|
|
const index = filePath.lastIndexOf('/');
|
|
const url = filePath.substring(0, index + 1) + sm;
|
|
return (0, exports.getSourceMapFromRemoteUrl)(url);
|
|
};
|
|
const fetchUrl = async (url) => {
|
|
const { request, response } = await (0, read_file_1.readFile)(url);
|
|
return new Promise((resolve, reject) => {
|
|
let downloaded = '';
|
|
response.on('data', (d) => {
|
|
downloaded += d;
|
|
});
|
|
response.on('end', () => {
|
|
request.destroy();
|
|
response.destroy();
|
|
resolve(downloaded);
|
|
});
|
|
response.on('error', (err) => {
|
|
request.destroy();
|
|
response.destroy();
|
|
return reject(err);
|
|
});
|
|
});
|
|
};
|
|
function getLinesAround(line, count, lines) {
|
|
const result = [];
|
|
for (let index = Math.max(0, line - 1 - count) + 1; index <= Math.min(lines.length - 1, line - 1 + count); ++index) {
|
|
result.push({
|
|
lineNumber: index + 1,
|
|
content: lines[index],
|
|
highlight: index + 1 === line,
|
|
});
|
|
}
|
|
return result;
|
|
}
|
|
const getOriginalPosition = (source_map, line, column) => {
|
|
const result = source_map.originalPositionFor({
|
|
line,
|
|
column,
|
|
});
|
|
return { line: result.line, column: result.column, source: result.source };
|
|
};
|
|
const symbolicateStackTraceFromRemoteFrames = async (frames) => {
|
|
const uniqueFileNames = [
|
|
...new Set(frames
|
|
.map((f) => f.fileName)
|
|
.filter((f) => f.startsWith('http://') || f.startsWith('https://'))
|
|
.filter(truthy_1.truthy)),
|
|
];
|
|
const maps = await Promise.all(uniqueFileNames.map((fileName) => {
|
|
return (0, exports.getSourceMapFromRemoteFile)(fileName);
|
|
}));
|
|
const mapValues = {};
|
|
for (let i = 0; i < uniqueFileNames.length; i++) {
|
|
mapValues[uniqueFileNames[i]] = maps[i];
|
|
}
|
|
return (0, exports.symbolicateFromSources)(frames, mapValues);
|
|
};
|
|
exports.symbolicateStackTraceFromRemoteFrames = symbolicateStackTraceFromRemoteFrames;
|
|
const symbolicateFromSources = (frames, mapValues) => {
|
|
return frames
|
|
.map((frame) => {
|
|
const map = mapValues[frame.fileName];
|
|
if (!map) {
|
|
return null;
|
|
}
|
|
return (0, exports.symbolicateStackFrame)(frame, map);
|
|
})
|
|
.filter(truthy_1.truthy)
|
|
.filter((f) => f.originalScriptCode !== null);
|
|
};
|
|
exports.symbolicateFromSources = symbolicateFromSources;
|
|
const symbolicateStackFrame = (frame, map) => {
|
|
const pos = getOriginalPosition(map, frame.lineNumber, frame.columnNumber);
|
|
const hasSource = pos.source ? map.sourceContentFor(pos.source, false) : null;
|
|
const scriptCode = hasSource && pos.line
|
|
? getLinesAround(pos.line, 3, hasSource.split('\n'))
|
|
: null;
|
|
return {
|
|
originalColumnNumber: pos.column,
|
|
originalFileName: pos.source,
|
|
originalFunctionName: frame.functionName,
|
|
originalLineNumber: pos.line,
|
|
originalScriptCode: scriptCode,
|
|
};
|
|
};
|
|
exports.symbolicateStackFrame = symbolicateStackFrame;
|
|
const getSourceMapFromRemoteFile = async (fileName) => {
|
|
const fileContents = await fetchUrl(fileName);
|
|
return getSourceMap(fileName, fileContents, 'remote');
|
|
};
|
|
exports.getSourceMapFromRemoteFile = getSourceMapFromRemoteFile;
|
|
const getSourceMapFromLocalFile = (fileName) => {
|
|
const fileContents = (0, fs_1.readFileSync)(fileName, 'utf8');
|
|
return getSourceMap(fileName, fileContents, 'local');
|
|
};
|
|
exports.getSourceMapFromLocalFile = getSourceMapFromLocalFile;
|