Repository navigation
Merge remote-tracking branch 'origin' into feat/migration-data-receive #45
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
| name: Build iOS | |
| on: | |
| push: | |
| branches: | |
| - "**" | |
| paths: | |
| - "apps/mobile/**" | |
| - "packages/app-zum-doc-utils/**" | |
| - "scripts/mobile-sync.sh" | |
| - "apps/mobile/build-metadata.json" | |
| - ".github/workflows/app-zum-doc-mobile-ios-build.yaml" | |
| workflow_call: | |
| outputs: | |
| app_version: | |
| description: Resolved iOS version name | |
| value: ${{ jobs.build.outputs.app_version }} | |
| build_number: | |
| description: Resolved iOS build number | |
| value: ${{ jobs.build.outputs.build_number }} | |
| workflow_dispatch: | |
| jobs: | |
| verify: | |
| runs-on: "ubuntu-24.04" # @sync android.runner | |
| outputs: | |
| app_version: ${{ steps.version.outputs.app_version }} | |
| code: ${{ steps.version.outputs.code }} | |
| steps: | |
| - uses: actions/checkout@v5 | |
| - id: version | |
| run: | | |
| set -euo pipefail | |
| chmod +x scripts/mobile-sync.sh | |
| scripts/mobile-sync.sh check | |
| if [[ "${GITHUB_REF_TYPE}" == "tag" ]]; then | |
| scripts/mobile-sync.sh check-tag "${GITHUB_REF_NAME}" | |
| else | |
| echo "not a tag (${GITHUB_REF_TYPE} ${GITHUB_REF_NAME}); skipping tag check" | |
| fi | |
| echo "app_version=$(scripts/mobile-sync.sh store-version)" >> "${GITHUB_OUTPUT}" | |
| echo "code=$(scripts/mobile-sync.sh get code)" >> "${GITHUB_OUTPUT}" | |
| build: | |
| needs: verify | |
| runs-on: "macos-15" # @sync ios.runner | |
| outputs: | |
| app_version: ${{ steps.version.outputs.app_version }} | |
| build_number: ${{ steps.version.outputs.build_number }} | |
| steps: | |
| - name: Checkout | |
| uses: actions/checkout@v5 | |
| - name: Select Xcode | |
| env: | |
| XCODE: "26.3" # @sync ios.xcode | |
| run: | | |
| set -euo pipefail | |
| XCODE_APP="/Applications/Xcode_${XCODE}.app" | |
| if [[ ! -d "${XCODE_APP}" ]]; then | |
| echo "Xcode ${XCODE} is not installed on this runner image. Available:" | |
| ls -d /Applications/Xcode*.app | |
| echo "Update ios.xcode or ios.runner in apps/mobile/build-metadata.json." | |
| exit 1 | |
| fi | |
| sudo xcode-select -s "${XCODE_APP}/Contents/Developer" | |
| xcodebuild -version | |
| - uses: pnpm/action-setup@v5 | |
| with: | |
| version: "11.13.0" # @sync pnpm | |
| - uses: actions/setup-node@v5 | |
| with: | |
| node-version: "24" # @sync node | |
| cache: pnpm | |
| - name: Resolve version | |
| id: version | |
| env: | |
| APP_VERSION: ${{ needs.verify.outputs.app_version }} | |
| BUILD_NUMBER: ${{ needs.verify.outputs.code }} | |
| run: | | |
| echo "version=${APP_VERSION} build_number=${BUILD_NUMBER}" | |
| echo "app_version=${APP_VERSION}" >> "${GITHUB_OUTPUT}" | |
| echo "build_number=${BUILD_NUMBER}" >> "${GITHUB_OUTPUT}" | |
| - name: Install Apple certificate and provisioning profile | |
| id: signing | |
| env: | |
| IOS_DIST_CERTIFICATE_BASE64: ${{ secrets.IOS_DIST_CERTIFICATE_BASE64 }} | |
| IOS_DIST_CERTIFICATE_PASSWORD: ${{ secrets.IOS_DIST_CERTIFICATE_PASSWORD }} | |
| IOS_PROVISIONING_PROFILE_BASE64: ${{ secrets.IOS_PROVISIONING_PROFILE_BASE64 }} | |
| run: | | |
| set -euo pipefail | |
| if [[ -z "${IOS_DIST_CERTIFICATE_BASE64}" || -z "${IOS_PROVISIONING_PROFILE_BASE64}" ]]; then | |
| echo "iOS distribution certificate and provisioning profile secrets are required." | |
| exit 1 | |
| fi | |
| CERTIFICATE_PATH="${RUNNER_TEMP}/dist_certificate.p12" | |
| PP_PATH="${RUNNER_TEMP}/appstore.mobileprovision" | |
| KEYCHAIN_PATH="${RUNNER_TEMP}/app-signing.keychain-db" | |
| KEYCHAIN_PASSWORD="$(openssl rand -base64 32)" | |
| PROFILE_PLIST="${RUNNER_TEMP}/profile.plist" | |
| echo -n "${IOS_DIST_CERTIFICATE_BASE64}" | base64 --decode -o "${CERTIFICATE_PATH}" | |
| echo -n "${IOS_PROVISIONING_PROFILE_BASE64}" | base64 --decode -o "${PP_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 "${CERTIFICATE_PATH}" \ | |
| -P "${IOS_DIST_CERTIFICATE_PASSWORD}" \ | |
| -A -t cert -f pkcs12 -k "${KEYCHAIN_PATH}" | |
| security set-key-partition-list -S apple-tool:,apple:,codesign: \ | |
| -s -k "${KEYCHAIN_PASSWORD}" "${KEYCHAIN_PATH}" | |
| security list-keychain -d user -s "${KEYCHAIN_PATH}" login.keychain-db | |
| security default-keychain -s "${KEYCHAIN_PATH}" | |
| security find-identity -v -p codesigning "${KEYCHAIN_PATH}" | grep -q "Apple Distribution" || { | |
| echo "No 'Apple Distribution' identity found in the imported .p12." | |
| security find-identity -v -p codesigning "${KEYCHAIN_PATH}" | |
| exit 1 | |
| } | |
| security cms -D -i "${PP_PATH}" > "${PROFILE_PLIST}" | |
| UUID="$(plutil -extract UUID raw "${PROFILE_PLIST}")" | |
| PROFILE_NAME="$(plutil -extract Name raw "${PROFILE_PLIST}")" | |
| TEAM_ID="$(plutil -extract TeamIdentifier.0 raw "${PROFILE_PLIST}")" | |
| APP_ID="$(plutil -extract Entitlements.application-identifier raw "${PROFILE_PLIST}")" | |
| BUNDLE_ID="${APP_ID#*.}" | |
| mkdir -p "${HOME}/Library/MobileDevice/Provisioning Profiles" | |
| mkdir -p "${HOME}/Library/Developer/Xcode/UserData/Provisioning Profiles" | |
| cp "${PP_PATH}" "${HOME}/Library/MobileDevice/Provisioning Profiles/${UUID}.mobileprovision" | |
| cp "${PP_PATH}" "${HOME}/Library/Developer/Xcode/UserData/Provisioning Profiles/${UUID}.mobileprovision" | |
| echo "Profile: ${PROFILE_NAME} | Team: ${TEAM_ID} | Bundle: ${BUNDLE_ID}" | |
| echo "profile_name=${PROFILE_NAME}" >> "${GITHUB_OUTPUT}" | |
| echo "profile_uuid=${UUID}" >> "${GITHUB_OUTPUT}" | |
| echo "team_id=${TEAM_ID}" >> "${GITHUB_OUTPUT}" | |
| echo "bundle_id=${BUNDLE_ID}" >> "${GITHUB_OUTPUT}" | |
| - name: Install JavaScript dependencies | |
| run: pnpm install --frozen-lockfile | |
| - name: Prepare packages | |
| env: | |
| EXPO_NO_GIT_STATUS: "1" | |
| LANG: en_US.UTF-8 | |
| APP_VERSION: ${{ steps.version.outputs.app_version }} | |
| IOS_BUILD_NUMBER: ${{ steps.version.outputs.build_number }} | |
| IOS_TEAM_ID: ${{ steps.signing.outputs.team_id }} | |
| run: | | |
| pnpm --filter @app-zum-doc/utils run init | |
| pnpm --filter @app-zum-doc/mobile run prebuild:ios | |
| - name: Build signed IPA | |
| env: | |
| LANG: en_US.UTF-8 | |
| RCT_NO_LAUNCH_PACKAGER: "1" | |
| PROFILE_NAME: ${{ steps.signing.outputs.profile_name }} | |
| TEAM_ID: ${{ steps.signing.outputs.team_id }} | |
| BUNDLE_ID: ${{ steps.signing.outputs.bundle_id }} | |
| run: | | |
| set -euo pipefail | |
| IOS_DIR="${GITHUB_WORKSPACE}/apps/mobile/ios" | |
| cd "${IOS_DIR}" | |
| find . -maxdepth 2 -name project.pbxproj -not -path './Pods/*' -print0 | xargs -0 sed -i '' \ | |
| -e 's/CODE_SIGN_STYLE = Automatic;/CODE_SIGN_STYLE = Manual;/g' | |
| WORKSPACE="$(find . -maxdepth 1 -name '*.xcworkspace' | head -n 1)" | |
| if [[ -z "${WORKSPACE}" ]]; then | |
| echo "No Xcode workspace found after prebuild." | |
| exit 1 | |
| fi | |
| SCHEME="$(xcodebuild -list -json -workspace "${WORKSPACE}" | python3 -c ' | |
| import json, sys | |
| data = json.load(sys.stdin) | |
| schemes = data.get("workspace", {}).get("schemes", []) | |
| for scheme in schemes: | |
| if scheme.lower() != "pods": | |
| print(scheme) | |
| break | |
| ')" | |
| if [[ -z "${SCHEME}" ]]; then | |
| echo "No Xcode scheme found after prebuild." | |
| exit 1 | |
| fi | |
| ARCHIVE_PATH="${RUNNER_TEMP}/AppZumDoc.xcarchive" | |
| EXPORT_PATH="${RUNNER_TEMP}/ios-export" | |
| EXPORT_OPTIONS="${RUNNER_TEMP}/ExportOptions.plist" | |
| rm -rf "${ARCHIVE_PATH}" "${EXPORT_PATH}" | |
| mkdir -p "${EXPORT_PATH}" | |
| cat > "${EXPORT_OPTIONS}" <<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-connect</string> | |
| <key>destination</key> | |
| <string>export</string> | |
| <key>signingStyle</key> | |
| <string>manual</string> | |
| <key>teamID</key> | |
| <string>${TEAM_ID}</string> | |
| <key>compileBitcode</key> | |
| <false/> | |
| <key>stripSwiftSymbols</key> | |
| <true/> | |
| <key>uploadSymbols</key> | |
| <true/> | |
| <key>provisioningProfiles</key> | |
| <dict> | |
| <key>${BUNDLE_ID}</key> | |
| <string>${PROFILE_NAME}</string> | |
| </dict> | |
| </dict> | |
| </plist> | |
| PLIST | |
| xcodebuild archive \ | |
| -workspace "${WORKSPACE}" \ | |
| -scheme "${SCHEME}" \ | |
| -configuration Release \ | |
| -destination "generic/platform=iOS" \ | |
| -archivePath "${ARCHIVE_PATH}" \ | |
| CODE_SIGN_STYLE=Manual \ | |
| CODE_SIGN_IDENTITY="Apple Distribution" \ | |
| DEVELOPMENT_TEAM="${TEAM_ID}" \ | |
| PROVISIONING_PROFILE_SPECIFIER="${PROFILE_NAME}" | |
| xcodebuild -exportArchive \ | |
| -archivePath "${ARCHIVE_PATH}" \ | |
| -exportOptionsPlist "${EXPORT_OPTIONS}" \ | |
| -exportPath "${EXPORT_PATH}" | |
| IPA="$(find "${EXPORT_PATH}" -name '*.ipa' | head -n 1)" | |
| if [[ -z "${IPA}" ]]; then | |
| echo "Export produced no IPA." | |
| exit 1 | |
| fi | |
| mv "${IPA}" "${EXPORT_PATH}/AppZumDoc.ipa" | |
| codesign -dv --verbose=2 "${EXPORT_PATH}/AppZumDoc.ipa" 2>&1 || true | |
| - name: Verify IPA version | |
| env: | |
| EXPECTED_VERSION: ${{ steps.version.outputs.app_version }} | |
| EXPECTED_BUILD: ${{ steps.version.outputs.build_number }} | |
| run: | | |
| set -euo pipefail | |
| unzip -p "${RUNNER_TEMP}/ios-export/AppZumDoc.ipa" 'Payload/*.app/Info.plist' > "${RUNNER_TEMP}/Info.plist" | |
| GOT_VERSION="$(plutil -extract CFBundleShortVersionString raw "${RUNNER_TEMP}/Info.plist")" | |
| GOT_BUILD="$(plutil -extract CFBundleVersion raw "${RUNNER_TEMP}/Info.plist")" | |
| echo "IPA: ${GOT_VERSION} (${GOT_BUILD}) — expected ${EXPECTED_VERSION} (${EXPECTED_BUILD})" | |
| [[ "${GOT_VERSION}" == "${EXPECTED_VERSION}" ]] || { echo "CFBundleShortVersionString mismatch"; exit 1; } | |
| [[ "${GOT_BUILD}" == "${EXPECTED_BUILD}" ]] || { echo "CFBundleVersion mismatch"; exit 1; } | |
| - name: Clean up keychain and provisioning profile | |
| if: ${{ always() }} | |
| run: | | |
| security delete-keychain "${RUNNER_TEMP}/app-signing.keychain-db" || true | |
| rm -f "${HOME}/Library/MobileDevice/Provisioning Profiles/"*.mobileprovision || true | |
| - name: Upload IPA | |
| uses: actions/upload-artifact@v6 | |
| with: | |
| name: ios-ipa | |
| path: ${{ runner.temp }}/ios-export/AppZumDoc.ipa | |
| if-no-files-found: error |