## Summary Adds GitCode Release mirroring for AIRI release assets. - Adds a reusable `.github/scripts/publish-gitcode-release.sh` helper that creates or reuses the matching GitCode release, compares GitHub and GitCode asset names, downloads only missing `AIRI-*` and `latest-*.yml` assets, uploads them through GitCode's upload URL API, and verifies the final asset list. - Hooks the mirror step into desktop, Android, and iOS release workflows so assets produced by separate release jobs can converge on the same GitCode release. - Keeps the mirror idempotent so repeated or concurrent release workflows skip already mirrored files. ## Why GitHub Releases are slow for many mainland China users. GitCode provides a domestic fallback release page, but manually copying installers after every release is error-prone and tedious. ## Validation - `bash -n .github/scripts/publish-gitcode-release.sh` - YAML parsing for: - `.github/workflows/release-tamagotchi.yml` - `.github/workflows/release-pocket-android.yml` - `.github/workflows/release-pocket-ios.yml` - `git diff --check -- .github/scripts/publish-gitcode-release.sh .github/workflows/release-tamagotchi.yml .github/workflows/release-pocket-android.yml .github/workflows/release-pocket-ios.yml` - Local idempotency check against existing `v0.10.1` GitHub/GitCode releases: - GitCode release already exists - all mirrored assets already present - final asset verification passed ## Setup Required The target repository needs these GitHub Secrets: - `GITCODE_TOKEN` - `GITCODE_OWNER` - `GITCODE_REPO` For the current GitCode project, `GITCODE_OWNER` should remain `MoeruAI` because GitCode's API and release download paths currently resolve that namespace even though the UI displays `moeru-ai`.
183 lines
6.5 KiB
YAML
183 lines
6.5 KiB
YAML
name: Release Pocket (iOS)
|
|
|
|
permissions:
|
|
contents: write
|
|
actions: read
|
|
|
|
on:
|
|
release:
|
|
types:
|
|
- prereleased
|
|
workflow_dispatch:
|
|
inputs:
|
|
testflight:
|
|
description: Upload to TestFlight
|
|
type: boolean
|
|
default: false
|
|
tag:
|
|
description: GitHub Release tag (empty to skip)
|
|
type: string
|
|
required: false
|
|
default: ''
|
|
|
|
jobs:
|
|
build:
|
|
name: Build iOS
|
|
runs-on: macos-26
|
|
env:
|
|
PRODUCT_NAME: 'AIRI'
|
|
VITE_ENABLE_POSTHOG: 'true'
|
|
UPLOAD_TESTFLIGHT: ${{ github.event_name == 'release' || (github.event_name == 'workflow_dispatch' && inputs.testflight) }}
|
|
RELEASE_TAG: ${{ github.event.release.tag_name || inputs.tag }}
|
|
steps:
|
|
- uses: actions/checkout@v6
|
|
|
|
- uses: pnpm/action-setup@v4
|
|
with:
|
|
run_install: false
|
|
- uses: actions/setup-node@v6
|
|
with:
|
|
node-version: lts/*
|
|
cache: pnpm
|
|
|
|
- run: pnpm install --frozen-lockfile
|
|
- run: pnpm run build:packages
|
|
|
|
- name: Build assets
|
|
run: pnpm -F @proj-airi/stage-pocket run build
|
|
|
|
- name: Sync assets to iOS
|
|
run: pnpm -F @proj-airi/stage-pocket exec cap sync ios
|
|
|
|
- name: Install Apple certificate
|
|
env:
|
|
CERT_BASE64: ${{ secrets.APPLE_DISTRIBUTION_CERT_BASE64 }}
|
|
CERT_PASSWORD: ${{ secrets.APPLE_DISTRIBUTION_CERT_PASSWORD }}
|
|
run: |
|
|
CERT_PATH="$RUNNER_TEMP/certificate.p12"
|
|
KEYCHAIN_PATH="$RUNNER_TEMP/app-signing.keychain-db"
|
|
KEYCHAIN_PASSWORD="$(openssl rand -hex 16)"
|
|
|
|
echo "$CERT_BASE64" | base64 --decode -o "$CERT_PATH"
|
|
|
|
security create-keychain -p "$KEYCHAIN_PASSWORD" "$KEYCHAIN_PATH"
|
|
security set-keychain-settings -lut 3600 "$KEYCHAIN_PATH"
|
|
security unlock-keychain -p "$KEYCHAIN_PASSWORD" "$KEYCHAIN_PATH"
|
|
|
|
security import "$CERT_PATH" -k "$KEYCHAIN_PATH" -P "$CERT_PASSWORD" \
|
|
-T /usr/bin/codesign -T /usr/bin/security
|
|
|
|
security set-key-partition-list -S apple-tool:,apple: -s -k "$KEYCHAIN_PASSWORD" "$KEYCHAIN_PATH"
|
|
security list-keychains -d user -s "$KEYCHAIN_PATH" login.keychain-db
|
|
|
|
echo "KEYCHAIN_PATH=$KEYCHAIN_PATH" >> "$GITHUB_ENV"
|
|
|
|
- name: Install provisioning profile
|
|
env:
|
|
PROFILE_BASE64: ${{ secrets.APPLE_PROVISIONING_PROFILE_BASE64 }}
|
|
run: |
|
|
PROFILE_PATH="$RUNNER_TEMP/profile.mobileprovision"
|
|
echo "$PROFILE_BASE64" | base64 --decode -o "$PROFILE_PATH"
|
|
|
|
mkdir -p ~/Library/MobileDevice/Provisioning\ Profiles
|
|
cp "$PROFILE_PATH" ~/Library/MobileDevice/Provisioning\ Profiles/
|
|
|
|
- name: Read version from Xcode project
|
|
run: |
|
|
PBXPROJ="apps/stage-pocket/ios/App/App.xcodeproj/project.pbxproj"
|
|
VERSION="$(sed -n 's/.*MARKETING_VERSION = \(.*\);/\1/p' "$PBXPROJ" | head -1 | tr -d ' ')"
|
|
BUILD_NUMBER="$(sed -n 's/.*CURRENT_PROJECT_VERSION = \(.*\);/\1/p' "$PBXPROJ" | head -1 | tr -d ' ')"
|
|
|
|
echo "VERSION=$VERSION" >> "$GITHUB_ENV"
|
|
echo "BUILD_NUMBER=$BUILD_NUMBER" >> "$GITHUB_ENV"
|
|
|
|
- name: Archive iOS
|
|
run: |
|
|
xcodebuild archive \
|
|
-project apps/stage-pocket/ios/App/App.xcodeproj \
|
|
-scheme App \
|
|
-configuration Release \
|
|
-destination "generic/platform=iOS" \
|
|
-archivePath "$RUNNER_TEMP/App.xcarchive" \
|
|
CURRENT_PROJECT_VERSION=${{ env.BUILD_NUMBER }}
|
|
|
|
- name: Export IPA
|
|
run: |
|
|
xcodebuild -exportArchive \
|
|
-archivePath "$RUNNER_TEMP/App.xcarchive" \
|
|
-exportPath "$RUNNER_TEMP/output" \
|
|
-exportOptionsPlist apps/stage-pocket/ios/ExportOptions.plist
|
|
|
|
- name: Rename IPA for distribution
|
|
run: |
|
|
IPA_SOURCE="$(find "$RUNNER_TEMP/output" -name '*.ipa' -print -quit)"
|
|
IPA_NAME="${PRODUCT_NAME}-${VERSION}_(${BUILD_NUMBER})-ios.ipa"
|
|
|
|
mv "$IPA_SOURCE" "$RUNNER_TEMP/output/$IPA_NAME"
|
|
|
|
echo "IPA_NAME=$IPA_NAME" >> "$GITHUB_ENV"
|
|
|
|
- name: Upload IPA artifact
|
|
uses: actions/upload-artifact@v4
|
|
with:
|
|
name: ${{ env.IPA_NAME }}
|
|
path: ${{ runner.temp }}/output/${{ env.IPA_NAME }}
|
|
if-no-files-found: error
|
|
|
|
- name: Install Transporter
|
|
if: ${{ env.UPLOAD_TESTFLIGHT == 'true' }}
|
|
run: |
|
|
curl -fsSL "https://itunesconnect.apple.com/WebObjects/iTunesConnect.woa/ra/resources/download/public/Transporter__OSX/bin/" -o "$RUNNER_TEMP/itmstransporter.pkg"
|
|
sudo installer -pkg "$RUNNER_TEMP/itmstransporter.pkg" -target /
|
|
rm -f "$RUNNER_TEMP/itmstransporter.pkg"
|
|
|
|
- name: Upload to TestFlight
|
|
if: ${{ env.UPLOAD_TESTFLIGHT == 'true' }}
|
|
uses: apple-actions/upload-testflight-build@v4
|
|
with:
|
|
app-path: ${{ runner.temp }}/output/${{ env.IPA_NAME }}
|
|
issuer-id: ${{ secrets.APPSTORE_CONNECT_API_ISSUER_ID }}
|
|
api-key-id: ${{ secrets.APPSTORE_CONNECT_API_KEY_ID }}
|
|
api-private-key: ${{ secrets.APPSTORE_CONNECT_API_KEY }}
|
|
|
|
- name: Upload to GitHub Releases
|
|
if: ${{ env.RELEASE_TAG != '' }}
|
|
uses: softprops/action-gh-release@v2
|
|
with:
|
|
files: ${{ runner.temp }}/output/${{ env.IPA_NAME }}
|
|
append_body: true
|
|
fail_on_unmatched_files: true
|
|
tag_name: ${{ env.RELEASE_TAG }}
|
|
|
|
- name: Clean up signing
|
|
if: always()
|
|
run: |
|
|
if [ -n "$KEYCHAIN_PATH" ]; then
|
|
security delete-keychain "$KEYCHAIN_PATH" 2>/dev/null || true
|
|
fi
|
|
rm -f ~/Library/MobileDevice/Provisioning\ Profiles/*.mobileprovision
|
|
|
|
publish-gitcode-release:
|
|
name: Publish GitCode Release Mirror
|
|
needs: build
|
|
if: ${{ always() && github.repository == 'moeru-ai/airi' && needs.build.result == 'success' && (github.event_name == 'release' || (github.event_name == 'workflow_dispatch' && inputs.tag != '')) }}
|
|
continue-on-error: true
|
|
runs-on: ubuntu-latest
|
|
steps:
|
|
- uses: actions/checkout@v6
|
|
|
|
- name: Validate release mirror tools
|
|
run: |
|
|
gh --version
|
|
jq --version
|
|
curl --version
|
|
|
|
- name: Publish release assets to GitCode
|
|
env:
|
|
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
|
GITCODE_TOKEN: ${{ secrets.GITCODE_TOKEN }}
|
|
GITCODE_OWNER: ${{ secrets.GITCODE_OWNER }}
|
|
GITCODE_REPO: ${{ secrets.GITCODE_REPO }}
|
|
GITCODE_RELEASE_TAG: ${{ github.event.release.tag_name || inputs.tag }}
|
|
run: bash .github/scripts/publish-gitcode-release.sh
|