Files
opencode-mobile/.github/workflows/publish-play-store.yml
engineer 42ceea3e1f fix(sentry): repair Android source-map upload (debug IDs + release/dist match)
Releases 0.4.3-0.4.7 uploaded zero source-map files to Sentry, leaving every
JS frame unsymbolicated (app:///index.android.bundle:1). Root-caused two
independent bugs:

1. No metro.config.js existed, so Metro never ran Sentry's debug-ID
   injection. Without an embedded debug ID, sentry.gradle's upload task
   falls back to matching source maps to events by release/dist string
   alone (see has-sourcemap-debugid.js check in sentry.gradle) - and that
   fallback was broken (see #2). Added metro.config.js wrapping Expo's
   default config with getSentryExpoConfig from @sentry/react-native/metro,
   the officially documented path for Expo + debug-ID symbolication.

   The installed @sentry/react-native@6.14.0 could not actually bundle with
   this enabled: its metro integration does a hard `require("metro/src/lib/
   countLines")`, a deep path metro 0.83.x (bundled by Expo SDK 54) no
   longer exposes via its package.json `exports` map, crashing every build.
   Bumped to ~6.22.0 (package.json:18), which vendors countLines and adds
   metro/private/* fallbacks for other deep metro imports. Verified via a
   real `npx expo export:embed` run: bundle and source map now share a
   matching `debugId`.

2. sentry.gradle's default release/dist for the upload is
   `${applicationId}@${versionName}+${versionCode}` (computed from
   android/app/build.gradle), which never matched what Sentry.init() reports
   at runtime (`opencode-mobile@${app.json version}`, src/lib/sentry.ts:33-34).
   Every source map was therefore filed under a release Sentry never
   queries. Added a "Set Sentry release identifiers" step to build.yml,
   publish-play-store.yml, and publish-fdroid.yml that exports
   SENTRY_RELEASE/SENTRY_DIST from app.json's version before the Gradle
   build step, forcing an exact match.

Also filled in organization/project on the `@sentry/react-native/expo`
plugin in app.json (previously a bare string, which only warned "Missing
config for organization, project" and relied on env-var fallback) so
android/sentry.properties is generated deterministically instead of by
accident/history.

Verified locally (no push - GitHub is down, consolidating to local main):
- npx expo export:embed (real Metro bundle) succeeds and embeds a matching
  debugId in both index.android.bundle and its .map
- npm run typecheck: clean
- npm test: 81/81 passing
- Full ./gradlew Android build not verified: this machine has no
  ANDROID_HOME/SDK and a JDK/Gradle-wrapper version mismatch unrelated to
  this change; CI's Java 17 + Android SDK toolchain is unaffected.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NJKAQ6HAikWGQK7PGZ5Y4E
2026-07-16 15:51:31 -07:00

141 lines
5.5 KiB
YAML

name: Publish to Google Play Store
on:
release:
types: [published]
push:
tags: ["v*"]
workflow_dispatch:
inputs:
track:
description: "Play track to release to"
required: false
default: "internal"
type: choice
options:
- internal
- alpha
- beta
- production
status:
description: "Release status (draft = uploads without going live/review; completed = submit)"
required: false
default: "completed"
type: choice
options:
- completed
- draft
jobs:
publish:
runs-on: ubuntu-latest
env:
EXPO_PUBLIC_SENTRY_DSN: ${{ secrets.EXPO_PUBLIC_SENTRY_DSN }}
SENTRY_AUTH_TOKEN: ${{ secrets.SENTRY_AUTH_TOKEN }}
SENTRY_ORG: ${{ secrets.SENTRY_ORG }}
SENTRY_PROJECT: ${{ secrets.SENTRY_PROJECT }}
steps:
- uses: actions/checkout@v6
- uses: actions/setup-node@v6
with:
node-version: 20
cache: npm
- uses: actions/setup-java@v5
with:
distribution: temurin
java-version: 17
- name: Setup Android SDK
uses: android-actions/setup-android@v4
- name: Cache Gradle
uses: actions/cache@v5
with:
path: |
~/.gradle/caches
~/.gradle/wrapper
android/.gradle
key: ${{ runner.os }}-gradle-${{ hashFiles('android/**/*.gradle*', 'android/gradle/wrapper/gradle-wrapper.properties') }}
restore-keys: |
${{ runner.os }}-gradle-
- name: Cache Android build outputs
uses: actions/cache@v5
with:
path: |
android/app/build/intermediates
android/build
android/app/.cxx
key: ${{ runner.os }}-android-build-${{ hashFiles('package-lock.json', 'android/**/*.gradle*') }}
restore-keys: |
${{ runner.os }}-android-build-
- name: Install dependencies
run: npm install --legacy-peer-deps
- name: Bump android.versionCode in app.json
# Play Store rejects duplicate versionCodes, so derive a monotonic value
# from github.run_number + offset. expo prebuild reads this into build.gradle.
# Offset 100 skips past historical collisions (run 31 collided with a
# manual upload at versionCode 31). Next run = run_number + 100.
run: |
node -e "const f='app.json';const j=require('./'+f);j.expo.android=j.expo.android||{};j.expo.android.versionCode=${{ github.run_number }}+100;require('fs').writeFileSync(f,JSON.stringify(j,null,2)+'\n')"
echo "versionCode now: $(node -p "require('./app.json').expo.android.versionCode")"
- name: Set Sentry release identifiers
# sentry.gradle (applied from android/app/build.gradle) defaults the
# upload's --release/--dist to `${applicationId}@${versionName}+${versionCode}`,
# which does NOT match the release/dist Sentry.init() reports at runtime
# (`opencode-mobile@${app.json version}`, see src/lib/sentry.ts). That
# mismatch made every uploaded source map land under a release Sentry
# never looks up, so symbolication silently failed. Pin the Gradle-side
# values to exactly what the app reports. (Only expo.version matters
# here, not the android.versionCode bumped above.)
run: |
VERSION=$(node -p "require('./app.json').expo.version")
echo "SENTRY_RELEASE=opencode-mobile@${VERSION}" >> "$GITHUB_ENV"
echo "SENTRY_DIST=${VERSION}" >> "$GITHUB_ENV"
echo "Sentry release=opencode-mobile@${VERSION} dist=${VERSION}"
- name: Purge stale generated sources
# The Android build cache (restore-keys prefix fallback) can restore a
# generated autolinking tree from a previous package id. Gradle then
# reuses ReactNativeApplicationEntryPoint.java referencing the OLD
# package (ai.opencode.mobile.BuildConfig) and compileReleaseJavaWithJavac
# fails. Delete generated sources so prebuild + Gradle regenerate them
# for the current package (cc.agentlabs.opencode).
run: rm -rf android/app/build/generated android/build/generated android/app/build/intermediates
- name: Expo prebuild
run: npx expo prebuild --platform android --no-install
- name: Decode keystore
run: echo "${{ secrets.KEYSTORE_BASE64 }}" | base64 -d > android/app/release.keystore
- name: Build AAB
working-directory: android
env:
RELEASE_STORE_FILE: release.keystore
RELEASE_STORE_PASSWORD: ${{ secrets.KEYSTORE_PASSWORD }}
RELEASE_KEY_ALIAS: ${{ secrets.KEY_ALIAS }}
RELEASE_KEY_PASSWORD: ${{ secrets.KEY_PASSWORD }}
run: ./gradlew bundleRelease
- name: Upload AAB artifact
uses: actions/upload-artifact@v7
with:
name: app-release-bundle
path: android/app/build/outputs/bundle/release/app-release.aab
- name: Publish to Play Store
uses: r0adkll/upload-google-play@e738b9dd8f2476ea806d921b64aacd24f34515a5 # v1.1.5
with:
serviceAccountJsonPlainText: ${{ secrets.PLAY_STORE_SERVICE_ACCOUNT_JSON }}
packageName: cc.agentlabs.opencode
releaseFiles: android/app/build/outputs/bundle/release/app-release.aab
track: ${{ github.event.inputs.track || 'internal' }}
status: ${{ github.event.inputs.status || 'completed' }}
whatsNewDirectory: distribution/whatsnew