diff --git a/CHANGELOG.md b/CHANGELOG.md index add9b2b74e..2a8f4c17f7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,12 @@ > make sure you follow our [migration guide](https://docs.sentry.io/platforms/react-native/migration/) first. +## Unreleased + +### Features + +- Support `SENTRY_ENVIRONMENT` in bare React Native builds ([#5823](https://github.com/getsentry/sentry-react-native/pull/5823)) + ## 8.4.0 ### Fixes diff --git a/packages/core/scripts/sentry-xcode.sh b/packages/core/scripts/sentry-xcode.sh index b73ffbca35..069f08be01 100755 --- a/packages/core/scripts/sentry-xcode.sh +++ b/packages/core/scripts/sentry-xcode.sh @@ -101,6 +101,20 @@ if [ "$SENTRY_COPY_OPTIONS_FILE" = true ]; then echo "[Sentry] $SENTRY_OPTIONS_FILE_PATH not found. $SENTRY_OPTIONS_FILE_ERROR_MESSAGE_POSTFIX" 1>&2 else cp "$SENTRY_OPTIONS_FILE_PATH" "$SENTRY_OPTIONS_FILE_DESTINATION_PATH" + + if [ -n "$SENTRY_ENVIRONMENT" ]; 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; + fs.writeFileSync(destPath, JSON.stringify(opts)); + " -- "$SENTRY_OPTIONS_FILE_DESTINATION_PATH" 2>/dev/null; then + echo "[Sentry] Overriding 'environment' from SENTRY_ENVIRONMENT environment variable" + else + echo "[Sentry] Failed to override environment, copied file as-is." 1>&2 + fi + fi echo "[Sentry] Copied $SENTRY_OPTIONS_FILE_PATH to $SENTRY_OPTIONS_FILE_DESTINATION_PATH" fi fi diff --git a/packages/core/sentry.gradle b/packages/core/sentry.gradle index 91171e9c3b..86dcc1cbbf 100644 --- a/packages/core/sentry.gradle +++ b/packages/core/sentry.gradle @@ -44,6 +44,19 @@ tasks.register("copySentryJsonConfiguration") { into androidAssetsDir rename { String fileName -> configFile } } + + def sentryEnv = System.getenv('SENTRY_ENVIRONMENT') + if (sentryEnv) { + try { + def destFile = new File(androidAssetsDir, configFile) + def content = new groovy.json.JsonSlurper().parseText(destFile.text) + content.environment = sentryEnv + destFile.text = groovy.json.JsonOutput.toJson(content) + logger.lifecycle("Overriding 'environment' from SENTRY_ENVIRONMENT environment variable") + } catch (Exception e) { + logger.warn("Failed to override environment in ${configFile}: ${e.message}. Copied file as-is.") + } + } logger.lifecycle("Copied ${configFile} to Android assets") } else { logger.warn("${configFile} not found in app root (${appRoot})") diff --git a/packages/core/test/scripts/sentry-xcode-scripts.test.ts b/packages/core/test/scripts/sentry-xcode-scripts.test.ts index b9154fd752..ec0056fe7b 100644 --- a/packages/core/test/scripts/sentry-xcode-scripts.test.ts +++ b/packages/core/test/scripts/sentry-xcode-scripts.test.ts @@ -455,6 +455,103 @@ describe('sentry-xcode.sh', () => { expect(result.stdout).toContain('skipping sourcemaps upload'); }); + describe('sentry.options.json SENTRY_ENVIRONMENT override', () => { + it('copies file without modification when SENTRY_ENVIRONMENT is not 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); + + 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, + }); + + expect(result.exitCode).toBe(0); + const destPath = path.join(buildDir, resourcesPath, 'sentry.options.json'); + const copied = JSON.parse(fs.readFileSync(destPath, 'utf8')); + expect(copied.dsn).toBe('https://key@sentry.io/123'); + expect(copied.environment).toBe('production'); + }); + + it('overrides environment from SENTRY_ENVIRONMENT env var', () => { + const optionsContent = JSON.stringify({ dsn: 'https://key@sentry.io/123', 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 }); + + 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', + }); + + expect(result.exitCode).toBe(0); + expect(result.stdout).toContain('Overriding'); + 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', () => { + const optionsContent = JSON.stringify({ dsn: 'https://key@sentry.io/123', 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', + }); + + const source = JSON.parse(fs.readFileSync(optionsFile, 'utf8')); + expect(source.environment).toBe('production'); + }); + + it('falls back to plain copy when sentry.options.json contains invalid JSON', () => { + const optionsFile = path.join(tempDir, 'sentry.options.json'); + fs.writeFileSync(optionsFile, 'invalid json{{{'); + + 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_ENVIRONMENT: 'staging', + }); + + expect(result.exitCode).toBe(0); + const destPath = path.join(buildDir, resourcesPath, 'sentry.options.json'); + expect(fs.readFileSync(destPath, 'utf8')).toBe('invalid json{{{'); + expect(result.stdout).toContain('Copied'); + }); + }); + describe('SOURCEMAP_FILE path resolution', () => { // Returns a mock sentry-cli that prints the SOURCEMAP_FILE env var it received. const makeSourcemapEchoScript = (dir: string): string => {