Skip to content

Commit

Permalink
feat(react-native): new source maps generation script with support fo…
Browse files Browse the repository at this point in the history
…r hermes (#1169)
  • Loading branch information
andreybutov authored Sep 7, 2023
1 parent 0f4698e commit ed10921
Show file tree
Hide file tree
Showing 4 changed files with 337 additions and 25 deletions.
20 changes: 19 additions & 1 deletion packages/react-native/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,25 @@ Error reports can be [customized](https://docs.honeybadger.io/lib/javascript/gui
Some native errors on Android may not be recorded if they cause an immediate crash of the app before the notice makes it to Honeybadger.

### Source Maps
The procedure to generate and upload sourcemaps for react-native is currently [under development](https://github.com/honeybadger-io/honeybadger-js/issues/1029).
To generate and upload source maps to Honeybadger, use the following command:
```shell
npx honeybadger-upload-sourcemaps --apiKey <your project API key> --revision <build revision>
```

The `--apiKey` param is your Honeybadger API key for the project. The `--revision` param should match the revision param of the `Honeybadger.init` call inside your application. This is done so that reported errors are correctly matched up against the generated source maps.

As of version 0.70, React Native uses Hermes as the default JavaScript engine. The source maps tool assumes that your project uses Hermes. If you are building against an earlier version of React Native, or are explicitly not using Hermes, add the `--no-hermes` flag to the sourcemaps tool, like so:

```shell
npx honeybadger-upload-sourcemaps --no-hermes --apiKey <your project API key> --revision <build revision>
```

If you just want to generate the sourcemaps without uploading them to Honeybadger, you can use the `--skip-upload` flag.

```shell
npx honeybadger-upload-sourcemaps --skip-upload --apiKey <your project API key> --revision <build revision>
```


## Example Projects

Expand Down
23 changes: 0 additions & 23 deletions packages/react-native/honeybadger-generate-sourcemaps.sh

This file was deleted.

2 changes: 1 addition & 1 deletion packages/react-native/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
"test": "jest"
},
"bin": {
"honeybadger-generate-sourcemaps": "./honeybadger-generate-sourcemaps.sh"
"honeybadger-upload-sourcemaps": "./scripts/honeybadger-upload-sourcemaps.sh"
},
"repository": {
"type": "git",
Expand Down
317 changes: 317 additions & 0 deletions packages/react-native/scripts/honeybadger-upload-sourcemaps.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,317 @@
#!/bin/bash

# ----------------------------------------------------------------------------
#
# Generate and upload sourcemaps to Honeybadger
#
# USAGE:
# npx honeybadger-upload-sourcemaps [--no-hermes] [--skip-upload] --apiKey <project-api-key> --revision <build-revision>
#
# Hermes is enabled by default in React Native projects as of React Native 0.70.
# This script assumes that Hermes is being used. If you are not using Hermes in
# your React Native project, use the "--no-hermes" flag.
#
# If you just need to generate the sourcemaps without uploading them to
# Honeybadger, use the --skip-upload flag.
#
# ----------------------------------------------------------------------------

USAGE="npx honeybadger-upload-sourcemaps [--no-hermes] [--skip-upload] --apiKey <project-api-key> --revision <build-revision>"

SCRIPT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null 2>&1 && pwd )"
PROJECT_ROOT_DIR="$SCRIPT_DIR/../.."
NODE_MODULES="$PROJECT_ROOT_DIR/node_modules"

ANDROID_PACKAGER_BUNDLE="$PROJECT_ROOT_DIR/index.android.bundle"
ANDROID_PACKAGER_SOURCE_MAP="$PROJECT_ROOT_DIR/index.android.bundle.packager.map"
ANDROID_COMPILER_BUNDLE="$PROJECT_ROOT_DIR/index.android.bundle.hbc"
ANDROID_COMPILER_SOURCE_MAP="$PROJECT_ROOT_DIR/index.android.bundle.hbc.map"
ANDROID_FINAL_SOURCE_MAP="$PROJECT_ROOT_DIR/index.android.bundle.map"

IOS_PACKAGER_BUNDLE="$PROJECT_ROOT_DIR/main.jsbundle"
IOS_PACKAGER_SOURCE_MAP="$PROJECT_ROOT_DIR/main.jsbundle.packager.map"
IOS_COMPILER_BUNDLE="$PROJECT_ROOT_DIR/main.jsbundle.hbc"
IOS_COMPILER_SOURCE_MAP="$PROJECT_ROOT_DIR/main.jsbundle.hbc.map"
IOS_FINAL_SOURCE_MAP="$PROJECT_ROOT_DIR/main.jsbundle.map"

# This is needed for the API call to upload source maps to Honeybadger.
EMPTY_FILE="$PROJECT_ROOT_DIR/empty_file.txt"

API_KEY=""
REVISION=""
USE_HERMES=true
SKIP_UPLOAD=false

OS_BIN=""


#
# Parse the command line variables.
# We need the apiKey, and the build revision.
# We also need to determine if we should use Hermes.
#

while [[ $# -gt 0 ]]; do
case "$1" in
--no-hermes)
USE_HERMES=false
shift
;;
--skip-upload)
SKIP_UPLOAD=true
shift
;;
--apiKey)
shift
if [[ $# -gt 0 ]]; then
API_KEY="$1"
shift
else
echo "Error: Missing value for --apiKey"
echo $USAGE
exit 1
fi
;;
--revision)
shift
if [[ $# -gt 0 ]]; then
REVISION="$1"
shift
else
echo "Error: Missing value for --revision"
echo $USAGE
exit 1
fi
;;
*)
echo "Unknown option: $1"
echo $USAGE
exit 1
;;
esac
done


#
# Make sure we have API_KEY and REVISION
#

if [[ -z "$API_KEY" ]]; then
echo "Error: apiKey is empty"
echo $USAGE
exit 1
fi


if [[ -z "$REVISION" ]]; then
echo "Error: revision is empty"
echo $USAGE
exit 1
fi


#
# Determine what the operating system is. This is needed to find the path
# to the Hermes executable.
#

if $USE_HERMES; then

case "$(uname -s)" in
Darwin)
OS_BIN="osx-bin"
;;
Linux)
OS_BIN="linux64-bin"
;;
CYGWIN*|MINGW32*|MSYS*)
OS_BIN="win64-bin"
;;
*)
echo "Unsupported operating system."
exit 1
;;
esac

