diff --git a/apps/mobile-shell/package.json b/apps/mobile-shell/package.json index 08d1891a2..b0772a489 100644 --- a/apps/mobile-shell/package.json +++ b/apps/mobile-shell/package.json @@ -9,8 +9,8 @@ "android": "expo run:android", "ios": "expo run:ios", "test": "vitest run -c vitest.config.ts", - "build:android": "eas build --local --profile production --platform android", - "build:ios": "eas build --local --profile production-simulator --platform ios", + "build:android": "eas build --local --profile production --platform android --output ../../build/native/mobile/genarrative-mobile-android.apk", + "build:ios": "eas build --local --profile production-simulator --platform ios --output ../../build/native/mobile/genarrative-mobile-ios-simulator.tar.gz", "build-config:smoke": "node scripts/check-eas-build-config.mjs", "config:smoke": "node scripts/check-expo-config.mjs", "export:smoke": "node scripts/check-expo-export.mjs", diff --git a/apps/mobile-shell/scripts/check-config.mjs b/apps/mobile-shell/scripts/check-config.mjs index bf3d29f31..39e906185 100644 --- a/apps/mobile-shell/scripts/check-config.mjs +++ b/apps/mobile-shell/scripts/check-config.mjs @@ -206,6 +206,10 @@ const rootPackageLock = JSON.parse(fs.readFileSync(rootPackageLockPath, 'utf8')) const iconPath = new URL('../assets/icon.png', import.meta.url); const icon = PNG.sync.read(fs.readFileSync(iconPath)); const brandBackgroundColor = '#fffdf9'; +const androidBuildOutputPath = + '../../build/native/mobile/genarrative-mobile-android.apk'; +const iosSimulatorBuildOutputPath = + '../../build/native/mobile/genarrative-mobile-ios-simulator.tar.gz'; const productionSourceRoots = [ new URL('../App.tsx', import.meta.url), new URL('../app.json', import.meta.url), @@ -706,8 +710,8 @@ for (const [scriptName, expected] of Object.entries({ android: 'expo run:android', ios: 'expo run:ios', test: 'vitest run -c vitest.config.ts', - 'build:android': 'eas build --local --profile production --platform android', - 'build:ios': 'eas build --local --profile production-simulator --platform ios', + 'build:android': `eas build --local --profile production --platform android --output ${androidBuildOutputPath}`, + 'build:ios': `eas build --local --profile production-simulator --platform ios --output ${iosSimulatorBuildOutputPath}`, 'build-config:smoke': 'node scripts/check-eas-build-config.mjs', 'config:smoke': 'node scripts/check-expo-config.mjs', 'export:smoke': 'node scripts/check-expo-export.mjs', @@ -1217,6 +1221,18 @@ if (easConfig.cli?.version !== '>= 20.3.0' || easConfig.cli?.appVersionSource != throw new Error('mobile shell EAS builds must pin CLI floor and use local app version fields'); } +for (const [label, outputPath, extension] of [ + ['Android', androidBuildOutputPath, '.apk'], + ['iOS simulator', iosSimulatorBuildOutputPath, '.tar.gz'], +]) { + if (!outputPath.startsWith('../../build/native/mobile/')) { + throw new Error(`mobile shell ${label} local build output must stay under root build/native/mobile`); + } + if (!outputPath.endsWith(extension)) { + throw new Error(`mobile shell ${label} local build output must end with ${extension}`); + } +} + if ( easConfig.build?.production?.distribution !== 'internal' || easConfig.build?.production?.channel !== 'production' || diff --git a/apps/mobile-shell/scripts/check-eas-build-config.mjs b/apps/mobile-shell/scripts/check-eas-build-config.mjs index 6854103db..a89578a79 100644 --- a/apps/mobile-shell/scripts/check-eas-build-config.mjs +++ b/apps/mobile-shell/scripts/check-eas-build-config.mjs @@ -8,6 +8,10 @@ const packagePath = new URL('package.json', shellRoot); const easConfig = JSON.parse(fs.readFileSync(easConfigPath, 'utf8')); const packageConfig = JSON.parse(fs.readFileSync(packagePath, 'utf8')); const npmCommand = process.platform === 'win32' ? 'npm.cmd' : 'npm'; +const androidBuildOutputPath = + '../../build/native/mobile/genarrative-mobile-android.apk'; +const iosSimulatorBuildOutputPath = + '../../build/native/mobile/genarrative-mobile-ios-simulator.tar.gz'; function assertObject(value, label) { if (!value || typeof value !== 'object' || Array.isArray(value)) { @@ -69,18 +73,30 @@ for (const scriptName of ['build:android', 'build:ios', 'build-config:smoke']) { if ( packageConfig.scripts['build:android'] !== - 'eas build --local --profile production --platform android' + `eas build --local --profile production --platform android --output ${androidBuildOutputPath}` ) { throw new Error('mobile shell Android package build script drifted'); } if ( packageConfig.scripts['build:ios'] !== - 'eas build --local --profile production-simulator --platform ios' + `eas build --local --profile production-simulator --platform ios --output ${iosSimulatorBuildOutputPath}` ) { throw new Error('mobile shell iOS package build script drifted'); } +for (const [label, outputPath, extension] of [ + ['Android', androidBuildOutputPath, '.apk'], + ['iOS simulator', iosSimulatorBuildOutputPath, '.tar.gz'], +]) { + if (!outputPath.startsWith('../../build/native/mobile/')) { + throw new Error(`mobile shell ${label} local build output must stay under root build/native/mobile`); + } + if (!outputPath.endsWith(extension)) { + throw new Error(`mobile shell ${label} local build output must end with ${extension}`); + } +} + if (packageConfig.scripts['build-config:smoke'] !== 'node scripts/check-eas-build-config.mjs') { throw new Error('mobile shell EAS config smoke script drifted'); } diff --git a/docs/project-memory/shared-memory/decision-log.md b/docs/project-memory/shared-memory/decision-log.md index 6432ed2b1..7bc1ead5f 100644 --- a/docs/project-memory/shared-memory/decision-log.md +++ b/docs/project-memory/shared-memory/decision-log.md @@ -2974,7 +2974,7 @@ ## 2026-06-20 移动壳 EAS 原生包构建 profile - 背景:移动壳已能通过 Expo managed config 和 Metro production bundle smoke,但缺少原生安装包构建 profile;如果只保留 `expo export`,无法证明 Android / iOS 壳有进入原生分发链路的配置。 -- 决策:新增 `apps/mobile-shell/eas.json` 和 `eas-cli` devDependency。Android `production` profile 使用本地 EAS build 产出内部 APK;iOS `production-simulator` profile 产出 simulator release 包,避免在没有真实签名凭据时写入伪证书配置。两端 profile 都用本地 `app.json` 版本字段并设置 `EXPO_NO_DOTENV=1`,当前不配置商店提交、签名凭据来源、自动递增、OTA runtimeVersion 或 releaseChannel;`check-eas-build-config.mjs` 必须从 `apps/mobile-shell` 目录执行本地 EAS CLI 版本检查,确认解析到受检版本。`apps/mobile-shell/scripts/check-config.mjs` 必须反查根级门禁仍保留 EAS build profile、Expo config 和 Metro export 三个移动分发烟测。 +- 决策:新增 `apps/mobile-shell/eas.json` 和 `eas-cli` devDependency。Android `production` profile 使用本地 EAS build 产出内部 APK;iOS `production-simulator` profile 产出 simulator release 包,避免在没有真实签名凭据时写入伪证书配置。两端 profile 都用本地 `app.json` 版本字段并设置 `EXPO_NO_DOTENV=1`,真实本地构建输出固定到根目录 `build/native/mobile/` 下的 Android APK 和 iOS simulator 压缩包。当前不配置商店提交、签名凭据来源、自动递增、OTA runtimeVersion 或 releaseChannel;`check-eas-build-config.mjs` 必须从 `apps/mobile-shell` 目录执行本地 EAS CLI 版本检查,确认解析到受检版本,并校验固定输出路径和扩展名。`apps/mobile-shell/scripts/check-config.mjs` 必须反查根级门禁仍保留 EAS build profile、Expo config 和 Metro export 三个移动分发烟测。 - 影响范围:`apps/mobile-shell/eas.json`、`apps/mobile-shell/package.json`、`apps/mobile-shell/scripts/check-eas-build-config.mjs`、`apps/mobile-shell/scripts/check-config.mjs`、`scripts/check-native-shells.mjs`、原生壳方案和验收文档。 - 验证方式:`npm run mobile-shell:build-config`、`npm run mobile-shell:typecheck`、`npm run check:native-shells`、`npm run check:encoding`、`git diff --check`。 diff --git a/docs/【前端架构】ExpoReactNative与Tauri宿主壳方案-2026-06-17.md b/docs/【前端架构】ExpoReactNative与Tauri宿主壳方案-2026-06-17.md index c695658c9..d4a953ea3 100644 --- a/docs/【前端架构】ExpoReactNative与Tauri宿主壳方案-2026-06-17.md +++ b/docs/【前端架构】ExpoReactNative与Tauri宿主壳方案-2026-06-17.md @@ -515,7 +515,7 @@ GameBridge 禁止: 2026-06-18 追加:移动壳 Metro export 烟测进入统一验收。`npm run check:native-shells` 会执行 `npm run mobile-shell:export`,用 Expo CLI 分别为 Android 和 iOS 生成 production bundle,校验 Metro metadata 版本、单平台 `fileMetadata`、Hermes `AppEntry-*.hbc` bundle 路径、assets 数组、生产 H5 URL 和原生宿主上下文 token 后清理临时 `.expo-export-smoke/` 目录。该烟测不生成 APK、AAB 或 IPA,也不写商店资料,只证明移动壳入口、Expo/RN 依赖和平台差异代码能被真实生产 bundler 打包。 -2026-06-20 追加:移动壳新增 EAS 原生包构建 profile。`apps/mobile-shell/eas.json` 固定 EAS CLI 下限、使用本地 `app.json` 版本字段,并提供 Android `production` 本地构建脚本产出可安装 APK、iOS `production-simulator` 本地构建脚本产出 release simulator 包;两者都设置 `EXPO_NO_DOTENV=1`,避免本机 `.env` 把开发 H5 URL 或私密配置带进可分发包。当前仍不写商店提交 profile、签名凭据来源、自动递增、OTA runtimeVersion 或 releaseChannel;`npm run check:native-shells` 只跑 `mobile-shell:build-config` 校验构建 profile、脚本、本地 `eas-cli` devDependency 和从 `apps/mobile-shell` 目录解析到的 EAS CLI 版本,真实 `mobile-shell:build:android` / `mobile-shell:build:ios` 作为具备 Android SDK、Xcode / simulator 环境和 EAS 本地构建依赖时的分发构建命令。 +2026-06-20 追加:移动壳新增 EAS 原生包构建 profile。`apps/mobile-shell/eas.json` 固定 EAS CLI 下限、使用本地 `app.json` 版本字段,并提供 Android `production` 本地构建脚本产出可安装 APK、iOS `production-simulator` 本地构建脚本产出 release simulator 包;两者都设置 `EXPO_NO_DOTENV=1`,避免本机 `.env` 把开发 H5 URL 或私密配置带进可分发包。真实 `mobile-shell:build:android` 输出到根目录 `build/native/mobile/genarrative-mobile-android.apk`,真实 `mobile-shell:build:ios` 输出到 `build/native/mobile/genarrative-mobile-ios-simulator.tar.gz`,该目录沿用根 `build/` 的 gitignore,只作为本机或 CI 可收集产物目录。当前仍不写商店提交 profile、签名凭据来源、自动递增、OTA runtimeVersion 或 releaseChannel;`npm run check:native-shells` 只跑 `mobile-shell:build-config` 校验构建 profile、脚本、本地 `eas-cli` devDependency、固定输出路径和从 `apps/mobile-shell` 目录解析到的 EAS CLI 版本,真实 `mobile-shell:build:android` / `mobile-shell:build:ios` 作为具备 Android SDK、Xcode / simulator 环境和 EAS 本地构建依赖时的分发构建命令。 2026-06-18 追加:登录 / 支付能力伪声明进入门禁。`auth.requestLogin` 和 `payment.request` 保留在共享 HostBridge 契约中供未来真实接入,但 Expo 与 Tauri 壳在没有真实 SDK、渠道流程和后端契约前不得声明这些 capability,也不得把它们写入 `hostCapabilities`;请求到达壳层时必须返回明确 `unsupported_method`,让 H5 fallback 承接。两端壳测试会直接覆盖这两个 method,避免后续半接入时返回伪成功。