Skip to content

Commit 62320b4

Browse files
feat(xcode): Dynamically resolve SDK and CLI package paths (#3431)
1 parent 5b207d9 commit 62320b4

12 files changed

+103
-75
lines changed

CHANGELOG.md

+3
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,9 @@
3030
});
3131
```
3232

33+
- Update `sentry-xcode.sh` scripts with Node modules resolution ([#3450](https://github.com/getsentry/sentry-react-native/pull/3450))
34+
- RN SDK and Sentry CLI are dynamically resolved if override is not supplied
35+
3336
### Fixes
3437

3538
- Transform shipped JSX for both react-native and web ([#3428](https://github.com/getsentry/sentry-react-native/pull/3428))

package.json

+2-2
Original file line numberDiff line numberDiff line change
@@ -31,10 +31,10 @@
3131
"test:tools": "jest --config jest.config.tools.js",
3232
"fix": "yarn fix:eslint && yarn fix:prettier",
3333
"fix:eslint": "eslint --config .eslintrc.js --fix .",
34-
"fix:prettier": "prettier --write \"{src,test,scripts}/**/**.ts\"",
34+
"fix:prettier": "prettier --write \"{src,test,scripts,plugin/src}/**/**.ts\"",
3535
"lint": "yarn lint:eslint && yarn lint:prettier",
3636
"lint:eslint": "eslint --config .eslintrc.js .",
37-
"lint:prettier": "prettier --check \"{src,test,scripts}/**/**.ts\"",
37+
"lint:prettier": "prettier --check \"{src,test,scripts,plugin/src}/**/**.ts\"",
3838
"test:watch": "jest --watch",
3939
"run-ios": "cd sample-new-architecture && yarn react-native run-ios",
4040
"run-android": "cd sample-new-architecture && yarn react-native run-android",

plugin/src/index.ts

+1-3
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,5 @@
11
import { withSentry } from './withSentry';
22

3-
export {
4-
withSentry,
5-
};
3+
export { withSentry };
64

75
export default withSentry;

plugin/src/utils.ts

+20
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
import * as fs from 'fs';
2+
import * as path from 'path';
3+
4+
export function writeSentryPropertiesTo(filepath: string, sentryProperties: string): void {
5+
if (!fs.existsSync(filepath)) {
6+
throw new Error(`Directory '${filepath}' does not exist.`);
7+
}
8+
9+
fs.writeFileSync(path.resolve(filepath, 'sentry.properties'), sentryProperties);
10+
}
11+
12+
const sdkPackage: {
13+
name: string;
14+
version: string;
15+
// eslint-disable-next-line @typescript-eslint/no-var-requires
16+
} = require('../../package.json');
17+
18+
const SDK_PACKAGE_NAME = sdkPackage.name;
19+
20+
export { sdkPackage, SDK_PACKAGE_NAME };

plugin/src/withSentry.ts

+9-13
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,10 @@
11
import type { ConfigPlugin } from 'expo/config-plugins';
22
import { createRunOncePlugin, WarningAggregator } from 'expo/config-plugins';
33

4+
import { SDK_PACKAGE_NAME, sdkPackage } from './utils';
45
import { withSentryAndroid } from './withSentryAndroid';
56
import { withSentryIOS } from './withSentryIOS';
67

7-
// eslint-disable-next-line @typescript-eslint/no-var-requires
8-
const pkg = require('../../package.json');
9-
108
interface PluginProps {
119
organization?: string;
1210
project?: string;
@@ -22,15 +20,15 @@ const withSentryPlugin: ConfigPlugin<PluginProps | void> = (config, props) => {
2220
cfg = withSentryAndroid(cfg, sentryProperties);
2321
} catch (e) {
2422
WarningAggregator.addWarningAndroid(
25-
'sentry-expo',
23+
SDK_PACKAGE_NAME,
2624
`There was a problem configuring sentry-expo in your native Android project: ${e}`,
2725
);
2826
}
2927
try {
3028
cfg = withSentryIOS(cfg, sentryProperties);
3129
} catch (e) {
3230
WarningAggregator.addWarningIOS(
33-
'sentry-expo',
31+
SDK_PACKAGE_NAME,
3432
`There was a problem configuring sentry-expo in your native iOS project: ${e}`,
3533
);
3634
}
@@ -50,25 +48,23 @@ export function getSentryProperties(props: PluginProps | void): string | null {
5048
if (missingProperties.length) {
5149
const warningMessage = `Missing Sentry configuration properties: ${missingProperties.join(
5250
', ',
53-
)} in config plugin. Builds will fall back to environment variables. Refer to @sentry/react-native docs for how to configure this.`;
54-
WarningAggregator.addWarningAndroid('sentry-expo', warningMessage);
55-
WarningAggregator.addWarningIOS('sentry-expo', warningMessage);
51+
)} in config plugin. Builds will fall back to environment variables. See: https://docs.sentry.io/platforms/react-native/manual-setup/.`;
52+
WarningAggregator.addWarningAndroid(SDK_PACKAGE_NAME, warningMessage);
53+
WarningAggregator.addWarningIOS(SDK_PACKAGE_NAME, warningMessage);
5654
}
5755

