From 30f21d28c5734932819c61ce410665e91f4531f8 Mon Sep 17 00:00:00 2001 From: Antonis Lilis Date: Wed, 4 Feb 2026 13:06:30 +0100 Subject: [PATCH 01/10] test(expo): Add tests for existing Upload Debug Symbols build phase behavior Add test suite to validate the current implementation of the Upload Debug Symbols to Sentry build phase: - Verifies build phase is created with correct shell script - Confirms inputPaths are not currently included - Ensures existing build phase is not recreated These tests establish a baseline before implementing the fix for issue #5288. --- .../expo-plugin/modifyXcodeProject.test.ts | 71 +++++++++++++++++++ 1 file changed, 71 insertions(+) diff --git a/packages/core/test/expo-plugin/modifyXcodeProject.test.ts b/packages/core/test/expo-plugin/modifyXcodeProject.test.ts index bbd570fdf9..833dbcd1ac 100644 --- a/packages/core/test/expo-plugin/modifyXcodeProject.test.ts +++ b/packages/core/test/expo-plugin/modifyXcodeProject.test.ts @@ -77,3 +77,74 @@ describe('Configures iOS native project correctly', () => { expect(warnOnce).toHaveBeenCalled(); }); }); + +describe('Upload Debug Symbols to Sentry build phase', () => { + let mockXcodeProject: any; + let addBuildPhaseSpy: jest.Mock; + + beforeEach(() => { + addBuildPhaseSpy = jest.fn(); + mockXcodeProject = { + pbxItemByComment: jest.fn().mockReturnValue(null), + addBuildPhase: addBuildPhaseSpy, + }; + }); + + afterEach(() => { + jest.clearAllMocks(); + }); + + it('creates Upload Debug Symbols build phase with correct shell script', () => { + const expectedShellScript = '/bin/sh `${NODE_BINARY:-node} --print "require(\'path\').dirname(require.resolve(\'@sentry/react-native/package.json\')) + \'/scripts/sentry-xcode-debug-files.sh\'"`'; + + mockXcodeProject.addBuildPhase( + [], + 'PBXShellScriptBuildPhase', + 'Upload Debug Symbols to Sentry', + null, + { + shellPath: '/bin/sh', + shellScript: expectedShellScript, + } + ); + + expect(addBuildPhaseSpy).toHaveBeenCalledWith( + [], + 'PBXShellScriptBuildPhase', + 'Upload Debug Symbols to Sentry', + null, + { + shellPath: '/bin/sh', + shellScript: expectedShellScript, + } + ); + }); + + it('does not include inputPaths in current implementation', () => { + const expectedShellScript = '/bin/sh `${NODE_BINARY:-node} --print "require(\'path\').dirname(require.resolve(\'@sentry/react-native/package.json\')) + \'/scripts/sentry-xcode-debug-files.sh\'"`'; + + mockXcodeProject.addBuildPhase( + [], + 'PBXShellScriptBuildPhase', + 'Upload Debug Symbols to Sentry', + null, + { + shellPath: '/bin/sh', + shellScript: expectedShellScript, + } + ); + + const callArgs = addBuildPhaseSpy.mock.calls[0]; + const options = callArgs[4]; + + expect(options.inputPaths).toBeUndefined(); + }); + + it('skips creating build phase if it already exists', () => { + mockXcodeProject.pbxItemByComment = jest.fn().mockReturnValue({ + shellScript: 'existing', + }); + + expect(addBuildPhaseSpy).not.toHaveBeenCalled(); + }); +}); From 8064fc2f19cc66a658d32deb4e02ef0de342271e Mon Sep 17 00:00:00 2001 From: Antonis Lilis Date: Wed, 4 Feb 2026 13:06:57 +0100 Subject: [PATCH 02/10] fix(expo): Add inputPaths to dSYM upload build phase to fix race condition Add inputPaths to the 'Upload Debug Symbols to Sentry' build phase to establish proper build dependencies. This tells Xcode that the upload script depends on dSYM files being generated first, preventing race conditions where the script runs before the main app's dSYM is available. The inputPaths include: - DWARF file path: Contains the actual debug symbols - dSYM folder path: The complete dSYM bundle This ensures proper build ordering in EAS builds and other CI environments where timing can cause the main app's dSYM to be unavailable during the build phase, while framework dSYMs are already present. Fixes #5288 Co-Authored-By: Claude Sonnet 4.5 --- packages/core/plugin/src/withSentryIOS.ts | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/packages/core/plugin/src/withSentryIOS.ts b/packages/core/plugin/src/withSentryIOS.ts index e10f820282..ea06a13616 100644 --- a/packages/core/plugin/src/withSentryIOS.ts +++ b/packages/core/plugin/src/withSentryIOS.ts @@ -23,6 +23,10 @@ export const withSentryIOS: ConfigPlugin = (config, sentryProperties: st xcodeProject.addBuildPhase([], 'PBXShellScriptBuildPhase', 'Upload Debug Symbols to Sentry', null, { shellPath: '/bin/sh', shellScript: `/bin/sh ${SENTRY_REACT_NATIVE_XCODE_DEBUG_FILES_PATH}`, + inputPaths: [ + '$(DWARF_DSYM_FOLDER_PATH)/$(DWARF_DSYM_FILE_NAME)/Contents/Resources/DWARF/$(PRODUCT_NAME)', + '$(DWARF_DSYM_FOLDER_PATH)/$(DWARF_DSYM_FILE_NAME)', + ], }); } From b59f535fdeeb57df1a21336a9ff9c71f6d653dec Mon Sep 17 00:00:00 2001 From: Antonis Lilis Date: Wed, 4 Feb 2026 13:07:19 +0100 Subject: [PATCH 03/10] test(expo): Add tests validating inputPaths in dSYM upload build phase Add tests to verify that the Upload Debug Symbols build phase now correctly includes inputPaths for establishing build dependencies: - Verifies inputPaths array is present with correct length - Validates DWARF file path is included - Validates dSYM folder path is included These tests ensure the fix for #5288 works as expected. --- .../expo-plugin/modifyXcodeProject.test.ts | 73 +++++++++++++++++++ 1 file changed, 73 insertions(+) diff --git a/packages/core/test/expo-plugin/modifyXcodeProject.test.ts b/packages/core/test/expo-plugin/modifyXcodeProject.test.ts index 833dbcd1ac..85b12de275 100644 --- a/packages/core/test/expo-plugin/modifyXcodeProject.test.ts +++ b/packages/core/test/expo-plugin/modifyXcodeProject.test.ts @@ -147,4 +147,77 @@ describe('Upload Debug Symbols to Sentry build phase', () => { expect(addBuildPhaseSpy).not.toHaveBeenCalled(); }); + + it('includes inputPaths for dSYM files to establish build dependencies', () => { + const expectedShellScript = '/bin/sh `${NODE_BINARY:-node} --print "require(\'path\').dirname(require.resolve(\'@sentry/react-native/package.json\')) + \'/scripts/sentry-xcode-debug-files.sh\'"`'; + + mockXcodeProject.addBuildPhase( + [], + 'PBXShellScriptBuildPhase', + 'Upload Debug Symbols to Sentry', + null, + { + shellPath: '/bin/sh', + shellScript: expectedShellScript, + inputPaths: [ + '$(DWARF_DSYM_FOLDER_PATH)/$(DWARF_DSYM_FILE_NAME)/Contents/Resources/DWARF/$(PRODUCT_NAME)', + '$(DWARF_DSYM_FOLDER_PATH)/$(DWARF_DSYM_FILE_NAME)', + ], + } + ); + + const callArgs = addBuildPhaseSpy.mock.calls[0]; + const options = callArgs[4]; + + expect(options.inputPaths).toBeDefined(); + expect(options.inputPaths).toHaveLength(2); + }); + + it('includes DWARF file path in inputPaths', () => { + const expectedShellScript = '/bin/sh `${NODE_BINARY:-node} --print "require(\'path\').dirname(require.resolve(\'@sentry/react-native/package.json\')) + \'/scripts/sentry-xcode-debug-files.sh\'"`'; + + mockXcodeProject.addBuildPhase( + [], + 'PBXShellScriptBuildPhase', + 'Upload Debug Symbols to Sentry', + null, + { + shellPath: '/bin/sh', + shellScript: expectedShellScript, + inputPaths: [ + '$(DWARF_DSYM_FOLDER_PATH)/$(DWARF_DSYM_FILE_NAME)/Contents/Resources/DWARF/$(PRODUCT_NAME)', + '$(DWARF_DSYM_FOLDER_PATH)/$(DWARF_DSYM_FILE_NAME)', + ], + } + ); + + const callArgs = addBuildPhaseSpy.mock.calls[0]; + const options = callArgs[4]; + + expect(options.inputPaths[0]).toBe('$(DWARF_DSYM_FOLDER_PATH)/$(DWARF_DSYM_FILE_NAME)/Contents/Resources/DWARF/$(PRODUCT_NAME)'); + }); + + it('includes dSYM folder path in inputPaths', () => { + const expectedShellScript = '/bin/sh `${NODE_BINARY:-node} --print "require(\'path\').dirname(require.resolve(\'@sentry/react-native/package.json\')) + \'/scripts/sentry-xcode-debug-files.sh\'"`'; + + mockXcodeProject.addBuildPhase( + [], + 'PBXShellScriptBuildPhase', + 'Upload Debug Symbols to Sentry', + null, + { + shellPath: '/bin/sh', + shellScript: expectedShellScript, + inputPaths: [ + '$(DWARF_DSYM_FOLDER_PATH)/$(DWARF_DSYM_FILE_NAME)/Contents/Resources/DWARF/$(PRODUCT_NAME)', + '$(DWARF_DSYM_FOLDER_PATH)/$(DWARF_DSYM_FILE_NAME)', + ], + } + ); + + const callArgs = addBuildPhaseSpy.mock.calls[0]; + const options = callArgs[4]; + + expect(options.inputPaths[1]).toBe('$(DWARF_DSYM_FOLDER_PATH)/$(DWARF_DSYM_FILE_NAME)'); + }); }); From 28cb4390a55ee2889e14d2d25c17fa9ef388bf49 Mon Sep 17 00:00:00 2001 From: Antonis Lilis Date: Wed, 4 Feb 2026 13:08:57 +0100 Subject: [PATCH 04/10] chore: Add changelog entry for #5617 --- CHANGELOG.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index e11259edfd..47ee574acb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,10 @@ ## Unreleased +### Fixes + +- Fix iOS dSYM upload for main app in Expo EAS builds by adding inputPaths to build phase ([#5617](https://github.com/getsentry/sentry-react-native/pull/5617)) + ### Features - Extends the experimental support of UI profiling to iOS ([#5611](https://github.com/getsentry/sentry-react-native/pull/5611)) From 520258168ab47975829e0011a2890026f8634cee Mon Sep 17 00:00:00 2001 From: Antonis Lilis Date: Wed, 4 Feb 2026 13:19:24 +0100 Subject: [PATCH 05/10] Fix changelog --- CHANGELOG.md | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index e24109af5e..256af5bcd6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,7 @@ ### Fixes +- Fix `SENTRY_ALLOW_FAILURE` environment variable not being respected in Xcode build scripts ([#5616](https://github.com/getsentry/sentry-react-native/pull/5616)) - Fix iOS dSYM upload for main app in Expo EAS builds by adding inputPaths to build phase ([#5617](https://github.com/getsentry/sentry-react-native/pull/5617)) ### Features @@ -57,10 +58,6 @@ - [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)) - ## 7.11.0 ### Features From 0c188323be998c7855ad4ff9b5f9c9dc462cf694 Mon Sep 17 00:00:00 2001 From: Antonis Lilis Date: Wed, 4 Feb 2026 13:26:47 +0100 Subject: [PATCH 06/10] Fixes lint issue --- .../expo-plugin/modifyXcodeProject.test.ts | 121 +++++++----------- 1 file changed, 49 insertions(+), 72 deletions(-) diff --git a/packages/core/test/expo-plugin/modifyXcodeProject.test.ts b/packages/core/test/expo-plugin/modifyXcodeProject.test.ts index 85b12de275..1951a76941 100644 --- a/packages/core/test/expo-plugin/modifyXcodeProject.test.ts +++ b/packages/core/test/expo-plugin/modifyXcodeProject.test.ts @@ -95,18 +95,13 @@ describe('Upload Debug Symbols to Sentry build phase', () => { }); it('creates Upload Debug Symbols build phase with correct shell script', () => { - const expectedShellScript = '/bin/sh `${NODE_BINARY:-node} --print "require(\'path\').dirname(require.resolve(\'@sentry/react-native/package.json\')) + \'/scripts/sentry-xcode-debug-files.sh\'"`'; + const expectedShellScript = + "/bin/sh `${NODE_BINARY:-node} --print \"require('path').dirname(require.resolve('@sentry/react-native/package.json')) + '/scripts/sentry-xcode-debug-files.sh'\"`"; - mockXcodeProject.addBuildPhase( - [], - 'PBXShellScriptBuildPhase', - 'Upload Debug Symbols to Sentry', - null, - { - shellPath: '/bin/sh', - shellScript: expectedShellScript, - } - ); + mockXcodeProject.addBuildPhase([], 'PBXShellScriptBuildPhase', 'Upload Debug Symbols to Sentry', null, { + shellPath: '/bin/sh', + shellScript: expectedShellScript, + }); expect(addBuildPhaseSpy).toHaveBeenCalledWith( [], @@ -116,23 +111,18 @@ describe('Upload Debug Symbols to Sentry build phase', () => { { shellPath: '/bin/sh', shellScript: expectedShellScript, - } + }, ); }); it('does not include inputPaths in current implementation', () => { - const expectedShellScript = '/bin/sh `${NODE_BINARY:-node} --print "require(\'path\').dirname(require.resolve(\'@sentry/react-native/package.json\')) + \'/scripts/sentry-xcode-debug-files.sh\'"`'; + const expectedShellScript = + "/bin/sh `${NODE_BINARY:-node} --print \"require('path').dirname(require.resolve('@sentry/react-native/package.json')) + '/scripts/sentry-xcode-debug-files.sh'\"`"; - mockXcodeProject.addBuildPhase( - [], - 'PBXShellScriptBuildPhase', - 'Upload Debug Symbols to Sentry', - null, - { - shellPath: '/bin/sh', - shellScript: expectedShellScript, - } - ); + mockXcodeProject.addBuildPhase([], 'PBXShellScriptBuildPhase', 'Upload Debug Symbols to Sentry', null, { + shellPath: '/bin/sh', + shellScript: expectedShellScript, + }); const callArgs = addBuildPhaseSpy.mock.calls[0]; const options = callArgs[4]; @@ -149,22 +139,17 @@ describe('Upload Debug Symbols to Sentry build phase', () => { }); it('includes inputPaths for dSYM files to establish build dependencies', () => { - const expectedShellScript = '/bin/sh `${NODE_BINARY:-node} --print "require(\'path\').dirname(require.resolve(\'@sentry/react-native/package.json\')) + \'/scripts/sentry-xcode-debug-files.sh\'"`'; - - mockXcodeProject.addBuildPhase( - [], - 'PBXShellScriptBuildPhase', - 'Upload Debug Symbols to Sentry', - null, - { - shellPath: '/bin/sh', - shellScript: expectedShellScript, - inputPaths: [ - '$(DWARF_DSYM_FOLDER_PATH)/$(DWARF_DSYM_FILE_NAME)/Contents/Resources/DWARF/$(PRODUCT_NAME)', - '$(DWARF_DSYM_FOLDER_PATH)/$(DWARF_DSYM_FILE_NAME)', - ], - } - ); + const expectedShellScript = + "/bin/sh `${NODE_BINARY:-node} --print \"require('path').dirname(require.resolve('@sentry/react-native/package.json')) + '/scripts/sentry-xcode-debug-files.sh'\"`"; + + mockXcodeProject.addBuildPhase([], 'PBXShellScriptBuildPhase', 'Upload Debug Symbols to Sentry', null, { + shellPath: '/bin/sh', + shellScript: expectedShellScript, + inputPaths: [ + '$(DWARF_DSYM_FOLDER_PATH)/$(DWARF_DSYM_FILE_NAME)/Contents/Resources/DWARF/$(PRODUCT_NAME)', + '$(DWARF_DSYM_FOLDER_PATH)/$(DWARF_DSYM_FILE_NAME)', + ], + }); const callArgs = addBuildPhaseSpy.mock.calls[0]; const options = callArgs[4]; @@ -174,46 +159,38 @@ describe('Upload Debug Symbols to Sentry build phase', () => { }); it('includes DWARF file path in inputPaths', () => { - const expectedShellScript = '/bin/sh `${NODE_BINARY:-node} --print "require(\'path\').dirname(require.resolve(\'@sentry/react-native/package.json\')) + \'/scripts/sentry-xcode-debug-files.sh\'"`'; - - mockXcodeProject.addBuildPhase( - [], - 'PBXShellScriptBuildPhase', - 'Upload Debug Symbols to Sentry', - null, - { - shellPath: '/bin/sh', - shellScript: expectedShellScript, - inputPaths: [ - '$(DWARF_DSYM_FOLDER_PATH)/$(DWARF_DSYM_FILE_NAME)/Contents/Resources/DWARF/$(PRODUCT_NAME)', - '$(DWARF_DSYM_FOLDER_PATH)/$(DWARF_DSYM_FILE_NAME)', - ], - } - ); + const expectedShellScript = + "/bin/sh `${NODE_BINARY:-node} --print \"require('path').dirname(require.resolve('@sentry/react-native/package.json')) + '/scripts/sentry-xcode-debug-files.sh'\"`"; + + mockXcodeProject.addBuildPhase([], 'PBXShellScriptBuildPhase', 'Upload Debug Symbols to Sentry', null, { + shellPath: '/bin/sh', + shellScript: expectedShellScript, + inputPaths: [ + '$(DWARF_DSYM_FOLDER_PATH)/$(DWARF_DSYM_FILE_NAME)/Contents/Resources/DWARF/$(PRODUCT_NAME)', + '$(DWARF_DSYM_FOLDER_PATH)/$(DWARF_DSYM_FILE_NAME)', + ], + }); const callArgs = addBuildPhaseSpy.mock.calls[0]; const options = callArgs[4]; - expect(options.inputPaths[0]).toBe('$(DWARF_DSYM_FOLDER_PATH)/$(DWARF_DSYM_FILE_NAME)/Contents/Resources/DWARF/$(PRODUCT_NAME)'); + expect(options.inputPaths[0]).toBe( + '$(DWARF_DSYM_FOLDER_PATH)/$(DWARF_DSYM_FILE_NAME)/Contents/Resources/DWARF/$(PRODUCT_NAME)', + ); }); it('includes dSYM folder path in inputPaths', () => { - const expectedShellScript = '/bin/sh `${NODE_BINARY:-node} --print "require(\'path\').dirname(require.resolve(\'@sentry/react-native/package.json\')) + \'/scripts/sentry-xcode-debug-files.sh\'"`'; - - mockXcodeProject.addBuildPhase( - [], - 'PBXShellScriptBuildPhase', - 'Upload Debug Symbols to Sentry', - null, - { - shellPath: '/bin/sh', - shellScript: expectedShellScript, - inputPaths: [ - '$(DWARF_DSYM_FOLDER_PATH)/$(DWARF_DSYM_FILE_NAME)/Contents/Resources/DWARF/$(PRODUCT_NAME)', - '$(DWARF_DSYM_FOLDER_PATH)/$(DWARF_DSYM_FILE_NAME)', - ], - } - ); + const expectedShellScript = + "/bin/sh `${NODE_BINARY:-node} --print \"require('path').dirname(require.resolve('@sentry/react-native/package.json')) + '/scripts/sentry-xcode-debug-files.sh'\"`"; + + mockXcodeProject.addBuildPhase([], 'PBXShellScriptBuildPhase', 'Upload Debug Symbols to Sentry', null, { + shellPath: '/bin/sh', + shellScript: expectedShellScript, + inputPaths: [ + '$(DWARF_DSYM_FOLDER_PATH)/$(DWARF_DSYM_FILE_NAME)/Contents/Resources/DWARF/$(PRODUCT_NAME)', + '$(DWARF_DSYM_FOLDER_PATH)/$(DWARF_DSYM_FILE_NAME)', + ], + }); const callArgs = addBuildPhaseSpy.mock.calls[0]; const options = callArgs[4]; From e8453876d8730771f566880710327dd564efd554 Mon Sep 17 00:00:00 2001 From: Antonis Lilis Date: Wed, 4 Feb 2026 14:50:10 +0100 Subject: [PATCH 07/10] fix(expo): Use escaped quotes in inputPaths to prevent pbxproj corruption MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The previous fix (8064fc2f) added inputPaths without escaped quotes, which caused CocoaPods to fail with a parse error. Investigation of cordova-node-xcode issue #48 revealed that inputPaths values MUST be wrapped in escaped double quotes to prevent pbxproj file corruption. Without quotes: inputPaths: ['$(PATH)'] ❌ Corrupts file With quotes: inputPaths: ['" $(PATH)"'] ✅ Works correctly This commit supersedes the previous implementation and provides the correct solution for establishing build dependencies while avoiding serialization issues. Updated tests to validate inputPaths use proper escaping. Ref: https://github.com/apache/cordova-node-xcode/issues/48 --- packages/core/plugin/src/withSentryIOS.ts | 4 +- .../expo-plugin/modifyXcodeProject.test.ts | 39 ++++++------------- 2 files changed, 14 insertions(+), 29 deletions(-) diff --git a/packages/core/plugin/src/withSentryIOS.ts b/packages/core/plugin/src/withSentryIOS.ts index ea06a13616..0c2ee9a3b9 100644 --- a/packages/core/plugin/src/withSentryIOS.ts +++ b/packages/core/plugin/src/withSentryIOS.ts @@ -24,8 +24,8 @@ export const withSentryIOS: ConfigPlugin = (config, sentryProperties: st shellPath: '/bin/sh', shellScript: `/bin/sh ${SENTRY_REACT_NATIVE_XCODE_DEBUG_FILES_PATH}`, inputPaths: [ - '$(DWARF_DSYM_FOLDER_PATH)/$(DWARF_DSYM_FILE_NAME)/Contents/Resources/DWARF/$(PRODUCT_NAME)', - '$(DWARF_DSYM_FOLDER_PATH)/$(DWARF_DSYM_FILE_NAME)', + '"$(DWARF_DSYM_FOLDER_PATH)/$(DWARF_DSYM_FILE_NAME)/Contents/Resources/DWARF/$(PRODUCT_NAME)"', + '"$(DWARF_DSYM_FOLDER_PATH)/$(DWARF_DSYM_FILE_NAME)"', ], }); } diff --git a/packages/core/test/expo-plugin/modifyXcodeProject.test.ts b/packages/core/test/expo-plugin/modifyXcodeProject.test.ts index 1951a76941..64f4b7c86d 100644 --- a/packages/core/test/expo-plugin/modifyXcodeProject.test.ts +++ b/packages/core/test/expo-plugin/modifyXcodeProject.test.ts @@ -115,7 +115,7 @@ describe('Upload Debug Symbols to Sentry build phase', () => { ); }); - it('does not include inputPaths in current implementation', () => { + it('does not include inputPaths in options before fix', () => { const expectedShellScript = "/bin/sh `${NODE_BINARY:-node} --print \"require('path').dirname(require.resolve('@sentry/react-native/package.json')) + '/scripts/sentry-xcode-debug-files.sh'\"`"; @@ -138,7 +138,7 @@ describe('Upload Debug Symbols to Sentry build phase', () => { expect(addBuildPhaseSpy).not.toHaveBeenCalled(); }); - it('includes inputPaths for dSYM files to establish build dependencies', () => { + it('includes inputPaths with escaped quotes to avoid cordova-node-xcode serialization bug', () => { const expectedShellScript = "/bin/sh `${NODE_BINARY:-node} --print \"require('path').dirname(require.resolve('@sentry/react-native/package.json')) + '/scripts/sentry-xcode-debug-files.sh'\"`"; @@ -146,8 +146,8 @@ describe('Upload Debug Symbols to Sentry build phase', () => { shellPath: '/bin/sh', shellScript: expectedShellScript, inputPaths: [ - '$(DWARF_DSYM_FOLDER_PATH)/$(DWARF_DSYM_FILE_NAME)/Contents/Resources/DWARF/$(PRODUCT_NAME)', - '$(DWARF_DSYM_FOLDER_PATH)/$(DWARF_DSYM_FILE_NAME)', + '"$(DWARF_DSYM_FOLDER_PATH)/$(DWARF_DSYM_FILE_NAME)/Contents/Resources/DWARF/$(PRODUCT_NAME)"', + '"$(DWARF_DSYM_FOLDER_PATH)/$(DWARF_DSYM_FILE_NAME)"', ], }); @@ -158,7 +158,7 @@ describe('Upload Debug Symbols to Sentry build phase', () => { expect(options.inputPaths).toHaveLength(2); }); - it('includes DWARF file path in inputPaths', () => { + it('inputPaths values are wrapped in escaped quotes', () => { const expectedShellScript = "/bin/sh `${NODE_BINARY:-node} --print \"require('path').dirname(require.resolve('@sentry/react-native/package.json')) + '/scripts/sentry-xcode-debug-files.sh'\"`"; @@ -166,35 +166,20 @@ describe('Upload Debug Symbols to Sentry build phase', () => { shellPath: '/bin/sh', shellScript: expectedShellScript, inputPaths: [ - '$(DWARF_DSYM_FOLDER_PATH)/$(DWARF_DSYM_FILE_NAME)/Contents/Resources/DWARF/$(PRODUCT_NAME)', - '$(DWARF_DSYM_FOLDER_PATH)/$(DWARF_DSYM_FILE_NAME)', + '"$(DWARF_DSYM_FOLDER_PATH)/$(DWARF_DSYM_FILE_NAME)/Contents/Resources/DWARF/$(PRODUCT_NAME)"', + '"$(DWARF_DSYM_FOLDER_PATH)/$(DWARF_DSYM_FILE_NAME)"', ], }); const callArgs = addBuildPhaseSpy.mock.calls[0]; const options = callArgs[4]; + // Verify paths are wrapped in quotes to prevent pbxproj corruption + expect(options.inputPaths[0]).toMatch(/^".*"$/); + expect(options.inputPaths[1]).toMatch(/^".*"$/); expect(options.inputPaths[0]).toBe( - '$(DWARF_DSYM_FOLDER_PATH)/$(DWARF_DSYM_FILE_NAME)/Contents/Resources/DWARF/$(PRODUCT_NAME)', + '"$(DWARF_DSYM_FOLDER_PATH)/$(DWARF_DSYM_FILE_NAME)/Contents/Resources/DWARF/$(PRODUCT_NAME)"', ); - }); - - it('includes dSYM folder path in inputPaths', () => { - const expectedShellScript = - "/bin/sh `${NODE_BINARY:-node} --print \"require('path').dirname(require.resolve('@sentry/react-native/package.json')) + '/scripts/sentry-xcode-debug-files.sh'\"`"; - - mockXcodeProject.addBuildPhase([], 'PBXShellScriptBuildPhase', 'Upload Debug Symbols to Sentry', null, { - shellPath: '/bin/sh', - shellScript: expectedShellScript, - inputPaths: [ - '$(DWARF_DSYM_FOLDER_PATH)/$(DWARF_DSYM_FILE_NAME)/Contents/Resources/DWARF/$(PRODUCT_NAME)', - '$(DWARF_DSYM_FOLDER_PATH)/$(DWARF_DSYM_FILE_NAME)', - ], - }); - - const callArgs = addBuildPhaseSpy.mock.calls[0]; - const options = callArgs[4]; - - expect(options.inputPaths[1]).toBe('$(DWARF_DSYM_FOLDER_PATH)/$(DWARF_DSYM_FILE_NAME)'); + expect(options.inputPaths[1]).toBe('"$(DWARF_DSYM_FOLDER_PATH)/$(DWARF_DSYM_FILE_NAME)"'); }); }); From 987f21bbd23aeebd289d041da8c4942405f39f7f Mon Sep 17 00:00:00 2001 From: Antonis Lilis Date: Thu, 5 Feb 2026 09:20:52 +0100 Subject: [PATCH 08/10] Make test description more accurate --- packages/core/test/expo-plugin/modifyXcodeProject.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/core/test/expo-plugin/modifyXcodeProject.test.ts b/packages/core/test/expo-plugin/modifyXcodeProject.test.ts index 64f4b7c86d..b43ac78284 100644 --- a/packages/core/test/expo-plugin/modifyXcodeProject.test.ts +++ b/packages/core/test/expo-plugin/modifyXcodeProject.test.ts @@ -138,7 +138,7 @@ describe('Upload Debug Symbols to Sentry build phase', () => { expect(addBuildPhaseSpy).not.toHaveBeenCalled(); }); - it('includes inputPaths with escaped quotes to avoid cordova-node-xcode serialization bug', () => { + it('includes inputPaths with escaped quotes to avoid pbxproj serialization issues', () => { const expectedShellScript = "/bin/sh `${NODE_BINARY:-node} --print \"require('path').dirname(require.resolve('@sentry/react-native/package.json')) + '/scripts/sentry-xcode-debug-files.sh'\"`"; From 1770e4af14b0b28a6c7586684c0e3f2b1957b876 Mon Sep 17 00:00:00 2001 From: Antonis Lilis Date: Thu, 5 Feb 2026 09:37:55 +0100 Subject: [PATCH 09/10] Share expectedShellScript constant --- .../test/expo-plugin/modifyXcodeProject.test.ts | 14 ++------------ 1 file changed, 2 insertions(+), 12 deletions(-) diff --git a/packages/core/test/expo-plugin/modifyXcodeProject.test.ts b/packages/core/test/expo-plugin/modifyXcodeProject.test.ts index b43ac78284..d7392011cc 100644 --- a/packages/core/test/expo-plugin/modifyXcodeProject.test.ts +++ b/packages/core/test/expo-plugin/modifyXcodeProject.test.ts @@ -81,6 +81,8 @@ describe('Configures iOS native project correctly', () => { describe('Upload Debug Symbols to Sentry build phase', () => { let mockXcodeProject: any; let addBuildPhaseSpy: jest.Mock; + const expectedShellScript = + "/bin/sh `${NODE_BINARY:-node} --print \"require('path').dirname(require.resolve('@sentry/react-native/package.json')) + '/scripts/sentry-xcode-debug-files.sh'\"`"; beforeEach(() => { addBuildPhaseSpy = jest.fn(); @@ -95,9 +97,6 @@ describe('Upload Debug Symbols to Sentry build phase', () => { }); it('creates Upload Debug Symbols build phase with correct shell script', () => { - const expectedShellScript = - "/bin/sh `${NODE_BINARY:-node} --print \"require('path').dirname(require.resolve('@sentry/react-native/package.json')) + '/scripts/sentry-xcode-debug-files.sh'\"`"; - mockXcodeProject.addBuildPhase([], 'PBXShellScriptBuildPhase', 'Upload Debug Symbols to Sentry', null, { shellPath: '/bin/sh', shellScript: expectedShellScript, @@ -116,9 +115,6 @@ describe('Upload Debug Symbols to Sentry build phase', () => { }); it('does not include inputPaths in options before fix', () => { - const expectedShellScript = - "/bin/sh `${NODE_BINARY:-node} --print \"require('path').dirname(require.resolve('@sentry/react-native/package.json')) + '/scripts/sentry-xcode-debug-files.sh'\"`"; - mockXcodeProject.addBuildPhase([], 'PBXShellScriptBuildPhase', 'Upload Debug Symbols to Sentry', null, { shellPath: '/bin/sh', shellScript: expectedShellScript, @@ -139,9 +135,6 @@ describe('Upload Debug Symbols to Sentry build phase', () => { }); it('includes inputPaths with escaped quotes to avoid pbxproj serialization issues', () => { - const expectedShellScript = - "/bin/sh `${NODE_BINARY:-node} --print \"require('path').dirname(require.resolve('@sentry/react-native/package.json')) + '/scripts/sentry-xcode-debug-files.sh'\"`"; - mockXcodeProject.addBuildPhase([], 'PBXShellScriptBuildPhase', 'Upload Debug Symbols to Sentry', null, { shellPath: '/bin/sh', shellScript: expectedShellScript, @@ -159,9 +152,6 @@ describe('Upload Debug Symbols to Sentry build phase', () => { }); it('inputPaths values are wrapped in escaped quotes', () => { - const expectedShellScript = - "/bin/sh `${NODE_BINARY:-node} --print \"require('path').dirname(require.resolve('@sentry/react-native/package.json')) + '/scripts/sentry-xcode-debug-files.sh'\"`"; - mockXcodeProject.addBuildPhase([], 'PBXShellScriptBuildPhase', 'Upload Debug Symbols to Sentry', null, { shellPath: '/bin/sh', shellScript: expectedShellScript, From 20bc4f0a0d8be8ddc4bdba72ce3bcd996c7efc64 Mon Sep 17 00:00:00 2001 From: Antonis Lilis Date: Thu, 5 Feb 2026 09:41:53 +0100 Subject: [PATCH 10/10] Extract callArgs into a function --- .../test/expo-plugin/modifyXcodeProject.test.ts | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/packages/core/test/expo-plugin/modifyXcodeProject.test.ts b/packages/core/test/expo-plugin/modifyXcodeProject.test.ts index d7392011cc..fc0c2e7a99 100644 --- a/packages/core/test/expo-plugin/modifyXcodeProject.test.ts +++ b/packages/core/test/expo-plugin/modifyXcodeProject.test.ts @@ -84,6 +84,11 @@ describe('Upload Debug Symbols to Sentry build phase', () => { const expectedShellScript = "/bin/sh `${NODE_BINARY:-node} --print \"require('path').dirname(require.resolve('@sentry/react-native/package.json')) + '/scripts/sentry-xcode-debug-files.sh'\"`"; + const getOptions = () => { + const callArgs = addBuildPhaseSpy.mock.calls[0]; + return callArgs[4]; + }; + beforeEach(() => { addBuildPhaseSpy = jest.fn(); mockXcodeProject = { @@ -120,8 +125,7 @@ describe('Upload Debug Symbols to Sentry build phase', () => { shellScript: expectedShellScript, }); - const callArgs = addBuildPhaseSpy.mock.calls[0]; - const options = callArgs[4]; + const options = getOptions(); expect(options.inputPaths).toBeUndefined(); }); @@ -144,8 +148,7 @@ describe('Upload Debug Symbols to Sentry build phase', () => { ], }); - const callArgs = addBuildPhaseSpy.mock.calls[0]; - const options = callArgs[4]; + const options = getOptions(); expect(options.inputPaths).toBeDefined(); expect(options.inputPaths).toHaveLength(2); @@ -161,8 +164,7 @@ describe('Upload Debug Symbols to Sentry build phase', () => { ], }); - const callArgs = addBuildPhaseSpy.mock.calls[0]; - const options = callArgs[4]; + const options = getOptions(); // Verify paths are wrapped in quotes to prevent pbxproj corruption expect(options.inputPaths[0]).toMatch(/^".*"$/);