Aplikasi React Native yang mengaktifkan Hermes engine mengompilasi JavaScript menjadi bytecode sebelum didistribusikan ke pengguna. Ketika terjadi crash di production, Sentry sering kali hanya menampilkan un-symbolicated stack trace berupa offset heksadesimal bytecode (misal: HermesRuntimeImpl::dumpCrashLog offset 0x1a2b3c) alih-alih baris file JavaScript/TypeScript asli. Masalah ini diselesaikan dengan menghasilkan composite source map dan mengunggahnya ke Sentry secara otomatis melalui pipeline Continuous Integration (CI).

Akar Masalah: Dual Source Map pada Hermes

Hermes tidak mengeksekusi plain JavaScript, melainkan bytecode biner (.hbc). Proses kompilasi aplikasi melibatkan dua fase pemetaan:

  1. Metro Bundler: Memetakan kode sumber asli (TypeScript/JavaScript) ke minified bundle JavaScript (bundle.packager.map).
  2. Hermes Compiler (hermesc): Mengompilasi minified bundle menjadi bytecode dan menghasilkan map kedua yang menghubungkan instruksi bytecode ke minified bundle (bundle.hbc.map).

Sentry memerlukan satu representasi tunggal yang memetakan bytecode langsung ke file sumber asli. Jika build runner CI hanya mengunggah source map dari Metro, Sentry gagal menerjemahkan offset runtime Hermes.

Sinkronisasi Identifier Release dan Dist

Sentry mencocokkan crash event dengan source map berdasarkan pasangan atribut release dan dist. Nilai ini harus identik antara inisialisasi runtime di aplikasi dan parameter upload pada CI.

Format penamaan standar yang direkomendasikan:

  • Release: <package_name>@<version>+<build_number> (contoh: [email protected]+45)
  • Dist: <build_number> (contoh: 45)

Konfigurasi di sisi aplikasi (runtime):

import * as Sentry from "@sentry/react-native";
import { Platform } from "react-native";
import VersionInfo from "react-native-version-info";

const releaseId = `${VersionInfo.bundleIdentifier}@${VersionInfo.appVersion}+${VersionInfo.buildVersion}`;

Sentry.init({
  dsn: "https://[email protected]/0",
  release: releaseId,
  dist: VersionInfo.buildVersion,
});

Skrip Otomasi: Ekstraksi dan Penggabungan Source Map

Gunakan skrip Node.js/CLI untuk membundel Metro, mengeksekusi hermesc, menggabungkan map via compose-source-maps, lalu mengirimkannya via @sentry/cli. Simpan sebagai scripts/build-and-upload-sourcemap.sh:

#!/usr/bin/env bash
set -euo pipefail

PLATFORM=$1 # android atau ios
RELEASE=$2
DIST=$3

BUNDLE_DIR="./build/sourcemaps/${PLATFORM}"
mkdir -p "${BUNDLE_DIR}"

BUNDLE_FILE="${BUNDLE_DIR}/index.android.bundle"
if [ "$PLATFORM" = "ios" ]; then
  BUNDLE_FILE="${BUNDLE_DIR}/main.jsbundle"
fi

PACKAGER_MAP="${BUNDLE_FILE}.packager.map"
HERMES_BYTECODE="${BUNDLE_FILE}.hbc"
HERMES_MAP="${HERMES_BYTECODE}.map"
FINAL_MAP="${BUNDLE_FILE}.map"

# 1. Generate JS bundle via Metro
npx react-native bundle \
  --platform "${PLATFORM}" \
  --dev false \
  --entry-file index.js \
  --bundle-output "${BUNDLE_FILE}" \
  --sourcemap-output "${PACKAGER_MAP}"

# 2. Kompilasi bytecode Hermes & emit source map Hermes
HERMESC_BIN="./node_modules/react-native/sdks/hermesc/linux64-bin/hermesc"
if [[ "$OSTYPE" == "darwin"* ]]; then
  HERMESC_BIN="./node_modules/react-native/sdks/hermesc/osx-bin/hermesc"
