@dynatrace/react-native-plugin
Version:
This plugin gives you the ability to use the Dynatrace Mobile agent in your react native application.
179 lines (178 loc) β’ 9.55 kB
JavaScript
;
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());
});
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.LineOffsetAnalyzer = void 0;
const fs = require("fs/promises");
const path_1 = require("path");
const diff_1 = require("diff");
const Logger_1 = require("../Logger");
const InstrumentUtil_1 = require("../util/InstrumentUtil");
const FileOperationHelper_1 = require("../../scripts/FileOperationHelper");
const ReactOptions_1 = require("../util/ReactOptions");
class LineOffsetAnalyzer {
constructor(options) {
this.rootDir = (0, path_1.resolve)(options.projectRoot);
this.instrumentedDir = (0, path_1.resolve)(options.buildPath);
this.outputFile = (0, path_1.resolve)(this.instrumentedDir, 'line-offsets.json');
this.logFile = (0, path_1.resolve)(this.instrumentedDir, 'debug.log');
this.appBundleInfo = options.appBundleInfo;
this.isProd = options.isProd;
this.androidSourcemapPath = options.androidSourcemapPath;
this.iosSourcemapPath = options.iosSourcemapPath;
}
run() {
return __awaiter(this, void 0, void 0, function* () {
try {
const buildExists = yield FileOperationHelper_1.default.checkIfFileExists(this.instrumentedDir);
if (!buildExists) {
yield this.log(`β Build directory not found at: ${this.instrumentedDir}`, true);
yield this.log('π οΈ Please run the instrumentation step first!', true);
console.error('π« Build directory missing. Make and instrument the project first.', true);
process.exit(1);
}
yield fs.writeFile(this.logFile, '');
const instrumentedFiles = (yield FileOperationHelper_1.default.getAllFiles(this.log, this.instrumentedDir))
.filter((filePath) => filePath.endsWith(InstrumentUtil_1.INSTRUMENTED_FILE_EXTENSION));
const mappings = {};
yield this.log(`π Found ${instrumentedFiles.length} instrumented files`, true);
for (const instrRelPath of instrumentedFiles) {
const baseRelPath = instrRelPath.slice(0, -InstrumentUtil_1.INSTRUMENTED_FILE_EXTENSION.length);
const origPath = (0, path_1.join)(this.rootDir, baseRelPath);
const instrPath = (0, path_1.join)(this.instrumentedDir, instrRelPath);
const origExists = yield FileOperationHelper_1.default.checkIfFileExists(origPath);
if (!origExists) {
yield this.log(`β οΈ Original file not found for: ${baseRelPath}`, true);
continue;
}
yield this.log(`π Comparing: ${baseRelPath}`, true);
const [originalContent, instrumentedContent] = yield Promise.all([
FileOperationHelper_1.default.readTextFromFile(origPath),
FileOperationHelper_1.default.readTextFromFile(instrPath),
]);
const offsets = this.computeLineOffsets(originalContent, instrumentedContent);
if (offsets.length > 0) {
mappings[origPath] = offsets;
yield this.log(`β
Offsets found for ${origPath}: ${offsets.length} entries`, true);
}
else {
yield this.log(`βΉοΈ No offsets needed for ${origPath}`, true);
}
}
const finalOutput = {
generationTime: new Date().toISOString(),
appVersion: this.appBundleInfo !== null ? this.appBundleInfo.version : 'application version not defined',
pluginVersion: this.appBundleInfo !== null ? this.appBundleInfo.pluginVersion : 'plugin version not defined',
mappings: {
[this.appBundleInfo !== null ? this.appBundleInfo.name : 'app name not defined']: mappings,
},
};
yield fs.writeFile(this.outputFile, JSON.stringify(finalOutput, null, 2));
yield this.log(`β
Line offsets written to ${this.outputFile}`, true);
if (this.isProd) {
yield this.log('π§ Patching source map with all offset data...');
const loadedMap = yield this.loadSourceMap();
if (loadedMap) {
const { map, path: originalMapPath } = loadedMap;
map.x_dynatrace_offset = mappings;
const backupPath = originalMapPath + '.bak';
const serilalisedPath = originalMapPath + '.serialised';
yield fs.copyFile(originalMapPath, backupPath);
yield this.log(`πΎ Backup created: ${backupPath}`);
yield fs.writeFile(originalMapPath, JSON.stringify(map));
yield fs.writeFile(serilalisedPath, JSON.stringify(map, null, 2));
yield this.log(`π Patched source map written to: ${originalMapPath}`);
}
else {
yield this.log('β οΈ No source map found β skipping patching step');
}
}
}
catch (err) {
console.error('β Unexpected error:', err);
}
});
}
loadSourceMap() {
return __awaiter(this, void 0, void 0, function* () {
const processValue = process.cwd();
const possiblePaths = [
(0, path_1.join)(processValue, 'app/build/generated/sourcemaps/react/release/index.android.bundle.map'),
(0, path_1.join)(this.androidSourcemapPath),
(0, path_1.join)(this.iosSourcemapPath),
];
let foundAny = false;
for (const mapPath of possiblePaths) {
try {
const exists = yield FileOperationHelper_1.default.checkIfFileExists(mapPath);
if (!exists) {
continue;
}
foundAny = true;
const content = yield fs.readFile(mapPath, 'utf-8');
const parsed = JSON.parse(content);
yield this.log(`πΊοΈ Found and parsed source map at: ${mapPath}`, true);
return { map: parsed, path: mapPath };
}
catch (err) {
yield this.log(`β οΈ Failed to load or parse source map at ${mapPath}: ${err.message}`, true);
}
}
if (!foundAny) {
yield this.log('β No valid source map found in any of the expected locations.', true);
}
return null;
});
}
log(msg, logOnScreen = false) {
return __awaiter(this, void 0, void 0, function* () {
if (ReactOptions_1.reactOptions && ReactOptions_1.reactOptions.react.debug && logOnScreen) {
Logger_1.default.logMessageSync(msg, Logger_1.default.INFO);
}
yield fs.appendFile(this.logFile, msg + '\n');
});
}
computeLineOffsets(originalContent, instrumentedContent) {
const diffs = (0, diff_1.diffLines)(originalContent, instrumentedContent);
let origLine = 0;
let instrLine = 0;
let offset = 0;
let lastRecordedOffset = null;
const offsets = [];
this.log('π Diff result:', true);
for (const part of diffs) {
const lines = part.value.split('\n').slice(0, -1);
if (part.added) {
this.log(`β Added ${lines.length} lines at instrumented line ${instrLine + 1}`);
lines.forEach((line, i) => this.log(` + ${instrLine + 1 + i}: ${line}`));
instrLine += lines.length;
offset -= lines.length;
}
else if (part.removed) {
this.log(`β Removed ${lines.length} lines at original line ${origLine + 1}`);
lines.forEach((line, i) => this.log(` - ${origLine + 1 + i}: ${line}`));
origLine += lines.length;
offset += lines.length;
}
else {
for (let i = 0; i < lines.length; i++) {
instrLine++;
origLine++;
if (offset !== 0 && offset !== lastRecordedOffset) {
offsets.push({ [instrLine]: offset });
lastRecordedOffset = offset;
}
}
}
}
return offsets;
}
}
exports.LineOffsetAnalyzer = LineOffsetAnalyzer;