5856
return `defaults.url=${url}
5957
${organization ? `defaults.org=${organization}` : missingOrgMessage}
6058
${project ? `defaults.project=${project}` : missingProjectMessage}
6159
${
6260
authToken
63-
? `# Configure this value through \`SENTRY_AUTH_TOKEN\` environment variable instead. See:https://docs.expo.dev/guides/using-sentry/#app-configuration\nauth.token=${authToken}`
61+
? `# Configure this value through \`SENTRY_AUTH_TOKEN\` environment variable instead. See: https://docs.sentry.io/platforms/react-native/manual-setup/\nauth.token=${authToken}`
6462
: missingAuthTokenMessage
6563
}
6664
`;
6765
}
6866

6967
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
70-
const withSentry = createRunOncePlugin(withSentryPlugin, pkg.name, pkg.version);
68+
const withSentry = createRunOncePlugin(withSentryPlugin, sdkPackage.name, sdkPackage.version);
7169

72-
export {
73-
withSentry,
74-
};
70+
export { withSentry };

plugin/src/withSentryAndroid.ts

+8-10
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import type { ConfigPlugin } from 'expo/config-plugins';
22
import { WarningAggregator, withAppBuildGradle, withDangerousMod } from 'expo/config-plugins';
33
import * as path from 'path';
44

5-
import { writeSentryPropertiesTo } from './withSentryIOS';
5+
import { SDK_PACKAGE_NAME, writeSentryPropertiesTo } from './utils';
66

77
export const withSentryAndroid: ConfigPlugin<string> = (config, sentryProperties: string) => {
88
const cfg = withAppBuildGradle(config, config => {
@@ -23,14 +23,14 @@ export const withSentryAndroid: ConfigPlugin<string> = (config, sentryProperties
2323
};
2424

2525
const resolveSentryReactNativePackageJsonPath =
26-
'["node", "--print", "require.resolve(\'@sentry/react-native/package.json\')"].execute().text.trim()';
26+
'["node", "--print", "require(\'path\').dirname(require.resolve(\'@sentry/react-native/package.json\'))"].execute().text.trim()';
2727

2828
/**
2929
* Writes to projectDirectory/android/app/build.gradle,
3030
* adding the relevant @sentry/react-native script.
3131
*/
3232
export function modifyAppBuildGradle(buildGradle: string): string {
33-
if (buildGradle.includes('/sentry.gradle"')) {
33+
if (buildGradle.includes('sentry.gradle')) {
3434
return buildGradle;
3535
}
3636

@@ -40,15 +40,13 @@ export function modifyAppBuildGradle(buildGradle: string): string {
4040

4141
if (!buildGradle.match(pattern)) {
4242
WarningAggregator.addWarningAndroid(
43-
'sentry-expo',
44-
'Could not find react.gradle script in android/app/build.gradle. Please open a bug report at https://github.com/expo/sentry-expo.',
43+
SDK_PACKAGE_NAME,
44+
'Could not find `^android {` in `android/app/build.gradle`. Please open a bug report at https://github.com/getsentry/sentry-react-native.',
4545
);
46+
return buildGradle;
4647
}
4748

48-
const sentryOptions = !buildGradle.includes('project.ext.sentryCli')
49-
? `project.ext.sentryCli=[collectModulesScript: new File(${resolveSentryReactNativePackageJsonPath}, "../dist/js/tools/collectModules.js")]`
50-
: '';
51-
const applyFrom = `apply from: new File(${resolveSentryReactNativePackageJsonPath}, "../sentry.gradle")`;
49+
const applyFrom = `apply from: new File(${resolveSentryReactNativePackageJsonPath}, "sentry.gradle")`;
5250

53-
return buildGradle.replace(pattern, match => `${sentryOptions}\n\n${applyFrom}\n\n${match}`);
51+
return buildGradle.replace(pattern, match => `${applyFrom}\n\n${match}`);
5452
}

plugin/src/withSentryIOS.ts

+21-27
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,16 @@
11
/* eslint-disable @typescript-eslint/no-unsafe-member-access */
22
import type { ConfigPlugin, XcodeProject } from 'expo/config-plugins';
33
import { WarningAggregator, withDangerousMod, withXcodeProject } from 'expo/config-plugins';
4-
import * as fs from 'fs';
54
import * as path from 'path';
65

7-
const SENTRY_CLI = "`node --print \"require.resolve('@sentry/cli/package.json').slice(0, -13) + '/bin/sentry-cli'\"`";
6+
import { SDK_PACKAGE_NAME, writeSentryPropertiesTo } from './utils';
7+
8+
type BuildPhase = { shellScript: string };
9+
10+
const SENTRY_REACT_NATIVE_XCODE_PATH =
11+
"`\"$NODE_BINARY\" --print \"require('path').dirname(require.resolve('@sentry/react-native/package.json')) + '/scripts/sentry-xcode.sh'\"`";
12+
const SENTRY_REACT_NATIVE_XCODE_DEBUG_FILES_PATH =
13+
"`${NODE_BINARY:-node} --print \"require('path').dirname(require.resolve('@sentry/react-native/package.json')) + '/scripts/sentry-xcode-debug-files.sh'\"`";
814

915
export const withSentryIOS: ConfigPlugin<string> = (config, sentryProperties: string) => {
1016
const cfg = withXcodeProject(config, config => {
@@ -17,11 +23,7 @@ export const withSentryIOS: ConfigPlugin<string> = (config, sentryProperties: st
1723
if (!sentryBuildPhase) {
1824
xcodeProject.addBuildPhase([], 'PBXShellScriptBuildPhase', 'Upload Debug Symbols to Sentry', null, {
1925
shellPath: '/bin/sh',
20-
shellScript: `
21-
export SENTRY_PROPERTIES=sentry.properties
22-
[[ $SENTRY_INCLUDE_NATIVE_SOURCES == "true" ]] && INCLUDE_SOURCES_FLAG="--include-sources" || INCLUDE_SOURCES_FLAG=""
23-
${SENTRY_CLI} debug-files upload --force-foreground "$INCLUDE_SOURCES_FLAG" "$DWARF_DSYM_FOLDER_PATH"
24-
`,
26+
shellScript: `/bin/sh ${SENTRY_REACT_NATIVE_XCODE_DEBUG_FILES_PATH}`,
2527
});
2628
}
2729

@@ -43,35 +45,27 @@ ${SENTRY_CLI} debug-files upload --force-foreground "$INCLUDE_SOURCES_FLAG" "$DW
4345
]);
4446
};
4547

46-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
47-
export function modifyExistingXcodeBuildScript(script: any): void {
48+
export function modifyExistingXcodeBuildScript(script: BuildPhase): void {
4849
if (
4950
!script.shellScript.match(/(packager|scripts)\/react-native-xcode\.sh\b/) ||
50-
script.shellScript.match(/bin\/sentry-cli.*react-native[\s-]xcode/)
51+
script.shellScript.includes('sentry-xcode.sh') ||
52+
script.shellScript.includes('@sentry')
5153
) {
5254
WarningAggregator.addWarningIOS(
53-
'sentry-expo',
55+
SDK_PACKAGE_NAME,
5456
"Unable to modify build script 'Bundle React Native code and images'. Please open a bug report at https://github.com/expo/sentry-expo.",
5557
);
5658
return;
5759
}
58-
let code = JSON.parse(script.shellScript);
59-
code = `${
60-
'export SENTRY_PROPERTIES=sentry.properties\n' +
61-
'export EXTRA_PACKAGER_ARGS="--sourcemap-output $DERIVED_FILE_DIR/main.jsbundle.map"\n'
62-
}${code.replace(
63-
/^.*?(packager|scripts)\/react-native-xcode\.sh\s*(\\'\\\\")?/m,
64-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
65-
(match: any) => `${SENTRY_CLI} react-native xcode --force-foreground ${match}`,
66-
)}\n\n\`node --print "require.resolve('@sentry/react-native/package.json').slice(0, -13) + '/scripts/collect-modules.sh'"\``;
6760

68-
script.shellScript = JSON.stringify(code);
61+
const code = JSON.parse(script.shellScript);
62+
script.shellScript = JSON.stringify(addSentryWithBundledScriptsToBundleShellScript(code));
6963
}
7064

71-
export function writeSentryPropertiesTo(filepath: string, sentryProperties: string): void {
72-
if (!fs.existsSync(filepath)) {
73-
throw new Error(`Directory '${filepath}' does not exist.`);
74-
}
75-
76-
fs.writeFileSync(path.resolve(filepath, 'sentry.properties'), sentryProperties);
65+
export function addSentryWithBundledScriptsToBundleShellScript(script: string): string {
66+
return script.replace(
67+
/^.*?(packager|scripts)\/react-native-xcode\.sh\s*(\\'\\\\")?/m,
68+
// eslint-disable-next-line no-useless-escape
69+
(match: string) => `/bin/sh ${SENTRY_REACT_NATIVE_XCODE_PATH} ${match}`,
70+
);
7771
}

sample-new-architecture/ios/.xcode.env

+1
Original file line numberDiff line numberDiff line change
@@ -17,3 +17,4 @@ export SENTRY_CLI_EXTRA_ARGS="--force-foreground"
1717
export SENTRY_CLI_DEBUG_FILES_UPLOAD_EXTRA_ARGS=""
1818
export SENTRY_CLI_RN_XCODE_EXTRA_ARGS=""
1919
export MODULES_PATHS="$PWD/../node_modules,$PWD/../../.."
20+
export SENTRY_COLLECT_MODULES="../../scripts/collect-modules.sh"

scripts/sentry-xcode-debug-files.sh

+5-1
Original file line numberDiff line numberDiff line change
@@ -5,14 +5,18 @@
55
# print commands before executing them and stop on first error
66
set -x -e
77

8+
LOCAL_NODE_BINARY=${NODE_BINARY:-node}
9+
810
# load envs if loader file exists (since rn 0.68)
911
WITH_ENVIRONMENT="../node_modules/react-native/scripts/xcode/with-environment.sh"
1012
if [ -f "$WITH_ENVIRONMENT" ]; then
1113
. "$WITH_ENVIRONMENT"
1214
fi
1315

1416
[ -z "$SENTRY_PROPERTIES" ] && export SENTRY_PROPERTIES=sentry.properties
15-
[ -z "$SENTRY_CLI_EXECUTABLE" ] && SENTRY_CLI_EXECUTABLE="../node_modules/@sentry/cli/bin/sentry-cli"
17+
18+
[ -z "$SENTRY_CLI_EXECUTABLE" ] && SENTRY_CLI_PACKAGE_PATH=$("$LOCAL_NODE_BINARY" --print "require('path').dirname(require.resolve('@sentry/cli/package.json'))")
19+
[ -z "$SENTRY_CLI_EXECUTABLE" ] && SENTRY_CLI_EXECUTABLE="${SENTRY_CLI_PACKAGE_PATH}/bin/sentry-cli"
1620

1721
[[ $SENTRY_INCLUDE_NATIVE_SOURCES == "true" ]] && INCLUDE_SOURCES_FLAG="--include-sources" || INCLUDE_SOURCES_FLAG=""
1822

scripts/sentry-xcode.sh

+7-2
Original file line numberDiff line numberDiff line change
@@ -7,9 +7,13 @@ set -x -e
77

88
# WITH_ENVIRONMENT is executed by React Native
99

10+
LOCAL_NODE_BINARY=${NODE_BINARY:-node}
11+
1012
[ -z "$SENTRY_PROPERTIES" ] && export SENTRY_PROPERTIES=sentry.properties
1113
[ -z "$SOURCEMAP_FILE" ] && export SOURCEMAP_FILE="$DERIVED_FILE_DIR/main.jsbundle.map"
12-
[ -z "$SENTRY_CLI_EXECUTABLE" ] && SENTRY_CLI_EXECUTABLE="../node_modules/@sentry/cli/bin/sentry-cli"
14+
15+
[ -z "$SENTRY_CLI_EXECUTABLE" ] && SENTRY_CLI_PACKAGE_PATH=$("$LOCAL_NODE_BINARY" --print "require('path').dirname(require.resolve('@sentry/cli/package.json'))")
16+
[ -z "$SENTRY_CLI_EXECUTABLE" ] && SENTRY_CLI_EXECUTABLE="${SENTRY_CLI_PACKAGE_PATH}/bin/sentry-cli"
1317

1418
REACT_NATIVE_XCODE=$1
1519

@@ -25,7 +29,8 @@ else
2529
/bin/sh -c "$REACT_NATIVE_XCODE"
2630
fi
2731

28-
[ -z "$SENTRY_COLLECT_MODULES" ] && SENTRY_COLLECT_MODULES="../../scripts/collect-modules.sh"
32+
[ -z "$SENTRY_COLLECT_MODULES" ] && SENTRY_RN_PACKAGE_PATH=$("$LOCAL_NODE_BINARY" --print "require('path').dirname(require.resolve('@sentry/react-native/package.json'))")
33+
[ -z "$SENTRY_COLLECT_MODULES" ] && SENTRY_COLLECT_MODULES="${SENTRY_RN_PACKAGE_PATH}/scripts/collect-modules.sh"
2934

3035
if [ -f "$SENTRY_COLLECT_MODULES" ]; then
3136
/bin/sh "$SENTRY_COLLECT_MODULES"

test/expo-plugin/modifyAppBuildGradle.test.ts

+6-6
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ jest.mock('@expo/config-plugins', () => {
1111
});
1212

1313
const buildGradleWithSentry = `
14-
apply from: new File(["node", "--print", "require.resolve('@sentry/react-native/package.json')"].execute().text.trim(), "../sentry.gradle")
14+
apply from: new File(["node", "--print", "require('path').dirname(require.resolve('@sentry/react-native/package.json'))"].execute().text.trim(), "sentry.gradle")
1515
1616
android {
1717
}
@@ -23,7 +23,7 @@ android {
2323
`;
2424

2525
const monoRepoBuildGradleWithSentry = `
26-
apply from: new File(["node", "--print", "require.resolve('@sentry/react-native/package.json')"].execute().text.trim(), "../sentry.gradle")
26+
apply from: new File(["node", "--print", "require('path').dirname(require.resolve('@sentry/react-native/package.json'))"].execute().text.trim(), "sentry.gradle")
2727
2828
android {
2929
}
@@ -39,19 +39,19 @@ const buildGradleWithOutReactGradleScript = `
3939

4040
describe('Configures Android native project correctly', () => {
4141
it("Non monorepo: Doesn't modify app/build.gradle if Sentry's already configured", () => {
42-
expect(modifyAppBuildGradle(buildGradleWithSentry)).toMatch(buildGradleWithSentry);
42+
expect(modifyAppBuildGradle(buildGradleWithSentry)).toStrictEqual(buildGradleWithSentry);
4343
});
4444

4545
it('Non monorepo: Adds sentry.gradle script if not present already', () => {
46-
expect(modifyAppBuildGradle(buildGradleWithOutSentry)).toMatch(buildGradleWithSentry);
46+
expect(modifyAppBuildGradle(buildGradleWithOutSentry)).toStrictEqual(buildGradleWithSentry);
4747
});
4848

4949
it("Monorepo: Doesn't modify app/build.gradle if Sentry's already configured", () => {
50-
expect(modifyAppBuildGradle(monoRepoBuildGradleWithSentry)).toMatch(monoRepoBuildGradleWithSentry);
50+
expect(modifyAppBuildGradle(monoRepoBuildGradleWithSentry)).toStrictEqual(monoRepoBuildGradleWithSentry);
5151
});
5252

5353
it('Monorepo: Adds sentry.gradle script if not present already', () => {
54-
expect(modifyAppBuildGradle(monoRepoBuildGradleWithOutSentry)).toMatch(monoRepoBuildGradleWithSentry);
54+
expect(modifyAppBuildGradle(monoRepoBuildGradleWithOutSentry)).toStrictEqual(monoRepoBuildGradleWithSentry);
5555
});
5656

5757
it('Warns to file a bug report if no react.gradle is found', () => {

0 commit comments

Comments
 (0)