diff --git a/examples/sdk/reactNative/README.md b/examples/sdk/reactNative/README.md index 584989d2..d89d82f3 100644 --- a/examples/sdk/reactNative/README.md +++ b/examples/sdk/reactNative/README.md @@ -8,53 +8,64 @@ This example app shows features available in the @backtrace/react-native package 2. `npm install`. If you're on iOS, navigate to the `ios` directory and run `pod install` 3. `npm run start` and pick desired platform -#### Source maps +### Source maps + +This example application is integrated with the source map support. Once you change the .backtracejsrc file, source maps will be automatically uploaded to your project. Before executing any step: -- Please update .backtracejsrc file with your symbols submission URL and your sourcemap settings. +> Please update .backtracejsrc file with your symbols submission URL and your sourcemap settings. -On Android: You can verify our example app with the source map support. In order to do that, please use the -android-sourcemap.sh script. +Backtrace is compatible with metro build system. To enable source map support, set a `customSerializer` method in the `metro.config.js` file to the `processSourceMap` function available in `@backtrace/react-native/scripts/processSourceMap`. -```bash -./android-sourcemap.sh ./optional-path-to-directory ``` +const { getDefaultConfig, mergeConfig } = require('@react-native/metro-config'); +const backtraceSourceMapProcessor = require('@backtrace/react-native/scripts/processSourceMap'); -The script will prepare a release APK version of your React Native application with the sourcemap and Hermes support. -The APK can be found in the ./android/app/build/outputs/apk/release/ directory. +const config = { + serializer: { + customSerializer: backtraceSourceMapProcessor.processSourceMap + }, +}; +module.exports = mergeConfig(getDefaultConfig(__dirname), config); -On iOS: Backtrace simplify the flow needed to upload source maps by instructing hermesc how to generate and prepare -source maps for your application. Without this additional step, react-native will not instruct your application to -support Backtrace source maps. +``` -In order to prepare your application for source maps and automatically upload them to Backtrace, modify your "Build -Phase" with the following code: +Add Backtrace to build automation to ensure every build has source map support. -```bash -set -e +In order to upload source maps to Backtrace, you can: -# destination source map directory -SOURCE_MAP_DIR="$(pwd)/../build" -mkdir -p $SOURCE_MAP_DIR +**On Android:** -export SOURCEMAP_FILE="$SOURCE_MAP_DIR/main.js.map"; -WITH_ENVIRONMENT="../node_modules/react-native/scripts/xcode/with-environment.sh" -REACT_NATIVE_XCODE="../node_modules/react-native/scripts/react-native-xcode.sh" +Enable source map support in `app/build.gradle` by uncommenting hermes source map flags. With additional parameters, source maps will be generated. To automatically upload them to Backtrace, you can use the gradle task available the @backtrace/react-native library. -# use hermesc script provided by Backtrace to populate source maps -# if you dont use hermes support, please skip this step. -export HERMES_CLI_PATH="$(pwd)/../ios-hermesc.sh" +`apply from: "$rootDir/../node_modules/@backtrace/react-native/android/upload-sourcemaps.gradle"` -/bin/sh -c "$WITH_ENVIRONMENT $REACT_NATIVE_XCODE" +Once you import the gradle task, you can simply add it to your flow for any build/assemble tasks. -# copy javascript build output to the build directory -cp "$CONFIGURATION_BUILD_DIR/main.jsbundle" $SOURCE_MAP_DIR +```gradle +tasks.matching { + it.name.startsWith("assemble") || it.name.startsWith("build") +}.configureEach { task -> + task.finalizedBy("uploadSourceMapsToBacktrace") +} +``` -# process source map with javascript code -npx --yes @backtrace/javascript-cli run --config "$(pwd)/../.backtracejsrc" --path "$SOURCE_MAP_DIR/main.jsbundle" +**On iOS.** -``` +Modify the code in the `Bundle React Native code and images` step in the `Build Phases` of your xcode project setting. In the end of the script, you can include the code below, to upload source maps directly to Backtrace after generating the applicaiton. -Note: this modification copy the output of the javascript build into the build directory created in your application -folder. Please also put ios-hermesc. +```bash +project_directory="$(pwd)/.." +# enable source map support +export SOURCEMAP_FILE="$project_directory/main.jsbundle.map" + +... + +# upload source maps to Backtrace +source_map_upload="$project_directory/node_modules/@backtrace/react-native/scripts/ios-sourcemap-upload.sh" +backtrace_js_config="$project_directory/.backtracejsrc" + +/bin/sh -c "$source_map_upload $SOURCEMAP_FILE $TARGET_BUILD_DIR/.backtrace-sourcemap-id $backtrace_js_config $project_directory" + +``` diff --git a/examples/sdk/reactNative/android-sourcemap.sh b/examples/sdk/reactNative/android-sourcemap.sh deleted file mode 100755 index e29e428b..00000000 --- a/examples/sdk/reactNative/android-sourcemap.sh +++ /dev/null @@ -1,66 +0,0 @@ -#/bin/bash - -# This script shows how to add source map support to any react-native android application. -# By using it, you can upload source maps to Backtrace and built a release version of the app -# with hermesc support. This script uses .backtracejsrc file available in your react-native directory. -# -# Additional information: This script prepares your bundle for you. In the release build, to prevent "double" application build -# while building final apk/aab, in the build.gradle file, please use `debuggableVariants = ["release"]`. Otherwise -# Gradle and react-native will try to build the application twice and override application version with source map support. - -BUILD_DIR=${1:-build} -BUNDLE_PATH="$BUILD_DIR/index.android.bundle" -SOURCE_MAP_PATH="$BUILD_DIR/index.android.js.map" - -mkdir -p $BUILD_DIR -# build react-native application -npx react-native bundle \ - --platform android \ - --dev false \ - --entry-file index.js \ - --reset-cache \ - --bundle-output $BUNDLE_PATH \ - --sourcemap-output $SOURCE_MAP_PATH \ - --minify false \ - --assets-dest ./android/app/src/main/res/ - - -# add source map identifier to final javascript bundle -npx --yes @backtrace/javascript-cli process --path=$BUNDLE_PATH - -HBC_OUTPUT="$BUILD_DIR/app.hbc" -HBC_MAP_OUTPUT="$HBC_OUTPUT.map" - -# generate react-native executable -./node_modules/react-native/sdks/hermesc/osx-bin/hermesc \ - -emit-binary \ - -max-diagnostic-width=80 \ - -output-source-map \ - -out=$HBC_OUTPUT \ - $BUNDLE_PATH - -# on this stage we have source map for built application via react-native -# and source map for the final executable. Combination of both should generate -# final source map needed to process correctly reports -node ./node_modules/react-native/scripts/compose-source-maps.js \ - $SOURCE_MAP_PATH \ - $HBC_MAP_OUTPUT \ - -o $SOURCE_MAP_PATH - -# upload data to Backtrace -npx --yes @backtrace/javascript-cli run $BUNDLE_PATH - -# prepare android application -mkdir -p ./android/app/src/main/assets -# rename hbc to android.bundle file -mv $HBC_OUTPUT $BUNDLE_PATH - -# prepare android application -mkdir -p ./android/app/build/generated/assets/createBundleReleaseJsAndAssets -cp $BUNDLE_PATH ./android/app/build/generated/assets/createBundleReleaseJsAndAssets/ -mkdir -p ./android/app/build/intermediates/assets/release/ -cp $BUNDLE_PATH ./android/app/build/intermediates/assets/release/ - -cd android -./gradlew assembleRelease -echo "Application build is available under path: ./android/app/build/outputs/apk/release/" \ No newline at end of file diff --git a/examples/sdk/reactNative/android/app/build.gradle b/examples/sdk/reactNative/android/app/build.gradle index d6a8b43d..2e842111 100644 --- a/examples/sdk/reactNative/android/app/build.gradle +++ b/examples/sdk/reactNative/android/app/build.gradle @@ -1,5 +1,7 @@ apply plugin: "com.android.application" apply plugin: "com.facebook.react" +apply from: "$rootDir/../node_modules/@backtrace/react-native/android/upload-sourcemaps.gradle" + /** * This is the configuration block to customize your React Native Android app. @@ -47,7 +49,7 @@ react { // hermesCommand = "$rootDir/my-custom-hermesc/bin/hermesc" // // The list of flags to pass to the Hermes compiler. By default is "-O", "-output-source-map" - // hermesFlags = ["-O", "-output-source-map"] + hermesFlags = ["-O", "-output-source-map"] /* Autolinking */ autolinkLibrariesWithApp() @@ -116,3 +118,9 @@ dependencies { implementation jscFlavor } } + +tasks.matching { + it.name.startsWith("assemble") || it.name.startsWith("build") +}.configureEach { task -> + task.finalizedBy("uploadSourceMapsToBacktrace") +} diff --git a/examples/sdk/reactNative/ios-hermesc.sh b/examples/sdk/reactNative/ios-hermesc.sh deleted file mode 100755 index 7a9c9124..00000000 --- a/examples/sdk/reactNative/ios-hermesc.sh +++ /dev/null @@ -1,28 +0,0 @@ -#!/bin/bash - -set -e -set -x - -# This script shows how process your application code with source maps and hermesc. By using this script, Backtrace integration can process -# your source code to generate valid source map files. This script does exactly the same what the hermesc script does, with one exception - -# before the native library is generated, this script will process source code and source map to generate output needed in next steps for source map integration. - - -hermes_engine_path="$PODS_ROOT/hermes-engine" -[ -z "$HERMES_CLI_PATH_OVERRIDE" ] && HERMES_CLI_PATH_OVERRIDE="$hermes_engine_path/destroot/bin/hermesc" - -app_bundle_file="${BASH_ARGV[0]}" - -if [[ ! -f "$app_bundle_file" ]]; then - echo "error: File $app_bundle_file does not exist. " >&2 - exit 2 -fi - -# check and assign NODE_BINARY env -source "$REACT_NATIVE_PATH/scripts/node-binary.sh" - -backtrace_js_path="${REACT_NATIVE_PATH}/../.bin/backtrace-js" - -"$NODE_BINARY" "$backtrace_js_path" process --path="$app_bundle_file" - -$HERMES_CLI_PATH_OVERRIDE "$@" diff --git a/examples/sdk/reactNative/ios-sourcemaps.sh b/examples/sdk/reactNative/ios-sourcemaps.sh deleted file mode 100755 index 5ccffdb3..00000000 --- a/examples/sdk/reactNative/ios-sourcemaps.sh +++ /dev/null @@ -1,32 +0,0 @@ -#!/bin/bash - -# This script runs backtrace-js on given path and using given config. -# In this example, backtrace-js will process and upload sourcemaps. -# This should be executed by the iOS build after creating the .jsbundle file. - -set -e -set -x - -script_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" -config_path=$BACKTRACE_JS_CONFIG -bundle_path=$BACKTRACE_JS_BUNDLE_PATH - -if [[ ! -f "$bundle_path" ]]; then - echo "warn: File $bundle_path does not exist. \ - Try switching to a Release build. \ - Sourcemaps will not be processed." - - exit 0 -fi - -# path to react-native module dir relative from this script -react_native_dir="${script_dir}/node_modules/react-native" -backtrace_js_path="${script_dir}/node_modules/.bin/backtrace-js" - -# check and assign NODE_BINARY env -source "$react_native_dir/scripts/node-binary.sh" - -# run backtrace-js on bundle -"$NODE_BINARY" "$backtrace_js_path" run \ - --config "$config_path" \ - --path "$bundle_path" diff --git a/examples/sdk/reactNative/ios/reactNative.xcodeproj/project.pbxproj b/examples/sdk/reactNative/ios/reactNative.xcodeproj/project.pbxproj index dbaa7e8c..cfa76c47 100644 --- a/examples/sdk/reactNative/ios/reactNative.xcodeproj/project.pbxproj +++ b/examples/sdk/reactNative/ios/reactNative.xcodeproj/project.pbxproj @@ -269,7 +269,7 @@ ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; - shellScript = "set -e\nset -x\n# destination source map directory\nSOURCE_MAP_DIR=\"$(pwd)/../build\"\nmkdir -p $SOURCE_MAP_DIR\n\nexport SOURCEMAP_FILE=\"$SOURCE_MAP_DIR/main.js.map\";\nWITH_ENVIRONMENT=\"$REACT_NATIVE_PATH/scripts/xcode/with-environment.sh\"\nREACT_NATIVE_XCODE=\"$REACT_NATIVE_PATH/scripts/react-native-xcode.sh\"\n\n# use hermesc script provided by Backtrace to populate source maps\n# if you dont use hermes support, please skip this step.\nexport HERMES_CLI_PATH=\"$(pwd)/../ios-hermesc.sh\"\n\n/bin/sh -c \"$WITH_ENVIRONMENT $REACT_NATIVE_XCODE\"\n\n# copy javascript build output to the build directory\ncp \"$CONFIGURATION_BUILD_DIR/main.jsbundle\" $SOURCE_MAP_DIR \n\nPROCESS_SOURCEMAPS_SCRIPT=\"$(pwd)/../ios-sourcemaps.sh\"\nexport BACKTRACE_JS_CONFIG=\"$(pwd)/../.backtracejsrc\"\nexport BACKTRACE_JS_BUNDLE_PATH=\"$SOURCE_MAP_DIR/main.jsbundle\"\n \n# process source map with javascript code\n/bin/sh -c \"$WITH_ENVIRONMENT $PROCESS_SOURCEMAPS_SCRIPT\"\n"; + shellScript = "set -e\nproject_directory=\"$(pwd)/..\"\n# enable source map support\nexport SOURCEMAP_FILE=\"$project_directory/main.jsbundle.map\"\n\nWITH_ENVIRONMENT=\"$REACT_NATIVE_PATH/scripts/xcode/with-environment.sh\"\nREACT_NATIVE_XCODE=\"$REACT_NATIVE_PATH/scripts/react-native-xcode.sh\"\n\n/bin/sh -c \"$WITH_ENVIRONMENT $REACT_NATIVE_XCODE\"\n\n# upload source maps to Backtrace\nsource_map_upload=\"$project_directory/node_modules/@backtrace/react-native/scripts/ios-sourcemap-upload.sh\"\nbacktrace_js_config=\"$project_directory/.backtracejsrc\"\n\n/bin/sh -c \"$source_map_upload $SOURCEMAP_FILE $TARGET_BUILD_DIR/.backtrace-sourcemap-id $backtrace_js_config $project_directory\" \n\n"; }; 00EEFC60759A1932668264C0 /* [CP] Embed Pods Frameworks */ = { isa = PBXShellScriptBuildPhase; diff --git a/examples/sdk/reactNative/metro.config.js b/examples/sdk/reactNative/metro.config.js index 01b1c1ae..e4a564eb 100644 --- a/examples/sdk/reactNative/metro.config.js +++ b/examples/sdk/reactNative/metro.config.js @@ -1,5 +1,6 @@ -const {getDefaultConfig, mergeConfig} = require('@react-native/metro-config'); +const { getDefaultConfig, mergeConfig } = require('@react-native/metro-config'); const path = require('path'); +const backtraceSourceMapProcessor = require('@backtrace/react-native/scripts/processSourceMap'); /** * Metro configuration @@ -8,12 +9,14 @@ const path = require('path'); * @type {import('metro-config').MetroConfig} */ const config = { - watchFolders: [ - path.resolve('../../../packages/react-native'), - path.resolve('../../../packages/react-native/node_modules'), - path.resolve('../../../node_modules'), - path.resolve('../../../packages/sdk-core'), - ], + watchFolders: [ + path.resolve('../../../packages/react-native'), + path.resolve('../../../packages/react-native/node_modules'), + path.resolve('../../../node_modules'), + path.resolve('../../../packages/sdk-core'), + ], + serializer: { + customSerializer: backtraceSourceMapProcessor.processSourceMap, + }, }; - module.exports = mergeConfig(getDefaultConfig(__dirname), config); diff --git a/examples/sdk/reactNative/package-lock.json b/examples/sdk/reactNative/package-lock.json index a3118123..edb786e8 100644 --- a/examples/sdk/reactNative/package-lock.json +++ b/examples/sdk/reactNative/package-lock.json @@ -17,6 +17,7 @@ "@babel/preset-env": "^7.20.0", "@babel/runtime": "^7.20.0", "@backtrace/javascript-cli": "file:../../../tools/cli", + "@backtrace/sourcemap-tools": "file:../../../tools/sourcemap-tools", "@react-native/babel-preset": "0.75.3", "@react-native/eslint-config": "0.75.3", "@react-native/metro-config": "0.75.3", @@ -82,6 +83,7 @@ } }, "../../../tools/cli": { + "name": "@backtrace/javascript-cli", "version": "0.3.2", "dev": true, "license": "MIT", @@ -109,6 +111,29 @@ "node": ">=14" } }, + "../../../tools/sourcemap-tools": { + "name": "@backtrace/sourcemap-tools", + "version": "0.2.3", + "dev": true, + "license": "MIT", + "dependencies": { + "tar-stream": "^3.1.6" + }, + "devDependencies": { + "@types/decompress": "^4.2.4", + "@types/jest": "^29.5.1", + "@types/tar-stream": "^2.2.2", + "decompress": "^4.2.1", + "jest": "^29.5.0", + "nock": "^13.3.1", + "source-map": "^0.7.4", + "ts-jest": "^29.1.0", + "typescript": "^5.0.4" + }, + "engines": { + "node": ">=14" + } + }, "node_modules/@ampproject/remapping": { "version": "2.3.0", "license": "Apache-2.0", @@ -1981,6 +2006,10 @@ "resolved": "../../../packages/react-native", "link": true }, + "node_modules/@backtrace/sourcemap-tools": { + "resolved": "../../../tools/sourcemap-tools", + "link": true + }, "node_modules/@bcoe/v8-coverage": { "version": "0.2.3", "dev": true, diff --git a/examples/sdk/reactNative/package.json b/examples/sdk/reactNative/package.json index 52d34df9..8c923703 100644 --- a/examples/sdk/reactNative/package.json +++ b/examples/sdk/reactNative/package.json @@ -18,6 +18,7 @@ "@babel/preset-env": "^7.20.0", "@babel/runtime": "^7.20.0", "@backtrace/javascript-cli": "file:../../../tools/cli", + "@backtrace/sourcemap-tools": "file:../../../tools/sourcemap-tools", "@react-native/babel-preset": "0.75.3", "@react-native/eslint-config": "0.75.3", "@react-native/metro-config": "0.75.3", diff --git a/packages/react-native/android/upload-sourcemaps.gradle b/packages/react-native/android/upload-sourcemaps.gradle new file mode 100644 index 00000000..659584dd --- /dev/null +++ b/packages/react-native/android/upload-sourcemaps.gradle @@ -0,0 +1,48 @@ +import groovy.json.JsonBuilder +import groovy.json.JsonSlurper + + +tasks.register("uploadSourceMapsToBacktrace") { + group = "backtrace" + description = "This task uploads source maps generated by the react-native builder to Backtrace based on the .backtracejsrc file." + + doLast { + // Fetch the build variant (debug or release) + // Default to 'release' if not set + def buildVariant = project.hasProperty("buildVariant") ? project.buildVariant.toLowerCase() : "release" + + def debugIdPathEnv = System.getenv("DEBUG_ID_PATH") + + def debugIdFile = debugIdPathEnv ? file(debugIdPathEnv) : layout.buildDirectory + .dir("intermediates/sourcemaps/react/${buildVariant}/.backtrace-sourcemap-id") + .get() + .asFile + + if (!debugIdFile.exists()) { + println("Backtrace: Cannot find .backtrace-sourcemap-id file. Check if customSerializer has been set to Backtrace serializer in metro.config.js.") + return + } + + def sourcemapDestinationDirectory = layout.buildDirectory.dir("generated/sourcemaps/react").get().asFile + def mapFiles = fileTree(dir: sourcemapDestinationDirectory, include: '**/*.map').files + def mapFile = mapFiles ? mapFiles.iterator().next() : null + + if (!mapFile) { + println("Backtrace: Cannot find source map file.") + return + } + + def jsonSlurper = new JsonSlurper() + def mapData = jsonSlurper.parse(mapFile) + mapData.debugId = debugIdFile.text + def jsonBuilder = new JsonBuilder(mapData) + mapFile.text = jsonBuilder.toString() + + // Execute Backtrace JS CLI to process upload processed source maps + def command = ["npx", "--yes", "@backtrace/javascript-cli", "upload", "-p", mapFile.absolutePath] + exec { + workingDir file("$rootProject.projectDir/..") + commandLine command + } + } +} diff --git a/packages/react-native/scripts/addDebugIdToSourceMap.js b/packages/react-native/scripts/addDebugIdToSourceMap.js new file mode 100644 index 00000000..77f280f3 --- /dev/null +++ b/packages/react-native/scripts/addDebugIdToSourceMap.js @@ -0,0 +1,27 @@ +const fs = require('fs'); +const path = require('path'); + +function includeDebugIdInSourceMap(sourceMapPath, debugId) { + if (!fs.existsSync(sourceMapPath)) { + console.error(`Backtrace: source map not found at path ${sourceMapPath}.`); + process.exit(1); + } + + const sourceMapContent = fs.readFileSync(sourceMapPath, 'utf8'); + const sourceMap = JSON.parse(sourceMapContent); + + sourceMap['debugId'] = debugId; + + fs.writeFileSync(sourceMapPath, JSON.stringify(sourceMap), 'utf8'); + console.debug(`Backtrace: Successfully updated source map file ${sourceMapPath} with debug id: ${debugId}`); +} + +const args = process.argv.slice(2); +if (args.length < 2) { + console.error(`Usage: node ${path.basename(args[1])} `); + process.exit(1); +} + +const sourceMapPath = path.resolve(args[0]); +const debugId = args[1]; +includeDebugIdInSourceMap(sourceMapPath, debugId); diff --git a/packages/react-native/scripts/ios-sourcemap-upload.sh b/packages/react-native/scripts/ios-sourcemap-upload.sh new file mode 100755 index 00000000..3ae49083 --- /dev/null +++ b/packages/react-native/scripts/ios-sourcemap-upload.sh @@ -0,0 +1,67 @@ +#!/bin/bash + +# Script responsible for preprocessing source maps with debugid and uploading it to Backtrace via backtrace-js. +# Usage: ./ios-sourcemap-upload.sh +# Parameters: +# (Required) Path to the source map file. +# (Required) Path to generated backtrace debug id. +# (Required) Path to the .backtracejsrc configuration file. +# (Required) Path to the react-native project directory +# +# Adjusting metro configuration is required in order to correctly use debug_id available in the debug_id_file_path. + + +set -e +set -x + +if [ -z "$1" ]; then + echo "Error: Missing path to the source map file." + exit 1 +fi + +source_map_file_path="$1" + +# Check if the file exists +if [ ! -f "$source_map_file_path" ]; then + echo "Error: File '$source_map_file_path' does not exist." + exit 1 +fi + +debug_id_file_path=${DEBUG_ID_PATH:-${2:-}} + +if [ ! -f "$debug_id_file_path" ]; then + echo "Error: File '$debug_id_file_path' does not exist." + exit 1 +fi + +if [ -z "$3" ]; then + echo "Error: Missing path to the .backtracejsrc file." + exit 1 +fi + +if [ -z "$4" ]; then + echo "Error: Missing path to the project directory." + exit 1 +fi + +project_directory_path="$4" + +backtrace_configuration_path="$3" + +# check and assign NODE_BINARY env +source "$REACT_NATIVE_PATH/scripts/node-binary.sh" +debug_id=$(<"$debug_id_file_path") + +script_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" + +"$NODE_BINARY" "$script_dir/addDebugIdToSourceMap.js" \ + "$source_map_file_path" \ + "$debug_id" + +# path to react-native module dir relative from this script +backtrace_js_path="${project_directory_path}/node_modules/.bin/backtrace-js" + +# run backtrace-js on bundle +"$NODE_BINARY" "$backtrace_js_path" upload \ + -p "$source_map_file_path" \ + --config "$backtrace_configuration_path" diff --git a/packages/react-native/scripts/processSourceMap.js b/packages/react-native/scripts/processSourceMap.js new file mode 100644 index 00000000..ed4e893b --- /dev/null +++ b/packages/react-native/scripts/processSourceMap.js @@ -0,0 +1,62 @@ +const bundleToString = require('metro/src/lib/bundleToString'); +const baseJSBundle = require('metro/src/DeltaBundler/Serializers/baseJSBundle'); +const CountingSet = require('metro/src/lib/CountingSet').default; +const fs = require('fs'); +const path = require('path'); +const { DebugIdGenerator, SourceProcessor } = require('@backtrace/sourcemap-tools'); + +const DEBUG_ID_PATH = process.env.DEBUG_ID_PATH; + +/** + * Process metro build with source map support powered by Backtrace. + */ +async function processSourceMap(entryPoint, preModules, graph, options) { + const bundle = bundleToString(baseJSBundle(entryPoint, preModules, graph, options)); + + // development build - skip source map upload + if (graph.transformOptions.hot || graph.transformOptions.dev) { + return bundle; + } + + // no source map option set + const sourceMapOutputPathParameter = process.argv.indexOf('--sourcemap-output'); + if (sourceMapOutputPathParameter === -1) { + return bundle; + } + + const sourceMapOutputPath = process.argv[sourceMapOutputPathParameter + 1]; + const backtraceSourceMapId = + DEBUG_ID_PATH ?? path.join(path.dirname(sourceMapOutputPath), '.backtrace-sourcemap-id'); + + const debugIdGenerator = new DebugIdGenerator(); + const sourceProcessor = new SourceProcessor(debugIdGenerator); + const { source, debugId } = await sourceProcessor.processSource(bundle.code); + const snippet = debugIdGenerator.generateSourceSnippet(debugId); + + console.debug(`Backtrace: saving debugId ${debugId} to ${backtraceSourceMapId}.`); + fs.writeFileSync(backtraceSourceMapId, debugId); + + const debugIdPrepend = { + dependencies: new Map(), + getSource: () => Buffer.from(snippet), + inverseDependencies: new CountingSet(), + path: '__prelude__', + output: [ + { + type: 'js/script/virtual', + data: { + code: snippet, + lineCount: 1, + map: [], + }, + }, + ], + }; + preModules.unshift(debugIdPrepend); + return { + ...bundle, + code: source, + }; +} + +module.exports = { processSourceMap };