Skip to content

Commit c1786c3

Browse files
Add collect modules (#2606)
1 parent edb9796 commit c1786c3

File tree

25 files changed

+403
-6
lines changed

25 files changed

+403
-6
lines changed

.npmignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,3 +10,4 @@
1010
!react-native.config.js
1111
!/ios/**/*
1212
!/android/**/*
13+
!scripts/collect-modules.sh

CHANGELOG.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,12 @@
11
# Changelog
22

3+
## Unreleased
4+
5+
### Features
6+
7+
- JS Runtime dependencies are sent in Events ([#2606](https://github.com/getsentry/sentry-react-native/pull/2606))
8+
- To collect JS dependencies on iOS add `../node_modules/@sentry/react-native/scripts/collect-modules.sh` at the end of the `Bundle React Native code and images` build phase. The collection only works on Release builds. Android builds have a new step in `sentry.gradle` plugin. More in [the migration documentation](https://docs.sentry.io/platforms/react-native/migration#from-48x-to-49x).
9+
310
## 4.9.0
411

512
### Features

android/src/main/java/io/sentry/react/RNSentryModule.java

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
import android.content.Context;
55
import android.content.pm.PackageInfo;
66
import android.content.pm.PackageManager;
7+
import android.content.res.AssetManager;
78
import android.util.SparseIntArray;
89

910
import androidx.core.app.FrameMetricsAggregator;
@@ -19,8 +20,12 @@
1920
import com.facebook.react.bridge.WritableMap;
2021
import com.facebook.react.module.annotations.ReactModule;
2122

23+
import java.io.BufferedInputStream;
2224
import java.io.File;
25+
import java.io.FileNotFoundException;
2326
import java.io.FileOutputStream;
27+
import java.io.InputStream;
28+
import java.nio.charset.Charset;
2429
import java.util.Date;
2530
import java.util.HashMap;
2631
import java.util.List;
@@ -51,6 +56,8 @@ public class RNSentryModule extends ReactContextBaseJavaModule {
5156
public static final String NAME = "RNSentry";
5257

5358
private static final Logger logger = Logger.getLogger("react-native-sentry");
59+
private static final String modulesPath = "modules.json";
60+
private static final Charset UTF_8 = Charset.forName("UTF-8");
5461

5562
private final PackageInfo packageInfo;
5663
private FrameMetricsAggregator frameMetricsAggregator = null;
@@ -174,6 +181,25 @@ public void crash() {
174181
throw new RuntimeException("TEST - Sentry Client Crash (only works in release mode)");
175182
}
176183

184+
@ReactMethod
185+
public void fetchModules(Promise promise) {
186+
final AssetManager assets = this.getReactApplicationContext().getResources().getAssets();
187+
try (final InputStream stream =
188+
new BufferedInputStream(assets.open(RNSentryModule.modulesPath))) {
189+
int size = stream.available();
190+
byte[] buffer = new byte[size];
191+
stream.read(buffer);
192+
stream.close();
193+
String modulesJson = new String(buffer, RNSentryModule.UTF_8);
194+
promise.resolve(modulesJson);
195+
} catch (FileNotFoundException e) {
196+
promise.resolve(null);
197+
} catch (Throwable e) {
198+
logger.warning("Fetching JS Modules failed.");
199+
promise.resolve(null);
200+
}
201+
}
202+
177203
@ReactMethod
178204
public void fetchNativeRelease(Promise promise) {
179205
WritableMap release = Arguments.createMap();

ios/RNSentry.m

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -157,6 +157,16 @@ - (void)setEventEnvironmentTag:(SentryEvent *)event
157157
});
158158
}
159159

160+
RCT_EXPORT_METHOD(fetchModules:(RCTPromiseResolveBlock)resolve
161+
rejecter:(RCTPromiseRejectBlock)reject)
162+
{
163+
NSString *filePath = [[NSBundle mainBundle] pathForResource:@"modules" ofType:@"json"];
164+
NSString* modulesString = [NSString stringWithContentsOfFile:filePath
165+
encoding:NSUTF8StringEncoding
166+
error:nil];
167+
resolve(modulesString);
168+
}
169+
160170
RCT_EXPORT_METHOD(fetchNativeDeviceContexts:(RCTPromiseResolveBlock)resolve
161171
rejecter:(RCTPromiseRejectBlock)reject)
162172
{

package.json

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,8 +11,10 @@
1111
},
1212
"main": "dist/js/index.js",
1313
"scripts": {
14-
"build": "tsc -p tsconfig.build.json",
15-
"build:watch": "tsc -p tsconfig.build.json -w --preserveWatchOutput",
14+
"build": "yarn build:sdk && yarn build:tools",
15+
"build:sdk": "tsc -p tsconfig.build.json",
16+
"build:sdk:watch": "tsc -p tsconfig.build.json -w --preserveWatchOutput",
17+
"build:tools": "tsc -p tsconfig.build.tools.json",
1618
"clean": "rimraf dist coverage",
1719
"test": "jest",
1820
"lint": "eslint .",

sample/android/app/build.gradle

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,15 @@ project.ext.react = [
8181
enableHermes: true, // clean and rebuild if changing
8282
]
8383

84+
project.ext.sentryCli = [
85+
collectModulesScript: "../../../dist/js/tools/collectModules.js",
86+
modulesPaths: [
87+
"node_modules",
88+
"../..",
89+
],
90+
skipCollectModules: false,
91+
]
92+
8493
apply from: "../../node_modules/react-native/react.gradle"
8594
apply from: "../../../sentry.gradle"
8695

sample/ios/sample.xcodeproj/project.pbxproj

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -384,7 +384,7 @@
384384
);
385385
runOnlyForDeploymentPostprocessing = 0;
386386
shellPath = /bin/sh;
387-
shellScript = "export SENTRY_PROPERTIES=sentry.properties\nexport EXTRA_PACKAGER_ARGS=\"--sourcemap-output $DERIVED_FILE_DIR/main.jsbundle.map\"\nset -e\n\nWITH_ENVIRONMENT=\"../node_modules/react-native/scripts/xcode/with-environment.sh\"\nREACT_NATIVE_XCODE=\"../node_modules/react-native/scripts/react-native-xcode.sh\"\n\n/bin/sh -c \"$WITH_ENVIRONMENT \\\"../../node_modules/@sentry/cli/bin/sentry-cli react-native xcode $REACT_NATIVE_XCODE\\\"\"\n";
387+
shellScript = "export SENTRY_PROPERTIES=sentry.properties\nexport EXTRA_PACKAGER_ARGS=\"--sourcemap-output $DERIVED_FILE_DIR/main.jsbundle.map\"\nset -e\n\nWITH_ENVIRONMENT=\"../node_modules/react-native/scripts/xcode/with-environment.sh\"\nREACT_NATIVE_XCODE=\"../node_modules/react-native/scripts/react-native-xcode.sh\"\n\n/bin/sh -c \"$WITH_ENVIRONMENT \\\"../../node_modules/@sentry/cli/bin/sentry-cli react-native xcode $REACT_NATIVE_XCODE\\\"\"\n\nexport MODULES_PATHS=\"$PWD/../node_modules,$PWD/../../..\"\n/bin/sh ../../scripts/collect-modules.sh\n";
388388
};
389389
0E8F2596BF2AF7D4635FC910 /* [CP] Embed Pods Frameworks */ = {
390390
isa = PBXShellScriptBuildPhase;

sample/metro.config.js

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,6 @@
77

88
const path = require('path');
99
const blacklist = require('metro-config/src/defaults/exclusionList');
10-
const resolve = require('metro-resolver/src/resolve');
11-
1210
const parentDir = path.resolve(__dirname, '..');
1311

1412
module.exports = {

scripts/collect-modules.sh

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
#!/bin/bash
2+
3+
# Print commands before executing them (useful for troubleshooting)
4+
set -x
5+
6+
if [[ "$CONFIGURATION" = *Debug* ]]; then
7+
echo "Debug build. Modules are not collected."
8+
exit 0
9+
fi
10+
11+
if [[ -z "$CONFIGURATION_BUILD_DIR" ]]; then
12+
echo "Missing env CONFIGURATION_BUILD_DIR" 1>&2
13+
exit 1
14+
fi
15+
16+
if [[ -z "$UNLOCALIZED_RESOURCES_FOLDER_PATH" ]]; then
17+
echo "Missing env UNLOCALIZED_RESOURCES_FOLDER_PATH" 1>&2
18+
exit 1
19+
fi
20+
21+
if [[ -z "$DERIVED_FILE_DIR" ]]; then
22+
echo "Missing env DERIVED_FILE_DIR" 1>&2
23+
exit 1
24+
fi
25+
26+
nodePath="node"
27+
thisFilePath=$(dirname $0)
28+
collectModulesScript="$thisFilePath/../dist/js/tools/collectModules.js"
29+
30+
destination="$CONFIGURATION_BUILD_DIR/$UNLOCALIZED_RESOURCES_FOLDER_PATH"
31+
modulesOutput="$destination/modules.json"
32+
if [[ -z "$SOURCE_MAP_PATH" ]]; then
33+
sourceMap="$DERIVED_FILE_DIR/main.jsbundle.map"
34+
else
35+
sourceMap="$SOURCE_MAP_PATH"
36+
fi
37+
if [[ -z "$MODULES_PATHS" ]]; then
38+
modulesPaths="$PWD/../node_modules"
39+
else
40+
modulesPaths="$MODULES_PATHS"
41+
fi
42+
43+
$nodePath $collectModulesScript $sourceMap $modulesOutput $modulesPaths

sentry.gradle

Lines changed: 48 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,8 @@ gradle.projectsEvaluated {
4141
def bundleOutput
4242
def props = bundleTask.getProperties()
4343
def reactRoot = props.get("workingDir")
44+
def modulesOutput = "$reactRoot/android/app/src/main/assets/modules.json"
45+
def modulesTask = null
4446

4547
(shouldCleanUp, bundleOutput, sourcemapOutput) = forceSourceMapOutputFromBundleTask(bundleTask)
4648

@@ -60,6 +62,7 @@ gradle.projectsEvaluated {
6062
def previousCliTask = null
6163

6264
def nameCleanup = "${bundleTask.name}_SentryUploadCleanUp"
65+
def nameModulesCleanup = "${bundleTask.name}_SentryCollectModulesCleanUp"
6366
// Upload the source map several times if necessary: once for each release and versionCode.
6467
currentVariants.each { key, currentVariant ->
6568
variant = currentVariant[0]
@@ -79,6 +82,7 @@ gradle.projectsEvaluated {
7982
// below) and distribution identifier (`--dist` below). Give the task a unique name
8083
// based on where we're uploading to.
8184
def nameCliTask = "${bundleTask.name}_SentryUpload_${releaseName}_${versionCode}"
85+
def nameModulesTask = "${bundleTask.name}_SentryCollectModules_${releaseName}_${versionCode}"
8286

8387
// If several outputs have the same releaseName and versionCode, we'd do the exact same
8488
// upload for each of them. No need to repeat.
@@ -153,6 +157,34 @@ gradle.projectsEvaluated {
153157
enabled true
154158
}
155159

160+
modulesTask = tasks.create(nameModulesTask, Exec) {
161+
description = "collect javascript modules from bundle source map"
162+
group = 'sentry.io'
163+
164+
workingDir reactRoot
165+
166+
def collectModulesScript = config.containsKey('collectModulesScript')
167+
? file(config.collectModulesScript).getAbsolutePath()
168+
: "$reactRoot/node_modules/@sentry/react-native/dist/js/tools/collectModules.js"
169+
def modulesPaths = config.containsKey('modulesPaths')
170+
? config.modulesPaths.join(',')
171+
: "$reactRoot/node_modules"
172+
def args = ["node",
173+
collectModulesScript,
174+
sourcemapOutput,
175+
modulesOutput,
176+
modulesPaths
177+
]
178+
179+
project.logger.info("Sentry-CollectModules arguments: ${args}")
180+
commandLine(*args)
181+
182+
def skip = config.containsKey('skipCollectModules')
183+
? config.skipCollectModules == true
184+
: false
185+
enabled !skip
186+
}
187+
156188
// chain the upload tasks so they run sequentially in order to run
157189
// the cliCleanUpTask after the final upload task is run
158190
if (previousCliTask != null) {
@@ -161,6 +193,20 @@ gradle.projectsEvaluated {
161193
bundleTask.finalizedBy cliTask
162194
}
163195
previousCliTask = cliTask
196+
cliTask.finalizedBy modulesTask
197+
198+
def modulesCleanUpTask = tasks.create(name: nameModulesCleanup, type: Delete) {
199+
description = "clean up collected modules generated file"
200+
group = 'sentry.io'
201+
202+
delete modulesOutput
203+
}
204+
205+
def packageTasks = tasks.findAll { task -> "package${variant}".equalsIgnoreCase(task.name) && task.enabled }
206+
packageTasks.each { packageTask ->
207+
packageTask.dependsOn modulesTask
208+
packageTask.finalizedBy modulesCleanUpTask
209+
}
164210
}
165211

166212
/** Delete sourcemap files */
@@ -169,7 +215,8 @@ gradle.projectsEvaluated {
169215
group = 'sentry.io'
170216

171217
delete sourcemapOutput
172-
delete "$buildDir/intermediates/assets/release/index.android.bundle.map" // react native default bundle dir
218+
delete "$buildDir/intermediates/assets/release/index.android.bundle.map"
219+
// react native default bundle dir
173220
}
174221

175222
// register clean task extension

0 commit comments

Comments
 (0)