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

326 lines
10 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
---
name: dev-deploy
description: 应用部署技能。支持 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 | 本机安装用于非交互 SSH`brew 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 连接构建机
```bash
# 使用 sshpass 进行非交互 SSH
sshpass -p '<password>' ssh -o PreferredAuthentications=password -o PubkeyAuthentication=no <user>@<host> '<command>'
```
**关键经验**
- SSH 远程 codesign 需要先**解锁 Keychain**,否则报 `errSecInternalComponent`
- 还需要 `set-key-partition-list` 授权 codesign 访问密钥
```bash
# 必须在每次 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: 拉取代码
```bash
cd <repo-path> && git pull origin develop
```
### Step 3: Archive无签名
**关键经验**Archive 阶段**不要签名**。原因:
- xcodebuild CLI 签名参数会泄漏到 SPM 依赖的 targets导致 "does not support provisioning profiles" 错误
- 正确做法是 archive 时禁用签名,在 export 阶段单独签名
```bash
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
```bash
# 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
```python
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: 验证部署状态
```python
# 检查 build 状态
GET /v1/builds/{build_id}?include=buildBetaDetail
# 期望结果:
# processingState: VALID
# internalBuildState: IN_BETA_TESTING
# usesNonExemptEncryption: false
```
### 一键部署脚本模板
将以上步骤整合为单次 SSH 调用:
```bash
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
```bash
./scripts/build-and-push.sh prod --detect --deploy --wait --verify
```
详见项目 `scripts/build-and-push.sh`
---
## 与需求工作流集成
部署完成后更新需求状态:
```bash
# 推进到 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=NO`Export 阶段签名 |
| 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 |