Folo(follow)移动端预览构建安装指南:基于 EAS Local Build 与 devicectl 的真机 iOS 安装流程
导读
本文以 Folo(follow)开源仓库中的 Agent 技能文档 installing-mobile-preview-builds 为主线,系统讲解如何在 macOS 上为 apps/mobile 移动应用构建一套全新的 preview(预览)本地 iOS 包,并将其安装到已连接的 iPhone 上用于类生产环境测试。读完本文,你将掌握从环境校验、设备选择、EAS 本地构建、IPA 解包安装到应用启动与故障处理的一整条可复现命令行流程,并能理解该流程背后与仓库中 eas.json、Expo 应用配置及资源预构建插件的对应关系。
技能定位与适用场景
.agents/skills/installing-mobile-preview-builds/SKILL.md 是仓库为 AI Agent(如 Claude/Copilot 类编码助手)提供的一个可调用技能文件,其 frontmatter 明确声明了它的行为边界:
description:当用户要求"在已连接的 iPhone 上安装 preview/internal iOS 构建包用于类生产测试"时触发;disable-model-invocation: true:禁止模型在未经用户许可的情况下自动调用;allowed-tools:仅允许Bash、Read、Glob、Grep四类工具;argument-hint:可选的设备标识参数[device-udid-or-name]。
因此该技能适用于如下场景:不借助 TestFlight、不需要等待 EAS 云端队列,而是在开发机上直接产出 preview 渠道的本地安装包,并通过 Apple 的 devicectl(新一代命令行设备管理工具)完成真机安装与启动,用于发布前的最终体验验证。
流程总览:一条命令链贯穿的五个阶段
整条流程可以归纳为五个阶段,对应技能文档中的五个 Workflow 步骤:
- 环境与登录校验:确认仓库结构、
pnpm/xcrun/xcodebuild/eas-cli可用性以及 EAS 登录态; - 目标设备解析:枚举已配对设备,按参数精确匹配 > 首台已配对 iPhone 的顺序选出安装目标;
- 触发本地 preview 构建:使用 EAS Local Build 产出
.ipa; - 本地安装到设备:解包 IPA 后通过
devicectl device install app安装; - 启动验证:通过
devicectl device process launch尝试拉起应用并确认激活状态。
下面逐一展开。
第一步:仓库与工具链校验
1.1 校验仓库布局与包管理器
该技能要求所有命令从仓库根目录开始执行,并确认 apps/mobile 目录存在。这是本仓库 pnpm workspace 布局所决定的:apps/mobile(包名 @follow/mobile,见 apps/mobile/package.json)是 Expo React Native 应用的主目录,其内部还嵌套了 web-app 与 web-app/html-renderer 两个 workspace 子包(见 pnpm-workspace.yaml)。
1.2 校验系统工具
流程要求在 PATH 中存在以下四个命令:
| 命令 | 作用 | 缺失时的典型表现 |
|---|---|---|
pnpm |
workspace 包管理与依赖安装、执行 EAS CLI | command not found: pnpm |
xcrun |
调用 Xcode 命令行工具(含 devicectl) |
提示需要安装 Xcode Command Line Tools |
xcodebuild |
参与 iOS 原生编译链路 | 需要安装完整 Xcode 及 iOS 平台支持 |
eas-cli |
Expo Application Services 命令行入口,通过 pnpm dlx eas-cli 临时拉取 |
网络不通时无法拉取 |
1.3 校验 EAS 登录态
进入 apps/mobile 后执行:
cd apps/mobile
pnpm dlx eas-cli whoami
该命令会返回当前登录的 EAS 账号。构建过程需要将产物与 EAS 项目绑定,本项目在 apps/mobile/app.config.base.ts 中通过 extra.eas.projectId(a6335b14-fb84-45aa-ba80-6f6ab8926920)与 owner: "follow" 完成关联,未登录或登录账号无权访问该项目时会在此步失败。
第二步:解析目标设备
2.1 枚举已配对设备
xcrun devicectl list devices
该命令输出当前 Mac 上所有经信任配对的 Apple 设备及其 UDID、名称、状态。devicectl 是 Xcode 15+ 引入的、用来取代老式 ios-deploy/ideviceinstaller 的官方设备控制工具,本文后续的安装与启动都依赖它。
2.2 设备选择优先级
技能文档规定按以下顺序确定 <device-id>:
- 若通过
$ARGUMENTS(即调用技能时的可选参数)传入了 UDID 或精确设备名,且与列表中的设备恰好唯一匹配,则使用该设备; - 否则自动选择第一台已配对的 iPhone(注意不是 iPad,因为 preview 构建目标是 iOS 真机)。
第三步:触发本地 preview iOS 构建
3.1 构建命令
mkdir -p .context/preview-install
cd apps/mobile
pnpm dlx eas-cli build -p ios --profile preview --non-interactive --local --output=./build-preview.ipa
cd ../..
cp apps/mobile/build-preview.ipa .context/preview-install/folo-preview.ipa
逐个拆解关键参数:
-p ios:只构建 iOS 平台;--profile preview:选用 EAS 构建配置中名为preview的 profile;--non-interactive:禁止交互式提问,适合自动化/Agent 调用;--local:在本地机器上执行完整构建(本地模式会调用本机 Xcode 工具链,不占用 EAS 云端队列),这也是"preview 真机内部测试"能快速完成的关键;--output=./build-preview.ipa:将最终产物写到apps/mobile/build-preview.ipa;- 最后复制到仓库根的
.context/preview-install/folo-preview.ipa统一归档。
3.2 preview profile 的底层配置
preview profile 在 apps/mobile/eas.json 中定义:
"preview": {
"distribution": "internal",
"channel": "preview",
"env": {
"PROFILE": "preview"
}
}
"distribution": "internal":产物为内部分发包,不面向 App Store,无需上传审核,可直接安装到注册设备;"channel": "preview":对应 Expo Updates 的 OTA 更新通道(项目更新服务地址为https://ota.folo.is/manifest,见 apps/mobile/app.config.base.ts),让该包可通过expo-updates接收 preview 通道的远程更新;PROFILE=preview环境变量会传导到 Expo 配置解析层。
3.3 PROFILE 环境变量如何影响 App 配置
apps/mobile/app.config.base.ts 中通过 process.env.PROFILE 驱动多处差异化配置,这是理解 preview 包与正式包差异的关键:
- 渠道映射(
channelNameMap):preview→preview通道,写入 Updates 请求头expo-channel-name; - 图标切换(
iconPathMap):preview使用assets/icon-staging.png(staging 图标,便于与生产包肉眼区分),而production使用assets/icon.png; - 开发类插件:当
process.env.PROFILE !== "production"时会额外注入plugins/android-trust-user-certs.js(信任用户自签证书的调试插件); - App 身份:iOS
bundleIdentifier固定为is.follow(apps/mobile/app.config.base.ts),Androidpackage同为is.follow。该标识后续启动命令会直接用到。
此外非 production 的 preview 构建会走完整原生编译(expo-build-properties 配置了 deploymentTarget: "16.4"、useFrameworks: "static" 等),因此要求本机具备完整 Xcode 环境。
第四步:解包并安装到设备
4.1 解包 IPA
IPA 本质上是一个 ZIP 归档,内含 Payload/<AppName>.app 目录。使用系统 unzip 解包:
unzip -q -o .context/preview-install/folo-preview.ipa -d .context/preview-install/unpacked
参数说明:-q 静默模式;-o 覆盖已存在文件(保证重复执行时的幂等性)。
4.2 定位 .app 目录
APP_PATH=$(find .context/preview-install/unpacked/Payload -maxdepth 1 -name '*.app' -type d | head -n 1)
find 在 Payload 下仅检索一层(-maxdepth 1),取第一个 .app 类型目录作为安装源;head -n 1 防止多 app 场景下变量被多行污染。
4.3 执行安装
xcrun devicectl device install app --device "<device-id>" "$APP_PATH"
--device "<device-id>"传入第二步解析出的设备标识;- 该命令将
.app签名安装到设备。若设备被锁屏或未信任此 Mac,会在此步骤失败。
第五步:启动应用并验证
xcrun devicectl device process launch --device "<device-id>" is.follow --activate
is.follow是应用的 bundle identifier;--activate表示启动后将应用带到前台激活。
技能文档同时记录了兜底策略:如果启动失败是因为设备处于锁屏状态,则应引导用户解锁 iPhone 并手动打开 Folo——这是因为自动化启动受设备锁屏策略限制,属于正常现象而非安装故障。
失败处理与恢复路径
5.1 本地构建失败的报告格式
若 eas-cli build --local 阶段失败,技能要求按固定格式上报三类信息,便于快速定位:
- build mode(固定为
local); - failing command(失败的命令原文);
- key error message from command output(命令输出中的关键错误信息)。
5.2 资源目录缺失:Assets source directory not found ... /out/rn-web
这是一个高概率出现的已知问题。Folo 移动端把 Web 端阅读渲染器构建产物作为内嵌静态资源打进原生包:配置文件 apps/mobile/plugins/with-follow-assets.js 在 prebuild 阶段检查 assetsPath(即仓库根下 out/rn-web),若目录不存在会先尝试自动执行 pnpm --dir ${webAppDir} build --outDir ...,仍失败则抛出:
Assets source directory not found! Please make sure the build is successful. path: .../out/rn-web
技能文档给出的手动恢复命令为:
pnpm --filter @follow/rn-micro-web-app build --outDir out/rn-web/html-renderer
需要说明其与仓库结构的对应关系:
@follow/rn-micro-web-app正是 workspace 包 apps/mobile/web-app/package.json 的包名;- 其下层的 apps/mobile/web-app/html-renderer/package.json(
@follow/rn-micro-web-app-html-renderer)定义了真正的 Vite 构建入口,vite.config.mts 将构建产物输出到仓库根级../../../../out/rn-web/html-renderer; with-follow-assets.js随后在 iOS 工程中把整个out/rn-web注册为名为Assets的 Xcode group / build file,在 Android 中拷贝到app/src/main/assets,从而让原生 WebView 能通过本地资源渲染 HTML。
执行成功后重试一次 preview 构建即可。
输出格式:可审计的结果汇报
无论成败,技能都要求最终返回以下五项结构化信息,便于人工复核与后续操作衔接:
- Build mode(
local)与最终状态(成功/失败); - 本地 IPA 路径(即
.context/preview-install/folo-preview.ipa); - 目标设备标识(UDID 或名称);
- 安装结果(
installed/failed)与启动结果; - 若需人工介入,下一步动作(例如"解锁手机后手动打开 Folo")。
小结与可复用要点
- 这套流程的本质是 EAS Local Build(
--local)+devicectl的组合:前者把 preview 内部分发包的构建完全放到本地执行,后者用 Apple 官方工具链完成安装与启动,二者都不依赖 TestFlight 与云端队列,非常适合发布前的真机验收; preview渠道在 apps/mobile/eas.json 中与development(开发客户端)和production隔离,并通过PROFILE环境变量在 apps/mobile/app.config.base.ts 中驱动图标、更新通道与插件差异,因此"preview 构建"并非简单的参数开关,而是一整套经过配置矩阵设计的产物变体;- 若在 CI/Agent 中复现,请特别注意两点:先完成 EAS 登录校验;若遇
Assets source directory not found,先执行pnpm --filter @follow/rn-micro-web-app build --outDir out/rn-web/html-renderer预生成 Web 渲染资源再重试。
需要复现上述全部命令时,可从仓库根目录执行技能原文 SKILL.md 中记录的完整命令序列;相关配置均可直接在 apps/mobile 目录下查阅、验证。
atomcodeClaude Code 的开源替代方案。连接任意大模型,编辑代码,运行命令,自动验证 — 全自动执行。用 Rust 构建,极致性能。 | An open-source alternative to Claude Code. Connect any LLM, edit code, run commands, and verify changes — autonomously. Built in Rust for speed. Get StartedRust0631
MiniCPM5-2BMiniCPM5-2B 是一款面向端侧、本地部署和资源受限场景的 2B 稠密 Transformer,能够达到同尺寸开源模型 SOTA 水平。Markdown00
GLM-5.3GLM-5.3 与 GLM-5.2 使用相同的基座模型——所有提升均来自后训练。与 GLM-5.2 相比,它在复杂编程和长程任务上的表现显著提升。Jinja00
HivisionIDPhotos⚡️HivisionIDPhotos: a lightweight and efficient AI ID photos tools. 一个轻量级的AI证件照制作算法。Python09
DragonOSDragonOS is an operating system developed from scratch using Rust, with Linux compatibility. It is designed for **Serverless** scenarios. 使用Rust从0自研内核,具有Linux兼容性的操作系统,面向云计算Serverless场景而设计。Rust00
Spark-X2.5-1.7BSpark-X2.5-1.7B 旨在让强大的 AI 更加实用、高效且易于获取。这些模型在广泛的日常任务中表现出色,涵盖对话、写作、翻译、推理、编程、工具调用和智能体工作流,并在同等规模的开源模型中取得领先结果。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00