移动/游戏
在非 Bazel 的 iOS/Apple 项目上…
通过 `lim xcode` 在 Limrun 远程 Xcode 上同步源码、构建、跑 XCTest、签名 IPA 并上传 App Store Connect/生成预览链接。
🧪 尚未被验证(还没有小呱复用过的记录,不代表不好)
GDI C 38/100
进化评分(GDI)
综合排序依据 —— 四个维度的加权结果,让你看出「为什么这条排在前面」
内在质量 55/100 ×0.35
使用度 0/100 ×0.30
口碑 50/100 ×0.20
新鲜度 60/100 ×0.15
适用场景
在非 Bazel 的 iOS/Apple特定
解决思路
- 使用 `lim xcode build .` 代替本地 `xcodebuild` 构建 Apple 项目。
- 不要回退到本地 Xcode、本地模拟器或本地构建工具。
- 构建成功不是终点:把应用跑起来、验证可用,并迭代到用户满意。
- 如未安装 CLI,运行 `npm install --global lim`。
- 认证用 `lim login` 或设置 `LIM_API_KEY`;API key 可能不在项目 `.env` 或当前 shell 中,不要仅因其缺失就向用户索要。
- 以 CLI 为准;若某个 flag 报错或需要未列出的 flag,先查看 `lim xcode --help`、`lim xcode build --help`,不要猜测。
- `lim xcode build .` 会创建或复用记住的 Xcode target,同步当前目录,并把构建日志流到 stdout/stderr。
- 项目有多个 scheme 或使用 workspace 时,加 `--scheme` 和 `--workspace`:`lim xcode build . --scheme MyApp --workspace MyApp.xcworkspace`。
- 需要特定 Xcode configuration 时用 `--configuration Debug` 或 `--configuration Release`:`lim xcode build . --configuration Debug`。
- 省略 configuration 时,Limrun 使用 limbuild 的项目类型默认值:原生 Xcode 构建为 `Debug`,React Native / Expo 构建为 `Release`。
- 用 `--detach` 让命令在构建被接受后立即返回;webhook 可选:`lim xcode build . --detach`。
- 用 `lim xcode logs` 读取最近一次构建日志,无需 exec ID,实例删除后仍可读持久化日志;加 `--follow` 等待完成:`lim xcode logs --follow`。
- sandbox 默认用其 node 的默认 Xcode 构建;如需另一个已安装 major,先为 workspace 设置偏好,后续 build、test、RBE session 和新 sandbox 都跟随,flag 可单次覆盖。
- 查看可构建版本:`lim xcode version list`;星号标记当前使用版本。
- 设置 workspace 偏好并立即切换记住的 sandbox:`lim xcode use xcode@27`;之后 `lim xcode build .` 用 27 构建;`lim xcode version` 显示当前 Xcode。
- 单次覆盖 Xcode 版本:`lim xcode build . --xcode-version 26`;遗忘偏好:`lim xcode version unset`。
- 可组合 Xcode 和 mise 选择:`lim xcode use xcode@27 node@24`。
- 脚本场景用 `lim xcode version list --quiet` 每行输出一个可选 major;`--json` 返回 `{ installed, bound, preferred }`,其中 `installed[].betaSeed` 带 beta seed;表格用 `*` 标记使用中的 Xcode。
- `lim xcode version set` 不会记录 node 缺少的 major(错误会列出可用项),但 sandbox 忙时仍会保留。
- 当 sandbox 与 workspace 偏好 major 不同,下一次构建会提示并先切换;切换会使另一版本产生的 build cache 失效(下一次冷构建),且 build、sync 或 `lim xcode rbe` stack 运行时拒绝切换。
- node 没有的 major 会失败并列出可用项;只能选择 major。beta Xcode 的 App Store 上传会被 Apple 拒绝,所以 `--upload-to-appstore` 保持默认 Xcode。
- `--dev-server-url` 仅支持 React Native / Expo 的 `--configuration Debug`;它是安装后启动 URL:limbuild 验证其为可解析的绝对 URL,然后在附加模拟器上安装后原样打开。
- 使用 dev server URL:`lim xcode build . --configuration Debug --dev-server-url '<absolute-url>'`。
- 如果应用启动后没有使用预期 URL,显式打开它以区分构建/安装问题和 URL 路由问题:`lim ios open-url --id <ios-instance-id> '<absolute-url>'`。
- 同步后,用 `lim xcode use` 在 sandbox 中选择工具并安装缺失版本。
- 对已同步项目的工具选择运行 `lim xcode tools install`。
- 工具版本用 major,或用 major.minor(Ruby、Flutter 和 Mint 等 pre-1.0 工具)。
- 查看工具:`lim xcode tools`;Node 含 npm/npx,Ruby 含 gem,Flutter 含 Dart,CocoaPods 含 cocoapods-patch。
- 选择工具示例:`lim xcode use node@24 pnpm@10 yarn@4 bun@1 ruby@3.3 bundler@4 cocoapods@1 cmake@3 java@jetbrains-21 corretto@21 flutter@3.44 mint@0.18 xcodegen@2 xcbeautify@3 zsign@1`。
- 安装并可在子目录选择:`lim xcode tools install`;`lim xcode use --cwd apps/mobile node@24`;`lim xcode tools install --cwd apps/mobile`;`lim xcode run -- mise use --pin node@24.5.0`。
- 如果仓库有 `project.yml` 且 `.xcodeproj` 被 gitignore,不要本地运行 xcodegen,也不要把缺失 project 当作错误;远程 sandbox 会在构建前从 `project.yml` 生成项目。
- 直接运行 `lim xcode build .`;spec 会在仓库根或下一级目录(如 `ios/`)找到,无需 flags。
- 项目每次构建都会重新生成,所以修改 `project.yml` 后只需重新构建即可生效;已提交或 force-sync 的 `.xcodeproj` 总是优先,sandbox 只在 sync 没提供时生成。
- 如果仓库 codegen 产生构建所需的 gitignored 输入(生成的本地 Swift package、配置派生源码),先在本地运行该步骤,再用 `--include` force-sync 输出:`make generate` 然后 `lim xcode build . --include '^ios/GeneratedKit/'`。
- `--include` 接受正则表达式(类似 `--ignore`),不是 gitignore 语法;要触达整体被忽略目录下的文件,pattern 也必须匹配目录路径本身。
- `lim xcode build` 是构建并安装;直到用户需要看到或交互应用时才附加模拟器。
- 检查/附加模拟器:`lim xcode get` 查看是否已附加;`lim ios create --attach` 附加一个(会立即安装最后一次构建)。
- 没有浏览器展示给用户时加 `--no-open`;它跳过本地打开 stream URL,但仍打印 URL 供分享。
- 如果 attach 输出包含签名的 stream URL,以 Markdown 链接分享给用户,如 `[Live simulator](<signed-stream-url>)`。
- 已附加模拟器时,每次成功的 `lim xcode build` 都会自动重新安装并重启应用,无需单独安装步骤。
- 要点击、输入、读取元素树、截图或录制,切换到 `limrun-ios-simulator` skill。
- 用 `lim xcode test` 在 sandbox 上构建 scheme 的 test targets,在已附加模拟器上运行(unit 和 UI 一样),每个测试用例流式输出一行;任何测试失败 exec 非零,可作为 CI gate。
- 运行测试:`lim xcode test .`;`lim xcode test ./MyProject --scheme MyApp`。
- 它会像 `lim xcode build --ios` 一样自动获取 simulator-backed target,并在重复运行时复用实例,因此迭代很快。
- scheme 必须配置 test action(有 test targets 的共享 scheme 会有一个)。
- `--xcode-version 27` 用该 Xcode 构建测试;模拟器保持 fleet 默认 runtime,所以运行会警告并继续(可能出现 runtime 相关失败)。
- 用 xcodebuild identifier 格式 `Target[/Class[/method]]` 选择子集;多个条目重复 flag;`--only-testing` 和 `--skip-testing` 互斥:`lim xcode test . --only-testing MyAppTests/LoginTests/testValidLogin`;`lim xcode test . --skip-testing MyAppUITests`。
- 裸 target 名称选择或跳过整个 target;`--only-testing` 条目命名了构建未产生的 target 会让运行失败,而不是静默运行全部。
- 机器消费时 `--json` 以 NDJSON 流式输出原始 per-case events,并以 `{"exitCode": N}` 记录结束:`lim xcode test . --json > results.ndjson`。
- `--build-only` 只编译 test targets 而不获取模拟器;产物留在 sandbox 供以后运行。
- 如果 UI tests 在连续运行许多套件后的实例上第一次交互就失败,下一次运行优先用 fresh instances:加 `--inactivity-timeout 30m`。
- 用户有 App Store Connect team API key 时优先用 Apple cloud signing;Apple 创建或复用 cloud-managed certificate 和 provisioning profile,用户无需提供 p12 或 `.mobileprovision`。
- cloud signing 设备构建示例:`lim xcode build . --sdk iphoneos --configuration Release --signing-method release-testing --team-id "$APPLE_TEAM_ID" --asc-key-id "$ASC_KEY_ID" --asc-issuer-id "$ASC_ISSUER_ID" --asc-key AuthKey.p8 --upload myapp.ipa`。
- signing methods:`debugging` 为已注册开发设备签名 development IPA;`release-testing` 为已注册测试设备签名 distribution IPA;`app-store-connect` 为 App Store Connect 签名 distribution IPA。
- cloud signing 要求 device SDK(`--sdk iphoneos` 或 `--sdk watchos`)、带 issuer ID 的 team API key、且 `--team-id` 匹配 key 的 Apple Developer team;distribution 方法要求 API key 是 Admin key 或启用了 Access to cloud-managed distribution certificates。
- 处理错误:`Cloud signing permission error` 表示缺少该权限;`No Account for Team` 表示 team ID 与 API key 不匹配;`Failed Registering Bundle Identifier` 表示 bundle ID 属于其他 team,不能自动注册。
- cloud signing 只从 `--entitlements` 取 entitlements,从不从项目 `.entitlements` 文件取:archive 未签名,export 只保留现有 code signature 的 entitlements;使用 capabilities(HealthKit、CloudKit、app groups、push)的应用必须传 flag,否则 capability 会被静默剥离。
- entitlement flag 中裸路径针对 app;`<bundleId>=<path>` 针对 embedded bundle(widget、watch app);每个 bundle 重复一次,如:`--entitlements ./MyApp/MyApp.entitlements --entitlements com.example.myapp.widgets=./Widgets/Widgets.entitlements`。
- entitlement plist 值必须完全展开(不要 `$(AppIdentifierPrefix)`,写具体 prefix),必须省略 export-managed keys(`application-identifier`、`com.apple.developer.team-identifier`、`get-task-allow`、`beta-reports-active`),且每个 capability 必须在 developer portal 的 App ID 上启用,否则 export 会点名失败。
- 用户已有 p12 和 profile 时可用 manual signing:`lim xcode build . --sdk iphoneos --configuration Release --certificate-p12 dist.p12 --certificate-password "$P12_PASSWORD" --provisioning-profile app.mobileprovision --upload myapp.ipa`。
- 上传输出包含签名 IPA 的下载 URL;SUCCEEDED build 表示签名已通过 Apple 服务器验证,除非用户要求,否则不要自行重新验证 IPA;无效签名会让构建响亮失败,而不是产出损坏 artifact。
- 如果 app 嵌入 extensions(WidgetKit widgets、share sheets、intents)或 watch app,App Store signing 需要每个 bundle id 一个 provisioning profile,且都由同一 distribution certificate 签发。
- 每个 bundle 重复 `--provisioning-profile`;每个 profile 通过其内部 application-identifier 匹配 bundle,顺序无关:`--provisioning-profile app.mobileprovision --provisioning-profile widgets.mobileprovision`。
- 多个 profile 时,每个 profile 必须带显式(非通配)bundle id;`signing preflight failed: no provisioning profile covers ...` 错误会点名缺少 profile 的 embedded bundle,向用户索要该 bundle id 的 profile。
- 使用包含完整 CA chain 的 p12,而不只是 leaf certificate;需要时用 `openssl pkcs12 -export -inkey dist.key -in dist.pem -certfile wwdr.pem -out dist-chain.p12` 重新导出。
- 识别签名失败:`Unknown issuer hash` 表示 p12 缺少 CA chain,按上面重新导出;`code signature verification failed` 表示平台 post-sign check 拒绝 artifact,不是用户代码问题,重试并在持续时报告;p12 password 错误表示 `--certificate-password` 与文件不匹配,向用户询问正确密码。
- 要将签名 IPA 上传到 App Store Connect 用于 TestFlight 或 App Store distribution,在已签名设备构建上使用 `--upload-to-appstore` 和 ASC API key flags。
- manual signing 加 upload 示例:`lim xcode build . --sdk iphoneos --configuration Release --certificate-p12 dist.p12 --certificate-password "$P12_PASSWORD" --provisioning-profile app.mobileprovision --upload-to-appstore --asc-key-id "$ASC_KEY_ID" --asc-issuer-id "$ASC_ISSUER_ID" --asc-key AuthKey.p8`。
- cloud signing 可用同一 API key 签名并上传:`lim xcode build . --sdk iphoneos --configuration Release --signing-method app-store-connect --team-id "$APPLE_TEAM_ID" --asc-key-id "$ASC_KEY_ID" --asc-issuer-id "$ASC_ISSUER_ID" --asc-key AuthKey.p8 --upload-to-appstore --auto-build-number`。
- `--upload-to-appstore` 要求 cloud signing 或 manual signing flags 之一,外加 `--asc-key-id` 和 `--asc-key`;用户还想要 IPA 进入 Asset Storage 时结合 `--upload <asset-name>`。
- device IPAs 在 `Payload/` 旁带 `Symbols/`,所以 ASC 无需单独上传 dSYM 即可符号化 crash reports;这要求构建产生 dSYMs:`--configuration Release` 默认产生,Debug 不产生,IPA 会不带 symbols。
- 向用户收集三项 ASC 信息:`--asc-key-id` 是 API key 旁的 Key ID;若没有,指向 Team Keys 的 Developer 角色(可上传构建的最小权限角色);创建 team keys 需要 Admin 账号。
- `--asc-issuer-id` 是 Integrations 页面顶部的 Issuer ID(team 值,非 per-key);cloud signing 要求 team key 和此 flag;manual signing 加 upload 时,individual API keys 可省略。
- `--asc-key` 是下载的 `.p8` 文件路径;Apple 不保存副本,离开页面后下载链接消失;若丢失必须生成新 key;绝不要提交 `.p8` 或把内容粘贴进文件;传文件系统路径。
- 默认构建在上传提交后立即返回,把处理结果留给 ASC(处理常需很多分钟);传 `--asc-wait-timeout <seconds>`(最大 1800)可在返回前观察 verdict。
- 从最终行读取结果:`App Store Connect: upload accepted.` 表示完成,Apple 处理完后出现在 TestFlight;`uploaded, still processing on Apple's side (upload <id>).` 表示 exit code 0 且上传成功,Apple 仍在处理,不要重试构建;`App Store Connect upload failed; the build and signing succeeded.` 表示 exit code 1,日志前面有 Apple 错误,仅 delivery 失败。
- 识别上传失败:bundle version 已被使用 -> bump `CFBundleVersion`(Expo:`expo.ios.buildNumber` in app.json)并重建;`HTTP 401` -> key ID / issuer ID / .p8 不匹配,或 key 被撤销;`HTTP 403` -> key 角色不能上传构建,需要 Developer 角色或更高;`no App Store Connect app with bundle id` -> app record 不存在,用户必须在 App Store Connect 手动创建(API 不能);TestFlight 中卡在 `Missing Compliance` -> 构建时未回答 export-compliance,设置 Info.plist 的 `ITSAppUsesNonExemptEncryption` 为 `NO`(Expo:`expo.ios.config.usesNonExemptEncryption: false` in app.json)并重建。
- 要 hands-off delivery 给测试者,app 的 internal TestFlight group 必须启用 automatic distribution(create-only setting)且设置上述 compliance key;这样上传后无需额外步骤。
- 仅在用户要求 preview build 或你要开 PR 时创建可复用 preview asset;构建并上传:`ASSET_NAME="<bundle id / pr number / or any session identifier>.zip"`;`lim xcode build . --upload ${ASSET_NAME}`;Debug preview build 加 `--configuration Debug`。
- build uploads 默认 14 天 TTL:每次 build 把 asset 过期时间推到该次上传后 14 天;传 `--upload-ttl` 用 Go duration 修改(如 `720h`;`1d` 无效)。
- 构造 preview 链接并包含在最后一条消息中(若开 PR 也放进 PR):`https://console.limrun.com/preview?asset=${ASSET_NAME}&platform=ios`。
- 构建错误是你的责任修复:构建失败时读错误输出、修代码、重新构建;不要让用户修构建错误。
- `lim ios` 命令需要 instance ID:它从 cwd 的 git worktree 解析当前实例,可能失败并显示 `No instance ID provided and no recent ios instance found`;从 `lim xcode get` 获取 ID 并传 `--id <ios-instance-id>`;完整方法见 limrun-ios-simulator 的 `Targeting the right instance` 小节。
- 不知道 bundle ID 时检查 Xcode project 文件,或成功构建后运行 `lim ios list-apps`。
- 已认证命令出现 auth errors 表示 session 过期或 `LIM_API_KEY` 错误;让用户运行 `lim login` 或提供 key。
- build settings 可覆盖 Limrun 默认值:`--build-setting KEY=VALUE` 接受任何环境风格 key,并用同 key 替换 managed value;device builds 已用标准架构(嵌入 watch app 保留 `arm64_32`)并禁用 coverage instrumentation,所以 App Store 上传无需额外 settings。
- 成功构建后 artifact not found:服务器自行解析 built `.app`,即使 scheme 名与 product 名不同(scheme `MyApp Dev` 构建 `MyApp-dev.app`);若 upload 仍失败并显示 `built artifact not found`,用 `--artifact-name MyApp-dev.app` 显式传完整 bundle 文件名(含 `.app` 扩展名),服务器会从 build products 逐字取该名。
- 保持同步文件小:单个约 2MB+ 文件可能让客户端 sync 在构建前因 ENOMEM 失败;压缩大资产。
- symlinks 在相对且位于根内时同步;目标是绝对路径的 symlink 会被跳过并警告,如构建需要则用相对目标重建;相对链接逃出同步文件夹会导致 sync 失败,`--ignore` 它或从包含目标的仓库根同步。
- 签名失败响亮且具体:`Unknown issuer hash` 表示 p12 缺 CA chain,重新导出;`code signature verification failed` 表示平台 post-sign check 拒绝 artifact,不是代码问题,重试或报告。
适用边界 · 注意事项
- Bazel 工作区:应改用 `limrun-xcode-bazel` skill,而不是本技能。
- 需要在模拟器上运行、点击、输入、读取元素树、截图或录制时:应改用 `limrun-ios-simulator` skill。
- 没有 Limrun/`lim` CLI、无法登录或没有 `LIM_API_KEY` 时无法执行远程构建/测试/签名/上传。