Files
ai-proj-helper/skills-dev/dev-deploy-plugin/skills/SKILL.md
dongliang b5f44ac6aa feat: add dev-deploy skill — iOS TestFlight deployment
新增部署技能,含 iOS TestFlight 完整部署流程:
- SSH 远程构建 + 无签名 Archive + Export 签名上传
- ASC API 补全合规/测试说明/版本关联
- 10 个坑的经验教训总结
- 一键部署脚本模板 + 检查清单

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-06 09:46:23 +09:30

10 KiB
Raw Blame History

name, description
name description
dev-deploy 应用部署技能。支持 iOS TestFlight、Docker 容器等多平台部署。当用户提到部署、发布、TestFlight、上架、build、archive 相关任务时自动激活。

应用部署 Skill (dev-deploy)

概述

管理应用从构建到发布的完整部署流程,支持多平台:

  • iOS: TestFlight 内测 / App Store 发布
  • Docker: Staging / Production 容器部署

集成 ai-proj 任务系统进行部署记录和需求阶段推进。


命令参考

命令 说明
/deploy ios iOS TestFlight 部署
/deploy docker [staging|prod] Docker 容器部署
/deploy status 查看部署状态

iOS TestFlight 部署

前置条件

项目 要求
构建机器 macOS + Xcode通过 SSH 访问)
签名证书 Apple Distribution 证书已安装在 Keychain
Provisioning Profile App Store Distribution profile 已安装
API Key App Store Connect API Key (.p8)
sshpass 本机安装用于非交互 SSHbrew install hudochenkov/sshpass/sshpass
xcodegen 构建机器安装用于从 project.yml 生成 xcodeproj

完整部署流程

1. git push        → 代码推送到远程仓库
2. SSH 连接构建机   → git pull 拉取最新代码
3. xcodebuild archive → 无签名构建 Archive
4. xcodebuild -exportArchive → Distribution 签名 + 上传 TestFlight
5. ASC API 补全     → 合规信息 + 测试说明 + build 关联版本
6. 验证             → 确认 TestFlight 状态为 IN_BETA_TESTING

Step 1: SSH 连接构建机

# 使用 sshpass 进行非交互 SSH
sshpass -p '<password>' ssh -o PreferredAuthentications=password -o PubkeyAuthentication=no <user>@<host> '<command>'

关键经验

  • SSH 远程 codesign 需要先解锁 Keychain,否则报 errSecInternalComponent
  • 还需要 set-key-partition-list 授权 codesign 访问密钥
# 必须在每次 SSH 会话开头执行
security unlock-keychain -p "<password>" ~/Library/Keychains/login.keychain-db
security set-key-partition-list -S apple-tool:,apple:,codesign: -s -k "<password>" ~/Library/Keychains/login.keychain-db

Step 2: 拉取代码

cd <repo-path> && git pull origin develop

Step 3: Archive无签名

关键经验Archive 阶段不要签名。原因:

  • xcodebuild CLI 签名参数会泄漏到 SPM 依赖的 targets导致 "does not support provisioning profiles" 错误
  • 正确做法是 archive 时禁用签名,在 export 阶段单独签名
xcodebuild archive \
  -project XiaoquCRM.xcodeproj \
  -scheme XiaoquCRM \
  -destination 'generic/platform=iOS' \
  -configuration Release \
  -archivePath ~/Desktop/XiaoquCRM.xcarchive \
  -skipMacroValidation \
  CODE_SIGNING_ALLOWED=NO \
  CODE_SIGNING_REQUIRED=NO

常见错误及解决

错误 原因 解决
Macro "X" must be enabled Swift Macros 安全限制 -skipMacroValidation
cannot find type 'AdminFeature' xcodeproj 未包含新文件 运行 xcodegen generate 重新生成
SPM 依赖报签名错误 签名参数泄漏到依赖 Archive 用 CODE_SIGNING_ALLOWED=NO

Step 4: Export + 上传 TestFlight

# ExportOptions.plist提前创建在构建机上
cat > /tmp/ExportOptions.plist << EOF
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN"
  "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
    <key>method</key>
    <string>app-store-connect</string>
    <key>destination</key>
    <string>upload</string>
    <key>teamID</key>
    <string>{TEAM_ID}</string>
    <key>signingStyle</key>
    <string>manual</string>
    <key>signingCertificate</key>
    <string>Apple Distribution</string>
    <key>provisioningProfiles</key>
    <dict>
        <key>{BUNDLE_ID}</key>
        <string>{PROFILE_NAME}</string>
    </dict>
    <key>manageAppVersionAndBuildNumber</key>
    <true/>
</dict>
</plist>
EOF

# Export + Upload
xcodebuild -exportArchive \
  -archivePath ~/Desktop/XiaoquCRM.xcarchive \
  -exportOptionsPlist /tmp/ExportOptions.plist \
  -exportPath ~/Desktop/XiaoquCRM-export \
  -authenticationKeyPath {API_KEY_PATH} \
  -authenticationKeyID {KEY_ID} \
  -authenticationKeyIssuerID {ISSUER_ID}

关键经验

问题 教训
errSecInternalComponent SSH 远程签名前必须 unlock-keychain + set-key-partition-list
No signing certificate "iOS Distribution" 机器上没装 Distribution 证书,需在 Xcode > Accounts 登录 Apple ID 下载
Redundant Binary Upload build number 重复,需要在 project.yml 递增 CURRENT_PROJECT_VERSION
Missing required icon file 需要 Assets.xcassets/AppIcon.appiconset 含 1024x1024 PNG
UIInterfaceOrientation iPad 错误 必须声明 iPad 四方向支持,或设置 UIRequiresFullScreen=true
Cloud signing permission error API Key 权限不够或 Issuer ID 错误;改用手动签名 + 本地 profile

