diff --git a/.claude-plugin/marketplace.json b/.claude-plugin/marketplace.json index 5d78702..0a6237d 100644 --- a/.claude-plugin/marketplace.json +++ b/.claude-plugin/marketplace.json @@ -73,6 +73,19 @@ ], "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", "source": "./skills-dev/dev-test-plugin", @@ -342,68 +355,6 @@ "tools" ], "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 } ] } \ No newline at end of file diff --git a/skills-dev/dev-deploy-plugin/.claude-plugin/plugin.json b/skills-dev/dev-deploy-plugin/.claude-plugin/plugin.json new file mode 100644 index 0000000..cec55fd --- /dev/null +++ b/skills-dev/dev-deploy-plugin/.claude-plugin/plugin.json @@ -0,0 +1,8 @@ +{ + "name": "dev-deploy-plugin", + "description": "Plugin for dev-deploy", + "version": "1.0.0", + "author": { + "name": "qiudl" + } +} diff --git a/skills-dev/dev-deploy-plugin/skills/SKILL.md b/skills-dev/dev-deploy-plugin/skills/SKILL.md new file mode 100644 index 0000000..7625eff --- /dev/null +++ b/skills-dev/dev-deploy-plugin/skills/SKILL.md @@ -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 '' ssh -o PreferredAuthentications=password -o PubkeyAuthentication=no @ '' +``` + +**关键经验**: +- SSH 远程 codesign 需要先**解锁 Keychain**,否则报 `errSecInternalComponent` +- 还需要 `set-key-partition-list` 授权 codesign 访问密钥 + +```bash +# 必须在每次 SSH 会话开头执行 +security unlock-keychain -p "" ~/Library/Keychains/login.keychain-db +security set-key-partition-list -S apple-tool:,apple:,codesign: -s -k "" ~/Library/Keychains/login.keychain-db +``` + +### Step 2: 拉取代码 + +```bash +cd && 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 + + + + + method + app-store-connect + destination + upload + teamID + {TEAM_ID} + signingStyle + manual + signingCertificate + Apple Distribution + provisioningProfiles + + {BUNDLE_ID} + {PROFILE_NAME} + + manageAppVersionAndBuildNumber + + + +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 '' ssh -o PreferredAuthentications=password \ + -o PubkeyAuthentication=no @ ' +# 0. Keychain +security unlock-keychain -p "" ~/Library/Keychains/login.keychain-db +security set-key-partition-list -S apple-tool:,apple:,codesign: -s -k "" ~/Library/Keychains/login.keychain-db 2>/dev/null + +# 1. Pull +cd && 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 \ + -authenticationKeyID \ + -authenticationKeyIssuerID \ + 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 --to released + +# 创建部署任务并关联 +ai-proj task create --title "【部署】TestFlight 发布: {需求标题}" +ai-proj req link --id --task-ids + +# 附加部署文档 +ai-proj task append-doc --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) |