-
-
Notifications
You must be signed in to change notification settings - Fork 354
feat(expo): Add RNSentrySDK APIs support to @sentry/react-native/expo plugin #4633
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: capture-app-start-errors-v7
Are you sure you want to change the base?
Changes from 33 commits
313e844
2e97acc
6eedaae
9ae5475
566550e
770c9f4
f8b37b5
d25db30
adc81a5
8c2cd73
a2b5575
5f4f7c5
0431cc3
62d39cc
235f3ef
369cce7
a53c7f4
5e4a98f
dce74b2
0ffd26c
744993c
5447be9
0b3423f
5c615fd
c356288
8e32556
a20984c
d1db4fa
7c25c2c
1918baf
b2a89f2
3885d70
d705efb
c318d40
1c2a7b7
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,41 @@ | ||
| const warningMap = new Map<string, boolean>(); | ||
|
|
||
| /** | ||
| * Log a warning message only once per run. | ||
| * This is used to avoid spamming the console with the same message. | ||
| */ | ||
| export function warnOnce(message: string): void { | ||
| if (!warningMap.has(message)) { | ||
| warningMap.set(message, true); | ||
| // eslint-disable-next-line no-console | ||
| console.warn(yellow(prefix(message))); | ||
| } | ||
| } | ||
|
|
||
| /** | ||
| * Prefix message with `› [value]`. | ||
| * | ||
| * Example: | ||
| * ``` | ||
| * › [@sentry/react-native/expo] This is a warning message | ||
| * ``` | ||
| */ | ||
| export function prefix(value: string): string { | ||
| return `› ${bold('[@sentry/react-native/expo]')} ${value}`; | ||
| } | ||
|
|
||
| /** | ||
| * The same as `chalk.yellow` | ||
| * This code is part of the SDK, we don't want to introduce a dependency on `chalk` just for this. | ||
| */ | ||
| export function yellow(message: string): string { | ||
| return `\x1b[33m${message}\x1b[0m`; | ||
| } | ||
|
|
||
| /** | ||
| * The same as `chalk.bold` | ||
| * This code is part of the SDK, we don't want to introduce a dependency on `chalk` just for this. | ||
| */ | ||
| export function bold(message: string): string { | ||
| return `\x1b[1m${message}\x1b[22m`; | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,8 @@ | ||
| const packageJson: { | ||
| name: string; | ||
| version: string; | ||
| // eslint-disable-next-line @typescript-eslint/no-var-requires | ||
| } = require('../../package.json'); | ||
|
|
||
| export const PLUGIN_NAME = `${packageJson.name}/expo`; | ||
| export const PLUGIN_VERSION = packageJson.version; |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,8 +1,10 @@ | ||
| import type { ExpoConfig } from '@expo/config-types'; | ||
| /* eslint-disable @typescript-eslint/no-unsafe-member-access */ | ||
| import type { ConfigPlugin, XcodeProject } from 'expo/config-plugins'; | ||
| import { withDangerousMod, withXcodeProject } from 'expo/config-plugins'; | ||
| import { withAppDelegate, withDangerousMod, withXcodeProject } from 'expo/config-plugins'; | ||
| import * as path from 'path'; | ||
| import { warnOnce, writeSentryPropertiesTo } from './utils'; | ||
| import { warnOnce } from './logger'; | ||
| import { writeSentryPropertiesTo } from './utils'; | ||
|
|
||
| type BuildPhase = { shellScript: string }; | ||
|
|
||
|
|
@@ -11,8 +13,11 @@ const SENTRY_REACT_NATIVE_XCODE_PATH = | |
| const SENTRY_REACT_NATIVE_XCODE_DEBUG_FILES_PATH = | ||
| "`${NODE_BINARY:-node} --print \"require('path').dirname(require.resolve('@sentry/react-native/package.json')) + '/scripts/sentry-xcode-debug-files.sh'\"`"; | ||
|
|
||
| export const withSentryIOS: ConfigPlugin<string> = (config, sentryProperties: string) => { | ||
| const cfg = withXcodeProject(config, config => { | ||
| export const withSentryIOS: ConfigPlugin<{ sentryProperties: string; useNativeInit: boolean | undefined }> = ( | ||
| config, | ||
| { sentryProperties, useNativeInit = false }, | ||
| ) => { | ||
| const xcodeProjectCfg = withXcodeProject(config, config => { | ||
| const xcodeProject: XcodeProject = config.modResults; | ||
|
|
||
| const sentryBuildPhase = xcodeProject.pbxItemByComment( | ||
|
|
@@ -35,7 +40,9 @@ export const withSentryIOS: ConfigPlugin<string> = (config, sentryProperties: st | |
| return config; | ||
| }); | ||
|
|
||
| return withDangerousMod(cfg, [ | ||
| const appDelegateCfc = useNativeInit ? modifyAppDelegate(xcodeProjectCfg) : xcodeProjectCfg; | ||
|
|
||
| return withDangerousMod(appDelegateCfc, [ | ||
| 'ios', | ||
| config => { | ||
| writeSentryPropertiesTo(path.resolve(config.modRequest.projectRoot, 'ios'), sentryProperties); | ||
|
|
@@ -78,3 +85,59 @@ export function addSentryWithBundledScriptsToBundleShellScript(script: string): | |
| (match: string) => `/bin/sh ${SENTRY_REACT_NATIVE_XCODE_PATH} ${match}`, | ||
| ); | ||
| } | ||
|
|
||
| export function modifyAppDelegate(config: ExpoConfig): ExpoConfig { | ||
| return withAppDelegate(config, async config => { | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Bug: The 🔍 Detailed AnalysisThe 💡 Suggested FixRemove the 🤖 Prompt for AI AgentDid we get this right? 👍 / 👎 to inform future reviews. |
||
| if (!config.modResults?.path) { | ||
| warnOnce("Can't add 'RNSentrySDK.start()' to the iOS AppDelegate, because the file was not found."); | ||
| return config; | ||
| } | ||
|
|
||
| const fileName = path.basename(config.modResults.path); | ||
|
|
||
| if (config.modResults.language === 'swift') { | ||
| if (config.modResults.contents.includes('RNSentrySDK.start()')) { | ||
| warnOnce(`Your '${fileName}' already contains 'RNSentrySDK.start()'.`); | ||
| return config; | ||
| } | ||
| // Add RNSentrySDK.start() at the beginning of application method | ||
| const originalContents = config.modResults.contents; | ||
| config.modResults.contents = config.modResults.contents.replace( | ||
| /(func application\([^)]*\) -> Bool \{)\s*\n(\s*)/s, | ||
| '$1\n$2RNSentrySDK.start()\n$2', | ||
| ); | ||
| if (config.modResults.contents === originalContents) { | ||
| warnOnce(`Failed to insert 'RNSentrySDK.start()' in '${fileName}'.`); | ||
| } else if (!config.modResults.contents.includes('import RNSentry')) { | ||
| // Insert import statement after UIKit import | ||
| config.modResults.contents = config.modResults.contents.replace(/(import UIKit\n)/, '$1import RNSentry\n'); | ||
| } | ||
| } else if (['objcpp', 'objc'].includes(config.modResults.language)) { | ||
| if (config.modResults.contents.includes('[RNSentrySDK start]')) { | ||
| warnOnce(`Your '${fileName}' already contains '[RNSentrySDK start]'.`); | ||
| return config; | ||
| } | ||
| // Add [RNSentrySDK start] at the beginning of application:didFinishLaunchingWithOptions method | ||
| const originalContents = config.modResults.contents; | ||
| config.modResults.contents = config.modResults.contents.replace( | ||
| /(- \(BOOL\)application:[\s\S]*?didFinishLaunchingWithOptions:[\s\S]*?\{\n)(\s*)/s, | ||
| '$1$2[RNSentrySDK start];\n$2', | ||
| ); | ||
| if (config.modResults.contents === originalContents) { | ||
| warnOnce(`Failed to insert '[RNSentrySDK start]' in '${fileName}.`); | ||
antonis marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| } else if (!config.modResults.contents.includes('#import <RNSentry/RNSentry.h>')) { | ||
| // Add import after AppDelegate.h | ||
| config.modResults.contents = config.modResults.contents.replace( | ||
| /(#import "AppDelegate.h"\n)/, | ||
| '$1#import <RNSentry/RNSentry.h>\n', | ||
| ); | ||
| } | ||
| } else { | ||
| warnOnce( | ||
| `Unsupported language '${config.modResults.language}' detected in '${fileName}', the native code won't be updated.`, | ||
| ); | ||
| } | ||
|
|
||
| return config; | ||
| }); | ||
| } | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nit. It would be nice to include an example code snippet and a small summary of what will the flag do.