Files
orca/mobile/scripts/prepare-android-release.mjs
T
Kelvin Amoaba 67dc1ad8a2 feat(mobile): ship Android as dev.orca.app on Google Play and GitHub with one Play-signed build (#42295)
* feat(mobile): ship Android as dev.orca.app and check updates by install source

Android becomes dev.orca.app, the package already live on Google Play; iOS keeps
com.stably.orca.mobile. The move screens and the "Orca (old)" label stay keyed to the old package,
so they never show in the new app.

The app asks Android who installed it. A Play install gets its updates from Play, so it runs no
GitHub check, hides "Check for updates" and points the update wall at Google Play. Any other
install checks GitHub for android-v releases; the old package keeps mobile-android-v.

* ci(mobile): release dev.orca.app to Play and publish Google's signed APK

An android-v tag (or a manual run) builds the AAB with the upload key and sends it to Play's
internal track. It then downloads the universal APK Google signed for that versionCode, fails unless
its V3.0 signer is Google's app signing key, and publishes it as the android-v GitHub pre-release.
Play and GitHub installs are the same signed app. A manual run with publish_github_release off only
validates the bundle with Play.

The orca.dev APK bump job is gone: the site builds its link from mobile-android-v, so it stays on
the last old-app APK, which sends people to the new app.
2026-11-15 00:36:13 +00:00

98 lines
3.1 KiB
JavaScript

#!/usr/bin/env node
import fs from 'node:fs'
import path from 'node:path'
import process from 'node:process'
const mobileRoot = path.resolve(import.meta.dirname, '..')
const appConfigPath = process.env.MOBILE_APP_CONFIG_PATH || path.join(mobileRoot, 'app.json')
const androidTagRefPrefix = 'refs/tags/android-v'
const semverPattern = /^\d+\.\d+\.\d+$/
function input(name) {
return (process.env[name] || '').trim()
}
function truthy(value) {
return ['1', 'true', 'yes', 'on'].includes(value.trim().toLowerCase())
}
function fail(message) {
console.error(message)
process.exit(1)
}
function validateSemver(version, name) {
if (!semverPattern.test(version)) {
fail(`${name} must use x.y.z format`)
}
}
function writeOutput(name, value) {
const outputPath = process.env.GITHUB_OUTPUT
if (!outputPath) {
return
}
fs.appendFileSync(outputPath, `${name}=${value}\n`)
}
const config = JSON.parse(fs.readFileSync(appConfigPath, 'utf8'))
const expo = config.expo || fail('app.json is missing expo config')
const android = expo.android || fail('app.json is missing expo.android config')
const currentVersion = String(expo.version || '').trim()
validateSemver(currentVersion, 'Current mobile version')
const currentVersionCode = Number(android.versionCode)
if (!Number.isSafeInteger(currentVersionCode) || currentVersionCode <= 0) {
fail('Current Android versionCode must be a positive integer')
}
const githubRef = input('GITHUB_REF')
const tagVersion = githubRef.startsWith(androidTagRefPrefix)
? githubRef.slice(androidTagRefPrefix.length)
: ''
const requestedVersion = input('MOBILE_ANDROID_RELEASE_VERSION')
const bumpPatch = truthy(input('MOBILE_ANDROID_BUMP_PATCH_VERSION'))
if (tagVersion) {
validateSemver(tagVersion, 'Android release tag version')
}
if (requestedVersion) {
validateSemver(requestedVersion, 'MOBILE_ANDROID_RELEASE_VERSION')
}
if (bumpPatch) {
fail('MOBILE_ANDROID_BUMP_PATCH_VERSION is no longer supported; commit mobile/app.json first')
}
if (tagVersion && tagVersion !== currentVersion) {
fail('Android release tag version must match the committed mobile app version')
}
if (requestedVersion && requestedVersion !== currentVersion) {
fail('MOBILE_ANDROID_RELEASE_VERSION must match the committed mobile app version')
}
const requestedVersionCode = input('MOBILE_ANDROID_VERSION_CODE')
const bumpVersionCode = truthy(input('MOBILE_ANDROID_BUMP_VERSION_CODE'))
if (requestedVersionCode || bumpVersionCode) {
// Android rejects lower versionCode; bump dev builds too or shipped APKs block local installs.
fail('Android versionCode changes must be committed in mobile/app.json before release')
}
const tag = `android-v${currentVersion}`
const publishRelease =
githubRef.startsWith(androidTagRefPrefix) || truthy(input('MOBILE_ANDROID_PUBLISH_RELEASE'))
writeOutput('version', currentVersion)
writeOutput('android_version_code', String(currentVersionCode))
writeOutput('tag', tag)
writeOutput('publish_release', publishRelease ? 'true' : 'false')
console.log(`Prepared Orca Mobile Android ${currentVersion} (${currentVersionCode})`)
console.log(`Release tag: ${tag}`)
console.log(`Publish GitHub Release: ${publishRelease ? 'yes' : 'no'}`)