Files
Folo/apps/mobile/scripts/verify-native-runtime-version.impl.ts
DIYgod 28ab52296e fix(mobile): embed the real OTA runtime version in Android builds
EAS generates android/ with `expo prebuild`, which sets NODE_ENV to
"development" when it is unset. The app config picked the 0.0.0-dev
runtime from NODE_ENV, so every Android binary since the React Native
0.79 upgrade embedded expo_runtime_version 0.0.0-dev, and ota.folo.is
rejects its update checks. iOS kept the real runtime because eas-cli
syncs its committed Expo.plist outside of prebuild.

Decide the development runtime from the EAS build profile and fall back
to NODE_ENV only without one (expo start, local prebuild). The
eas-build-post-install hook now fails any build whose native runtime
differs from the expected one, and a test evaluates every eas.json
profile the way prebuild does.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-16 16:21:11 +08:00

47 lines
1.8 KiB
TypeScript

import { readFile } from "node:fs/promises"
import { join } from "pathe"
import { isDevelopmentRuntime, resolveRuntimeVersion } from "../app.config.base"
const nativeRuntimeVersionSources = {
android: {
path: "android/app/src/main/res/values/strings.xml",
pattern: /<string name="expo_runtime_version">([^<]*)<\/string>/,
},
ios: {
path: "ios/Folo/Supporting/Expo.plist",
pattern: /<key>EXUpdatesRuntimeVersion<\/key>\s*<string>([^<]*)<\/string>/,
},
}
export async function verifyNativeRuntimeVersion(input: {
projectDir: string
platform: string | undefined
profile: string | undefined
nodeEnv: string | undefined
}) {
if (input.platform !== "android" && input.platform !== "ios") {
throw new Error(`Expected EAS_BUILD_PLATFORM to be android or ios, got ${input.platform}`)
}
const source = nativeRuntimeVersionSources[input.platform]
const nativeConfig = await readFile(join(input.projectDir, source.path), "utf8")
const nativeRuntimeVersion = nativeConfig.match(source.pattern)?.[1] ?? null
const packageJsonPath = join(input.projectDir, "package.json")
const { version } = JSON.parse(await readFile(packageJsonPath, "utf8")) as { version: string }
// No OTA_RUNTIME_VERSION here: OTA releases take their runtime from the binary version.
const expectedRuntimeVersion = resolveRuntimeVersion({
isDevelopment: isDevelopmentRuntime({ profile: input.profile, nodeEnv: input.nodeEnv }),
packageVersion: version,
})
if (nativeRuntimeVersion !== expectedRuntimeVersion) {
throw new Error(
`${source.path} has runtime version ${nativeRuntimeVersion ?? "(missing)"}, expected ${expectedRuntimeVersion} for build profile ${input.profile ?? "(unset)"}. This binary would never match the OTA updates published for its version.`,
)
}
return nativeRuntimeVersion
}