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>
This commit is contained in:
@@ -73,6 +73,19 @@
|
|||||||
],
|
],
|
||||||
"strict": false
|
"strict": false
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"name": "dev-deploy-plugin",
|
||||||
|
"source": "./skills-dev/dev-deploy-plugin",
|
||||||
|
"description": "Plugin for dev-deploy",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"category": "development",
|
||||||
|
"keywords": [
|
||||||
|
"development",
|
||||||
|
"coding",
|
||||||
|
"workflow"
|
||||||
|
],
|
||||||
|
"strict": false
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"name": "dev-test-plugin",
|
"name": "dev-test-plugin",
|
||||||
"source": "./skills-dev/dev-test-plugin",
|
"source": "./skills-dev/dev-test-plugin",
|
||||||
@@ -342,68 +355,6 @@
|
|||||||
"tools"
|
"tools"
|
||||||
],
|
],
|
||||||
"strict": false
|
"strict": false
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "gitea-plugin",
|
|
||||||
"source": "./skills-personal/gitea-plugin",
|
|
||||||
"description": "Gitea 代码托管与 CI/CD 管理。用于 Gitea Actions workflow 管理、Runner 管理、PR 操作、仓库配置。",
|
|
||||||
"version": "1.0.0",
|
|
||||||
"category": "utility",
|
|
||||||
"keywords": [
|
|
||||||
"utility",
|
|
||||||
"tools"
|
|
||||||
],
|
|
||||||
"strict": false
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "openclaw-plugin",
|
|
||||||
"source": "./skills-personal/openclaw-plugin",
|
|
||||||
"description": "OpenClaw (龙虾) 远程 AI 计算调度系统 - 概念设计与运维管理",
|
|
||||||
"version": "1.0.0",
|
|
||||||
"category": "utility",
|
|
||||||
"keywords": [
|
|
||||||
"utility",
|
|
||||||
"tools"
|
|
||||||
],
|
|
||||||
"strict": false
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "ops-tools-plugin",
|
|
||||||
"source": "./skills-personal/ops-tools-plugin",
|
|
||||||
"description": "Plugin for ops-tools",
|
|
||||||
"version": "1.0.0",
|
|
||||||
"category": "devops",
|
|
||||||
"keywords": [
|
|
||||||
"devops",
|
|
||||||
"deployment",
|
|
||||||
"operations"
|
|
||||||
],
|
|
||||||
"strict": false
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "qiudl-personal-plugin",
|
|
||||||
"source": "./skills-personal/qiudl-personal-plugin",
|
|
||||||
"description": "Plugin for qiudl-personal",
|
|
||||||
"version": "1.0.0",
|
|
||||||
"category": "utility",
|
|
||||||
"keywords": [
|
|
||||||
"utility",
|
|
||||||
"tools"
|
|
||||||
],
|
|
||||||
"strict": false
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "req-deploy-plugin",
|
|
||||||
"source": "./skills-personal/req-deploy-plugin",
|
|
||||||
"description": "Plugin for req-deploy",
|
|
||||||
"version": "1.0.0",
|
|
||||||
"category": "devops",
|
|
||||||
"keywords": [
|
|
||||||
"devops",
|
|
||||||
"deployment",
|
|
||||||
"operations"
|
|
||||||
],
|
|
||||||
"strict": false
|
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
8
skills-dev/dev-deploy-plugin/.claude-plugin/plugin.json
Normal file
8
skills-dev/dev-deploy-plugin/.claude-plugin/plugin.json
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
{
|
||||||
|
"name": "dev-deploy-plugin",
|
||||||
|
"description": "Plugin for dev-deploy",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"author": {
|
||||||
|
"name": "qiudl"
|
||||||
|
}
|
||||||
|
}
|
||||||
325
skills-dev/dev-deploy-plugin/skills/SKILL.md
Normal file
325
skills-dev/dev-deploy-plugin/skills/SKILL.md
Normal file
@@ -0,0 +1,325 @@
|
|||||||
|
---
|
||||||
|
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) |
|
||||||
Reference in New Issue
Block a user