UNPKG

@backtrace/sourcemap-tools

Version:
261 lines 13.4 kB
"use strict"; var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } return new (P || (P = Promise))(function (resolve, reject) { function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } step((generator = generator.apply(thisArg, _arguments || [])).next()); }); }; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.SourceProcessor = void 0; const path_1 = __importDefault(require("path")); const common_1 = require("./helpers/common"); const flow_1 = require("./helpers/flow"); const stringHelpers_1 = require("./helpers/stringHelpers"); const stringToUuid_1 = require("./helpers/stringToUuid"); const Result_1 = require("./models/Result"); class SourceProcessor { constructor(_debugIdGenerator) { this._debugIdGenerator = _debugIdGenerator; } isSourceProcessed(source) { return !!this._debugIdGenerator.getSourceDebugIdFromComment(source); } isSourceMapProcessed(sourceMap) { return !!this._debugIdGenerator.getSourceMapDebugId(sourceMap); } isSourceFileProcessed(sourcePath) { return __awaiter(this, void 0, void 0, function* () { return (0, flow_1.pipe)(sourcePath, common_1.readFile, Result_1.R.map((v) => this.isSourceProcessed(v))); }); } isSourceMapFileProcessed(sourceMapPath) { return __awaiter(this, void 0, void 0, function* () { return (0, flow_1.pipe)(sourceMapPath, common_1.readFile, Result_1.R.map((common_1.parseJSON)), Result_1.R.map((v) => this.isSourceMapProcessed(v))); }); } getSourceDebugId(source) { return this._debugIdGenerator.getSourceDebugIdFromComment(source); } getSourceMapDebugId(sourceMap) { return this._debugIdGenerator.getSourceMapDebugId(sourceMap); } getSourceMapFileDebugId(sourceMapPath) { return __awaiter(this, void 0, void 0, function* () { return (0, flow_1.pipe)(sourceMapPath, common_1.readFile, Result_1.R.map((common_1.parseJSON)), Result_1.R.map((sourceMap) => this.getSourceMapDebugId(sourceMap))); }); } /** * Adds required snippets and comments to source * @param source Source content. * @param debugId Debug ID. If not provided, one will be generated from `source`. * @param force Force adding changes. * @returns Used debug ID, new source and new sourcemap. */ processSource(source, debugId, force) { return __awaiter(this, void 0, void 0, function* () { return yield this.processSourceAndAvailableSourceMap(source, undefined, debugId, force); }); } /** * Adds required snippets and comments to source, and modifies sourcemap to include debug ID. * @param source Source content. * @param sourceMap Sourcemap object or JSON. * @param debugId Debug ID. If not provided, one will be generated from `source`. * @param force Force adding changes. * @returns Used debug ID, new source and new sourcemap. */ processSourceAndSourceMap(source, sourceMap, debugId, force) { return __awaiter(this, void 0, void 0, function* () { return yield this.processSourceAndAvailableSourceMap(source, sourceMap, debugId, force); }); } processSourceAndAvailableSourceMap(source, sourceMap, debugId, force) { return __awaiter(this, void 0, void 0, function* () { var _a, _b, _c; const sourceDebugId = this.getSourceDebugId(source); if (!debugId) { debugId = sourceDebugId !== null && sourceDebugId !== void 0 ? sourceDebugId : (0, stringToUuid_1.stringToUuid)(source); } let newSource = source; let offsetSourceMap; // If source has debug ID, but it is different, we need to only replace it if (sourceDebugId && debugId !== sourceDebugId) { newSource = this._debugIdGenerator.replaceDebugId(source, sourceDebugId, debugId); } if (force || !sourceDebugId || !this._debugIdGenerator.hasCodeSnippet(source, debugId)) { const sourceSnippet = this._debugIdGenerator.generateSourceSnippet(debugId); const shebang = (_a = source.match(/^(#!.+\n)/)) === null || _a === void 0 ? void 0 : _a[1]; newSource = shebang ? shebang + sourceSnippet + '\n' + source.substring(shebang.length) : sourceSnippet + '\n' + source; if (sourceMap) { // We need to offset the source map by amount of lines that we're inserting to the source code // Sourcemaps map code like this: // original code X:Y => generated code A:B // So if we add any code to generated code, mappings after that code will become invalid // We need to offset the mapping lines by sourceSnippetNewlineCount: // original code X:Y => generated code (A + sourceSnippetNewlineCount):B const sourceSnippetNewlineCount = (_c = (_b = sourceSnippet.match(/\n/g)) === null || _b === void 0 ? void 0 : _b.length) !== null && _c !== void 0 ? _c : 0; offsetSourceMap = yield this.offsetSourceMap(sourceMap, sourceSnippetNewlineCount + 1); } } if (force || !sourceDebugId || !this._debugIdGenerator.hasCommentSnippet(source, debugId)) { const sourceComment = this._debugIdGenerator.generateSourceComment(debugId); newSource = (0, stringHelpers_1.appendBeforeWhitespaces)(newSource, '\n' + sourceComment); } if (!sourceMap) { return { debugId, source: newSource }; } const newSourceMap = this._debugIdGenerator.addSourceMapDebugId(offsetSourceMap !== null && offsetSourceMap !== void 0 ? offsetSourceMap : sourceMap, debugId); return { debugId, source: newSource, sourceMap: newSourceMap }; }); } /** * Adds required snippets and comments to source, and modifies sourcemap to include debug ID. * Will write modified content to the files. * @param sourcePath Path to the source. * @param sourceMapPath Path to the sourcemap. If not specified, will try to resolve from sourceMapURL. * @param debugId Debug ID. If not provided, one will be generated from `source`. * @returns Used debug ID. */ processSourceAndSourceMapFiles(sourcePath, sourceMapPath, debugId, force) { return __awaiter(this, void 0, void 0, function* () { const sourceReadResult = yield (0, common_1.readFile)(sourcePath); if (sourceReadResult.isErr()) { return sourceReadResult; } const source = sourceReadResult.data; if (!sourceMapPath) { const pathFromSource = yield this.getSourceMapPathFromSource(source, sourcePath); if (!pathFromSource) { return (0, Result_1.Err)('could not find source map for source'); } sourceMapPath = pathFromSource; } const sourceMapReadResult = yield (0, common_1.readFile)(sourceMapPath); if (sourceMapReadResult.isErr()) { return sourceMapReadResult; } const sourceMapJson = sourceMapReadResult.data; const parseResult = (0, common_1.parseJSON)(sourceMapJson); if (parseResult.isErr()) { return parseResult; } const sourceMap = parseResult.data; const processResult = yield this.processSourceAndSourceMap(source, sourceMap, debugId, force); return (0, Result_1.Ok)(Object.assign(Object.assign({}, processResult), { sourcePath, sourceMapPath })); }); } getSourceMapPathFromSourceFile(sourcePath) { return __awaiter(this, void 0, void 0, function* () { const sourceReadResult = yield (0, common_1.readFile)(sourcePath); if (sourceReadResult.isErr()) { return sourceReadResult; } return (0, Result_1.Ok)(yield this.getSourceMapPathFromSource(sourceReadResult.data, sourcePath)); }); } getSourceMapPathFromSource(source, sourcePath) { return __awaiter(this, void 0, void 0, function* () { const matchAll = (str, regex) => { const result = []; // eslint-disable-next-line no-constant-condition while (true) { const match = regex.exec(str); if (!match) { return result; } result.push(match); } }; const checkFile = (filePath) => (0, flow_1.pipe)(filePath, common_1.statFile, (result) => (result.isOk() && result.data.isFile() ? filePath : undefined)); const sourceMapName = path_1.default.basename(sourcePath) + '.map'; const checkFileInDir = (dir) => (0, flow_1.pipe)(dir, common_1.statFile, (result) => result.isOk() && result.data.isDirectory() ? // If path exists and is a directory, check if file exists in that dir checkFile(path_1.default.join(dir, sourceMapName)) : // If path does not exist or is not a directory, check if file exists in dir of that path checkFile(path_1.default.join(path_1.default.dirname(dir), sourceMapName))); const matches = matchAll(source, /^\s*\/\/# sourceMappingURL=(.+)$/gm); if (!matches.length) { return checkFileInDir(sourcePath); } for (const match of matches.reverse()) { const file = match[1]; if (!file) { continue; } const fullPath = path_1.default.resolve(path_1.default.dirname(sourcePath), file); if (yield checkFile(fullPath)) { return fullPath; } const fileInDir = yield checkFileInDir(fullPath); if (fileInDir) { return fileInDir; } } return checkFileInDir(sourcePath); }); } addSourcesToSourceMap(sourceMap, sourceMapPath, force) { return __awaiter(this, void 0, void 0, function* () { var _a; if (typeof sourceMap === 'string') { const parseResult = (0, common_1.parseJSON)(sourceMap); if (parseResult.isErr()) { return parseResult; } sourceMap = parseResult.data; } const sourceRoot = sourceMap.sourceRoot ? path_1.default.resolve(path_1.default.dirname(sourceMapPath), sourceMap.sourceRoot) : path_1.default.resolve(path_1.default.dirname(sourceMapPath)); const succeeded = []; const skipped = []; const failed = []; const sourcesContent = (_a = sourceMap.sourcesContent) !== null && _a !== void 0 ? _a : []; for (let i = 0; i < sourceMap.sources.length; i++) { const sourcePath = sourceMap.sources[i]; if (sourcesContent[i] && !force) { skipped.push(sourcePath); continue; } const readResult = yield (0, common_1.readFile)(path_1.default.resolve(sourceRoot, sourcePath)); if (readResult.isErr()) { failed.push(sourcePath); } else { sourcesContent[i] = readResult.data; succeeded.push(sourcePath); } } return (0, Result_1.Ok)({ sourceMap: Object.assign(Object.assign({}, sourceMap), { sourcesContent }), succeeded, skipped, failed, }); }); } doesSourceMapHaveSources(sourceMap) { var _a, _b; return ((_a = sourceMap.sources) === null || _a === void 0 ? void 0 : _a.length) === ((_b = sourceMap.sourcesContent) === null || _b === void 0 ? void 0 : _b.length); } offsetSourceMap(sourceMap, count) { return __awaiter(this, void 0, void 0, function* () { // Each line in sourcemap is separated by a semicolon. // Offsetting source map lines is just done by prepending semicolons const offset = ';'.repeat(count); const mappings = offset + sourceMap.mappings; return Object.assign(Object.assign({}, sourceMap), { mappings }); }); } } exports.SourceProcessor = SourceProcessor; //# sourceMappingURL=SourceProcessor.js.map