Merge pull request #695 from chaitin/fix/member-stats-exclude-admin #43
Workflow file for this run
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| # 打 tag(移动端用日期序号 v<YYMMDD><NN>,如 v26060901)后自动构建 Windows / macOS 桌面与 Android/iOS 客户端,并发布到 GitHub Release。 | |
| # 桌面 electron-builder 需 semver,会把日期 tag 转成 20YY.MMDD.NN(如 2026.609.1);移动端 versionName/Code 直接用 26060901。 | |
| # 手动运行 workflow 仅上传 Actions Artifact,不创建 Release(便于试打)。 | |
| # Android 为 Expo 客户端(mobile/)的 release APK,需在仓库 Secrets 配置签名密钥: | |
| # ANDROID_KEYSTORE_BASE64 / ANDROID_KEY_ALIAS / ANDROID_KEY_PASSWORD / ANDROID_STORE_PASSWORD | |
| # iOS 为 Expo 客户端(mobile/)的 release IPA(xcodebuild archive+export),需在 Secrets 配置: | |
| # IOS_CERTIFICATE_BASE64 / IOS_CERTIFICATE_PASSWORD / IOS_PROVISIONING_PROFILE_BASE64 / IOS_TEAM_ID | |
| name: Client Release | |
| on: | |
| push: | |
| tags: | |
| - "v*" | |
| workflow_dispatch: | |
| # 仅写 contents 会把其余权限置为 none,会导致 download-artifact 找不到同 run 上传的产物 | |
| permissions: | |
| contents: write | |
| actions: write | |
| concurrency: | |
| group: electron-release-${{ github.ref }} | |
| cancel-in-progress: true | |
| env: | |
| NODE_VERSION: "20" | |
| CSC_IDENTITY_AUTO_DISCOVERY: false | |
| jobs: | |
| electron-windows: | |
| runs-on: windows-latest | |
| steps: | |
| - uses: actions/checkout@v4 | |
| - uses: pnpm/action-setup@v4 | |
| with: | |
| version: 9 | |
| - uses: actions/setup-node@v4 | |
| with: | |
| node-version: ${{ env.NODE_VERSION }} | |
| cache: pnpm | |
| cache-dependency-path: desktop/pnpm-lock.yaml | |
| - name: Cache Electron binary | |
| uses: actions/cache@v4 | |
| with: | |
| path: ~\AppData\Local\electron\Cache | |
| key: electron-win-${{ hashFiles('desktop/package.json') }} | |
| - name: Cache electron-builder tools | |
| uses: actions/cache@v4 | |
| with: | |
| path: ~\AppData\Local\electron-builder\Cache | |
| key: electron-builder-win-${{ hashFiles('desktop/package.json') }} | |
| - name: Set package.json version | |
| shell: bash | |
| working-directory: desktop | |
| run: | | |
| if [[ "${{ github.ref }}" == refs/tags/v* ]]; then | |
| RAW="${GITHUB_REF_NAME#v}" | |
| # electron-builder 要求严格 semver;移动端日期 tag v<YYMMDD><NN> → 20YY.MMDD.NN | |
| # (如 v26060901 → 2026.609.1;各段 ≤65535 兼容 Windows 版本号,且随 tag 单调递增) | |
| if [[ "$RAW" =~ ^[0-9]{8}$ ]]; then | |
| V="20${RAW:0:2}.$((10#${RAW:2:4})).$((10#${RAW:6:2}))" | |
| elif [[ "$RAW" =~ ^[0-9]+\.[0-9]+$ ]]; then | |
| V="${RAW}.0" | |
| else | |
| V="$RAW" | |
| fi | |
| else | |
| V="0.0.0-ci.${{ github.run_number }}" | |
| fi | |
| node -e "const fs=require('fs');const p='package.json';const j=JSON.parse(fs.readFileSync(p,'utf8'));j.version=process.argv[1];fs.writeFileSync(p,JSON.stringify(j,null,2)+'\n');" "$V" | |
| # windows-latest 默认 run 用 pwsh;pnpm/electron-builder 在 Git Bash 下 cwd 更可靠 | |
| - name: Install dependencies | |
| working-directory: desktop | |
| shell: bash | |
| run: pnpm install --frozen-lockfile | |
| # 用 bash + pnpm exec,避免 pwsh 下子进程 cwd/退出码异常;ci-win.json 显式 directories.output | |
| - name: Build Windows (NSIS installer) | |
| working-directory: desktop | |
| shell: bash | |
| run: | | |
| set -euxo pipefail | |
| pwd | |
| test -f package.json | |
| test -f electron/main.cjs | |
| pnpm exec electron-builder --win nsis --x64 --publish never -c electron-builder.ci-win.json | |
| if [[ ! -d release ]]; then | |
| echo "electron-builder 已结束但 ./release 不存在,当前目录:" | |
| ls -la | |
| exit 1 | |
| fi | |
| ls -laR release | |
| - name: Rename Windows artifact | |
| shell: bash | |
| run: cp "$(ls desktop/release/*.exe | head -1)" desktop/release/MonkeyCode-windows.exe | |
| - uses: actions/upload-artifact@v4 | |
| with: | |
| name: electron-windows-x64 | |
| path: desktop/release/MonkeyCode-windows.exe | |
| if-no-files-found: error | |
| electron-macos: | |
| runs-on: macos-latest | |
| steps: | |
| - uses: actions/checkout@v4 | |
| - uses: pnpm/action-setup@v4 | |
| with: | |
| version: 9 | |
| - uses: actions/setup-node@v4 | |
| with: | |
| node-version: ${{ env.NODE_VERSION }} | |
| cache: pnpm | |
| cache-dependency-path: desktop/pnpm-lock.yaml | |
| - name: Cache Electron binary | |
| uses: actions/cache@v4 | |
| with: | |
| path: ~/Library/Caches/electron | |
| key: electron-mac-${{ hashFiles('desktop/package.json') }} | |
| - name: Cache electron-builder tools | |
| uses: actions/cache@v4 | |
| with: | |
| path: ~/Library/Caches/electron-builder | |
| key: electron-builder-mac-${{ hashFiles('desktop/package.json') }} | |
| - name: Set package.json version | |
| working-directory: desktop | |
| run: | | |
| if [[ "${{ github.ref }}" == refs/tags/v* ]]; then | |
| RAW="${GITHUB_REF_NAME#v}" | |
| # electron-builder 要求严格 semver;移动端日期 tag v<YYMMDD><NN> → 20YY.MMDD.NN | |
| # (如 v26060901 → 2026.609.1;各段 ≤65535 兼容 Windows 版本号,且随 tag 单调递增) | |
| if [[ "$RAW" =~ ^[0-9]{8}$ ]]; then | |
| V="20${RAW:0:2}.$((10#${RAW:2:4})).$((10#${RAW:6:2}))" | |
| elif [[ "$RAW" =~ ^[0-9]+\.[0-9]+$ ]]; then | |
| V="${RAW}.0" | |
| else | |
| V="$RAW" | |
| fi | |
| else | |
| V="0.0.0-ci.${{ github.run_number }}" | |
| fi | |
| node -e "const fs=require('fs');const p='package.json';const j=JSON.parse(fs.readFileSync(p,'utf8'));j.version=process.argv[1];fs.writeFileSync(p,JSON.stringify(j,null,2)+'\n');" "$V" | |
| - name: Install dependencies | |
| working-directory: desktop | |
| run: pnpm install --frozen-lockfile | |
| # 目标格式(dmg / zip)取自 package.json 的 build.mac.target | |
| - name: Build macOS (arm64 + x64) | |
| working-directory: desktop | |
| run: | | |
| set -euo pipefail | |
| pwd | |
| pnpm run electron:ci:mac | |
| test -d release || (echo "missing desktop/release"; ls -la; exit 1) | |
| ls -laR release | |
| - name: Rename macOS artifacts | |
| run: | | |
| ARM64=$(ls desktop/release/*arm64*.dmg 2>/dev/null | head -1) | |
| X64=$(ls desktop/release/*.dmg | grep -v arm64 | head -1) | |
| [ -n "$ARM64" ] && cp "$ARM64" desktop/release/MonkeyCode-macos-arm64.dmg | |
| [ -n "$X64" ] && cp "$X64" desktop/release/MonkeyCode-macos-x86.dmg | |
| - uses: actions/upload-artifact@v4 | |
| with: | |
| name: electron-macos-universal | |
| path: | | |
| desktop/release/MonkeyCode-macos-arm64.dmg | |
| desktop/release/MonkeyCode-macos-x86.dmg | |
| if-no-files-found: error | |
| mobile-android: | |
| runs-on: ubuntu-latest | |
| steps: | |
| - uses: actions/checkout@v4 | |
| - uses: actions/setup-node@v4 | |
| with: | |
| node-version: ${{ env.NODE_VERSION }} | |
| cache: npm | |
| cache-dependency-path: mobile/package-lock.json | |
| - uses: actions/setup-java@v4 | |
| with: | |
| distribution: "temurin" | |
| java-version: "17" | |
| # 缓存 Gradle 发行版 + 依赖 + 构建缓存(android/ 由 prebuild 生成、不在仓库里, | |
| # 所以不用 setup-java 的 cache:gradle——它靠 hash 仓库里的 gradle 文件,这里 hash 不到) | |
| - uses: gradle/actions/setup-gradle@v4 | |
| - name: Set versions (from tag, e.g. v26060901) | |
| working-directory: mobile | |
| shell: bash | |
| run: | | |
| set -euo pipefail | |
| if [[ "${{ github.ref }}" == refs/tags/v* ]]; then | |
| V="${GITHUB_REF_NAME#v}" # 你打的 tag 形如 v<YYMMDD><2位序号>,自己保证唯一递增 | |
| else | |
| V="${{ github.run_number }}" # 非 tag 的手动试打:用 run_number | |
| fi | |
| # versionName 与 versionCode 都取 V → 装机版本可直接对应到 tag / CI run。 | |
| # versionCode 必须是整数且 < 2,100,000,000;YYMMDDNN(如 26060901)恒满足,每天最多 99 次。 | |
| node -e "const fs=require('fs');const p='app.json';const j=JSON.parse(fs.readFileSync(p,'utf8'));j.expo.version=String(process.argv[1]);j.expo.android=j.expo.android||{};j.expo.android.versionCode=Number(process.argv[1]);fs.writeFileSync(p,JSON.stringify(j,null,2)+'\n');" "$V" | |
| - name: Install mobile deps | |
| working-directory: mobile | |
| run: npm ci | |
| - name: Expo prebuild (generate android project) | |
| working-directory: mobile | |
| run: npx expo prebuild -p android --no-install | |
| - name: Build Android APK (release, signed) | |
| working-directory: mobile/android | |
| env: | |
| ANDROID_KEYSTORE_BASE64: ${{ secrets.ANDROID_KEYSTORE_BASE64 }} | |
| ANDROID_STORE_PASSWORD: ${{ secrets.ANDROID_STORE_PASSWORD }} | |
| ANDROID_KEY_ALIAS: ${{ secrets.ANDROID_KEY_ALIAS }} | |
| ANDROID_KEY_PASSWORD: ${{ secrets.ANDROID_KEY_PASSWORD }} | |
| run: | | |
| set -euo pipefail | |
| KS="$RUNNER_TEMP/release.jks" | |
| printf '%s' "$ANDROID_KEYSTORE_BASE64" | base64 -d > "$KS" | |
| chmod +x gradlew | |
| # 用 AGP 注入签名(无需改 build.gradle):release APK 直接用 keystore 签名。 | |
| # 只编真机 ABI(arm64 + armv7):x86/x86_64 仅模拟器需要,少编一半 C++(新架构下这是大头) | |
| ./gradlew assembleRelease --no-daemon --stacktrace -Dorg.gradle.jvmargs=-Xmx4096m \ | |
| -PreactNativeArchitectures=arm64-v8a,armeabi-v7a \ | |
| -Pandroid.injected.signing.store.file="$KS" \ | |
| -Pandroid.injected.signing.store.password="$ANDROID_STORE_PASSWORD" \ | |
| -Pandroid.injected.signing.key.alias="$ANDROID_KEY_ALIAS" \ | |
| -Pandroid.injected.signing.key.password="$ANDROID_KEY_PASSWORD" | |
| - name: Stage APK for artifact / release | |
| shell: bash | |
| run: | | |
| mkdir -p mobile/release-apk | |
| cp mobile/android/app/build/outputs/apk/release/app-release.apk mobile/release-apk/MonkeyCode-android.apk | |
| ls -lh mobile/release-apk/ | |
| - uses: actions/upload-artifact@v4 | |
| with: | |
| name: mobile-android-apk | |
| path: mobile/release-apk/*.apk | |
| if-no-files-found: error | |
| # iOS(Expo):用 xcodebuild archive+export,复用与原 CI 相同的签名 Secrets: | |
| # IOS_CERTIFICATE_BASE64 / IOS_CERTIFICATE_PASSWORD / IOS_PROVISIONING_PROFILE_BASE64 / IOS_TEAM_ID | |
| # 证书的 bundleId 必须是 com.chaitin.baizhi.monkeycode;导出方式默认 app-store(上 TestFlight/商店), | |
| # 想直接装真机改成 ad-hoc。 | |
| # 注意:不要设 RCT_USE_PREBUILT_RNCORE=1。它强制用预编译 React.framework(依赖 ReactNativeDependencies.framework), | |
| # 与 app.json 的 ios.buildReactNativeFromSource=true 冲突 → ReactNativeDependencies.framework 没被嵌入 .app, | |
| # 真机一启动就 dyld 崩溃(Library not loaded: @rpath/ReactNativeDependencies.framework/...)。 | |
| # 保持从源码编译(app.json 已配 buildReactNativeFromSource=true),是 RN 官方对该实验特性的建议,能正常过 TestFlight。 | |
| mobile-ios: | |
| runs-on: macos-latest | |
| steps: | |
| - uses: actions/checkout@v4 | |
| # 预编译 RN / ExpoModulesJSI 的 SwiftPM 依赖要求 swift-tools 6.2(Xcode 26);runner 默认 Xcode 可能只有 | |
| # Swift 6.1,导致 "Build ExpoModulesJSI xcframework" 报 'using Swift tools version 6.2.0 but installed is 6.1.0'。 | |
| # 选最新 stable Xcode。若日志里 swift --version 仍是 6.1,说明该 runner 镜像没装 Xcode26,需换更新的 runs-on。 | |
| - name: Select latest stable Xcode (need Swift 6.2 / Xcode 26) | |
| uses: maxim-lobanov/setup-xcode@v1 | |
| with: | |
| xcode-version: latest-stable | |
| - name: Show Xcode / Swift version | |
| run: xcodebuild -version && swift --version | |
| - uses: actions/setup-node@v4 | |
| with: | |
| node-version: ${{ env.NODE_VERSION }} | |
| cache: npm | |
| cache-dependency-path: mobile/package-lock.json | |
| - name: Set versions (from tag, e.g. v26060901) | |
| working-directory: mobile | |
| shell: bash | |
| run: | | |
| set -euo pipefail | |
| if [[ "${{ github.ref }}" == refs/tags/v* ]]; then | |
| V="${GITHUB_REF_NAME#v}" | |
| else | |
| V="${{ github.run_number }}" | |
| fi | |
| # CFBundleShortVersionString(version) 与 CFBundleVersion(build) 都取 V → 可对应 tag / CI run。 | |
| node -e "const fs=require('fs');const p='app.json';const j=JSON.parse(fs.readFileSync(p,'utf8'));j.expo.version=String(process.argv[1]);j.expo.ios=j.expo.ios||{};j.expo.ios.buildNumber=String(process.argv[1]);fs.writeFileSync(p,JSON.stringify(j,null,2)+'\n');" "$V" | |
| - name: Install mobile deps | |
| working-directory: mobile | |
| run: npm ci | |
| - name: Expo prebuild (generate ios project) | |
| working-directory: mobile | |
| run: npx expo prebuild -p ios --no-install | |
| - name: Pod install | |
| working-directory: mobile/ios | |
| run: pod install | |
| - name: Install signing assets | |
| shell: bash | |
| env: | |
| IOS_CERTIFICATE_BASE64: ${{ secrets.IOS_CERTIFICATE_BASE64 }} | |
| IOS_CERTIFICATE_PASSWORD: ${{ secrets.IOS_CERTIFICATE_PASSWORD }} | |
| IOS_PROVISIONING_PROFILE_BASE64: ${{ secrets.IOS_PROVISIONING_PROFILE_BASE64 }} | |
| run: | | |
| set -euo pipefail | |
| : "${IOS_CERTIFICATE_BASE64:?Missing IOS_CERTIFICATE_BASE64}" | |
| : "${IOS_CERTIFICATE_PASSWORD:?Missing IOS_CERTIFICATE_PASSWORD}" | |
| : "${IOS_PROVISIONING_PROFILE_BASE64:?Missing IOS_PROVISIONING_PROFILE_BASE64}" | |
| CERT_PATH="$RUNNER_TEMP/build_certificate.p12" | |
| PROFILE_PATH="$RUNNER_TEMP/build_profile.mobileprovision" | |
| KEYCHAIN_PATH="$RUNNER_TEMP/app-signing.keychain-db" | |
| KEYCHAIN_PASSWORD="$(openssl rand -base64 24)" | |
| echo "$IOS_CERTIFICATE_BASE64" | base64 -D > "$CERT_PATH" | |
| echo "$IOS_PROVISIONING_PROFILE_BASE64" | base64 -D > "$PROFILE_PATH" | |
| security create-keychain -p "$KEYCHAIN_PASSWORD" "$KEYCHAIN_PATH" | |
| security set-keychain-settings -lut 21600 "$KEYCHAIN_PATH" | |
| security unlock-keychain -p "$KEYCHAIN_PASSWORD" "$KEYCHAIN_PATH" | |
| security import "$CERT_PATH" -P "$IOS_CERTIFICATE_PASSWORD" -A -t cert -f pkcs12 -k "$KEYCHAIN_PATH" | |
| security list-keychains -d user -s "$KEYCHAIN_PATH" | |
| security default-keychain -s "$KEYCHAIN_PATH" | |
| security set-key-partition-list -S apple-tool:,apple:,codesign: -s -k "$KEYCHAIN_PASSWORD" "$KEYCHAIN_PATH" | |
| mkdir -p "$HOME/Library/MobileDevice/Provisioning Profiles" | |
| security cms -D -i "$PROFILE_PATH" > "$RUNNER_TEMP/profile.plist" | |
| PROFILE_UUID=$(/usr/libexec/PlistBuddy -c "Print UUID" "$RUNNER_TEMP/profile.plist") | |
| PROFILE_NAME=$(/usr/libexec/PlistBuddy -c "Print Name" "$RUNNER_TEMP/profile.plist") | |
| cp "$PROFILE_PATH" "$HOME/Library/MobileDevice/Provisioning Profiles/$PROFILE_UUID.mobileprovision" | |
| # 取出导入证书的签名标识名(如 "Apple Distribution: …"),用于强制手动签名,避免回退到 "iOS Development" | |
| SIGNING_IDENTITY=$(security find-identity -v -p codesigning "$KEYCHAIN_PATH" | sed -n 's/.*"\(.*\)".*/\1/p' | head -1) | |
| : "${SIGNING_IDENTITY:?No code-signing identity found in imported certificate}" | |
| echo "IOS_KEYCHAIN_PATH=$KEYCHAIN_PATH" >> "$GITHUB_ENV" | |
| echo "IOS_PROFILE_NAME=$PROFILE_NAME" >> "$GITHUB_ENV" | |
| echo "IOS_SIGNING_IDENTITY=$SIGNING_IDENTITY" >> "$GITHUB_ENV" | |
| - name: Archive & export IPA | |
| working-directory: mobile/ios | |
| env: | |
| IOS_TEAM_ID: ${{ secrets.IOS_TEAM_ID }} | |
| run: | | |
| set -euo pipefail | |
| BUNDLE_ID="com.chaitin.baizhi.monkeycode" | |
| cat > "$RUNNER_TEMP/ExportOptions.plist" <<PLIST | |
| <?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</string> | |
| <key>teamID</key><string>${IOS_TEAM_ID}</string> | |
| <key>signingStyle</key><string>manual</string> | |
| <key>provisioningProfiles</key> | |
| <dict><key>${BUNDLE_ID}</key><string>${IOS_PROFILE_NAME}</string></dict> | |
| <key>stripSwiftSymbols</key><true/> | |
| <key>uploadBitcode</key><false/> | |
| <key>compileBitcode</key><false/> | |
| </dict> | |
| </plist> | |
| PLIST | |
| xcodebuild -workspace MonkeyCode.xcworkspace -scheme MonkeyCode \ | |
| -configuration Release -sdk iphoneos -destination "generic/platform=iOS" \ | |
| -archivePath "$RUNNER_TEMP/MonkeyCode.xcarchive" \ | |
| DEVELOPMENT_TEAM="$IOS_TEAM_ID" CODE_SIGN_STYLE=Manual \ | |
| CODE_SIGN_IDENTITY="$IOS_SIGNING_IDENTITY" \ | |
| PROVISIONING_PROFILE_SPECIFIER="$IOS_PROFILE_NAME" \ | |
| archive | |
| xcodebuild -exportArchive \ | |
| -archivePath "$RUNNER_TEMP/MonkeyCode.xcarchive" \ | |
| -exportOptionsPlist "$RUNNER_TEMP/ExportOptions.plist" \ | |
| -exportPath "$RUNNER_TEMP/export" | |
| ls -lh "$RUNNER_TEMP/export" | |
| - name: Stage IPA for artifact / release | |
| shell: bash | |
| run: | | |
| set -euxo pipefail | |
| mkdir -p mobile/release-ios | |
| IPA="$(find "$RUNNER_TEMP/export" -name '*.ipa' | head -1)" | |
| test -n "$IPA" | |
| cp "$IPA" mobile/release-ios/MonkeyCode-ios.ipa | |
| ls -lh mobile/release-ios/ | |
| - uses: actions/upload-artifact@v4 | |
| with: | |
| name: mobile-ios-ipa | |
| path: mobile/release-ios/*.ipa | |
| if-no-files-found: error | |
| - name: Cleanup signing keychain | |
| if: always() && env.IOS_KEYCHAIN_PATH != '' | |
| shell: bash | |
| run: | | |
| security delete-keychain "$IOS_KEYCHAIN_PATH" || true | |
| publish-release: | |
| needs: [electron-windows, electron-macos, mobile-android, mobile-ios] | |
| if: startsWith(github.ref, 'refs/tags/v') | |
| runs-on: ubuntu-latest | |
| steps: | |
| - uses: actions/download-artifact@v4 | |
| with: | |
| name: electron-windows-x64 | |
| path: release-assets/windows | |
| - uses: actions/download-artifact@v4 | |
| with: | |
| name: electron-macos-universal | |
| path: release-assets/macos | |
| - uses: actions/download-artifact@v4 | |
| with: | |
| name: mobile-android-apk | |
| path: release-assets/android | |
| - uses: actions/download-artifact@v4 | |
| with: | |
| name: mobile-ios-ipa | |
| path: release-assets/ios | |
| - name: List release files | |
| run: find release-assets -type f -exec ls -lh {} \; | |
| - uses: softprops/action-gh-release@v2 | |
| with: | |
| tag_name: ${{ github.ref_name }} | |
| name: MonkeyCode ${{ github.ref_name }} | |
| generate_release_notes: true | |
| fail_on_unmatched_files: false | |
| files: | | |
| release-assets/windows/* | |
| release-assets/macos/* | |
| release-assets/android/* | |
| release-assets/ios/* | |
| env: | |
| GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
| # iOS 上传 TestFlight(App Store Connect)。仅 tag 发布时跑(workflow_dispatch 试打不传,避免污染 TestFlight)。 | |
| # 不自动提审——上传后 build 进入 App Store Connect,TestFlight 即可内测;要上架就在网页「App Store」里选这个 | |
| # 已上传的 build 提交审核,无需重传 IPA。需配 Secrets: | |
| # APP_STORE_CONNECT_KEY_ID / APP_STORE_CONNECT_ISSUER_ID / APP_STORE_CONNECT_PRIVATE_KEY_BASE64(.p8 的 base64) | |
| ios-testflight: | |
| needs: [mobile-ios] | |
| if: startsWith(github.ref, 'refs/tags/v') | |
| runs-on: macos-latest | |
| steps: | |
| - name: Download IPA artifact | |
| uses: actions/download-artifact@v4 | |
| with: | |
| name: mobile-ios-ipa | |
| path: ipa | |
| - name: Upload to TestFlight (App Store Connect) | |
| shell: bash | |
| env: | |
| ASC_KEY_ID: ${{ secrets.APP_STORE_CONNECT_KEY_ID }} | |
| ASC_ISSUER_ID: ${{ secrets.APP_STORE_CONNECT_ISSUER_ID }} | |
| ASC_PRIVATE_KEY_BASE64: ${{ secrets.APP_STORE_CONNECT_PRIVATE_KEY_BASE64 }} | |
| run: | | |
| set -euo pipefail | |
| : "${ASC_KEY_ID:?Missing APP_STORE_CONNECT_KEY_ID}" | |
| : "${ASC_ISSUER_ID:?Missing APP_STORE_CONNECT_ISSUER_ID}" | |
| : "${ASC_PRIVATE_KEY_BASE64:?Missing APP_STORE_CONNECT_PRIVATE_KEY_BASE64}" | |
| # altool 约定:API key(.p8) 须放在 ~/.appstoreconnect/private_keys 且命名 AuthKey_<KEY_ID>.p8 | |
| KEY_DIR="$HOME/.appstoreconnect/private_keys" | |
| mkdir -p "$KEY_DIR" | |
| KEY_FILE="$KEY_DIR/AuthKey_${ASC_KEY_ID}.p8" | |
| echo "$ASC_PRIVATE_KEY_BASE64" | base64 -D > "$KEY_FILE" | |
| trap 'rm -f "$KEY_FILE"' EXIT | |
| IPA="$(find ipa -name '*.ipa' | head -1)" | |
| test -n "$IPA" | |
| echo "Uploading $(basename "$IPA") to App Store Connect / TestFlight..." | |
| # altool --upload-app 仍可用;若某天被移除可改用 xcrun iTMSTransporter / Transporter.app | |
| xcrun altool --upload-app -t ios -f "$IPA" --apiKey "$ASC_KEY_ID" --apiIssuer "$ASC_ISSUER_ID" |