Cap Mobile iOS 开发指南:Expo Router、EAS 构建与 OTA 更新全流程
【免费下载链接】CapOpen source Loom alternative. Beautiful, shareable screen recordings.项目地址: https://gitcode.com/GitHub_Trending/cap1/Cap
Cap Mobile 是开源屏幕录制项目 Cap 的 iPhone 客户端,基于 Expo SDK 55 + Expo Router 构建,支持相机/麦克风录制、媒体导入、上传分享、评论与观看分析等功能。本文以 apps/mobile/README.md 为核心骨架,结合仓库中的package.json、app.config.js、eas.json、store.config.json及scripts/下的自动化脚本,系统讲解从本地开发、真机联调、EAS 一次性初始化,到模拟器/内部/生产构建、App Store 提交以及 OTA(Over-The-Air)更新的完整实战流程,读完即可独立跑通一条 iOS 移动端发布流水线。
说明:Cap Mobile 是 iPhone-only 的 Expo 应用,原生工程由 Expo 本地生成或由 EAS 远程生成,不会提交到 Git 仓库;除特别注明外,所有命令都在
apps/mobile目录下执行。
一、项目架构与技术栈
在深入命令之前,先理解这个客户端是如何被组织的,这有助于解释后续各命令为什么这么设计。
1.1 Expo + 连续原生生成(CNG)
仓库 apps/mobile/README.md 明确指出:Cap Mobile 是一个使用Expo Router和Continuous Native Generation(连续原生生成)构建的 iOS Expo 应用,原生工程由本地expo prebuild或 EAS 生成,不进版本库。这一点在 apps/mobile/package.json 中可以得到印证——没有ios/目录的固定工程文件,只有prebuild:ios脚本(expo prebuild --platform ios --no-install)。
核心技术栈(来自 apps/mobile/package.json):
| 技术 | 版本 | 用途 |
|---|---|---|
| expo | ~55.0.29 | 运行时基础框架 |
| expo-router | ~55.0.18 | 文件路由(main为expo-router/entry) |
| react-native | 0.83.10 | 原生渲染引擎 |
| react | 19.2.0 | UI 框架 |
| expo-camera | ~55.0.22 | 相机与麦克风录制 |
| expo-video | ~55.0.20 | 原生播放 |
| expo-dev-client | ~55.0.38 | 开发客户端 |
| expo-updates | ~55.0.27 | OTA 更新 |
| expo-secure-store | ~55.0.17 | 账号密钥安全存储 |
| expo-apple-authentication | ~55.0.16 | Sign in with Apple |
| @shopify/flash-list | 2.0.2 | 高性能视频列表 |
| effect | ^3.18.4 | 类型安全的副作用管理(业务层) |
1.2 路由与功能模块
Expo Router 的文件路由入口位于 apps/mobile/app/_layout.tsx 的根布局:应用启动后先展示加载屏,未登录时渲染SignInPanel登录面板,已登录则进入 Stack 导航,挂载(tabs)(My Caps 列表)、caps/[id](Cap 详情)、analytics、organization-settings、loom-import等页面。根布局还通过AuthProvider与RecordingUploadProvider提供了全局的登录态与上传进度管理,并在屏幕底部常驻RecordingUploadStatus显示上传状态。
三个主 Tab 定义在 apps/mobile/app/(tabs)/_layout.tsx/_layout.tsx):index(My Caps 个人库)、upload(隐藏路由,作为录制/上传入口)、account(账户设置)。个人库页面(apps/mobile/app/(tabs)/index.tsx/index.tsx))使用FlashList渲染 Cap 卡片,并支持文件夹、空间切换、分享、密码设置、保存到相册等操作。
录制页 apps/mobile/app/record.tsx 聚合了CapRecorderView(相机录制原生模块,位于 apps/mobile/modules/cap-recorder)与CapScreenRecorderView(屏幕录制扩展,位于 apps/mobile/modules/cap-screen-recorder),并内置提词器(Teleprompter)。屏幕录制功能通过 App Group 与 Broadcast Extension 实现,其配置见下文app.config.js中的cap-screen-recorder插件。
1.3 两个原生自定义模块
- cap-recorder:Swift 实现的相机录制模块(含
.podspec),提供CapRecorderView及录制事件回调,供record.tsx使用。 - cap-screen-recorder:屏幕录制模块,包含 Swift 源码、Broadcast Extension 的
.plist/.entitlements与app.plugin.js配置插件,用于 iPhone 屏幕录制(注意:当前 iPhone 端以相机录制为主,屏幕录制为扩展能力)。
二、本地开发:四条命令的完整脉络
README 给出了四条本地开发命令,它们的关系可以用一句话概括:决定"要不要启动 Web 后端"与"跑在模拟器还是真机"这两个维度。
2.1 命令矩阵与根级脚本
| 命令 | 后端 | 目标设备 | 用途 |
|---|---|---|---|
bun run dev:mobile | 启动(docker + @cap/web) | iOS 模拟器 | 标准全栈开发 |
bun run dev:mobile:physical | 启动 | 真机 iPhone | 全栈 + 真机联调 |
bun run dev | 不启动(复用已运行后端) | 模拟器 | 仅移动端开发 |
bun run dev:physical | 不启动 | 真机 | 真机 + 已有后端 |
前两个命令定义在仓库根目录的 package.json 中:dev:mobile会先docker:up拉起依赖容器、以trap保证退出时docker:stop,随后通过 Turbo 同时运行@cap/web与@cap/mobile两个 workspace;dev:mobile:physical额外设置CAP_MOBILE_DEVICE=physical,让移动端脚本走真机分支。
后两个命令来自 apps/mobile/package.json 的dev/dev:physical脚本,二者都会先执行scripts/prepare-ios-development.mjs,再根据CAP_MOBILE_DEVICE环境变量选择调用run-ios-simulator.mjs或run-ios-device.mjs:
// apps/mobile/package.json(节选) "dev": "CAP_MOBILE_DISABLE_ASSOCIATED_DOMAINS=1 CAP_MOBILE_BUILD_REACT_NATIVE_FROM_SOURCE=1 sh -c 'node scripts/prepare-ios-development.mjs && if [ \"${CAP_MOBILE_DEVICE:-simulator}\" = \"physical\" ]; then node scripts/run-ios-device.mjs; else node scripts/run-ios-simulator.mjs; fi'", "dev:physical": "CAP_MOBILE_DEVICE=physical bun run dev"2.2 prepare-ios-development.mjs:预构建与依赖安装
scripts/prepare-ios-development.mjs 做两件事:
- 执行
expo prebuild --platform ios --no-install生成(或刷新)原生 iOS 工程; - 比较
ios/Podfile.lock与ios/Pods/Manifest.lock,只要存在差异、或包含React-Core-prebuilt/ReactNativeDependencies、或缺少ExpoAppleAuthentication,就自动执行pod install --project-directory=ios安装 CocoaPods 依赖。
这样每次开发前都能自动保证原生工程与依赖是同步的。脚本支持CAP_MOBILE_DRY_RUN=1干跑模式,只打印命令不执行,便于排查。
2.3 run-ios-simulator.mjs:模拟器选择与容错
scripts/run-ios-simulator.mjs 通过xcrun simctl list devices available --json枚举可用 iPhone 模拟器,选择顺序为:
IOS_SIMULATOR_UDID环境变量指定的设备;IOS_SIMULATOR_DEVICE环境变量指定的名称;- 已 Booted 的模拟器;
- 名称包含 "Pro" 的型号;
- 列表中的第一个可用设备。
选定后会确保模拟器完成启动(simctl boot+bootstatus -b等待),再执行expo run:ios --device <udid>。值得注意的是脚本的容错设计:如果 Expo 启动过程中模拟器意外退出,它会自动重新启动并重试一次。
另外,当CAP_MOBILE_DISABLE_ASSOCIATED_DOMAINS=1且检测到工程中已有 Associated Domains 相关 entitlement 时,会先执行一次expo prebuild --clean重新生成工程,确保开发构建不携带生产关联域名。
2.4 run-ios-device.mjs:真机联调的网络自动发现
真机调试最麻烦的是"iPhone 访问 Mac 上的 API 与 Metro"。仓库的解决方案在 scripts/run-ios-device.mjs 与 scripts/mobile-development-network.mjs 中:
- 优先使用
CAP_MOBILE_DEVICE_API_URL(如果显式指定); - 否则通过
findLanAddress()探测 Mac 的私网 IPv4 地址——按en0、en1、en2优先,自动跳过awdl、bridge、docker、lo、tailscale、utun、vbox、vmnet等虚拟网卡; - 找到后组合为
http://<LAN_IP>:3000作为后端地址,并通过环境变量注入:EXPO_PUBLIC_CAP_WEB_URL=<apiBaseUrl>、REACT_NATIVE_PACKAGER_HOSTNAME=<LAN_IP>(后者让 Metro 也走局域网地址); - 端口可通过
CAP_MOBILE_LOCAL_API_PORT覆盖(默认 3000),局域网 IP 可通过CAP_MOBILE_LAN_IP覆盖。
如果既找不到局域网地址、也没有EXPO_PUBLIC_CAP_WEB_URL,脚本会报错并提示"请显式设置CAP_MOBILE_DEVICE_API_URL"。
三、一次性 EAS 初始化
从本地跑通到云端构建,需要把项目与 Expo 的 EAS(Expo Application Services)项目关联起来。
3.1 project:init 关联 EAS 项目
需要拥有对cap-software-incExpo 组织(或你自己的组织)有权限的 Expo 账号,执行:
bunx eas-cli@21.0.2 project:init该命令会把应用关联到 EAS 项目,并生成一个公开的项目 ID。这个 ID 会被写入 apps/mobile/app.config.js:
const projectId = process.env.EXPO_PROJECT_ID ?? "616ebd7a-e876-4b21-82be-d626028042f6";3.2 项目 ID 的用处
在 apps/mobile/app.config.js 中,projectId 被用于两处:
updates: projectId ? { url: `https://u.expo.dev/${projectId}`, // expo-updates 的 OTA 更新端点 } : undefined, // ... extra: { apiBaseUrl: process.env.EXPO_PUBLIC_CAP_WEB_URL ?? "https://cap.so", eas: projectId ? { projectId } : undefined, },updates.url:OTA 更新的服务端点,指向该项目的 Expo Updates 服务;extra.eas.projectId:运行时(含 expo-dev-client)用于识别所属 EAS 项目。
该项目 ID 是公开标识符,README 特别强调它"是 EAS Build 与 EAS Update 所必需的",提交到仓库是安全的。
3.3 后端地址与关联域名
EXPO_PUBLIC_CAP_WEB_URL:后端 API 基地址,默认生产环境为https://cap.so。需要在 development / preview / production 三个 EAS 环境分别配置,当某个 profile 需要指向非生产后端时(例如预发布环境)覆盖即可;CAP_MOBILE_ASSOCIATED_DOMAINS:逗号分隔的关联域名列表(用于 Universal Links / Associated Domains),仅在显式设置时才会写入 iOS 工程的associatedDomains;CAP_MOBILE_BUILD_REACT_NATIVE_FROM_SOURCE=1:从源码构建 React Native(开发 profile 开启)。
3.4 其他关键配置速览
apps/mobile/app.config.js 中还包含大量值得了解的配置项:
| 配置 | 值/行为 | 说明 |
|---|---|---|
bundleIdentifier | so.cap.mobile | iOS Bundle ID |
appleTeamId | 47B7FCLL43 | 发布团队 ID |
version | 1.0.0 | 应用版本(OTA 更新按此隔离) |
runtimeVersion | policy: "appVersion" | 运行时版本跟随应用版本 |
scheme | cap | 深链 scheme |
platforms | ["ios"] | 仅 iOS(当前无 Android) |
usesNonExemptEncryption | false | 声明无豁免出口加密限制 |
usesAppleSignIn | true | 启用 Sign in with Apple |
supportsTablet | false | 仅手机,不支持 iPad |
userInterfaceStyle | light | 浅色模式 |
experiments.typedRoutes | true | 类型化路由(编译期校验路由) |
权限文案也已配置好:相册读取("Cap imports videos from Photos for upload.")、相册写入("Cap saves downloaded videos to Photos.")、相机/麦克风("Allow Cap to use your camera/microphone while recording videos.")、Face ID("Allow Cap to protect your account key.")。字体资源(NeueMontreal 三字重)、启动屏(apps/mobile/assets/splash-icon.png)与图标(apps/mobile/assets/icon.png)同样在 config 中注册。
3.5 iOS 签名凭据
EAS 负责远程管理 iOS 签名凭据。首次真机(preview)或生产构建时,EAS 会要求被授权的 Apple Developer 账号创建或选择:分发证书(Distribution Certificate)、Provisioning Profile与App Store Connect API Key。也就是说,签名凭据不需要也不应该出现在仓库中。
四、三种构建 profile 与 App Store 提交
apps/mobile/eas.json 定义了三种构建 profile,对应的 npm script 与用途如下:
4.1 development:模拟器开发客户端
bun run build:development # 等价于 bunx eas-cli@21.0.2 build --platform ios --profile developmentprofile 特性:developmentClient: true(开发客户端,可连接 Metro)、distribution: "internal"、simulator: true(纯模拟器构建,无需签名)、channel 为development,并在构建环境注入CAP_MOBILE_BUILD_REACT_NATIVE_FROM_SOURCE=1。
本地开发并不强制需要 EAS 构建——
bun run dev配合本地expo run:ios即可。development 构建适用于需要完整原生依赖打包、或 CI 分发开发构建的场景。
4.2 preview:内部真机构建
bun run build:previewprofile 特性:developmentClient未开启、distribution: "internal"(通过 TestFlight 或 Ad Hoc 分发给内部测试者)、channel 为preview。这是首次需要签名凭据的构建类型,EAS 会引导创建或选择证书与 profile。
4.3 production:生产构建与版本号管理
bun run build:productionprofile 特性:autoIncrement: true(生产构建号由 EAS 自动管理并递增)、credentialsSource: "remote"(凭据由 EAS 远程托管)、distribution: "store"(面向 App Store 分发)、channel 为production。
eas.json中cli.appVersionSource: "remote"也表明应用版本号来源是 EAS 远程管理,而不是仓库内的静态版本。
4.4 提交到 App Store Connect
构建通过发布验证后,提交最新生产构建与store.config.json中的元数据:
bun run submit:production # 等价于 bunx eas-cli@21.0.2 submit --platform ios --profile production --latest提交配置(apps/mobile/eas.json 的submit.production)包含:appleTeamId: "47B7FCLL43"、appName: "Cap"、bundleIdentifier: "so.cap.mobile"、companyName: "Cap Software, Inc."、语言en-US、SKUcap-mobile-ios,以及元数据路径./store.config.json。
apps/mobile/store.config.json 提供了完整的商店文案:应用名 "Cap"、副标题 "Record, share, collaborate"、分类PRODUCTIVITY与PHOTO_AND_VIDEO、关键词(camera recorder、async video、video sharing、analytics 等),并声明automaticRelease: false(构建上传后手动发布)。
五、OTA 更新:preview 先行,production 兜底
5.1 先发 preview 验证
bun run update:preview -- --message "Describe the update" # 等价于 bunx eas-cli@21.0.2 update --channel preview --environment preview --message "..."将当前提交的 JavaScript 与静态资源更新发布到preview通道,供内部测试者先验证。
5.2 验证后发 production
bun run update:production -- --message "Describe the update" # 等价于 bunx eas-cli@21.0.2 update --channel production --environment production --message "..."5.3 更新隔离规则
README 明确指出两条关键约束:
- 按通道隔离:更新只影响对应 channel 的客户端,preview 的验证结果不影响 production 用户;
- 按应用版本隔离:OTA 更新不能跨越原生版本。当原生依赖或 Expo 配置变化时,必须递增 apps/mobile/app.config.js 中的
version,并发布一个新的生产构建,而不是推送一个不兼容的 OTA 更新。
结合runtimeVersion: { policy: "appVersion" }的配置可以理解其机制:expo-updates 以应用版本作为运行时版本标识,因此版本不变时 JS 更新可以热推,版本改变后旧的 OTA 更新包会自动失效,必须走新的原生构建。
六、从开发到上线的完整流水线
综合 README 与仓库脚本,一条完整的移动端发布流水线如下:
# 1. 本地全栈开发(模拟器) bun run dev:mobile # 2. 真机联调(自动发现 Mac 局域网地址) bun run dev:mobile:physical # 3. 一次性关联 EAS 项目(首次) bunx eas-cli@21.0.2 project:init # 4. 构建三种 profile bun run build:development # 模拟器开发客户端 bun run build:preview # 内部真机构建 bun run build:production # 生产构建(构建号由 EAS 自动递增) # 5. OTA 更新(先 preview 验证) bun run update:preview -- --message "Describe the update" bun run update:production -- --message "Describe the update" # 6. 提交 App Store bun run submit:production版本策略小结
| 变更类型 | 操作 |
|---|---|
| JS / 资源 / 业务逻辑改动 | bun run update:preview验证 →bun run update:production发布 OTA |
| 原生依赖 / Expo 配置 / 原生模块改动 | 递增app.config.js中的version→bun run build:production→bun run submit:production |
七、上线质量保障(仓库提供的实测依据)
虽然 README 未展开,但仓库中的 apps/mobile/app-store-release.md 给出了 1.0 版本发布前的完整核验清单,可作为"版本发布"章节的实践佐证:
- 基础验证:Expo Doctor 全部 19 项检查通过;Expo SDK 55 各包版本对齐;TypeScript 校验通过;移动端测试套件通过 34 个文件、214 个测试(对应 apps/mobile/package.json 的
typecheck与test脚本); - 构建验证:clean prebuild、CocoaPods install、生产 JS 导出、Xcode Release 模拟器构建全部通过,构建产物能在 iPhone 17 Pro Max 模拟器上从内嵌生产 bundle 正常启动;
- 合规验证:非豁免加密声明为 false、包含聚合隐私清单、无广告 SDK、不请求 App Tracking Transparency 权限、默认不启用 Associated Domains;
- 功能边界:免费账号最多录制 5 分钟,1.0 版本不在应用内售卖数字功能(不提供外部 Stripe 结账),现有 Cap Pro 订阅在其他平台购买的权益会在账户页被识别;
- 上线前核对命令(在
apps/mobile下执行):
bunx eas-cli@21.0.2 project:info --non-interactive bunx eas-cli@21.0.2 config --platform ios --profile production bunx expo-doctor@latest bun run typecheck bun run test bun run expo prebuild --platform ios --clean --no-install bunx eas-cli@21.0.2 build --platform ios --profile production八、常见问题与排查建议
Q1:真机开发时 iPhone 连不上后端?检查CAP_MOBILE_DEVICE_API_URL是否显式设置;确认 Mac 与 iPhone 在同一局域网;必要时用CAP_MOBILE_LAN_IP指定局域网 IP、用CAP_MOBILE_LOCAL_API_PORT指定端口(默认 3000)。网络发现逻辑见 scripts/mobile-development-network.mjs。
Q2:pod install一直不执行或依赖不同步?开发前会自动比对ios/Podfile.lock与ios/Pods/Manifest.lock;若提示依赖不一致,可删除本地ios/目录后重新执行bun run dev(会重新 prebuild),或用bun run prebuild:ios手动触发。
Q3:OTA 更新发布后用户没收到?先确认发布到的 channel 与客户端构建时的 channel 一致(development / preview / production);再确认没有跨版本发布——原生依赖变更后必须提升app.config.js的version并重新走生产构建。
Q4:找不到合适的模拟器?run-ios-simulator.mjs支持用IOS_SIMULATOR_UDID或IOS_SIMULATOR_DEVICE指定目标设备,否则按"已启动 → 含 Pro → 第一个可用"的顺序自动选择。
Q5:构建失败卡在签名?preview 与 production 构建依赖 EAS 远程签名凭据,需确认 Expo 账号有权限、Apple Developer 账号已完成证书创建授权;本地模拟器开发构建(development profile 且simulator: true)不需要签名。
结语
Cap Mobile 用"Expo Router + CNG + EAS + expo-updates"搭建了一条典型的现代 iOS 应用开发与发布流水线:本地通过 scripts/ 下的脚本自动完成 prebuild、Pod 安装、模拟器选择与真机网络发现;云端通过 apps/mobile/eas.json 的三种 profile 覆盖开发、内部测试与生产分发;日常迭代用 channel 隔离的 OTA 更新热推 JS 与资源,原生变更则回归构建 + 提交。文中所有命令与配置均可在仓库的 apps/mobile 目录下找到对应实现,按本文流程即可从零跑通 Cap Mobile 的完整 iOS 发布闭环。
【免费下载链接】CapOpen source Loom alternative. Beautiful, shareable screen recordings.项目地址: https://gitcode.com/GitHub_Trending/cap1/Cap
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考