diff --git a/.github/workflows/README.md b/.github/workflows/README.md new file mode 100644 index 0000000..3fe1146 --- /dev/null +++ b/.github/workflows/README.md @@ -0,0 +1,129 @@ +# GitHub Workflows Documentation + +This directory contains GitHub Actions workflows for automating SDK updates and Google Play Store publishing. + +## Workflows + +### 1. Monthly SDK Bump (`sdk-bump.yml`) + +**Purpose:** Automatically updates the Android SDK to the latest version on a monthly schedule. + +**Schedule:** Runs at 00:00 UTC on the first day of every month + +**Trigger:** +- Scheduled (monthly cron) +- Manual trigger via `workflow_dispatch` + +**What it does:** +1. Fetches the latest Android SDK version from Google's repository +2. Compares with the current SDK version in `app/build.gradle` +3. If an update is available: + - Updates `compileSdkVersion` and `targetSdkVersion` + - Increments `versionCode` and `versionName` + - Verifies the build works with the new SDK + - Creates a pull request with the changes + +**No secrets required** - Uses `GITHUB_TOKEN` for PR creation + +--- + +### 2. Publish to Google Play (`publish-play-store.yml`) + +**Purpose:** Automatically publishes new app versions to Google Play Store when version numbers change. + +**Trigger:** +- Push to `main` or `master` branch with changes to `app/build.gradle` +- Manual trigger via `workflow_dispatch` + +**What it does:** +1. Detects if the `versionCode` in `app/build.gradle` has changed +2. If version changed: + - Builds a signed release AAB (Android App Bundle) + - Uploads to Google Play Store (production track) + - Creates a GitHub release with the version tag + +**Required Secrets:** + +Configure these secrets in your repository settings (Settings → Secrets and variables → Actions): + +| Secret Name | Description | +|-------------|-------------| +| `ANDROID_KEYSTORE_BASE64` | Base64-encoded release keystore file. Create with: `base64 -w 0 your-release-key.keystore` | +| `SIGNING_KEY_ALIAS` | Alias of the signing key in the keystore | +| `SIGNING_KEY_PASSWORD` | Password for the signing key | +| `SIGNING_STORE_PASSWORD` | Password for the keystore file | +| `GOOGLE_PLAY_SERVICE_ACCOUNT_JSON` | JSON key for Google Play service account with publishing permissions | + +#### Setting up Google Play Service Account + +1. Go to [Google Play Console](https://play.google.com/console) +2. Navigate to Setup → API access +3. Create a new service account or use an existing one +4. Grant the service account the following permissions: + - "Release to production, exclude devices, and use Play App Signing" + - "Manage store presence" +5. Create and download a JSON key +6. Copy the entire JSON content and add it as the `GOOGLE_PLAY_SERVICE_ACCOUNT_JSON` secret + +#### Setting up Android Keystore + +If you don't have a release keystore yet: + +```bash +# Generate a new keystore +keytool -genkey -v -keystore release.keystore -alias your-key-alias \ + -keyalg RSA -keysize 2048 -validity 10000 + +# Convert to base64 for GitHub secret +base64 -w 0 release.keystore > keystore.txt +``` + +Then add the base64 content as the `ANDROID_KEYSTORE_BASE64` secret. + +## Workflow Integration + +These workflows work together as follows: + +1. **Monthly SDK Bump** creates a PR with SDK updates +2. When the PR is merged to `main`, it triggers the **Publish to Google Play** workflow +3. The app is automatically built and published to Google Play Store +4. A GitHub release is created with the version tag + +## Manual Triggers + +Both workflows can be manually triggered: + +1. Go to Actions → Select the workflow +2. Click "Run workflow" +3. Choose the branch and click "Run workflow" + +## Testing + +To test the SDK bump workflow without waiting for the monthly schedule: +1. Use the "Run workflow" button in the GitHub Actions UI +2. Or modify the cron schedule temporarily for testing + +To test the Google Play publish workflow: +1. Make a version number change in `app/build.gradle` +2. Commit and push to `main` (or use manual trigger) +3. Note: This will actually publish to Google Play if secrets are configured + +## Troubleshooting + +### SDK Bump Issues + +- **Gradle build fails**: The workflow includes a build verification step. If it fails, the PR won't be created. +- **No PR created**: Check if the SDK is already at the latest version in the workflow logs. + +### Google Play Publish Issues + +- **"Keystore not found"**: Verify `ANDROID_KEYSTORE_BASE64` secret is set correctly +- **"Unauthorized"**: Check that the service account has the correct permissions in Google Play Console +- **"Version code already exists"**: Ensure `versionCode` in `app/build.gradle` is higher than what's currently in Google Play + +## Maintenance + +- Review and merge SDK bump PRs promptly to keep the app up to date +- Monitor Google Play publish workflow for any failures +- Update secrets if keystore or service account credentials change +- Review and update the workflows as Android/Gradle tooling evolves diff --git a/.github/workflows/publish-play-store.yml b/.github/workflows/publish-play-store.yml new file mode 100644 index 0000000..f35ced3 --- /dev/null +++ b/.github/workflows/publish-play-store.yml @@ -0,0 +1,129 @@ +name: Publish to Google Play + +on: + push: + branches: + - main + - master + paths: + - 'app/build.gradle' + workflow_dispatch: # Allow manual trigger + +jobs: + check-version: + runs-on: ubuntu-latest + permissions: + contents: read + outputs: + should_publish: ${{ steps.version-check.outputs.should_publish }} + version_code: ${{ steps.get-version.outputs.version_code }} + version_name: ${{ steps.get-version.outputs.version_name }} + + steps: + - name: Checkout code + uses: actions/checkout@v4 + with: + fetch-depth: 2 + + - name: Get current version + id: get-version + run: | + VERSION_CODE=$(grep -oP 'versionCode \K[0-9]+' app/build.gradle) + VERSION_NAME=$(grep -oP 'versionName "\K[^"]+' app/build.gradle) + + echo "version_code=$VERSION_CODE" >> $GITHUB_OUTPUT + echo "version_name=$VERSION_NAME" >> $GITHUB_OUTPUT + + echo "Current versionCode: $VERSION_CODE" + echo "Current versionName: $VERSION_NAME" + + - name: Check if version changed + id: version-check + run: | + # Get the previous version from the parent commit + git checkout HEAD~1 -- app/build.gradle 2>/dev/null || true + + if [ -f app/build.gradle ]; then + PREV_VERSION_CODE=$(grep -oP 'versionCode \K[0-9]+' app/build.gradle || echo "0") + else + PREV_VERSION_CODE="0" + fi + + # Restore current version + git checkout HEAD -- app/build.gradle + + CURRENT_VERSION_CODE="${{ steps.get-version.outputs.version_code }}" + + echo "Previous versionCode: $PREV_VERSION_CODE" + echo "Current versionCode: $CURRENT_VERSION_CODE" + + if [ "$CURRENT_VERSION_CODE" != "$PREV_VERSION_CODE" ]; then + echo "should_publish=true" >> $GITHUB_OUTPUT + echo "Version changed - will proceed with publishing" + else + echo "should_publish=false" >> $GITHUB_OUTPUT + echo "Version unchanged - skipping publish" + fi + + publish: + needs: check-version + if: needs.check-version.outputs.should_publish == 'true' + runs-on: ubuntu-latest + permissions: + contents: write + + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Set up JDK + uses: actions/setup-java@v4 + with: + distribution: 'temurin' + java-version: '17' + + - name: Setup Gradle + uses: gradle/gradle-build-action@v3 + + - name: Decode keystore + run: | + echo "${{ secrets.ANDROID_KEYSTORE_BASE64 }}" | base64 -d > ${{ github.workspace }}/release.keystore + env: + ANDROID_KEYSTORE_BASE64: ${{ secrets.ANDROID_KEYSTORE_BASE64 }} + + - name: Build Release AAB + run: | + # Note: Using 'gradle' command as this repo doesn't have a gradlew wrapper + # The gradle-build-action sets up gradle with the version specified in gradle-wrapper.properties + gradle bundleRelease --no-daemon + env: + SIGNING_KEY_ALIAS: ${{ secrets.SIGNING_KEY_ALIAS }} + SIGNING_KEY_PASSWORD: ${{ secrets.SIGNING_KEY_PASSWORD }} + SIGNING_STORE_PASSWORD: ${{ secrets.SIGNING_STORE_PASSWORD }} + + - name: Upload to Google Play + uses: r0adkll/upload-google-play@v1 + with: + serviceAccountJsonPlainText: ${{ secrets.GOOGLE_PLAY_SERVICE_ACCOUNT_JSON }} + packageName: com.destinyitemmanager.app + releaseFiles: app/build/outputs/bundle/release/app-release.aab + track: production + status: completed + whatsNewDirectory: distribution/whatsnew + mappingFile: app/build/outputs/mapping/release/mapping.txt + + - name: Create GitHub Release + uses: actions/create-release@v1 + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + with: + tag_name: v${{ needs.check-version.outputs.version_name }} + release_name: Release v${{ needs.check-version.outputs.version_name }} + body: | + ## Release v${{ needs.check-version.outputs.version_name }} + + Version Code: ${{ needs.check-version.outputs.version_code }} + + This release has been automatically published to Google Play Store. + draft: false + prerelease: false diff --git a/.github/workflows/sdk-bump.yml b/.github/workflows/sdk-bump.yml new file mode 100644 index 0000000..7a17a73 --- /dev/null +++ b/.github/workflows/sdk-bump.yml @@ -0,0 +1,158 @@ +name: Monthly SDK Bump + +on: + schedule: + # Run at 00:00 UTC on the first day of every month + - cron: '0 0 1 * *' + workflow_dispatch: # Allow manual trigger + +jobs: + update-sdk: + runs-on: ubuntu-latest + permissions: + contents: write + pull-requests: write + + steps: + - name: Checkout code + uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - name: Set up JDK + uses: actions/setup-java@v4 + with: + distribution: 'temurin' + java-version: '17' + + - name: Get latest Android SDK version + id: sdk-version + run: | + # Fetch the latest stable SDK version from Android's official repository + # Using the latest stable version available in the Android SDK Platform packages + LATEST_SDK=$(curl -s https://dl.google.com/android/repository/repository2-3.xml | \ + grep -oP '(?<=> $GITHUB_OUTPUT + echo "Latest SDK version: $LATEST_SDK" + + - name: Get current SDK versions + id: current-versions + run: | + COMPILE_SDK=$(grep -oP 'compileSdkVersion \K[0-9]+' app/build.gradle) + TARGET_SDK=$(grep -oP 'targetSdkVersion \K[0-9]+' app/build.gradle) + VERSION_CODE=$(grep -oP 'versionCode \K[0-9]+' app/build.gradle) + VERSION_NAME=$(grep -oP 'versionName "\K[^"]+' app/build.gradle) + + echo "compile_sdk=$COMPILE_SDK" >> $GITHUB_OUTPUT + echo "target_sdk=$TARGET_SDK" >> $GITHUB_OUTPUT + echo "version_code=$VERSION_CODE" >> $GITHUB_OUTPUT + echo "version_name=$VERSION_NAME" >> $GITHUB_OUTPUT + + echo "Current compileSdkVersion: $COMPILE_SDK" + echo "Current targetSdkVersion: $TARGET_SDK" + echo "Current versionCode: $VERSION_CODE" + echo "Current versionName: $VERSION_NAME" + + - name: Check if update is needed + id: check-update + run: | + LATEST_SDK="${{ steps.sdk-version.outputs.latest_sdk }}" + CURRENT_SDK="${{ steps.current-versions.outputs.compile_sdk }}" + + if [ "$LATEST_SDK" -gt "$CURRENT_SDK" ]; then + echo "update_needed=true" >> $GITHUB_OUTPUT + echo "SDK update needed: $CURRENT_SDK -> $LATEST_SDK" + else + echo "update_needed=false" >> $GITHUB_OUTPUT + echo "SDK is already up to date ($CURRENT_SDK)" + fi + + - name: Update SDK versions and version code + if: steps.check-update.outputs.update_needed == 'true' + run: | + LATEST_SDK="${{ steps.sdk-version.outputs.latest_sdk }}" + CURRENT_VERSION_CODE="${{ steps.current-versions.outputs.version_code }}" + CURRENT_VERSION_NAME="${{ steps.current-versions.outputs.version_name }}" + + # Increment version code + NEW_VERSION_CODE=$((CURRENT_VERSION_CODE + 1)) + + # Parse version name more robustly + # Check if version follows major.minor.patch pattern + if [[ "$CURRENT_VERSION_NAME" =~ ^([0-9]+\.[0-9]+)\.([0-9]+)$ ]]; then + VERSION_BASE="${BASH_REMATCH[1]}" + VERSION_PATCH="${BASH_REMATCH[2]}" + NEW_VERSION_PATCH=$((VERSION_PATCH + 1)) + NEW_VERSION_NAME="${VERSION_BASE}.${NEW_VERSION_PATCH}" + else + # Fallback: just increment the version code suffix + NEW_VERSION_NAME="${CURRENT_VERSION_NAME%.*}.$NEW_VERSION_CODE" + fi + + # Update compileSdkVersion + sed -i "s/compileSdkVersion [0-9]*/compileSdkVersion $LATEST_SDK/" app/build.gradle + + # Update targetSdkVersion + sed -i "s/targetSdkVersion [0-9]*/targetSdkVersion $LATEST_SDK/" app/build.gradle + + # Update versionCode + sed -i "s/versionCode [0-9]*/versionCode $NEW_VERSION_CODE/" app/build.gradle + + # Update versionName + sed -i "s/versionName \"[^\"]*\"/versionName \"$NEW_VERSION_NAME\"/" app/build.gradle + + echo "Updated compileSdkVersion and targetSdkVersion to $LATEST_SDK" + echo "Updated versionCode to $NEW_VERSION_CODE" + echo "Updated versionName to $NEW_VERSION_NAME" + + - name: Setup Gradle + uses: gradle/gradle-build-action@v3 + + - name: Verify Gradle build + if: steps.check-update.outputs.update_needed == 'true' + run: | + # Note: Using 'gradle' command as this repo doesn't have a gradlew wrapper + # The gradle-build-action sets up gradle with the version specified in gradle-wrapper.properties + gradle assembleDebug --no-daemon + + - name: Create Pull Request + if: steps.check-update.outputs.update_needed == 'true' + uses: peter-evans/create-pull-request@v6 + with: + token: ${{ secrets.GITHUB_TOKEN }} + commit-message: 'chore: Update Android SDK to ${{ steps.sdk-version.outputs.latest_sdk }}' + branch: automated/sdk-bump-${{ steps.sdk-version.outputs.latest_sdk }} + delete-branch: true + title: 'chore: Update Android SDK to ${{ steps.sdk-version.outputs.latest_sdk }}' + body: | + ## SDK Update + + This PR updates the Android SDK to the latest version. + + ### Changes + - compileSdkVersion: ${{ steps.current-versions.outputs.compile_sdk }} → ${{ steps.sdk-version.outputs.latest_sdk }} + - targetSdkVersion: ${{ steps.current-versions.outputs.target_sdk }} → ${{ steps.sdk-version.outputs.latest_sdk }} + - versionCode: ${{ steps.current-versions.outputs.version_code }} → incremented + - versionName: ${{ steps.current-versions.outputs.version_name }} → incremented + + This PR was automatically created by the monthly SDK bump workflow. + + ### Testing + - [x] Gradle build verification completed successfully + + Please review and merge if the build passes all checks. + labels: | + automated + sdk-update + + - name: No update needed + if: steps.check-update.outputs.update_needed == 'false' + run: echo "SDK is already at the latest version. No update needed." diff --git a/README.md b/README.md index 359ca7f..733551e 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,12 @@ # Android PWA -Load the project into Android Studio and hit `Build > Make Project`. \ No newline at end of file +Load the project into Android Studio and hit `Build > Make Project`. + +## Automated Workflows + +This repository includes GitHub Actions workflows for: + +- **Monthly SDK Bump**: Automatically updates Android SDK to the latest version on the first of each month +- **Google Play Publishing**: Automatically publishes new versions to Google Play Store when version numbers change + +See [.github/workflows/README.md](.github/workflows/README.md) for detailed documentation on these workflows and required setup. \ No newline at end of file diff --git a/app/build.gradle b/app/build.gradle index 8d780cc..0f4217a 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -52,6 +52,21 @@ def twaManifest = [ android { compileSdkVersion 36 namespace 'com.destinyitemmanager.app' + + signingConfigs { + release { + if (System.getenv("SIGNING_KEY_ALIAS")) { + def keystorePath = System.getenv("GITHUB_WORKSPACE") + ? "${System.getenv('GITHUB_WORKSPACE')}/release.keystore" + : "release.keystore" + storeFile file(keystorePath) + storePassword System.getenv("SIGNING_STORE_PASSWORD") + keyAlias System.getenv("SIGNING_KEY_ALIAS") + keyPassword System.getenv("SIGNING_KEY_PASSWORD") + } + } + } + defaultConfig { applicationId "com.destinyitemmanager.app" minSdkVersion 19 @@ -143,6 +158,9 @@ android { buildTypes { release { minifyEnabled true + if (System.getenv("SIGNING_KEY_ALIAS")) { + signingConfig signingConfigs.release + } } } compileOptions {