fi

"${HERMESC_BIN}" -emit-binary -O \
  -out "${HERMES_BYTECODE}" \
  "${BUNDLE_FILE}" \
  -output-source-map

# 3. Gabungkan Metro map dan Hermes map
node ./node_modules/react-native/scripts/compose-source-maps.js \
  "${PACKAGER_MAP}" \
  "${HERMES_MAP}" \
  -o "${FINAL_MAP}"

# 4. Upload ke Sentry
npx @sentry/cli releases new "${RELEASE}"
npx @sentry/cli releases set-commits "${RELEASE}" --auto || true

npx @sentry/cli releases files "${RELEASE}" upload-sourcemaps \
  --dist "${DIST}" \
  --strip-prefix "$(pwd)" \
  "${FINAL_MAP}" \
  "${BUNDLE_FILE}"

npx @sentry/cli releases finalize "${RELEASE}"

Pipeline GitHub Actions

Konfigurasi workflow CI untuk mengeksekusi pipeline release Android dan otomasi upload source map Hermes. Masukkan secret repository: SENTRY_AUTH_TOKEN, SENTRY_ORG, dan SENTRY_PROJECT.

name: Android Release & Sentry Upload

on:
  push:
    tags:
      - 'v*'

jobs:
  build-android:
    runs-on: ubuntu-latest
    env:
      SENTRY_AUTH_TOKEN: ${{ secrets.SENTRY_AUTH_TOKEN }}
      SENTRY_ORG: ${{ secrets.SENTRY_ORG }}
      SENTRY_PROJECT: ${{ secrets.SENTRY_PROJECT }}
    steps:
      - name: Checkout repository
        uses: actions/checkout@v4
        with:
          fetch-depth: 0

      - name: Setup Node.js
        uses: actions/setup-node@v4
        with:
          node-version: 20
          cache: 'yarn'

      - name: Install Dependencies
        run: yarn install --frozen-lockfile

      - name: Set Release Identifiers
        id: versioning
        run: |
          VERSION=$(node -p "require('./package.json').version")
          DIST=${{ github.run_number }}
          RELEASE="com.example.app@${VERSION}+${DIST}"
          echo "RELEASE=${RELEASE}" >> $GITHUB_ENV
          echo "DIST=${DIST}" >> $GITHUB_ENV

      - name: Build and Upload Hermes Source Maps
        run: |
          chmod +x ./scripts/build-and-upload-sourcemap.sh
          ./scripts/build-and-upload-sourcemap.sh android "${RELEASE}" "${DIST}"

      - name: Build Android Release APK/AAB
        run: |
          cd android
          ./gradlew bundleRelease -x bundleReleaseJsAndAssets
        # Flag -x bundleReleaseJsAndAssets opsional jika JS bundle sudah dibuat manual
Catatan Keamanan: Berikan hak akses terbatas (least privilege) pada SENTRY_AUTH_TOKEN. Token hanya memerlukan scope: project:releases, project:write, dan org:read. Jangan gunakan user account auth token pribadi; gunakan Dedicated Integration/Internal Bot Token.

Verifikasi Artifact pada Sentry Dashboard

Setelah CI pipeline selesai, validasi kelengkapan artifact sebelum merilis binary aplikasi ke publik:

  1. Masuk ke akun Sentry, buka menu Settings > Projects > pilih project React Native.
  2. Navigasi ke panel Source Maps atau Releases di sidebar utama.
  3. Pilih release tag yang baru dibuat (misal: [email protected]+45).
  4. Buka tab Artifacts. Pastikan file berikut terdaftar dan berada di bawah dist yang sama:
    • index.android.bundle (atau main.jsbundle untuk iOS)
    • index.android.bundle.map (composite map)
  5. Trigger test crash di device menggunakan build release: throw new Error("Sentry Hermes Verification");.
  6. Pastikan issue baru di Sentry menampilkan function name, file name (.ts/.tsx), dan baris asli tanpa pesan "Source map was not found" atau "Hermes bytecode offset".