add staging ios app build script

This commit is contained in:
Kevin
2026-05-20 10:27:40 +08:00
parent 0d417331fd
commit 81458c7046
14 changed files with 102 additions and 31 deletions

View File

@@ -1,12 +1,27 @@
# 复制为 .env.development / .env.staging / .env.production 后填写(勿提交含密钥的副本)。
# 环境模板在 app-expo/env/{development,staging,production}(勿在根目录放 .env.production
# 本地npm start 会通过 prestart 执行 `use-env development` 生成 .env
# 或手动 `npm run use-env -- staging` / `npm run use-env -- production`。
# CIGitHub Actions 在构建 APK 前会按分支调用 use-envmain → stagingtag → production
#
# 勿在 app-expo 根目录创建 .env.staging / .env.productionRelease 构建时 Expo 会按
# NODE_ENV=production 自动加载 .env.production覆盖 use-env 写入的 .env。
#
# APP_VARIANT / EXPO_PUBLIC_APP_VARIANT控制 Bundle ID 与「关于」页是否显示后端地址
# development / staging → 显示版本 + API 地址production → 仅版本号
#
# 变量在构建时注入;修改后需重新 prebuild/打包客户端。
#
# 助手朗读:无独立 EXPO_PUBLIC_* TTS 开关。会话页顶栏在每轮 WebSocket 中带 `tts_this_turn`
# 服务端是否具备合成能力见 api/.env 中 ENABLE_TTS 等(模板见 api/.env.example
# --- development本地关于页显示版本 + API---
# APP_VARIANT=development
# EXPO_PUBLIC_APP_VARIANT=development
# EXPO_PUBLIC_API_URL=http://127.0.0.1:8000
# EXPO_PUBLIC_WS_URL=ws://127.0.0.1:8000
# --- staging ---
# APP_VARIANT=staging
# EXPO_PUBLIC_APP_VARIANT=staging
EXPO_PUBLIC_API_URL=https://your-api.example.com
EXPO_PUBLIC_WS_URL=wss://your-api.example.com

View File

@@ -1,3 +0,0 @@
# 仅 API/WS 基址TTS 每轮开关由运行时 WS payload 与服务端 ENABLE_TTS 控制(见 api/.env.example
EXPO_PUBLIC_API_URL=https://lifecho.worldsplats.com
EXPO_PUBLIC_WS_URL=wss://lifecho.worldsplats.com

View File

@@ -1,2 +0,0 @@
EXPO_PUBLIC_API_URL=http://1.15.29.57:8000/
EXPO_PUBLIC_WS_URL=ws://1.15.29.57:8000/

View File

@@ -150,10 +150,10 @@ curl -Ls "https://get.maestro.mobile.dev" | bash
| 场景 | 构建命令 | 读取配置 |
|------|---------|---------|
| 普通开发 | `pnpm ios` / `pnpm android` | `.env.development` |
| 普通开发 | `pnpm ios` / `pnpm android` | `env/development` |
| 需要登录态的 E2E | `pnpm ios:e2e` / `pnpm android:e2e` | `.env.e2e`staging backend + E2E 开关) |
正常 staging 构建继续使用 `.env.staging`,不注入 `EXPO_PUBLIC_E2E`
正常 staging 构建继续使用 `env/staging``use-env staging`,不注入 `EXPO_PUBLIC_E2E`
### 现有 flow

5
app-expo/env/development vendored Normal file
View File

@@ -0,0 +1,5 @@
# 本地开发npm start / prestart 默认 use-env development
APP_VARIANT=development
EXPO_PUBLIC_APP_VARIANT=development
EXPO_PUBLIC_API_URL=https://api.b102.org
EXPO_PUBLIC_WS_URL=wss://api.b102.org

6
app-expo/env/production vendored Normal file
View File

@@ -0,0 +1,6 @@
# 正式关于页仅显示版本号iOS Bundle ID org.brighteng.lifecho
# TTS 每轮开关由运行时 WS payload 与服务端 ENABLE_TTS 控制(见 api/.env.example
APP_VARIANT=production
EXPO_PUBLIC_APP_VARIANT=production
EXPO_PUBLIC_API_URL=https://lifecho.worldsplats.com
EXPO_PUBLIC_WS_URL=wss://lifecho.worldsplats.com

5
app-expo/env/staging vendored Normal file
View File

