diff --git a/.github/workflows/build-android.yml b/.github/workflows/build-android.yml index 38daac2..c67089b 100644 --- a/.github/workflows/build-android.yml +++ b/.github/workflows/build-android.yml @@ -125,8 +125,8 @@ jobs: serviceAccountJsonPlainText: ${{ secrets.GOOGLE_PLAY_SERVICE_ACCOUNT_JSON }} packageName: online.globalemergency.emerkit releaseFiles: build/app/outputs/bundle/release/app-release.aab - track: ${{ inputs.android_track || 'internal' }} - status: draft + track: ${{ inputs.android_track || 'production' }} + status: completed releaseName: ${{ steps.version.outputs.version }} - name: Upload APK to Release diff --git a/.github/workflows/build-ios.yml b/.github/workflows/build-ios.yml index 628d028..8a2de12 100644 --- a/.github/workflows/build-ios.yml +++ b/.github/workflows/build-ios.yml @@ -25,8 +25,8 @@ permissions: jobs: build: - name: Build & Upload to TestFlight - runs-on: macos-15 + name: Build & Submit to App Store + runs-on: macos-26 timeout-minutes: 60 steps: - uses: actions/checkout@v6 @@ -194,11 +194,154 @@ jobs: -authenticationKeyIssuerID "$ISSUER_ID" \ -allowProvisioningUpdates - echo "✅ IPA exported" - ls -la ../build/ios/ipa/ 2>/dev/null || echo "IPA uploaded directly to App Store Connect" + echo "✅ IPA exported and uploaded to App Store Connect" - # Note: ExportOptions.plist has destination=upload, so xcodebuild - # uploads directly to App Store Connect. No separate altool step needed. + - name: Install Python dependencies for App Store Connect API + run: pip3 install PyJWT requests + + - name: Submit to App Review + env: + KEY_ID: ${{ secrets.APP_STORE_CONNECT_KEY_ID }} + ISSUER_ID: ${{ secrets.APP_STORE_CONNECT_ISSUER_ID }} + API_KEY: ${{ secrets.APP_STORE_CONNECT_API_KEY }} + run: | + # Install API key for altool/notarytool + mkdir -p ~/private_keys + echo "$API_KEY" > ~/private_keys/AuthKey_${KEY_ID}.p8 + + VERSION="${{ steps.version.outputs.version }}" + BUILD="${{ steps.version.outputs.build_number }}" + + echo "⏳ Waiting for build $VERSION ($BUILD) to finish processing..." + # App Store Connect needs time to process the uploaded build + for i in $(seq 1 30); do + sleep 30 + # Use xcrun to check build processing status + BUILD_STATUS=$(xcrun altool --list-builds \ + --apiKey "$KEY_ID" \ + --apiIssuer "$ISSUER_ID" \ + --output-format json 2>/dev/null | \ + python3 -c " + import json, sys + try: + data = json.load(sys.stdin) + builds = data.get('builds', []) + for b in builds: + if b.get('buildVersion') == '$BUILD': + print(b.get('processingState', 'unknown')) + sys.exit(0) + print('not_found') + except: + print('error') + " 2>/dev/null || echo "error") + + echo " Attempt $i/30: status=$BUILD_STATUS" + if [ "$BUILD_STATUS" = "VALID" ] || [ "$BUILD_STATUS" = "valid" ]; then + echo "✅ Build processing complete" + break + fi + if [ "$i" -eq 30 ]; then + echo "⚠️ Build still processing after 15 minutes, submitting anyway..." + fi + done + + # Submit for review using App Store Connect API via Python + python3 << 'PYEOF' + import json, time, jwt, requests, os, sys + + KEY_ID = os.environ["KEY_ID"] + ISSUER_ID = os.environ["ISSUER_ID"] + key_path = os.path.expanduser(f"~/private_keys/AuthKey_{KEY_ID}.p8") + + with open(key_path, "r") as f: + private_key = f.read() + + # Generate JWT token + now = int(time.time()) + payload = { + "iss": ISSUER_ID, + "iat": now, + "exp": now + 1200, + "aud": "appstoreconnect-v1" + } + token = jwt.encode(payload, private_key, algorithm="ES256", headers={"kid": KEY_ID}) + headers = {"Authorization": f"Bearer {token}", "Content-Type": "application/json"} + base = "https://api.appstoreconnect.apple.com/v1" + + # Find the app + r = requests.get(f"{base}/apps?filter[bundleId]=online.globalemergency.emerkit", headers=headers) + r.raise_for_status() + app_id = r.json()["data"][0]["id"] + print(f"App ID: {app_id}") + + # Get the latest build (just uploaded) + version = os.environ.get("VERSION", "") + build_num = os.environ.get("BUILD", "") + r = requests.get( + f"{base}/builds?filter[app]={app_id}&filter[version]={build_num}&sort=-uploadedDate&limit=1", + headers=headers + ) + r.raise_for_status() + builds = r.json()["data"] + if not builds: + print("⚠️ Build not found yet in App Store Connect, skipping auto-submit") + sys.exit(0) + + build_id = builds[0]["id"] + processing = builds[0]["attributes"].get("processingState", "unknown") + print(f"Build ID: {build_id}, processing: {processing}") + + # Get or create the app store version + r = requests.get( + f"{base}/apps/{app_id}/appStoreVersions?filter[appStoreState]=READY_FOR_SALE,PREPARE_FOR_SUBMISSION,WAITING_FOR_REVIEW,IN_REVIEW,DEVELOPER_REJECTED&limit=1", + headers=headers + ) + r.raise_for_status() + versions = r.json()["data"] + + if versions and versions[0]["attributes"]["appStoreState"] == "PREPARE_FOR_SUBMISSION": + version_id = versions[0]["id"] + print(f"Using existing version: {version_id}") + else: + # Create new version + r = requests.post(f"{base}/appStoreVersions", headers=headers, json={ + "data": { + "type": "appStoreVersions", + "attributes": {"platform": "IOS", "versionString": version}, + "relationships": {"app": {"data": {"type": "apps", "id": app_id}}} + } + }) + r.raise_for_status() + version_id = r.json()["data"]["id"] + print(f"Created version: {version_id}") + + # Attach build to version + r = requests.patch(f"{base}/appStoreVersions/{version_id}/relationships/build", headers=headers, json={ + "data": {"type": "builds", "id": build_id} + }) + if r.ok: + print(f"✅ Build {build_id} attached to version {version_id}") + else: + print(f"⚠️ Could not attach build: {r.status_code} {r.text}") + + # Submit for review + r = requests.post(f"{base}/appStoreVersionSubmissions", headers=headers, json={ + "data": { + "type": "appStoreVersionSubmissions", + "relationships": { + "appStoreVersion": {"data": {"type": "appStoreVersions", "id": version_id}} + } + } + }) + if r.ok: + print(f"✅ Submitted version {version} for App Review!") + else: + print(f"⚠️ Could not submit for review: {r.status_code} {r.text}") + print("You may need to submit manually from App Store Connect") + PYEOF + env: + VERSION: ${{ steps.version.outputs.version }} + BUILD: ${{ steps.version.outputs.build_number }} - name: Cleanup keychain if: always() diff --git a/ios/Runner/Info.plist b/ios/Runner/Info.plist index 104f2fa..d6c9ad9 100644 --- a/ios/Runner/Info.plist +++ b/ios/Runner/Info.plist @@ -68,7 +68,11 @@ UIStatusBarHidden + ITSAppUsesNonExemptEncryption + NSLocationWhenInUseUsageDescription EmerKit necesita tu ubicacion para mostrar tus coordenadas GPS en diferentes formatos y facilitar la comunicacion de tu posicion en emergencias. + NSLocationAlwaysAndWhenInUseUsageDescription + EmerKit necesita tu ubicacion para mostrar tus coordenadas GPS en diferentes formatos y facilitar la comunicacion de tu posicion en emergencias.