fix(mobile,ci): build releases from the lockfile, drop Expo, cache the native compiles - #551
Conversation
|
The latest updates on your projects. Learn more about Vercel for GitHub. 1 Skipped Deployment
|
There was a problem hiding this comment.
Code Review: PR #551 — fix(mobile,ci): build releases from the lockfile, drop Expo, cache the native compiles
Scope: PR #551 in thomasluizon/orbit-ui-mobile (.github/workflows/android-release.yml + apps/mobile/{app.json,eas.json,plugins,scripts,__tests__})
Recommendation: NEEDS WORK
Summary
This PR replaces the isolated-workspace Android release build with a root npm ci install, replaces eas-cli submit with a direct r0adkll/upload-google-play Play Developer API call, adds ccache + Gradle build-cache + reactNativeArchitectures restriction for build speed, and fixes a real bug where configure-android-release-signing.js was clobbering the JVM heap size the release plugin set. The dead workspace/verification/device scripts and eas.json are cleanly removed with no dangling references left anywhere in the repo. One High-severity issue: the new Google Play upload step's with: block passes tracks: (plural) to an action whose declared input is track (singular) in every version of r0adkll/upload-google-play this reviewer is aware of — which would mean the per-dispatch track selection (the whole point of the new "Resolve Google Play track" step) never reaches the upload action.
Findings
Critical
None.
High
[HIGH] tracks: is very likely the wrong input key for r0adkll/upload-google-play, so the resolved release track may never reach the upload step
· dimension: 1 — Correctness
· location: .github/workflows/android-release.yml:472-479 (the "Submit build to Google Play" step)
· issue: The step resolves the dispatched track into env.PLAY_TRACK (a new, well-designed step) and then passes it as tracks: ${{ env.PLAY_TRACK }} to r0adkll/upload-google-play@e738b9dd8f2476ea806d921b64aacd24f34515a5 (v1.1.5). Every published version of this action I'm aware of declares its release-track input as track (singular) — releaseFiles is plural, track is not. If the action's action.yml at this pinned commit really only declares track, GitHub Actions logs an "Unexpected input(s) 'tracks'" warning and continues, and the action never receives the resolved value — it either falls back to its own default/required-input error, or a hardcoded track, independent of what inputs.track (internal/closed/open/production) the human actually dispatched.
· risk: A release dispatched to internal or closed could silently upload to whatever track the action defaults to instead — worst case, straight to production for real users, with the workflow reporting green. The PR's own body flags this exact step as "Not provable without a release" (unlike the rest of the diff, which was verified locally against the real tree), so this is precisely the part of the change most likely to hide a bug until a real release runs.
· fix: Confirm the exact input name in action.yml at commit e738b9dd8f2476ea806d921b64aacd24f34515a5 (https://github.com/r0adkll/upload-google-play/blob/e738b9dd8f2476ea806d921b64aacd24f34515a5/action.yml) before merging. If it is track (singular), rename the key. Given the PR's own recommendation to dispatch to internal before an open-track release, also consider a one-line assertion/log of the track the action itself reports using, so a future mismatch fails loudly instead of silently.
· reference: PR body, "Not provable without a release" section; CLAUDE.md rule 1 (root cause / correctness at the boundary this diff itself introduces).
Note on confidence: I could not reach the network from this review sandbox (no gh api/curl/WebFetch/WebSearch access was granted) to read the pinned action.yml directly, so this is reported at High rather than Critical per the rubric's "downgrade when uncertain" guidance — verify the single fact above and this either closes as a one-line fix or confirms a real release-safety bug.
Medium
None.
Low / Info
inputs.message's description ("Optional build message shown in Expo") is now stale — this PR removes every EAS/Expo submission step, so there is no longer anywhere for the message to be "shown in Expo." The field was already unused by any actual step before this PR (only echoed to the summary/re-run command), so this is a pre-existing dead field whose description this diff incidentally made more wrong. Not blocking; worth a follow-up line update or removal.
Subagents
Ran inline (this session had no access to spawn the specialist subagents or the network they'd typically use); folded the equivalent checks into the dimensions above.
| Agent | Verdict |
|---|---|
| parity-checker | N/A — Android-native release/CI tooling has no apps/web counterpart (PR body states this explicitly; consistent with the precedent set by #545). |
| i18n-syncer | N/A — no user-facing strings changed. |
| contract-aligner | N/A — no packages/shared/src/types/* or endpoints.ts change; orbit-api not touched. |
| security-reviewer | N/A — orbit-api not touched. Frontend-security note: this PR is a net security improvement (removes EXPO_TOKEN, stops writing the Play service-account key to disk, pins the new third-party action to a commit SHA rather than a floating tag). |
| design-reviewer | N/A — no apps/* UI files changed. |
Validation
| Check | Result |
|---|---|
| Lint | N/A — no node_modules installed in this review sandbox and package install requires network approval not available here |
| Type check | N/A — same reason |
| Tests | N/A — same reason; PR body claims 244 files / 1589 tests pass, and the new with-android-release-build-fixes.test.ts was manually traced against the plugin's forceJvmFlag logic and is internally consistent (idempotency case included) |
| Build (api) | N/A — orbit-api not touched |
Deferred — N/A dimensions & files not verdicted
- DESIGN.md/AI-slop (#8), i18n (#10), contract drift (#11), backend hard rules (#13), FEATURES.md parity (#14): all N/A — the diff never touches their surface (no UI files, no strings, no shared types, no orbit-api, no user-facing feature/gating change).
- Live verification of the
r0adkll/upload-google-playaction's actualaction.ymlinput schema at the pinned commit: not performed — this review sandbox had no approved path to the network (gh api,curl,WebFetch, andWebSearchall required a permission grant that wasn't available). This is the one open question behind the High finding above; the reviewer should close it before merge. - Full validation (lint/typecheck/tests) per Phase 7: not run — no installed dependencies and no approved install path in this sandbox. Relying on the PR body's stated local verification (tsc, expo lint, 1589 tests, plus the author's own real-prebuild-output evidence for
reactNativeArchitecturesand the AdMob/widget manifest checks). - Every other changed file (
app.json,eas.jsonremoval,with-android-release-build-fixes.js/.d.ts,configure-android-release-signing.js,set-app-version.js, the three deleted scripts and their test) was read in full and given a verdict: clean removals, no dangling references anywhere in the repo (grep-verified), and the JVM-heap ownership fix (plugin now sole writer oforg.gradle.jvmargs) is correct and covered by a new, well-constructed test.
What's good
- Root-causes a real, previously-invisible drift bug (13/57 native modules + 87 JS packages resolving off-lockfile at release time) by deleting the mechanism that caused it, rather than adding a verification script to guard around it — exactly the rule-1 "root cause over workaround" standard.
- Finds and fixes a second real bug in the same path (
configure-android-release-signing.jssilently undoing the release JVM heap plugin's work on every CI release) and backs the fix with a genuinely useful regression test (with-android-release-build-fixes.test.ts), including an idempotency case. - Net −384 lines: three dead scripts and
eas.jsonremoved outright, withgrepconfirming zero remaining references anywhere in the repo. - Meaningful security hardening as a side effect:
EXPO_TOKENgone, the Play service-account key is never written to disk, and the new third-party action is pinned to a commit SHA (not a floating tag) with the version documented in a trailing comment, consistent with the file's existing convention for third-party actions. - Honest and specific about what wasn't (and couldn't be) verified locally — the PR body itself calls out the Gradle/signing/Play-upload path as unprovable without a real release, which is exactly where this review's own High finding lives.
Recommendation
Verify the r0adkll/upload-google-play input name (track vs tracks) against the pinned commit's action.yml before merging. If it needs to change, it's a one-line fix. Once confirmed (or corrected), this is a clean, well-verified, and well-explained release-pipeline rewrite — re-review is not expected to surface anything further; a quick internal-track dispatch to confirm the real upload behaves as intended (per the PR's own recommendation) is the remaining real-world proof point.
…review) Review flagged that `tracks:` might be the wrong input for upload-google-play. Checked action.yml at the pinned commit e738b9dd: `tracks` is correct, and `track` is the deprecated one -- validateTracks warns "'track' is deprecated ... please migrate fully to 'tracks'" -- so the suggested change would have moved this onto a deprecated input. The review's underlying concern is real, though, and worse than reported: when validateTracks receives no track it does not fail, it returns ['production']. A PLAY_TRACK that never reaches the action -- a reordered or skipped resolve step, a GITHUB_ENV that did not carry -- would therefore publish to real users while the workflow stayed green. The value is verified as correct today, but the failure mode is silent and the blast radius is production, so assert it is non-empty before the upload rather than let that default stand. Also corrects the `message` input description, which still promised the note was "shown in Expo" after this PR removed every Expo submission step. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01DDueAWEMzgcryVDyG7yBq2
|
Thanks — the High finding is resolved, and it was worth raising even though the premise turned out inverted.
|
There was a problem hiding this comment.
Code Review: PR #551 — fix(mobile,ci): build releases from the lockfile, drop Expo, cache the native compiles
Recommendation: APPROVE
No Critical or High findings survive this round. The one High finding from the prior review round (tracks: vs track: input key on the r0adkll/upload-google-play step) has been resolved: commit d5e23ea confirms against the pinned action.yml that tracks (plural) is the current input and track is deprecated, and additionally hardens the upload against the real underlying risk — a new "Assert the Google Play track resolved" step fails the run if PLAY_TRACK is empty, rather than letting the action silently default to the production track.
What the PR does
Rebuilds the Android release CI pipeline: builds from npm ci against the committed root lockfile instead of an isolated unpinned-install workspace, replaces eas-cli submit (which routed the AAB and the Play service-account key through Expo's backend) with the directly-pinned r0adkll/upload-google-play action, adds Gradle/ccache build caching, drops x86/x86_64 native targets, and fixes a real latent bug where configure-android-release-signing.js was silently clobbering the release JVM heap/metaspace that with-android-release-build-fixes.js had set. Pure CI/build infrastructure — no app runtime, UI, contract, or i18n surface touched.
Findings
Critical / High / Medium: none.
Info only:
- The "Assert the Google Play track resolved" guard (
.github/workflows/android-release.yml:469-481) is correctly ordered before "Submit build to Google Play" — verified the full step sequence, no gap. - The
org.gradle.jvmargssingle-writer split between the plugin andconfigure-android-release-signing.js:221-224is a legitimate root-cause fix, backed by a new regression test (apps/mobile/__tests__/plugins/with-android-release-build-fixes.test.ts) that fails on the exact clobber that used to happen. - Two newly pinned action SHAs (
gradle/actions/setup-gradle@3f131e8...andr0adkll/upload-google-play@e738b9d...) are correctly-shaped 40-char hex strings; theupload-google-playpin was independently verified against itsaction.ymlper the author's commit message for thetracksinput resolution.
Confirmed via repo-wide grep: zero leftover references to the deleted RELEASE_WORKDIR, prepare-release-workspace.js, verify-release-native-modules.js, android-release-device.js, eas.json, eas-cli, or EXPO_TOKEN.
Subagents
| Agent | Verdict |
|---|---|
| parity-checker | N/A (exempt) — Android-native release/CI machinery, no apps/web equivalent surface |
| i18n-syncer | N/A — no user-facing app strings changed |
| contract-aligner | N/A — no packages/shared/src/types/*/endpoints.ts changes |
| security-reviewer | N/A — no orbit-api changes; net security improvement (EXPO_TOKEN removed, Play service-account key no longer written to disk, action pinned to commit SHA) |
| design-reviewer | N/A — no UI files changed |
Validation
Skipped per CI adaptation — Build / Unit Tests / SonarCloud run as separate required checks on this PR. SonarCloud quality gate has already passed.
What's good
- Fixes a real, previously-invisible bug (silent JVM heap downgrade on every CI release) with a proper regression test.
- Removes a third-party credential hop (Play service-account key no longer travels to Expo's backend) and pins the replacement action to a commit SHA.
- Root-cause fix (rule 1):
npm ciagainst the lockfile replaces an unpinned isolated install and deletes the drift-verification script that install pattern required. - Responded to the prior review's High finding with real verification (checked the pinned
action.yml) rather than a guess, and went further by hardening against the actual failure mode the finding was pointing at (silent default-to-production on an empty track).
…e native compiles Rebuilds the Android release path around one idea: ship the tree the repo tests. Closes #550. ## The release did not build what we test The job copied apps/mobile into an isolated workspace and installed with --package-lock=false, resolving every dependency fresh from the registry at release time. The AAB was therefore compiled from a tree no local install or CI job ever had. #545 pinned the 57 native modules after @expo/ui floated to 57.0.6 and failed R8 eleven minutes into run 29514193887, but that closed only half of it: 87 JS packages still drifted, including @expo/cli, @expo/config-plugins and @expo/prebuild-config -- the tooling that generates the Android project. npm ci at the repo root installs exactly the committed lockfile, so the release tree equals the tested tree by construction. No pins to maintain, no drift guard, no second lockfile. The isolated workspace only ever existed to dodge npm hoisting. Its stated blocker -- @expo/cli crashing on the tsconfig extends chain ("Failed to collapse") -- does not reproduce outside the workspace it was written for: prebuild runs clean in the real monorepo with tsconfig.json untouched, which is what scripts/android-release-apk.js has always done locally. So it goes, and with it prepare-release-workspace.js, verify-release-native-modules.js (tautological once npm ci guarantees the tree), and the .release-mobile indirection through every step. ## Google Play, without the Expo account eas-cli submit is not a local pass-through: at the v21.0.1 tag it uploads the AAB to Expo's GCS bucket and posts the Play service account key to Expo's GraphQL backend, which then makes the androidpublisher call. Every release sent the artifact and the signing credential through a third party, as `npx eas-cli@latest` -- unpinned, fetched fresh each run -- to reach an API the runner can call itself. EAS Submit is free on every tier, so this is not about cost. r0adkll/upload-google-play calls that API directly, pinned to the v1.1.5 commit because this step handles the signing key. The closed->alpha / open->beta mapping that lived only in eas.json becomes an explicit step that runs early, so an unmappable track fails in seconds rather than after the build. It also uploads mapping.txt, so R8-obfuscated Play crash reports deobfuscate. eas.json is then entirely dead -- its build profiles were already vestigial -- as are EXPO_TOKEN and the step that wrote the key to disk. EAS_BUILD_PROFILE stays despite the name: app.config.js uses it as the gate that throws on unresolved production AdMob IDs. ## 31m39s of a 37m13s run was Gradle Measured on run 29517434610, the first green release since #545. Nothing was reused between releases, because android/ is regenerated every run. - Stop building x86/x86_64. Nothing set reactNativeArchitectures, so all four ABIs were built and worklets and reanimated each ran CMake four times. Play serves only the matching split per device and Google's 64-bit policy does not require x86. ChromeOS/ARCVM loses native x86_64 and falls back to Houdini ("OK" rather than "Best"), accepted deliberately; one line to revert. - ccache the NDK compiles. CMake initializes CMAKE_<LANG>_COMPILER_LAUNCHER from the environment, so no generated CMake needs editing. compiler_check=content is required on CI. Keyed on the lockfile, so a dependency change misses. - Cache Gradle task outputs via gradle/actions/setup-gradle. setup-java's `cache: gradle` cached only dependencies and must not run alongside it, so it is removed rather than kept. ## Faults found in the same path - with-android-release-build-fixes.js sets -Xmx6144m/2048m and says it forces them "authoritatively". It did not: configure-android-release-signing.js runs after prebuild and upserted the property back to -Xmx4g/1024m, so every release has minified under a heap the plugin's own comment calls insufficient. The plugin is now the only writer. workers.max=2 is left alone -- it is sized for this heap. - SENTRY_AUTH_TOKEN was missing from the secrets guard while the sourcemap upload runs inside the Gradle task, surfacing ~15 minutes in instead of in one second. - set-app-version.js patched android/app/build.gradle behind an existsSync guard that its only caller can never satisfy: the workflow runs it before prebuild creates android/. Redundant even if reached, since prebuild regenerates those values from the app.json the script already wrote. - scripts/android-release-device.js had zero references repo-wide. ## Verified Locally, against the real tree, with the production AdMob env: `expo config` + the AdMob assertion pass; prebuild finishes clean in place; the generated manifest carries the production AdMob app ID and no sample IDs; expo-modules-autolinking resolves orbit-widget (43 modules) from the real tree, so the widget still reaches the manifest merge. Generated gradle.properties reads, from a real prebuild: org.gradle.jvmargs=-Xmx6144m -XX:MaxMetaspaceSize=2048m -Dfile.encoding=UTF-8 -XX:+HeapDumpOnOutOfMemoryError reactNativeArchitectures=armeabi-v7a,arm64-v8a against armeabi-v7a,arm64-v8a,x86,x86_64 on the same command without buildArchs. The plugin's jvmargs mod is covered by a new test that fails on a heap regression. tsc, expo lint and the mobile suite (244 files / 1589 tests) pass. Not provable without a release: the Gradle build, signing and the Play upload itself. Dispatch to the internal track before an open-track release; the first run is a guaranteed ccache miss and will not be faster. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01DDueAWEMzgcryVDyG7yBq2
…review) Review flagged that `tracks:` might be the wrong input for upload-google-play. Checked action.yml at the pinned commit e738b9dd: `tracks` is correct, and `track` is the deprecated one -- validateTracks warns "'track' is deprecated ... please migrate fully to 'tracks'" -- so the suggested change would have moved this onto a deprecated input. The review's underlying concern is real, though, and worse than reported: when validateTracks receives no track it does not fail, it returns ['production']. A PLAY_TRACK that never reaches the action -- a reordered or skipped resolve step, a GITHUB_ENV that did not carry -- would therefore publish to real users while the workflow stayed green. The value is verified as correct today, but the failure mode is silent and the blast radius is production, so assert it is non-empty before the upload rather than let that default stand. Also corrects the `message` input description, which still promised the note was "shown in Expo" after this PR removed every Expo submission step. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01DDueAWEMzgcryVDyG7yBq2
d5e23ea to
023f065
Compare
|
There was a problem hiding this comment.
Code Review: PR #551 — fix(mobile,ci): build releases from the lockfile, drop Expo, cache the native compiles
Recommendation: APPROVE
Summary
Rebuilds the Android release GitHub Actions pipeline around one idea — install with npm ci at the repo root so the release tree is exactly the tree the repo tests, instead of resolving dependencies fresh in an isolated, lockfile-less workspace. Also swaps eas-cli submit for a direct r0adkll/upload-google-play call (removing Expo/EXPO_TOKEN from the release path and the plaintext service-account-key file), adds ccache + Gradle build-cache caching, restricts release APK architectures to armeabi-v7a/arm64-v8a, fixes a JVM-heap-size regression where a later script silently clobbered the config plugin's -Xmx6144m, and deletes ~384 lines of now-dead workspace/verification scripts. The diff is CI/build infrastructure only — no app UI, no shared contract, no user-facing feature surface — and every changed file traces to a stated, verified reason in the PR body.
Findings
Critical / High / Medium: None.
Info: The "Assert the Google Play track resolved" guard (refusing to upload if PLAY_TRACK failed to resolve, since r0adkll/upload-google-play's validateTracks falls back to the production track on empty input) is already present in this diff — noting only so it isn't mistakenly re-flagged as missing later.
Subagents
| Agent | Verdict |
|---|---|
| parity-checker | N/A-EXEMPT — Android-native release machinery has no apps/web counterpart (confirmed for all 12 changed files) |
| i18n-syncer | N/A — no user-facing strings changed |
| contract-aligner | N/A — no shared types / DTOs touched |
| security-reviewer | N/A — orbit-api not touched |
| design-reviewer | N/A — no apps/* UI files touched |
Validation
Not independently re-executed in this review environment (no network egress for npm ci). Relying on the PR body's self-reported local verification: real expo prebuild output confirming reactNativeArchitectures=armeabi-v7a,arm64-v8a and the raised heap, tsc/expo lint clean, mobile suite (244 files / 1589 tests) green including the new plugin regression test that fails when the heap regression is reintroduced.
What's good
- Root-cause fix, not a patch. The isolated release workspace existed only to dodge a
tsconfig.jsonextends-chain crash the PR proves (with a real prebuild run) does not reproduce outside the workspace invented for it — the whole indirection (RELEASE_WORKDIR,prepare-release-workspace.js,verify-release-native-modules.js) is deleted, not patched around. - Real regression coverage for the exact heap-clobbering failure mode this PR fixes, confirmed to fail on a reintroduced regression.
- Credential custody improved, not just moved — drops
EXPO_TOKENand the plaintext service-account-key file; the new upload action is pinned to a commit SHA with the rationale spelled out. - Clean dead-code removal — grepped the tree for every deleted symbol/file (
EXPO_TOKEN,eas.json,eas-cli,prepare-release-workspace,verify-release-native-modules,android-release-device); no dangling references anywhere. - Comment discipline — every comment is a WHY note with a URL or a concrete prior CI run number, no narration.
Recommendation
Merge as-is. Per the PR's own note, dispatch to the internal track first — the first run is a guaranteed ccache miss and won't demonstrate the speedup.
🤖 Reviewed via /pr-review



Rebuilds the Android release path around one idea: ship the tree the repo tests.
Closes #550. Supersedes #547 and #548 (both closed in favour of this).
The release did not build what we test
The job copied
apps/mobileinto an isolated workspace and installed with--package-lock=false, resolving every dependency fresh from the registry at release time. The AAB was compiled from a tree no local install or CI job ever had.#545 pinned the 57 native modules after
@expo/uifloated to 57.0.6 and failed R8 eleven minutes into a release — but that closed only half of it. 87 JS packages still drifted, including the tooling that generates the Android project:@expo/cli@expo/config-plugins@expo/prebuild-confignpm ciat the root installs exactly the committed lockfile, so the release tree equals the tested tree by construction. No pins to maintain, no drift guard, no second lockfile.The isolated workspace only ever existed to dodge npm hoisting. Its stated blocker —
@expo/clicrashing on the tsconfigextendschain ("Failed to collapse") — does not reproduce outside the workspace it was written for: prebuild runs clean in the real monorepo withtsconfig.jsonuntouched, which is exactly whatscripts/android-release-apk.jshas always done locally. So it goes, and with itprepare-release-workspace.js,verify-release-native-modules.js(tautological oncenpm ciguarantees the tree), and the.release-mobileindirection through every step.Google Play, without the Expo account
eas-cli submitis not a local pass-through. At the v21.0.1 tag it uploads the AAB to Expo's GCS bucket and posts the Play service-account key to Expo's GraphQL backend, which makes theandroidpublishercall for you — asnpx eas-cli@latest, unpinned, fetched fresh each run, to reach an API the runner can call itself.EAS Submit is free on every tier, so this is not about cost — it's credential custody and one less dependency.
r0adkll/upload-google-playcalls that API directly, pinned to the v1.1.5 commit. Theclosed→alpha/open→betamapping that lived only ineas.jsonbecomes an explicit step that runs early, so an unmappable track fails in seconds. It also uploadsmapping.txt, so R8-obfuscated Play crash reports deobfuscate.eas.jsonis then entirely dead (itsbuildprofiles were already vestigial), as areEXPO_TOKENand the step that wrote the key to disk.EAS_BUILD_PROFILEstays despite the name —app.config.jsuses it as the gate that throws on unresolved production AdMob IDs.31m39s of a 37m13s run was Gradle
Measured on run 29517434610, the first green release since #545:
reactNativeArchitectures, so all four ABIs were built and worklets/reanimated each ran CMake 4×. Play serves only the matching split per device; Google's 64-bit policy doesn't require x86. Tradeoff: ChromeOS/ARCVM loses native x86_64 → Houdini fallback ("OK" not "Best"). One line to revert.CMAKE_<LANG>_COMPILER_LAUNCHERfrom the environment, so no generated CMake needs editing.compiler_check=contentis required on CI. Keyed on the lockfile.gradle/actions/setup-gradle.setup-java'scache: gradlecached only dependencies and must not run alongside it, so it's removed.Faults found in the same path, fixed not filed
-Xmx6144m/2048mand claims it forces them "authoritatively" — butconfigure-android-release-signing.jsruns after prebuild and upserted it back to-Xmx4g/1024m. The plugin is now the sole writer.workers.max=2left alone — correctly sized for this heap.SENTRY_AUTH_TOKENmissing from the secrets guard while the sourcemap upload runs inside Gradle (surfaced ~15min in, not 1s).set-app-version.jspatchedbuild.gradlebehind anexistsSyncguard its only caller can never satisfy (runs before prebuild createsandroid/).scripts/android-release-device.js: zero references repo-wide.Net −384 lines.
Verified locally, against the real tree, with production AdMob env
expo config+ the AdMob assertion passexpo-modules-autolinkingresolves orbit-widget (43 modules) from the real tree — the widget still reaches the manifest mergeReal prebuild output:
against
armeabi-v7a,arm64-v8a,x86,x86_64on the same command withoutbuildArchs.Plugin jvmargs covered by a new test that fails on a heap regression (confirmed by reintroducing it).
tsc,expo lint, mobile suite (244 files / 1589 tests) pass.Not provable without a release
The Gradle build, signing, and the Play upload itself. Dispatch to the
internaltrack before an open-track release — the first run is a guaranteed ccache miss and will not be faster.Parity: Android-native release machinery;
apps/webhas no counterpart.🤖 Generated with Claude Code
https://claude.ai/code/session_01DDueAWEMzgcryVDyG7yBq2