@@ -0,0 +1,5 @@
# 预发:关于页显示版本 + 后端地址iOS Bundle ID org.brighteng.lifecho.staging
APP_VARIANT=staging
EXPO_PUBLIC_APP_VARIANT=staging
EXPO_PUBLIC_API_URL=http://1.15.29.57:8000
EXPO_PUBLIC_WS_URL=ws://1.15.29.57:8000

View File

@@ -33,9 +33,17 @@ esac
ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
cd "$ROOT"
echo "==> Switching to .env.${ENV}"
echo "==> Switching to env/${ENV} → .env"
npm run use-env -- "$ENV"
# Release Archive 时 NODE_ENV=productionExpo 会加载根目录 .env.production 并覆盖 .env
for legacy in .env.production .env.staging .env.development; do
if [[ -f "$legacy" ]]; then
echo "::error::Found legacy $legacy — it overrides use-env on Release builds. Use app-expo/env/ templates only." >&2
exit 1
fi
done
echo "==> expo prebuild --platform ios --clean"
npx expo prebuild --platform ios --clean

View File

@@ -1,22 +1,39 @@
/**
* 将 app-expo/.env.<name> 复制为 .env供 Metro/Expo 读取 EXPO_PUBLIC_*。
* 将 app-expo/env/<name> 复制为 .env供 Metro/Expo 读取 EXPO_PUBLIC_*。
*
* 环境模板放在 env/ 子目录,而非 .env.staging / .env.production
* Release 构建时 Expo 会按 NODE_ENV=production 自动加载根目录 .env.production
* 若与 use-env 写入的 .env 并存production 会覆盖 staging见 @expo/env getEnvFiles
*
* 参数 name → 源文件:
* development → .env.development仓库已提交npm start / prestart 默认)
* staging → .env.staging
* production → .env.production
* development → env/development
* staging → env/staging
* production → env/production
*
* CI.github/workflows/app-expo-deploy.yml 按分支/tag 调用本脚本,与后端 env 策略对齐
* CI.github/workflows/app-expo-deploy.yml 按分支/tag 调用本脚本。
*/
const fs = require('fs');
const path = require('path');
const env = process.argv[2] || 'development';
const src = path.join(__dirname, '..', `.env.${env}`);
const dest = path.join(__dirname, '..', '.env');
if (fs.existsSync(src)) {
fs.copyFileSync(src, dest);
console.log(`Switched to .env.${env}`);
} else {
console.error(`Missing .env.${env}`);
const root = path.join(__dirname, '..');
const src = path.join(root, 'env', env);
const dest = path.join(root, '.env');
/** @deprecated 旧路径;若仍存在会在 Release 构建中覆盖 .env */
const legacyEnvFile = path.join(root, `.env.${env}`);
if (!fs.existsSync(src)) {
console.error(`Missing env/${env} (expected ${src})`);
process.exit(1);
}
fs.copyFileSync(src, dest);
console.log(`Switched to env/${env} → .env`);
if (fs.existsSync(legacyEnvFile)) {
console.warn(
`Warning: legacy ${path.basename(legacyEnvFile)} still exists. ` +
'Remove it or Release builds may load production values over .env.',
);
}

View File

@@ -12,11 +12,21 @@ function resolveAppVariant(): AppVariant {
if (__DEV__) {
return 'development';
}
// Release 包若 env 未写入 APP_VARIANT例如仅复制了 env/staging 到 .env 但变量缺失),
// 仍按 API 是否为 http 推断预发,避免关于页误隐藏后端地址。
const apiUrl = process.env.EXPO_PUBLIC_API_URL ?? '';
if (apiUrl.startsWith('http://')) {
return 'staging';
}
return 'production';
}
/** Shown on About screen for dev/staging builds only. */
/** Shown on About screen for dev/staging builds only (never production Release). */
export function shouldShowAboutBackendUrl(variant: AppVariant = appVariant): boolean {
// Metro / 调试包:始终显示,避免 .env 误用 production variant 时看不到实际 API
if (__DEV__) {
return true;
}
return variant === 'development' || variant === 'staging';
}

View File

@@ -11,8 +11,12 @@ describe('shouldShowAboutBackendUrl', () => {
expect(shouldShowAboutBackendUrl('staging')).toBe(true);
});
it('hides backend URL for production', () => {
expect(shouldShowAboutBackendUrl('production')).toBe(false);
it('hides backend URL for production when not in __DEV__', () => {
if (__DEV__) {
expect(shouldShowAboutBackendUrl('production')).toBe(true);
} else {
expect(shouldShowAboutBackendUrl('production')).toBe(false);
}
});
it('matches config.showAboutBackendUrl for current build', () => {