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
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,10 @@
- [changelog](https://github.com/getsentry/sentry-javascript-bundler-plugins/blob/main/CHANGELOG.md#490)
- [diff](https://github.com/getsentry/sentry-javascript-bundler-plugins/compare/4.8.0...4.9.0)

### Fixes

- Fix `SENTRY_ALLOW_FAILURE` environment variable not being respected in Xcode build scripts ([#5616](https://github.com/getsentry/sentry-react-native/pull/5616))
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I just realised that the Fixes section is in the wrong position. I moved it in the correct place in a next PR with 5202581


## 7.11.0

### Features
Expand Down
12 changes: 9 additions & 3 deletions packages/core/scripts/sentry-xcode-debug-files.sh
Original file line number Diff line number Diff line change
Expand Up @@ -70,11 +70,17 @@ else
# 'warning:' triggers a warning in Xcode, 'error:' triggers an error
set +x +e # disable printing commands otherwise we might print `error:` by accident and allow continuing on error
SENTRY_UPLOAD_COMMAND_OUTPUT=$(/bin/sh -c "\"$LOCAL_NODE_BINARY\" $UPLOAD_DEBUG_FILES" 2>&1)
if [ $? -eq 0 ]; then
UPLOAD_EXIT_CODE=$?
if [ $UPLOAD_EXIT_CODE -eq 0 ]; then
echo "$SENTRY_UPLOAD_COMMAND_OUTPUT" | awk '{print "output: sentry-cli - " $0}'
else
echo "error: sentry-cli - To disable native debug files auto upload, set SENTRY_DISABLE_AUTO_UPLOAD=true in your environment variables. Or to allow failing upload, set SENTRY_ALLOW_FAILURE=true"
echo "error: sentry-cli - $SENTRY_UPLOAD_COMMAND_OUTPUT"
if [ "$SENTRY_ALLOW_FAILURE" == true ]; then
echo "warning: sentry-cli - Debug files upload failed, but continuing build because SENTRY_ALLOW_FAILURE=true"
echo "warning: sentry-cli - $SENTRY_UPLOAD_COMMAND_OUTPUT"
else
echo "error: sentry-cli - To disable native debug files auto upload, set SENTRY_DISABLE_AUTO_UPLOAD=true in your environment variables. Or to allow failing upload, set SENTRY_ALLOW_FAILURE=true"
echo "error: sentry-cli - $SENTRY_UPLOAD_COMMAND_OUTPUT"
fi
fi
set -x -e # re-enable
fi
11 changes: 8 additions & 3 deletions packages/core/scripts/sentry-xcode.sh
Original file line number Diff line number Diff line change
Expand Up @@ -56,9 +56,14 @@ if [ "$SENTRY_DISABLE_AUTO_UPLOAD" != true ]; then
if [ $? -eq 0 ]; then
echo "$SENTRY_XCODE_COMMAND_OUTPUT" | awk '{print "output: sentry-cli - " $0}'
else
echo "error: sentry-cli - To disable source maps auto upload, set SENTRY_DISABLE_AUTO_UPLOAD=true in your environment variables. Or to allow failing upload, set SENTRY_ALLOW_FAILURE=true"
echo "error: sentry-cli - $SENTRY_XCODE_COMMAND_OUTPUT"
exitCode=1
if [ "$SENTRY_ALLOW_FAILURE" == true ]; then
echo "warning: sentry-cli - Source maps upload failed, but continuing build because SENTRY_ALLOW_FAILURE=true"
echo "warning: sentry-cli - $SENTRY_XCODE_COMMAND_OUTPUT"
else
echo "error: sentry-cli - To disable source maps auto upload, set SENTRY_DISABLE_AUTO_UPLOAD=true in your environment variables. Or to allow failing upload, set SENTRY_ALLOW_FAILURE=true"
echo "error: sentry-cli - $SENTRY_XCODE_COMMAND_OUTPUT"
exitCode=1
fi
fi
set -x -e # re-enable
else
Expand Down
266 changes: 266 additions & 0 deletions packages/core/test/scripts/sentry-xcode-scripts.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,266 @@
import { execSync } from 'child_process';
import * as fs from 'fs';
import * as os from 'os';
import * as path from 'path';

const SCRIPTS_DIR = path.resolve(__dirname, '../../scripts');
const DEBUG_FILES_SCRIPT = path.join(SCRIPTS_DIR, 'sentry-xcode-debug-files.sh');
const XCODE_SCRIPT = path.join(SCRIPTS_DIR, 'sentry-xcode.sh');

describe('sentry-xcode-debug-files.sh', () => {
let tempDir: string;
let mockSentryCliScript: string;

beforeEach(() => {
// Create a temporary directory for test artifacts
tempDir = fs.mkdtempSync(path.join(os.tmpdir(), 'sentry-xcode-test-'));

// Create a mock sentry-cli script that can simulate success or failure
mockSentryCliScript = path.join(tempDir, 'mock-sentry-cli.js');
fs.writeFileSync(
mockSentryCliScript,
`
const exitCode = process.env.MOCK_CLI_EXIT_CODE || '0';
const output = process.env.MOCK_CLI_OUTPUT || 'Mock upload output';
console.log(output);
process.exit(parseInt(exitCode));
`,
);
});

afterEach(() => {
// Clean up temp directory
if (tempDir && fs.existsSync(tempDir)) {
fs.rmSync(tempDir, { recursive: true, force: true });
}
});

const runScript = (env: Record<string, string> = {}): { stdout: string; stderr: string; exitCode: number } => {
const defaultEnv = {
NODE_BINARY: process.execPath,
SENTRY_CLI_EXECUTABLE: mockSentryCliScript,
DWARF_DSYM_FOLDER_PATH: tempDir,
CONFIGURATION: 'Release',
PROJECT_DIR: tempDir,
DERIVED_FILE_DIR: tempDir,
};

try {
const stdout = execSync(`bash "${DEBUG_FILES_SCRIPT}"`, {
env: { ...process.env, ...defaultEnv, ...env },
encoding: 'utf8',
stdio: 'pipe',
});
return { stdout, stderr: '', exitCode: 0 };
} catch (error: any) {
return {
stdout: error.stdout?.toString() || '',
stderr: error.stderr?.toString() || '',
exitCode: error.status || 1,
};
}
};

it('exits with 0 when upload succeeds', () => {
const result = runScript({
MOCK_CLI_EXIT_CODE: '0',
MOCK_CLI_OUTPUT: 'Upload successful',
});

expect(result.exitCode).toBe(0);
expect(result.stdout).toContain('Upload successful');
});

it('exits with 0 when SENTRY_ALLOW_FAILURE=true and upload fails', () => {
const result = runScript({
MOCK_CLI_EXIT_CODE: '1',
MOCK_CLI_OUTPUT: 'Upload failed: API error',
SENTRY_ALLOW_FAILURE: 'true',
});

expect(result.exitCode).toBe(0);
expect(result.stdout).toContain('warning: sentry-cli');
expect(result.stdout).toContain('continuing build because SENTRY_ALLOW_FAILURE=true');
expect(result.stdout).toContain('Upload failed: API error');
});

it('exits with 0 but prints error when SENTRY_ALLOW_FAILURE not set and upload fails', () => {
const result = runScript({
MOCK_CLI_EXIT_CODE: '1',
MOCK_CLI_OUTPUT: 'Upload failed: API error',
});

// Original behavior: script exits 0, but Xcode fails build due to "error:" prefix
expect(result.exitCode).toBe(0);
expect(result.stdout).toContain('error: sentry-cli');
expect(result.stdout).toContain('SENTRY_ALLOW_FAILURE=true');
expect(result.stdout).toContain('Upload failed: API error');
});

it('exits with 0 but prints error when SENTRY_ALLOW_FAILURE=false and upload fails', () => {
const result = runScript({
MOCK_CLI_EXIT_CODE: '1',
MOCK_CLI_OUTPUT: 'Upload failed: Network error',
SENTRY_ALLOW_FAILURE: 'false',
});

// Original behavior: script exits 0, but Xcode fails build due to "error:" prefix
expect(result.exitCode).toBe(0);
expect(result.stdout).toContain('error: sentry-cli');
});

it('skips upload when SENTRY_DISABLE_AUTO_UPLOAD=true', () => {
const result = runScript({
SENTRY_DISABLE_AUTO_UPLOAD: 'true',
});

expect(result.exitCode).toBe(0);
expect(result.stdout).toContain('SENTRY_DISABLE_AUTO_UPLOAD=true');
expect(result.stdout).toContain('skipping debug files upload');
});

it('skips upload for Debug configuration', () => {
const result = runScript({
CONFIGURATION: 'Debug',
});

expect(result.exitCode).toBe(0);
expect(result.stdout).toContain('Skipping debug files upload for *Debug* configuration');
});

it('skips upload for debug configuration (case insensitive)', () => {
const result = runScript({
CONFIGURATION: 'debug',
});

expect(result.exitCode).toBe(0);
expect(result.stdout).toContain('Skipping debug files upload for *Debug* configuration');
});
});

describe('sentry-xcode.sh', () => {
let tempDir: string;
let mockSentryCliScript: string;
let mockReactNativeScript: string;

beforeEach(() => {
// Create a temporary directory for test artifacts
tempDir = fs.mkdtempSync(path.join(os.tmpdir(), 'sentry-xcode-test-'));

// Create a mock sentry-cli script
mockSentryCliScript = path.join(tempDir, 'mock-sentry-cli.js');
fs.writeFileSync(
mockSentryCliScript,
`
const exitCode = process.env.MOCK_CLI_EXIT_CODE || '0';
const output = process.env.MOCK_CLI_OUTPUT || 'Mock upload output';
console.log(output);
process.exit(parseInt(exitCode));
`,
);

// Create a mock react-native-xcode.sh script
mockReactNativeScript = path.join(tempDir, 'react-native-xcode.sh');
fs.writeFileSync(
mockReactNativeScript,
`#!/bin/bash
echo "Mock React Native bundle"
exit 0
`,
);
fs.chmodSync(mockReactNativeScript, '755');
});

afterEach(() => {
// Clean up temp directory
if (tempDir && fs.existsSync(tempDir)) {
fs.rmSync(tempDir, { recursive: true, force: true });
}
});

const runScript = (env: Record<string, string> = {}): { stdout: string; stderr: string; exitCode: number } => {
// Create a mock collect-modules.sh script to prevent script failure
const mockCollectModulesScript = path.join(tempDir, 'collect-modules.sh');
fs.writeFileSync(mockCollectModulesScript, '#!/bin/bash\nexit 0\n');
fs.chmodSync(mockCollectModulesScript, '755');

const defaultEnv = {
NODE_BINARY: process.execPath,
SENTRY_CLI_EXECUTABLE: mockSentryCliScript,
PROJECT_DIR: tempDir,
DERIVED_FILE_DIR: tempDir,
SENTRY_COLLECT_MODULES: mockCollectModulesScript, // Set this to avoid package resolution failure
};

try {
const stdout = execSync(`bash "${XCODE_SCRIPT}" "${mockReactNativeScript}"`, {
env: { ...process.env, ...defaultEnv, ...env },
encoding: 'utf8',
stdio: 'pipe',
});
return { stdout, stderr: '', exitCode: 0 };
} catch (error: any) {
return {
stdout: error.stdout?.toString() || '',
stderr: error.stderr?.toString() || '',
exitCode: error.status || 1,
};
}
};

it('exits with 0 when upload succeeds', () => {
const result = runScript({
MOCK_CLI_EXIT_CODE: '0',
MOCK_CLI_OUTPUT: 'Source maps uploaded successfully',
});

expect(result.exitCode).toBe(0);
expect(result.stdout).toContain('Source maps uploaded successfully');
});

it('exits with 0 when SENTRY_ALLOW_FAILURE=true and upload fails', () => {
const result = runScript({
MOCK_CLI_EXIT_CODE: '1',
MOCK_CLI_OUTPUT: 'Upload failed: Connection timeout',
SENTRY_ALLOW_FAILURE: 'true',
});

expect(result.exitCode).toBe(0);
expect(result.stdout).toContain('warning: sentry-cli');
expect(result.stdout).toContain('continuing build because SENTRY_ALLOW_FAILURE=true');
expect(result.stdout).toContain('Upload failed: Connection timeout');
});

it('exits with 1 when SENTRY_ALLOW_FAILURE not set and upload fails', () => {
const result = runScript({
MOCK_CLI_EXIT_CODE: '1',
MOCK_CLI_OUTPUT: 'Upload failed: Invalid auth token',
});

expect(result.exitCode).toBe(1);
expect(result.stdout).toContain('error: sentry-cli');
expect(result.stdout).toContain('SENTRY_ALLOW_FAILURE=true');
expect(result.stdout).toContain('Upload failed: Invalid auth token');
});

it('exits with 1 when SENTRY_ALLOW_FAILURE=false and upload fails', () => {
const result = runScript({
MOCK_CLI_EXIT_CODE: '1',
MOCK_CLI_OUTPUT: 'Upload failed',
SENTRY_ALLOW_FAILURE: 'false',
});

expect(result.exitCode).toBe(1);
expect(result.stdout).toContain('error: sentry-cli');
});

it('skips upload when SENTRY_DISABLE_AUTO_UPLOAD=true', () => {
const result = runScript({
SENTRY_DISABLE_AUTO_UPLOAD: 'true',
});

expect(result.exitCode).toBe(0);
expect(result.stdout).toContain('SENTRY_DISABLE_AUTO_UPLOAD=true');
expect(result.stdout).toContain('skipping sourcemaps upload');
});
});
Loading