From a09129bc5e1ec520d8e5c1cd3ff3641b82d7429b Mon Sep 17 00:00:00 2001 From: k6G52m4Dz75W <74605402+k6G52m4Dz75W@users.noreply.github.com> Date: Mon, 17 Aug 2026 14:33:49 +0800 Subject: [PATCH 1/9] =?UTF-8?q?=EF=BB=BFfix(app-expo):=20run=20dev/eas=20s?= =?UTF-8?q?cripts=20on=20Windows=20via=20cross-env?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The npm scripts used Unix-style APP_VARIANT=development cmd env assignment, which fails on Windows cmd/PowerShell ('APP_VARIANT' is not recognized). Prefix them with cross-env (added as devDependency) so pnpm start / pnpm android work cross-platform. --- packages/app-expo/package.json | 25 +++++++++++++------------ 1 file changed, 13 insertions(+), 12 deletions(-) diff --git a/packages/app-expo/package.json b/packages/app-expo/package.json index 8117f1158..8a7d461d5 100644 --- a/packages/app-expo/package.json +++ b/packages/app-expo/package.json @@ -16,17 +16,17 @@ } }, "scripts": { - "start": "APP_VARIANT=development pnpm run build:reader && APP_VARIANT=development node scripts/configure-native-variant.js && APP_VARIANT=development expo start --dev-client --scheme readany-dev", - "start:clear": "APP_VARIANT=development pnpm run build:reader && APP_VARIANT=development node scripts/configure-native-variant.js && APP_VARIANT=development expo start --dev-client --scheme readany-dev --clear", + "start": "cross-env APP_VARIANT=development pnpm run build:reader && cross-env APP_VARIANT=development node scripts/configure-native-variant.js && cross-env APP_VARIANT=development expo start --dev-client --scheme readany-dev", + "start:clear": "cross-env APP_VARIANT=development pnpm run build:reader && cross-env APP_VARIANT=development node scripts/configure-native-variant.js && cross-env APP_VARIANT=development expo start --dev-client --scheme readany-dev --clear", "start:dev": "pnpm run start", "start:dev:clear": "pnpm run start:clear", "preandroid": "pnpm run build:reader", "android": "expo run:android", - "android:dev": "APP_VARIANT=development pnpm run build:reader && APP_VARIANT=development expo run:android --device", + "android:dev": "cross-env APP_VARIANT=development pnpm run build:reader && cross-env APP_VARIANT=development expo run:android --device", "preios": "pnpm run build:reader && node scripts/configure-native-variant.js", "ios": "expo run:ios", - "ios:dev": "APP_VARIANT=development pnpm run build:reader && APP_VARIANT=development node scripts/configure-native-variant.js && APP_VARIANT=development expo run:ios --device", - "ios:simulator": "APP_VARIANT=development pnpm run build:reader && APP_VARIANT=development node scripts/configure-native-variant.js && APP_VARIANT=development expo run:ios", + "ios:dev": "cross-env APP_VARIANT=development pnpm run build:reader && cross-env APP_VARIANT=development node scripts/configure-native-variant.js && cross-env APP_VARIANT=development expo run:ios --device", + "ios:simulator": "cross-env APP_VARIANT=development pnpm run build:reader && cross-env APP_VARIANT=development node scripts/configure-native-variant.js && cross-env APP_VARIANT=development expo run:ios", "preprebuild": "pnpm run build:reader", "prebuild": "expo prebuild", "build:reader": "node scripts/build-reader.js", @@ -36,13 +36,13 @@ "eas-build-pre-install": "bash scripts/eas-install-ios-build-tools.sh", "eas-build-post-install": "pnpm run build:reader && node scripts/configure-native-variant.js", "eas:device:create": "pnpm exec eas device:create", - "eas:build:android": "APP_VARIANT=production pnpm exec eas build --profile production --platform android", - "eas:build:android:dev": "APP_VARIANT=development pnpm exec eas build --profile development --platform android", - "eas:build:android:preview": "APP_VARIANT=preview pnpm exec eas build --profile preview --platform android", - "eas:build:ios": "APP_VARIANT=production pnpm exec eas build --profile production --platform ios", - "eas:build:ios:dev": "APP_VARIANT=development pnpm exec eas build --profile development --platform ios", - "eas:build:ios:simulator": "APP_VARIANT=development pnpm exec eas build --profile development-simulator --platform ios", - "eas:build:ios:preview": "APP_VARIANT=preview pnpm exec eas build --profile preview --platform ios" + "eas:build:android": "cross-env APP_VARIANT=production pnpm exec eas build --profile production --platform android", + "eas:build:android:dev": "cross-env APP_VARIANT=development pnpm exec eas build --profile development --platform android", + "eas:build:android:preview": "cross-env APP_VARIANT=preview pnpm exec eas build --profile preview --platform android", + "eas:build:ios": "cross-env APP_VARIANT=production pnpm exec eas build --profile production --platform ios", + "eas:build:ios:dev": "cross-env APP_VARIANT=development pnpm exec eas build --profile development --platform ios", + "eas:build:ios:simulator": "cross-env APP_VARIANT=development pnpm exec eas build --profile development-simulator --platform ios", + "eas:build:ios:preview": "cross-env APP_VARIANT=preview pnpm exec eas build --profile preview --platform ios" }, "dependencies": { "@aws-sdk/client-s3": "^3.712.0", @@ -117,6 +117,7 @@ "@types/react": "^19.1.17", "babel-plugin-module-resolver": "^5.0.2", "babel-plugin-transform-import-meta": "^2.3.2", + "cross-env": "^10.1.0", "eas-cli": "^18.11.0", "esbuild": "^0.27.3", "react-native-svg-transformer": "^1.5.3", From cb6abf7e633eceb5373ab04841ac657b5102fed2 Mon Sep 17 00:00:00 2001 From: k6G52m4Dz75W <74605402+k6G52m4Dz75W@users.noreply.github.com> Date: Mon, 17 Aug 2026 14:39:40 +0800 Subject: [PATCH 2/9] =?UTF-8?q?=EF=BB=BFfeat(app-expo):=20Windows=20Androi?= =?UTF-8?q?d=20onboarding=20-=20cross-env=20scripts=20+=20env=20doctor?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - npm scripts used Unix-style APP_VARIANT=... which fails on Windows cmd/PowerShell. Prefix with cross-env (new devDependency) so pnpm expo:start / expo:android work cross-platform. - Add scripts/check-android-env.ps1: a doctor that verifies Node/pnpm, JDK 17+, Android SDK (platform-tools, platforms, build-tools, licenses) and an emulator/device, printing concrete fix steps and exiting non-zero on failure. - Wire pnpm expo:doctor:android (app-expo doctor:android + root alias). - Document the Windows Android setup in README.md / README_CN.md. --- README.md | 29 +++++ README_CN.md | 28 ++++ package.json | 1 + packages/app-expo/package.json | 1 + .../app-expo/scripts/check-android-env.ps1 | 122 ++++++++++++++++++ pnpm-lock.yaml | 18 +++ 6 files changed, 199 insertions(+) create mode 100644 packages/app-expo/scripts/check-android-env.ps1 diff --git a/README.md b/README.md index ff550fd8b..34a6ccd9d 100644 --- a/README.md +++ b/README.md @@ -228,6 +228,35 @@ app. For simulators, use `pnpm expo:ios:simulator` on iOS. For Android, start an Android emulator first, then run `pnpm expo:android`. +#### Windows: Android development setup + +The npm scripts above use `cross-env`, so they work in Windows PowerShell/cmd +out of the box. The first Android build needs a native toolchain: + +1. Install **JDK 17 or 21** ([Eclipse Temurin](https://adoptium.net)) and set + the `JAVA_HOME` user environment variable to its folder. +2. Install **[Android Studio](https://developer.android.com/studio)** and, in + the **SDK Manager**, install: an Android platform (`Android 15/16`, API + 35/36), **Android SDK Build-Tools**, **Android SDK Platform-Tools**, and an + `x86_64` system image for the emulator. +3. Create an AVD in **Device Manager** (or connect a device with USB debugging). +4. Set the `ANDROID_HOME` user environment variable to your SDK path + (default: `%LOCALAPPDATA%\Android\Sdk`). + +Check that everything is in place: + +```powershell +pnpm expo:doctor:android +``` + +Then start Metro and build the dev client (first build downloads Gradle and +native dependencies, so it takes a while): + +```powershell +pnpm expo:start # terminal 1: Metro dev server +pnpm expo:android # terminal 2: build + install the dev client +``` + Mobile app source lives in [`packages/app-expo`](packages/app-expo). ### AI Configuration diff --git a/README_CN.md b/README_CN.md index 92179df04..05c756dd0 100644 --- a/README_CN.md +++ b/README_CN.md @@ -227,6 +227,34 @@ Expo Go。Expo Go 无法加载 ReadAny 当前依赖的原生模块和应用配 模拟器调试时,iOS 使用 `pnpm expo:ios:simulator`;Android 先启动 Android 模拟器,再运行 `pnpm expo:android`。 +#### Windows:Android 开发环境配置 + +上面的 npm 脚本都用了 `cross-env`,在 Windows 的 PowerShell/cmd 下可直接运行。 +首次 Android 构建需要原生工具链: + +1. 安装 **JDK 17 或 21**([Eclipse Temurin](https://adoptium.net)),并把 + `JAVA_HOME` 用户环境变量指向其目录。 +2. 安装 **[Android Studio](https://developer.android.com/studio)**,在 + **SDK Manager** 中安装:Android 平台(`Android 15/16`,API 35/36)、 + **Android SDK Build-Tools**、**Android SDK Platform-Tools**,以及一个 + `x86_64` 模拟器系统镜像。 +3. 在 **Device Manager** 创建 AVD(或用开启 USB 调试的真机)。 +4. 把 `ANDROID_HOME` 用户环境变量设为 SDK 路径(默认 + `%LOCALAPPDATA%\Android\Sdk`)。 + +检查环境是否就绪: + +```powershell +pnpm expo:doctor:android +``` + +然后启动 Metro 并构建 dev client(首次构建会下载 Gradle 和原生依赖,耗时较长): + +```powershell +pnpm expo:start # 终端 1:Metro dev server +pnpm expo:android # 终端 2:构建并安装 dev client +``` + 移动端源码位于 [`packages/app-expo`](packages/app-expo)。 ### AI 配置 diff --git a/package.json b/package.json index e596e59c8..634d2b3ad 100644 --- a/package.json +++ b/package.json @@ -17,6 +17,7 @@ "expo:ios": "pnpm --filter @readany/app-expo ios", "expo:ios:simulator": "pnpm --filter @readany/app-expo ios:simulator", "expo:android": "pnpm --filter @readany/app-expo android", + "expo:doctor:android": "pnpm --filter @readany/app-expo doctor:android", "expo:prebuild": "pnpm --filter @readany/app-expo prebuild", "eas:device:create": "pnpm --filter @readany/app-expo eas:device:create", "eas:build:android": "pnpm --filter @readany/app-expo eas:build:android", diff --git a/packages/app-expo/package.json b/packages/app-expo/package.json index 8a7d461d5..053c78db4 100644 --- a/packages/app-expo/package.json +++ b/packages/app-expo/package.json @@ -31,6 +31,7 @@ "prebuild": "expo prebuild", "build:reader": "node scripts/build-reader.js", "sync:native-variant": "node scripts/configure-native-variant.js", + "doctor:android": "powershell -NoProfile -ExecutionPolicy Bypass -File scripts/check-android-env.ps1", "lint": "biome check .", "test": "vitest run", "eas-build-pre-install": "bash scripts/eas-install-ios-build-tools.sh", diff --git a/packages/app-expo/scripts/check-android-env.ps1 b/packages/app-expo/scripts/check-android-env.ps1 new file mode 100644 index 000000000..26434f193 --- /dev/null +++ b/packages/app-expo/scripts/check-android-env.ps1 @@ -0,0 +1,122 @@ +<# +.SYNOPSIS + ReadAny - Android development environment checker (Windows). + +.DESCRIPTION + Verifies the toolchain needed to build and run the Expo dev client on + Android: Node.js/pnpm, JDK 17+, the Android SDK (platform-tools, platforms, + build-tools, accepted licenses), and an emulator AVD or connected device. + Prints PASS/FAIL per check with concrete fix steps and exits non-zero when + any check fails. + +.EXAMPLE + powershell -NoProfile -ExecutionPolicy Bypass -File scripts/check-android-env.ps1 +#> +[CmdletBinding()] +param() + +$ErrorActionPreference = "SilentlyContinue" + +$script:Pass = 0 +$script:Fail = 0 + +function Write-Result { + param([string]$Name, [bool]$Ok, [string]$Hint) + if ($Ok) { $script:Pass++ } else { $script:Fail++ } + $mark = if ($Ok) { "[ OK ]" } else { "[FAIL]" } + $color = if ($Ok) { "Green" } else { "Red" } + Write-Host ("{0} {1}" -f $mark, $Name) -ForegroundColor $color + if (-not $Ok -and $Hint) { + Write-Host (" > {0}" -f $Hint) -ForegroundColor Yellow + } +} + +Write-Host "ReadAny - Android dev environment check" -ForegroundColor Cyan +Write-Host "" + +# --- Node.js / pnpm -------------------------------------------------- +$node = Get-Command node -ErrorAction SilentlyContinue +Write-Result "Node.js" ($null -ne $node) "Install Node.js 18+ from https://nodejs.org" + +$pnpm = Get-Command pnpm -ErrorAction SilentlyContinue +Write-Result "pnpm" ($null -ne $pnpm) "Install pnpm: npm install -g pnpm" + +# --- JDK ------------------------------------------------------------- +$javaHome = $env:JAVA_HOME +$jdkOk = $false +$jdkHint = "Install JDK 17 or 21 from https://adoptium.net, then set the JAVA_HOME user environment variable to its folder." +if ($javaHome) { + $javaBin = Join-Path $javaHome "bin\java.exe" + if (Test-Path $javaBin) { + $verLine = (& $javaBin -version 2>&1 | Select-Object -First 1) + if ($verLine -match 'version "(\d+)') { + $major = [int]$Matches[1] + $jdkOk = $major -ge 17 + if (-not $jdkOk) { + $jdkHint = "Found JDK $major - Gradle 8.13 needs JDK 17 or 21 (JDK 25 is too new)." + } + } else { + $jdkHint = "Could not read the JDK version from $javaBin." + } + } else { + $jdkHint = "JAVA_HOME is set to `"$javaHome`" but bin\java.exe was not found there." + } +} else { + $jdkHint = "JAVA_HOME is not set. Install JDK 17/21 and set JAVA_HOME (e.g. C:\Program Files\Eclipse Adoptium\jdk-21.x)." +} +Write-Result "JDK (JAVA_HOME=$javaHome)" $jdkOk $jdkHint + +# --- Android SDK ----------------------------------------------------- +$sdk = $env:ANDROID_HOME +if (-not $sdk) { $sdk = $env:ANDROID_SDK_ROOT } +$sdkDefault = Join-Path $env:LOCALAPPDATA "Android\Sdk" +if (-not $sdk) { $sdk = $sdkDefault } +$sdkOk = Test-Path $sdk +Write-Result "Android SDK ($sdk)" $sdkOk ( + "Install Android Studio or the command-line tools. If you used the default location, set the ANDROID_HOME user environment variable to: $sdkDefault" +) + +if ($sdkOk) { + $adb = Join-Path $sdk "platform-tools\adb.exe" + $adbOk = Test-Path $adb + Write-Result "platform-tools (adb)" $adbOk "SDK Manager -> SDK Tools -> check 'Android SDK Platform-Tools'." + + $platforms = @(Get-ChildItem (Join-Path $sdk "platforms") -Directory -ErrorAction SilentlyContinue | Where-Object { $_.Name -match '^android-(3[5-9]|[4-9][0-9])$' }) + Write-Result "Android SDK Platform (API 35+)" ($platforms.Count -gt 0) ( + "SDK Manager -> SDK Platforms -> check 'Android 15/16 (API 35/36)'. Gradle auto-downloads the exact platform if licenses are accepted." + ) + + $buildTools = @(Get-ChildItem (Join-Path $sdk "build-tools") -Directory -ErrorAction SilentlyContinue) + Write-Result "Android SDK Build-Tools" ($buildTools.Count -gt 0) "SDK Manager -> SDK Tools -> check 'Android SDK Build-Tools'." + + Write-Result "SDK licenses accepted" (Test-Path (Join-Path $sdk "licenses")) ( + "Accept them via SDK Manager, or run sdkmanager --licenses." + ) + + $deviceCount = 0 + if ($adbOk) { + $devOut = @(& $adb devices 2>&1) + $deviceCount = @($devOut | Where-Object { $_ -match "\tdevice$" }).Count + } + Write-Result "Android device/emulator connected" ($deviceCount -gt 0) ( + "Start an emulator (Device Manager) or connect a device with USB debugging enabled." + ) + + $emu = Join-Path $sdk "emulator\emulator.exe" + $avdCount = 0 + if (Test-Path $emu) { + $avdOut = @(& $emu -list-avds 2>&1) + $avdCount = @($avdOut | Where-Object { $_ -match '\S' -and $_ -notmatch '^INFO|^WARNING|^ERROR' }).Count + } + Write-Result "Emulator AVD configured" ($avdCount -gt 0) ( + "Android Studio -> Device Manager -> Create device (x86_64 system image, API 35)." + ) +} + +# --- Summary --------------------------------------------------------- +Write-Host "" +$color = if ($script:Fail -eq 0) { "Green" } else { "Red" } +Write-Host ("{0} passed, {1} failed" -f $script:Pass, $script:Fail) -ForegroundColor $color +if ($script:Fail -gt 0) { + exit 1 +} diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 8aed804ec..dc8fcbb40 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -470,6 +470,9 @@ importers: babel-plugin-transform-import-meta: specifier: ^2.3.2 version: 2.3.3(@babel/core@7.29.0) + cross-env: + specifier: ^10.1.0 + version: 10.1.0 eas-cli: specifier: ^18.11.0 version: 18.11.0(@types/node@25.5.2)(typescript@5.9.3) @@ -1466,6 +1469,9 @@ packages: '@emnapi/runtime@1.8.1': resolution: {integrity: sha512-mehfKSMWjjNol8659Z8KxEMrdSJDDot5SXMq00dM8BN4o+CLNXQ0xH2V7EchNHV4RmbZLmmPdEaXZc5H2FXmDg==} + '@epic-web/invariant@1.0.0': + resolution: {integrity: sha512-lrTPqgvfFQtR/eY/qkIzp98OGdNJu0m5ji3q/nJI8v3SXkRKEnWiOxMmbvcSoAIzv/cGiuvRy57k4suKQSAdwA==} + '@esbuild/aix-ppc64@0.25.12': resolution: {integrity: sha512-Hhmwd6CInZ3dwpuGTF8fJG6yoWmsToE+vYgD4nytZVxcu1ulHpUQRAB1UJ8+N1Am3Mz4+xOByoQoSZf4D+CpkA==} engines: {node: '>=18'} @@ -5013,6 +5019,11 @@ packages: crelt@1.0.6: resolution: {integrity: sha512-VQ2MBenTq1fWZUH9DJNGti7kKv6EeAuYr3cLwxUWhIu1baTaXh4Ib5W2CqHVqib4/MqbYGJqiL3Zb8GJZr3l4g==} + cross-env@10.1.0: + resolution: {integrity: sha512-GsYosgnACZTADcmEyJctkJIoqAhHjttw7RsFrVoJNXbsWWqaq6Ym+7kZjq6mS45O0jij6vtiReppKQEtqWy6Dw==} + engines: {node: '>=20'} + hasBin: true + cross-spawn@7.0.6: resolution: {integrity: sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==} engines: {node: '>= 8'} @@ -10783,6 +10794,8 @@ snapshots: tslib: 2.8.1 optional: true + '@epic-web/invariant@1.0.0': {} + '@esbuild/aix-ppc64@0.25.12': optional: true @@ -14876,6 +14889,11 @@ snapshots: crelt@1.0.6: {} + cross-env@10.1.0: + dependencies: + '@epic-web/invariant': 1.0.0 + cross-spawn: 7.0.6 + cross-spawn@7.0.6: dependencies: path-key: 3.1.1 From 91117f8dc551c43aaf5b9ff841556010b0e241d4 Mon Sep 17 00:00:00 2001 From: k6G52m4Dz75W <74605402+k6G52m4Dz75W@users.noreply.github.com> Date: Mon, 17 Aug 2026 15:01:46 +0800 Subject: [PATCH 3/9] =?UTF-8?q?=EF=BB=BFfix(app-expo):=20read=20JDK=20vers?= =?UTF-8?q?ion=20via=20cmd=20/c=20so=20SilentlyContinue=20doesn't=20swallo?= =?UTF-8?q?w=20it?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Under Continue=SilentlyContinue, PowerShell drops the NativeCommandError records produced by 2>&1, so java -version came back empty and the doctor reported "Could not read the JDK version". Merge java's stderr at the OS level with cmd /c ... 2>&1 instead. --- packages/app-expo/scripts/check-android-env.ps1 | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/packages/app-expo/scripts/check-android-env.ps1 b/packages/app-expo/scripts/check-android-env.ps1 index 26434f193..9e8a284ff 100644 --- a/packages/app-expo/scripts/check-android-env.ps1 +++ b/packages/app-expo/scripts/check-android-env.ps1 @@ -48,7 +48,10 @@ $jdkHint = "Install JDK 17 or 21 from https://adoptium.net, then set the JAVA_HO if ($javaHome) { $javaBin = Join-Path $javaHome "bin\java.exe" if (Test-Path $javaBin) { - $verLine = (& $javaBin -version 2>&1 | Select-Object -First 1) + # `cmd /c ... 2>&1` merges java's stderr at the OS level; under + # $ErrorActionPreference=SilentlyContinue PowerShell would otherwise swallow + # the NativeCommandError records produced by `2>&1`. + $verLine = (& cmd /c "`"$javaBin`" -version 2>&1" 2>$null | Select-Object -First 1) if ($verLine -match 'version "(\d+)') { $major = [int]$Matches[1] $jdkOk = $major -ge 17 From 14af9c25b2deeeaa815a750511384f7d8c02e585 Mon Sep 17 00:00:00 2001 From: k6G52m4Dz75W <74605402+k6G52m4Dz75W@users.noreply.github.com> Date: Tue, 25 Aug 2026 22:18:38 +0800 Subject: [PATCH 4/9] chore: trigger PR diff refresh against latest main From 5f41ab9d85f8183d8a69f5d44533c0ce1364c4e4 Mon Sep 17 00:00:00 2001 From: k6G52m4Dz75W <74605402+k6G52m4Dz75W@users.noreply.github.com> Date: Sat, 12 Sep 2026 19:26:35 +0800 Subject: [PATCH 5/9] fix(app-expo): harden the Android env doctor per OCR review MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - JDK check: enforce the full Gradle 8.13 range (17-21) — the old $major -ge 17 silently passed JDK 22+ while the hint text claimed they were too new; normalize legacy 1.8.0_x version strings (they parsed as 'JDK 1'); split the hint for too-old vs too-new. - SDK check: do not report PASS on the default-folder fallback alone. Gradle resolves the SDK from ANDROID_HOME/ANDROID_SDK_ROOT or android/local.properties — a bare default path is exactly the 'SDK location not found' trap, so it now WARNs with the fix steps. - Device/AVD checks: honor the documented 'AVD or connected device' semantics — each half degrades to WARN when the other is satisfied, so a clean setup with the emulator not yet running no longer fails the whole doctor. - Node check: parse node -v and enforce the documented 18+ minimum instead of just checking the command exists. - cross-env: pin ^7.0.3 — v10 requires Node >=20 while the project documents Node 18; v7 fully covers the env-var prefix use case. --- packages/app-expo/package.json | 2 +- .../app-expo/scripts/check-android-env.ps1 | 73 ++++++++++++++----- pnpm-lock.yaml | 18 ++--- 3 files changed, 62 insertions(+), 31 deletions(-) diff --git a/packages/app-expo/package.json b/packages/app-expo/package.json index 053c78db4..2fbfc9ad4 100644 --- a/packages/app-expo/package.json +++ b/packages/app-expo/package.json @@ -118,7 +118,7 @@ "@types/react": "^19.1.17", "babel-plugin-module-resolver": "^5.0.2", "babel-plugin-transform-import-meta": "^2.3.2", - "cross-env": "^10.1.0", + "cross-env": "^7.0.3", "eas-cli": "^18.11.0", "esbuild": "^0.27.3", "react-native-svg-transformer": "^1.5.3", diff --git a/packages/app-expo/scripts/check-android-env.ps1 b/packages/app-expo/scripts/check-android-env.ps1 index 9e8a284ff..d29d3f2e7 100644 --- a/packages/app-expo/scripts/check-android-env.ps1 +++ b/packages/app-expo/scripts/check-android-env.ps1 @@ -4,10 +4,10 @@ .DESCRIPTION Verifies the toolchain needed to build and run the Expo dev client on - Android: Node.js/pnpm, JDK 17+, the Android SDK (platform-tools, platforms, - build-tools, accepted licenses), and an emulator AVD or connected device. - Prints PASS/FAIL per check with concrete fix steps and exits non-zero when - any check fails. + Android: Node.js/pnpm, JDK 17-21, the Android SDK (platform-tools, + platforms, build-tools, accepted licenses), and an emulator AVD or + connected device. Prints PASS/WARN/FAIL per check with concrete fix steps + and exits non-zero when any check fails. .EXAMPLE powershell -NoProfile -ExecutionPolicy Bypass -File scripts/check-android-env.ps1 @@ -19,12 +19,15 @@ $ErrorActionPreference = "SilentlyContinue" $script:Pass = 0 $script:Fail = 0 +$script:Warn = 0 +# -Warn marks a condition that alone should not fail the doctor (the paired +# check being satisfied is enough), so it counts separately from failures. function Write-Result { - param([string]$Name, [bool]$Ok, [string]$Hint) - if ($Ok) { $script:Pass++ } else { $script:Fail++ } - $mark = if ($Ok) { "[ OK ]" } else { "[FAIL]" } - $color = if ($Ok) { "Green" } else { "Red" } + param([string]$Name, [bool]$Ok, [string]$Hint, [switch]$Warn) + if ($Ok) { $script:Pass++ } elseif ($Warn) { $script:Warn++ } else { $script:Fail++ } + $mark = if ($Ok) { "[ OK ]" } elseif ($Warn) { "[WARN]" } else { "[FAIL]" } + $color = if ($Ok) { "Green" } elseif ($Warn) { "Yellow" } else { "Red" } Write-Host ("{0} {1}" -f $mark, $Name) -ForegroundColor $color if (-not $Ok -and $Hint) { Write-Host (" > {0}" -f $Hint) -ForegroundColor Yellow @@ -35,8 +38,18 @@ Write-Host "ReadAny - Android dev environment check" -ForegroundColor Cyan Write-Host "" # --- Node.js / pnpm -------------------------------------------------- +# The doctor exists to catch version problems, so verify the documented +# minimum (Node 18+), not just that the command resolves. $node = Get-Command node -ErrorAction SilentlyContinue -Write-Result "Node.js" ($null -ne $node) "Install Node.js 18+ from https://nodejs.org" +$nodeVer = $null +$nodeOk = $false +if ($node) { + $nodeVer = (& node -v 2>$null | Select-Object -First 1) + $nodeOk = ($nodeVer -match '^v(\d+)') -and ([int]$Matches[1] -ge 18) +} +Write-Result "Node.js" $nodeOk ( + "Node.js 18+ is required (found: $(if ($nodeVer) { $nodeVer.Trim() } else { 'not installed' })). Install from https://nodejs.org" +) $pnpm = Get-Command pnpm -ErrorAction SilentlyContinue Write-Result "pnpm" ($null -ne $pnpm) "Install pnpm: npm install -g pnpm" @@ -54,9 +67,18 @@ if ($javaHome) { $verLine = (& cmd /c "`"$javaBin`" -version 2>&1" 2>$null | Select-Object -First 1) if ($verLine -match 'version "(\d+)') { $major = [int]$Matches[1] - $jdkOk = $major -ge 17 + # Legacy layouts report `java version "1.8.0_401"` — the leading 1 is + # not the major, the minor after "1." is (that's JDK 8). + if ($major -eq 1 -and $verLine -match 'version "1\.(\d+)') { $major = [int]$Matches[1] } + # Enforce the range Gradle 8.13 actually supports; without the upper + # bound a too-new JDK (22+) silently passes and the build fails later. + $jdkOk = $major -ge 17 -and $major -le 21 if (-not $jdkOk) { - $jdkHint = "Found JDK $major - Gradle 8.13 needs JDK 17 or 21 (JDK 25 is too new)." + if ($major -lt 17) { + $jdkHint = "Found JDK $major - Gradle 8.13 needs JDK 17 or 21." + } else { + $jdkHint = "Found JDK $major - Gradle 8.13 needs JDK 17 or 21 (JDK 22+ is too new)." + } } } else { $jdkHint = "Could not read the JDK version from $javaBin." @@ -70,14 +92,24 @@ if ($javaHome) { Write-Result "JDK (JAVA_HOME=$javaHome)" $jdkOk $jdkHint # --- Android SDK ----------------------------------------------------- +# Gradle resolves the SDK from ANDROID_HOME/ANDROID_SDK_ROOT or +# android/local.properties — NOT from Android Studio's default install +# folder. A bare default-path fallback therefore must not report PASS, +# or the doctor passes while `pnpm expo:android` still fails with +# "SDK location not found". $sdk = $env:ANDROID_HOME if (-not $sdk) { $sdk = $env:ANDROID_SDK_ROOT } +$sdkFromEnv = [bool]$sdk $sdkDefault = Join-Path $env:LOCALAPPDATA "Android\Sdk" if (-not $sdk) { $sdk = $sdkDefault } $sdkOk = Test-Path $sdk +$sdkWarn = $sdkOk -and -not $sdkFromEnv Write-Result "Android SDK ($sdk)" $sdkOk ( - "Install Android Studio or the command-line tools. If you used the default location, set the ANDROID_HOME user environment variable to: $sdkDefault" -) + "Install Android Studio or the command-line tools, then set the ANDROID_HOME user environment variable to the SDK folder (e.g. $sdkDefault)." +) -Warn:$sdkWarn +if ($sdkWarn) { + Write-Host " > ANDROID_HOME is not set. If android/local.properties does not pin sdk.dir, Gradle will fail with 'SDK location not found' even though the default folder exists." -ForegroundColor Yellow +} if ($sdkOk) { $adb = Join-Path $sdk "platform-tools\adb.exe" @@ -101,9 +133,6 @@ if ($sdkOk) { $devOut = @(& $adb devices 2>&1) $deviceCount = @($devOut | Where-Object { $_ -match "\tdevice$" }).Count } - Write-Result "Android device/emulator connected" ($deviceCount -gt 0) ( - "Start an emulator (Device Manager) or connect a device with USB debugging enabled." - ) $emu = Join-Path $sdk "emulator\emulator.exe" $avdCount = 0 @@ -111,15 +140,23 @@ if ($sdkOk) { $avdOut = @(& $emu -list-avds 2>&1) $avdCount = @($avdOut | Where-Object { $_ -match '\S' -and $_ -notmatch '^INFO|^WARNING|^ERROR' }).Count } + + # DESCRIPTION promises "an emulator AVD or connected device" — either one + # satisfies the toolchain. A missing half degrades to WARN when its twin + # is present, so a clean setup (AVD created, emulator simply not running + # yet — `expo run:android` starts it) does not fail the whole doctor. + Write-Result "Android device connected" ($deviceCount -gt 0) ( + "Start an emulator (Device Manager) or connect a device with USB debugging enabled." + ) -Warn:($avdCount -gt 0) Write-Result "Emulator AVD configured" ($avdCount -gt 0) ( "Android Studio -> Device Manager -> Create device (x86_64 system image, API 35)." - ) + ) -Warn:($deviceCount -gt 0) } # --- Summary --------------------------------------------------------- Write-Host "" $color = if ($script:Fail -eq 0) { "Green" } else { "Red" } -Write-Host ("{0} passed, {1} failed" -f $script:Pass, $script:Fail) -ForegroundColor $color +Write-Host ("{0} passed, {1} warned, {2} failed" -f $script:Pass, $script:Warn, $script:Fail) -ForegroundColor $color if ($script:Fail -gt 0) { exit 1 } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index dc8fcbb40..a1ed6aa7f 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -471,8 +471,8 @@ importers: specifier: ^2.3.2 version: 2.3.3(@babel/core@7.29.0) cross-env: - specifier: ^10.1.0 - version: 10.1.0 + specifier: ^7.0.3 + version: 7.0.3 eas-cli: specifier: ^18.11.0 version: 18.11.0(@types/node@25.5.2)(typescript@5.9.3) @@ -1469,9 +1469,6 @@ packages: '@emnapi/runtime@1.8.1': resolution: {integrity: sha512-mehfKSMWjjNol8659Z8KxEMrdSJDDot5SXMq00dM8BN4o+CLNXQ0xH2V7EchNHV4RmbZLmmPdEaXZc5H2FXmDg==} - '@epic-web/invariant@1.0.0': - resolution: {integrity: sha512-lrTPqgvfFQtR/eY/qkIzp98OGdNJu0m5ji3q/nJI8v3SXkRKEnWiOxMmbvcSoAIzv/cGiuvRy57k4suKQSAdwA==} - '@esbuild/aix-ppc64@0.25.12': resolution: {integrity: sha512-Hhmwd6CInZ3dwpuGTF8fJG6yoWmsToE+vYgD4nytZVxcu1ulHpUQRAB1UJ8+N1Am3Mz4+xOByoQoSZf4D+CpkA==} engines: {node: '>=18'} @@ -5019,9 +5016,9 @@ packages: crelt@1.0.6: resolution: {integrity: sha512-VQ2MBenTq1fWZUH9DJNGti7kKv6EeAuYr3cLwxUWhIu1baTaXh4Ib5W2CqHVqib4/MqbYGJqiL3Zb8GJZr3l4g==} - cross-env@10.1.0: - resolution: {integrity: sha512-GsYosgnACZTADcmEyJctkJIoqAhHjttw7RsFrVoJNXbsWWqaq6Ym+7kZjq6mS45O0jij6vtiReppKQEtqWy6Dw==} - engines: {node: '>=20'} + cross-env@7.0.3: + resolution: {integrity: sha512-+/HKd6EgcQCJGh2PSjZuUitQBQynKor4wrFbRg4DtAgS1aWO+gU52xpH7M9ScGgXSYmAVS9bIJ8EzuaGw0oNAw==} + engines: {node: '>=10.14', npm: '>=6', yarn: '>=1'} hasBin: true cross-spawn@7.0.6: @@ -10794,8 +10791,6 @@ snapshots: tslib: 2.8.1 optional: true - '@epic-web/invariant@1.0.0': {} - '@esbuild/aix-ppc64@0.25.12': optional: true @@ -14889,9 +14884,8 @@ snapshots: crelt@1.0.6: {} - cross-env@10.1.0: + cross-env@7.0.3: dependencies: - '@epic-web/invariant': 1.0.0 cross-spawn: 7.0.6 cross-spawn@7.0.6: From e72bddf4791b4f2c2f16473a23aecfc1a1038eb7 Mon Sep 17 00:00:00 2001 From: k6G52m4Dz75W <74605402+k6G52m4Dz75W@users.noreply.github.com> Date: Sat, 12 Sep 2026 19:34:04 +0800 Subject: [PATCH 6/9] fix(app-expo): make the SDK WARN branch actually reachable (OCR re-review) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The WARN flag was passed to Write-Result while Ok stayed true, so the elseif branch never ran and a default-path fallback still printed a green pass — silently violating the very rule the comment stated. Pass Ok as (found AND env-sourced) so the three states are distinct: env-sourced = OK, default-path only = WARN, missing = FAIL. Also tint the summary yellow when only warnings remain. Verified by running the doctor with ANDROID_HOME cleared (WARN branch fires, exit stays 0). --- packages/app-expo/scripts/check-android-env.ps1 | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/packages/app-expo/scripts/check-android-env.ps1 b/packages/app-expo/scripts/check-android-env.ps1 index d29d3f2e7..d8988482c 100644 --- a/packages/app-expo/scripts/check-android-env.ps1 +++ b/packages/app-expo/scripts/check-android-env.ps1 @@ -104,7 +104,9 @@ $sdkDefault = Join-Path $env:LOCALAPPDATA "Android\Sdk" if (-not $sdk) { $sdk = $sdkDefault } $sdkOk = Test-Path $sdk $sdkWarn = $sdkOk -and -not $sdkFromEnv -Write-Result "Android SDK ($sdk)" $sdkOk ( +# Pass only when the SDK is BOTH found AND env-sourced; a bare default-path +# hit lands in the WARN branch ($Ok false, -Warn true). +Write-Result "Android SDK ($sdk)" ($sdkOk -and $sdkFromEnv) ( "Install Android Studio or the command-line tools, then set the ANDROID_HOME user environment variable to the SDK folder (e.g. $sdkDefault)." ) -Warn:$sdkWarn if ($sdkWarn) { @@ -155,7 +157,7 @@ if ($sdkOk) { # --- Summary --------------------------------------------------------- Write-Host "" -$color = if ($script:Fail -eq 0) { "Green" } else { "Red" } +$color = if ($script:Fail -gt 0) { "Red" } elseif ($script:Warn -gt 0) { "Yellow" } else { "Green" } Write-Host ("{0} passed, {1} warned, {2} failed" -f $script:Pass, $script:Warn, $script:Fail) -ForegroundColor $color if ($script:Fail -gt 0) { exit 1 From f927a16ef5a12201390e7e66e55cf31cb03d89cc Mon Sep 17 00:00:00 2001 From: k6G52m4Dz75W <74605402+k6G52m4Dz75W@users.noreply.github.com> Date: Sat, 12 Sep 2026 19:36:42 +0800 Subject: [PATCH 7/9] fix(app-expo): two self-review nits in the env doctor MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Node check: use an explicit if instead of -and chaining — PowerShell's -and does not short-circuit, so a failed node -v match would still evaluate [int]$Matches[1] against whatever the JDK regex left behind. - SDK WARN hint: point at the actual fix (set ANDROID_HOME / pin sdk.dir) instead of telling the user to install Android Studio they already have; the install guidance stays for the true FAIL case. Verified: normal env (10 passed), cleared env (WARN branch fires, exit 0), and the JDK 25 too-new branch resolves ok=False. --- packages/app-expo/scripts/check-android-env.ps1 | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/packages/app-expo/scripts/check-android-env.ps1 b/packages/app-expo/scripts/check-android-env.ps1 index d8988482c..3ce8fd06a 100644 --- a/packages/app-expo/scripts/check-android-env.ps1 +++ b/packages/app-expo/scripts/check-android-env.ps1 @@ -45,7 +45,10 @@ $nodeVer = $null $nodeOk = $false if ($node) { $nodeVer = (& node -v 2>$null | Select-Object -First 1) - $nodeOk = ($nodeVer -match '^v(\d+)') -and ([int]$Matches[1] -ge 18) + # Explicit if, not `-and` chaining: PowerShell's -and does not short-circuit, + # so a failed match would still evaluate [int]$Matches[1] against whatever + # the previous regex (JDK check) left behind. + if ($nodeVer -match '^v(\d+)') { $nodeOk = [int]$Matches[1] -ge 18 } } Write-Result "Node.js" $nodeOk ( "Node.js 18+ is required (found: $(if ($nodeVer) { $nodeVer.Trim() } else { 'not installed' })). Install from https://nodejs.org" @@ -105,12 +108,16 @@ if (-not $sdk) { $sdk = $sdkDefault } $sdkOk = Test-Path $sdk $sdkWarn = $sdkOk -and -not $sdkFromEnv # Pass only when the SDK is BOTH found AND env-sourced; a bare default-path -# hit lands in the WARN branch ($Ok false, -Warn true). -Write-Result "Android SDK ($sdk)" ($sdkOk -and $sdkFromEnv) ( +# hit lands in the WARN branch ($Ok false, -Warn true). The WARN hint points +# at the actual fix (set the env var / pin sdk.dir) — not at reinstalling. +$sdkHint = if ($sdkWarn) { + "ANDROID_HOME is not set. Set the ANDROID_HOME user environment variable to the SDK folder (e.g. $sdkDefault), or pin sdk.dir in android/local.properties." +} else { "Install Android Studio or the command-line tools, then set the ANDROID_HOME user environment variable to the SDK folder (e.g. $sdkDefault)." -) -Warn:$sdkWarn +} +Write-Result "Android SDK ($sdk)" ($sdkOk -and $sdkFromEnv) $sdkHint -Warn:$sdkWarn if ($sdkWarn) { - Write-Host " > ANDROID_HOME is not set. If android/local.properties does not pin sdk.dir, Gradle will fail with 'SDK location not found' even though the default folder exists." -ForegroundColor Yellow + Write-Host " > Without one of those, Gradle fails with 'SDK location not found' even though the default folder exists." -ForegroundColor Yellow } if ($sdkOk) { From 230bf3562821c976cd0cd8c3a039f70c0b64f4db Mon Sep 17 00:00:00 2001 From: k6G52m4Dz75W <74605402+k6G52m4Dz75W@users.noreply.github.com> Date: Sat, 12 Sep 2026 19:43:59 +0800 Subject: [PATCH 8/9] fix(app-expo): verify local.properties in the SDK check (OCR round 3) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Round-3 review found the SDK WARN still exited 0 without any verified alternative: android/local.properties was cited as a possible fallback but never read (android/ is generated, nothing writes sdk.dir for a fresh clone). Now the doctor actually reads it — a pinned sdk.dir pointing at an existing SDK passes (un-escaping the properties format: doubled backslashes, \: for the drive colon); a bare default-path fallback with no env var and no pin is a FAIL, since Gradle would fail with 'SDK location not found' anyway; an env var pointing at a missing folder gets its own hint instead of the generic install text. Also correct the Node-check comment: PowerShell's -and DOES short-circuit, so the previous chained form was correct — the explicit if is a clarity choice, not a fix (round-3 review caught my wrong language claim). Verified on this machine across the full matrix: env-sourced PASS, default-path-no-pin FAIL (exit 1), pinned sdk.dir without env PASS (real Gradle escape format), and env-pointing-at-nothing via the branch-specific hint. --- .../app-expo/scripts/check-android-env.ps1 | 52 ++++++++++++------- 1 file changed, 32 insertions(+), 20 deletions(-) diff --git a/packages/app-expo/scripts/check-android-env.ps1 b/packages/app-expo/scripts/check-android-env.ps1 index 3ce8fd06a..ce0d45f4d 100644 --- a/packages/app-expo/scripts/check-android-env.ps1 +++ b/packages/app-expo/scripts/check-android-env.ps1 @@ -45,9 +45,9 @@ $nodeVer = $null $nodeOk = $false if ($node) { $nodeVer = (& node -v 2>$null | Select-Object -First 1) - # Explicit if, not `-and` chaining: PowerShell's -and does not short-circuit, - # so a failed match would still evaluate [int]$Matches[1] against whatever - # the previous regex (JDK check) left behind. + # Explicit if keeps the version comparison self-contained and independent + # of $Matches state. (PowerShell's -and short-circuits, so the previous + # chained form was correct too — this is for clarity, not a fix.) if ($nodeVer -match '^v(\d+)') { $nodeOk = [int]$Matches[1] -ge 18 } } Write-Result "Node.js" $nodeOk ( @@ -95,31 +95,43 @@ if ($javaHome) { Write-Result "JDK (JAVA_HOME=$javaHome)" $jdkOk $jdkHint # --- Android SDK ----------------------------------------------------- -# Gradle resolves the SDK from ANDROID_HOME/ANDROID_SDK_ROOT or -# android/local.properties — NOT from Android Studio's default install -# folder. A bare default-path fallback therefore must not report PASS, -# or the doctor passes while `pnpm expo:android` still fails with -# "SDK location not found". +# Gradle resolves the SDK from ANDROID_HOME/ANDROID_SDK_ROOT, then from +# android/local.properties (sdk.dir — written by Android Studio / Gradle +# on first sync). Everything else must NOT pass: a bare default-folder +# fallback used to print a green PASS while the build failed with +# "SDK location not found" — exactly the trap this doctor exists to catch. $sdk = $env:ANDROID_HOME if (-not $sdk) { $sdk = $env:ANDROID_SDK_ROOT } $sdkFromEnv = [bool]$sdk $sdkDefault = Join-Path $env:LOCALAPPDATA "Android\Sdk" if (-not $sdk) { $sdk = $sdkDefault } $sdkOk = Test-Path $sdk -$sdkWarn = $sdkOk -and -not $sdkFromEnv -# Pass only when the SDK is BOTH found AND env-sourced; a bare default-path -# hit lands in the WARN branch ($Ok false, -Warn true). The WARN hint points -# at the actual fix (set the env var / pin sdk.dir) — not at reinstalling. -$sdkHint = if ($sdkWarn) { - "ANDROID_HOME is not set. Set the ANDROID_HOME user environment variable to the SDK folder (e.g. $sdkDefault), or pin sdk.dir in android/local.properties." -} else { - "Install Android Studio or the command-line tools, then set the ANDROID_HOME user environment variable to the SDK folder (e.g. $sdkDefault)." -} -Write-Result "Android SDK ($sdk)" ($sdkOk -and $sdkFromEnv) $sdkHint -Warn:$sdkWarn -if ($sdkWarn) { - Write-Host " > Without one of those, Gradle fails with 'SDK location not found' even though the default folder exists." -ForegroundColor Yellow + +# Honor a pinned sdk.dir as the alternative source the env vars would be. +$localProps = Join-Path (Split-Path $PSScriptRoot -Parent) "android\local.properties" +$sdkDirPinned = $false +$sdkPinnedPath = $null +if (Test-Path $localProps) { + $pinnedLine = Get-Content $localProps | Where-Object { $_ -match '^sdk\.dir=(.+)$' } | Select-Object -First 1 + if ($pinnedLine) { + # properties-file escapes: backslashes doubled, colon written as \: + $sdkPinnedPath = $pinnedLine.Substring(8).Replace('\\', '\').Replace('\:', ':') + $sdkDirPinned = Test-Path $sdkPinnedPath + } } +$sdkPass = ($sdkOk -and $sdkFromEnv) -or $sdkDirPinned +$sdkDisplay = if (-not $sdkFromEnv -and $sdkDirPinned) { $sdkPinnedPath } else { $sdk } +$sdkHint = + if ($sdkOk -and -not $sdkFromEnv -and -not $sdkDirPinned) { + "Found the SDK only at the default location, but Gradle does not look there. Set the ANDROID_HOME user environment variable to $sdkDefault (or let Android Studio write android/local.properties)." + } elseif ($sdkFromEnv -and -not $sdkOk) { + "ANDROID_HOME/ANDROID_SDK_ROOT is set to `"$sdk`" but that folder does not exist. Update the variable (e.g. to $sdkDefault) or reinstall the SDK there." + } else { + "Install Android Studio or the command-line tools, then set the ANDROID_HOME user environment variable to the SDK folder (e.g. $sdkDefault)." + } +Write-Result "Android SDK ($sdkDisplay)" $sdkPass $sdkHint + if ($sdkOk) { $adb = Join-Path $sdk "platform-tools\adb.exe" $adbOk = Test-Path $adb From d4caba20e2f6d200e05a3a0433812624a2855b2a Mon Sep 17 00:00:00 2001 From: k6G52m4Dz75W <74605402+k6G52m4Dz75W@users.noreply.github.com> Date: Sat, 12 Sep 2026 20:41:35 +0800 Subject: [PATCH 9/9] fix(app-expo): reject a JRE-shaped JAVA_HOME in the env doctor The check ran java -version and looked no further, so a JAVA_HOME pointing at a JRE passed the doctor while Gradle fails on the missing compiler (JRE installs ship bin\java.exe without bin\javac.exe). Require bin\javac.exe next to it, with a hint that names the JRE-vs-JDK distinction. --- packages/app-expo/scripts/check-android-env.ps1 | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/packages/app-expo/scripts/check-android-env.ps1 b/packages/app-expo/scripts/check-android-env.ps1 index ce0d45f4d..cdbac2ec2 100644 --- a/packages/app-expo/scripts/check-android-env.ps1 +++ b/packages/app-expo/scripts/check-android-env.ps1 @@ -83,6 +83,12 @@ if ($javaHome) { $jdkHint = "Found JDK $major - Gradle 8.13 needs JDK 17 or 21 (JDK 22+ is too new)." } } + # A JRE passes the java -version check but Gradle needs the compiler: + # JRE installs ship bin\java.exe without bin\javac.exe. + if ($jdkOk -and -not (Test-Path (Join-Path $javaHome "bin\javac.exe"))) { + $jdkOk = $false + $jdkHint = "JAVA_HOME points to a JRE (no bin\javac.exe) - Gradle needs a full JDK. Install JDK 17 or 21 from https://adoptium.net (choose JDK, not JRE)." + } } else { $jdkHint = "Could not read the JDK version from $javaBin." }