Step 5: ASC API 补全 TestFlight 信息

上传成功后,需要通过 App Store Connect API 补全三项信息,否则测试者收不到通知或无法安装:

5.1 生成 JWT Token

import jwt, time
key = open("AuthKey_XXXXXX.p8").read()
token = jwt.encode(
    {"iss": "{ISSUER_ID}", "iat": int(time.time()),
     "exp": int(time.time()) + 1200, "aud": "appstoreconnect-v1"},
    key, algorithm="ES256",
    headers={"kid": "{KEY_ID}"}  # ← 必须包含 kid
)

关键经验JWT 必须包含 headers={"kid": KEY_ID},否则 401 认证失败。还需要安装 cryptography 库支持 ES256。

5.2 设置出口合规

PATCH /v1/builds/{build_id}
{"data": {"type": "builds", "id": "{build_id}",
  "attributes": {"usesNonExemptEncryption": false}}}

不设置此项build 会卡在 "Missing Compliance" 状态,内部测试者无法安装。

5.3 填写测试说明 (whatsNew)

# 先获取 localization ID
GET /v1/builds/{build_id}/betaBuildLocalizations

# 更新 whatsNew
PATCH /v1/betaBuildLocalizations/{loc_id}
{"data": {"type": "betaBuildLocalizations", "id": "{loc_id}",
  "attributes": {"whatsNew": "更新内容..."}}}

5.4 关联 Build 到 App Store 版本

关键经验App Store Connect 页面的 App Icon 来自关联的 build。如果没有把 build 关联到 App Store 版本,图标显示为空。

# 关联 build 到版本
PATCH /v1/appStoreVersions/{version_id}/relationships/build
{"data": {"type": "builds", "id": "{build_id}"}}

Step 6: 验证部署状态

# 检查 build 状态
GET /v1/builds/{build_id}?include=buildBetaDetail

# 期望结果:
# processingState: VALID
# internalBuildState: IN_BETA_TESTING
# usesNonExemptEncryption: false

一键部署脚本模板

将以上步骤整合为单次 SSH 调用:

sshpass -p '<password>' ssh -o PreferredAuthentications=password \
  -o PubkeyAuthentication=no <user>@<host> '
# 0. Keychain
security unlock-keychain -p "<password>" ~/Library/Keychains/login.keychain-db
security set-key-partition-list -S apple-tool:,apple:,codesign: -s -k "<password>" ~/Library/Keychains/login.keychain-db 2>/dev/null

# 1. Pull
cd <repo> && git pull origin develop

# 2. Archive
cd ios && rm -rf ~/Desktop/App.xcarchive ~/Desktop/App-export
xcodebuild archive -project App.xcodeproj -scheme App \
  -destination "generic/platform=iOS" -configuration Release \
  -archivePath ~/Desktop/App.xcarchive \
  -skipMacroValidation CODE_SIGNING_ALLOWED=NO CODE_SIGNING_REQUIRED=NO \
  2>&1 | tail -1

# 3. Export + Upload
xcodebuild -exportArchive \
  -archivePath ~/Desktop/App.xcarchive \
  -exportOptionsPlist /tmp/ExportOptions.plist \
  -exportPath ~/Desktop/App-export \
  -authenticationKeyPath <key_path> \
  -authenticationKeyID <key_id> \
  -authenticationKeyIssuerID <issuer_id> \
  2>&1 | grep -E "Upload|EXPORT|error:" | tail -5
'

iOS 部署检查清单

部署前逐项确认:

  • build number 已递增(CURRENT_PROJECT_VERSION in project.yml
  • xcodegen generate 已运行(新文件已包含在 xcodeproj 中)
  • 代码已 push 到远程仓库
  • 构建机可 SSH 访问
  • Assets.xcassets 包含 1024x1024 App Icon
  • Info.plist 包含 iPad 四方向支持
  • Distribution 证书已安装在构建机 Keychain

部署后逐项确认:

  • Archive 成功
  • Export + Upload 成功
  • 合规信息已设置usesNonExemptEncryption
  • 测试说明已填写whatsNew
  • Build 已关联到 App Store 版本
  • TestFlight 状态为 IN_BETA_TESTING
  • 测试者收到更新通知

Docker 容器部署

Staging自动

Push 到 develop 分支自动触发 staging 部署。

Production

./scripts/build-and-push.sh prod --detect --deploy --wait --verify

详见项目 scripts/build-and-push.sh


与需求工作流集成

部署完成后更新需求状态:

# 推进到 released
ai-proj req advance --id <req_id> --to released

# 创建部署任务并关联
ai-proj task create --title "【部署】TestFlight 发布: {需求标题}"
ai-proj req link --id <req_id> --task-ids <task_id>

# 附加部署文档
ai-proj task append-doc --id <task_id> --content "部署记录..."

经验教训汇总

iOS TestFlight 部署的 10 个坑

# 解决方案
1 SSH 远程 codesign 失败 unlock-keychain + set-key-partition-list
2 SPM 依赖报签名错误 Archive 阶段 CODE_SIGNING_ALLOWED=NOExport 阶段签名
3 Swift Macros 被拒 -skipMacroValidation
4 xcodeproj 缺文件 新增源文件后必须 xcodegen generate
5 无 Distribution 证书 Xcode > Accounts 登录 Apple ID 自动下载
6 build number 冲突 每次部署前递增 CURRENT_PROJECT_VERSION
7 缺 App Icon Assets.xcassets + AppIcon.appiconset + 1024x1024 PNG
8 iPad 方向验证失败 声明四方向或 UIRequiresFullScreen=true
9 ASC API 401 JWT 必须包含 kid header + 正确的 Issuer ID
10 App Store 图标为空 需将 build 关联到 App Store 版本PATCH relationships/build