补齐移动壳原生包构建配置

移动壳新增 EAS Android 和 iOS 构建 profile

移动壳增加原生包构建配置 smoke 门禁

原生壳总验收接入移动 EAS 配置检查

宿主壳文档同步移动原生包构建口径
This commit is contained in:
2026-06-20 06:11:53 +08:00
parent 8f29ddf8e0
commit c4193e27db
11 changed files with 7893 additions and 15 deletions
+28
View File
@@ -0,0 +1,28 @@
{
"cli": {
"version": ">= 20.3.0",
"appVersionSource": "local"
},
"build": {
"production": {
"distribution": "internal",
"channel": "production",
"android": {
"buildType": "apk"
},
"env": {
"EXPO_NO_DOTENV": "1"
}
},
"production-simulator": {
"distribution": "internal",
"channel": "production",
"ios": {
"simulator": true
},
"env": {
"EXPO_NO_DOTENV": "1"
}
}
}
}
+3
View File
@@ -9,6 +9,9 @@
"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-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",
"typecheck": "tsc -p tsconfig.json --noEmit && node scripts/check-config.mjs"
+52 -1
View File
@@ -89,6 +89,8 @@ const sharedContractPath = new URL(
const sharedContractSource = fs.readFileSync(sharedContractPath, 'utf8');
const packagePath = new URL('../package.json', import.meta.url);
const packageConfig = JSON.parse(fs.readFileSync(packagePath, 'utf8'));
const easConfigPath = new URL('../eas.json', import.meta.url);
const easConfig = JSON.parse(fs.readFileSync(easConfigPath, 'utf8'));
const rootPackagePath = new URL('../../../package.json', import.meta.url);
const rootPackageConfig = JSON.parse(fs.readFileSync(rootPackagePath, 'utf8'));
const rootPackageLockPath = new URL('../../../package-lock.json', import.meta.url);
@@ -438,7 +440,7 @@ function collectProductionSourceFiles(entry) {
if (!productionFileExtensions.has(extension)) {
return [];
}
if (path.includes('.test.') || path.endsWith('/scripts/check-config.mjs')) {
if (path.includes('.test.') || path.includes('/scripts/check-')) {
return [];
}
@@ -524,6 +526,9 @@ 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-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',
typecheck: 'tsc -p tsconfig.json --noEmit && node scripts/check-config.mjs',
@@ -535,6 +540,9 @@ for (const [scriptName, expected] of Object.entries({
'mobile-shell:dev': 'npm --prefix apps/mobile-shell run dev',
'mobile-shell:typecheck': 'npm --prefix apps/mobile-shell run typecheck',
'mobile-shell:test': 'npm --prefix apps/mobile-shell run test',
'mobile-shell:build-config': 'npm --prefix apps/mobile-shell run build-config:smoke',
'mobile-shell:build:android': 'npm --prefix apps/mobile-shell run build:android',
'mobile-shell:build:ios': 'npm --prefix apps/mobile-shell run build:ios',
'mobile-shell:config': 'npm --prefix apps/mobile-shell run config:smoke',
'mobile-shell:export': 'npm --prefix apps/mobile-shell run export:smoke',
})) {
@@ -618,6 +626,15 @@ for (const [dependency, expected] of Object.entries({
);
}
assertPackageDependencyVersion(
rootPackageConfig,
'root package',
'devDependencies',
'eas-cli',
'^20.3.0',
);
assertPackageLockVersion('eas-cli', '20.3.0');
for (const [dependency, expected] of Object.entries({
typescript: '5.8.3',
vitest: '0.34.6',
@@ -983,6 +1000,40 @@ if ('releaseChannel' in appConfig || 'channel' in appConfig) {
throw new Error('mobile shell must not configure an app release channel without a real release process');
}
if (easConfig.cli?.version !== '>= 20.3.0' || easConfig.cli?.appVersionSource !== 'local') {
throw new Error('mobile shell EAS builds must pin CLI floor and use local app version fields');
}
if (
easConfig.build?.production?.distribution !== 'internal' ||
easConfig.build?.production?.channel !== 'production' ||
easConfig.build?.production?.android?.buildType !== 'apk' ||
easConfig.build?.production?.env?.EXPO_NO_DOTENV !== '1'
) {
throw new Error('mobile shell EAS Android production profile must build an internal APK without local dotenv');
}
if (
easConfig.build?.['production-simulator']?.distribution !== 'internal' ||
easConfig.build?.['production-simulator']?.channel !== 'production' ||
easConfig.build?.['production-simulator']?.ios?.simulator !== true ||
easConfig.build?.['production-simulator']?.env?.EXPO_NO_DOTENV !== '1'
) {
throw new Error('mobile shell EAS iOS production smoke profile must build an internal simulator package without local dotenv');
}
for (const [profileName, profile] of Object.entries(easConfig.build ?? {})) {
for (const blockedKey of ['credentialsSource', 'autoIncrement', 'submit', 'releaseChannel']) {
if (blockedKey in profile) {
throw new Error(`mobile shell EAS ${profileName} profile must not configure ${blockedKey}`);
}
}
}
if ('submit' in easConfig) {
throw new Error('mobile shell EAS config must not include store submit profiles yet');
}
assertSameList(
appConfig.ios?.associatedDomains ?? [],
[sharedPublicWebAssociatedDomain],
@@ -0,0 +1,104 @@
import fs from 'node:fs';
const shellRoot = new URL('../', import.meta.url);
const easConfigPath = new URL('eas.json', shellRoot);
const packagePath = new URL('package.json', shellRoot);
const easConfig = JSON.parse(fs.readFileSync(easConfigPath, 'utf8'));
const packageConfig = JSON.parse(fs.readFileSync(packagePath, 'utf8'));
function assertObject(value, label) {
if (!value || typeof value !== 'object' || Array.isArray(value)) {
throw new Error(`${label} must be an object`);
}
}
function assertNoKeys(value, blockedKeys, label) {
for (const key of blockedKeys) {
if (key in value) {
throw new Error(`${label} must not configure ${key}`);
}
}
}
assertObject(easConfig.cli, 'EAS cli config');
assertObject(easConfig.build, 'EAS build config');
if (easConfig.cli.version !== '>= 20.3.0') {
throw new Error('mobile shell EAS CLI version gate must stay pinned to the checked devDependency floor');
}
if (easConfig.cli.appVersionSource !== 'local') {
throw new Error('mobile shell EAS builds must use local app.json version fields');
}
for (const scriptName of ['build:android', 'build:ios', 'build-config:smoke']) {
if (typeof packageConfig.scripts?.[scriptName] !== 'string') {
throw new Error(`mobile shell package missing ${scriptName} script`);
}
}
if (
packageConfig.scripts['build:android'] !==
'eas build --local --profile production --platform android'
) {
throw new Error('mobile shell Android package build script drifted');
}
if (
packageConfig.scripts['build:ios'] !==
'eas build --local --profile production-simulator --platform ios'
) {
throw new Error('mobile shell iOS package build script drifted');
}
if (packageConfig.scripts['build-config:smoke'] !== 'node scripts/check-eas-build-config.mjs') {
throw new Error('mobile shell EAS config smoke script drifted');
}
const production = easConfig.build.production;
const productionSimulator = easConfig.build['production-simulator'];
assertObject(production, 'EAS Android production profile');
assertObject(productionSimulator, 'EAS iOS simulator production profile');
if (production.distribution !== 'internal' || production.channel !== 'production') {
throw new Error('mobile shell Android production profile must build an internal production package');
}
if (production.android?.buildType !== 'apk') {
throw new Error('mobile shell Android production profile must produce an installable APK');
}
if (production.env?.EXPO_NO_DOTENV !== '1') {
throw new Error('mobile shell Android production profile must ignore local dotenv files');
}
if (
productionSimulator.distribution !== 'internal' ||
productionSimulator.channel !== 'production'
) {
throw new Error('mobile shell iOS simulator production profile must stay internal production');
}
if (productionSimulator.ios?.simulator !== true) {
throw new Error('mobile shell iOS production smoke profile must target simulator builds until signing is configured');
}
if (productionSimulator.env?.EXPO_NO_DOTENV !== '1') {
throw new Error('mobile shell iOS simulator production profile must ignore local dotenv files');
}
for (const [profileName, profile] of Object.entries(easConfig.build)) {
assertObject(profile, `EAS ${profileName} profile`);
assertNoKeys(
profile,
['credentialsSource', 'autoIncrement', 'submit', 'releaseChannel'],
`EAS ${profileName} profile`,
);
}
if ('submit' in easConfig) {
throw new Error('mobile shell EAS config must not include store submit profiles yet');
}
console.log('[mobile-shell:eas-build-config] OK');
@@ -2873,6 +2873,13 @@
- 影响范围:`apps/desktop-shell/src-tauri/src/app.rs``apps/desktop-shell/src-tauri/src/shell/menu.rs``apps/desktop-shell/src-tauri/src/shell/tray.rs``apps/desktop-shell/scripts/check-config.mjs`、宿主壳方案文档。
- 验证方式:`cargo test --manifest-path apps/desktop-shell/src-tauri/Cargo.toml``npm run desktop-shell:typecheck``npm run check:native-shells``npm run check:encoding``git diff --check`
## 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 产出内部 APKiOS `production-simulator` profile 产出 simulator release 包,避免在没有真实签名凭据时写入伪证书配置。两端 profile 都用本地 `app.json` 版本字段并设置 `EXPO_NO_DOTENV=1`,当前不配置商店提交、签名凭据来源、自动递增、OTA runtimeVersion 或 releaseChannel。
- 影响范围:`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`
## 2026-06-20 移动壳 ShellApp HostBridge 事件注入必须可执行覆盖
- 背景:Expo 移动壳声明 `host.events``app.lifecycle``network.statusChanged``navigation.canGoBack`,但 ShellApp 真实 AppState、Network 和 WebView 返回栈注入链路需要和扫码链路一样有可执行测试覆盖,不能只靠字符串门禁。
@@ -216,7 +216,7 @@ npm run build
npm run check:native-shells
```
该命令会覆盖 H5 HostBridge 关键测试、微信 / Expo / Tauri 三端桥接层文件结构门禁、完整相对路径文档反查、微信 capability 到真实 WebView / 支付 / 分享页面流程和测试清单的映射门禁、H5 HostBridge 事件订阅双能力门控反查、H5 `navigation.canGoBack` 消费 hook 与直达二级页返回锚点测试、移动端和桌面端单端源码清单门禁、Expo 壳 typecheck / test / config smoke / Metro export smoke、Tauri 壳 typecheck / cargo test、桌面壳 release `--no-bundle` 构建烟测,以及可分发壳与 H5 HostBridge 真实调用链的临时替身词扫描,确认 Expo managed config、移动端 iOS / Android production bundle、打包 H5 资产、Tauri release 入口、H5 页面内导航保留完整原生宿主上下文和 H5 HostBridge 真实调用链没有漂移;扫描范围包含微信小程序壳生产 `.js`、Tauri `Info.plist`、共享 HostBridge 契约、H5 native transport,并自动覆盖已接入真实宿主能力 facade 的 H5 生产调用链文件,但不扫描 Expo export、Tauri `target/`、Cargo / Metro 缓存或 release 构建产物。移动壳配置检查必须反查文本 / 文档 / 图片 / 音频导入边界都来自共享 HostBridge 契约。登录与支付外链跳转必须保持在该调用链扫描内,`src/services/authService.ts``src/services/payment/paymentRedirect.ts` 是必扫文件;`AuthGate` 的登录成功、退出登录、身份边界刷新和登录状态异常重试都必须通过 `app.reloadWebView` 优先路径,并由 `src/components/auth/AuthGate.test.tsx` 进入该门禁。壳源码和配置继续严格禁止 mock / fake / placeholder / stub / TODO / FIXME / 占位 / 模拟 / 伪造 / 未实现 / 临时;H5 业务调用链允许正常表单 `placeholder` 属性、业务占位图文案和真实兼容 / 故障语义中的“未实现”“临时”表述,但仍禁止 mock / fake / stub / TODO / FIXME / 模拟 / 伪造等替身痕迹。
该命令会覆盖 H5 HostBridge 关键测试、微信 / Expo / Tauri 三端桥接层文件结构门禁、完整相对路径文档反查、微信 capability 到真实 WebView / 支付 / 分享页面流程和测试清单的映射门禁、H5 HostBridge 事件订阅双能力门控反查、H5 `navigation.canGoBack` 消费 hook 与直达二级页返回锚点测试、移动端和桌面端单端源码清单门禁、Expo 壳 typecheck / test / EAS build config smoke / config smoke / Metro export smoke、Tauri 壳 typecheck / cargo test、桌面壳 release `--no-bundle` 构建烟测,以及可分发壳与 H5 HostBridge 真实调用链的临时替身词扫描,确认 Expo managed config、移动端 EAS 原生包构建 profile、移动端 iOS / Android production bundle、打包 H5 资产、Tauri release 入口、H5 页面内导航保留完整原生宿主上下文和 H5 HostBridge 真实调用链没有漂移;扫描范围包含微信小程序壳生产 `.js`、Tauri `Info.plist`、共享 HostBridge 契约、H5 native transport,并自动覆盖已接入真实宿主能力 facade 的 H5 生产调用链文件,但不扫描 Expo export、Tauri `target/`、Cargo / Metro 缓存或 release 构建产物。移动壳配置检查必须反查 EAS 生产 profile、文本 / 文档 / 图片 / 音频导入边界都来自共享 HostBridge 契约。登录与支付外链跳转必须保持在该调用链扫描内,`src/services/authService.ts``src/services/payment/paymentRedirect.ts` 是必扫文件;`AuthGate` 的登录成功、退出登录、身份边界刷新和登录状态异常重试都必须通过 `app.reloadWebView` 优先路径,并由 `src/components/auth/AuthGate.test.tsx` 进入该门禁。壳源码和配置继续严格禁止 mock / fake / placeholder / stub / TODO / FIXME / 占位 / 模拟 / 伪造 / 未实现 / 临时;H5 业务调用链允许正常表单 `placeholder` 属性、业务占位图文案和真实兼容 / 故障语义中的“未实现”“临时”表述,但仍禁止 mock / fake / stub / TODO / FIXME / 模拟 / 伪造等替身痕迹。
创作 Agent 原生壳文档导入优先走 `file.importDocument`,旧壳只声明 `file.importText` 时才回退文本导入;相关变更必须让根级和单端门禁覆盖共享 method、capability profile、文档 MIME / 5 MiB 上限、读取前 size 校验,以及 H5 base64 转 `File` 后继续走后端文档解析的链路。
创作 Agent 参考图上传在原生壳声明 `file.importImage` 时必须优先走宿主图片导入,并把 H5 base64 转 `File` 后继续交给既有 `onReferenceImageChange` 校验链路;用户取消原生选择不应再连带弹出浏览器文件输入,普通浏览器、小程序和未声明能力的裁剪壳才使用原隐藏文件输入。
创作 Agent 轻输入 composer 的参考图按钮在原生壳声明 `file.importImage` 时必须优先走宿主图片导入;移动壳声明 `file.captureImage` 时才显示拍摄参考图入口,并把宿主图片同样转为 `File` 后复用 `readPuzzleReferenceImageAsDataUrl` 的类型、大小、压缩和预览链路。
@@ -487,6 +487,8 @@ GameBridge 禁止:
2026-06-18 追加:移动壳 Metro export 烟测进入统一验收。`npm run check:native-shells` 会执行 `npm run mobile-shell:export`,用 Expo CLI 分别为 Android 和 iOS 生成 production bundle,校验 Metro metadata 与 `AppEntry` bundle 已产出后清理临时 `.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 和脚本,真实 `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,避免后续半接入时返回伪成功。
2026-06-18 追加:微信 / Expo / Tauri 三端壳的生产源码和配置禁止出现 mock / fake / placeholder / stub / TODO / FIXME / 占位 / 模拟 / 伪造 / 未实现 / 临时 等脚手架或替身词;测试文件仍可使用 `vi.mock` 或等价测试替身。`apps/mobile-shell/scripts/check-config.mjs``apps/desktop-shell/scripts/check-config.mjs` 会扫描各自生产入口、配置和壳实现;根级 `npm run check:native-shells` 会统一扫描 `miniprogram``apps/mobile-shell``apps/desktop-shell`、H5 HostBridge transport 和共享 HostBridge 契约生产源码,防止把临时替身或占位文案带进可分发壳。
@@ -544,7 +546,7 @@ GameBridge 禁止:
- AI sandbox 无法调用 HostBridge,也无法读取 H5 登录态。
- Tauri release 包不允许任意远端页面调用桌面命令。
- Expo WebView 外链离开主站后不保留完整 HostBridge。
- 根级验收入口 `npm run check:native-shells` 必须同时覆盖 H5 HostBridge 关键路径、宿主上下文 query 契约、微信小程序页面路由与来源 query 反查、三端桥接层文件结构门禁、Expo 壳 typecheck / test / config smoke / Metro export smoke、Tauri 壳 typecheck / cargo test、桌面 release `--no-bundle` 构建烟测,以及可分发壳与 H5 HostBridge 真实调用链的临时替身词扫描。该扫描范围必须包含微信小程序壳生产 `.js`、共享 HostBridge 契约、H5 native transport,并自动覆盖已接入真实宿主能力 facade 的 H5 生产调用链文件。
- 根级验收入口 `npm run check:native-shells` 必须同时覆盖 H5 HostBridge 关键路径、宿主上下文 query 契约、微信小程序页面路由与来源 query 反查、三端桥接层文件结构门禁、Expo 壳 typecheck / test / EAS build config smoke / config smoke / Metro export smoke、Tauri 壳 typecheck / cargo test、桌面 release `--no-bundle` 构建烟测,以及可分发壳与 H5 HostBridge 真实调用链的临时替身词扫描。该扫描范围必须包含微信小程序壳生产 `.js`、共享 HostBridge 契约、H5 native transport,并自动覆盖已接入真实宿主能力 facade 的 H5 生产调用链文件。
## 参考资料
@@ -93,7 +93,7 @@ Tauri 桌面壳的文件能力边界统一在 `apps/desktop-shell/src-tauri/src/
2. `authService` 保留原导出,但内部委托 HostBridge,避免一次性改动 AuthGate。
3. 分享弹窗、分享目标同步、九宫切图、微信小程序支付和订阅授权改用 HostBridge 通用接口;旧微信命名服务只作为兼容导出。
4. 后续新增 `native_app` adapter 时只补桥接实现和测试,业务层不新增平台分叉;主 App 启动会触发一次 `host.getRuntime` 回读并订阅能力变化,避免裁剪壳或旧入口 URL 缺少 `hostCapabilities` 时长期隐藏真实可用能力。
5. 每次新增或调整 native capability、HostBridge event 或宿主上下文 query 后,必须先更新 `packages/shared/src/contracts/hostBridge.ts` 中对应微信 / Expo / Tauri capability profile、事件白名单和 query 契约,再运行 `npm run check:native-shells`,统一覆盖 H5 HostBridge 关键测试、三端桥接层文件结构门禁、微信小程序页面路由与 H5 常量反查、Expo 壳 typecheck / test / config smoke / Metro export smoke、Tauri 壳 typecheck / cargo test、桌面 release `--no-bundle` 构建烟测,以及可分发壳与 H5 HostBridge 真实调用链的临时替身词扫描。移动壳 Metro export smoke 必须读取 iOS / Android production bundle,确认最终 bundle 使用共享 HostBridge 契约中的生产 H5 URL,且没有混入本机开发 H5 URL。移动壳和桌面壳文档中的主状态段落能力清单与完整能力清单都必须反查共享 capability profile,避免同一文档内部漂移;桌面壳未声明的共享 request method 必须由 Rust 测试从 `HOST_BRIDGE_METHODS - capabilities()` 自动派生为 `unsupported_method` 覆盖清单,当前包括 `auth.requestLogin``payment.request``file.captureImage``scanner.scanQrCode``haptics.impact`,不得伪造成功;Expo 移动壳未声明的共享 request method 必须由 `HOST_BRIDGE_METHODS - HOST_BRIDGE_EXPO_MOBILE_IOS_CAPABILITIES` 自动派生测试覆盖,确保未接 SDK / 渠道的 method 返回明确 `unsupported_method`H5 facade 除 `host.getRuntime` 真实回读外,所有 native_app request 能力都必须通过 `canUseNativeHostCapability(...)` 统一门控,根级门禁会从共享 `HOST_BRIDGE_METHODS` 自动派生需检查清单;排查单端问题时再单独运行 `npm run mobile-shell:typecheck``npm run mobile-shell:test``npm run mobile-shell:config``npm run mobile-shell:export``npm run desktop-shell:typecheck``npm run desktop-shell:test``npm run desktop-shell:build -- --no-bundle`
5. 每次新增或调整 native capability、HostBridge event 或宿主上下文 query 后,必须先更新 `packages/shared/src/contracts/hostBridge.ts` 中对应微信 / Expo / Tauri capability profile、事件白名单和 query 契约,再运行 `npm run check:native-shells`,统一覆盖 H5 HostBridge 关键测试、三端桥接层文件结构门禁、微信小程序页面路由与 H5 常量反查、Expo 壳 typecheck / test / EAS build config smoke / config smoke / Metro export smoke、Tauri 壳 typecheck / cargo test、桌面 release `--no-bundle` 构建烟测,以及可分发壳与 H5 HostBridge 真实调用链的临时替身词扫描。移动壳 EAS build config smoke 必须确认 Android 生产 profile 能产出内部 APKiOS 生产 smoke profile 只产出 simulator 包,且不写商店提交、签名凭据来源、OTA channel 或本机 dotenv;移动壳 Metro export smoke 必须读取 iOS / Android production bundle,确认最终 bundle 使用共享 HostBridge 契约中的生产 H5 URL,且没有混入本机开发 H5 URL。移动壳和桌面壳文档中的主状态段落能力清单与完整能力清单都必须反查共享 capability profile,避免同一文档内部漂移;桌面壳未声明的共享 request method 必须由 Rust 测试从 `HOST_BRIDGE_METHODS - capabilities()` 自动派生为 `unsupported_method` 覆盖清单,当前包括 `auth.requestLogin``payment.request``file.captureImage``scanner.scanQrCode``haptics.impact`,不得伪造成功;Expo 移动壳未声明的共享 request method 必须由 `HOST_BRIDGE_METHODS - HOST_BRIDGE_EXPO_MOBILE_IOS_CAPABILITIES` 自动派生测试覆盖,确保未接 SDK / 渠道的 method 返回明确 `unsupported_method`H5 facade 除 `host.getRuntime` 真实回读外,所有 native_app request 能力都必须通过 `canUseNativeHostCapability(...)` 统一门控,根级门禁会从共享 `HOST_BRIDGE_METHODS` 自动派生需检查清单;排查单端问题时再单独运行 `npm run mobile-shell:typecheck``npm run mobile-shell:test``npm run mobile-shell:build-config``npm run mobile-shell:config``npm run mobile-shell:export``npm run desktop-shell:typecheck``npm run desktop-shell:test``npm run desktop-shell:build -- --no-bundle`
## 验收
@@ -102,7 +102,7 @@ Tauri 桌面壳的文件能力边界统一在 `apps/desktop-shell/src-tauri/src/
- 小程序支付仍跳转 `/pages/wechat-pay/index` 并保留支付结果 hash 回灌确认。
- 小程序订阅授权仍跳转 `/pages/subscribe-message/index`,且返回不阻断生成主链路。
- 普通浏览器分享、H5 支付和 Native 二维码支付不受影响。
- 原生壳统一验收入口 `npm run check:native-shells` 通过,能力白名单、微信 / Expo / Tauri 共享 capability profile、HostBridge event 白名单、宿主上下文 query 契约、壳 runtime 回包、URL `hostCapabilities`、H5 fallback、微信小程序 `app.json.pages``protocol.js` 页面 URL、H5 小程序页面常量、H5 订阅授权页面常量、WebView 分享入口、分享目标消息类型、WebView source query、微信请求头来源标记、H5 路由保留字段、生产 / 开发 H5 与 API HTTPS 域名格式、三端桥接层结构、两端壳实现、Expo managed config、移动端 production bundle 主站 URL、桌面 release 构建入口,以及可分发壳与 H5 HostBridge 真实调用链的临时替身词扫描没有漂移;扫描范围包含微信小程序壳生产 `.js`、Tauri `Info.plist`、共享 HostBridge 契约、H5 native transport,并自动覆盖已接入真实宿主能力 facade 的 H5 生产调用链文件。H5 业务文件允许正常表单 `placeholder` 属性、业务占位图文案和真实兼容 / 故障语义中的“未实现”“临时”表述,但不得出现 mock / fake / stub / TODO / FIXME / 模拟 / 伪造等替身痕迹。
- 原生壳统一验收入口 `npm run check:native-shells` 通过,能力白名单、微信 / Expo / Tauri 共享 capability profile、HostBridge event 白名单、宿主上下文 query 契约、壳 runtime 回包、URL `hostCapabilities`、H5 fallback、微信小程序 `app.json.pages``protocol.js` 页面 URL、H5 小程序页面常量、H5 订阅授权页面常量、WebView 分享入口、分享目标消息类型、WebView source query、微信请求头来源标记、H5 路由保留字段、生产 / 开发 H5 与 API HTTPS 域名格式、三端桥接层结构、两端壳实现、Expo managed config、移动端 EAS build profile、移动端 production bundle 主站 URL、桌面 release 构建入口,以及可分发壳与 H5 HostBridge 真实调用链的临时替身词扫描没有漂移;扫描范围包含微信小程序壳生产 `.js`、Tauri `Info.plist`、共享 HostBridge 契约、H5 native transport,并自动覆盖已接入真实宿主能力 facade 的 H5 生产调用链文件。H5 业务文件允许正常表单 `placeholder` 属性、业务占位图文案和真实兼容 / 故障语义中的“未实现”“临时”表述,但不得出现 mock / fake / stub / TODO / FIXME / 模拟 / 伪造等替身痕迹。
## 后续
+7684 -10
View File
File diff suppressed because it is too large Load Diff
+4
View File
@@ -12,6 +12,9 @@
"mobile-shell:dev": "npm --prefix apps/mobile-shell run dev",
"mobile-shell:typecheck": "npm --prefix apps/mobile-shell run typecheck",
"mobile-shell:test": "npm --prefix apps/mobile-shell run test",
"mobile-shell:build-config": "npm --prefix apps/mobile-shell run build-config:smoke",
"mobile-shell:build:android": "npm --prefix apps/mobile-shell run build:android",
"mobile-shell:build:ios": "npm --prefix apps/mobile-shell run build:ios",
"mobile-shell:config": "npm --prefix apps/mobile-shell run config:smoke",
"mobile-shell:export": "npm --prefix apps/mobile-shell run export:smoke",
"desktop-shell:dev": "npm --prefix apps/desktop-shell run dev",
@@ -122,6 +125,7 @@
"@typescript-eslint/parser": "^6.21.0",
"@vitest/coverage-v8": "^0.34.6",
"autoprefixer": "^10.4.21",
"eas-cli": "^20.3.0",
"eslint": "^8.57.1",
"eslint-config-prettier": "^9.1.0",
"eslint-plugin-react-hooks": "^4.6.2",
+5
View File
@@ -513,6 +513,11 @@ const steps = [
command: npmCommand,
args: ['run', 'mobile-shell:test'],
},
{
label: 'mobile-shell-eas-build-config-smoke',
command: npmCommand,
args: ['run', 'mobile-shell:build-config'],
},
{
label: 'mobile-shell-expo-config-smoke',
command: npmCommand,