2026年 07月 的归档
fastlane——App Store Connect CLI(非官方)
我的打鼾监测 App NightSnore 支持 7 种语言(简中、英、日、韩、德、法、阿拉伯语)。这带来一个每次发版都要经历的痛苦环节:在 App Store Connect 后台,把 What’s New(新功能介绍)逐个语言粘贴进去——切语言、粘贴、保存,再切下一个,七遍。要是 Promotional Text(推广文本)也更新了,那就是十四遍。发布过APP的朋友,肯定对此就深有体会了。
这次发 2.3.0 的时候我终于忍不住了:这玩意儿就没有 CLI 能自动化吗?
还真有,而且就是 fastlane。有意思的是,fastlane 我其实早就装了——之前一直拿它给 App Store 截图加设备边框(frameit),我一直以为它就是个截图美化工具,哈哈。这次才发现,截图加框只是它十八般武艺里最不起眼的一样。
fastlane 到底是什么
fastlane 的定位是”把 iOS/Android 发布流程的每个环节都变成可脚本化的命令”。它其实是一整套工具的集合,每个工具管一段:
1. deliver:上传元数据(What’s New、描述、关键词、截图)到 App Store Connect,本文主角。
2. snapshot:跑 UI 测试自动截图,能覆盖每种语言 × 每种设备尺寸。
3. frameit:给截图加设备边框,我之前唯一用过的那个。
4. gym / pilot:打包上传、TestFlight 分发和测试员管理。
5. match / cert / sigh:证书和描述文件的团队共享管理。
6. precheck:上传前扫描文案里的审核高危词。
单人开发、Xcode 自动签名的话,match 这类团队工具基本用不上;但 deliver 对多语言 App 来说是刚需级的效率工具。
deliver:把 ASC 表单变成本地文件
deliver 的思路很直接:ASC 后台的每个表单字段,对应本地一个文本文件,目录按语言组织:
|
1 2 3 4 5 6 7 8 9 10 |
fastlane/metadata/ ├── zh-Hans/ │ ├── release_notes.txt ← What's New │ └── promotional_text.txt ← 推广文本 ├── en-US/ ├── ja/ ├── ko/ ├── de-DE/ ├── fr-FR/ └── ar-SA/ |
一个很贴心的设计是:目录里有什么文件,它就只上传什么。我只放了 release_notes.txt 和 promotional_text.txt,那么描述、关键词、截图这些都不会被碰。配置文件 Deliverfile 里再把二进制和截图明确跳过:
|
1 2 3 4 5 6 7 |
app_identifier "Senob.NightSnore" skip_binary_upload true skip_screenshots true force true # 跳过上传前的 HTML 预览确认 run_precheck_before_submit false submit_for_review false # 只填表单,提交审核仍手动 |
搭一条 What’s New 流水线
我的 App Store 文案一直维护在仓库的 AppStore/*.md 里(七个语言各一个文件),每次发版往里追加一段”## What’s New (X.Y.Z)”。这个 Markdown 就是唯一数据源,所以流水线只需要一个提取脚本:从 md 里抠出指定版本的段落,写到 deliver 要的 metadata 目录去。再包一个 fastlane lane 串起来:
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
lane :whatsnew do |options| version = options[:version] || get_version_number( xcodeproj: "NightSnore.xcodeproj", target: "NightSnore" ) # 从 AppStore/*.md 生成 fastlane/metadata/<locale>/release_notes.txt sh("python3", "../utils/whatsnew_sync.py", version) deliver( api_key_path: "fastlane/api_key.json", app_version: version ) end |
提取脚本里顺手做了两层校验:七个语言缺任何一个对应版本的段落就直接报错(强制多语言同步,防漏),超过 ASC 的字符上限(What’s New 4000 字符、Promotional Text 170 字符)也直接拦下。以后发版就是一条命令:
|
1 2 3 4 5 6 7 8 9 |
$ fastlane whatsnew version:2.3.0 [18:02:20]: ▸ 同步版本 2.3.0 的 What's New + Promotional Text → fastlane/metadata/ [18:02:20]: ▸ zh-Hans notes 240 字符 / promo 57 字符 [18:02:20]: ▸ en-US notes 659 字符 / promo 158 字符 ... [18:02:29]: Uploading metadata to App Store Connect for localized version 'ja' [18:02:29]: Uploading metadata to App Store Connect for localized version 'ar-SA' [18:02:31]: ✅ 2.3.0 七语言 What's New 已同步到 ASC [18:02:31]: fastlane.tools finished successfully 🎉 |
从跑命令到 ASC 七个语言全部填好,9 秒。之前手动粘贴至少十分钟,还得祈祷别粘串了语言。
API Key,和一个专门坑你的格式问题
deliver 走的是 App Store Connect API,需要一个 API 密钥:ASC 后台”用户和访问 → 集成 → App Store Connect API”里创建一个团队密钥,角色选 App Manager,会得到一个 .p8 私钥文件(只能下载一次)加 Key ID 和 Issuer ID。
然后我就结结实实踩了个坑。deliver 支持用一个 JSON 文件传密钥,我很自然地写成了指向 .p8 文件路径的形式,结果:
|
1 2 |
Spaceship::ConnectAPI::Token.from_json_file': [!] App Store Connect API key JSON is missing field(s): key (RuntimeError) |
查了才知道:fastlane 的 Fastfile 里有个 app_store_connect_api_key 这个 action,它支持 key_filepath 参数指向 .p8 文件;但 deliver 的 api_key_path 参数指向的 JSON 文件,只认内联的 key 字段——你得把 .p8 的 PEM 内容整个塞进 JSON 字符串里(换行转成 \n)。同一个工具链里两种密钥写法长得几乎一样但互不兼容,这不纯纯挖坑嘛。正确格式长这样:
|
1 2 3 4 5 6 |
{ "key_id": "ABCD123456", "issuer_id": "12345678-abcd-....", "key": "-----BEGIN PRIVATE KEY-----\nMIGT...\n-----END PRIVATE KEY-----", "in_house": false } |
这个文件含私钥,务必进 .gitignore,待遇跟你的其他 secrets 一样。
踩坑与注意事项
1. deliver 只能写”可编辑状态”的版本(准备提交、被拒等),已在审核中或已上架的版本改不了。所以要在上传 build 之后、点提交审核之前跑它;版本还没建也没关系,deliver 会自动创建。
2. locale 代码不都带地区后缀:日语是 ja 不是 ja-JP,韩语是 ko,但英语是 en-US、德语是 de-DE。写映射表的时候留意。
3. metadata 目录里有什么就传什么,这既是特性也是风险:目录里残留一个过期的 description.txt,就会把线上描述覆盖掉。我的做法是 metadata 目录整个 gitignore,每次由脚本从 md 重新生成,保证它永远是纯派生产物。
用 skill 操作,vibe coding 更顺畅
还有一层我觉得比工具本身更有意思:这整条流水线,从功能开发、写七语言文案,到搭 fastlane、踩坑、修好,都是在 Claude Code 里完成的。搭好之后我顺手让它把整个发版流程沉淀成一个项目 skill——仓库里的一个 .claude/skills/release/SKILL.md 文件,把九个步骤写成清单:bump 版本号 → 编译验证 → 七语言 What’s New / Promotional Text → commit → xcodebuild 归档上传 → 打 tag → fastlane 同步 ASC 表单,连”deliver 的 JSON 只认内联 key”这种坑位说明都写在里面。
下次发版,我只需要说一句 /release 2.4.0,AI 就照着清单把整条链路跑完,唯一剩下的手动操作是去 ASC 点提交审核。skill 跟着仓库走,clone 下来就有,等于把发版的”部落知识”固化成了可执行的文档。对 vibe coding 来说这很关键:写代码交给 AI 大家都会了,但发布环节往往还是人肉在各个后台之间点来点去——把这段也纳入对话式工作流,从开发到上架才算真正闭环。
下一个目标
尝到甜头之后我看了一圈 fastlane 工具箱,对我这种多语言独立开发场景,下一个最值得上的是 snapshot:七个语言的商店截图现在还是手动截的,每次界面大改就是一下午。snapshot 跑 UI 测试自动出全语言截图,再接上我已经会用的 frameit 加框、deliver 上传,理论上截图这条线也能变成一条命令。挖个坑,做完再写。
发布流程自动化这件事,本质上是把”每次发版都要凭记忆和手感重复一遍的操作”变成”写一次、以后白嫖”的脚本。对独立开发者来说,省的那十几分钟是小事,真正值钱的是不再需要担心”这次是不是又漏了哪个语言”——机器不会漏。
就此,完毕。