Skip to content

Implemented the gif in the Readme #43

Implemented the gif in the Readme

Implemented the gif in the Readme #43

Workflow file for this run

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."