Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@

### Features

- Support `SENTRY_RELEASE` and `SENTRY_DIST` env vars in build scripts to override values in `sentry.options.json` at build time ([#6070](https://github.com/getsentry/sentry-react-native/pull/6070))
- Add `includeWebFeedback` Metro config option to exclude `@sentry-internal/feedback` from the bundle ([#6025](https://github.com/getsentry/sentry-react-native/pull/6025))
- Add rage tap detection โ€” rapid consecutive taps on the same element emit `ui.multiClick` breadcrumbs and appear on the replay timeline with the rage click icon ([#5992](https://github.com/getsentry/sentry-react-native/pull/5992))

Expand Down
12 changes: 8 additions & 4 deletions packages/core/scripts/sentry-xcode.sh
Original file line number Diff line number Diff line change
Expand Up @@ -105,17 +105,21 @@ if [ "$SENTRY_COPY_OPTIONS_FILE" = true ]; then
else
cp "$SENTRY_OPTIONS_FILE_PATH" "$SENTRY_OPTIONS_FILE_DESTINATION_PATH"

if [ -n "$SENTRY_ENVIRONMENT" ]; then
if [ -n "$SENTRY_ENVIRONMENT" ] || [ -n "$SENTRY_RELEASE" ] || [ -n "$SENTRY_DIST" ]; then
if "$LOCAL_NODE_BINARY" -e "
var fs = require('fs');
var destPath = process.argv[1];
var opts = JSON.parse(fs.readFileSync(destPath, 'utf8'));
opts.environment = process.env.SENTRY_ENVIRONMENT;
if (process.env.SENTRY_ENVIRONMENT) { opts.environment = process.env.SENTRY_ENVIRONMENT; }
if (process.env.SENTRY_RELEASE) { opts.release = process.env.SENTRY_RELEASE; }
if (process.env.SENTRY_DIST) { opts.dist = process.env.SENTRY_DIST; }
fs.writeFileSync(destPath, JSON.stringify(opts));
" -- "$SENTRY_OPTIONS_FILE_DESTINATION_PATH" 2>/dev/null; then
echo "[Sentry] Overriding 'environment' from SENTRY_ENVIRONMENT environment variable"
[ -n "$SENTRY_ENVIRONMENT" ] && echo "[Sentry] Overriding 'environment' from SENTRY_ENVIRONMENT environment variable"
[ -n "$SENTRY_RELEASE" ] && echo "[Sentry] Overriding 'release' from SENTRY_RELEASE environment variable"
[ -n "$SENTRY_DIST" ] && echo "[Sentry] Overriding 'dist' from SENTRY_DIST environment variable"
else
echo "[Sentry] Failed to override environment, copied file as-is." 1>&2
echo "[Sentry] Failed to override options from environment variables, copied file as-is." 1>&2
fi
fi
echo "[Sentry] Copied $SENTRY_OPTIONS_FILE_PATH to $SENTRY_OPTIONS_FILE_DESTINATION_PATH"
Expand Down
14 changes: 10 additions & 4 deletions packages/core/sentry.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -46,15 +46,21 @@ tasks.register("copySentryJsonConfiguration") {
}

def sentryEnv = System.getenv('SENTRY_ENVIRONMENT')
if (sentryEnv) {
def sentryRelease = System.getenv('SENTRY_RELEASE')
def sentryDist = System.getenv('SENTRY_DIST')
if (sentryEnv || sentryRelease || sentryDist) {
try {
def destFile = new File(androidAssetsDir, configFile)
def content = new groovy.json.JsonSlurper().parseText(destFile.text)
content.environment = sentryEnv
if (sentryEnv) { content.environment = sentryEnv }
if (sentryRelease) { content.release = sentryRelease }
if (sentryDist) { content.dist = sentryDist }
destFile.text = groovy.json.JsonOutput.toJson(content)
logger.lifecycle("Overriding 'environment' from SENTRY_ENVIRONMENT environment variable")
if (sentryEnv) { logger.lifecycle("Overriding 'environment' from SENTRY_ENVIRONMENT environment variable") }
if (sentryRelease) { logger.lifecycle("Overriding 'release' from SENTRY_RELEASE environment variable") }
if (sentryDist) { logger.lifecycle("Overriding 'dist' from SENTRY_DIST environment variable") }
} catch (Exception e) {
logger.warn("Failed to override environment in ${configFile}: ${e.message}. Copied file as-is.")
logger.warn("Failed to override options in ${configFile}: ${e.message}. Copied file as-is.")
}
}
logger.lifecycle("Copied ${configFile} to Android assets")
Expand Down
127 changes: 122 additions & 5 deletions packages/core/test/scripts/sentry-xcode-scripts.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -531,8 +531,8 @@ describe('sentry-xcode.sh', () => {
});
});

describe('sentry.options.json SENTRY_ENVIRONMENT override', () => {
it('copies file without modification when SENTRY_ENVIRONMENT is not set', () => {
describe('sentry.options.json environment variable overrides', () => {
it('copies file without modification when no override env vars are set', () => {
const optionsContent = JSON.stringify({ dsn: 'https://key@sentry.io/123', environment: 'production' });
const optionsFile = path.join(tempDir, 'sentry.options.json');
fs.writeFileSync(optionsFile, optionsContent);
Expand Down Expand Up @@ -575,14 +575,92 @@ describe('sentry-xcode.sh', () => {
});

expect(result.exitCode).toBe(0);
expect(result.stdout).toContain('Overriding');
expect(result.stdout).toContain("Overriding 'environment' from SENTRY_ENVIRONMENT");
const destPath = path.join(buildDir, resourcesPath, 'sentry.options.json');
const copied = JSON.parse(fs.readFileSync(destPath, 'utf8'));
expect(copied.environment).toBe('staging');
expect(copied.dsn).toBe('https://key@sentry.io/123');
});

it('does not modify the source sentry.options.json', () => {
it('overrides release from SENTRY_RELEASE env var', () => {
const optionsContent = JSON.stringify({ dsn: 'https://key@sentry.io/123' });
const optionsFile = path.join(tempDir, 'sentry.options.json');
fs.writeFileSync(optionsFile, optionsContent);

const buildDir = path.join(tempDir, 'build');
const resourcesPath = 'Resources';
fs.mkdirSync(path.join(buildDir, resourcesPath), { recursive: true });

const result = runScript({
SENTRY_DISABLE_AUTO_UPLOAD: 'true',
SENTRY_COPY_OPTIONS_FILE: 'true',
SENTRY_OPTIONS_FILE_PATH: optionsFile,
CONFIGURATION_BUILD_DIR: buildDir,
UNLOCALIZED_RESOURCES_FOLDER_PATH: resourcesPath,
SENTRY_RELEASE: 'my-app@1.0.0+42',
});

expect(result.exitCode).toBe(0);
expect(result.stdout).toContain("Overriding 'release' from SENTRY_RELEASE");
const destPath = path.join(buildDir, resourcesPath, 'sentry.options.json');
const copied = JSON.parse(fs.readFileSync(destPath, 'utf8'));
expect(copied.release).toBe('my-app@1.0.0+42');
expect(copied.dsn).toBe('https://key@sentry.io/123');
});

it('overrides existing release value from SENTRY_RELEASE env var', () => {
const optionsContent = JSON.stringify({ dsn: 'https://key@sentry.io/123', release: 'old@1.0.0', dist: '1' });
const optionsFile = path.join(tempDir, 'sentry.options.json');
fs.writeFileSync(optionsFile, optionsContent);

const buildDir = path.join(tempDir, 'build');
const resourcesPath = 'Resources';
fs.mkdirSync(path.join(buildDir, resourcesPath), { recursive: true });

const result = runScript({
SENTRY_DISABLE_AUTO_UPLOAD: 'true',
SENTRY_COPY_OPTIONS_FILE: 'true',
SENTRY_OPTIONS_FILE_PATH: optionsFile,
CONFIGURATION_BUILD_DIR: buildDir,
UNLOCALIZED_RESOURCES_FOLDER_PATH: resourcesPath,
SENTRY_RELEASE: 'new@2.0.0',
SENTRY_DIST: '2',
});

expect(result.exitCode).toBe(0);
const destPath = path.join(buildDir, resourcesPath, 'sentry.options.json');
const copied = JSON.parse(fs.readFileSync(destPath, 'utf8'));
expect(copied.release).toBe('new@2.0.0');
expect(copied.dist).toBe('2');
});

it('overrides dist from SENTRY_DIST env var', () => {
const optionsContent = JSON.stringify({ dsn: 'https://key@sentry.io/123' });
const optionsFile = path.join(tempDir, 'sentry.options.json');
fs.writeFileSync(optionsFile, optionsContent);

const buildDir = path.join(tempDir, 'build');
const resourcesPath = 'Resources';
fs.mkdirSync(path.join(buildDir, resourcesPath), { recursive: true });

const result = runScript({
SENTRY_DISABLE_AUTO_UPLOAD: 'true',
SENTRY_COPY_OPTIONS_FILE: 'true',
SENTRY_OPTIONS_FILE_PATH: optionsFile,
CONFIGURATION_BUILD_DIR: buildDir,
UNLOCALIZED_RESOURCES_FOLDER_PATH: resourcesPath,
SENTRY_DIST: '42',
});

expect(result.exitCode).toBe(0);
expect(result.stdout).toContain("Overriding 'dist' from SENTRY_DIST");
const destPath = path.join(buildDir, resourcesPath, 'sentry.options.json');
const copied = JSON.parse(fs.readFileSync(destPath, 'utf8'));
expect(copied.dist).toBe('42');
expect(copied.dsn).toBe('https://key@sentry.io/123');
});

it('overrides release, dist, and environment together', () => {
const optionsContent = JSON.stringify({ dsn: 'https://key@sentry.io/123', environment: 'production' });
const optionsFile = path.join(tempDir, 'sentry.options.json');
fs.writeFileSync(optionsFile, optionsContent);
Expand All @@ -591,17 +669,56 @@ describe('sentry-xcode.sh', () => {
const resourcesPath = 'Resources';
fs.mkdirSync(path.join(buildDir, resourcesPath), { recursive: true });

const result = runScript({
SENTRY_DISABLE_AUTO_UPLOAD: 'true',
SENTRY_COPY_OPTIONS_FILE: 'true',
SENTRY_OPTIONS_FILE_PATH: optionsFile,
CONFIGURATION_BUILD_DIR: buildDir,
UNLOCALIZED_RESOURCES_FOLDER_PATH: resourcesPath,
SENTRY_ENVIRONMENT: 'staging',
SENTRY_RELEASE: 'my-app@2.0.0+10',
SENTRY_DIST: '10',
});

expect(result.exitCode).toBe(0);
expect(result.stdout).toContain("Overriding 'environment' from SENTRY_ENVIRONMENT");
expect(result.stdout).toContain("Overriding 'release' from SENTRY_RELEASE");
expect(result.stdout).toContain("Overriding 'dist' from SENTRY_DIST");
const destPath = path.join(buildDir, resourcesPath, 'sentry.options.json');
const copied = JSON.parse(fs.readFileSync(destPath, 'utf8'));
expect(copied.environment).toBe('staging');
expect(copied.release).toBe('my-app@2.0.0+10');
expect(copied.dist).toBe('10');
});

it('does not modify the source file when overriding', () => {
const optionsContent = JSON.stringify({
dsn: 'https://key@sentry.io/123',
release: 'original@1.0.0',
environment: 'production',
});
const optionsFile = path.join(tempDir, 'sentry.options.json');
fs.writeFileSync(optionsFile, optionsContent);

const buildDir = path.join(tempDir, 'build');
const resourcesPath = 'Resources';
fs.mkdirSync(path.join(buildDir, resourcesPath), { recursive: true });

runScript({
SENTRY_DISABLE_AUTO_UPLOAD: 'true',
SENTRY_COPY_OPTIONS_FILE: 'true',
SENTRY_OPTIONS_FILE_PATH: optionsFile,
CONFIGURATION_BUILD_DIR: buildDir,
UNLOCALIZED_RESOURCES_FOLDER_PATH: resourcesPath,
SENTRY_ENVIRONMENT: 'staging',
SENTRY_RELEASE: 'override@2.0.0',
SENTRY_DIST: '99',
});

const source = JSON.parse(fs.readFileSync(optionsFile, 'utf8'));
expect(source.environment).toBe('production');
expect(source.release).toBe('original@1.0.0');
expect(source.dist).toBeUndefined();
});

it('falls back to plain copy when sentry.options.json contains invalid JSON', () => {
Expand All @@ -618,7 +735,7 @@ describe('sentry-xcode.sh', () => {
SENTRY_OPTIONS_FILE_PATH: optionsFile,
CONFIGURATION_BUILD_DIR: buildDir,
UNLOCALIZED_RESOURCES_FOLDER_PATH: resourcesPath,
SENTRY_ENVIRONMENT: 'staging',
SENTRY_RELEASE: 'my-app@1.0.0',
});

expect(result.exitCode).toBe(0);
Expand Down
Loading