From effe4c27e6b521e619eb50724e0f71143e850451 Mon Sep 17 00:00:00 2001 From: Marco Saia Date: Tue, 11 Jun 2024 18:06:03 +0200 Subject: [PATCH 1/2] Fix datadog-ci path resolving on iOS scripts --- .../withIosSourcemaps.test.ts.snap | 49 +++++++++++++++++++ ...getErrorTrackingPluginsFromOptions.test.ts | 4 +- .../withAndroidConfiguration.test.ts | 8 +-- .../__tests__/withAndroidSourcemaps.test.ts | 2 +- .../__tests__/withIosSourcemaps.test.ts | 18 ++----- src/plugin/common/config.ts | 2 + src/plugin/common/exports.ts | 7 +++ .../getErrorTrackingPluginsFromOptions.ts | 4 +- src/plugin/index.ts | 2 +- .../withAndroidConfiguration.ts | 5 +- .../withAndroidSourcemaps.ts | 4 +- src/plugin/withIosDsyms/withIosDsyms.ts | 29 ++++++++--- .../withIosSourcemaps/withIosSourcemaps.ts | 27 +++++----- 13 files changed, 116 insertions(+), 45 deletions(-) create mode 100644 src/plugin/__tests__/__snapshots__/withIosSourcemaps.test.ts.snap create mode 100644 src/plugin/common/config.ts create mode 100644 src/plugin/common/exports.ts diff --git a/src/plugin/__tests__/__snapshots__/withIosSourcemaps.test.ts.snap b/src/plugin/__tests__/__snapshots__/withIosSourcemaps.test.ts.snap new file mode 100644 index 0000000..10660e4 --- /dev/null +++ b/src/plugin/__tests__/__snapshots__/withIosSourcemaps.test.ts.snap @@ -0,0 +1,49 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`withIosSourcemaps on pristine project adds script to upload sourcemaps to Datadog 1`] = ` +""if [[ -f \\"$PODS_ROOT/../.xcode.env\\" ]]; then\\n source \\"$PODS_ROOT/../.xcode.env\\"\\nfi\\nif [[ -f \\"$PODS_ROOT/../.xcode.env.local\\" ]]; then\\n source \\"$PODS_ROOT/../.xcode.env.local\\"\\nfi\\n\\n# The project root by default is one level up from the ios directory\\nexport PROJECT_ROOT=\\"$PROJECT_DIR\\"/..\\n\\nif [[ \\"$CONFIGURATION\\" = *Debug* ]]; then\\n export SKIP_BUNDLING=1\\nfi\\nexport SOURCEMAP_FILE=$DERIVED_FILE_DIR/main.jsbundle.map\\n + +if [[ -z "$DATADOG_CI_EXEC" ]]; then + export DATADOG_CI_EXEC="$("$NODE_BINARY" --print "require('path').resolve(require('path').dirname(require.resolve('@datadog/datadog-ci/package.json')), '../../.bin/datadog-ci')")" +fi + + +$DATADOG_CI_EXEC react-native xcode \`"$NODE_BINARY" --print "require('path').dirname(require.resolve('react-native/package.json')) + '/scripts/react-native-xcode.sh'"\` +\\n\\n"" +`; + +exports[`withIosSourcemaps on pristine project adds script to upload sourcemaps to Datadog with custom service name 1`] = ` +""if [[ -f \\"$PODS_ROOT/../.xcode.env\\" ]]; then\\n source \\"$PODS_ROOT/../.xcode.env\\"\\nfi\\nif [[ -f \\"$PODS_ROOT/../.xcode.env.local\\" ]]; then\\n source \\"$PODS_ROOT/../.xcode.env.local\\"\\nfi\\n\\n# The project root by default is one level up from the ios directory\\nexport PROJECT_ROOT=\\"$PROJECT_DIR\\"/..\\n\\nif [[ \\"$CONFIGURATION\\" = *Debug* ]]; then\\n export SKIP_BUNDLING=1\\nfi\\nexport SOURCEMAP_FILE=$DERIVED_FILE_DIR/main.jsbundle.map\\n + +if [[ -z "$DATADOG_CI_EXEC" ]]; then + export DATADOG_CI_EXEC="$("$NODE_BINARY" --print "require('path').resolve(require('path').dirname(require.resolve('@datadog/datadog-ci/package.json')), '../../.bin/datadog-ci')")" +fi + + +$DATADOG_CI_EXEC react-native xcode \`"$NODE_BINARY" --print "require('path').dirname(require.resolve('react-native/package.json')) + '/scripts/react-native-xcode.sh'"\` + --service com.company.app\\n\\n"" +`; + +exports[`withIosSourcemaps on projects implementing Sentry adds script to upload sourcemaps to Datadog 1`] = ` +""export SENTRY_PROPERTIES=sentry.properties\\nexport EXTRA_PACKAGER_ARGS=\\"--sourcemap-output $DERIVED_FILE_DIR/main.jsbundle.map\\"\\nif [[ -f \\"$PODS_ROOT/../.xcode.env\\" ]]; then\\n source \\"$PODS_ROOT/../.xcode.env\\"\\nfi\\nif [[ -f \\"$PODS_ROOT/../.xcode.env.local\\" ]]; then\\n source \\"$PODS_ROOT/../.xcode.env.local\\"\\nfi\\n\\n# The project root by default is one level up from the ios directory\\nexport PROJECT_ROOT=\\"$PROJECT_DIR\\"/..\\n\\nif [[ \\"$CONFIGURATION\\" = *Debug* ]]; then\\n export SKIP_BUNDLING=1\\nfi\\n\`node --print \\"require.resolve('@sentry/cli/package.json').slice(0, -13) + '/bin/sentry-cli'\\"\` react-native xcode --force-foreground \`\\"$NODE_BINARY\\" --print \\"require('path').dirname(require.resolve('react-native/package.json')) + '/scripts/react-native-xcode.sh'\\"\`\\n\\n\\n + +if [[ -z "$DATADOG_CI_EXEC" ]]; then + export DATADOG_CI_EXEC="$("$NODE_BINARY" --print "require('path').resolve(require('path').dirname(require.resolve('@datadog/datadog-ci/package.json')), '../../.bin/datadog-ci')")" +fi + + +$DATADOG_CI_EXEC react-native xcode \`"$NODE_BINARY" --print "require('path').dirname(require.resolve('react-native/package.json')) + '/scripts/react-native-xcode.sh'"\` +"" +`; + +exports[`withIosSourcemaps on projects implementing Sentry adds script to upload sourcemaps to Datadog with custom service name 1`] = ` +""export SENTRY_PROPERTIES=sentry.properties\\nexport EXTRA_PACKAGER_ARGS=\\"--sourcemap-output $DERIVED_FILE_DIR/main.jsbundle.map\\"\\nif [[ -f \\"$PODS_ROOT/../.xcode.env\\" ]]; then\\n source \\"$PODS_ROOT/../.xcode.env\\"\\nfi\\nif [[ -f \\"$PODS_ROOT/../.xcode.env.local\\" ]]; then\\n source \\"$PODS_ROOT/../.xcode.env.local\\"\\nfi\\n\\n# The project root by default is one level up from the ios directory\\nexport PROJECT_ROOT=\\"$PROJECT_DIR\\"/..\\n\\nif [[ \\"$CONFIGURATION\\" = *Debug* ]]; then\\n export SKIP_BUNDLING=1\\nfi\\n\`node --print \\"require.resolve('@sentry/cli/package.json').slice(0, -13) + '/bin/sentry-cli'\\"\` react-native xcode --force-foreground \`\\"$NODE_BINARY\\" --print \\"require('path').dirname(require.resolve('react-native/package.json')) + '/scripts/react-native-xcode.sh'\\"\`\\n\\n\\n + +if [[ -z "$DATADOG_CI_EXEC" ]]; then + export DATADOG_CI_EXEC="$("$NODE_BINARY" --print "require('path').resolve(require('path').dirname(require.resolve('@datadog/datadog-ci/package.json')), '../../.bin/datadog-ci')")" +fi + + +$DATADOG_CI_EXEC react-native xcode \`"$NODE_BINARY" --print "require('path').dirname(require.resolve('react-native/package.json')) + '/scripts/react-native-xcode.sh'"\` + --service com.company.app"" +`; diff --git a/src/plugin/__tests__/getErrorTrackingPluginsFromOptions.test.ts b/src/plugin/__tests__/getErrorTrackingPluginsFromOptions.test.ts index 3362aaa..c1c9a38 100644 --- a/src/plugin/__tests__/getErrorTrackingPluginsFromOptions.test.ts +++ b/src/plugin/__tests__/getErrorTrackingPluginsFromOptions.test.ts @@ -22,7 +22,7 @@ const ALL_PLUGINS = [ describe("getErrorTrackingPluginsFromOptions", () => { it("returns all plugins if no option is provided", () => { expect(getErrorTrackingPluginsFromOptions()).toHaveLength( - ALL_PLUGINS.length, + ALL_PLUGINS.length ); }); @@ -31,7 +31,7 @@ describe("getErrorTrackingPluginsFromOptions", () => { getErrorTrackingPluginsFromOptions({ iosDsyms: true, androidProguardMappingFiles: false, - }).length, + }).length ).toEqual(ALL_PLUGINS.filter((_, index) => index !== 3).length); }); }); diff --git a/src/plugin/__tests__/withAndroidConfiguration.test.ts b/src/plugin/__tests__/withAndroidConfiguration.test.ts index c4cc58d..7f2a77e 100644 --- a/src/plugin/__tests__/withAndroidConfiguration.test.ts +++ b/src/plugin/__tests__/withAndroidConfiguration.test.ts @@ -6,8 +6,8 @@ import { withAppBuildGradle } from "@expo/config-plugins"; -import buildGradle from "./__fixtures__/build.gradle"; import withAndroidConfiguration from "../withAndroidConfiguration/withAndroidConfiguration"; +import buildGradle from "./__fixtures__/build.gradle"; jest.mock("@expo/config-plugins", () => { return { @@ -46,10 +46,10 @@ describe("withAndroidConfiguration", () => { it("adds a datadog config block", async () => { mockAppBuildGradle(buildGradle); const result = (await withAndroidConfiguration({})( - createFakeConfig(), + createFakeConfig() )) as any; expect(result.modResults.contents).toMatch( - 'id("com.datadoghq.dd-sdk-android-gradle-plugin") version "1.14.0"', + 'id("com.datadoghq.dd-sdk-android-gradle-plugin") version "1.14.0"' ); expect(result.modResults.contents).toMatchSnapshot(); }); @@ -70,7 +70,7 @@ describe("withAndroidConfiguration", () => { datadogGradlePluginVersion: "1.9.0", })(createFakeConfig())) as any; expect(result.modResults.contents).toMatch( - 'id("com.datadoghq.dd-sdk-android-gradle-plugin") version "1.9.0"', + 'id("com.datadoghq.dd-sdk-android-gradle-plugin") version "1.9.0"' ); }); }); diff --git a/src/plugin/__tests__/withAndroidSourcemaps.test.ts b/src/plugin/__tests__/withAndroidSourcemaps.test.ts index 0248fc9..e26242d 100644 --- a/src/plugin/__tests__/withAndroidSourcemaps.test.ts +++ b/src/plugin/__tests__/withAndroidSourcemaps.test.ts @@ -6,8 +6,8 @@ import { withAppBuildGradle } from "@expo/config-plugins"; -import buildGradle from "./__fixtures__/build.gradle"; import withAndroidSourcemaps from "../withAndroidSourcemaps/withAndroidSourcemaps"; +import buildGradle from "./__fixtures__/build.gradle"; jest.mock("@expo/config-plugins", () => { return { diff --git a/src/plugin/__tests__/withIosSourcemaps.test.ts b/src/plugin/__tests__/withIosSourcemaps.test.ts index 720d198..51d6524 100644 --- a/src/plugin/__tests__/withIosSourcemaps.test.ts +++ b/src/plugin/__tests__/withIosSourcemaps.test.ts @@ -6,9 +6,9 @@ import { withXcodeProject } from "@expo/config-plugins"; +import withIosSourcemaps from "../withIosSourcemaps/withIosSourcemaps"; import pristineProject from "./__fixtures__/pristineProjectPbxproj.json"; import sentryProject from "./__fixtures__/sentryProjectPbxproj.json"; -import withIosSourcemaps from "../withIosSourcemaps/withIosSourcemaps"; jest.mock("@expo/config-plugins", () => { return { @@ -49,36 +49,28 @@ describe("withIosSourcemaps", () => { it("adds script to upload sourcemaps to Datadog", async () => { mockXcodeProject(pristineProject); const result = (await withIosSourcemaps({})(createFakeConfig())) as any; - expect(result.xcodeProject.shellScript).toMatchInlineSnapshot( - `""if [[ -f \\"$PODS_ROOT/../.xcode.env\\" ]]; then\\n source \\"$PODS_ROOT/../.xcode.env\\"\\nfi\\nif [[ -f \\"$PODS_ROOT/../.xcode.env.local\\" ]]; then\\n source \\"$PODS_ROOT/../.xcode.env.local\\"\\nfi\\n\\n# The project root by default is one level up from the ios directory\\nexport PROJECT_ROOT=\\"$PROJECT_DIR\\"/..\\n\\nif [[ \\"$CONFIGURATION\\" = *Debug* ]]; then\\n export SKIP_BUNDLING=1\\nfi\\nexport SOURCEMAP_FILE=$DERIVED_FILE_DIR/main.jsbundle.map\\n ../node_modules/.bin/datadog-ci react-native xcode \`\\"$NODE_BINARY\\" --print \\"require('path').dirname(require.resolve('react-native/package.json')) + '/scripts/react-native-xcode.sh'\\"\`\\n\\n""`, - ); + expect(result.xcodeProject.shellScript).toMatchSnapshot(); }); it("adds script to upload sourcemaps to Datadog with custom service name", async () => { mockXcodeProject(pristineProject); const result = (await withIosSourcemaps({ serviceName: "com.company.app", })(createFakeConfig())) as any; - expect(result.xcodeProject.shellScript).toMatchInlineSnapshot( - `""if [[ -f \\"$PODS_ROOT/../.xcode.env\\" ]]; then\\n source \\"$PODS_ROOT/../.xcode.env\\"\\nfi\\nif [[ -f \\"$PODS_ROOT/../.xcode.env.local\\" ]]; then\\n source \\"$PODS_ROOT/../.xcode.env.local\\"\\nfi\\n\\n# The project root by default is one level up from the ios directory\\nexport PROJECT_ROOT=\\"$PROJECT_DIR\\"/..\\n\\nif [[ \\"$CONFIGURATION\\" = *Debug* ]]; then\\n export SKIP_BUNDLING=1\\nfi\\nexport SOURCEMAP_FILE=$DERIVED_FILE_DIR/main.jsbundle.map\\n ../node_modules/.bin/datadog-ci react-native xcode \`\\"$NODE_BINARY\\" --print \\"require('path').dirname(require.resolve('react-native/package.json')) + '/scripts/react-native-xcode.sh'\\"\` --service com.company.app\\n\\n""`, - ); + expect(result.xcodeProject.shellScript).toMatchSnapshot(); }); }); describe("on projects implementing Sentry", () => { it("adds script to upload sourcemaps to Datadog", async () => { mockXcodeProject(sentryProject); const result = (await withIosSourcemaps({})(createFakeConfig())) as any; - expect(result.xcodeProject.shellScript).toMatchInlineSnapshot( - `""export SENTRY_PROPERTIES=sentry.properties\\nexport EXTRA_PACKAGER_ARGS=\\"--sourcemap-output $DERIVED_FILE_DIR/main.jsbundle.map\\"\\nif [[ -f \\"$PODS_ROOT/../.xcode.env\\" ]]; then\\n source \\"$PODS_ROOT/../.xcode.env\\"\\nfi\\nif [[ -f \\"$PODS_ROOT/../.xcode.env.local\\" ]]; then\\n source \\"$PODS_ROOT/../.xcode.env.local\\"\\nfi\\n\\n# The project root by default is one level up from the ios directory\\nexport PROJECT_ROOT=\\"$PROJECT_DIR\\"/..\\n\\nif [[ \\"$CONFIGURATION\\" = *Debug* ]]; then\\n export SKIP_BUNDLING=1\\nfi\\n\`node --print \\"require.resolve('@sentry/cli/package.json').slice(0, -13) + '/bin/sentry-cli'\\"\` react-native xcode --force-foreground \`\\"$NODE_BINARY\\" --print \\"require('path').dirname(require.resolve('react-native/package.json')) + '/scripts/react-native-xcode.sh'\\"\`\\n\\n\\n ../node_modules/.bin/datadog-ci react-native xcode \`\\"$NODE_BINARY\\" --print \\"require('path').dirname(require.resolve('react-native/package.json')) + '/scripts/react-native-xcode.sh'\\"\`""`, - ); + expect(result.xcodeProject.shellScript).toMatchSnapshot(); }); it("adds script to upload sourcemaps to Datadog with custom service name", async () => { mockXcodeProject(sentryProject); const result = (await withIosSourcemaps({ serviceName: "com.company.app", })(createFakeConfig())) as any; - expect(result.xcodeProject.shellScript).toMatchInlineSnapshot( - `""export SENTRY_PROPERTIES=sentry.properties\\nexport EXTRA_PACKAGER_ARGS=\\"--sourcemap-output $DERIVED_FILE_DIR/main.jsbundle.map\\"\\nif [[ -f \\"$PODS_ROOT/../.xcode.env\\" ]]; then\\n source \\"$PODS_ROOT/../.xcode.env\\"\\nfi\\nif [[ -f \\"$PODS_ROOT/../.xcode.env.local\\" ]]; then\\n source \\"$PODS_ROOT/../.xcode.env.local\\"\\nfi\\n\\n# The project root by default is one level up from the ios directory\\nexport PROJECT_ROOT=\\"$PROJECT_DIR\\"/..\\n\\nif [[ \\"$CONFIGURATION\\" = *Debug* ]]; then\\n export SKIP_BUNDLING=1\\nfi\\n\`node --print \\"require.resolve('@sentry/cli/package.json').slice(0, -13) + '/bin/sentry-cli'\\"\` react-native xcode --force-foreground \`\\"$NODE_BINARY\\" --print \\"require('path').dirname(require.resolve('react-native/package.json')) + '/scripts/react-native-xcode.sh'\\"\`\\n\\n\\n ../node_modules/.bin/datadog-ci react-native xcode \`\\"$NODE_BINARY\\" --print \\"require('path').dirname(require.resolve('react-native/package.json')) + '/scripts/react-native-xcode.sh'\\"\` --service com.company.app""`, - ); + expect(result.xcodeProject.shellScript).toMatchSnapshot(); }); }); }); diff --git a/src/plugin/common/config.ts b/src/plugin/common/config.ts new file mode 100644 index 0000000..52ba6d8 --- /dev/null +++ b/src/plugin/common/config.ts @@ -0,0 +1,2 @@ +export const DEFAULT_DATADOG_GRADLE_PLUGIN_VERSION = "1.14.0"; +export const IOS_DSYMS_BUILD_PHASE_NAME = "Upload dSYMs to Datadog"; diff --git a/src/plugin/common/exports.ts b/src/plugin/common/exports.ts new file mode 100644 index 0000000..13a2b83 --- /dev/null +++ b/src/plugin/common/exports.ts @@ -0,0 +1,7 @@ +export const IOS_SOURCEMAP_FILE_EXPORT = + "export SOURCEMAP_FILE=$DERIVED_FILE_DIR/main.jsbundle.map"; +export const IOS_DATADOG_CI_EXPORT = ` +if [[ -z "$DATADOG_CI_EXEC" ]]; then + export DATADOG_CI_EXEC="$("$NODE_BINARY" --print "require('path').resolve(require('path').dirname(require.resolve('@datadog/datadog-ci/package.json')), '../../.bin/datadog-ci')")" +fi +`; diff --git a/src/plugin/getErrorTrackingPluginsFromOptions.ts b/src/plugin/getErrorTrackingPluginsFromOptions.ts index 5571aec..da9149e 100644 --- a/src/plugin/getErrorTrackingPluginsFromOptions.ts +++ b/src/plugin/getErrorTrackingPluginsFromOptions.ts @@ -57,7 +57,7 @@ export type ErrorTrackingOptions = FileUploadOptions & * to `false`. */ export const getErrorTrackingPluginsFromOptions = ( - options?: ErrorTrackingOptions, + options?: ErrorTrackingOptions ): (ConfigPlugin | StaticPlugin)[] => { const ERROR_TRACKING_CONFIG_PLUGINS_MAP: Record< keyof FileUploadOptions, @@ -73,7 +73,7 @@ export const getErrorTrackingPluginsFromOptions = ( const configPluginsKeys = ( Object.keys( - ERROR_TRACKING_CONFIG_PLUGINS_MAP, + ERROR_TRACKING_CONFIG_PLUGINS_MAP ) as (keyof FileUploadOptions)[] ).filter((option) => !options || options[option] !== false); diff --git a/src/plugin/index.ts b/src/plugin/index.ts index 586f191..b4c28ee 100644 --- a/src/plugin/index.ts +++ b/src/plugin/index.ts @@ -21,7 +21,7 @@ type PluginConfiguration = const withDatadog: ConfigPlugin = (config, options) => { return withPlugins( config, - getErrorTrackingPluginsFromOptions(options && options.errorTracking), + getErrorTrackingPluginsFromOptions(options && options.errorTracking) ); }; diff --git a/src/plugin/withAndroidConfiguration/withAndroidConfiguration.ts b/src/plugin/withAndroidConfiguration/withAndroidConfiguration.ts index 373a23a..f342558 100644 --- a/src/plugin/withAndroidConfiguration/withAndroidConfiguration.ts +++ b/src/plugin/withAndroidConfiguration/withAndroidConfiguration.ts @@ -7,16 +7,15 @@ import type { ConfigPlugin } from "@expo/config-plugins"; import { withAppBuildGradle } from "@expo/config-plugins"; +import { DEFAULT_DATADOG_GRADLE_PLUGIN_VERSION } from "../common/config"; import { AndroidProguardMappingFilesOptions, SourceMapUploadOptions, } from "../getErrorTrackingPluginsFromOptions"; -const DEFAULT_DATADOG_GRADLE_PLUGIN_VERSION = "1.14.0"; - const withAndroidConfiguration = ( - options: SourceMapUploadOptions & AndroidProguardMappingFilesOptions, + options: SourceMapUploadOptions & AndroidProguardMappingFilesOptions ): ConfigPlugin => (config) => { return withAppBuildGradle(config, async (config) => { diff --git a/src/plugin/withAndroidSourcemaps/withAndroidSourcemaps.ts b/src/plugin/withAndroidSourcemaps/withAndroidSourcemaps.ts index b724f62..8db0201 100644 --- a/src/plugin/withAndroidSourcemaps/withAndroidSourcemaps.ts +++ b/src/plugin/withAndroidSourcemaps/withAndroidSourcemaps.ts @@ -17,8 +17,8 @@ const withAndroidSourcemaps: ConfigPlugin = (config) => { appBuildGradle.contents = appBuildGradle.contents.replace( /apply plugin: "com\.facebook\.react"/, `apply plugin: "com.facebook.react"\napply from: "${require("path").dirname( - require.resolve("@datadog/mobile-react-native/package.json"), - )}/datadog-sourcemaps.gradle"`, + require.resolve("@datadog/mobile-react-native/package.json") + )}/datadog-sourcemaps.gradle"` ); return config; diff --git a/src/plugin/withIosDsyms/withIosDsyms.ts b/src/plugin/withIosDsyms/withIosDsyms.ts index 82b1e41..122bda5 100644 --- a/src/plugin/withIosDsyms/withIosDsyms.ts +++ b/src/plugin/withIosDsyms/withIosDsyms.ts @@ -7,14 +7,15 @@ import type { ConfigPlugin } from "@expo/config-plugins"; import { withXcodeProject } from "@expo/config-plugins"; -const BUILD_PHASE_NAME = "Upload dSYMs to Datadog"; +import { IOS_DSYMS_BUILD_PHASE_NAME } from "../common/config"; +import { IOS_DATADOG_CI_EXPORT } from "../common/exports"; const withIosDsyms: ConfigPlugin = (config) => { return withXcodeProject(config, async (config) => { const xcodeProject = config.modResults; const buildPhase = xcodeProject.pbxItemByComment( - BUILD_PHASE_NAME, - "PBXShellScriptBuildPhase", + IOS_DSYMS_BUILD_PHASE_NAME, + "PBXShellScriptBuildPhase" ); if (buildPhase) { return config; @@ -23,12 +24,28 @@ const withIosDsyms: ConfigPlugin = (config) => { xcodeProject.addBuildPhase( [], "PBXShellScriptBuildPhase", - BUILD_PHASE_NAME, + IOS_DSYMS_BUILD_PHASE_NAME, null /* target */, { - shellScript: `set -e\\n ../node_modules/.bin/datadog-ci dsyms upload $DWARF_DSYM_FOLDER_PATH`, + shellScript: `set -e + + if [[ -f "$PODS_ROOT/../.xcode.env" ]]; then + source "$PODS_ROOT/../.xcode.env" + fi + if [[ -f "$PODS_ROOT/../.xcode.env.local" ]]; then + source "$PODS_ROOT/../.xcode.env.local" + fi + + if [[ -z "$NODE_BINARY" ]]; then + echo "ERROR: NODE_BINARY env variable is not set" + fi + + ${IOS_DATADOG_CI_EXPORT} + + $DATADOG_CI_EXEC dsyms upload $DWARF_DSYM_FOLDER_PATH + `, shellPath: "/bin/sh", - }, + } ); return config; diff --git a/src/plugin/withIosSourcemaps/withIosSourcemaps.ts b/src/plugin/withIosSourcemaps/withIosSourcemaps.ts index aefb024..632f420 100644 --- a/src/plugin/withIosSourcemaps/withIosSourcemaps.ts +++ b/src/plugin/withIosSourcemaps/withIosSourcemaps.ts @@ -7,16 +7,21 @@ import type { ConfigPlugin } from "@expo/config-plugins"; import { withXcodeProject } from "@expo/config-plugins"; +import { + IOS_DATADOG_CI_EXPORT, + IOS_SOURCEMAP_FILE_EXPORT, +} from "../common/exports"; import { SourceMapUploadOptions } from "../getErrorTrackingPluginsFromOptions"; -const SOURCEMAP_FILE_COMMAND = - "export SOURCEMAP_FILE=$DERIVED_FILE_DIR/main.jsbundle.map"; +const DATADOG_XCODE_COMMAND = ` +${IOS_DATADOG_CI_EXPORT} + +$DATADOG_CI_EXEC react-native xcode \`"$NODE_BINARY" --print "require('path').dirname(require.resolve('react-native/package.json')) + '/scripts/react-native-xcode.sh'"\` +`; const getDatadogXCodeCommand = ( - serviceName: SourceMapUploadOptions["serviceName"], + serviceName: SourceMapUploadOptions["serviceName"] ) => - `../node_modules/.bin/datadog-ci react-native xcode \`\\"$NODE_BINARY\\" --print \\"require('path').dirname(require.resolve('react-native/package.json')) + '/scripts/react-native-xcode.sh'\\"\`${ - serviceName ? ` --service ${serviceName}` : "" - }`; + `${DATADOG_XCODE_COMMAND}${serviceName ? ` --service ${serviceName}` : ""}`; const withIosSourcemaps = (options: SourceMapUploadOptions): ConfigPlugin => @@ -25,7 +30,7 @@ const withIosSourcemaps = const xcodeProject = config.modResults; const bundlePhase = xcodeProject.pbxItemByComment( "Bundle React Native code and images", - "PBXShellScriptBuildPhase", + "PBXShellScriptBuildPhase" ); if (bundlePhase.shellScript.match("datadog-ci react-native xcode")) { return config; @@ -42,17 +47,17 @@ const withIosSourcemaps = */ bundlePhase.shellScript = `${bundlePhase.shellScript.replace( /.$/, - "", + "" )}\\n ${getDatadogXCodeCommand(options.serviceName)}"`; return config; } const [beforeScript, afterScript] = bundlePhase.shellScript.split( - "`\\\"$NODE_BINARY\\\" --print \\\"require('path').dirname(require.resolve('react-native/package.json')) + '/scripts/react-native-xcode.sh'\\\"`", + "`\\\"$NODE_BINARY\\\" --print \\\"require('path').dirname(require.resolve('react-native/package.json')) + '/scripts/react-native-xcode.sh'\\\"`" ); - const datadogScript = `${SOURCEMAP_FILE_COMMAND}\\n ${getDatadogXCodeCommand( - options.serviceName, + const datadogScript = `${IOS_SOURCEMAP_FILE_EXPORT}\\n ${getDatadogXCodeCommand( + options.serviceName )}`; bundlePhase.shellScript = `${beforeScript}${datadogScript}${afterScript}`; From f19acf0bc32b0abca710df8a08fbac23f0a857d6 Mon Sep 17 00:00:00 2001 From: Marco Saia Date: Wed, 12 Jun 2024 15:21:55 +0200 Subject: [PATCH 2/2] Improved datadog-ci command safety --- .../withIosSourcemaps.test.ts.snap | 48 ++----------------- src/plugin/common/exports.ts | 11 ++++- src/plugin/common/utils.ts | 6 +++ src/plugin/withIosDsyms/withIosDsyms.ts | 27 +++++------ .../withIosSourcemaps/withIosSourcemaps.ts | 5 +- 5 files changed, 36 insertions(+), 61 deletions(-) create mode 100644 src/plugin/common/utils.ts diff --git a/src/plugin/__tests__/__snapshots__/withIosSourcemaps.test.ts.snap b/src/plugin/__tests__/__snapshots__/withIosSourcemaps.test.ts.snap index 10660e4..6aa2c72 100644 --- a/src/plugin/__tests__/__snapshots__/withIosSourcemaps.test.ts.snap +++ b/src/plugin/__tests__/__snapshots__/withIosSourcemaps.test.ts.snap @@ -1,49 +1,9 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`withIosSourcemaps on pristine project adds script to upload sourcemaps to Datadog 1`] = ` -""if [[ -f \\"$PODS_ROOT/../.xcode.env\\" ]]; then\\n source \\"$PODS_ROOT/../.xcode.env\\"\\nfi\\nif [[ -f \\"$PODS_ROOT/../.xcode.env.local\\" ]]; then\\n source \\"$PODS_ROOT/../.xcode.env.local\\"\\nfi\\n\\n# The project root by default is one level up from the ios directory\\nexport PROJECT_ROOT=\\"$PROJECT_DIR\\"/..\\n\\nif [[ \\"$CONFIGURATION\\" = *Debug* ]]; then\\n export SKIP_BUNDLING=1\\nfi\\nexport SOURCEMAP_FILE=$DERIVED_FILE_DIR/main.jsbundle.map\\n +exports[`withIosSourcemaps on pristine project adds script to upload sourcemaps to Datadog 1`] = `""if [[ -f \\"$PODS_ROOT/../.xcode.env\\" ]]; then\\n source \\"$PODS_ROOT/../.xcode.env\\"\\nfi\\nif [[ -f \\"$PODS_ROOT/../.xcode.env.local\\" ]]; then\\n source \\"$PODS_ROOT/../.xcode.env.local\\"\\nfi\\n\\n# The project root by default is one level up from the ios directory\\nexport PROJECT_ROOT=\\"$PROJECT_DIR\\"/..\\n\\nif [[ \\"$CONFIGURATION\\" = *Debug* ]]; then\\n export SKIP_BUNDLING=1\\nfi\\nexport SOURCEMAP_FILE=$DERIVED_FILE_DIR/main.jsbundle.map\\n \\n\\nif [[ -z \\"$DATADOG_CI_EXEC\\" ]]; then\\n DATADOG_CI_EXEC=\\"$(\\"$NODE_BINARY\\" --print \\"require('path').resolve(require('path').dirname(require.resolve('@datadog/datadog-ci/package.json')), '../../.bin/datadog-ci')\\")\\";\\n \\n # Check if the file exists and is executable\\n if [[ -x \\"$DATADOG_CI_EXEC\\" ]]; then\\n export DATADOG_CI_EXEC;\\n else\\n echo \\"Error: DATADOG_CI_EXEC does not exist or is not executable\\";\\n exit 1;\\n fi\\nfi\\n\\n\\n$DATADOG_CI_EXEC react-native xcode \`\\"$NODE_BINARY\\" --print \\"require('path').dirname(require.resolve('react-native/package.json')) + '/scripts/react-native-xcode.sh'\\"\`\\n\\n\\n""`; -if [[ -z "$DATADOG_CI_EXEC" ]]; then - export DATADOG_CI_EXEC="$("$NODE_BINARY" --print "require('path').resolve(require('path').dirname(require.resolve('@datadog/datadog-ci/package.json')), '../../.bin/datadog-ci')")" -fi +exports[`withIosSourcemaps on pristine project adds script to upload sourcemaps to Datadog with custom service name 1`] = `""if [[ -f \\"$PODS_ROOT/../.xcode.env\\" ]]; then\\n source \\"$PODS_ROOT/../.xcode.env\\"\\nfi\\nif [[ -f \\"$PODS_ROOT/../.xcode.env.local\\" ]]; then\\n source \\"$PODS_ROOT/../.xcode.env.local\\"\\nfi\\n\\n# The project root by default is one level up from the ios directory\\nexport PROJECT_ROOT=\\"$PROJECT_DIR\\"/..\\n\\nif [[ \\"$CONFIGURATION\\" = *Debug* ]]; then\\n export SKIP_BUNDLING=1\\nfi\\nexport SOURCEMAP_FILE=$DERIVED_FILE_DIR/main.jsbundle.map\\n \\n\\nif [[ -z \\"$DATADOG_CI_EXEC\\" ]]; then\\n DATADOG_CI_EXEC=\\"$(\\"$NODE_BINARY\\" --print \\"require('path').resolve(require('path').dirname(require.resolve('@datadog/datadog-ci/package.json')), '../../.bin/datadog-ci')\\")\\";\\n \\n # Check if the file exists and is executable\\n if [[ -x \\"$DATADOG_CI_EXEC\\" ]]; then\\n export DATADOG_CI_EXEC;\\n else\\n echo \\"Error: DATADOG_CI_EXEC does not exist or is not executable\\";\\n exit 1;\\n fi\\nfi\\n\\n\\n$DATADOG_CI_EXEC react-native xcode \`\\"$NODE_BINARY\\" --print \\"require('path').dirname(require.resolve('react-native/package.json')) + '/scripts/react-native-xcode.sh'\\"\`\\n --service com.company.app\\n\\n""`; +exports[`withIosSourcemaps on projects implementing Sentry adds script to upload sourcemaps to Datadog 1`] = `""export SENTRY_PROPERTIES=sentry.properties\\nexport EXTRA_PACKAGER_ARGS=\\"--sourcemap-output $DERIVED_FILE_DIR/main.jsbundle.map\\"\\nif [[ -f \\"$PODS_ROOT/../.xcode.env\\" ]]; then\\n source \\"$PODS_ROOT/../.xcode.env\\"\\nfi\\nif [[ -f \\"$PODS_ROOT/../.xcode.env.local\\" ]]; then\\n source \\"$PODS_ROOT/../.xcode.env.local\\"\\nfi\\n\\n# The project root by default is one level up from the ios directory\\nexport PROJECT_ROOT=\\"$PROJECT_DIR\\"/..\\n\\nif [[ \\"$CONFIGURATION\\" = *Debug* ]]; then\\n export SKIP_BUNDLING=1\\nfi\\n\`node --print \\"require.resolve('@sentry/cli/package.json').slice(0, -13) + '/bin/sentry-cli'\\"\` react-native xcode --force-foreground \`\\"$NODE_BINARY\\" --print \\"require('path').dirname(require.resolve('react-native/package.json')) + '/scripts/react-native-xcode.sh'\\"\`\\n\\n\\n \\n\\nif [[ -z \\"$DATADOG_CI_EXEC\\" ]]; then\\n DATADOG_CI_EXEC=\\"$(\\"$NODE_BINARY\\" --print \\"require('path').resolve(require('path').dirname(require.resolve('@datadog/datadog-ci/package.json')), '../../.bin/datadog-ci')\\")\\";\\n \\n # Check if the file exists and is executable\\n if [[ -x \\"$DATADOG_CI_EXEC\\" ]]; then\\n export DATADOG_CI_EXEC;\\n else\\n echo \\"Error: DATADOG_CI_EXEC does not exist or is not executable\\";\\n exit 1;\\n fi\\nfi\\n\\n\\n$DATADOG_CI_EXEC react-native xcode \`\\"$NODE_BINARY\\" --print \\"require('path').dirname(require.resolve('react-native/package.json')) + '/scripts/react-native-xcode.sh'\\"\`\\n""`; -$DATADOG_CI_EXEC react-native xcode \`"$NODE_BINARY" --print "require('path').dirname(require.resolve('react-native/package.json')) + '/scripts/react-native-xcode.sh'"\` -\\n\\n"" -`; - -exports[`withIosSourcemaps on pristine project adds script to upload sourcemaps to Datadog with custom service name 1`] = ` -""if [[ -f \\"$PODS_ROOT/../.xcode.env\\" ]]; then\\n source \\"$PODS_ROOT/../.xcode.env\\"\\nfi\\nif [[ -f \\"$PODS_ROOT/../.xcode.env.local\\" ]]; then\\n source \\"$PODS_ROOT/../.xcode.env.local\\"\\nfi\\n\\n# The project root by default is one level up from the ios directory\\nexport PROJECT_ROOT=\\"$PROJECT_DIR\\"/..\\n\\nif [[ \\"$CONFIGURATION\\" = *Debug* ]]; then\\n export SKIP_BUNDLING=1\\nfi\\nexport SOURCEMAP_FILE=$DERIVED_FILE_DIR/main.jsbundle.map\\n - -if [[ -z "$DATADOG_CI_EXEC" ]]; then - export DATADOG_CI_EXEC="$("$NODE_BINARY" --print "require('path').resolve(require('path').dirname(require.resolve('@datadog/datadog-ci/package.json')), '../../.bin/datadog-ci')")" -fi - - -$DATADOG_CI_EXEC react-native xcode \`"$NODE_BINARY" --print "require('path').dirname(require.resolve('react-native/package.json')) + '/scripts/react-native-xcode.sh'"\` - --service com.company.app\\n\\n"" -`; - -exports[`withIosSourcemaps on projects implementing Sentry adds script to upload sourcemaps to Datadog 1`] = ` -""export SENTRY_PROPERTIES=sentry.properties\\nexport EXTRA_PACKAGER_ARGS=\\"--sourcemap-output $DERIVED_FILE_DIR/main.jsbundle.map\\"\\nif [[ -f \\"$PODS_ROOT/../.xcode.env\\" ]]; then\\n source \\"$PODS_ROOT/../.xcode.env\\"\\nfi\\nif [[ -f \\"$PODS_ROOT/../.xcode.env.local\\" ]]; then\\n source \\"$PODS_ROOT/../.xcode.env.local\\"\\nfi\\n\\n# The project root by default is one level up from the ios directory\\nexport PROJECT_ROOT=\\"$PROJECT_DIR\\"/..\\n\\nif [[ \\"$CONFIGURATION\\" = *Debug* ]]; then\\n export SKIP_BUNDLING=1\\nfi\\n\`node --print \\"require.resolve('@sentry/cli/package.json').slice(0, -13) + '/bin/sentry-cli'\\"\` react-native xcode --force-foreground \`\\"$NODE_BINARY\\" --print \\"require('path').dirname(require.resolve('react-native/package.json')) + '/scripts/react-native-xcode.sh'\\"\`\\n\\n\\n - -if [[ -z "$DATADOG_CI_EXEC" ]]; then - export DATADOG_CI_EXEC="$("$NODE_BINARY" --print "require('path').resolve(require('path').dirname(require.resolve('@datadog/datadog-ci/package.json')), '../../.bin/datadog-ci')")" -fi - - -$DATADOG_CI_EXEC react-native xcode \`"$NODE_BINARY" --print "require('path').dirname(require.resolve('react-native/package.json')) + '/scripts/react-native-xcode.sh'"\` -"" -`; - -exports[`withIosSourcemaps on projects implementing Sentry adds script to upload sourcemaps to Datadog with custom service name 1`] = ` -""export SENTRY_PROPERTIES=sentry.properties\\nexport EXTRA_PACKAGER_ARGS=\\"--sourcemap-output $DERIVED_FILE_DIR/main.jsbundle.map\\"\\nif [[ -f \\"$PODS_ROOT/../.xcode.env\\" ]]; then\\n source \\"$PODS_ROOT/../.xcode.env\\"\\nfi\\nif [[ -f \\"$PODS_ROOT/../.xcode.env.local\\" ]]; then\\n source \\"$PODS_ROOT/../.xcode.env.local\\"\\nfi\\n\\n# The project root by default is one level up from the ios directory\\nexport PROJECT_ROOT=\\"$PROJECT_DIR\\"/..\\n\\nif [[ \\"$CONFIGURATION\\" = *Debug* ]]; then\\n export SKIP_BUNDLING=1\\nfi\\n\`node --print \\"require.resolve('@sentry/cli/package.json').slice(0, -13) + '/bin/sentry-cli'\\"\` react-native xcode --force-foreground \`\\"$NODE_BINARY\\" --print \\"require('path').dirname(require.resolve('react-native/package.json')) + '/scripts/react-native-xcode.sh'\\"\`\\n\\n\\n - -if [[ -z "$DATADOG_CI_EXEC" ]]; then - export DATADOG_CI_EXEC="$("$NODE_BINARY" --print "require('path').resolve(require('path').dirname(require.resolve('@datadog/datadog-ci/package.json')), '../../.bin/datadog-ci')")" -fi - - -$DATADOG_CI_EXEC react-native xcode \`"$NODE_BINARY" --print "require('path').dirname(require.resolve('react-native/package.json')) + '/scripts/react-native-xcode.sh'"\` - --service com.company.app"" -`; +exports[`withIosSourcemaps on projects implementing Sentry adds script to upload sourcemaps to Datadog with custom service name 1`] = `""export SENTRY_PROPERTIES=sentry.properties\\nexport EXTRA_PACKAGER_ARGS=\\"--sourcemap-output $DERIVED_FILE_DIR/main.jsbundle.map\\"\\nif [[ -f \\"$PODS_ROOT/../.xcode.env\\" ]]; then\\n source \\"$PODS_ROOT/../.xcode.env\\"\\nfi\\nif [[ -f \\"$PODS_ROOT/../.xcode.env.local\\" ]]; then\\n source \\"$PODS_ROOT/../.xcode.env.local\\"\\nfi\\n\\n# The project root by default is one level up from the ios directory\\nexport PROJECT_ROOT=\\"$PROJECT_DIR\\"/..\\n\\nif [[ \\"$CONFIGURATION\\" = *Debug* ]]; then\\n export SKIP_BUNDLING=1\\nfi\\n\`node --print \\"require.resolve('@sentry/cli/package.json').slice(0, -13) + '/bin/sentry-cli'\\"\` react-native xcode --force-foreground \`\\"$NODE_BINARY\\" --print \\"require('path').dirname(require.resolve('react-native/package.json')) + '/scripts/react-native-xcode.sh'\\"\`\\n\\n\\n \\n\\nif [[ -z \\"$DATADOG_CI_EXEC\\" ]]; then\\n DATADOG_CI_EXEC=\\"$(\\"$NODE_BINARY\\" --print \\"require('path').resolve(require('path').dirname(require.resolve('@datadog/datadog-ci/package.json')), '../../.bin/datadog-ci')\\")\\";\\n \\n # Check if the file exists and is executable\\n if [[ -x \\"$DATADOG_CI_EXEC\\" ]]; then\\n export DATADOG_CI_EXEC;\\n else\\n echo \\"Error: DATADOG_CI_EXEC does not exist or is not executable\\";\\n exit 1;\\n fi\\nfi\\n\\n\\n$DATADOG_CI_EXEC react-native xcode \`\\"$NODE_BINARY\\" --print \\"require('path').dirname(require.resolve('react-native/package.json')) + '/scripts/react-native-xcode.sh'\\"\`\\n --service com.company.app""`; diff --git a/src/plugin/common/exports.ts b/src/plugin/common/exports.ts index 13a2b83..df55478 100644 --- a/src/plugin/common/exports.ts +++ b/src/plugin/common/exports.ts @@ -1,7 +1,16 @@ export const IOS_SOURCEMAP_FILE_EXPORT = "export SOURCEMAP_FILE=$DERIVED_FILE_DIR/main.jsbundle.map"; + export const IOS_DATADOG_CI_EXPORT = ` if [[ -z "$DATADOG_CI_EXEC" ]]; then - export DATADOG_CI_EXEC="$("$NODE_BINARY" --print "require('path').resolve(require('path').dirname(require.resolve('@datadog/datadog-ci/package.json')), '../../.bin/datadog-ci')")" + DATADOG_CI_EXEC="$("$NODE_BINARY" --print "require('path').resolve(require('path').dirname(require.resolve('@datadog/datadog-ci/package.json')), '../../.bin/datadog-ci')")"; + + # Check if the file exists and is executable + if [[ -x "$DATADOG_CI_EXEC" ]]; then + export DATADOG_CI_EXEC; + else + echo "Error: DATADOG_CI_EXEC does not exist or is not executable"; + exit 1; + fi fi `; diff --git a/src/plugin/common/utils.ts b/src/plugin/common/utils.ts new file mode 100644 index 0000000..59aeedb --- /dev/null +++ b/src/plugin/common/utils.ts @@ -0,0 +1,6 @@ +export const escapeStringForIOSBuildPhase = (str: string) => { + return str + .replace(/\\/g, "\\\\") // Escape backslashes + .replace(/"/g, '\\"') // Escape double quotes + .replace(/\n/g, "\\n"); // Replace newlines with \n +}; diff --git a/src/plugin/withIosDsyms/withIosDsyms.ts b/src/plugin/withIosDsyms/withIosDsyms.ts index 122bda5..a6cdd7a 100644 --- a/src/plugin/withIosDsyms/withIosDsyms.ts +++ b/src/plugin/withIosDsyms/withIosDsyms.ts @@ -29,20 +29,19 @@ const withIosDsyms: ConfigPlugin = (config) => { { shellScript: `set -e - if [[ -f "$PODS_ROOT/../.xcode.env" ]]; then - source "$PODS_ROOT/../.xcode.env" - fi - if [[ -f "$PODS_ROOT/../.xcode.env.local" ]]; then - source "$PODS_ROOT/../.xcode.env.local" - fi - - if [[ -z "$NODE_BINARY" ]]; then - echo "ERROR: NODE_BINARY env variable is not set" - fi - - ${IOS_DATADOG_CI_EXPORT} - - $DATADOG_CI_EXEC dsyms upload $DWARF_DSYM_FOLDER_PATH +if [[ -f "$PODS_ROOT/../.xcode.env" ]]; then + source "$PODS_ROOT/../.xcode.env" +fi + +if [[ -f "$PODS_ROOT/../.xcode.env.local" ]]; then + source "$PODS_ROOT/../.xcode.env.local" +fi + +if [[ -z "$NODE_BINARY" ]]; then + echo "ERROR: NODE_BINARY env variable is not set" +fi +${IOS_DATADOG_CI_EXPORT} +$DATADOG_CI_EXEC dsyms upload $DWARF_DSYM_FOLDER_PATH `, shellPath: "/bin/sh", } diff --git a/src/plugin/withIosSourcemaps/withIosSourcemaps.ts b/src/plugin/withIosSourcemaps/withIosSourcemaps.ts index 632f420..f40d783 100644 --- a/src/plugin/withIosSourcemaps/withIosSourcemaps.ts +++ b/src/plugin/withIosSourcemaps/withIosSourcemaps.ts @@ -11,13 +11,14 @@ import { IOS_DATADOG_CI_EXPORT, IOS_SOURCEMAP_FILE_EXPORT, } from "../common/exports"; +import { escapeStringForIOSBuildPhase } from "../common/utils"; import { SourceMapUploadOptions } from "../getErrorTrackingPluginsFromOptions"; -const DATADOG_XCODE_COMMAND = ` +const DATADOG_XCODE_COMMAND = escapeStringForIOSBuildPhase(` ${IOS_DATADOG_CI_EXPORT} $DATADOG_CI_EXEC react-native xcode \`"$NODE_BINARY" --print "require('path').dirname(require.resolve('react-native/package.json')) + '/scripts/react-native-xcode.sh'"\` -`; +`); const getDatadogXCodeCommand = ( serviceName: SourceMapUploadOptions["serviceName"] ) =>