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);