diff --git a/.github/workflows/publish-play-store.yml b/.github/workflows/publish-play-store.yml new file mode 100644 index 0000000..4c10f52 --- /dev/null +++ b/.github/workflows/publish-play-store.yml @@ -0,0 +1,58 @@ +name: Publish to Google Play Store + +on: + release: + types: [published] + push: + tags: ["v*"] + +jobs: + publish: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - uses: actions/setup-node@v4 + with: + node-version: 20 + + - uses: actions/setup-java@v4 + with: + distribution: temurin + java-version: 17 + + - name: Setup Android SDK + uses: android-actions/setup-android@v3 + + - name: Install dependencies + run: npm install + + - name: Expo prebuild + run: npx expo prebuild --platform android --no-install + + - name: Decode keystore + run: echo "${{ secrets.KEYSTORE_BASE64 }}" | base64 -d > android/app/release.keystore + + - name: Build AAB + working-directory: android + env: + RELEASE_STORE_FILE: release.keystore + RELEASE_STORE_PASSWORD: ${{ secrets.KEYSTORE_PASSWORD }} + RELEASE_KEY_ALIAS: ${{ secrets.KEY_ALIAS }} + RELEASE_KEY_PASSWORD: ${{ secrets.KEY_PASSWORD }} + run: ./gradlew bundleRelease + + - name: Upload AAB artifact + uses: actions/upload-artifact@v4 + with: + name: app-release-bundle + path: android/app/build/outputs/bundle/release/app-release.aab + + - name: Publish to Play Store + uses: r0adkll/upload-google-play@v1 + with: + serviceAccountJsonPlainText: ${{ secrets.PLAY_STORE_SERVICE_ACCOUNT_JSON }} + packageName: ai.opencode.mobile + releaseFiles: android/app/build/outputs/bundle/release/app-release.aab + track: internal + status: completed diff --git a/Gemfile b/Gemfile new file mode 100644 index 0000000..7a118b4 --- /dev/null +++ b/Gemfile @@ -0,0 +1,3 @@ +source "https://rubygems.org" + +gem "fastlane" diff --git a/PUBLISHING.md b/PUBLISHING.md new file mode 100644 index 0000000..05e4dab --- /dev/null +++ b/PUBLISHING.md @@ -0,0 +1,54 @@ +# Publishing to Google Play Store + +## Required GitHub Secrets + +Configure these in **Settings > Secrets and variables > Actions**: + +| Secret | Description | +|--------|-------------| +| `PLAY_STORE_SERVICE_ACCOUNT_JSON` | Google Play Console service account JSON key (full JSON content) | +| `KEYSTORE_BASE64` | Base64-encoded release keystore (`base64 -w0 release.keystore`) | +| `KEYSTORE_PASSWORD` | Keystore password | +| `KEY_ALIAS` | Key alias in the keystore | +| `KEY_PASSWORD` | Key password | + +## Setup Steps + +### 1. Create a release keystore + +```bash +keytool -genkeypair -v -storetype PKCS12 \ + -keystore release.keystore -alias release \ + -keyalg RSA -keysize 2048 -validity 10000 +``` + +Encode it for GitHub secrets: +```bash +base64 -w0 release.keystore +``` + +### 2. Create a Google Play service account + +1. Go to [Google Cloud Console](https://console.cloud.google.com/) > IAM > Service Accounts +2. Create a service account and download the JSON key +3. In Google Play Console > Settings > API access, link the service account +4. Grant it release management permissions for your app + +### 3. Workflow triggers + +The publish workflow runs on: +- GitHub Release publish events +- Tag pushes matching `v*` + +It builds an AAB (Android App Bundle), signs it with the release keystore, and uploads to the **internal** track. Promote to production via Play Console. + +## Fastlane (Alternative) + +A Fastlane setup is included for local publishing: + +```bash +bundle install +bundle exec fastlane android deploy +``` + +Set environment variables: `SUPPLY_JSON_KEY`, `RELEASE_STORE_FILE`, `RELEASE_STORE_PASSWORD`, `RELEASE_KEY_ALIAS`, `RELEASE_KEY_PASSWORD`. diff --git a/android/app/build.gradle b/android/app/build.gradle index 93f41d1..aa5873b 100644 --- a/android/app/build.gradle +++ b/android/app/build.gradle @@ -104,15 +104,21 @@ android { keyAlias 'androiddebugkey' keyPassword 'android' } + release { + if (System.getenv("RELEASE_STORE_FILE")) { + storeFile file(System.getenv("RELEASE_STORE_FILE")) + storePassword System.getenv("RELEASE_STORE_PASSWORD") + keyAlias System.getenv("RELEASE_KEY_ALIAS") + keyPassword System.getenv("RELEASE_KEY_PASSWORD") + } + } } buildTypes { debug { signingConfig signingConfigs.debug } release { - // Caution! In production, you need to generate your own keystore file. - // see https://reactnative.dev/docs/signed-apk-android. - signingConfig signingConfigs.debug + signingConfig System.getenv("RELEASE_STORE_FILE") ? signingConfigs.release : signingConfigs.debug def enableShrinkResources = findProperty('android.enableShrinkResourcesInReleaseBuilds') ?: 'false' shrinkResources enableShrinkResources.toBoolean() minifyEnabled enableMinifyInReleaseBuilds diff --git a/fastlane/Fastfile b/fastlane/Fastfile new file mode 100644 index 0000000..2927876 --- /dev/null +++ b/fastlane/Fastfile @@ -0,0 +1,21 @@ +default_platform(:android) + +platform :android do + desc "Build and publish to Google Play internal track" + lane :deploy do + gradle( + project_dir: "../android", + task: "bundle", + build_type: "Release" + ) + upload_to_play_store( + package_name: "ai.opencode.mobile", + track: "internal", + aab: "../android/app/build/outputs/bundle/release/app-release.aab", + json_key: ENV["SUPPLY_JSON_KEY"] || "play-store-key.json", + skip_upload_metadata: true, + skip_upload_images: true, + skip_upload_screenshots: true + ) + end +end