Skip to content

Commit 42d7ec5

Browse files
committed
feat(expo): Warn when prebuilt native projects are missing Sentry config
When using the Sentry Expo Plugin with prebuilt native projects, users need to run `npx expo prebuild --clean` to apply the native changes. Without this step, source maps upload and native crash reporting won't work correctly. This adds a check in `getSentryExpoConfig()` that runs at Metro startup and inspects existing ios/android directories for Sentry markers: - iOS: looks for `sentry-xcode` or `Upload Debug Symbols to Sentry` in the pbxproj file - Android: looks for `sentry.gradle` in app/build.gradle The check only runs for Expo projects (detected via package.json) and is fully wrapped in try/catch to never crash Metro startup. Closes #4640
1 parent 3ce5254 commit 42d7ec5

File tree

2 files changed

+92
-0
lines changed

2 files changed

+92
-0
lines changed

packages/core/src/js/tools/metroconfig.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import type { DefaultConfigOptions } from './vendor/expo/expoconfig';
1010

1111
import { enableLogger } from './enableLogger';
1212
import { withSentryMiddleware } from './metroMiddleware';
13+
import { checkSentryExpoNativeProject } from './sentryExpoNativeCheck';
1314
import {
1415
setSentryBabelTransformerOptions,
1516
setSentryDefaultBabelTransformerPathEnv,
@@ -114,6 +115,8 @@ export function getSentryExpoConfig(
114115
): MetroConfig {
115116
setSentryMetroDevServerEnvFlag();
116117

118+
checkSentryExpoNativeProject(projectRoot);
119+
117120
const getDefaultConfig = options.getDefaultConfig || loadExpoMetroConfigModule().getDefaultConfig;
118121
const config = getDefaultConfig(projectRoot, {
119122
...options,
Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
import * as fs from 'fs';
2+
import * as path from 'path';
3+
4+
const PREFIX = '[@sentry/react-native/expo]';
5+
6+
/**
7+
* Checks if prebuilt native projects (ios/android) have been configured with Sentry.
8+
* If native directories exist but are missing Sentry configuration, warns the user
9+
* to run `npx expo prebuild --clean`.
10+
*
11+
* This only runs for Expo projects (detected by the presence of `expo` in package.json dependencies).
12+
*/
13+
export function checkSentryExpoNativeProject(projectRoot: string): void {
14+
try {
15+
if (!isExpoProject(projectRoot)) {
16+
return;
17+
}
18+
19+
const missingPlatforms: string[] = [];
20+
21+
if (isIOSProjectMissingSentry(projectRoot)) {
22+
missingPlatforms.push('iOS');
23+
}
24+
25+
if (isAndroidProjectMissingSentry(projectRoot)) {
26+
missingPlatforms.push('Android');
27+
}
28+
29+
if (missingPlatforms.length > 0) {
30+
const platforms = missingPlatforms.join(' and ');
31+
// oxlint-disable-next-line eslint(no-console)
32+
console.warn(
33+
`${PREFIX} Sentry native configuration is missing from your prebuilt ${platforms} project.
34+
Run \`npx expo prebuild --clean\` to apply the Sentry Expo Plugin changes.
35+
Without this, source maps upload and native crash reporting may not work correctly.`,
36+
);
37+
}
38+
} catch (_) {
39+
// Never crash Metro startup — silently ignore any errors
40+
}
41+
}
42+
43+
function isExpoProject(projectRoot: string): boolean {
44+
const packageJsonPath = path.resolve(projectRoot, 'package.json');
45+
if (!fs.existsSync(packageJsonPath)) {
46+
return false;
47+
}
48+
49+
const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, 'utf8'));
50+
return !!(packageJson.dependencies?.expo || packageJson.devDependencies?.expo);
51+
}
52+
53+
function isIOSProjectMissingSentry(projectRoot: string): boolean {
54+
const iosDir = path.resolve(projectRoot, 'ios');
55+
if (!fs.existsSync(iosDir)) {
56+
return false;
57+
}
58+
59+
const pbxprojPath = findPbxprojFile(iosDir);
60+
if (!pbxprojPath) {
61+
return false;
62+
}
63+
64+
const pbxprojContents = fs.readFileSync(pbxprojPath, 'utf8');
65+
return !pbxprojContents.includes('sentry-xcode') && !pbxprojContents.includes('Upload Debug Symbols to Sentry');
66+
}
67+
68+
function isAndroidProjectMissingSentry(projectRoot: string): boolean {
69+
const buildGradlePath = path.resolve(projectRoot, 'android', 'app', 'build.gradle');
70+
if (!fs.existsSync(buildGradlePath)) {
71+
return false;
72+
}
73+
74+
const buildGradleContents = fs.readFileSync(buildGradlePath, 'utf8');
75+
return !buildGradleContents.includes('sentry.gradle');
76+
}
77+
78+
function findPbxprojFile(iosDir: string): string | null {
79+
const entries = fs.readdirSync(iosDir);
80+
for (const entry of entries) {
81+
if (entry.endsWith('.xcodeproj')) {
82+
const pbxprojPath = path.resolve(iosDir, entry, 'project.pbxproj');
83+
if (fs.existsSync(pbxprojPath)) {
84+
return pbxprojPath;
85+
}
86+
}
87+
}
88+
return null;
89+
}

0 commit comments

Comments
 (0)