fi


echo "Generating the Android source map ..."

npx react-native bundle \
--platform android \
--dev false \
--entry-file "$PROJECT_ROOT_DIR/index.js" \
--reset-cache \
--bundle-output "$ANDROID_PACKAGER_BUNDLE" \
--sourcemap-output "$ANDROID_PACKAGER_SOURCE_MAP" \
--minify false > /dev/null 2>&1


echo "Generating the iOS source map ..."

npx react-native bundle \
--platform ios \
--dev false \
--entry-file index.js \
--reset-cache \
--bundle-output "$IOS_PACKAGER_BUNDLE" \
--sourcemap-output "$IOS_PACKAGER_SOURCE_MAP" \
--minify false > /dev/null 2>&1


if $USE_HERMES; then

echo "Hermes is enabled ..."

#
# Need to find the Hermes compiler executable for Android.
#

# react native >= 0.69
HERMES_PATH_1="$NODE_MODULES/react-native/sdks/hermesc/$OS_BIN/hermesc"

# react native <= 0.68
HERMES_PATH_2="$NODE_MODULES/hermes-engine/$OS_BIN/hermesc"


if [ -f "$HERMES_PATH_1" ]; then
HERMES_EXECUTABLE="$HERMES_PATH_1"
elif [ -f "$HERMES_PATH_2" ]; then
HERMES_EXECUTABLE="$HERMES_PATH_2"
else
echo "Error: The Hermes compiler executable for Android was not found in the expected locations."
exit 1
fi


echo "Generating Hermes Android source map ..."

#
# compile the Android code to bytecode and generate the Hermes sourcemap
#

