Implemented the gif in the Readme #43
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: 🚀 Automated APK Release | |
| on: | |
| pull_request: | |
| types: [closed, labeled] | |
| branches: [main] | |
| permissions: | |
| contents: write # For pushing commits and tags | |
| pull-requests: read # For reading PR information | |
| jobs: | |
| check-release-trigger: | |
| if: github.event.pull_request.merged == true || (github.event.action == 'labeled' && github.event.pull_request.merged == true) | |
| runs-on: ubuntu-latest | |
| outputs: | |
| should-release: ${{ steps.check.outputs.should-release }} | |
| version-bump: ${{ steps.check.outputs.version-bump }} | |
| release-notes: ${{ steps.check.outputs.release-notes }} | |
| steps: | |
| - name: Check Release Trigger | |
| id: check | |
| run: | | |
| # Check if PR has release label or [release] in title | |
| LABELS="${{ join(github.event.pull_request.labels.*.name, ' ') }}" | |
| TITLE="${{ github.event.pull_request.title }}" | |
| PR_BODY="${{ github.event.pull_request.body }}" | |
| ACTION="${{ github.event.action }}" | |
| echo "Action: $ACTION" | |
| echo "Checking labels: $LABELS" | |
| echo "Checking title: $TITLE" | |
| echo "PR merged: ${{ github.event.pull_request.merged }}" # Determine version bump type from commit messages and PR title | |
| VERSION_BUMP="patch" | |
| if [[ "$TITLE" == *"[major]"* ]] || [[ "$TITLE" == *"BREAKING CHANGE"* ]]; then | |
| VERSION_BUMP="major" | |
| elif [[ "$TITLE" == *"[minor]"* ]] || [[ "$TITLE" == *"feat"* ]] || [[ "$TITLE" == *"feature"* ]]; then | |
| VERSION_BUMP="minor" | |
| fi | |
| # Check if should release | |
| SHOULD_RELEASE=false | |
| if [[ "$LABELS" == *"release"* ]] || [[ "$TITLE" == *"[release]"* ]]; then | |
| SHOULD_RELEASE=true | |
| fi | |
| # Special handling for label events - check if release label was just added | |
| if [[ "$ACTION" == "labeled" ]]; then | |
| ADDED_LABEL="${{ github.event.label.name }}" | |
| echo "Label added: $ADDED_LABEL" | |
| if [[ "$ADDED_LABEL" != "release" ]]; then | |
| echo "Label '$ADDED_LABEL' is not a release trigger, skipping..." | |
| SHOULD_RELEASE=false | |
| fi | |
| fi | |
| # Prepare release notes from PR body | |
| RELEASE_NOTES=$(echo "$PR_BODY" | head -n 20) | |
| echo "should-release=$SHOULD_RELEASE" >> $GITHUB_OUTPUT | |
| echo "version-bump=$VERSION_BUMP" >> $GITHUB_OUTPUT | |
| echo "release-notes<<EOF" >> $GITHUB_OUTPUT | |
| echo "$RELEASE_NOTES" >> $GITHUB_OUTPUT | |
| echo "EOF" >> $GITHUB_OUTPUT | |
| build-and-release: | |
| needs: check-release-trigger | |
| if: needs.check-release-trigger.outputs.should-release == 'true' | |
| runs-on: ubuntu-latest | |
| steps: | |
| - name: 🏗️ Checkout Repository | |
| uses: actions/checkout@v4 | |
| with: | |
| token: ${{ secrets.GITHUB_TOKEN }} | |
| fetch-depth: 0 | |
| - name: 🏷️ Get Repository Name | |
| id: repo-info | |
| run: | | |
| REPO_NAME=$(echo "${{ github.repository }}" | cut -d'/' -f2) | |
| echo "name=$REPO_NAME" >> $GITHUB_OUTPUT | |
| echo "Repository name: $REPO_NAME" | |
| - name: 📦 Detect Package Manager | |
| id: package-manager | |
| run: | | |
| if [ -f "pnpm-lock.yaml" ]; then | |
| echo "manager=pnpm" >> $GITHUB_OUTPUT | |
| echo "install_cmd=pnpm install --frozen-lockfile" >> $GITHUB_OUTPUT | |
| echo "version_cmd=pnpm version" >> $GITHUB_OUTPUT | |
| echo "list_cmd=pnpm list" >> $GITHUB_OUTPUT | |
| echo "cache_path=~/.pnpm-store" >> $GITHUB_OUTPUT | |
| echo "cache_key=pnpm-${{ hashFiles('**/pnpm-lock.yaml') }}" >> $GITHUB_OUTPUT | |
| echo "🔧 Detected package manager: pnpm" | |
| elif [ -f "yarn.lock" ]; then | |
| echo "manager=yarn" >> $GITHUB_OUTPUT | |
| echo "install_cmd=yarn install --frozen-lockfile" >> $GITHUB_OUTPUT | |
| echo "version_cmd=yarn version --new-version" >> $GITHUB_OUTPUT | |
| echo "list_cmd=yarn list" >> $GITHUB_OUTPUT | |
| echo "cache_path=~/.cache/yarn" >> $GITHUB_OUTPUT | |
| echo "cache_key=yarn-${{ hashFiles('**/yarn.lock') }}" >> $GITHUB_OUTPUT | |
| echo "🔧 Detected package manager: yarn" | |
| elif [ -f "package-lock.json" ]; then | |
| echo "manager=npm" >> $GITHUB_OUTPUT | |
| echo "install_cmd=npm ci" >> $GITHUB_OUTPUT | |
| echo "version_cmd=npm version" >> $GITHUB_OUTPUT | |
| echo "list_cmd=npm list" >> $GITHUB_OUTPUT | |
| echo "cache_path=~/.npm" >> $GITHUB_OUTPUT | |
| echo "cache_key=npm-${{ hashFiles('**/package-lock.json') }}" >> $GITHUB_OUTPUT | |
| echo "🔧 Detected package manager: npm" | |
| else | |
| echo "manager=npm" >> $GITHUB_OUTPUT | |
| echo "install_cmd=npm install" >> $GITHUB_OUTPUT | |
| echo "version_cmd=npm version" >> $GITHUB_OUTPUT | |
| echo "list_cmd=npm list" >> $GITHUB_OUTPUT | |
| echo "cache_path=~/.npm" >> $GITHUB_OUTPUT | |
| echo "cache_key=npm-${{ hashFiles('**/package.json') }}" >> $GITHUB_OUTPUT | |
| echo "⚠️ No lock file detected, defaulting to npm" | |
| fi | |
| - name: 🔍 Check for Existing Release | |
| id: check-existing | |
| run: | | |
| # Get current version first | |
| CURRENT_VERSION=$(node -p "require('./package.json').version") | |
| BUMP_TYPE="${{ needs.check-release-trigger.outputs.version-bump }}" | |
| # Parse current version | |
| IFS='.' read -ra VERSION_PARTS <<< "$CURRENT_VERSION" | |
| MAJOR=${VERSION_PARTS[0]} | |
| MINOR=${VERSION_PARTS[1]} | |
| PATCH=${VERSION_PARTS[2]} | |
| # Calculate new version | |
| case $BUMP_TYPE in | |
| "major") | |
| MAJOR=$((MAJOR + 1)) | |
| MINOR=0 | |
| PATCH=0 | |
| ;; | |
| "minor") | |
| MINOR=$((MINOR + 1)) | |
| PATCH=0 | |
| ;; | |
| "patch") | |
| PATCH=$((PATCH + 1)) | |
| ;; | |
| esac | |
| NEW_VERSION="${MAJOR}.${MINOR}.${PATCH}" | |
| # Check if tag already exists | |
| if git rev-parse "v$NEW_VERSION" >/dev/null 2>&1; then | |
| echo "⚠️ Tag v$NEW_VERSION already exists!" | |
| echo "existing=true" >> $GITHUB_OUTPUT | |
| # Check if GitHub release exists | |
| RELEASE_EXISTS=$(gh release view "v$NEW_VERSION" >/dev/null 2>&1 && echo "true" || echo "false") | |
| echo "github-release-exists=$RELEASE_EXISTS" >> $GITHUB_OUTPUT | |
| echo "skip-build=true" >> $GITHUB_OUTPUT | |
| else | |
| echo "✅ No existing release found for v$NEW_VERSION" | |
| echo "existing=false" >> $GITHUB_OUTPUT | |
| echo "github-release-exists=false" >> $GITHUB_OUTPUT | |
| echo "skip-build=false" >> $GITHUB_OUTPUT | |
| fi | |
| env: | |
| GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
| - name: 🔧 Setup Node.js | |
| uses: actions/setup-node@v4 | |
| with: | |
| node-version: '20' | |
| cache: ${{ steps.package-manager.outputs.manager }} | |
| cache-dependency-path: | | |
| package.json | |
| ${{ steps.package-manager.outputs.manager == 'yarn' && 'yarn.lock' || '' }} | |
| ${{ steps.package-manager.outputs.manager == 'npm' && 'package-lock.json' || '' }} | |
| ${{ steps.package-manager.outputs.manager == 'pnpm' && 'pnpm-lock.yaml' || '' }} | |
| - name: 💾 Cache Dependencies | |
| uses: actions/cache@v4 | |
| with: | |
| path: | | |
| node_modules | |
| ${{ steps.package-manager.outputs.cache_path }} | |
| key: ${{ runner.os }}-${{ steps.package-manager.outputs.cache_key }} | |
| restore-keys: | | |
| ${{ runner.os }}-${{ steps.package-manager.outputs.manager }}- | |
| - name: ☕ Setup Java JDK | |
| uses: actions/setup-java@v4 | |
| with: | |
| distribution: 'zulu' | |
| java-version: '17' | |
| - name: 📦 Setup PNPM (if detected) | |
| if: steps.package-manager.outputs.manager == 'pnpm' | |
| uses: pnpm/action-setup@v2 | |
| with: | |
| version: latest | |
| - name: 📥 Install Dependencies | |
| run: | | |
| echo "Installing dependencies with ${{ steps.package-manager.outputs.manager }}..." | |
| # Install dependencies based on detected package manager | |
| ${{ steps.package-manager.outputs.install_cmd }} | |
| # Verify React Native plugin is installed | |
| echo "Checking for React Native Gradle plugin..." | |
| if [ -d "node_modules/@react-native/gradle-plugin" ]; then | |
| echo "✅ React Native Gradle plugin found" | |
| ls -la node_modules/@react-native/gradle-plugin | |
| else | |
| echo "❌ React Native Gradle plugin NOT found" | |
| echo "Available @react-native packages:" | |
| ls -la node_modules/@react-native/ || echo "No @react-native directory found" | |
| fi | |
| # List specific package with the detected package manager | |
| ${{ steps.package-manager.outputs.list_cmd }} --pattern "@react-native/gradle-plugin" 2>/dev/null || echo "Package listing completed" | |
| - name: �🔍 Get Current Version | |
| id: current-version | |
| run: | | |
| CURRENT_VERSION=$(node -p "require('./package.json').version") | |
| echo "current=$CURRENT_VERSION" >> $GITHUB_OUTPUT | |
| echo "Current version: $CURRENT_VERSION" | |
| - name: 📈 Calculate New Version | |
| id: new-version | |
| run: | | |
| CURRENT="${{ steps.current-version.outputs.current }}" | |
| BUMP_TYPE="${{ needs.check-release-trigger.outputs.version-bump }}" | |
| # Parse current version | |
| IFS='.' read -ra VERSION_PARTS <<< "$CURRENT" | |
| MAJOR=${VERSION_PARTS[0]} | |
| MINOR=${VERSION_PARTS[1]} | |
| PATCH=${VERSION_PARTS[2]} | |
| # Calculate new version based on bump type | |
| case $BUMP_TYPE in | |
| "major") | |
| MAJOR=$((MAJOR + 1)) | |
| MINOR=0 | |
| PATCH=0 | |
| ;; | |
| "minor") | |
| MINOR=$((MINOR + 1)) | |
| PATCH=0 | |
| ;; | |
| "patch") | |
| PATCH=$((PATCH + 1)) | |
| ;; | |
| esac | |
| NEW_VERSION="${MAJOR}.${MINOR}.${PATCH}" | |
| echo "new=$NEW_VERSION" >> $GITHUB_OUTPUT | |
| echo "New version: $NEW_VERSION" | |
| - name: 📝 Update Version in Files | |
| if: steps.check-existing.outputs.skip-build == 'false' | |
| run: | | |
| NEW_VERSION="${{ steps.new-version.outputs.new }}" | |
| # Update package.json using the detected package manager | |
| if [[ "${{ steps.package-manager.outputs.manager }}" == "yarn" ]]; then | |
| yarn version --new-version $NEW_VERSION --no-git-tag-version | |
| elif [[ "${{ steps.package-manager.outputs.manager }}" == "pnpm" ]]; then | |
| pnpm version $NEW_VERSION --no-git-tag-version | |
| else | |
| npm version $NEW_VERSION --no-git-tag-version | |
| fi | |
| # Update Android versionName and versionCode | |
| VERSION_CODE=$(grep "versionCode" android/app/build.gradle | grep -o '[0-9]\+') | |
| NEW_VERSION_CODE=$((VERSION_CODE + 1)) | |
| sed -i "s/versionCode $VERSION_CODE/versionCode $NEW_VERSION_CODE/" android/app/build.gradle | |
| sed -i "s/versionName \".*\"/versionName \"$NEW_VERSION\"/" android/app/build.gradle | |
| echo "Updated version to $NEW_VERSION (code: $NEW_VERSION_CODE)" | |
| - name: ⚠️ Release Already Exists | |
| if: steps.check-existing.outputs.skip-build == 'true' | |
| run: | | |
| echo "## ⚠️ Release Already Exists" | |
| echo "A release for this version already exists. Skipping build to prevent duplicates." | |
| echo "If you need to rebuild, please:" | |
| echo "1. Delete the existing tag and release, OR" | |
| echo "2. Increment the version manually and re-trigger" | |
| - name: 🔑 Setup Keystore | |
| if: steps.check-existing.outputs.skip-build == 'false' | |
| run: | | |
| # Create keystore from secrets | |
| echo "${{ secrets.RELEASE_KEYSTORE_BASE64 }}" | base64 -d > android/app/release-key.jks | |
| # Create keystore.properties | |
| cat > android/keystore.properties << EOF | |
| storeFile=release-key.jks | |
| storePassword=${{ secrets.RELEASE_STORE_PASSWORD }} | |
| keyAlias=${{ secrets.RELEASE_KEY_ALIAS }} | |
| keyPassword=${{ secrets.RELEASE_KEY_PASSWORD }} | |
| EOF | |
| # Create google-services.json from secrets | |
| if [ -n "${{ secrets.GOOGLE_SERVICES_BASE64 }}" ]; then | |
| echo "${{ secrets.GOOGLE_SERVICES_BASE64 }}" | base64 -d > android/app/google-services.json | |
| echo "✅ Google Services configuration created" | |
| else | |
| echo "⚠️ GOOGLE_SERVICES_BASE64 secret not found. Using template or existing file..." | |
| if [ ! -f "android/app/google-services.json" ]; then | |
| echo "❌ No google-services.json found and no secret provided. Build will fail." | |
| echo "Please add GOOGLE_SERVICES_BASE64 secret to your repository secrets." | |
| exit 1 | |
| fi | |
| fi | |
| - name: 🌍 Setup Environment Variables | |
| if: steps.check-existing.outputs.skip-build == 'false' | |
| run: | | |
| echo "🔧 Setting up environment variables..." | |
| # Create .env file for the build process | |
| cat > .env << EOF | |
| GOOGLE_GEMINI_API_KEY=${{ secrets.GOOGLE_GEMINI_API_KEY }} | |
| EOF | |
| echo "✅ Environment variables configured" | |
| echo "📝 Created .env file with:" | |
| echo " - GOOGLE_WEB_CLIENT_ID (masked)" | |
| echo " - GOOGLE_GEMINI_API_KEY (masked)" | |
| - name: � Generate React Native Codegen | |
| if: steps.check-existing.outputs.skip-build == 'false' | |
| run: | | |
| echo "🔧 Generating React Native Codegen artifacts..." | |
| # Generate codegen for React Native libraries | |
| cd android | |
| chmod +x gradlew | |
| # Clean first to ensure fresh codegen | |
| ./gradlew clean | |
| # Generate codegen artifacts for all autolinked libraries | |
| echo "Generating codegen artifacts from schema..." | |
| ./gradlew generateCodegenArtifactsFromSchema --stacktrace --debug || { | |
| echo "⚠️ Primary codegen generation failed, trying alternative approach..." | |
| # Try generating codegen for individual packages | |
| echo "Generating codegen for individual packages..." | |
| ./gradlew :generateCodegenSchemaFromJavaScript --stacktrace || echo "Schema generation completed with warnings" | |
| } | |
| # Verify codegen directories were created | |
| echo "Verifying codegen artifacts..." | |
| find ../node_modules -name "codegen" -type d | head -10 | |
| echo "✅ Codegen generation completed" | |
| - name: 🔨 Build Release APK | |
| if: steps.check-existing.outputs.skip-build == 'false' | |
| run: | | |
| # Ensure node_modules exists and has React Native plugin | |
| if [ ! -d "node_modules/@react-native/gradle-plugin" ]; then | |
| echo "❌ React Native Gradle plugin not found in node_modules!" | |
| echo "Available in node_modules/@react-native/:" | |
| ls -la node_modules/@react-native/ || echo "Directory not found" | |
| exit 1 | |
| fi | |
| # Build Android APK with codegen artifacts | |
| echo "Building Android APK with codegen..." | |
| cd android | |
| chmod +x gradlew | |
| ./gradlew assembleRelease --stacktrace | |
| # Verify APK was created | |
| if [ ! -f "app/build/outputs/apk/release/app-release.apk" ]; then | |
| echo "❌ APK build failed!" | |
| exit 1 | |
| fi | |
| # Rename APK with version | |
| NEW_VERSION="${{ steps.new-version.outputs.new }}" | |
| REPO_NAME="${{ steps.repo-info.outputs.name }}" | |
| cp app/build/outputs/apk/release/app-release.apk app/build/outputs/apk/release/${REPO_NAME}-v${NEW_VERSION}.apk | |
| # Get APK info | |
| APK_SIZE=$(du -h app/build/outputs/apk/release/${REPO_NAME}-v${NEW_VERSION}.apk | cut -f1) | |
| echo "✅ APK built successfully! Size: $APK_SIZE" | |
| - name: 🏷️ Create Git Tag | |
| if: steps.check-existing.outputs.skip-build == 'false' | |
| run: | | |
| NEW_VERSION="${{ steps.new-version.outputs.new }}" | |
| git config user.name "github-actions[bot]" | |
| git config user.email "github-actions[bot]@users.noreply.github.com" | |
| git add package.json android/app/build.gradle | |
| git commit -m "🔖 Release version v$NEW_VERSION" | |
| git tag -a "v$NEW_VERSION" -m "Release version v$NEW_VERSION" | |
| git push origin main | |
| git push origin "v$NEW_VERSION" | |
| - name: 📝 Generate Release Notes | |
| if: steps.check-existing.outputs.skip-build == 'false' | |
| id: release-notes | |
| run: | | |
| NEW_VERSION="${{ steps.new-version.outputs.new }}" | |
| CURRENT_VERSION="${{ steps.current-version.outputs.current }}" | |
| # Create comprehensive release notes | |
| cat > release-notes.md << EOF | |
| ## 🚀 What's New in v$NEW_VERSION | |
| ### 📋 Changes from PR #${{ github.event.pull_request.number }} | |
| **${{ github.event.pull_request.title }}** | |
| ${{ needs.check-release-trigger.outputs.release-notes }} | |
| ### 📊 Release Information | |
| - **Previous Version:** v$CURRENT_VERSION | |
| - **New Version:** v$NEW_VERSION | |
| - **Version Bump:** ${{ needs.check-release-trigger.outputs.version-bump }} | |
| - **Build Date:** $(date -u +"%Y-%m-%d %H:%M:%S UTC") | |
| - **Commit SHA:** ${{ github.sha }} | |
| ### 💻 Installation | |
| 1. Download the APK file below | |
| 2. Enable "Install from Unknown Sources" in Android settings | |
| 3. Install the APK on your device | |
| ### 🔧 Technical Details | |
| - **Min SDK:** 21 (Android 5.0) | |
| - **Target SDK:** 34 (Android 14) | |
| - **Architecture:** Universal APK | |
| - **Signed:** Yes ✅ | |
| --- | |
| *This release was automatically generated by GitHub Actions* | |
| EOF | |
| echo "Release notes generated successfully" | |
| - name: 🎉 Create GitHub Release | |
| if: steps.check-existing.outputs.skip-build == 'false' | |
| uses: softprops/action-gh-release@v1 | |
| with: | |
| tag_name: v${{ steps.new-version.outputs.new }} | |
| name: 🚀 ${{ steps.repo-info.outputs.name }} v${{ steps.new-version.outputs.new }} | |
| body_path: release-notes.md | |
| files: | | |
| android/app/build/outputs/apk/release/${{ steps.repo-info.outputs.name }}-v${{ steps.new-version.outputs.new }}.apk | |
| draft: false | |
| prerelease: false | |
| generate_release_notes: true | |
| env: | |
| GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
| - name: 🧹 Cleanup | |
| if: always() | |
| run: | | |
| # Remove sensitive files | |
| rm -f android/app/release-key.jks | |
| rm -f android/keystore.properties | |
| rm -f android/app/google-services.json | |
| rm -f .env | |
| echo "🧹 Cleanup completed" | |
| - name: 📱 Post-Release Summary | |
| if: steps.check-existing.outputs.skip-build == 'false' | |
| run: | | |
| NEW_VERSION="${{ steps.new-version.outputs.new }}" | |
| REPO_NAME="${{ steps.repo-info.outputs.name }}" | |
| APK_PATH="android/app/build/outputs/apk/release/${REPO_NAME}-v${NEW_VERSION}.apk" | |
| APK_SIZE=$(du -h "$APK_PATH" | cut -f1) | |
| echo "## 🎉 Release Summary" | |
| echo "✅ **Repository:** $REPO_NAME" | |
| echo "✅ **Version:** v$NEW_VERSION" | |
| echo "✅ **Package Manager:** ${{ steps.package-manager.outputs.manager }}" | |
| echo "✅ **APK Size:** $APK_SIZE" | |
| echo "✅ **Release URL:** ${{ github.server_url }}/${{ github.repository }}/releases/tag/v$NEW_VERSION" | |
| echo "✅ **Status:** Successfully Released!" | |
| notify-failure: | |
| needs: [check-release-trigger, build-and-release] | |
| if: failure() && needs.check-release-trigger.outputs.should-release == 'true' | |
| runs-on: ubuntu-latest | |
| steps: | |
| - name: 📢 Notify Build Failure | |
| run: | | |
| echo "❌ Release build failed for PR #${{ github.event.pull_request.number }}" | |
| echo "Please check the workflow logs and fix the issues." |