Make App Screenshots collapsible & add Back to Top button at bottom of README #33
Workflow file for this run
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: 🔍 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: 'yarn' | |
| - name: 💾 Cache node_modules | |
| uses: actions/cache@v4 | |
| with: | |
| path: | | |
| node_modules | |
| ~/.cache/yarn | |
| key: ${{ runner.os }}-yarn-${{ hashFiles('**/package.json') }} | |
| restore-keys: | | |
| ${{ runner.os }}-yarn- | |
| - name: ☕ Setup Java JDK | |
| uses: actions/setup-java@v4 | |
| with: | |
| distribution: 'zulu' | |
| java-version: '17' | |
| - name: � Install Dependencies | |
| run: | | |
| echo "Installing dependencies with Yarn..." | |
| # Check if yarn.lock exists | |
| if [ -f "yarn.lock" ]; then | |
| echo "yarn.lock found, using frozen lockfile" | |
| yarn install --frozen-lockfile | |
| else | |
| echo "No yarn.lock found, generating new one" | |
| yarn install | |
| fi | |
| # 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 | |
| yarn list --pattern "@react-native/gradle-plugin" # List specific package | |
| - 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 | |
| yarn version --new-version $NEW_VERSION --no-git-tag-version | |
| # 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 | |
| - 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 }}" | |
| cp app/build/outputs/apk/release/app-release.apk app/build/outputs/apk/release/ScheduleX-v${NEW_VERSION}.apk | |
| # Get APK info | |
| APK_SIZE=$(du -h app/build/outputs/apk/release/ScheduleX-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: 🚀 ScheduleX v${{ steps.new-version.outputs.new }} | |
| body_path: release-notes.md | |
| files: | | |
| android/app/build/outputs/apk/release/ScheduleX-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 | |
| echo "🧹 Cleanup completed" | |
| - name: 📱 Post-Release Summary | |
| if: steps.check-existing.outputs.skip-build == 'false' | |
| run: | | |
| NEW_VERSION="${{ steps.new-version.outputs.new }}" | |
| APK_PATH="android/app/build/outputs/apk/release/ScheduleX-v${NEW_VERSION}.apk" | |
| APK_SIZE=$(du -h "$APK_PATH" | cut -f1) | |
| echo "## 🎉 Release Summary" | |
| echo "✅ **Version:** v$NEW_VERSION" | |
| 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." |