diff --git a/docs/index.rst b/docs/index.rst
index 89c6b48c2d..0be7887663 100644
--- a/docs/index.rst
+++ b/docs/index.rst
@@ -5,143 +5,95 @@
React Native
============
-This is the documentation for our beta clients for React-Native. This is
-an early release with various different levels of support. iOS is best
-supported if you are also using the native extension and if not we fall
-back to pure JavaScript for basic support.
-
-We would love to get your feedback!
+This is the documentation for our beta clients for React-Native. The
+React-Native client uses a native extension for iOS and Android but can
+fall back to a pure JavaScript version if needed.
Installation
------------
-Start with adding sentry and linking it::
+Start by adding Sentry and then linking it::
$ npm install react-native-sentry --save
$ react-native link react-native-sentry
-The `link` step will pull in the native dependency. If you are using
-expo you don't have to (or can't) run that step. In that case we fall
-back automatically.
+The `link` step will pull in the native dependency and patch your project
+accordingly. If you are using expo you don't have to (or can't) run that
+link step. For more information about that see :doc:`expo`.
+
+On linking you will automatically be prompted for your DSN and other
+information and we will configure your app automatically for react-native
+and change files accordingly. You will need to provide the following
+data: your DSN, the slug of your organization in Sentry, the slug of your
+project in Sentry as well as the API key.
+
+You can find the slugs in the URL of your project
+(``sentry.io/your-org-slug/your-project-slug``) If you don't have an auth
+token yet you can `create an auth token here `_.
-On linking you will usually be prompted for your DSN and we will configure
-your app automatically for react-native and change files accordingly.
Upon linking the following changes will be performed:
-* added the raven-java package for native crash reporting on android
-* added the sentry-swift package for native crash reporting on iOS
-* enabled the sentry gradle build step for android
+* add the raven-java package for native crash reporting on Android
+* add the sentry-swift package for native crash reporting on iOS
+* enable the sentry gradle build step for android
* patch `AppDelegate.m` for iOS
* patch `MainApplication.java` for Android
-* configured Sentry for the supplied DSN in your `index.js` files
+* configure Sentry for the supplied DSN in your `index.js` files
+* store build credentials in `ios/sentry.properties` and
+ `android/sentry.properties`.
+
+To see what is happening during linking you can refer to
+:doc:`manual-setup` which will give you all the details.
Note that we only support ``react-native >= 0.38`` at the moment.
+Upgrading
+---------
+
+If you are upgrading from an earlier version of sentry-react-native you
+should re-link the package to ensure the generated code is updated to the
+latest version::
+
+ $ react-native unlink react-native-sentry
+ $ react-native link react-native-sentry
+
iOS Specifics
-------------
Since we use our `Swift Client
`_ in the background, your
-project has to embed the swift standard libraries.
-
-Search for ``ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES`` in your Xcode project
-build settings and set it to ``YES``.
-
-You will get this error message if you forget to set it::
-
- dyld: Library not loaded: @rpath/libswiftCore.dylib
- Referenced from: [Redacted]/Sentry.framework/Sentry
- Reason: image not found
-
-Also note that if you build the project without setting this, you have to
-run clean in order to make the change work.
+project has to embed the swift standard libraries. The link step will do
+this automatically for your project.
When you use xcode you can hook directly into the build process to upload
-debug symbols. Open up your xcode project in the iOS folder, go to your
-project's target and change the "Bundle React Native code and images"
-build script. The script that is currently there needs to be adjusted as
-follows::
-
- export SENTRY_ORG=___ORG_NAME___
- export SENTRY_PROJECT=___PROJECT_NAME___
- export SENTRY_AUTH_TOKEN=YOUR_AUTH_TOKEN
- export NODE_BINARY=node
- ../node_modules/react-native-sentry/bin/bundle-frameworks
- ../node_modules/sentry-cli-binary/bin/sentry-cli react-native-xcode \
- ../node_modules/react-native/packager/react-native-xcode.sh
- ../node_modules/sentry-cli-binary/bin/sentry-cli upload-dsym
-
-You can find the slugs in the URL of your project
-(sentry.io/your-org-slug/your-project-slug) If you don't have an auth
-token yet you can `create an auth token here `_.
-
-This also uploads debug symbols in the last line which however will not
-work for bitcode enabled builds. If you are using bitcode you need to
-remove that line (``../node_modules/sentry-cli-binary/bin/sentry-cli
-upload-dsym``) and consult the documentation on dsym handling instead (see
-:ref:`dsym-with-bitcode`).
-
-Note that uploading of debug simulator builds by default is disabled for
-speed reasons. If you do want to also generate debug symbols for debug
-builds you can pass `--allow-fetch` as a parameter to ``react-native-xcode``.
+debug symbols and sourcemaps. If you however are using bitcode you will
+need to disable the "Upload Debug Symbols to Sentry" build phase and then
+separately upload debug symbols from iTunes Connect to Sentry.
Android Specifics
-----------------
For Android we hook into gradle for the sourcemap build process. When you
-run ``react-native link`` the gradle files are automatically updated but
-in case you are not using linked frameworks you might have to do it
-manually. Whenever you run ``./gradlew assembleRelease`` sourcemaps are
-automatically built and uploaded to Sentry.
-
-To enable the gradle integration you need to change your
-``android/app/build.gradle`` file and add the following line after the
-``react.gradle`` one::
-
- apply from: "../../node_modules/react-native-sentry/sentry.gradle"
-
-Additionally you need to create an ``android/sentry.properties`` file with
-the access credentials:
-
-.. sourcecode:: ini
-
- defaults.org=___ORG_NAME___
- defaults.project=___PROJECT_NAME___
- auth.token=YOUR_AUTH_TOKEN
+run ``react-native link`` the gradle files are automatically updated.
+When you run ``./gradlew assembleRelease`` sourcemaps are automatically
+built and uploaded to Sentry.
Client Configuration
--------------------
-Note: When you run ``react-native link`` we will attempt to automatically
-patch your code so you might notice that some of these changes were
-already performed.
-
-Add Sentry to your `index.ios.js` and `index.android.js`:
+Note: When you run ``react-native link`` we will automatically update your
+`index.ios.js` / `index.android.js` with the following changes:
.. sourcecode:: javascript
import { Sentry } from 'react-native-sentry';
-
Sentry.config('___DSN___').install();
-If you are using the binary version of the package (eg: you ran
-``react-native link``) then you additionally need to register the native
-crash handler in your `AppDelegate.m` after the root view was created for
-iOS:
-
-.. sourcecode:: objc
-
- #if __has_include()
- #import // This is used for versions of react >= 0.40
- #else
- #import "RNSentry.h" // This is used for versions of react < 0.40
- #endif
-
- /* in your didFinishLaunchingWithOptions */
- [RNSentry installWithRootView:rootView];
+You can pass additional configuration options to the `config()` method if
+you want to do so.
-More
-----
+Deep Dive
+---------
.. toctree::
:maxdepth: 2
@@ -150,3 +102,4 @@ More
expo
sourcemaps
cocoapods
+ manual-setup
diff --git a/docs/manual-setup.rst b/docs/manual-setup.rst
new file mode 100644
index 0000000000..4db71d14f7
--- /dev/null
+++ b/docs/manual-setup.rst
@@ -0,0 +1,71 @@
+Manual Setup
+============
+
+If you can't (or don't want) to run the linking step you can see here what
+is happening on each platform.
+
+iOS
+---
+
+Since we use our `Swift Client
+`_ in the background, your
+project has to embed the swift standard libraries.
+
+Xcode Settings
+``````````````
+
+The link step sets ``ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES`` in your Xcode
+project's build settings to ``YES``.
+
+You will get this error message if that setting is not set::
+
+ dyld: Library not loaded: @rpath/libswiftCore.dylib
+ Referenced from: [Redacted]/Sentry.framework/Sentry
+ Reason: image not found
+
+Build Steps
+```````````
+
+When you use Xcode you can hook directly into the build process to upload
+debug symbols. When linking one build phase script is changed and two more
+are added.
+
+We modify the react-native build phase ("Bundle React Native code and images")
+slightly from this::
+
+ export NODE_BINARY=node
+ ../node_modules/react-native/packager/react-native-xcode.sh
+
+To this::
+
+ export NODE_BINARY=node
+ export SENTRY_PROPERTIES=sentry.properties
+ ../node_modules/sentry-cli-binary/bin/sentry-cli react-native-xcode \
+ ../node_modules/react-native/packager/react-native-xcode.sh
+
+Additionally we add a build script called "Bundle react-native-sentry
+Frameworks" which bundles necessary frameworks as well as a build
+step called "Upload Debug Symbols to Sentry" which uploads debug symbols
+to Sentry. The latter needs to be disabled if you use bitcode.
+
+This also uploads debug symbols in the last line which however will not
+work for bitcode enabled builds. If you are using bitcode you need to
+remove that line (``../node_modules/sentry-cli-binary/bin/sentry-cli
+upload-dsym``) and consult the documentation on dsym handling instead (see
+:ref:`dsym-with-bitcode`).
+
+Note that uploading of debug simulator builds by default is disabled for
+speed reasons. If you do want to also generate debug symbols for debug
+builds you can pass `--allow-fetch` as a parameter to ``react-native-xcode``
+in the above mentioned build phase.
+
+Android
+-------
+
+For Android we hook into gradle for the sourcemap build process. When you
+run ``react-native link`` the gradle files are automatically updated.
+
+We enable the gradle integration in your ``android/app/build.gradle`` file
+by adding the following line after the ``react.gradle`` one::
+
+ apply from: "../../node_modules/react-native-sentry/sentry.gradle"
diff --git a/package.json b/package.json
index 186b0e63a3..e368c23b5f 100644
--- a/package.json
+++ b/package.json
@@ -20,10 +20,12 @@
"react-native": ">=0.38.0"
},
"dependencies": {
+ "chalk": "^1.1.1",
+ "glob": "7.1.1",
+ "inquirer": "3.0.6",
"raven-js": "^3.15.0",
"sentry-cli-binary": "^1.9.0",
- "inquirer": "3.0.6",
- "glob": "7.1.1"
+ "xcode": "0.9.3"
},
"rnpm": {
"commands": {
diff --git a/scripts/postlink b/scripts/postlink
index b3796f0ad9..04fc779ecd 100644
--- a/scripts/postlink
+++ b/scripts/postlink
@@ -1,8 +1,11 @@
-let glob = require('glob');
-let fs = require('fs');
-let inquirer = require('inquirer');
+const glob = require('glob');
+const fs = require('fs');
+const inquirer = require('inquirer');
+const xcode = require('xcode');
+const chalk = require('chalk');
-let OBJC_HEADER = '\
+const PLATFORMS = ['android', 'ios'];
+const OBJC_HEADER = '\
#if __has_include()\n\
#import // This is used for versions of react >= 0.40\n\
#else\n\
@@ -10,23 +13,148 @@ let OBJC_HEADER = '\
#endif';
let cachedDsn = null;
+let cachedProps = {};
let patchedAny = false;
+let didShowInfoHint = false;
+let configurePlatform = {};
+
+function getPlatformName(platform) {
+ return {
+ 'android': 'Android',
+ 'ios': 'iOS',
+ }[platform] || platform;
+}
+
+function considerShowingInfoHint() {
+ if (didShowInfoHint) {
+ return;
+ }
+
+ let {green, dim} = chalk;
+ function l(msg) { console.log(msg); }
+
+
+ l('');
+ l(green('You are about to configure Sentry for React Native'));
+ l(dim('We will ask you a bunch of questions to configure Sentry for you.'));
+ l(dim('If you chose not to configure an integration you can run link again'));
+ l(dim('later to configure that platform.'));
+ l('');
+ l('You will need the DSN and an API key for the application to proceed.');
+ l('The keys can be found the project settings and at sentry.io/api/');
+ l('');
+ didShowInfoHint = true;
+}
+
+function shouldConfigurePlatform(platform) {
+ if (configurePlatform[platform] !== undefined) {
+ return Promise.resolve(configurePlatform[platform]);
+ }
+ // if a sentry.properties file exists for the platform we want to configure
+ // without asking the user. This means that re-linking later will not
+ // bring up a useless dialog.
+ if (fs.existsSync(platform + '/sentry.properties')) {
+ configurePlatform[platform] = true;
+ return Promise.resolve(true);
+ }
+ considerShowingInfoHint();
+ return inquirer.prompt([{
+ type: 'list',
+ name: 'configure',
+ message: `Do you want to configure Sentry for ${getPlatformName(platform)}?`,
+ choices: [
+ {
+ name: 'Yes',
+ value: true
+ },
+ {
+ name: 'No (or later)',
+ value: false
+ }
+ ]
+ }]).then(function(answers) {
+ configurePlatform[platform] = answers.configure;
+ return Promise.resolve(answers.configure);
+ });
+}
function getDsn(platform) {
+ considerShowingInfoHint();
return inquirer.prompt([{
type: 'input',
default: cachedDsn || process.env.SENTRY_DSN || 'YOUR_DSN_HERE',
- message: 'The DSN for ' + {
- 'android': 'Android',
- 'ios': 'iOS',
- }[platform] || platform,
+ message: 'The DSN for ' + getPlatformName(platform),
name: 'dsn',
+ validate: function(value) {
+ let m = value.match(/^(?:(\w+):)?\/\/(?:(\w+)(:\w+)?@)?([\w\.-]+)(?::(\d+))?(\/.*)$/);
+ if (!m) {
+ return 'invalid dsn format';
+ }
+ if (m[1] !== 'http' && m[1] !== 'https') {
+ return 'unsupported protocol for dsn: ' + m[1];
+ }
+ if (!m[3]) {
+ return 'missing secret in dsn';
+ }
+ return true;
+ }
}]).then(function(answers) {
cachedDsn = answers.dsn;
return Promise.resolve(answers.dsn);
});
}
+function getDefaultUrl() {
+ if (cachedDsn) {
+ let match = cachedDsn.match(/^(https?).*?@(.*?)\//);
+ if (match) {
+ return match[1] + '://' + match[2] + '/';
+ }
+ }
+ return 'https://sentry.io/';
+}
+
+function getProperties(platform) {
+ return inquirer.prompt([{
+ type: 'input',
+ default: cachedProps['defaults/url'] || process.env.SENTRY_URL || getDefaultUrl(),
+ message: 'The Sentry Server URL for ' + getPlatformName(platform),
+ name: 'defaults/url',
+ }, {
+ type: 'input',
+ default: cachedProps['defaults/org'] || process.env.SENTRY_ORG || 'your-org-slug',
+ message: 'The Organization for ' + getPlatformName(platform),
+ name: 'defaults/org',
+ }, {
+ type: 'input',
+ default: cachedProps['defaults/project'] || process.env.SENTRY_PROJECT || 'your-project-slug',
+ message: 'The Project for ' + getPlatformName(platform),
+ name: 'defaults/project',
+ }, {
+ type: 'password',
+ default: cachedProps['auth/token'] || process.env.SENTRY_AUTH_TOKEN || 'YOUR_AUTH_TOKEN',
+ message: 'The Auth-Token for ' + getPlatformName(platform),
+ name: 'auth/token',
+ }]).then(function(answers) {
+ cachedProps = answers;
+ return Promise.resolve(answers);
+ });
+}
+
+function dumpProperties(props) {
+ let rv = [];
+ for (let key in props) {
+ let value = props[key];
+ key = key.replace(/\//g, '.');
+ if (value === undefined || value === null) {
+ rv.push('#' + key + '=');
+ } else {
+ rv.push(key + '=' + value);
+ }
+ }
+ return rv.join('\n') + '\n';
+}
+
function patchAppDelegate(contents) {
// add the header if it's not there yet.
if (!contents.match(/#import "RNSentry.h"/)) {
@@ -56,31 +184,163 @@ function patchAppDelegate(contents) {
function patchIndexJs(contents, filename) {
// since the init call could live in other places too, we really only
// want to do this if we managed to patch any of the other files as well.
- if (contents.match(/Sentry.config\(/) && patchedAny) {
- return Promise.resolve(contents);
+ if (contents.match(/Sentry.config\(/) || !patchedAny) {
+ return Promise.resolve(null);
}
let platform = filename.match(/index\.([^.]+?)\.js/)[1];
- return getDsn(platform).then(function(dsn) {
- return Promise.resolve(contents.replace(/^([^]*)(import\s+[^;]*?;$)/m, function(match) {
- return match + '\n\nimport { Sentry } from \'react-native-sentry\';\n\n' +
- 'Sentry.config(' + JSON.stringify(dsn) + ').install();\n';
- }));
+ return shouldConfigurePlatform(platform).then((shouldConfigure) => {
+ if (!shouldConfigure) {
+ return null;
+ }
+
+ return getDsn(platform).then(function(dsn) {
+ return Promise.resolve(contents.replace(/^([^]*)(import\s+[^;]*?;$)/m, function(match) {
+ return match + '\n\nimport { Sentry } from \'react-native-sentry\';\n\n' +
+ 'Sentry.config(' + JSON.stringify(dsn) + ').install();\n';
+ }));
+ });
});
}
function patchBuildGradle(contents) {
let applyFrom = 'apply from: "../../node_modules/react-native-sentry/sentry.gradle"';
if (contents.indexOf(applyFrom) >= 0) {
- return Promise.resolve(contents);
+ return Promise.resolve(null);
+ }
+
+ return shouldConfigurePlatform('android').then((shouldConfigure) => {
+ if (!shouldConfigure) {
+ return null;
+ }
+
+ return Promise.resolve(contents.replace(
+ /^apply from: "..\/..\/node_modules\/react-native\/react.gradle"/m,
+ function(match) {
+ return match + '\n' + applyFrom;
+ }
+ ));
+ });
+}
+
+function patchAlwaysIncludeSwift(proj) {
+ let nativeTargets = proj.hash.project.objects.PBXNativeTarget;
+ let buildConfigs = proj.pbxXCBuildConfigurationSection();
+
+ for (let key in nativeTargets) {
+ let data = nativeTargets[key];
+ if (typeof data === 'string') {
+ continue;
+ }
+
+ if (!data.productReference_comment.match(/\.app$/)) {
+ continue;
+ }
+
+ let cfgList = proj.pbxXCConfigurationList()[data.buildConfigurationList];
+ if (!cfgList) {
+ continue;
+ }
+
+ for (let cfgRef of cfgList.buildConfigurations) {
+ let cfg = buildConfigs[cfgRef.value];
+ cfg.buildSettings.ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = 'YES';
+ }
}
+}
- return Promise.resolve(contents.replace(
- /^apply from: "..\/..\/node_modules\/react-native\/react.gradle"/m,
- function(match) {
- return match + '\n' + applyFrom;
+function patchExistingXcodeBuildScripts(buildScripts) {
+ for (let script of buildScripts) {
+ if (!script.shellScript.match(/packager\/react-native-xcode\.sh\b/) ||
+ script.shellScript.match(/sentry-cli\s+react-native-xcode/)) {
+ continue;
}
- ));
+ let code = JSON.parse(script.shellScript);
+ code = (
+ 'export SENTRY_PROPERTIES=sentry.properties\n' +
+ code.replace(/^.*?\/packager\/react-native-xcode\.sh\s*/m, function(match) {
+ return '../node_modules/sentry-cli-binary/bin/sentry-cli react-native-xcode ' + match;
+ })
+ );
+ script.shellScript = JSON.stringify(code);
+ }
+}
+
+function addNewXcodeBuildPhaseForSymbols(buildScripts, proj) {
+ for (let script of buildScripts) {
+ if (script.shellScript.match(/sentry-cli\s+upload-dsym/)) {
+ return;
+ }
+ }
+
+ proj.addBuildPhase(
+ [],
+ 'PBXShellScriptBuildPhase',
+ 'Upload Debug Symbols to Sentry',
+ null,
+ {
+ shellPath: '/bin/sh',
+ shellScript: (
+ 'export SENTRY_PROPERTIES=sentry.properties\\n' +
+ '../node_modules/sentry-cli-binary/bin/sentry-cli upload-dsym'
+ )
+ }
+ );
+}
+
+function addNewXcodeBuildPhaseForBundleFw(buildScripts, proj) {
+ for (let script of buildScripts) {
+ if (script.shellScript.match(/react-native-sentry\/bin\/bundle-frameworks/)) {
+ return;
+ }
+ }
+
+ proj.addBuildPhase(
+ [],
+ 'PBXShellScriptBuildPhase',
+ 'Bundle react-native-sentry Frameworks',
+ null,
+ {
+ shellPath: '/bin/sh',
+ shellScript: (
+ '../node_modules/react-native-sentry/bin/bundle-frameworks'
+ )
+ }
+ );
+}
+
+function patchXcodeProj(contents, filename) {
+ let proj = xcode.project(filename);
+ return new Promise(function(resolve, reject) {
+ proj.parse(function(err) {
+ if (err) {
+ reject(err);
+ return;
+ }
+
+ let buildScripts = Object.values(
+ proj.hash.project.objects.PBXShellScriptBuildPhase || {})
+ .filter((val) => val.isa);
+
+ patchAlwaysIncludeSwift(proj);
+ patchExistingXcodeBuildScripts(buildScripts);
+ addNewXcodeBuildPhaseForSymbols(buildScripts, proj);
+ addNewXcodeBuildPhaseForBundleFw(buildScripts, proj);
+
+ // we always modify the xcode file in memory but we only want to save it
+ // in case the user wants configuration for ios. This is why we check
+ // here first if changes are made before we might prompt the platform
+ // continue prompt.
+ let newContents = proj.writeSync();
+ if (newContents === contents) {
+ resolve(null);
+ } else {
+ return shouldConfigurePlatform('ios').then((shouldConfigure) => {
+ resolve(shouldConfigure ? newContents : null);
+ });
+ }
+ });
+ });
}
function patchMatchingFile(pattern, func) {
@@ -93,7 +353,7 @@ function patchMatchingFile(pattern, func) {
encoding: 'utf-8'
});
rv = rv.then(() => func(contents, match)).then(function(newContents) {
- if (contents != newContents) {
+ if (newContents !== null && contents !== undefined && contents != newContents) {
patchedAny = true;
fs.writeFileSync(match, newContents);
}
@@ -102,10 +362,46 @@ function patchMatchingFile(pattern, func) {
return rv;
}
+function addSentryInit() {
+ let rv = Promise.resolve();
+ for (let platform of PLATFORMS) {
+ rv = rv.then(() => patchMatchingFile(`index.${platform}.js`, patchIndexJs));
+ }
+ return rv;
+}
+
+function addSentryProperties() {
+ let rv = Promise.resolve();
+
+ for (let platform of PLATFORMS) {
+ let fn = platform + '/sentry.properties';
+ if (fs.existsSync(fn)) {
+ continue;
+ }
+
+ rv = rv.then(() => shouldConfigurePlatform(platform).then((shouldConfigure) => {
+ if (!shouldConfigure) {
+ return null;
+ }
+ return getProperties(platform).then((props) => {
+ fs.writeFileSync(fn, dumpProperties(props));
+ });
+ }));
+ }
+
+ return rv;
+}
+
Promise.resolve()
- .then(() => patchMatchingFile('**/AppDelegate.m', patchAppDelegate))
+ /* these steps patch the build files without user interactions */
.then(() => patchMatchingFile('**/app/build.gradle', patchBuildGradle))
- .then(() => patchMatchingFile('index.*.js', patchIndexJs))
+ .then(() => patchMatchingFile('**/*.xcodeproj/project.pbxproj', patchXcodeProj))
+ .then(() => patchMatchingFile('**/AppDelegate.m', patchAppDelegate))
+ /* if any of the previous steps did something, this will patch
+ the index.PLATFORM.js files with the necessary initialization code */
+ .then(() => addSentryInit())
+ /* writes sentry.properties files with the API key and other settings */
+ .then(() => addSentryProperties())
.catch(function(e) {
console.log('Could not link react-native-sentry: ' + e);
return Promise.resolve();
diff --git a/scripts/postunlink b/scripts/postunlink
index 23a1b6009f..7b37183915 100644
--- a/scripts/postunlink
+++ b/scripts/postunlink
@@ -1,24 +1,112 @@
let glob = require('glob');
let fs = require('fs');
+let xcode = require('xcode');
function unpatchAppDelegate(contents) {
return Promise.resolve(contents
- .replace(/^#if __has_include\()[^]*?\#endif\r?\n/m, '')
- .replace(/^\s*\[RNSentry\s+installWithRootView:.*?\];\r?\n/m, ''));
-}
-
-function unpatchIndexJs(contents, filename) {
- return Promise.resolve(contents
- .replace(/^\s*Sentry\.config\((.*?)\);?\s*\r?\n/mg, '');
- .replace(/(\r?\n){,2}import\s*\{[^]*?\}\s*from\s+['"]react-native-sentry['"];\s*(\r?\n){,2}/mg, ''));
+ .replace(/^#if __has_include\(\)[^]*?\#endif\r?\n/m, '')
+ .replace(/^#import\s+(?:|"RNSentry.h")\s*?\r?\n/m, '')
+ .replace(/(\r?\n|^)\s*\[RNSentry\s+installWithRootView:.*?\];\s*?\r?\n/m, ''));
}
function unpatchBuildGradle(contents) {
return Promise.resolve(contents.replace(
- /^\s*apply from: ["']..\/..\/node_modules\/react-native-sentry\/sentry.gradle["'];\s*\r?\n/m,
+ /^\s*apply from: ["']..\/..\/node_modules\/react-native-sentry\/sentry.gradle["'];?\s*?\r?\n/m,
''));
}
+function unpatchXcodeBuildScripts(proj) {
+ let scripts = proj.hash.project.objects.PBXShellScriptBuildPhase || {};
+ let firstTarget = proj.getFirstTarget().uuid;
+ let nativeTargets = proj.hash.project.objects.PBXNativeTarget;
+
+ // scripts to patch partially. Run this first so that we don't
+ // accidentally delete some scripts later entirely that we only want to
+ // rewrite.
+ for (let key of Object.keys(scripts)) {
+ let script = scripts[key];
+
+ // ignore comments
+ if (typeof script === 'string') {
+ continue;
+ }
+
+ // ignore scripts that do not invoke the react-native-xcode command.
+ if (!script.shellScript.match(/sentry-cli\s+react-native-xcode\b/)) {
+ continue;
+ }
+
+ script.shellScript = JSON.stringify(JSON.parse(script.shellScript)
+ // "legacy" location for this. This is what happens if users followed
+ // the old documentation for where to add the bundle command
+ .replace(
+ /^..\/node_modules\/react-native-sentry\/bin\/bundle-frameworks\s*?\r\n?/m,
+ '')
+ // legacy location for dsym upload
+ .replace(
+ /^..\/node_modules\/sentry-cli-binary\/bin\/sentry-cli upload-dsym\s*?\r?\n/m,
+ '')
+ // remove sentry properties export
+ .replace(
+ /^export SENTRY_PROPERTIES=sentry.properties\r?\n/m,
+ '')
+ // unwrap react-native-xcode.sh command. In case someone replaced it
+ // entirely with the sentry-cli command we need to put the original
+ // version back in.
+ .replace(
+ /^(?:..\/node_modules\/sentry-cli-binary\/bin\/)?sentry-cli\s+react-native-xcode(\s+.*?)$/m,
+ function(match, m1) {
+ let rv = m1.trim();
+ if (rv === '') {
+ return '../node_modules/react-native/packager/react-native-xcode.sh';
+ } else {
+ return rv;
+ }
+ }));
+ }
+
+ // scripts to kill entirely.
+ for (let key of Object.keys(scripts)) {
+ let script = scripts[key];
+
+ // ignore comments and keys that got deleted
+ if (typeof script === 'string' || script === undefined) {
+ continue;
+ }
+
+ if (script.shellScript.match(/react-native-sentry\/bin\/bundle-frameworks\b/) ||
+ script.shellScript.match(/sentry-cli-binary\/bin\/sentry-cli\s+upload-dsym\b/)) {
+ delete scripts[key];
+ delete scripts[key + '_comment'];
+ let phases = nativeTargets[firstTarget].buildPhases;
+ if (phases) {
+ for (let i = 0; i < phases.length; i++) {
+ if (phases[i].value === key) {
+ phases.splice(i, 1);
+ break;
+ }
+ }
+ }
+ continue;
+ }
+ }
+}
+
+function unpatchXcodeProj(contents, filename) {
+ let proj = xcode.project(filename);
+ return new Promise(function(resolve, reject) {
+ proj.parse(function(err) {
+ if (err) {
+ reject(err);
+ return;
+ }
+
+ unpatchXcodeBuildScripts(proj);
+ resolve(proj.writeSync());
+ });
+ });
+}
+
function patchMatchingFile(pattern, func) {
let matches = glob.sync(pattern, {
ignore: 'node_modules/**'
@@ -38,8 +126,8 @@ function patchMatchingFile(pattern, func) {
}
Promise.resolve()
+ .then(() => patchMatchingFile('**/*.xcodeproj/project.pbxproj', unpatchXcodeProj))
.then(() => patchMatchingFile('**/AppDelegate.m', unpatchAppDelegate))
- .then(() => patchMatchingFile('index.*.js', unpatchIndexJs))
.then(() => patchMatchingFile('**/app/build.gradle', unpatchBuildGradle))
.catch(function(e) {
console.log('Could not unlink react-native-sentry: ' + e);