Skip to content

Commit 33cfe1f

Browse files
antonisclaudelucas-zimerman
authored
feat(core): Generate sentry.options.json from Expo plugin config (#5804)
* feat(core): Generate sentry.options.json from Expo plugin config Adds an `options` property to the Expo config plugin that generates `sentry.options.json` during prebuild, removing the need to manually create the file when using `useNativeInit: true`. Closes #5664 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * Update changelog * Change message for clarity Co-authored-by: LucasZF <lucas-zimerman1@hotmail.com> --------- Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com> Co-authored-by: LucasZF <lucas-zimerman1@hotmail.com>
1 parent 991892d commit 33cfe1f

File tree

8 files changed

+130
-96
lines changed

8 files changed

+130
-96
lines changed

CHANGELOG.md

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,19 @@
3131
```json
3232
["@sentry/react-native/expo", {
3333
"useNativeInit": true,
34-
"environment": "staging"
34+
"options": {
35+
"environment": "staging"
36+
}
37+
}]
38+
```
39+
- Generate `sentry.options.json` from the Expo config plugin `options` property ([#5804](https://github.com/getsentry/sentry-react-native/pull/5804/))
40+
```json
41+
["@sentry/react-native/expo", {
42+
"useNativeInit": true,
43+
"options": {
44+
"dsn": "https://key@sentry.io/123",
45+
"tracesSampleRate": 1.0
46+
}
3547
}]
3648
```
3749

packages/core/plugin/src/utils.ts

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -12,19 +12,19 @@ export function writeSentryPropertiesTo(filepath: string, sentryProperties: stri
1212

1313
const SENTRY_OPTIONS_FILE_NAME = 'sentry.options.json';
1414

15-
export function writeSentryOptionsEnvironment(projectRoot: string, environment: string): void {
15+
export function writeSentryOptions(projectRoot: string, pluginOptions: Record<string, unknown>): void {
1616
const optionsFilePath = path.resolve(projectRoot, SENTRY_OPTIONS_FILE_NAME);
1717

18-
let options: Record<string, unknown> = {};
18+
let existingOptions: Record<string, unknown> = {};
1919
if (fs.existsSync(optionsFilePath)) {
2020
try {
21-
options = JSON.parse(fs.readFileSync(optionsFilePath, 'utf8'));
21+
existingOptions = JSON.parse(fs.readFileSync(optionsFilePath, 'utf8'));
2222
} catch (e) {
23-
warnOnce(`Failed to parse ${SENTRY_OPTIONS_FILE_NAME}: ${e}. The environment will not be set.`);
23+
warnOnce(`Failed to parse ${SENTRY_OPTIONS_FILE_NAME}: ${e}. These options will not be set.`);
2424
return;
2525
}
2626
}
2727

28-
options.environment = environment;
29-
fs.writeFileSync(optionsFilePath, `${JSON.stringify(options, null, 2)}\n`);
28+
const mergedOptions = { ...existingOptions, ...pluginOptions };
29+
fs.writeFileSync(optionsFilePath, `${JSON.stringify(mergedOptions, null, 2)}\n`);
3030
}

packages/core/plugin/src/withSentry.ts

Lines changed: 11 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import type { ExpoConfig } from '@expo/config-types';
22
import type { ConfigPlugin } from 'expo/config-plugins';
33
import { createRunOncePlugin, withDangerousMod } from 'expo/config-plugins';
44
import { bold, warnOnce } from './logger';
5-
import { writeSentryOptionsEnvironment } from './utils';
5+
import { writeSentryOptions } from './utils';
66
import { PLUGIN_NAME, PLUGIN_VERSION } from './version';
77
import { withSentryAndroid } from './withSentryAndroid';
88
import type { SentryAndroidGradlePluginOptions } from './withSentryAndroidGradlePlugin';
@@ -15,7 +15,7 @@ interface PluginProps {
1515
authToken?: string;
1616
url?: string;
1717
useNativeInit?: boolean;
18-
environment?: string;
18+
options?: Record<string, unknown>;
1919
experimental_android?: SentryAndroidGradlePluginOptions;
2020
}
2121

@@ -28,9 +28,13 @@ const withSentryPlugin: ConfigPlugin<PluginProps | void> = (config, props) => {
2828
}
2929

3030
let cfg = config;
31-
const environment = props?.environment ?? process.env.SENTRY_ENVIRONMENT;
31+
const pluginOptions = props?.options ? { ...props.options } : {};
32+
const environment = process.env.SENTRY_ENVIRONMENT;
3233
if (environment) {
33-
cfg = withSentryOptionsEnvironment(cfg, environment);
34+
pluginOptions.environment = environment;
35+
}
36+
if (Object.keys(pluginOptions).length > 0) {
37+
cfg = withSentryOptionsFile(cfg, pluginOptions);
3438
}
3539
if (sentryProperties !== null) {
3640
try {
@@ -87,20 +91,20 @@ ${project ? `defaults.project=${project}` : missingProjectMessage}
8791
${authToken ? `${existingAuthTokenMessage}\nauth.token=${authToken}` : missingAuthTokenMessage}`;
8892
}
8993

90-
function withSentryOptionsEnvironment(config: ExpoConfig, environment: string): ExpoConfig {
94+
function withSentryOptionsFile(config: ExpoConfig, pluginOptions: Record<string, unknown>): ExpoConfig {
9195
// withDangerousMod requires a platform key, but sentry.options.json is at the project root.
9296
// We apply to both platforms so it works with `expo prebuild --platform ios` or `--platform android`.
9397
let cfg = withDangerousMod(config, [
9498
'android',
9599
mod => {
96-
writeSentryOptionsEnvironment(mod.modRequest.projectRoot, environment);
100+
writeSentryOptions(mod.modRequest.projectRoot, pluginOptions);
97101
return mod;
98102
},
99103
]);
100104
cfg = withDangerousMod(cfg, [
101105
'ios',
102106
mod => {
103-
writeSentryOptionsEnvironment(mod.modRequest.projectRoot, environment);
107+
writeSentryOptions(mod.modRequest.projectRoot, pluginOptions);
104108
return mod;
105109
},
106110
]);
Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
import * as fs from 'fs';
2+
import * as os from 'os';
3+
import * as path from 'path';
4+
import { writeSentryOptions } from '../../plugin/src/utils';
5+
6+
jest.mock('../../plugin/src/logger');
7+
8+
describe('writeSentryOptions', () => {
9+
let tempDir: string;
10+
11+
beforeEach(() => {
12+
tempDir = fs.mkdtempSync(path.join(os.tmpdir(), 'sentry-options-test-'));
13+
});
14+
15+
afterEach(() => {
16+
fs.rmSync(tempDir, { recursive: true, force: true });
17+
});
18+
19+
test('creates sentry.options.json when file does not exist', () => {
20+
writeSentryOptions(tempDir, { dsn: 'https://key@sentry.io/123', environment: 'staging' });
21+
22+
const filePath = path.join(tempDir, 'sentry.options.json');
23+
const content = JSON.parse(fs.readFileSync(filePath, 'utf8'));
24+
expect(content).toEqual({ dsn: 'https://key@sentry.io/123', environment: 'staging' });
25+
});
26+
27+
test('merges options into existing sentry.options.json', () => {
28+
const filePath = path.join(tempDir, 'sentry.options.json');
29+
fs.writeFileSync(filePath, JSON.stringify({ dsn: 'https://key@sentry.io/123', environment: 'production' }));
30+
31+
writeSentryOptions(tempDir, { environment: 'staging', debug: true });
32+
33+
const content = JSON.parse(fs.readFileSync(filePath, 'utf8'));
34+
expect(content).toEqual({ dsn: 'https://key@sentry.io/123', environment: 'staging', debug: true });
35+
});
36+
37+
test('plugin options take precedence over existing file values', () => {
38+
const filePath = path.join(tempDir, 'sentry.options.json');
39+
fs.writeFileSync(filePath, JSON.stringify({ dsn: 'https://old@sentry.io/1', debug: false }));
40+
41+
writeSentryOptions(tempDir, { dsn: 'https://new@sentry.io/2' });
42+
43+
const content = JSON.parse(fs.readFileSync(filePath, 'utf8'));
44+
expect(content.dsn).toBe('https://new@sentry.io/2');
45+
expect(content.debug).toBe(false); // preserved from existing file
46+
});
47+
48+
test('does not crash and warns when sentry.options.json contains invalid JSON', () => {
49+
const { warnOnce } = require('../../plugin/src/logger');
50+
const filePath = path.join(tempDir, 'sentry.options.json');
51+
fs.writeFileSync(filePath, 'invalid json{{{');
52+
53+
writeSentryOptions(tempDir, { environment: 'staging' });
54+
55+
expect(warnOnce).toHaveBeenCalledWith(expect.stringContaining('Failed to parse'));
56+
// File should remain unchanged
57+
expect(fs.readFileSync(filePath, 'utf8')).toBe('invalid json{{{');
58+
});
59+
60+
test('writes multiple options at once', () => {
61+
writeSentryOptions(tempDir, {
62+
dsn: 'https://key@sentry.io/123',
63+
debug: true,
64+
tracesSampleRate: 0.5,
65+
environment: 'production',
66+
});
67+
68+
const filePath = path.join(tempDir, 'sentry.options.json');
69+
const content = JSON.parse(fs.readFileSync(filePath, 'utf8'));
70+
expect(content).toEqual({
71+
dsn: 'https://key@sentry.io/123',
72+
debug: true,
73+
tracesSampleRate: 0.5,
74+
environment: 'production',
75+
});
76+
});
77+
});

packages/core/test/expo-plugin/writeSentryOptionsEnvironment.test.ts

Lines changed: 0 additions & 60 deletions
This file was deleted.

samples/expo/.gitignore

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,9 @@ yarn-error.*
3838
/android
3939
/ios
4040

41+
# Generated by @sentry/react-native expo plugin
42+
sentry.options.json
43+
4144
# @generated expo-cli sync-2b81b286409207a5da26e14c78851eb30d8ccbdb
4245
# The following patterns were generated by expo-cli
4346

samples/expo/app.json

Lines changed: 20 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -13,9 +13,7 @@
1313
"resizeMode": "contain",
1414
"backgroundColor": "#ffffff"
1515
},
16-
"assetBundlePatterns": [
17-
"**/*"
18-
],
16+
"assetBundlePatterns": ["**/*"],
1917
"ios": {
2018
"supportsTablet": true,
2119
"bundleIdentifier": "io.sentry.expo.sample",
@@ -46,6 +44,24 @@
4644
"project": "sentry-react-native",
4745
"organization": "sentry-sdks",
4846
"useNativeInit": true,
47+
"options": {
48+
"dsn": "https://1df17bd4e543fdb31351dee1768bb679@o447951.ingest.sentry.io/5428561",
49+
"debug": true,
50+
"environment": "dev",
51+
"enableUserInteractionTracing": true,
52+
"enableAutoSessionTracking": true,
53+
"sessionTrackingIntervalMillis": 30000,
54+
"enableTracing": true,
55+
"tracesSampleRate": 1.0,
56+
"attachStacktrace": true,
57+
"attachScreenshot": true,
58+
"attachViewHierarchy": true,
59+
"enableCaptureFailedRequests": true,
60+
"profilesSampleRate": 1.0,
61+
"replaysSessionSampleRate": 1.0,
62+
"replaysOnErrorSampleRate": 1.0,
63+
"spotlight": true
64+
},
4965
"experimental_android": {
5066
"enableAndroidGradlePlugin": true,
5167
"autoUploadProguardMapping": true,
@@ -90,4 +106,4 @@
90106
"url": "https://u.expo.dev/00000000-0000-0000-0000-000000000000"
91107
}
92108
}
93-
}
109+
}

samples/expo/sentry.options.json

Lines changed: 0 additions & 18 deletions
This file was deleted.

0 commit comments

Comments
 (0)