"$HERMES_EXECUTABLE" \
-O \
-emit-binary \
-output-source-map \
-out="$ANDROID_COMPILER_BUNDLE" \
"$ANDROID_PACKAGER_BUNDLE" > /dev/null 2>&1

#
# merge the two Android sourcemaps
#

node "$NODE_MODULES/react-native/scripts/compose-source-maps.js" \
"$ANDROID_PACKAGER_SOURCE_MAP" \
"$ANDROID_COMPILER_SOURCE_MAP" \
-o "$ANDROID_FINAL_SOURCE_MAP" > /dev/null 2>&1


#
# Need to find the Hermes compiler executable for iOS.
#

HERMES_EXECUTABLE="$PROJECT_ROOT_DIR/ios/Pods/hermes-engine/destroot/bin/hermesc"

if [ ! -f "$HERMES_EXECUTABLE" ]; then
echo "Error: The Hermes compiler executable for iOS was not found in the expected location: $HERMES_EXECUTABLE . Did you run pod install in the ios/ directory?"
exit 1
fi


echo "Generating Hermes iOS source map ..."

#
# Compile the iOS code to bytecode and generate the Hermes sourcemap.
#

"$HERMES_EXECUTABLE" \
-O \
-emit-binary \
-output-source-map \
-out="$IOS_COMPILER_BUNDLE" \
"$IOS_PACKAGER_BUNDLE" > /dev/null 2>&1

#
# merge the two iOS sourcemaps
#

node "$NODE_MODULES/react-native/scripts/compose-source-maps.js" \
"$IOS_PACKAGER_SOURCE_MAP" \
"$IOS_COMPILER_SOURCE_MAP" \
-o "$IOS_FINAL_SOURCE_MAP" > /dev/null 2>&1


#
# cleanup
#

rm -f \
"$ANDROID_PACKAGER_BUNDLE" \
"$ANDROID_PACKAGER_SOURCE_MAP" \
"$ANDROID_COMPILER_BUNDLE" \
"$ANDROID_COMPILER_SOURCE_MAP" \
"$IOS_PACKAGER_BUNDLE" \
"$IOS_PACKAGER_SOURCE_MAP" \
"$IOS_COMPILER_BUNDLE" \
"$IOS_COMPILER_SOURCE_MAP"

else

echo "Hermes is not enabled ..."

#
# The packager source maps are the only source maps we need.
#

mv "$ANDROID_PACKAGER_SOURCE_MAP" "$ANDROID_FINAL_SOURCE_MAP"
mv "$IOS_PACKAGER_SOURCE_MAP" "$IOS_FINAL_SOURCE_MAP"

#
# cleanup
#

rm -f "$ANDROID_PACKAGER_BUNDLE" "$IOS_PACKAGER_BUNDLE"

fi


#
# We should now have Android and iOS source maps ready for upload.
#

if $SKIP_UPLOAD; then
echo "Source maps generated."
echo " iOS: main.jsbundle.map"
echo " Android: index.android.bundle.map"
echo "Skipping upload.";
exit;
fi

echo "Source maps generated. Uploading to Honeybadger ...";

rm -f "$EMPTY_FILE"
touch "$EMPTY_FILE"

curl https://api.honeybadger.io/v1/source_maps \
-F api_key="$API_KEY" \
-F revision="$REVISION" \
-F minified_url=index.android.bundle \
-F source_map=@"$ANDROID_FINAL_SOURCE_MAP" \
-F minified_file=@"$EMPTY_FILE" > /dev/null 2>&1

curl https://api.honeybadger.io/v1/source_maps \
-F api_key="$API_KEY" \
-F revision="$REVISION" \
-F minified_url=main.jsbundle \
-F source_map=@"$IOS_FINAL_SOURCE_MAP" \
-F minified_file=@"$EMPTY_FILE" > /dev/null 2>&1

#
# Cleanup
#

rm -f "$ANDROID_FINAL_SOURCE_MAP" "$IOS_FINAL_SOURCE_MAP" "$EMPTY_FILE"

echo "Source maps uploaded to Honeybadger."

0 comments on commit ed10921

Please sign in to comment.