diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 0000000..8c596cd --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,189 @@ +name: Android CI/CD - Play Store Release + +permissions: + contents: write + +on: + push: + branches: + - main + workflow_dispatch: + inputs: + track: + description: 'Play Store track (internal, alpha, beta, production)' + required: false + default: 'internal' + type: choice + options: + - internal + - alpha + - beta + - production + +env: + JAVA_VERSION: '17' + JAVA_DISTRIBUTION: 'temurin' + +jobs: + build-and-deploy: + if: "!contains(github.event.head_commit.message, '[skip release]')" + runs-on: ubuntu-latest + env: + FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true + permissions: + contents: write + + steps: + - name: Checkout code + uses: actions/checkout@v4 + with: + fetch-depth: 0 + token: ${{ secrets.GITHUB_TOKEN }} + + - name: Set up JDK + uses: actions/setup-java@v4 + with: + distribution: ${{ env.JAVA_DISTRIBUTION }} + java-version: ${{ env.JAVA_VERSION }} + + - name: Grant execute permission for gradlew + run: chmod +x gradlew + + - name: Bump version + id: version + run: | + CURRENT_VERSION=$(grep -oP 'versionName\s*=\s*"\K[^"]+' app/build.gradle.kts) + CURRENT_VERSION_CODE=$(grep -oP 'versionCode\s*=\s*\K\d+' app/build.gradle.kts) + + if [ -z "$CURRENT_VERSION" ]; then + CURRENT_VERSION="1.0.0" + fi + if [ -z "$CURRENT_VERSION_CODE" ]; then + CURRENT_VERSION_CODE=0 + fi + + IFS='.' read -r MAJOR MINOR PATCH <<< "$CURRENT_VERSION" + if [ -z "$PATCH" ]; then PATCH=0; fi + NEW_PATCH=$((PATCH + 1)) + NEW_VERSION="${MAJOR}.${MINOR}.${NEW_PATCH}" + + NEW_VERSION_CODE=$((CURRENT_VERSION_CODE + 1)) + + echo "Updating version from $CURRENT_VERSION ($CURRENT_VERSION_CODE) to $NEW_VERSION ($NEW_VERSION_CODE)" + + sed -i "s/versionCode = $CURRENT_VERSION_CODE/versionCode = $NEW_VERSION_CODE/" app/build.gradle.kts + sed -i "s/versionName = \"$CURRENT_VERSION\"/versionName = \"$NEW_VERSION\"/" app/build.gradle.kts + + git config --local user.email "github-actions[bot]@users.noreply.github.com" + git config --local user.name "github-actions[bot]" + git add app/build.gradle.kts + git commit -m "chore: bump version to $NEW_VERSION ($NEW_VERSION_CODE) [skip ci]" + git push + + echo "VERSION_CODE=$NEW_VERSION_CODE" >> $GITHUB_OUTPUT + echo "VERSION_NAME=$NEW_VERSION" >> $GITHUB_OUTPUT + + - name: Decode Keystore + env: + KEYSTORE_BASE64: ${{ secrets.KEYSTORE_BASE64 }} + run: | + printf '%s' "$KEYSTORE_BASE64" | base64 -d > keystore.jks + echo "Keystore decoded to keystore.jks" + + - name: Verify Keystore Integrity + run: | + echo "Keystore file size: $(wc -c < keystore.jks) bytes" + if [ ! -s keystore.jks ]; then + echo "❌ ERROR: Keystore file is empty or missing!" + exit 1 + fi + echo "Keystore file exists and is non-empty" + + - name: Cache Gradle packages + uses: actions/cache@v4 + with: + path: | + ~/.gradle/caches + ~/.gradle/wrapper + key: ${{ runner.os }}-gradle-${{ hashFiles('**/*.gradle*', '**/gradle-wrapper.properties') }} + restore-keys: | + ${{ runner.os }}-gradle- + + - name: Create local.properties + run: | + echo "sdk.dir=/usr/local/lib/android/sdk" > local.properties + + - name: Build AAB + env: + KEYSTORE_PATH: ${{ github.workspace }}/keystore.jks + KEYSTORE_PASSWORD: ${{ secrets.KEYSTORE_PASSWORD }} + KEY_ALIAS: ${{ secrets.KEY_ALIAS }} + KEY_PASSWORD: ${{ secrets.KEY_PASSWORD }} + run: | + ./gradlew bundleRelease --stacktrace + + - name: Verify AAB exists + run: | + AAB_PATH="app/build/outputs/bundle/release/app-release.aab" + if [ ! -f "$AAB_PATH" ]; then + echo "AAB not found!" + exit 1 + fi + echo "AAB found at $AAB_PATH" + + - name: Upload AAB as artifact + uses: actions/upload-artifact@v4 + with: + name: app-release-${{ steps.version.outputs.VERSION_CODE }} + path: app/build/outputs/bundle/release/app-release.aab + retention-days: 30 + + - name: Create GitHub Release + uses: softprops/action-gh-release@v1 + with: + tag_name: v${{ steps.version.outputs.VERSION_NAME }}-${{ steps.version.outputs.VERSION_CODE }} + name: Release v${{ steps.version.outputs.VERSION_NAME }} (Code ${{ steps.version.outputs.VERSION_CODE }}) + files: app/build/outputs/bundle/release/app-release.aab + generate_release_notes: true + + - name: Set up Ruby + uses: ruby/setup-ruby@v1 + with: + ruby-version: '3.2' + bundler-cache: true + + - name: Install Fastlane + run: | + gem install fastlane -NV + + - name: Deploy to Play Store + continue-on-error: true + env: + PLAY_JSON_KEY_DATA: ${{ secrets.PLAY_JSON_KEY_DATA }} + run: | + if [ -z "$PLAY_JSON_KEY_DATA" ]; then + echo "Skipping Play Store deployment: PLAY_JSON_KEY_DATA secret is not set." + exit 0 + fi + + if [ "${{ github.event_name }}" == "push" ]; then + TRACK="production" + else + TRACK="${{ github.event.inputs.track || 'internal' }}" + fi + echo "Deploying to track: $TRACK" + + case $TRACK in + internal) fastlane deploy_internal ;; + alpha) fastlane deploy_alpha ;; + beta) fastlane deploy_beta ;; + production) fastlane deploy_production ;; + *) fastlane deploy_internal ;; + esac + + - name: Build Summary + run: | + echo "## Release Summary" >> $GITHUB_STEP_SUMMARY + echo "- **Version Code**: ${{ steps.version.outputs.VERSION_CODE }}" >> $GITHUB_STEP_SUMMARY + echo "- **Version Name**: ${{ steps.version.outputs.VERSION_NAME }}" >> $GITHUB_STEP_SUMMARY + echo "- **Status**: ✅ Release built and GitHub Release created" >> $GITHUB_STEP_SUMMARY \ No newline at end of file diff --git a/.github/workflows/test-build.yml b/.github/workflows/test-build.yml new file mode 100644 index 0000000..eda14d5 --- /dev/null +++ b/.github/workflows/test-build.yml @@ -0,0 +1,159 @@ +name: Android CI/CD - Build Only + +permissions: + contents: write + +on: + push: + branches: + - main + - feature/ci-cd + workflow_dispatch: + +env: + JAVA_VERSION: '17' + JAVA_DISTRIBUTION: 'temurin' + +jobs: + build-test: + if: "!contains(github.event.head_commit.message, '[skip ci]')" + runs-on: ubuntu-latest + env: + FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true + permissions: + contents: write + + steps: + - name: Checkout code + uses: actions/checkout@v4 + with: + fetch-depth: 0 + token: ${{ secrets.GITHUB_TOKEN }} + + - name: Set up JDK + uses: actions/setup-java@v4 + with: + distribution: ${{ env.JAVA_DISTRIBUTION }} + java-version: ${{ env.JAVA_VERSION }} + + - name: Grant execute permission for gradlew + run: chmod +x gradlew + + - name: Decode Keystore + env: + KEYSTORE_BASE64: ${{ secrets.KEYSTORE_BASE64 }} + run: | + printf '%s' "$KEYSTORE_BASE64" | base64 -d > keystore.jks + echo "Keystore decoded to keystore.jks" + + - name: Verify Keystore Integrity + run: | + echo "Keystore file size: $(wc -c < keystore.jks) bytes" + echo "SHA256: $(sha256sum keystore.jks | cut -d' ' -f1)" + if [ ! -s keystore.jks ]; then + echo "ERROR: Keystore file is empty or missing!" + exit 1 + fi + echo "Keystore file exists and is non-empty" + + - name: Read current version + id: version + run: | + CURRENT_VERSION=$(grep -oP 'versionName\s*=\s*"\K[^"]+' app/build.gradle.kts) + CURRENT_VERSION_CODE=$(grep -oP 'versionCode\s*=\s*\K\d+' app/build.gradle.kts) + + if [ -z "$CURRENT_VERSION" ]; then + CURRENT_VERSION="1.0.0" + echo "Fallback version: $CURRENT_VERSION" + fi + + if [ -z "$CURRENT_VERSION_CODE" ]; then + CURRENT_VERSION_CODE=0 + echo "Fallback version code: $CURRENT_VERSION_CODE" + fi + + echo "VERSION_CODE=$CURRENT_VERSION_CODE" >> $GITHUB_OUTPUT + echo "VERSION_NAME=$CURRENT_VERSION" >> $GITHUB_OUTPUT + echo "Using version code: $CURRENT_VERSION_CODE" + echo "Using version name: $CURRENT_VERSION" + + - name: Cache Gradle packages + uses: actions/cache@v4 + with: + path: | + ~/.gradle/caches + ~/.gradle/wrapper + key: ${{ runner.os }}-gradle-${{ hashFiles('**/*.gradle*', '**/gradle-wrapper.properties') }} + restore-keys: | + ${{ runner.os }}-gradle- + + - name: Create local.properties + run: | + echo "sdk.dir=/usr/local/lib/android/sdk" > local.properties + + - name: Verify Keystore Credentials + env: + KEYSTORE_PASSWORD: ${{ secrets.KEYSTORE_PASSWORD }} + KEY_ALIAS: ${{ secrets.KEY_ALIAS }} + KEY_PASSWORD: ${{ secrets.KEY_PASSWORD }} + run: | + echo "Testing keystore with provided credentials..." + echo "KEY_ALIAS value: $KEY_ALIAS" + echo "KEYSTORE_PASSWORD length: ${#KEYSTORE_PASSWORD}" + echo "KEY_PASSWORD length: ${#KEY_PASSWORD}" + keytool -list -keystore keystore.jks -storepass "$KEYSTORE_PASSWORD" -v 2>&1 | head -20 || echo "❌ Store password is WRONG" + echo "---" + keytool -keypasswd -keystore keystore.jks -storepass "$KEYSTORE_PASSWORD" -alias "$KEY_ALIAS" -keypass "$KEY_PASSWORD" -new "$KEY_PASSWORD" 2>&1 || echo "❌ Key password or alias is WRONG" + + - name: Build AAB + env: + KEYSTORE_PATH: ${{ github.workspace }}/keystore.jks + KEYSTORE_PASSWORD: ${{ secrets.KEYSTORE_PASSWORD }} + KEY_ALIAS: ${{ secrets.KEY_ALIAS }} + KEY_PASSWORD: ${{ secrets.KEY_PASSWORD }} + VERSION_CODE: ${{ steps.version.outputs.VERSION_CODE }} + VERSION_NAME: ${{ steps.version.outputs.VERSION_NAME }} + run: | + ./gradlew bundleRelease --stacktrace + + - name: Verify AAB exists + run: | + AAB_PATH="app/build/outputs/bundle/release/app-release.aab" + if [ ! -f "$AAB_PATH" ]; then + echo "AAB not found!" + find app/build/outputs -name "*.aab" || true + exit 1 + fi + echo "AAB found: $AAB_PATH" + ls -lh "$AAB_PATH" + + - name: Upload AAB as artifact + uses: actions/upload-artifact@v4 + with: + name: app-release-${{ steps.version.outputs.VERSION_CODE }} + path: | + app/build/outputs/bundle/release/app-release.aab + retention-days: 30 + + - name: Build Summary + run: | + echo "## Build Summary" >> $GITHUB_STEP_SUMMARY + echo "" >> $GITHUB_STEP_SUMMARY + echo "- **Version Code**: ${{ steps.version.outputs.VERSION_CODE }}" >> $GITHUB_STEP_SUMMARY + echo "- **Version Name**: ${{ steps.version.outputs.VERSION_NAME }}" >> $GITHUB_STEP_SUMMARY + echo "- **Status**: ✅ Build successful" >> $GITHUB_STEP_SUMMARY + echo "" >> $GITHUB_STEP_SUMMARY + echo "The AAB artifact has been uploaded and can be downloaded from the workflow run." >> $GITHUB_STEP_SUMMARY + + - name: Notify build status + if: always() + run: | + if [ ${{ job.status }} == 'success' ]; then + echo "Build successful" + echo "Version: ${{ steps.version.outputs.VERSION_NAME }}" + echo "Download AAB from workflow artifacts" + else + echo "Build failed" + echo "Check workflow logs for details" + exit 1 + fi \ No newline at end of file diff --git a/.idea/appInsightsSettings.xml b/.idea/appInsightsSettings.xml index e90109e..371f2e2 100644 --- a/.idea/appInsightsSettings.xml +++ b/.idea/appInsightsSettings.xml @@ -1,37 +1,8 @@ -