UNPKG

@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
"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()); }); }; 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;