From a80b3c25b9de29daf413f30bb2a2b08c605f68d6 Mon Sep 17 00:00:00 2001 From: Krystof Woldrich Date: Thu, 29 Jun 2023 13:33:42 +0200 Subject: [PATCH 01/16] POC: Add preModule for debugIds --- sample-new-architecture/metro.config.js | 36 ++++++++++++++++++++++++- 1 file changed, 35 insertions(+), 1 deletion(-) diff --git a/sample-new-architecture/metro.config.js b/sample-new-architecture/metro.config.js index 7e04f49873..0543c21d41 100644 --- a/sample-new-architecture/metro.config.js +++ b/sample-new-architecture/metro.config.js @@ -1,9 +1,31 @@ const { getDefaultConfig, mergeConfig } = require('@react-native/metro-config'); const path = require('path'); const blacklist = require('metro-config/src/defaults/exclusionList'); +const bundleToString = require('metro/src/lib/bundleToString'); +const baseJSBundle = require('metro/src/DeltaBundler/Serializers/baseJSBundle'); +const CountingSet = require('metro/src/lib/CountingSet').default; +const countLines = require('metro/src/lib/countLines'); const parentDir = path.resolve(__dirname, '..'); +const debugIdCode = 'console.log("debugId");'; +const debugIdModule = { + dependencies: new Map(), + getSource: () => Buffer.from(debugIdCode), + inverseDependencies: new CountingSet(), + path: '__debugid__', + output: [ + { + type: 'js/script/virtual', + data: { + code: debugIdCode, + lineCount: countLines(debugIdCode), + map: [], + }, + }, + ], +}; + /** * Metro configuration * https://facebook.github.io/metro/docs/configuration @@ -40,6 +62,18 @@ const config = { }, ), }, + serializer: { + customSerializer: (entryPoint, preModules, graph, options) => { + console.log('Adding debugId module...'); + preModules.unshift(debugIdModule); + const bundle = baseJSBundle(entryPoint, preModules, graph, options); + return bundleToString(bundle).code; + }, + }, }; -module.exports = mergeConfig(getDefaultConfig(__dirname), config); +const m = mergeConfig(getDefaultConfig(__dirname), config); +// m.transformer.getTransformOptions().then(opts => { +// console.log(opts); +// }); +module.exports = m; From ca911819f3e875c58d4cc972404378f62a1f164c Mon Sep 17 00:00:00 2001 From: Krystof Woldrich Date: Thu, 29 Jun 2023 14:09:56 +0200 Subject: [PATCH 02/16] Add debug id steps --- sample-new-architecture/metro.config.js | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/sample-new-architecture/metro.config.js b/sample-new-architecture/metro.config.js index 0543c21d41..1743fa5796 100644 --- a/sample-new-architecture/metro.config.js +++ b/sample-new-architecture/metro.config.js @@ -64,6 +64,10 @@ const config = { }, serializer: { customSerializer: (entryPoint, preModules, graph, options) => { + // TODO: + // 1. Deterministically order all the modules (besides assets) preModules and graph dependencies + // 2. Generate Debug ID using https://github.com/getsentry/sentry-javascript-bundler-plugins/blob/36bf09880f983d562d9179cbbeac40f7083be0ff/packages/bundler-plugin-core/src/utils.ts#L174 + console.log('Adding debugId module...'); console.log('Adding debugId module...'); preModules.unshift(debugIdModule); const bundle = baseJSBundle(entryPoint, preModules, graph, options); From 2467d0d5cd54a316fab724562471ea6c0a9c2765 Mon Sep 17 00:00:00 2001 From: Krystof Woldrich Date: Thu, 29 Jun 2023 17:29:07 +0200 Subject: [PATCH 03/16] !wip --- sample-new-architecture/metro.config.js | 50 +++++++++++++++++++++++-- 1 file changed, 46 insertions(+), 4 deletions(-) diff --git a/sample-new-architecture/metro.config.js b/sample-new-architecture/metro.config.js index 1743fa5796..f30a5e4a3d 100644 --- a/sample-new-architecture/metro.config.js +++ b/sample-new-architecture/metro.config.js @@ -3,12 +3,19 @@ const path = require('path'); const blacklist = require('metro-config/src/defaults/exclusionList'); const bundleToString = require('metro/src/lib/bundleToString'); const baseJSBundle = require('metro/src/DeltaBundler/Serializers/baseJSBundle'); +const sourceMapString = require('metro/src/DeltaBundler/Serializers/sourceMapString'); const CountingSet = require('metro/src/lib/CountingSet').default; const countLines = require('metro/src/lib/countLines'); +const uuidv4 = require('uuid').v4; const parentDir = path.resolve(__dirname, '..'); -const debugIdCode = 'console.log("debugId");'; +function getDebugIdSnippet(debugId) { + return `;!function(){try{var e="undefined"!=typeof window?window:"undefined"!=typeof global?global:"undefined"!=typeof self?self:{},n=(new Error).stack;n&&(e._sentryDebugIds=e._sentryDebugIds||{},e._sentryDebugIds[n]="${debugId}",e._sentryDebugIdIdentifier="sentry-dbid-${debugId}")}catch(e){}}();`; +} + +const debugId = uuidv4(); +const debugIdCode = getDebugIdSnippet(debugId); const debugIdModule = { dependencies: new Map(), getSource: () => Buffer.from(debugIdCode), @@ -63,15 +70,50 @@ const config = { ), }, serializer: { - customSerializer: (entryPoint, preModules, graph, options) => { + customSerializer: function (entryPoint, preModules, graph, options) { + const createModuleId = this.createModuleIdFactory(); + const getSortedModules = () => { + const modules = [...graph.dependencies.values()]; + // Assign IDs to modules in a consistent order + for (const module of modules) { + createModuleId(module.path); + } + // Sort by IDs + return modules.sort( + (a, b) => createModuleId(a.path) - createModuleId(b.path), + ); + }; + // TODO: // 1. Deterministically order all the modules (besides assets) preModules and graph dependencies // 2. Generate Debug ID using https://github.com/getsentry/sentry-javascript-bundler-plugins/blob/36bf09880f983d562d9179cbbeac40f7083be0ff/packages/bundler-plugin-core/src/utils.ts#L174 console.log('Adding debugId module...'); - console.log('Adding debugId module...'); preModules.unshift(debugIdModule); const bundle = baseJSBundle(entryPoint, preModules, graph, options); - return bundleToString(bundle).code; + // TODO: Extract to addDebugIdComment + const bundleCodeWithoutDebugIdComment = + bundleToString(bundle).code + `\n//# debugId=${debugId}`; + const sourceMapComment = bundleCodeWithoutDebugIdComment.substring( + bundleCodeWithoutDebugIdComment.lastIndexOf('//# sourceMappingURL='), + ); + // end addDebugIdComment + + const bundleMapString = sourceMapString( + [...preModules, ...getSortedModules(graph)], + { + processModuleFilter: this.processModuleFilter, + shouldAddToIgnoreList: options.shouldAddToIgnoreList, + }, + ); + const bundleMap = JSON.parse(bundleMapString); + // For now we write both fields until we know what will become the standard - if ever. + bundleMap['debug_id'] = debugId; + bundleMap['debugId'] = debugId; + + return { + code: bundleCode, + map: JSON.stringify(bundleMap), + }; }, }, }; From aaa789a940d18e22b8fe8f14744bd4fdee3a95a2 Mon Sep 17 00:00:00 2001 From: Krystof Woldrich Date: Fri, 30 Jun 2023 11:05:52 +0200 Subject: [PATCH 04/16] Fix serialized to work with the dev server --- sample-new-architecture/metro.config.js | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/sample-new-architecture/metro.config.js b/sample-new-architecture/metro.config.js index f30a5e4a3d..1ee5675785 100644 --- a/sample-new-architecture/metro.config.js +++ b/sample-new-architecture/metro.config.js @@ -71,7 +71,7 @@ const config = { }, serializer: { customSerializer: function (entryPoint, preModules, graph, options) { - const createModuleId = this.createModuleIdFactory(); + const createModuleId = options.createModuleId; const getSortedModules = () => { const modules = [...graph.dependencies.values()]; // Assign IDs to modules in a consistent order @@ -87,7 +87,6 @@ const config = { // TODO: // 1. Deterministically order all the modules (besides assets) preModules and graph dependencies // 2. Generate Debug ID using https://github.com/getsentry/sentry-javascript-bundler-plugins/blob/36bf09880f983d562d9179cbbeac40f7083be0ff/packages/bundler-plugin-core/src/utils.ts#L174 - console.log('Adding debugId module...'); preModules.unshift(debugIdModule); const bundle = baseJSBundle(entryPoint, preModules, graph, options); // TODO: Extract to addDebugIdComment @@ -98,6 +97,11 @@ const config = { ); // end addDebugIdComment + if (this.processModuleFilter === undefined) { + // processModuleFilter is undefined when processing build request from the dev server + return bundleCodeWithoutDebugIdComment; + } + const bundleMapString = sourceMapString( [...preModules, ...getSortedModules(graph)], { @@ -111,7 +115,7 @@ const config = { bundleMap['debugId'] = debugId; return { - code: bundleCode, + code: bundleCodeWithoutDebugIdComment, map: JSON.stringify(bundleMap), }; }, From b093f67712a98bc80b03d5f85b58b84fb16f1adb Mon Sep 17 00:00:00 2001 From: Krystof Woldrich Date: Fri, 30 Jun 2023 12:47:59 +0200 Subject: [PATCH 05/16] add debug id before source map comment, fix debug id snippet --- sample-new-architecture/metro.config.js | 49 +++++++++++++++++++------ 1 file changed, 38 insertions(+), 11 deletions(-) diff --git a/sample-new-architecture/metro.config.js b/sample-new-architecture/metro.config.js index 1ee5675785..25f67120c7 100644 --- a/sample-new-architecture/metro.config.js +++ b/sample-new-architecture/metro.config.js @@ -11,7 +11,7 @@ const uuidv4 = require('uuid').v4; const parentDir = path.resolve(__dirname, '..'); function getDebugIdSnippet(debugId) { - return `;!function(){try{var e="undefined"!=typeof window?window:"undefined"!=typeof global?global:"undefined"!=typeof self?self:{},n=(new Error).stack;n&&(e._sentryDebugIds=e._sentryDebugIds||{},e._sentryDebugIds[n]="${debugId}",e._sentryDebugIdIdentifier="sentry-dbid-${debugId}")}catch(e){}}();`; + return `var _sentryDebugIdIdentifier="sentry-dbid-${debugId}";`; } const debugId = uuidv4(); @@ -32,6 +32,11 @@ const debugIdModule = { }, ], }; +const defaultSerializer = (entryPoint, preModules, graph, options) => + bundleToString(baseJSBundle(entryPoint, preModules, graph, options)).code; +const PRELUDE_MODULE_PATH = '__prelude__'; +const SOURCE_MAP_COMMENT = '//# sourceMappingURL='; +const DEBUG_ID_COMMENT = '//# debugId='; /** * Metro configuration @@ -87,21 +92,43 @@ const config = { // TODO: // 1. Deterministically order all the modules (besides assets) preModules and graph dependencies // 2. Generate Debug ID using https://github.com/getsentry/sentry-javascript-bundler-plugins/blob/36bf09880f983d562d9179cbbeac40f7083be0ff/packages/bundler-plugin-core/src/utils.ts#L174 - preModules.unshift(debugIdModule); - const bundle = baseJSBundle(entryPoint, preModules, graph, options); - // TODO: Extract to addDebugIdComment - const bundleCodeWithoutDebugIdComment = - bundleToString(bundle).code + `\n//# debugId=${debugId}`; - const sourceMapComment = bundleCodeWithoutDebugIdComment.substring( - bundleCodeWithoutDebugIdComment.lastIndexOf('//# sourceMappingURL='), + + // TODO: Only add debug id if it's not already present + if (preModules[0].path === PRELUDE_MODULE_PATH) { + // prelude module must be first as it measures the bundle startup time + preModules.unshift(preModules[0]); + preModules[1] = debugIdModule; + } else { + preModules.unshift(debugIdModule); + } + + const bundleCode = defaultSerializer( + entryPoint, + preModules, + graph, + options, ); - // end addDebugIdComment + + // Add debug id comment to the bundle + const debugIdComment = `${DEBUG_ID_COMMENT}${debugId}`; + const indexOfSourceMapComment = + bundleCode.lastIndexOf(SOURCE_MAP_COMMENT); + const bundleCodeWithDebugId = + indexOfSourceMapComment === -1 + ? // If source map comment is missing lets just add the debug id comment + bundleCode + '\n' + debugIdComment + : // If source map comment is present lets add the debug id comment before it + bundleCode.substring(0, indexOfSourceMapComment) + + debugIdComment + + '\n' + + bundleCode.substring(indexOfSourceMapComment); if (this.processModuleFilter === undefined) { // processModuleFilter is undefined when processing build request from the dev server - return bundleCodeWithoutDebugIdComment; + return bundleCodeWithDebugId; } + // Generate bundle map const bundleMapString = sourceMapString( [...preModules, ...getSortedModules(graph)], { @@ -115,7 +142,7 @@ const config = { bundleMap['debugId'] = debugId; return { - code: bundleCodeWithoutDebugIdComment, + code: bundleCodeWithDebugId, map: JSON.stringify(bundleMap), }; }, From e0c46a6e00aa3d9edfe0a1301f1d527cacc89842 Mon Sep 17 00:00:00 2001 From: Krystof Woldrich Date: Wed, 5 Jul 2023 15:59:23 +0200 Subject: [PATCH 06/16] Update sentry.gradle to use debug ids --- sample-new-architecture/metro.config.js | 2 +- scripts/copy-debugid.js | 37 ++++++++++++++ scripts/sentry-debugid-injection-snippet.js | 13 +++++ sentry.gradle | 54 ++++++++++++++------- 4 files changed, 87 insertions(+), 19 deletions(-) create mode 100644 scripts/copy-debugid.js create mode 100644 scripts/sentry-debugid-injection-snippet.js diff --git a/sample-new-architecture/metro.config.js b/sample-new-architecture/metro.config.js index 25f67120c7..ab184eb743 100644 --- a/sample-new-architecture/metro.config.js +++ b/sample-new-architecture/metro.config.js @@ -11,7 +11,7 @@ const uuidv4 = require('uuid').v4; const parentDir = path.resolve(__dirname, '..'); function getDebugIdSnippet(debugId) { - return `var _sentryDebugIdIdentifier="sentry-dbid-${debugId}";`; + return `var _sentryDebugIds={},_sentryDebugIdIdentifier="";try{var e=Error().stack;e&&(_sentryDebugIds[e]="${debugId}",_sentryDebugIdIdentifier="sentry-dbid-${debugId}")}catch(r){}`; } const debugId = uuidv4(); diff --git a/scripts/copy-debugid.js b/scripts/copy-debugid.js new file mode 100644 index 0000000000..8a23ad7a10 --- /dev/null +++ b/scripts/copy-debugid.js @@ -0,0 +1,37 @@ +const process = require('process'); +const fs = require('fs'); + +console.log('Copy `debugId` from packager source map to Hermes source map...'); + +const packagerSourceMapPath = process.argv[2]; +const hermesSourceMapPath = process.argv[3]; + +if (!packagerSourceMapPath) { + console.error('Please provide packager source map path (A path to copy `debugId` from).'); + process.exit(0); +} +if (!hermesSourceMapPath) { + console.error('Please provide Hermes source map path. ((A path to copy `debugId` to))'); + process.exit(0); +} +if (!fs.existsSync(packagerSourceMapPath)) { + console.error('Packager source map path (A path to copy `debugId` from).'); + process.exit(0); +} +if (!fs.existsSync(hermesSourceMapPath)) { + console.error('Hermes source map not found. ((A path to copy `debugId` to))'); + process.exit(0); +} + +const from = fs.readFileSync(process.argv[2], 'utf8'); +const to = fs.readFileSync(process.argv[3], 'utf8'); + +const fromParsed = JSON.parse(from); +const toParsed = JSON.parse(to); + +toParsed.debugId = fromParsed.debugId; +toParsed.debug_id = fromParsed.debug_id; + +fs.writeFileSync(process.argv[3], JSON.stringify(toParsed)); + +console.log('Done.'); diff --git a/scripts/sentry-debugid-injection-snippet.js b/scripts/sentry-debugid-injection-snippet.js new file mode 100644 index 0000000000..fba6423b76 --- /dev/null +++ b/scripts/sentry-debugid-injection-snippet.js @@ -0,0 +1,13 @@ +// This is non minified version the debug id injection snippet used in the Metro plugin. +var _sentryDebugIds = {}; +var _sentryDebugIdIdentifier = ''; +try { + var stack = new Error().stack; + if (stack) { + _sentryDebugIds[stack] = '__SENTRY_DEBUG_ID__'; + // eslint-disable-next-line no-unused-vars + _sentryDebugIdIdentifier = 'sentry-dbid-__SENTRY_DEBUG_ID__'; + } +} catch (e) { + /**/ +} diff --git a/sentry.gradle b/sentry.gradle index b4671fa477..fa60d4a842 100644 --- a/sentry.gradle +++ b/sentry.gradle @@ -39,6 +39,7 @@ gradle.projectsEvaluated { def shouldCleanUp def sourcemapOutput def bundleOutput + def packagerSourcemapOutput def props = bundleTask.getProperties() def reactRoot = props.get("workingDir") if (reactRoot == null) { @@ -47,7 +48,7 @@ gradle.projectsEvaluated { def modulesOutput = "$reactRoot/android/app/src/main/assets/modules.json" def modulesTask = null - (shouldCleanUp, bundleOutput, sourcemapOutput) = forceSourceMapOutputFromBundleTask(bundleTask) + (shouldCleanUp, bundleOutput, sourcemapOutput, packagerSourcemapOutput) = forceSourceMapOutputFromBundleTask(bundleTask) // Lets leave this here if we need to debug // println bundleTask.properties @@ -86,14 +87,21 @@ gradle.projectsEvaluated { // based on where we're uploading to. def nameCliTask = "${bundleTask.name}_SentryUpload_${releaseName}_${versionCode}" def nameModulesTask = "${bundleTask.name}_SentryCollectModules_${releaseName}_${versionCode}" + def nameCopyDebugIdTask = "${bundleTask.name}_SentryCopyDebugId_${releaseName}_${versionCode}" // If several outputs have the same releaseName and versionCode, we'd do the exact same // upload for each of them. No need to repeat. try { tasks.named(nameCliTask); return } catch (Exception e) {} + def copyDebugIdTask = tasks.create(nameCopyDebugIdTask, Exec) { + def sentryPackage = resolveSentryReactNativeSDKPath(reactRoot) + def osCompatibilityCopyCommand = Os.isFamily(Os.FAMILY_WINDOWS) ? ['cmd', '/c'] : [] + commandLine(*osCompatibilityCopyCommand, 'node', "../../../scripts/copy-debugid.js", packagerSourcemapOutput, sourcemapOutput) + } + /** Upload source map file to the sentry server via CLI call. */ def cliTask = tasks.create(nameCliTask, Exec) { - description = "upload debug symbols to sentry" + description = "upload source maps to Sentry" group = 'sentry.io' workingDir reactRoot @@ -143,9 +151,7 @@ gradle.projectsEvaluated { ]) args.addAll(["react-native", "gradle", "--bundle", bundleOutput, // The path to a bundle that should be uploaded. - "--sourcemap", sourcemapOutput, // The path to a sourcemap that should be uploaded. - "--release", releaseName, // The name of the release to publish. - "--dist", versionCode + "--sourcemap", sourcemapOutput // The path to a sourcemap that should be uploaded. ]) args.addAll(!config.flavorAware ? [] : [ "--org", sentryProps.get("defaults.org"), @@ -166,11 +172,7 @@ gradle.projectsEvaluated { workingDir reactRoot - def resolvedSentryPath = null - try { - resolvedSentryPath = new File(["node", "--print", "require.resolve('@sentry/react-native/package.json')"].execute(null, rootDir).text.trim()).getParentFile(); - } catch (Throwable ignored) {} // if the resolve fails we fallback to the default path - def sentryPackage = resolvedSentryPath != null && resolvedSentryPath.exists() ? resolvedSentryPath.getAbsolutePath() : "$reactRoot/node_modules/@sentry/react-native" + def sentryPackage = resolveSentryReactNativeSDKPath() def collectModulesScript = config.collectModulesScript ? file(config.collectModulesScript).getAbsolutePath() @@ -202,11 +204,12 @@ gradle.projectsEvaluated { // chain the upload tasks so they run sequentially in order to run // the cliCleanUpTask after the final upload task is run if (previousCliTask != null) { - previousCliTask.finalizedBy cliTask + previousCliTask.finalizedBy copyDebugIdTask } else { - bundleTask.finalizedBy cliTask + bundleTask.finalizedBy copyDebugIdTask } - previousCliTask = cliTask + previousCliTask = copyDebugIdTask + copyDebugIdTask.finalizedBy cliTask cliTask.finalizedBy modulesTask } @@ -249,6 +252,15 @@ gradle.projectsEvaluated { } } +def resolveSentryReactNativeSDKPath(reactRoot) { + def resolvedSentryPath = null + try { + resolvedSentryPath = new File(["node", "--print", "require.resolve('@sentry/react-native/package.json')"].execute(null, rootDir).text.trim()).getParentFile(); + } catch (Throwable ignored) {} // if the resolve fails we fallback to the default path + def sentryPackage = resolvedSentryPath != null && resolvedSentryPath.exists() ? resolvedSentryPath.getAbsolutePath() : "$reactRoot/node_modules/@sentry/react-native" + return sentryPackage +} + /** Compose lookup map of build variants - to - outputs. */ def extractReleasesInfo() { def releases = [:] @@ -276,6 +288,8 @@ def extractReleasesInfo() { static extractBundleTaskArgumentsLegacy(cmdArgs, Project project) { def bundleOutput = null def sourcemapOutput = null + def packagerSourcemapOutput = null + // packagerBundleOutput doesn't exist, because packager output is overwritten by Hermes cmdArgs.eachWithIndex { String arg, int i -> if (arg == "--bundle-output") { @@ -283,6 +297,7 @@ static extractBundleTaskArgumentsLegacy(cmdArgs, Project project) { project.logger.info("--bundle-output: `${bundleOutput}`") } else if (arg == "--sourcemap-output") { sourcemapOutput = cmdArgs[i + 1] + packagerSourcemapOutput = sourcemapOutput project.logger.info("--sourcemap-output param: `${sourcemapOutput}`") } } @@ -314,7 +329,7 @@ static extractBundleTaskArgumentsLegacy(cmdArgs, Project project) { } } - return [bundleOutput, sourcemapOutput] + return [bundleOutput, sourcemapOutput, packagerSourcemapOutput] } /** Extract bundle and sourcemap paths from bundle task props. @@ -331,10 +346,12 @@ static extractBundleTaskArgumentsRN71AndAbove(bundleTask, logger) { def bundleFile = new File(props.jsBundleDir.get().asFile.absolutePath, bundleAssetName) def outputSourceMap = new File(props.jsSourceMapsDir.get().asFile.absolutePath, "${bundleAssetName}.map") + def packagerOutputSourceMap = new File(props.jsIntermediateSourceMapsDir.get().asFile.absolutePath, "${bundleAssetName}.packager.map") logger.info("bundleFile: `${bundleFile}`") logger.info("outputSourceMap: `${outputSourceMap}`") - return [bundleFile, outputSourceMap] + logger.info("packagerOutputSourceMap: `${packagerOutputSourceMap}`") + return [bundleFile, outputSourceMap, packagerOutputSourceMap] } /** Force Bundle task to produce sourcemap files if they are not pre-configured by user yet. */ @@ -345,10 +362,11 @@ def forceSourceMapOutputFromBundleTask(bundleTask) { def shouldCleanUp = false def bundleOutput = null def sourcemapOutput = null + def packagerSourcemapOutput = null - (bundleOutput, sourcemapOutput) = extractBundleTaskArgumentsRN71AndAbove(bundleTask, logger) + (bundleOutput, sourcemapOutput, packagerSourcemapOutput) = extractBundleTaskArgumentsRN71AndAbove(bundleTask, logger) if (bundleOutput == null) { - (bundleOutput, sourcemapOutput) = extractBundleTaskArgumentsLegacy(cmdArgs, project) + (bundleOutput, sourcemapOutput, packagerSourcemapOutput) = extractBundleTaskArgumentsLegacy(cmdArgs, project) } if (sourcemapOutput == null) { @@ -367,7 +385,7 @@ def forceSourceMapOutputFromBundleTask(bundleTask) { project.logger.info("Info: used pre-configured source map files: ${sourcemapOutput}") } - return [shouldCleanUp, bundleOutput, sourcemapOutput] + return [shouldCleanUp, bundleOutput, sourcemapOutput, packagerSourcemapOutput] } /** compose array with one item - current build flavor name */ From 4fa6068eb9f3fd6250a1ac131f2c66dd525f6dd0 Mon Sep 17 00:00:00 2001 From: Krystof Woldrich Date: Thu, 14 Sep 2023 17:21:30 +0200 Subject: [PATCH 07/16] Copy debug id from `debugId` and `debug_id` --- scripts/copy-debugid.js | 19 +++++++++++++++++-- 1 file changed, 17 insertions(+), 2 deletions(-) diff --git a/scripts/copy-debugid.js b/scripts/copy-debugid.js index 8a23ad7a10..e5bbbf1153 100644 --- a/scripts/copy-debugid.js +++ b/scripts/copy-debugid.js @@ -29,8 +29,23 @@ const to = fs.readFileSync(process.argv[3], 'utf8'); const fromParsed = JSON.parse(from); const toParsed = JSON.parse(to); -toParsed.debugId = fromParsed.debugId; -toParsed.debug_id = fromParsed.debug_id; +if (!fromParsed.debugId && !fromParsed.debug_id) { + console.error('Packager source map does not have `debugId`.'); + process.exit(0); +} + +if (toParsed.debugId || toParsed.debug_id) { + console.log('Hermes combined source map already has `debugId`.'); + process.exit(0); +} + +if (fromParsed.debugId) { + toParsed.debugId = fromParsed.debugId; + toParsed.debug_id = fromParsed.debugId; +} else if (fromParsed.debug_id) { + toParsed.debugId = fromParsed.debug_id; + toParsed.debug_id = fromParsed.debug_id; +} fs.writeFileSync(process.argv[3], JSON.stringify(toParsed)); From 7f5514db6d7fc0a38d79bbd0205870e3d8121503 Mon Sep 17 00:00:00 2001 From: Krystof Woldrich Date: Tue, 19 Sep 2023 14:45:28 +0200 Subject: [PATCH 08/16] Add sentry metro serializer to the SDK project --- package.json | 5 +- sample-new-architecture/metro.config.js | 112 +---------------- src/js/tools/sentryMetroSerializer.ts | 160 ++++++++++++++++++++++++ tsconfig.build.tools.json | 3 +- typings/metro.d.ts | 46 +++++++ 5 files changed, 216 insertions(+), 110 deletions(-) create mode 100644 src/js/tools/sentryMetroSerializer.ts create mode 100644 typings/metro.d.ts diff --git a/package.json b/package.json index 1d97e4e9f3..d465dab151 100644 --- a/package.json +++ b/package.json @@ -72,6 +72,7 @@ "@sentry/wizard": "3.13.0", "@types/jest": "^29.5.3", "@types/react": "^18.2.14", + "@types/uuid": "^9.0.4", "babel-jest": "^29.6.2", "downlevel-dts": "^0.11.0", "eslint": "^7.6.0", @@ -79,13 +80,15 @@ "eslint-plugin-react-native": "^3.8.1", "jest": "^29.6.2", "jest-environment-jsdom": "^29.6.2", + "metro": "0.76", "prettier": "^2.0.5", "react": "18.2.0", "react-native": "0.72.4", "replace-in-file": "^7.0.1", "rimraf": "^4.1.1", "ts-jest": "^29.1.1", - "typescript": "4.9.5" + "typescript": "4.9.5", + "uuid": "^9.0.1" }, "rnpm": { "commands": {}, diff --git a/sample-new-architecture/metro.config.js b/sample-new-architecture/metro.config.js index 8ad24c9b0f..48331da62a 100644 --- a/sample-new-architecture/metro.config.js +++ b/sample-new-architecture/metro.config.js @@ -1,43 +1,12 @@ const { getDefaultConfig, mergeConfig } = require('@react-native/metro-config'); const path = require('path'); const blacklist = require('metro-config/src/defaults/exclusionList'); -const bundleToString = require('metro/src/lib/bundleToString'); -const baseJSBundle = require('metro/src/DeltaBundler/Serializers/baseJSBundle'); -const sourceMapString = require('metro/src/DeltaBundler/Serializers/sourceMapString'); -const CountingSet = require('metro/src/lib/CountingSet').default; -const countLines = require('metro/src/lib/countLines'); -const uuidv4 = require('uuid').v4; +const { + createSentryMetroSerializer, +} = require('../dist/js/tools/sentryMetroSerializer'); const parentDir = path.resolve(__dirname, '..'); -function getDebugIdSnippet(debugId) { - return `var _sentryDebugIds={},_sentryDebugIdIdentifier="";try{var e=Error().stack;e&&(_sentryDebugIds[e]="${debugId}",_sentryDebugIdIdentifier="sentry-dbid-${debugId}")}catch(r){}`; -} - -const debugId = uuidv4(); -const debugIdCode = getDebugIdSnippet(debugId); -const debugIdModule = { - dependencies: new Map(), - getSource: () => Buffer.from(debugIdCode), - inverseDependencies: new CountingSet(), - path: '__debugid__', - output: [ - { - type: 'js/script/virtual', - data: { - code: debugIdCode, - lineCount: countLines(debugIdCode), - map: [], - }, - }, - ], -}; -const defaultSerializer = (entryPoint, preModules, graph, options) => - bundleToString(baseJSBundle(entryPoint, preModules, graph, options)).code; -const PRELUDE_MODULE_PATH = '__prelude__'; -const SOURCE_MAP_COMMENT = '//# sourceMappingURL='; -const DEBUG_ID_COMMENT = '//# debugId='; - /** * Metro configuration * https://facebook.github.io/metro/docs/configuration @@ -76,82 +45,9 @@ const config = { ), }, serializer: { - customSerializer: function (entryPoint, preModules, graph, options) { - const createModuleId = options.createModuleId; - const getSortedModules = () => { - const modules = [...graph.dependencies.values()]; - // Assign IDs to modules in a consistent order - for (const module of modules) { - createModuleId(module.path); - } - // Sort by IDs - return modules.sort( - (a, b) => createModuleId(a.path) - createModuleId(b.path), - ); - }; - - // TODO: - // 1. Deterministically order all the modules (besides assets) preModules and graph dependencies - // 2. Generate Debug ID using https://github.com/getsentry/sentry-javascript-bundler-plugins/blob/36bf09880f983d562d9179cbbeac40f7083be0ff/packages/bundler-plugin-core/src/utils.ts#L174 - - // TODO: Only add debug id if it's not already present - if (preModules[0].path === PRELUDE_MODULE_PATH) { - // prelude module must be first as it measures the bundle startup time - preModules.unshift(preModules[0]); - preModules[1] = debugIdModule; - } else { - preModules.unshift(debugIdModule); - } - - const bundleCode = defaultSerializer( - entryPoint, - preModules, - graph, - options, - ); - - // Add debug id comment to the bundle - const debugIdComment = `${DEBUG_ID_COMMENT}${debugId}`; - const indexOfSourceMapComment = - bundleCode.lastIndexOf(SOURCE_MAP_COMMENT); - const bundleCodeWithDebugId = - indexOfSourceMapComment === -1 - ? // If source map comment is missing lets just add the debug id comment - bundleCode + '\n' + debugIdComment - : // If source map comment is present lets add the debug id comment before it - bundleCode.substring(0, indexOfSourceMapComment) + - debugIdComment + - '\n' + - bundleCode.substring(indexOfSourceMapComment); - - if (this.processModuleFilter === undefined) { - // processModuleFilter is undefined when processing build request from the dev server - return bundleCodeWithDebugId; - } - - // Generate bundle map - const bundleMapString = sourceMapString( - [...preModules, ...getSortedModules(graph)], - { - processModuleFilter: this.processModuleFilter, - shouldAddToIgnoreList: options.shouldAddToIgnoreList, - }, - ); - const bundleMap = JSON.parse(bundleMapString); - // For now we write both fields until we know what will become the standard - if ever. - bundleMap['debug_id'] = debugId; - bundleMap['debugId'] = debugId; - - return { - code: bundleCodeWithDebugId, - map: JSON.stringify(bundleMap), - }; - }, + customSerializer: createSentryMetroSerializer(), }, }; const m = mergeConfig(getDefaultConfig(__dirname), config); -// m.transformer.getTransformOptions().then(opts => { -// console.log(opts); -// }); module.exports = m; diff --git a/src/js/tools/sentryMetroSerializer.ts b/src/js/tools/sentryMetroSerializer.ts new file mode 100644 index 0000000000..9857f0ce0c --- /dev/null +++ b/src/js/tools/sentryMetroSerializer.ts @@ -0,0 +1,160 @@ +import type { MixedOutput, Module } from 'metro'; +import type { SerializerConfigT } from 'metro-config'; +import * as baseJSBundle from 'metro/src/DeltaBundler/Serializers/baseJSBundle'; +import * as sourceMapString from 'metro/src/DeltaBundler/Serializers/sourceMapString'; +import * as bundleToString from 'metro/src/lib/bundleToString'; +import CountingSet from 'metro/src/lib/CountingSet'; +import * as countLines from 'metro/src/lib/countLines'; +import { v4 as uuidv4 } from 'uuid'; + +type SourceMap = Record; + +type ExpectedSerializedConfigThisContext = Partial + +type MetroSerializer = (...args: Parameters>) + => string | { code: string, map: string } | Promise; + +const getDebugIdSnippet = (debugId: string): string => { + return `var _sentryDebugIds={},_sentryDebugIdIdentifier="";try{var e=Error().stack;e&&(_sentryDebugIds[e]="${debugId}",_sentryDebugIdIdentifier="sentry-dbid-${debugId}")}catch(r){}`; +} + +const debugId = uuidv4(); +const debugIdCode = getDebugIdSnippet(debugId); +const debugIdModule: Module<{ + type: string; + data: { + code: string; + lineCount: number; + map: []; + }; +}> = { + dependencies: new Map(), + getSource: () => Buffer.from(debugIdCode), + inverseDependencies: new CountingSet(), + path: '__debugid__', + output: [ + { + type: 'js/script/virtual', + data: { + code: debugIdCode, + lineCount: countLines(debugIdCode), + map: [], + }, + }, + ], +}; + +export const createDefaultMetroSerializer = ( + serializerConfig: Partial, +): MetroSerializer => { + return (entryPoint, preModules, graph, options) => { + const createModuleId = options.createModuleId; + const getSortedModules = (): Module[] => { + const modules = [...graph.dependencies.values()]; + // Assign IDs to modules in a consistent order + for (const module of modules) { + createModuleId(module.path); + } + // Sort by IDs + return modules.sort( + (a, b) => createModuleId(a.path) - createModuleId(b.path), + ); + }; + + const { code } = bundleToString(baseJSBundle(entryPoint, preModules, graph, options)); + if (serializerConfig.processModuleFilter === undefined) { + // processModuleFilter is undefined when processing build request from the dev server + return code; + } + + // Always generate source maps, can't use Sentry without source maps + const map = sourceMapString( + [...preModules, ...getSortedModules()], + { + processModuleFilter: serializerConfig.processModuleFilter, + shouldAddToIgnoreList: options.shouldAddToIgnoreList, + }, + ); + return { code, map }; + } +}; + +const PRELUDE_MODULE_PATH = '__prelude__'; +const SOURCE_MAP_COMMENT = '//# sourceMappingURL='; +const DEBUG_ID_COMMENT = '//# debugId='; + +export const createSentryMetroSerializer = ( + customSerializer?: MetroSerializer, +): MetroSerializer => { + return async function ( + this: ExpectedSerializedConfigThisContext, + entryPoint, + preModules, + graph, + options) { + // eslint-disable-next-line no-console + console.log('createSentryMetroSerializer', this); + const serializer = customSerializer || createDefaultMetroSerializer(this); + // TODO: + // 1. Deterministically order all the modules (besides assets) preModules and graph dependencies + // 2. Generate Debug ID using https://github.com/getsentry/sentry-javascript-bundler-plugins/blob/36bf09880f983d562d9179cbbeac40f7083be0ff/packages/bundler-plugin-core/src/utils.ts#L174 + // 3. Only when hermes disabled + + // TODO: Only add debug id if it's not already present + const modifiedPreModules: Module[] = [...preModules]; + if (modifiedPreModules[0].path === PRELUDE_MODULE_PATH) { + // prelude module must be first as it measures the bundle startup time + modifiedPreModules.unshift(preModules[0]); + modifiedPreModules[1] = debugIdModule; + } else { + modifiedPreModules.unshift(debugIdModule); + } + + let bundleCode: string = ''; + let bundleMapString: string = '{}'; + + const serializerResult = serializer(entryPoint, preModules, graph, options); + if (typeof serializerResult === 'string') { + bundleCode = serializerResult; + } else if ('map' in serializerResult) { + bundleCode = serializerResult.code; + bundleMapString = serializerResult.map; + } else { + const awaitedResult = await serializerResult; + if (typeof awaitedResult === 'string') { + bundleCode = awaitedResult; + } else { + bundleCode = awaitedResult.code; + bundleMapString = awaitedResult.map; + } + } + + // Add debug id comment to the bundle + const debugIdComment = `${DEBUG_ID_COMMENT}${debugId}`; + const indexOfSourceMapComment = + bundleCode.lastIndexOf(SOURCE_MAP_COMMENT); + const bundleCodeWithDebugId = + indexOfSourceMapComment === -1 + ? // If source map comment is missing lets just add the debug id comment + `${bundleCode}\n${debugIdComment}` + : // If source map comment is present lets add the debug id comment before it + `${bundleCode.substring(0, indexOfSourceMapComment) + + debugIdComment + }\n${bundleCode.substring(indexOfSourceMapComment)}`; + + if (this.processModuleFilter === undefined) { + // processModuleFilter is undefined when processing build request from the dev server + return bundleCodeWithDebugId; + } + + const bundleMap: SourceMap = JSON.parse(bundleMapString); + // For now we write both fields until we know what will become the standard - if ever. + bundleMap['debug_id'] = debugId; + bundleMap['debugId'] = debugId; + + return { + code: bundleCodeWithDebugId, + map: JSON.stringify(bundleMap), + }; + }; +}; diff --git a/tsconfig.build.tools.json b/tsconfig.build.tools.json index 30d5648e41..584b6bc851 100644 --- a/tsconfig.build.tools.json +++ b/tsconfig.build.tools.json @@ -1,7 +1,8 @@ { "extends": "./node_modules/@sentry/typescript/tsconfig.json", "include": [ - "src/js/tools/*.ts" + "src/js/tools/*.ts", + "typings/*.d.ts" ], "exclude": [ "node_modules" diff --git a/typings/metro.d.ts b/typings/metro.d.ts new file mode 100644 index 0000000000..e58637d95f --- /dev/null +++ b/typings/metro.d.ts @@ -0,0 +1,46 @@ +declare module 'metro/src/DeltaBundler/Serializers/baseJSBundle' { + type Bundle = { + modules: ModuleMap, + post: string, + pre: string, + }; + + const baseJSBundle: ( + entryPoint: string, + preModules: ReadonlyArray, + graph: ReadOnlyGraph, + options: SerializerOptions, + ) => Bundle; + export = baseJSBundle; +}; + +declare module 'metro/src/lib/bundleToString' { + type Bundle = { + modules: ModuleMap, + post: string, + pre: string, + }; + + const baseJSBundle: (bundle: Bundle) => { + code: string, + metadata: BundleMetadata, + }; + + export = baseJSBundle; +}; + +declare module 'metro/src/lib/countLines' { + const countLines: (code: string) => number; + export = countLines; +} + +declare module 'metro/src/DeltaBundler/Serializers/sourceMapString' { + import type { MixedOutput, Module } from 'metro'; + + const sourceMapString: (bundle: Module[], options: { + excludeSource?: boolean, + processModuleFilter?: (module: Module) => boolean, + shouldAddToIgnoreList?: (module: Module) => boolean, + }) => string; + export = sourceMapString; +} From b12997fabbcb79ae8471e246fa31a687af1adc76 Mon Sep 17 00:00:00 2001 From: Krystof Woldrich Date: Tue, 19 Sep 2023 15:33:51 +0200 Subject: [PATCH 09/16] fix yarn lock --- yarn.lock | 231 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 231 insertions(+) diff --git a/yarn.lock b/yarn.lock index 636f4444dd..07a58bcaa0 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2343,6 +2343,11 @@ resolved "https://registry.yarnpkg.com/@types/tough-cookie/-/tough-cookie-4.0.2.tgz#6286b4c7228d58ab7866d19716f3696e03a09397" integrity sha512-Q5vtl1W5ue16D+nIaW8JWebSSraJVlK+EthKn7e7UcD4KWsaSJ8BqGPXNaPghgtcn/fhvrN17Tv8ksUsQpiplw== +"@types/uuid@^9.0.4": + version "9.0.4" + resolved "https://registry.yarnpkg.com/@types/uuid/-/uuid-9.0.4.tgz#e884a59338da907bda8d2ed03e01c5c49d036f1c" + integrity sha512-zAuJWQflfx6dYJM62vna+Sn5aeSWhh3OB+wfUEACNcqUSc0AGc5JKl+ycL1vrH7frGTXhJchYjE1Hak8L819dA== + "@types/yargs-parser@*": version "15.0.0" resolved "https://registry.yarnpkg.com/@types/yargs-parser/-/yargs-parser-15.0.0.tgz#cb3f9f741869e20cce330ffbeb9271590483882d" @@ -5951,11 +5956,25 @@ metro-babel-transformer@0.76.7: hermes-parser "0.12.0" nullthrows "^1.1.1" +metro-babel-transformer@0.76.8: + version "0.76.8" + resolved "https://registry.yarnpkg.com/metro-babel-transformer/-/metro-babel-transformer-0.76.8.tgz#5efd1027353b36b73706164ef09c290dceac096a" + integrity sha512-Hh6PW34Ug/nShlBGxkwQJSgPGAzSJ9FwQXhUImkzdsDgVu6zj5bx258J8cJVSandjNoQ8nbaHK6CaHlnbZKbyA== + dependencies: + "@babel/core" "^7.20.0" + hermes-parser "0.12.0" + nullthrows "^1.1.1" + metro-cache-key@0.76.7: version "0.76.7" resolved "https://registry.yarnpkg.com/metro-cache-key/-/metro-cache-key-0.76.7.tgz#70913f43b92b313096673c37532edd07438cb325" integrity sha512-0pecoIzwsD/Whn/Qfa+SDMX2YyasV0ndbcgUFx7w1Ct2sLHClujdhQ4ik6mvQmsaOcnGkIyN0zcceMDjC2+BFQ== +metro-cache-key@0.76.8: + version "0.76.8" + resolved "https://registry.yarnpkg.com/metro-cache-key/-/metro-cache-key-0.76.8.tgz#8a0a5e991c06f56fcc584acadacb313c312bdc16" + integrity sha512-buKQ5xentPig9G6T37Ww/R/bC+/V1MA5xU/D8zjnhlelsrPG6w6LtHUS61ID3zZcMZqYaELWk5UIadIdDsaaLw== + metro-cache@0.76.7: version "0.76.7" resolved "https://registry.yarnpkg.com/metro-cache/-/metro-cache-0.76.7.tgz#e49e51423fa960df4eeff9760d131f03e003a9eb" @@ -5964,6 +5983,14 @@ metro-cache@0.76.7: metro-core "0.76.7" rimraf "^3.0.2" +metro-cache@0.76.8: + version "0.76.8" + resolved "https://registry.yarnpkg.com/metro-cache/-/metro-cache-0.76.8.tgz#296c1c189db2053b89735a8f33dbe82575f53661" + integrity sha512-QBJSJIVNH7Hc/Yo6br/U/qQDUpiUdRgZ2ZBJmvAbmAKp2XDzsapnMwK/3BGj8JNWJF7OLrqrYHsRsukSbUBpvQ== + dependencies: + metro-core "0.76.8" + rimraf "^3.0.2" + metro-config@0.76.7: version "0.76.7" resolved "https://registry.yarnpkg.com/metro-config/-/metro-config-0.76.7.tgz#f0fc171707523aa7d3a9311550872136880558c0" @@ -5977,6 +6004,19 @@ metro-config@0.76.7: metro-core "0.76.7" metro-runtime "0.76.7" +metro-config@0.76.8: + version "0.76.8" + resolved "https://registry.yarnpkg.com/metro-config/-/metro-config-0.76.8.tgz#20bd5397fcc6096f98d2a813a7cecb38b8af062d" + integrity sha512-SL1lfKB0qGHALcAk2zBqVgQZpazDYvYFGwCK1ikz0S6Y/CM2i2/HwuZN31kpX6z3mqjv/6KvlzaKoTb1otuSAA== + dependencies: + connect "^3.6.5" + cosmiconfig "^5.0.5" + jest-validate "^29.2.1" + metro "0.76.8" + metro-cache "0.76.8" + metro-core "0.76.8" + metro-runtime "0.76.8" + metro-core@0.76.7: version "0.76.7" resolved "https://registry.yarnpkg.com/metro-core/-/metro-core-0.76.7.tgz#5d2b8bac2cde801dc22666ad7be1336d1f021b61" @@ -5985,6 +6025,14 @@ metro-core@0.76.7: lodash.throttle "^4.1.1" metro-resolver "0.76.7" +metro-core@0.76.8: + version "0.76.8" + resolved "https://registry.yarnpkg.com/metro-core/-/metro-core-0.76.8.tgz#917c8157c63406cb223522835abb8e7c6291dcad" + integrity sha512-sl2QLFI3d1b1XUUGxwzw/KbaXXU/bvFYrSKz6Sg19AdYGWFyzsgZ1VISRIDf+HWm4R/TJXluhWMEkEtZuqi3qA== + dependencies: + lodash.throttle "^4.1.1" + metro-resolver "0.76.8" + metro-file-map@0.76.7: version "0.76.7" resolved "https://registry.yarnpkg.com/metro-file-map/-/metro-file-map-0.76.7.tgz#0f041a4f186ac672f0188180310609c8483ffe89" @@ -6005,6 +6053,26 @@ metro-file-map@0.76.7: optionalDependencies: fsevents "^2.3.2" +metro-file-map@0.76.8: + version "0.76.8" + resolved "https://registry.yarnpkg.com/metro-file-map/-/metro-file-map-0.76.8.tgz#a1db1185b6c316904ba6b53d628e5d1323991d79" + integrity sha512-A/xP1YNEVwO1SUV9/YYo6/Y1MmzhL4ZnVgcJC3VmHp/BYVOXVStzgVbWv2wILe56IIMkfXU+jpXrGKKYhFyHVw== + dependencies: + anymatch "^3.0.3" + debug "^2.2.0" + fb-watchman "^2.0.0" + graceful-fs "^4.2.4" + invariant "^2.2.4" + jest-regex-util "^27.0.6" + jest-util "^27.2.0" + jest-worker "^27.2.0" + micromatch "^4.0.4" + node-abort-controller "^3.1.1" + nullthrows "^1.1.1" + walker "^1.0.7" + optionalDependencies: + fsevents "^2.3.2" + metro-inspector-proxy@0.76.7: version "0.76.7" resolved "https://registry.yarnpkg.com/metro-inspector-proxy/-/metro-inspector-proxy-0.76.7.tgz#c067df25056e932002a72a4b45cf7b4b749f808e" @@ -6016,6 +6084,17 @@ metro-inspector-proxy@0.76.7: ws "^7.5.1" yargs "^17.6.2" +metro-inspector-proxy@0.76.8: + version "0.76.8" + resolved "https://registry.yarnpkg.com/metro-inspector-proxy/-/metro-inspector-proxy-0.76.8.tgz#6b8678a7461b0b42f913a7881cc9319b4d3cddff" + integrity sha512-Us5o5UEd4Smgn1+TfHX4LvVPoWVo9VsVMn4Ldbk0g5CQx3Gu0ygc/ei2AKPGTwsOZmKxJeACj7yMH2kgxQP/iw== + dependencies: + connect "^3.6.5" + debug "^2.2.0" + node-fetch "^2.2.0" + ws "^7.5.1" + yargs "^17.6.2" + metro-minify-terser@0.76.7: version "0.76.7" resolved "https://registry.yarnpkg.com/metro-minify-terser/-/metro-minify-terser-0.76.7.tgz#aefac8bb8b6b3a0fcb5ea0238623cf3e100893ff" @@ -6023,6 +6102,13 @@ metro-minify-terser@0.76.7: dependencies: terser "^5.15.0" +metro-minify-terser@0.76.8: + version "0.76.8" + resolved "https://registry.yarnpkg.com/metro-minify-terser/-/metro-minify-terser-0.76.8.tgz#915ab4d1419257fc6a0b9fa15827b83fe69814bf" + integrity sha512-Orbvg18qXHCrSj1KbaeSDVYRy/gkro2PC7Fy2tDSH1c9RB4aH8tuMOIXnKJE+1SXxBtjWmQ5Yirwkth2DyyEZA== + dependencies: + terser "^5.15.0" + metro-minify-uglify@0.76.7: version "0.76.7" resolved "https://registry.yarnpkg.com/metro-minify-uglify/-/metro-minify-uglify-0.76.7.tgz#3e0143786718dcaea4e28a724698d4f8ac199a43" @@ -6030,6 +6116,13 @@ metro-minify-uglify@0.76.7: dependencies: uglify-es "^3.1.9" +metro-minify-uglify@0.76.8: + version "0.76.8" + resolved "https://registry.yarnpkg.com/metro-minify-uglify/-/metro-minify-uglify-0.76.8.tgz#74745045ea2dd29f8783db483b2fce58385ba695" + integrity sha512-6l8/bEvtVaTSuhG1FqS0+Mc8lZ3Bl4RI8SeRIifVLC21eeSDp4CEBUWSGjpFyUDfi6R5dXzYaFnSgMNyfxADiQ== + dependencies: + uglify-es "^3.1.9" + metro-react-native-babel-preset@0.76.7: version "0.76.7" resolved "https://registry.yarnpkg.com/metro-react-native-babel-preset/-/metro-react-native-babel-preset-0.76.7.tgz#dfe15c040d0918147a8b0e9f530d558287acbb54" @@ -6075,6 +6168,51 @@ metro-react-native-babel-preset@0.76.7: babel-plugin-transform-flow-enums "^0.0.2" react-refresh "^0.4.0" +metro-react-native-babel-preset@0.76.8: + version "0.76.8" + resolved "https://registry.yarnpkg.com/metro-react-native-babel-preset/-/metro-react-native-babel-preset-0.76.8.tgz#7476efae14363cbdfeeec403b4f01d7348e6c048" + integrity sha512-Ptza08GgqzxEdK8apYsjTx2S8WDUlS2ilBlu9DR1CUcHmg4g3kOkFylZroogVAUKtpYQNYwAvdsjmrSdDNtiAg== + dependencies: + "@babel/core" "^7.20.0" + "@babel/plugin-proposal-async-generator-functions" "^7.0.0" + "@babel/plugin-proposal-class-properties" "^7.18.0" + "@babel/plugin-proposal-export-default-from" "^7.0.0" + "@babel/plugin-proposal-nullish-coalescing-operator" "^7.18.0" + "@babel/plugin-proposal-numeric-separator" "^7.0.0" + "@babel/plugin-proposal-object-rest-spread" "^7.20.0" + "@babel/plugin-proposal-optional-catch-binding" "^7.0.0" + "@babel/plugin-proposal-optional-chaining" "^7.20.0" + "@babel/plugin-syntax-dynamic-import" "^7.8.0" + "@babel/plugin-syntax-export-default-from" "^7.0.0" + "@babel/plugin-syntax-flow" "^7.18.0" + "@babel/plugin-syntax-nullish-coalescing-operator" "^7.0.0" + "@babel/plugin-syntax-optional-chaining" "^7.0.0" + "@babel/plugin-transform-arrow-functions" "^7.0.0" + "@babel/plugin-transform-async-to-generator" "^7.20.0" + "@babel/plugin-transform-block-scoping" "^7.0.0" + "@babel/plugin-transform-classes" "^7.0.0" + "@babel/plugin-transform-computed-properties" "^7.0.0" + "@babel/plugin-transform-destructuring" "^7.20.0" + "@babel/plugin-transform-flow-strip-types" "^7.20.0" + "@babel/plugin-transform-function-name" "^7.0.0" + "@babel/plugin-transform-literals" "^7.0.0" + "@babel/plugin-transform-modules-commonjs" "^7.0.0" + "@babel/plugin-transform-named-capturing-groups-regex" "^7.0.0" + "@babel/plugin-transform-parameters" "^7.0.0" + "@babel/plugin-transform-react-display-name" "^7.0.0" + "@babel/plugin-transform-react-jsx" "^7.0.0" + "@babel/plugin-transform-react-jsx-self" "^7.0.0" + "@babel/plugin-transform-react-jsx-source" "^7.0.0" + "@babel/plugin-transform-runtime" "^7.0.0" + "@babel/plugin-transform-shorthand-properties" "^7.0.0" + "@babel/plugin-transform-spread" "^7.0.0" + "@babel/plugin-transform-sticky-regex" "^7.0.0" + "@babel/plugin-transform-typescript" "^7.5.0" + "@babel/plugin-transform-unicode-regex" "^7.0.0" + "@babel/template" "^7.0.0" + babel-plugin-transform-flow-enums "^0.0.2" + react-refresh "^0.4.0" + metro-react-native-babel-transformer@0.76.7: version "0.76.7" resolved "https://registry.yarnpkg.com/metro-react-native-babel-transformer/-/metro-react-native-babel-transformer-0.76.7.tgz#ccc7c25b49ee8a1860aafdbf48bfa5441d206f8f" @@ -6091,6 +6229,11 @@ metro-resolver@0.76.7: resolved "https://registry.yarnpkg.com/metro-resolver/-/metro-resolver-0.76.7.tgz#f00ebead64e451c060f30926ecbf4f797588df52" integrity sha512-pC0Wgq29HHIHrwz23xxiNgylhI8Rq1V01kQaJ9Kz11zWrIdlrH0ZdnJ7GC6qA0ErROG+cXmJ0rJb8/SW1Zp2IA== +metro-resolver@0.76.8: + version "0.76.8" + resolved "https://registry.yarnpkg.com/metro-resolver/-/metro-resolver-0.76.8.tgz#0862755b9b84e26853978322464fb37c6fdad76d" + integrity sha512-KccOqc10vrzS7ZhG2NSnL2dh3uVydarB7nOhjreQ7C4zyWuiW9XpLC4h47KtGQv3Rnv/NDLJYeDqaJ4/+140HQ== + metro-runtime@0.76.7: version "0.76.7" resolved "https://registry.yarnpkg.com/metro-runtime/-/metro-runtime-0.76.7.tgz#4d75f2dbbcd19a4f01e0d89494e140b0ba8247e4" @@ -6170,6 +6313,17 @@ metro-transform-plugins@0.76.7: "@babel/traverse" "^7.20.0" nullthrows "^1.1.1" +metro-transform-plugins@0.76.8: + version "0.76.8" + resolved "https://registry.yarnpkg.com/metro-transform-plugins/-/metro-transform-plugins-0.76.8.tgz#d77c28a6547a8e3b72250f740fcfbd7f5408f8ba" + integrity sha512-PlkGTQNqS51Bx4vuufSQCdSn2R2rt7korzngo+b5GCkeX5pjinPjnO2kNhQ8l+5bO0iUD/WZ9nsM2PGGKIkWFA== + dependencies: + "@babel/core" "^7.20.0" + "@babel/generator" "^7.20.0" + "@babel/template" "^7.0.0" + "@babel/traverse" "^7.20.0" + nullthrows "^1.1.1" + metro-transform-worker@0.76.7: version "0.76.7" resolved "https://registry.yarnpkg.com/metro-transform-worker/-/metro-transform-worker-0.76.7.tgz#b842d5a542f1806cca401633fc002559b3e3d668" @@ -6188,6 +6342,78 @@ metro-transform-worker@0.76.7: metro-transform-plugins "0.76.7" nullthrows "^1.1.1" +metro-transform-worker@0.76.8: + version "0.76.8" + resolved "https://registry.yarnpkg.com/metro-transform-worker/-/metro-transform-worker-0.76.8.tgz#b9012a196cee205170d0c899b8b175b9305acdea" + integrity sha512-mE1fxVAnJKmwwJyDtThildxxos9+DGs9+vTrx2ktSFMEVTtXS/bIv2W6hux1pqivqAfyJpTeACXHk5u2DgGvIQ== + dependencies: + "@babel/core" "^7.20.0" + "@babel/generator" "^7.20.0" + "@babel/parser" "^7.20.0" + "@babel/types" "^7.20.0" + babel-preset-fbjs "^3.4.0" + metro "0.76.8" + metro-babel-transformer "0.76.8" + metro-cache "0.76.8" + metro-cache-key "0.76.8" + metro-source-map "0.76.8" + metro-transform-plugins "0.76.8" + nullthrows "^1.1.1" + +metro@0.76, metro@0.76.8: + version "0.76.8" + resolved "https://registry.yarnpkg.com/metro/-/metro-0.76.8.tgz#ba526808b99977ca3f9ac5a7432fd02a340d13a6" + integrity sha512-oQA3gLzrrYv3qKtuWArMgHPbHu8odZOD9AoavrqSFllkPgOtmkBvNNDLCELqv5SjBfqjISNffypg+5UGG3y0pg== + dependencies: + "@babel/code-frame" "^7.0.0" + "@babel/core" "^7.20.0" + "@babel/generator" "^7.20.0" + "@babel/parser" "^7.20.0" + "@babel/template" "^7.0.0" + "@babel/traverse" "^7.20.0" + "@babel/types" "^7.20.0" + accepts "^1.3.7" + async "^3.2.2" + chalk "^4.0.0" + ci-info "^2.0.0" + connect "^3.6.5" + debug "^2.2.0" + denodeify "^1.2.1" + error-stack-parser "^2.0.6" + graceful-fs "^4.2.4" + hermes-parser "0.12.0" + image-size "^1.0.2" + invariant "^2.2.4" + jest-worker "^27.2.0" + jsc-safe-url "^0.2.2" + lodash.throttle "^4.1.1" + metro-babel-transformer "0.76.8" + metro-cache "0.76.8" + metro-cache-key "0.76.8" + metro-config "0.76.8" + metro-core "0.76.8" + metro-file-map "0.76.8" + metro-inspector-proxy "0.76.8" + metro-minify-terser "0.76.8" + metro-minify-uglify "0.76.8" + metro-react-native-babel-preset "0.76.8" + metro-resolver "0.76.8" + metro-runtime "0.76.8" + metro-source-map "0.76.8" + metro-symbolicate "0.76.8" + metro-transform-plugins "0.76.8" + metro-transform-worker "0.76.8" + mime-types "^2.1.27" + node-fetch "^2.2.0" + nullthrows "^1.1.1" + rimraf "^3.0.2" + serialize-error "^2.1.0" + source-map "^0.5.6" + strip-ansi "^6.0.0" + throat "^5.0.0" + ws "^7.5.1" + yargs "^17.6.2" + metro@0.76.7: version "0.76.7" resolved "https://registry.yarnpkg.com/metro/-/metro-0.76.7.tgz#4885917ad28738c7d1e556630e0155f687336230" @@ -8289,6 +8515,11 @@ uuid@^7.0.3: resolved "https://registry.yarnpkg.com/uuid/-/uuid-7.0.3.tgz#c5c9f2c8cf25dc0a372c4df1441c41f5bd0c680b" integrity sha512-DPSke0pXhTZgoF/d+WSt2QaKMCFSfx7QegxEWT+JOuHF5aWrKEn0G+ztjuJg/gG8/ItK+rbPCD/yNv8yyih6Cg== +uuid@^9.0.1: + version "9.0.1" + resolved "https://registry.yarnpkg.com/uuid/-/uuid-9.0.1.tgz#e188d4c8853cc722220392c424cd637f32293f30" + integrity sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA== + v8-compile-cache@^2.0.3: version "2.1.1" resolved "https://registry.yarnpkg.com/v8-compile-cache/-/v8-compile-cache-2.1.1.tgz#54bc3cdd43317bca91e35dcaf305b1a7237de745" From 3bb9a532d034d9c3c0e955c6aaa5ef6d65a36fec Mon Sep 17 00:00:00 2001 From: Krystof Woldrich Date: Tue, 19 Sep 2023 15:34:10 +0200 Subject: [PATCH 10/16] Exclude metro plugin types from react native sdk build --- tsconfig.build.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tsconfig.build.json b/tsconfig.build.json index 4bbaba5ee1..ede2570695 100644 --- a/tsconfig.build.json +++ b/tsconfig.build.json @@ -2,7 +2,7 @@ "extends": "./node_modules/@sentry/typescript/tsconfig.json", "include": [ "src/js/*.ts", - "typings/*.d.ts" + "typings/react-native.d.ts" ], "exclude": [ "node_modules" From ec34509bb431c582d3222432e84fb9c89b9a3709 Mon Sep 17 00:00:00 2001 From: Krystof Woldrich Date: Tue, 19 Sep 2023 17:59:19 +0200 Subject: [PATCH 11/16] Add deterministic debug id generator --- src/js/tools/sentryMetroSerializer.ts | 153 ++++++++++++++++++-------- 1 file changed, 108 insertions(+), 45 deletions(-) diff --git a/src/js/tools/sentryMetroSerializer.ts b/src/js/tools/sentryMetroSerializer.ts index 9857f0ce0c..29f7e33a63 100644 --- a/src/js/tools/sentryMetroSerializer.ts +++ b/src/js/tools/sentryMetroSerializer.ts @@ -1,3 +1,4 @@ +import * as crypto from 'crypto'; import type { MixedOutput, Module } from 'metro'; import type { SerializerConfigT } from 'metro-config'; import * as baseJSBundle from 'metro/src/DeltaBundler/Serializers/baseJSBundle'; @@ -5,7 +6,6 @@ import * as sourceMapString from 'metro/src/DeltaBundler/Serializers/sourceMapSt import * as bundleToString from 'metro/src/lib/bundleToString'; import CountingSet from 'metro/src/lib/CountingSet'; import * as countLines from 'metro/src/lib/countLines'; -import { v4 as uuidv4 } from 'uuid'; type SourceMap = Record; @@ -14,36 +14,98 @@ type ExpectedSerializedConfigThisContext = Partial type MetroSerializer = (...args: Parameters>) => string | { code: string, map: string } | Promise; -const getDebugIdSnippet = (debugId: string): string => { +type MetroSerializerOptions = Parameters[3]; + +const DEBUG_ID_MODULE_PATH = '__debugid__'; +const PRELUDE_MODULE_PATH = '__prelude__'; +const SOURCE_MAP_COMMENT = '//# sourceMappingURL='; +const DEBUG_ID_COMMENT = '//# debugId='; + +const createDebugIdSnippet = (debugId: string): string => { return `var _sentryDebugIds={},_sentryDebugIdIdentifier="";try{var e=Error().stack;e&&(_sentryDebugIds[e]="${debugId}",_sentryDebugIdIdentifier="sentry-dbid-${debugId}")}catch(r){}`; } -const debugId = uuidv4(); -const debugIdCode = getDebugIdSnippet(debugId); -const debugIdModule: Module<{ +const createDebugIdModule = (debugId: string): Module<{ type: string; data: { code: string; lineCount: number; map: []; }; -}> = { - dependencies: new Map(), - getSource: () => Buffer.from(debugIdCode), - inverseDependencies: new CountingSet(), - path: '__debugid__', - output: [ - { - type: 'js/script/virtual', - data: { - code: debugIdCode, - lineCount: countLines(debugIdCode), - map: [], +}> => { + const debugIdCode = createDebugIdSnippet(debugId); + + return { + dependencies: new Map(), + getSource: () => Buffer.from(debugIdCode), + inverseDependencies: new CountingSet(), + path: DEBUG_ID_MODULE_PATH, + output: [ + { + type: 'js/script/virtual', + data: { + code: debugIdCode, + lineCount: countLines(debugIdCode), + map: [], + }, }, - }, - ], + ], + }; }; +/** + * Deterministically hashes a string and turns the hash into a uuid. + * https://github.com/getsentry/sentry-javascript-bundler-plugins/blob/58271f1af2ade6b3e64d393d70376ae53bc5bd2f/packages/bundler-plugin-core/src/utils.ts#L174 + */ +export function stringToUUID(str: string): string { + const md5sum = crypto.createHash('md5'); + md5sum.update(str); + const md5Hash = md5sum.digest('hex'); + + // Position 16 is fixed to either 8, 9, a, or b in the uuid v4 spec (10xx in binary) + // RFC 4122 section 4.4 + const v4variant = ['8', '9', 'a', 'b'][md5Hash.substring(16, 17).charCodeAt(0) % 4] as string; + + return ( + `${md5Hash.substring(0, 8) + }-${ + md5Hash.substring(8, 12) + }-4${ + md5Hash.substring(13, 16) + }-${ + v4variant + }${md5Hash.substring(17, 20) + }-${ + md5Hash.substring(20)}` + ).toLowerCase(); +} + +const calculateDebugId = (modules: readonly Module[], serializerOptions: MetroSerializerOptions): string => { + const createModuleId = serializerOptions.createModuleId; + const sortedModules = [...modules].sort((a, b) => createModuleId(a.path) - createModuleId(b.path)); + + const hash = crypto.createHash('md5'); + for (const module of sortedModules) { + for (const output of module.output) { + if (!output.type.startsWith('js/script')) { + continue; + } + + const code = output.data.code; + if (typeof code === 'string' && code.length > 0) { + hash + .update('\0', 'utf8') + .update(code, 'utf8'); + } + } + } + + const debugId = stringToUUID(hash.digest('hex')); + // eslint-disable-next-line no-console + console.log('info ' + `Bundle Debug ID: ${debugId}`); + return debugId; +} + export const createDefaultMetroSerializer = ( serializerConfig: Partial, ): MetroSerializer => { @@ -79,41 +141,42 @@ export const createDefaultMetroSerializer = ( } }; -const PRELUDE_MODULE_PATH = '__prelude__'; -const SOURCE_MAP_COMMENT = '//# sourceMappingURL='; -const DEBUG_ID_COMMENT = '//# debugId='; - export const createSentryMetroSerializer = ( customSerializer?: MetroSerializer, -): MetroSerializer => { - return async function ( - this: ExpectedSerializedConfigThisContext, - entryPoint, - preModules, - graph, - options) { - // eslint-disable-next-line no-console - console.log('createSentryMetroSerializer', this); + ): MetroSerializer => { + return async function ( + this: ExpectedSerializedConfigThisContext, + entryPoint, + preModules, + graph, + options) { + // TODO: Exclude Debug ID Module for Hermes builds with SDK capable reading Hermes Bytecode Hash const serializer = customSerializer || createDefaultMetroSerializer(this); - // TODO: - // 1. Deterministically order all the modules (besides assets) preModules and graph dependencies - // 2. Generate Debug ID using https://github.com/getsentry/sentry-javascript-bundler-plugins/blob/36bf09880f983d562d9179cbbeac40f7083be0ff/packages/bundler-plugin-core/src/utils.ts#L174 - // 3. Only when hermes disabled - - // TODO: Only add debug id if it's not already present - const modifiedPreModules: Module[] = [...preModules]; - if (modifiedPreModules[0].path === PRELUDE_MODULE_PATH) { - // prelude module must be first as it measures the bundle startup time - modifiedPreModules.unshift(preModules[0]); - modifiedPreModules[1] = debugIdModule; + const debugId = calculateDebugId([...preModules, ...graph.dependencies.values()], options); + + // Add debug id module to the preModules + let modifiedPreModules: readonly Module[]; + const containsDebugIdModule = preModules.some((module) => module.path === DEBUG_ID_MODULE_PATH); + if (!containsDebugIdModule) { + const debugIdModule = createDebugIdModule(debugId); + const tmpPreModules = [...preModules]; + if (tmpPreModules[0].path === PRELUDE_MODULE_PATH) { + // prelude module must be first as it measures the bundle startup time + tmpPreModules.unshift(preModules[0]); + tmpPreModules[1] = debugIdModule; + } else { + tmpPreModules.unshift(debugIdModule); + } + modifiedPreModules = tmpPreModules; } else { - modifiedPreModules.unshift(debugIdModule); + modifiedPreModules = preModules; } let bundleCode: string = ''; let bundleMapString: string = '{}'; - const serializerResult = serializer(entryPoint, preModules, graph, options); + // Run wrapped serializer + const serializerResult = serializer(entryPoint, modifiedPreModules, graph, options); if (typeof serializerResult === 'string') { bundleCode = serializerResult; } else if ('map' in serializerResult) { From 157cd4206bfae86b0538fa9a932f0527f3ffcf94 Mon Sep 17 00:00:00 2001 From: Krystof Woldrich Date: Wed, 20 Sep 2023 11:42:45 +0200 Subject: [PATCH 12/16] Add sentry bundle callback and js docs --- src/js/tools/sentryMetroSerializer.ts | 142 +++++++++++++++++--------- typings/metro.d.ts | 26 +++-- 2 files changed, 108 insertions(+), 60 deletions(-) diff --git a/src/js/tools/sentryMetroSerializer.ts b/src/js/tools/sentryMetroSerializer.ts index 29f7e33a63..865283d4be 100644 --- a/src/js/tools/sentryMetroSerializer.ts +++ b/src/js/tools/sentryMetroSerializer.ts @@ -1,5 +1,5 @@ import * as crypto from 'crypto'; -import type { MixedOutput, Module } from 'metro'; +import type { MixedOutput, Module, ReadOnlyGraph, SerializerOptions } from 'metro'; import type { SerializerConfigT } from 'metro-config'; import * as baseJSBundle from 'metro/src/DeltaBundler/Serializers/baseJSBundle'; import * as sourceMapString from 'metro/src/DeltaBundler/Serializers/sourceMapString'; @@ -11,11 +11,23 @@ type SourceMap = Record; type ExpectedSerializedConfigThisContext = Partial -type MetroSerializer = (...args: Parameters>) - => string | { code: string, map: string } | Promise; +type MetroSerializer = ( + entryPoint: string, + preModules: ReadonlyArray, + graph: ReadOnlyGraph, + options: SerializerOptions & { sentryBundleCallback: (bundle: Bundle) => Bundle }, +) => string | { code: string, map: string } | Promise; -type MetroSerializerOptions = Parameters[3]; +type MetroModuleId = number; +type MetroModuleCode = string; +type Bundle = { + modules: [MetroModuleId, MetroModuleCode][], + post: string, + pre: string, +}; + +const DEBUG_ID_PLACE_HOLDER = '__debug_id_place_holder__'; const DEBUG_ID_MODULE_PATH = '__debugid__'; const PRELUDE_MODULE_PATH = '__prelude__'; const SOURCE_MAP_COMMENT = '//# sourceMappingURL='; @@ -57,7 +69,7 @@ const createDebugIdModule = (debugId: string): Module<{ * Deterministically hashes a string and turns the hash into a uuid. * https://github.com/getsentry/sentry-javascript-bundler-plugins/blob/58271f1af2ade6b3e64d393d70376ae53bc5bd2f/packages/bundler-plugin-core/src/utils.ts#L174 */ -export function stringToUUID(str: string): string { +function stringToUUID(str: string): string { const md5sum = crypto.createHash('md5'); md5sum.update(str); const md5Hash = md5sum.digest('hex'); @@ -80,25 +92,13 @@ export function stringToUUID(str: string): string { ).toLowerCase(); } -const calculateDebugId = (modules: readonly Module[], serializerOptions: MetroSerializerOptions): string => { - const createModuleId = serializerOptions.createModuleId; - const sortedModules = [...modules].sort((a, b) => createModuleId(a.path) - createModuleId(b.path)); - +const calculateDebugId = (bundle: Bundle): string => { const hash = crypto.createHash('md5'); - for (const module of sortedModules) { - for (const output of module.output) { - if (!output.type.startsWith('js/script')) { - continue; - } - - const code = output.data.code; - if (typeof code === 'string' && code.length > 0) { - hash - .update('\0', 'utf8') - .update(code, 'utf8'); - } - } + hash.update(bundle.pre); + for (const [, code] of bundle.modules) { + hash.update(code); } + hash.update(bundle.post); const debugId = stringToUUID(hash.digest('hex')); // eslint-disable-next-line no-console @@ -106,24 +106,44 @@ const calculateDebugId = (modules: readonly Module[], serializerOpt return debugId; } -export const createDefaultMetroSerializer = ( +const injectDebugId = (code: string, debugId: string): string => + code.replace(new RegExp(DEBUG_ID_PLACE_HOLDER, 'g'), debugId); + +/** + * This function is expected to be called after serializer creates the final bundle object + * and before the source maps are generated. + * + * It injects a debug ID into the bundle and returns the modified bundle. + * + * Access it via `options.sentryBundleCallback` in your custom serializer. + */ +const sentryBundleCallback = (bundle: Bundle): Bundle => { + const debugId = calculateDebugId(bundle); + bundle.pre = injectDebugId(bundle.pre, debugId); + return bundle; +}; + +/** + * Creates the default Metro plain bundle serializer. + * This is used when the user does not provide a custom serializer. + */ +const createDefaultMetroSerializer = ( serializerConfig: Partial, ): MetroSerializer => { return (entryPoint, preModules, graph, options) => { const createModuleId = options.createModuleId; - const getSortedModules = (): Module[] => { - const modules = [...graph.dependencies.values()]; - // Assign IDs to modules in a consistent order - for (const module of modules) { - createModuleId(module.path); - } - // Sort by IDs - return modules.sort( - (a, b) => createModuleId(a.path) - createModuleId(b.path), - ); - }; + for (const module of graph.dependencies.values()) { + options.createModuleId(module.path); + } + const modules = [...graph.dependencies.values()].sort( + (a, b) => createModuleId(a.path) - createModuleId(b.path), + ); - const { code } = bundleToString(baseJSBundle(entryPoint, preModules, graph, options)); + let bundle = baseJSBundle(entryPoint, preModules, graph, options); + if (options.sentryBundleCallback) { + bundle = options.sentryBundleCallback(bundle); + } + const { code } = bundleToString(bundle); if (serializerConfig.processModuleFilter === undefined) { // processModuleFilter is undefined when processing build request from the dev server return code; @@ -131,7 +151,7 @@ export const createDefaultMetroSerializer = ( // Always generate source maps, can't use Sentry without source maps const map = sourceMapString( - [...preModules, ...getSortedModules()], + [...preModules, ...modules], { processModuleFilter: serializerConfig.processModuleFilter, shouldAddToIgnoreList: options.shouldAddToIgnoreList, @@ -141,24 +161,49 @@ export const createDefaultMetroSerializer = ( } }; +/** + * Looks for a particular string pattern (`sdbid-[debug ID]`) in the bundle + * source and extracts the bundle's debug ID from it. + * + * The string pattern is injected via the debug ID injection snipped. + */ +function determineDebugIdFromBundleSource(code: string): string | undefined { + const match = code.match( + /sentry-dbid-([0-9a-fA-F]{8}\b-[0-9a-fA-F]{4}\b-[0-9a-fA-F]{4}\b-[0-9a-fA-F]{4}\b-[0-9a-fA-F]{12})/ + ); + + if (match) { + return match[1]; + } else { + return undefined; + } +} + +/** + * Creates a Metro serializer that adds Debug ID module to the plain bundle. + * The Debug ID module is a virtual module that provides a debug ID in runtime. + * + * RAM Bundles do not support custom serializers. + */ export const createSentryMetroSerializer = ( customSerializer?: MetroSerializer, - ): MetroSerializer => { - return async function ( - this: ExpectedSerializedConfigThisContext, - entryPoint, - preModules, - graph, - options) { - // TODO: Exclude Debug ID Module for Hermes builds with SDK capable reading Hermes Bytecode Hash +): MetroSerializer => { + return async function ( + this: ExpectedSerializedConfigThisContext, + entryPoint, + preModules, + graph, + options, + ) { + let currentDebugIdModule: Module | undefined; const serializer = customSerializer || createDefaultMetroSerializer(this); - const debugId = calculateDebugId([...preModules, ...graph.dependencies.values()], options); + options.sentryBundleCallback = sentryBundleCallback; // Add debug id module to the preModules let modifiedPreModules: readonly Module[]; const containsDebugIdModule = preModules.some((module) => module.path === DEBUG_ID_MODULE_PATH); if (!containsDebugIdModule) { - const debugIdModule = createDebugIdModule(debugId); + const debugIdModule = createDebugIdModule(DEBUG_ID_PLACE_HOLDER); const tmpPreModules = [...preModules]; if (tmpPreModules[0].path === PRELUDE_MODULE_PATH) { // prelude module must be first as it measures the bundle startup time @@ -193,6 +238,11 @@ export const createSentryMetroSerializer = ( } // Add debug id comment to the bundle + const debugId = determineDebugIdFromBundleSource(bundleCode); + if (!debugId) { + throw new Error('Debug ID was not found in the bundle. Call `options.sentryBundleCallback` if you are using a custom serializer.'); + } + currentDebugIdModule?.output[0].data.code.replace(DEBUG_ID_PLACE_HOLDER, debugId); const debugIdComment = `${DEBUG_ID_COMMENT}${debugId}`; const indexOfSourceMapComment = bundleCode.lastIndexOf(SOURCE_MAP_COMMENT); diff --git a/typings/metro.d.ts b/typings/metro.d.ts index e58637d95f..df5d05f961 100644 --- a/typings/metro.d.ts +++ b/typings/metro.d.ts @@ -1,27 +1,25 @@ declare module 'metro/src/DeltaBundler/Serializers/baseJSBundle' { - type Bundle = { - modules: ModuleMap, - post: string, - pre: string, - }; - const baseJSBundle: ( entryPoint: string, preModules: ReadonlyArray, graph: ReadOnlyGraph, options: SerializerOptions, - ) => Bundle; - export = baseJSBundle; -}; - -declare module 'metro/src/lib/bundleToString' { - type Bundle = { - modules: ModuleMap, + ) => { + modules: [number, string][], post: string, pre: string, }; + export = baseJSBundle; +}; - const baseJSBundle: (bundle: Bundle) => { +declare module 'metro/src/lib/bundleToString' { + const baseJSBundle: ( + bundle: { + modules: [number, string][], + post: string, + pre: string, + }, + ) => { code: string, metadata: BundleMetadata, }; From a9988243fae7556657d1853e6dd74d9b5d6303fc Mon Sep 17 00:00:00 2001 From: Krystof Woldrich Date: Thu, 21 Sep 2023 14:21:37 +0200 Subject: [PATCH 13/16] Add backward compatible Debug ID upload to `sentry.gradle` --- .../android/app/build.gradle | 2 + .../android/gradle.properties | 1 + scripts/copy-debugid.js | 10 +- scripts/hasSourceMapDebugId.js | 31 +++ sentry.gradle | 183 +++++++++++------- src/js/tools/sentryMetroSerializer.ts | 2 + 6 files changed, 151 insertions(+), 78 deletions(-) create mode 100644 scripts/hasSourceMapDebugId.js diff --git a/sample-new-architecture/android/app/build.gradle b/sample-new-architecture/android/app/build.gradle index e66f107efd..3456bae82b 100644 --- a/sample-new-architecture/android/app/build.gradle +++ b/sample-new-architecture/android/app/build.gradle @@ -74,6 +74,8 @@ project.ext.sentryCli = [ "../..", ], skipCollectModules: false, + copyDebugIdScript: "../../../scripts/copy-debugid.js", + hasSourceMapDebugIdScript: "../../../scripts/hasSourceMapDebugId.js", ] apply from: "../../../sentry.gradle" diff --git a/sample-new-architecture/android/gradle.properties b/sample-new-architecture/android/gradle.properties index 885445bfef..7661dfcf3e 100644 --- a/sample-new-architecture/android/gradle.properties +++ b/sample-new-architecture/android/gradle.properties @@ -11,6 +11,7 @@ # The setting is particularly useful for tweaking memory settings. # Default value: -Xmx512m -XX:MaxMetaspaceSize=256m org.gradle.jvmargs=-Xmx2048m -XX:MaxMetaspaceSize=512m +org.gradle.logging.level=lifecycle # When configured, Gradle will run in incubating parallel mode. # This option should only be used with decoupled projects. More details, visit diff --git a/scripts/copy-debugid.js b/scripts/copy-debugid.js index e5bbbf1153..7df5fe105b 100644 --- a/scripts/copy-debugid.js +++ b/scripts/copy-debugid.js @@ -7,19 +7,19 @@ const packagerSourceMapPath = process.argv[2]; const hermesSourceMapPath = process.argv[3]; if (!packagerSourceMapPath) { - console.error('Please provide packager source map path (A path to copy `debugId` from).'); + console.log('Please provide packager source map path (A path to copy `debugId` from).'); process.exit(0); } if (!hermesSourceMapPath) { - console.error('Please provide Hermes source map path. ((A path to copy `debugId` to))'); + console.log('Please provide Hermes source map path. ((A path to copy `debugId` to))'); process.exit(0); } if (!fs.existsSync(packagerSourceMapPath)) { - console.error('Packager source map path (A path to copy `debugId` from).'); + console.log('Packager source map path (A path to copy `debugId` from).'); process.exit(0); } if (!fs.existsSync(hermesSourceMapPath)) { - console.error('Hermes source map not found. ((A path to copy `debugId` to))'); + console.log('Hermes source map not found. ((A path to copy `debugId` to))'); process.exit(0); } @@ -30,7 +30,7 @@ const fromParsed = JSON.parse(from); const toParsed = JSON.parse(to); if (!fromParsed.debugId && !fromParsed.debug_id) { - console.error('Packager source map does not have `debugId`.'); + console.log('Packager source map does not have `debugId`.'); process.exit(0); } diff --git a/scripts/hasSourceMapDebugId.js b/scripts/hasSourceMapDebugId.js new file mode 100644 index 0000000000..2e49ac9245 --- /dev/null +++ b/scripts/hasSourceMapDebugId.js @@ -0,0 +1,31 @@ +const process = require('process'); +const fs = require('fs'); + +const sourceMapPath = process.argv[2]; + +if (!sourceMapPath) { + console.log('Add source map path as first argument of the script.'); + process.exit(1); +} + +if (!fs.existsSync(sourceMapPath)) { + console.log(`${sourceMapPath} does not exist.`); + process.exit(1); +} + +let sourceMap; +try { + sourceMap = JSON.parse(fs.readFileSync(sourceMapPath, 'utf8')); +} catch (e) { + console.log(`${sourceMapPath} is not valid JSON`, e); + process.exist(1); +} + +if (typeof sourceMap.debugId === 'string' && sourceMap.debugId.length > 0) { + console.log(sourceMap.debugId); +} else if (typeof sourceMap.debug_id === 'string' && sourceMap.debug_id.length > 0) { + console.log(sourceMap.debug_id); +} else { + console.log(`${sourceMapPath} does not contain 'debugId' nor 'debug_id'.`); + process.exist(1); +} diff --git a/sentry.gradle b/sentry.gradle index e383b32ff1..216f74b7c9 100644 --- a/sentry.gradle +++ b/sentry.gradle @@ -40,6 +40,7 @@ gradle.projectsEvaluated { def sourcemapOutput def bundleOutput def packagerSourcemapOutput + def bundleCommand def props = bundleTask.getProperties() def reactRoot = props.get("workingDir") if (reactRoot == null) { @@ -48,7 +49,7 @@ gradle.projectsEvaluated { def modulesOutput = "$reactRoot/android/app/src/main/assets/modules.json" def modulesTask = null - (shouldCleanUp, bundleOutput, sourcemapOutput, packagerSourcemapOutput) = forceSourceMapOutputFromBundleTask(bundleTask) + (shouldCleanUp, bundleOutput, sourcemapOutput, packagerSourcemapOutput, bundleCommand) = forceSourceMapOutputFromBundleTask(bundleTask) // Lets leave this here if we need to debug // println bundleTask.properties @@ -89,82 +90,113 @@ gradle.projectsEvaluated { // based on where we're uploading to. def nameCliTask = "${bundleTask.name}_SentryUpload_${releaseName}_${versionCode}" def nameModulesTask = "${bundleTask.name}_SentryCollectModules_${releaseName}_${versionCode}" - def nameCopyDebugIdTask = "${bundleTask.name}_SentryCopyDebugId_${releaseName}_${versionCode}" // If several outputs have the same releaseName and versionCode, we'd do the exact same // upload for each of them. No need to repeat. try { tasks.named(nameCliTask); return } catch (Exception e) {} - def copyDebugIdTask = tasks.create(nameCopyDebugIdTask, Exec) { - def sentryPackage = resolveSentryReactNativeSDKPath(reactRoot) - def osCompatibilityCopyCommand = Os.isFamily(Os.FAMILY_WINDOWS) ? ['cmd', '/c'] : [] - commandLine(*osCompatibilityCopyCommand, 'node', "../../../scripts/copy-debugid.js", packagerSourcemapOutput, sourcemapOutput) - } - /** Upload source map file to the sentry server via CLI call. */ - def cliTask = tasks.create(nameCliTask, Exec) { + def cliTask = tasks.create(nameCliTask) { description = "upload source maps to Sentry" group = 'sentry.io' - workingDir reactRoot + def extraArgs = [] - def propertiesFile = config.sentryProperties - ? config.sentryProperties - : "$reactRoot/android/sentry.properties" - - if (config.flavorAware) { - propertiesFile = "$reactRoot/android/sentry-${variant}.properties" - project.logger.info("For $variant using: $propertiesFile") - } else { - environment("SENTRY_PROPERTIES", propertiesFile) - } - - Properties sentryProps = new Properties() - try { - sentryProps.load(new FileInputStream(propertiesFile)) - } catch (FileNotFoundException e) { - project.logger.info("file not found '$propertiesFile' for '$variant'") + def sentryPackage = resolveSentryReactNativeSDKPath(reactRoot) + def copyDebugIdScript = config.copyDebugIdScript + ? file(config.copyDebugIdScript).getAbsolutePath() + : "$sentryPackage/scripts/copy-debugid.js" + def hasSourceMapDebugIdScript = config.hasSourceMapDebugIdScript + ? file(config.hasSourceMapDebugIdScript).getAbsolutePath() + : "$sentryPackage/scripts/hasSourceMapDebugId.js" + + doFirst { + // Copy Debug ID from packager source map to Hermes composed source map + exec { + def args = ["node", + copyDebugIdScript, + packagerSourcemapOutput, + sourcemapOutput] + def osCompatibilityCopyCommand = Os.isFamily(Os.FAMILY_WINDOWS) ? ['cmd', '/c'] : [] + commandLine(*osCompatibilityCopyCommand, *args) + } + + // Add release and dist for backward compatibility if no Debug ID detected in output soruce map + def process = ["node", hasSourceMapDebugIdScript, sourcemapOutput].execute(null, new File("$reactRoot")) + project.logger.lifecycle("Check generated source map for Debug ID: ${process.text}") + def notIncludeRelease = "$bundleCommand" == "bundle" && process.exitValue() == 0 + def not = notIncludeRelease ? 'not ' : '' + project.logger.lifecycle("Sentry Source Maps upload will ${not}include the release name and dist.") + extraArgs.addAll(notIncludeRelease ? [] : [ + "--release", releaseName, + "--dist", versionCode + ]) } - def resolvedCliPackage = null - try { - resolvedCliPackage = new File(["node", "--print", "require.resolve('@sentry/cli/package.json')"].execute(null, rootDir).text.trim()).getParentFile(); - } catch (Throwable ignored) {} - def cliPackage = resolvedCliPackage != null && resolvedCliPackage.exists() ? resolvedCliPackage.getAbsolutePath() : "$reactRoot/node_modules/@sentry/cli" - def cliExecutable = sentryProps.get("cli.executable", "$cliPackage/bin/sentry-cli") - - // fix path separator for Windows - if (Os.isFamily(Os.FAMILY_WINDOWS)) { - cliExecutable = cliExecutable.replaceAll("/", "\\\\") + doLast { + exec { + workingDir reactRoot + + def propertiesFile = config.sentryProperties + ? config.sentryProperties + : "$reactRoot/android/sentry.properties" + + if (config.flavorAware) { + propertiesFile = "$reactRoot/android/sentry-${variant}.properties" + project.logger.info("For $variant using: $propertiesFile") + } else { + environment("SENTRY_PROPERTIES", propertiesFile) + } + + Properties sentryProps = new Properties() + try { + sentryProps.load(new FileInputStream(propertiesFile)) + } catch (FileNotFoundException e) { + project.logger.info("file not found '$propertiesFile' for '$variant'") + } + + def resolvedCliPackage = null + try { + resolvedCliPackage = new File(["node", "--print", "require.resolve('@sentry/cli/package.json')"].execute(null, rootDir).text.trim()).getParentFile(); + } catch (Throwable ignored) {} + def cliPackage = resolvedCliPackage != null && resolvedCliPackage.exists() ? resolvedCliPackage.getAbsolutePath() : "$reactRoot/node_modules/@sentry/cli" + def cliExecutable = sentryProps.get("cli.executable", "$cliPackage/bin/sentry-cli") + + // fix path separator for Windows + if (Os.isFamily(Os.FAMILY_WINDOWS)) { + cliExecutable = cliExecutable.replaceAll("/", "\\\\") + } + + // + // based on: + // https://github.com/getsentry/sentry-cli/blob/master/src/commands/react_native_gradle.rs + // + def args = [cliExecutable] + + args.addAll(!config.logLevel ? [] : [ + "--log-level", config.logLevel // control verbosity of the output + ]) + args.addAll(!config.flavorAware ? [] : [ + "--url", sentryProps.get("defaults.url"), + "--auth-token", sentryProps.get("auth.token") + ]) + args.addAll(["react-native", "gradle", + "--bundle", bundleOutput, // The path to a bundle that should be uploaded. + "--sourcemap", sourcemapOutput // The path to a sourcemap that should be uploaded. + ]) + args.addAll(!config.flavorAware ? [] : [ + "--org", sentryProps.get("defaults.org"), + "--project", sentryProps.get("defaults.project") + ]) + + args.addAll(extraArgs) + + project.logger.lifecycle("Sentry-CLI arguments: ${args}") + def osCompatibility = Os.isFamily(Os.FAMILY_WINDOWS) ? ['cmd', '/c', 'node'] : [] + commandLine(*osCompatibility, *args) + } } - // - // based on: - // https://github.com/getsentry/sentry-cli/blob/master/src/commands/react_native_gradle.rs - // - def args = [cliExecutable] - - args.addAll(!config.logLevel ? [] : [ - "--log-level", config.logLevel // control verbosity of the output - ]) - args.addAll(!config.flavorAware ? [] : [ - "--url", sentryProps.get("defaults.url"), - "--auth-token", sentryProps.get("auth.token") - ]) - args.addAll(["react-native", "gradle", - "--bundle", bundleOutput, // The path to a bundle that should be uploaded. - "--sourcemap", sourcemapOutput // The path to a sourcemap that should be uploaded. - ]) - args.addAll(!config.flavorAware ? [] : [ - "--org", sentryProps.get("defaults.org"), - "--project", sentryProps.get("defaults.project") - ]) - - project.logger.info("Sentry-CLI arguments: ${args}") - - def osCompatibility = Os.isFamily(Os.FAMILY_WINDOWS) ? ['cmd', '/c', 'node'] : [] - commandLine(*osCompatibility, *args) - enabled true } @@ -174,7 +206,7 @@ gradle.projectsEvaluated { workingDir reactRoot - def sentryPackage = resolveSentryReactNativeSDKPath() + def sentryPackage = resolveSentryReactNativeSDKPath(reactRoot) def collectModulesScript = config.collectModulesScript ? file(config.collectModulesScript).getAbsolutePath() @@ -206,12 +238,11 @@ gradle.projectsEvaluated { // chain the upload tasks so they run sequentially in order to run // the cliCleanUpTask after the final upload task is run if (previousCliTask != null) { - previousCliTask.finalizedBy copyDebugIdTask + previousCliTask.finalizedBy cliTask } else { - bundleTask.finalizedBy copyDebugIdTask + bundleTask.finalizedBy cliTask } - previousCliTask = copyDebugIdTask - copyDebugIdTask.finalizedBy cliTask + previousCliTask = cliTask cliTask.finalizedBy modulesTask } @@ -328,7 +359,11 @@ static extractBundleTaskArgumentsLegacy(cmdArgs, Project project) { } } - return [bundleOutput, sourcemapOutput, packagerSourcemapOutput] + // get the current bundle command, if not peresent use default plain "bundle" + // we use this later to decide how to upload source maps + def bundleCommand = project.ext.react.get("bundleCommand", "bundle") + + return [bundleOutput, sourcemapOutput, packagerSourcemapOutput, bundleCommand] } /** Extract bundle and sourcemap paths from bundle task props. @@ -343,6 +378,7 @@ static extractBundleTaskArgumentsRN71AndAbove(bundleTask, logger) { return [null, null] } + def bundleCommand = props.bundleCommand.get() def bundleFile = new File(props.jsBundleDir.get().asFile.absolutePath, bundleAssetName) def outputSourceMap = new File(props.jsSourceMapsDir.get().asFile.absolutePath, "${bundleAssetName}.map") def packagerOutputSourceMap = new File(props.jsIntermediateSourceMapsDir.get().asFile.absolutePath, "${bundleAssetName}.packager.map") @@ -350,7 +386,7 @@ static extractBundleTaskArgumentsRN71AndAbove(bundleTask, logger) { logger.info("bundleFile: `${bundleFile}`") logger.info("outputSourceMap: `${outputSourceMap}`") logger.info("packagerOutputSourceMap: `${packagerOutputSourceMap}`") - return [bundleFile, outputSourceMap, packagerOutputSourceMap] + return [bundleFile, outputSourceMap, packagerOutputSourceMap, bundleCommand] } /** Force Bundle task to produce sourcemap files if they are not pre-configured by user yet. */ @@ -362,10 +398,11 @@ def forceSourceMapOutputFromBundleTask(bundleTask) { def bundleOutput = null def sourcemapOutput = null def packagerSourcemapOutput = null + def bundleCommand = null - (bundleOutput, sourcemapOutput, packagerSourcemapOutput) = extractBundleTaskArgumentsRN71AndAbove(bundleTask, logger) + (bundleOutput, sourcemapOutput, packagerSourcemapOutput, bundleCommand) = extractBundleTaskArgumentsRN71AndAbove(bundleTask, logger) if (bundleOutput == null) { - (bundleOutput, sourcemapOutput, packagerSourcemapOutput) = extractBundleTaskArgumentsLegacy(cmdArgs, project) + (bundleOutput, sourcemapOutput, packagerSourcemapOutput, bundleCommand) = extractBundleTaskArgumentsLegacy(cmdArgs, project) } if (sourcemapOutput == null) { @@ -384,7 +421,7 @@ def forceSourceMapOutputFromBundleTask(bundleTask) { project.logger.info("Info: used pre-configured source map files: ${sourcemapOutput}") } - return [shouldCleanUp, bundleOutput, sourcemapOutput, packagerSourcemapOutput] + return [shouldCleanUp, bundleOutput, sourcemapOutput, packagerSourcemapOutput, bundleCommand] } /** compose array with one item - current build flavor name */ diff --git a/src/js/tools/sentryMetroSerializer.ts b/src/js/tools/sentryMetroSerializer.ts index 865283d4be..9212070585 100644 --- a/src/js/tools/sentryMetroSerializer.ts +++ b/src/js/tools/sentryMetroSerializer.ts @@ -166,6 +166,8 @@ const createDefaultMetroSerializer = ( * source and extracts the bundle's debug ID from it. * * The string pattern is injected via the debug ID injection snipped. + * + * https://github.com/getsentry/sentry-javascript-bundler-plugins/blob/40f918458ed449d8b3eabaf64d13c08218213f65/packages/bundler-plugin-core/src/debug-id-upload.ts#L293-L294 */ function determineDebugIdFromBundleSource(code: string): string | undefined { const match = code.match( From 0955f2d962c6d6ab8b99aaa37ba5dc12f9ec0047 Mon Sep 17 00:00:00 2001 From: Krystof Woldrich Date: Thu, 21 Sep 2023 14:23:18 +0200 Subject: [PATCH 14/16] Add new script to published package --- .npmignore | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.npmignore b/.npmignore index ad2bd3e10d..866da48bc5 100644 --- a/.npmignore +++ b/.npmignore @@ -13,3 +13,5 @@ !/android/**/* !src/js/NativeRNSentry.ts !scripts/collect-modules.sh +!scripts/copy-debugid.js +!scripts/hasSourceMapDebugId.js From 6efb62046a0a37e33f83bf352c1aba01dfbc9fbf Mon Sep 17 00:00:00 2001 From: Krystof Woldrich Date: Thu, 21 Sep 2023 16:56:01 +0200 Subject: [PATCH 15/16] Move xcode build phase script to rn sdk package --- .npmignore | 3 +++ sample-new-architecture/ios/.xcode.env | 8 +++++++ .../project.pbxproj | 4 ++-- scripts/sentry-xcode-debug-files.sh | 21 +++++++++++++++++ scripts/sentry-xcode.sh | 23 +++++++++++++++++++ 5 files changed, 57 insertions(+), 2 deletions(-) create mode 100755 scripts/sentry-xcode-debug-files.sh create mode 100755 scripts/sentry-xcode.sh diff --git a/.npmignore b/.npmignore index 866da48bc5..2caea3b039 100644 --- a/.npmignore +++ b/.npmignore @@ -15,3 +15,6 @@ !scripts/collect-modules.sh !scripts/copy-debugid.js !scripts/hasSourceMapDebugId.js +!scripts/hasSourceMapDebugId.js +!scripts/sentry-xcode.sh +!scripts/sentry-xcode-debug-files.sh diff --git a/sample-new-architecture/ios/.xcode.env b/sample-new-architecture/ios/.xcode.env index dcc18e1756..8c616ffc34 100644 --- a/sample-new-architecture/ios/.xcode.env +++ b/sample-new-architecture/ios/.xcode.env @@ -9,3 +9,11 @@ # For example, to use nvm with brew, add the following line #. "$(brew --prefix nvm)/nvm.sh" --no-use export NODE_BINARY=$(command -v node) + +export EXTRA_COMPILER_ARGS="-w" + +export SENTRY_CLI_EXECUTABLE="../../node_modules/@sentry/cli/bin/sentry-cli" +export SENTRY_CLI_EXTRA_ARGS="--force-foreground" +export SENTRY_CLI_DEBUG_FILES_UPLOAD_EXTRA_ARGS="--include-sources --log-level=DEBUG" +export SENTRY_CLI_RN_XCODE_EXTRA_ARGS="" +export MODULES_PATHS="$PWD/../node_modules,$PWD/../../.." diff --git a/sample-new-architecture/ios/sampleNewArchitecture.xcodeproj/project.pbxproj b/sample-new-architecture/ios/sampleNewArchitecture.xcodeproj/project.pbxproj index 1387b931ec..e169d111d9 100644 --- a/sample-new-architecture/ios/sampleNewArchitecture.xcodeproj/project.pbxproj +++ b/sample-new-architecture/ios/sampleNewArchitecture.xcodeproj/project.pbxproj @@ -271,7 +271,7 @@ ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; - shellScript = "set -e\nWITH_ENVIRONMENT=\"../node_modules/react-native/scripts/xcode/with-environment.sh\"\n\nexport SENTRY_PROPERTIES=sentry.properties\nexport EXTRA_PACKAGER_ARGS=\"--sourcemap-output $DERIVED_FILE_DIR/main.jsbundle.map\"\n\nREACT_NATIVE_XCODE=\"../node_modules/react-native/scripts/react-native-xcode.sh\"\nSENTRY_CLI=\"../../node_modules/@sentry/cli/bin/sentry-cli\"\nBUNDLE_REACT_NATIVE=\"$SENTRY_CLI react-native xcode --force-foreground $REACT_NATIVE_XCODE\"\n\n/bin/sh -c \"$WITH_ENVIRONMENT \\\"$BUNDLE_REACT_NATIVE\\\"\"\n\nexport MODULES_PATHS=\"$PWD/../node_modules,$PWD/../../..\"\nCOLLECT_MODULES=\"../../scripts/collect-modules.sh\"\n\n/bin/sh -c \"$WITH_ENVIRONMENT \\\"$COLLECT_MODULES\\\"\"\n"; + shellScript = "set -e\nWITH_ENVIRONMENT=\"../node_modules/react-native/scripts/xcode/with-environment.sh\"\nREACT_NATIVE_XCODE=\"../node_modules/react-native/scripts/react-native-xcode.sh\"\nBUNDLE_REACT_NATIVE=\"/bin/sh ../../scripts/sentry-xcode.sh $REACT_NATIVE_XCODE\"\n\n/bin/sh -c \"$WITH_ENVIRONMENT \\\"$BUNDLE_REACT_NATIVE\\\"\"\n"; }; 00EEFC60759A1932668264C0 /* [CP] Embed Pods Frameworks */ = { isa = PBXShellScriptBuildPhase; @@ -303,7 +303,7 @@ ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; - shellScript = "set -e\nWITH_ENVIRONMENT=\"../node_modules/react-native/scripts/xcode/with-environment.sh\"\n\nexport SENTRY_PROPERTIES=sentry.properties\n\n[[ $SENTRY_INCLUDE_NATIVE_SOURCES == \"true\" ]] && INCLUDE_SOURCES_FLAG=\"--include-sources\" || INCLUDE_SOURCES_FLAG=\"\"\nSENTRY_CLI=\"../../node_modules/@sentry/cli/bin/sentry-cli\"\nFLAGS=\"--force-foreground $INCLUDE_SOURCES_FLAG\"\n\n/bin/sh -c \"$WITH_ENVIRONMENT \\\"$UPLOAD_DEBUG_FILES\\\"\"\n"; + shellScript = "#/bin/sh ../../scripts/sentry-xcode-debug-files.sh\n"; }; A55EABD7B0C7F3A422A6CC61 /* [CP] Check Pods Manifest.lock */ = { isa = PBXShellScriptBuildPhase; diff --git a/scripts/sentry-xcode-debug-files.sh b/scripts/sentry-xcode-debug-files.sh new file mode 100755 index 0000000000..dee5afdd3a --- /dev/null +++ b/scripts/sentry-xcode-debug-files.sh @@ -0,0 +1,21 @@ +# Upload Debug Symbols to Sentry Xcode Build Phase +# PWD=ios + +# print commands before executing them and stop on first error +set -x -e + +# load envs if loader file exists (since rn 0.68) +WITH_ENVIRONMENT="../node_modules/react-native/scripts/xcode/with-environment.sh" +if [ -f "$WITH_ENVIRONMENT" ]; then + . "$WITH_ENVIRONMENT" +fi + +[ -z "$SENTRY_PROPERTIES" ] && export SENTRY_PROPERTIES=sentry.properties +[ -z "$SENTRY_CLI_EXECUTABLE" ] && SENTRY_CLI_EXECUTABLE="../node_modules/@sentry/cli/bin/sentry-cli" + +[[ $SENTRY_INCLUDE_NATIVE_SOURCES == "true" ]] && INCLUDE_SOURCES_FLAG="--include-sources" || INCLUDE_SOURCES_FLAG="" + +EXTRA_ARGS="$SENTRY_CLI_EXTRA_ARGS $SENTRY_CLI_DEBUG_FILES_UPLOAD_EXTRA_ARGS $INCLUDE_SOURCES_FLAG" + +UPLOAD_DEBUG_FILES="\"$SENTRY_CLI_EXECUTABLE\" debug-files upload $EXTRA_ARGS \"$DWARF_DSYM_FOLDER_PATH\"" +/bin/sh -c "$UPLOAD_DEBUG_FILES" diff --git a/scripts/sentry-xcode.sh b/scripts/sentry-xcode.sh new file mode 100755 index 0000000000..10642b1948 --- /dev/null +++ b/scripts/sentry-xcode.sh @@ -0,0 +1,23 @@ +# Sentry Bundle React Native code and images +# PWD=ios + +# print commands before executing them and stop on first error +set -x -e + +# WITH_ENVIRONMENT is executed by React Native + +[ -z "$SENTRY_PROPERTIES" ] && export SENTRY_PROPERTIES=sentry.properties +[ -z "$EXTRA_PACKAGER_ARGS" ] && export EXTRA_PACKAGER_ARGS="--sourcemap-output $DERIVED_FILE_DIR/main.jsbundle.map" +[ -z "$SENTRY_CLI_EXECUTABLE" ] && SENTRY_CLI_EXECUTABLE="../node_modules/@sentry/cli/bin/sentry-cli" + +REACT_NATIVE_XCODE=$1 + +BUNDLE_REACT_NATIVE="\"$SENTRY_CLI_EXECUTABLE\" react-native xcode $SENTRY_CLI_EXTRA_ARGS $SENTRY_CLI_RN_XCODE_EXTRA_ARGS \"$REACT_NATIVE_XCODE\"" + +/bin/sh -c "$BUNDLE_REACT_NATIVE" + +[ -z "$SENTRY_COLLECT_MODULES" ] && SENTRY_COLLECT_MODULES="../../scripts/collect-modules.sh" + +if [ -f "$SENTRY_COLLECT_MODULES" ]; then + /bin/sh "$SENTRY_COLLECT_MODULES" +fi From 731af022662ddbbfa5d1e3511341c89e9149b066 Mon Sep 17 00:00:00 2001 From: Krystof Woldrich Date: Thu, 21 Sep 2023 17:01:15 +0200 Subject: [PATCH 16/16] Remove extra sentry cli args --- sample-new-architecture/ios/.xcode.env | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sample-new-architecture/ios/.xcode.env b/sample-new-architecture/ios/.xcode.env index 8c616ffc34..b2f8e82e61 100644 --- a/sample-new-architecture/ios/.xcode.env +++ b/sample-new-architecture/ios/.xcode.env @@ -14,6 +14,6 @@ export EXTRA_COMPILER_ARGS="-w" export SENTRY_CLI_EXECUTABLE="../../node_modules/@sentry/cli/bin/sentry-cli" export SENTRY_CLI_EXTRA_ARGS="--force-foreground" -export SENTRY_CLI_DEBUG_FILES_UPLOAD_EXTRA_ARGS="--include-sources --log-level=DEBUG" +export SENTRY_CLI_DEBUG_FILES_UPLOAD_EXTRA_ARGS="" export SENTRY_CLI_RN_XCODE_EXTRA_ARGS="" export MODULES_PATHS="$PWD/../node_modules,$PWD/../../.."