@observerkit/metro
v0.2.0
Published
Metro plugin for ObserverKit: injects debug IDs and uploads React Native source maps
Readme
@observerkit/metro
Metro plugin for ObserverKit: injects debug IDs into React Native bundles and uploads source maps so production stack traces symbolicate, including Hermes builds.
Install
npm install --save-dev @observerkit/metro1. Wrap your Metro config
// metro.config.js
const { getDefaultConfig } = require("@react-native/metro-config")
const withObserverkit = require("@observerkit/metro")
module.exports = withObserverkit(getDefaultConfig(__dirname))Debug-id injection only runs for release bundles; dev builds are untouched.
2. Upload source maps from your native builds
The observerkit-upload CLI reads the project key from --project-key or the OBSERVERKIT_PROJECT_KEY environment variable.
Expo
Add the config plugin; prebuild wires both native builds automatically:
{
"expo": {
"plugins": [["@observerkit/metro/expo", { "projectKey": "YOUR_PROJECT_KEY" }]]
}
}For EAS Update bundles, upload after export:
npx expo export && npx observerkit-upload --dir distBare React Native: iOS
Edit the "Bundle React Native code and images" build phase so a source map is emitted and uploaded:
export SOURCEMAP_FILE="$DERIVED_FILE_DIR/main.jsbundle.map"
set -e
WITH_ENVIRONMENT="../node_modules/react-native/scripts/xcode/with-environment.sh"
REACT_NATIVE_XCODE="../node_modules/react-native/scripts/react-native-xcode.sh"
/bin/sh -c "$WITH_ENVIRONMENT $REACT_NATIVE_XCODE"
if [ -f "$SOURCEMAP_FILE" ]; then
npx observerkit-upload --map "$SOURCEMAP_FILE" --bundle "$CONFIGURATION_BUILD_DIR/main.jsbundle" --project-key "YOUR_PROJECT_KEY" || echo "[ObserverKit] source map upload failed"
fi--bundle points at the pre-Hermes JS bundle, which is where observerkit-upload finds the debugId on iOS (see Hermes below). The || echo makes sure a failed upload never fails the archive, since this phase runs under set -e.
Bare React Native: Android
Append to android/app/build.gradle:
tasks.configureEach { task ->
def matcher = task.name =~ /^createBundle(\w*)ReleaseJsAndAssets$/
if (matcher.matches()) {
task.doLast {
def flavor = matcher.group(1)
def variant = flavor.isEmpty()
? "release"
: flavor.substring(0, 1).toLowerCase() + flavor.substring(1) + "Release"
def mapFile = file("$buildDir/generated/sourcemaps/react/" + variant + "/index.android.bundle.map")
def packagerMapFile = file("$buildDir/intermediates/sourcemaps/react/" + variant + "/index.android.bundle.packager.map")
if (mapFile.exists()) {
def result = exec {
workingDir rootProject.projectDir.parentFile
environment "OBSERVERKIT_PROJECT_KEY", "YOUR_PROJECT_KEY"
commandLine "npx", "observerkit-upload", "--map", mapFile.absolutePath, "--packager-map", packagerMapFile.absolutePath
ignoreExitValue true
}
if (result.exitValue != 0) {
logger.warn("[ObserverKit] source map upload failed")
}
} else {
logger.warn("[ObserverKit] No source map found at " + mapFile + ", skipping upload")
}
}
}
}The composed map lands in generated/sourcemaps/react/<variant>/, while the pre-compose packager map (needed to restore the debugId, see Hermes below) stays behind in intermediates/sourcemaps/react/<variant>/. ignoreExitValue plus the logged warning make sure a failed upload never fails assembleRelease.
Hermes
Symbolication support targets Hermes, the React Native default JS engine; JSC is not supported.
Hermes release builds compose the Metro source map with the bytecode map, which drops the debugId field. observerkit-upload restores it automatically:
- On Android, from the packager map (
<bundle>.packager.mapnext to the final map by convention, or pass--packager-mapexplicitly). The Gradle plugin keeps this file around after composing. - On iOS,
react-native-xcode.shdeletes the packager map once the final map is composed, so there is nothing to restore it from there. Insteadobserverkit-uploadreads the trailing//# debugId=<uuid>comment the ObserverKit serializer appends to the pre-Hermes JS bundle (the final map path with.mapstripped, or pass--bundleexplicitly).
CLI reference
observerkit-upload --map <path> [--packager-map <path>] [--bundle <path>]
observerkit-upload --dir <path>
--project-key <key> Defaults to $OBSERVERKIT_PROJECT_KEY
--endpoint <url> Defaults to $OBSERVERKIT_ENDPOINT or https://ingest.observerkit.compnpm note
If your app uses pnpm and Metro is not a direct dependency, add it (pnpm add -D metro) so the plugin can load Metro's default serializer.
