mirror of
https://github.com/selfxyz/self.git
synced 2026-01-09 14:48:06 -05:00
Feat/mobile deployment automation (#759)
* feat: add version management system with build number tracking - Add version.json to track iOS/Android build numbers separately - Create version.cjs script for build number management - Add Fastlane version_manager.rb helper - Keep npm version for semver, version.json for build tracking * feat: integrate version.json with Fastlane deployment process ## What Changed - Updated iOS and Android Fastlane lanes to use version.json for build number management - Added automatic build number increment on deployment - Added deployment timestamp tracking ## How It Works ### iOS Deployment 1. Reads current build number from version.json 2. Increments iOS build number (e.g., 148 → 149) 3. Updates Xcode project with new build number via increment_build_number 4. Proceeds with TestFlight deployment 5. Updates lastDeployed timestamp on successful upload ### Android Deployment 1. Reads current build number from version.json 2. Increments Android build number (e.g., 82 → 83) 3. Updates build.gradle with new version code via increment_version_code 4. Proceeds with Play Store deployment 5. Updates lastDeployed timestamp on successful upload ## Why This Change - Eliminates manual version/build number entry - Prevents version conflicts between deployments - Provides single source of truth for build numbers - Enables automatic deployments without human intervention - Tracks deployment history with timestamps ## Dependencies - Requires version.json file (already created in previous commit) - Uses existing Fastlane plugins: - increment_build_number (iOS - built-in) - increment_version_code (Android - from plugin) - Version numbers still managed by npm version command * feat: enhance deploy confirmation with version.json info * fix: use ENV variable directly in increment_build_number to avoid secret masking * fix: correct xcodeproj path for GitHub Actions workflow * feat: add test mode to workflow for safe testing - Skip store uploads when test_mode is true - Test version bumps and builds without deployment - Prevent accidental pushes to TestFlight/Play Store * fix: use gradle_file_path instead of gradle_file for increment_version_code * fix: use gsub to remove ../ prefix for CI compatibility * chore: remove accidentally committed files - Remove .cursor/mcp.json - Remove .cursorignore - Remove deployment-automation-summary.md - Remove deployment-meeting-questions.md - Remove pipeline.md * feat: auto-commit version.json after successful deployment - Commits version.json changes back to repository - Only runs when test_mode is false - Uses [skip ci] to prevent infinite loops - Checks for actual changes before committing * feat : update package.json in build step using npm version * feat: add comprehensive caching to mobile deployment workflow - Add caching for Yarn dependencies, Ruby gems, CocoaPods, Gradle, and Android NDK - Implement cache versioning strategy for easy cache invalidation - Fix cache order: caches now restored after checkout but before dependency installation - Update mobile-setup action to skip installs when dependencies are cached - Add cache size monitoring to track usage against GitHub's 10GB limit - Fix Slack notification bug: skip notifications in test_mode - Add detailed logging for package.json version updates (show from/to versions) Expected performance improvement: ~50% faster builds (from ~15min to ~7-10min) * fix: move bundler config after Ruby setup in mobile-setup action * fix: rename cache env vars to avoid Yarn conflicts Yarn was interpreting YARN_CACHE_VERSION as its own config setting. Prefixed all cache version env vars with GH_ to avoid conflicts. * fix: remove bundler deployment mode to allow Gemfile updates The deployment mode was causing bundler to fail when Gemfile changed (nokogiri was removed). CI should be able to update the lockfile as needed. * feat: implement strict lock file enforcement (Option 1) - Re-enable bundler deployment mode for strict Gemfile.lock checking - Use yarn install --immutable for strict yarn.lock checking - Add clear error messages when lock files are out of date - Add pre-checks to verify lock files exist - This ensures reproducible builds and makes caching maximally effective When developers change dependencies, they must now: 1. Run yarn install or bundle install locally 2. Commit the updated lock files 3. CI will fail with helpful instructions if they forget * fix: update Gemfile.lock for CI environment Remove nokogiri from Gemfile.lock since it's excluded in CI environments (GITHUB_ACTIONS=true). This allows the strict lock file checks to pass in CI. * fix: correct yarn.lock path for monorepo workspace The project uses Yarn workspaces with yarn.lock at the repository root, not in the app directory. Updated paths to check for yarn.lock at workspace root and use it for cache keys. * fix: handle both boolean and string test_mode parameter The test_mode parameter was only checking for string 'true' but could be passed as boolean true from command line. Now handles both cases to ensure test mode works correctly for iOS and Android. * fix: address code review feedback for mobile deployment workflow - Replace jq with Node.js for version extraction (jq not available on macOS runners) - Fix concurrent commit race condition by creating separate update-version job - Add platform validation to version_manager.rb and version.cjs scripts - Use POSIX-compatible single = for shell string comparisons - Ensure single atomic commit when deploying to both platforms * fix: formatting and linting issues - Remove trailing spaces from workflow YAML file - Fix prettier formatting in JavaScript files - Add -y flag to yarn version command for non-interactive mode - Address all lint warnings from CI --------- Co-authored-by: Jayaditya Gupta <nightmare@Jayadityas-MacBook-Pro.local>
This commit is contained in:
46
.github/actions/mobile-setup/action.yml
vendored
46
.github/actions/mobile-setup/action.yml
vendored
@@ -47,11 +47,55 @@ runs:
|
||||
with:
|
||||
node-version: ${{ inputs.node_version }}
|
||||
|
||||
- name: Configure bundler
|
||||
shell: bash
|
||||
run: |
|
||||
cd ${{ inputs.app_path }}
|
||||
bundle config set --local path 'vendor/bundle'
|
||||
bundle config set --local deployment 'true'
|
||||
echo "✅ Bundler configured for strict mode (deployment=true)"
|
||||
|
||||
- name: Install app dependencies
|
||||
shell: bash
|
||||
run: |
|
||||
cd ${{ inputs.app_path }}
|
||||
|
||||
# Configure Yarn
|
||||
corepack enable
|
||||
yarn set version 4.6.0
|
||||
yarn install
|
||||
|
||||
echo "📦 Installing JavaScript dependencies with strict lock file..."
|
||||
if ! yarn install --immutable; then
|
||||
echo ""
|
||||
echo "❌ ERROR: yarn.lock is out of date!"
|
||||
echo ""
|
||||
echo "This happens when package.json was modified but yarn.lock wasn't updated."
|
||||
echo ""
|
||||
echo "To fix this:"
|
||||
echo " 1. Run 'yarn install' locally in the app directory"
|
||||
echo " 2. Commit the updated yarn.lock file"
|
||||
echo " 3. Push your changes"
|
||||
echo ""
|
||||
echo "This ensures everyone has the exact same dependency versions."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Run mobile-specific installation
|
||||
yarn install-app:mobile-deploy
|
||||
|
||||
# Install Ruby gems with bundler (respecting cache)
|
||||
echo "📦 Installing Ruby gems with strict lock file..."
|
||||
if ! bundle install --jobs 4 --retry 3; then
|
||||
echo ""
|
||||
echo "❌ ERROR: Gemfile.lock is out of date!"
|
||||
echo ""
|
||||
echo "This happens when Gemfile was modified but Gemfile.lock wasn't updated."
|
||||
echo ""
|
||||
echo "To fix this:"
|
||||
echo " 1. Run 'bundle install' locally in the app directory"
|
||||
echo " 2. Commit the updated Gemfile.lock file"
|
||||
echo " 3. Push your changes"
|
||||
echo ""
|
||||
echo "This ensures everyone has the exact same gem versions."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
302
.github/workflows/mobile-deploy.yml
vendored
302
.github/workflows/mobile-deploy.yml
vendored
@@ -8,6 +8,13 @@ env:
|
||||
ANDROID_API_LEVEL: 35
|
||||
ANDROID_NDK_VERSION: 26.1.10909125
|
||||
|
||||
# Cache versioning - increment these to bust caches when needed
|
||||
GH_CACHE_VERSION: v1 # Global cache version
|
||||
GH_YARN_CACHE_VERSION: v1 # Yarn-specific cache version
|
||||
GH_GEMS_CACHE_VERSION: v1 # Ruby gems cache version
|
||||
GH_PODS_CACHE_VERSION: v1 # CocoaPods cache version
|
||||
GH_GRADLE_CACHE_VERSION: v1 # Gradle cache version
|
||||
|
||||
# Path configuration
|
||||
WORKSPACE: ${{ github.workspace }}
|
||||
APP_PATH: ${{ github.workspace }}/app
|
||||
@@ -36,6 +43,11 @@ on:
|
||||
- ios
|
||||
- android
|
||||
- both
|
||||
test_mode:
|
||||
description: "Test mode (skip upload to stores)"
|
||||
required: false
|
||||
type: boolean
|
||||
default: false
|
||||
|
||||
jobs:
|
||||
build-ios:
|
||||
@@ -52,6 +64,9 @@ jobs:
|
||||
echo "🚀 Mobile deployment is enabled - proceeding with iOS build"
|
||||
fi
|
||||
|
||||
- uses: actions/checkout@v4
|
||||
if: github.event_name == 'workflow_dispatch' && github.event.inputs.platform != 'android'
|
||||
|
||||
- name: Set up Xcode
|
||||
if: github.event_name == 'workflow_dispatch' && github.event.inputs.platform != 'android'
|
||||
uses: maxim-lobanov/setup-xcode@v1
|
||||
@@ -59,8 +74,64 @@ jobs:
|
||||
# # some cocoapods won't compile with xcode 16.3
|
||||
# xcode-version: "16.2"
|
||||
|
||||
- uses: actions/checkout@v4
|
||||
if: github.event_name == 'workflow_dispatch' && github.event.inputs.platform != 'android'
|
||||
- name: Cache Yarn dependencies
|
||||
id: yarn-cache
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
path: |
|
||||
${{ env.APP_PATH }}/node_modules
|
||||
~/.cache/yarn
|
||||
key: ${{ runner.os }}-yarn-${{ env.GH_CACHE_VERSION }}-${{ env.GH_YARN_CACHE_VERSION }}-${{ hashFiles('yarn.lock') }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-yarn-${{ env.GH_CACHE_VERSION }}-${{ env.GH_YARN_CACHE_VERSION }}-
|
||||
${{ runner.os }}-yarn-${{ env.GH_CACHE_VERSION }}-
|
||||
|
||||
- name: Cache Ruby gems
|
||||
id: gems-cache
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
path: ${{ env.APP_PATH }}/vendor/bundle
|
||||
key: ${{ runner.os }}-gems-${{ env.GH_CACHE_VERSION }}-${{ env.GH_GEMS_CACHE_VERSION }}-${{ hashFiles('app/Gemfile.lock') }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-gems-${{ env.GH_CACHE_VERSION }}-${{ env.GH_GEMS_CACHE_VERSION }}-
|
||||
${{ runner.os }}-gems-${{ env.GH_CACHE_VERSION }}-
|
||||
|
||||
- name: Cache CocoaPods
|
||||
id: pods-cache
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
path: ${{ env.APP_PATH }}/ios/Pods
|
||||
key: ${{ runner.os }}-pods-${{ env.GH_CACHE_VERSION }}-${{ env.GH_PODS_CACHE_VERSION }}-${{ hashFiles('app/ios/Podfile.lock') }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-pods-${{ env.GH_CACHE_VERSION }}-${{ env.GH_PODS_CACHE_VERSION }}-
|
||||
${{ runner.os }}-pods-${{ env.GH_CACHE_VERSION }}-
|
||||
|
||||
- name: Log cache status
|
||||
run: |
|
||||
echo "Cache hit results:"
|
||||
echo "- Yarn cache hit: ${{ steps.yarn-cache.outputs.cache-hit }}"
|
||||
echo "- Gems cache hit: ${{ steps.gems-cache.outputs.cache-hit }}"
|
||||
echo "- Pods cache hit: ${{ steps.pods-cache.outputs.cache-hit }}"
|
||||
|
||||
- name: Verify lock files are up to date
|
||||
run: |
|
||||
echo "🔍 Checking if lock files are in sync with dependency files..."
|
||||
|
||||
# For yarn workspaces, yarn.lock is at root
|
||||
if [ ! -f "${{ env.WORKSPACE }}/yarn.lock" ]; then
|
||||
echo "❌ ERROR: yarn.lock file is missing at workspace root!"
|
||||
echo "Run 'yarn install' at the repository root and commit the yarn.lock file."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Gemfile.lock is in app directory
|
||||
if [ ! -f "${{ env.APP_PATH }}/Gemfile.lock" ]; then
|
||||
echo "❌ ERROR: Gemfile.lock file is missing!"
|
||||
echo "Run 'bundle install' in the app directory and commit the Gemfile.lock file."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "✅ Lock files exist"
|
||||
|
||||
- name: Install Mobile Dependencies
|
||||
if: github.event_name == 'workflow_dispatch' && github.event.inputs.platform != 'android'
|
||||
@@ -310,8 +381,15 @@ jobs:
|
||||
echo "Identities in build.keychain:"
|
||||
security find-identity -v -p codesigning build.keychain || echo "Failed to find identities in build.keychain"
|
||||
echo "--- Starting Fastlane ---"
|
||||
echo "🚀 Uploading to App Store Connect/TestFlight..."
|
||||
bundle exec fastlane ios internal_test --verbose
|
||||
if [ "${{ github.event.inputs.test_mode }}" = "true" ]; then
|
||||
echo "🧪 Running in TEST MODE - will skip upload to TestFlight"
|
||||
bundle exec fastlane ios internal_test --verbose test_mode:true
|
||||
else
|
||||
echo "🚀 Uploading to App Store Connect/TestFlight..."
|
||||
bundle exec fastlane ios internal_test --verbose
|
||||
fi
|
||||
|
||||
# Version updates moved to separate job to avoid race conditions
|
||||
|
||||
- name: Remove project.pbxproj updates we don't want to commit
|
||||
if: github.event_name == 'workflow_dispatch' && github.event.inputs.platform != 'android'
|
||||
@@ -369,6 +447,30 @@ jobs:
|
||||
commit_message: "incrementing ios build number for version ${{ env.VERSION }}"
|
||||
commit_paths: "./app/ios/OpenPassport/Info.plist ./app/ios/Self.xcodeproj/project.pbxproj"
|
||||
|
||||
- name: Monitor cache usage
|
||||
if: always()
|
||||
run: |
|
||||
echo "📊 Cache Size Report (iOS Build)"
|
||||
echo "================================"
|
||||
|
||||
if [ -d "${{ env.APP_PATH }}/node_modules" ]; then
|
||||
NODE_SIZE=$(du -sh "${{ env.APP_PATH }}/node_modules" | cut -f1)
|
||||
echo "Node modules: $NODE_SIZE"
|
||||
fi
|
||||
|
||||
if [ -d "${{ env.APP_PATH }}/vendor/bundle" ]; then
|
||||
GEMS_SIZE=$(du -sh "${{ env.APP_PATH }}/vendor/bundle" | cut -f1)
|
||||
echo "Ruby gems: $GEMS_SIZE"
|
||||
fi
|
||||
|
||||
if [ -d "${{ env.APP_PATH }}/ios/Pods" ]; then
|
||||
PODS_SIZE=$(du -sh "${{ env.APP_PATH }}/ios/Pods" | cut -f1)
|
||||
echo "CocoaPods: $PODS_SIZE"
|
||||
fi
|
||||
|
||||
echo "================================"
|
||||
echo "💡 GitHub Actions cache limit: 10GB per repository"
|
||||
|
||||
build-android:
|
||||
runs-on: ubuntu-latest
|
||||
if: github.event_name == 'workflow_dispatch' && (github.event.inputs.platform == 'android' || github.event.inputs.platform == 'both')
|
||||
@@ -386,6 +488,75 @@ jobs:
|
||||
- uses: actions/checkout@v4
|
||||
if: github.event_name == 'workflow_dispatch' && github.event.inputs.platform != 'ios'
|
||||
|
||||
- name: Cache Yarn dependencies
|
||||
id: yarn-cache
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
path: |
|
||||
${{ env.APP_PATH }}/node_modules
|
||||
~/.cache/yarn
|
||||
key: ${{ runner.os }}-yarn-${{ env.GH_CACHE_VERSION }}-${{ env.GH_YARN_CACHE_VERSION }}-${{ hashFiles('yarn.lock') }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-yarn-${{ env.GH_CACHE_VERSION }}-${{ env.GH_YARN_CACHE_VERSION }}-
|
||||
${{ runner.os }}-yarn-${{ env.GH_CACHE_VERSION }}-
|
||||
|
||||
- name: Cache Ruby gems
|
||||
id: gems-cache
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
path: ${{ env.APP_PATH }}/vendor/bundle
|
||||
key: ${{ runner.os }}-gems-${{ env.GH_CACHE_VERSION }}-${{ env.GH_GEMS_CACHE_VERSION }}-${{ hashFiles('app/Gemfile.lock') }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-gems-${{ env.GH_CACHE_VERSION }}-${{ env.GH_GEMS_CACHE_VERSION }}-
|
||||
${{ runner.os }}-gems-${{ env.GH_CACHE_VERSION }}-
|
||||
|
||||
- name: Cache Gradle dependencies
|
||||
id: gradle-cache
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
path: |
|
||||
~/.gradle/caches
|
||||
~/.gradle/wrapper
|
||||
key: ${{ runner.os }}-gradle-${{ env.GH_CACHE_VERSION }}-${{ env.GH_GRADLE_CACHE_VERSION }}-${{ hashFiles('app/android/**/*.gradle*', 'app/android/gradle/wrapper/gradle-wrapper.properties') }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-gradle-${{ env.GH_CACHE_VERSION }}-${{ env.GH_GRADLE_CACHE_VERSION }}-
|
||||
${{ runner.os }}-gradle-${{ env.GH_CACHE_VERSION }}-
|
||||
|
||||
- name: Cache Android NDK
|
||||
id: ndk-cache
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
path: ${{ env.ANDROID_SDK_ROOT }}/ndk/${{ env.ANDROID_NDK_VERSION }}
|
||||
key: ${{ runner.os }}-ndk-${{ env.ANDROID_NDK_VERSION }}
|
||||
|
||||
- name: Log cache status
|
||||
run: |
|
||||
echo "Cache hit results:"
|
||||
echo "- Yarn cache hit: ${{ steps.yarn-cache.outputs.cache-hit }}"
|
||||
echo "- Gems cache hit: ${{ steps.gems-cache.outputs.cache-hit }}"
|
||||
echo "- Gradle cache hit: ${{ steps.gradle-cache.outputs.cache-hit }}"
|
||||
echo "- NDK cache hit: ${{ steps.ndk-cache.outputs.cache-hit }}"
|
||||
|
||||
- name: Verify lock files are up to date
|
||||
run: |
|
||||
echo "🔍 Checking if lock files are in sync with dependency files..."
|
||||
|
||||
# For yarn workspaces, yarn.lock is at root
|
||||
if [ ! -f "${{ env.WORKSPACE }}/yarn.lock" ]; then
|
||||
echo "❌ ERROR: yarn.lock file is missing at workspace root!"
|
||||
echo "Run 'yarn install' at the repository root and commit the yarn.lock file."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Gemfile.lock is in app directory
|
||||
if [ ! -f "${{ env.APP_PATH }}/Gemfile.lock" ]; then
|
||||
echo "❌ ERROR: Gemfile.lock file is missing!"
|
||||
echo "Run 'bundle install' in the app directory and commit the Gemfile.lock file."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "✅ Lock files exist"
|
||||
|
||||
- name: Install Mobile Dependencies
|
||||
if: github.event_name == 'workflow_dispatch' && github.event.inputs.platform != 'ios'
|
||||
uses: ./.github/actions/mobile-setup
|
||||
@@ -410,7 +581,7 @@ jobs:
|
||||
accept-android-sdk-licenses: true
|
||||
|
||||
- name: Install NDK
|
||||
if: github.event_name == 'workflow_dispatch' && github.event.inputs.platform != 'ios'
|
||||
if: github.event_name == 'workflow_dispatch' && github.event.inputs.platform != 'ios' && steps.ndk-cache.outputs.cache-hit != 'true'
|
||||
run: |
|
||||
max_attempts=5
|
||||
attempt=1
|
||||
@@ -495,8 +666,15 @@ jobs:
|
||||
SLACK_ANNOUNCE_CHANNEL_NAME: ${{ secrets.SLACK_ANNOUNCE_CHANNEL_NAME }}
|
||||
run: |
|
||||
cd ${{ env.APP_PATH }}
|
||||
echo "🚀 Uploading to Google Play Internal Testing..."
|
||||
bundle exec fastlane android internal_test --verbose
|
||||
if [ "${{ github.event.inputs.test_mode }}" = "true" ]; then
|
||||
echo "🧪 Running in TEST MODE - will skip upload to Play Store"
|
||||
bundle exec fastlane android internal_test --verbose test_mode:true
|
||||
else
|
||||
echo "🚀 Uploading to Google Play Internal Testing..."
|
||||
bundle exec fastlane android internal_test --verbose
|
||||
fi
|
||||
|
||||
# Version updates moved to separate job to avoid race conditions
|
||||
|
||||
- name: Get version from package.json
|
||||
if: github.event_name == 'workflow_dispatch' && github.event.inputs.platform != 'ios'
|
||||
@@ -511,3 +689,113 @@ jobs:
|
||||
with:
|
||||
commit_message: "incrementing android build version for version ${{ env.VERSION }}"
|
||||
commit_paths: "./app/android/app/build.gradle"
|
||||
|
||||
- name: Monitor cache usage
|
||||
if: always()
|
||||
run: |
|
||||
echo "📊 Cache Size Report (Android Build)"
|
||||
echo "===================================="
|
||||
|
||||
if [ -d "${{ env.APP_PATH }}/node_modules" ]; then
|
||||
NODE_SIZE=$(du -sh "${{ env.APP_PATH }}/node_modules" | cut -f1)
|
||||
echo "Node modules: $NODE_SIZE"
|
||||
fi
|
||||
|
||||
if [ -d "${{ env.APP_PATH }}/vendor/bundle" ]; then
|
||||
GEMS_SIZE=$(du -sh "${{ env.APP_PATH }}/vendor/bundle" | cut -f1)
|
||||
echo "Ruby gems: $GEMS_SIZE"
|
||||
fi
|
||||
|
||||
if [ -d "$HOME/.gradle/caches" ]; then
|
||||
GRADLE_SIZE=$(du -sh "$HOME/.gradle/caches" | cut -f1)
|
||||
echo "Gradle caches: $GRADLE_SIZE"
|
||||
fi
|
||||
|
||||
if [ -d "${{ env.ANDROID_SDK_ROOT }}/ndk/${{ env.ANDROID_NDK_VERSION }}" ]; then
|
||||
NDK_SIZE=$(du -sh "${{ env.ANDROID_SDK_ROOT }}/ndk/${{ env.ANDROID_NDK_VERSION }}" | cut -f1)
|
||||
echo "Android NDK: $NDK_SIZE"
|
||||
fi
|
||||
|
||||
echo "===================================="
|
||||
echo "💡 GitHub Actions cache limit: 10GB per repository"
|
||||
|
||||
# Separate job to update version files after successful deployment
|
||||
# This avoids race conditions when both iOS and Android run in parallel
|
||||
update-version:
|
||||
runs-on: ubuntu-latest
|
||||
needs: [build-ios, build-android]
|
||||
if: |
|
||||
always() &&
|
||||
github.event.inputs.test_mode != 'true' &&
|
||||
(needs.build-ios.result == 'success' || needs.build-android.result == 'success')
|
||||
env:
|
||||
NODE_VERSION: 18
|
||||
APP_PATH: ${{ github.workspace }}/app
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
token: ${{ github.token }}
|
||||
fetch-depth: 0
|
||||
|
||||
- uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: ${{ env.NODE_VERSION }}
|
||||
|
||||
- name: Update package.json version
|
||||
run: |
|
||||
cd ${{ env.APP_PATH }}
|
||||
|
||||
# Get current version from package.json
|
||||
CURRENT_VERSION=$(node -p "require('./package.json').version")
|
||||
|
||||
# Get new version from version.json (if it exists and has version field)
|
||||
if [ -f version.json ] && grep -q '"version"' version.json; then
|
||||
NEW_VERSION=$(node -pe 'require("./version.json").version' 2>/dev/null || echo "")
|
||||
else
|
||||
# Fallback: use current version from package.json
|
||||
NEW_VERSION="$CURRENT_VERSION"
|
||||
fi
|
||||
|
||||
# Only update if versions differ
|
||||
if [ "$CURRENT_VERSION" != "$NEW_VERSION" ] && [ -n "$NEW_VERSION" ]; then
|
||||
echo "📦 Updating package.json version:"
|
||||
echo " From: v$CURRENT_VERSION"
|
||||
echo " To: v$NEW_VERSION"
|
||||
|
||||
# Use yarn to update package.json and the lockfile
|
||||
yarn version --new-version "$NEW_VERSION" --no-git-tag-version -y
|
||||
else
|
||||
echo "ℹ️ Version already up to date or no version field in version.json"
|
||||
fi
|
||||
|
||||
- name: Commit and push version files
|
||||
run: |
|
||||
cd ${{ github.workspace }}
|
||||
|
||||
# Configure git
|
||||
git config user.name "github-actions[bot]"
|
||||
git config user.email "github-actions[bot]@users.noreply.github.com"
|
||||
|
||||
# Check if there are any changes to commit
|
||||
if git diff --quiet app/version.json app/package.json yarn.lock 2>/dev/null; then
|
||||
echo "No changes to version files, skipping commit"
|
||||
else
|
||||
# Stage the changes
|
||||
git add app/version.json app/package.json yarn.lock 2>/dev/null || true
|
||||
|
||||
# Create commit message based on which platforms were deployed
|
||||
COMMIT_MSG="chore: update version files after"
|
||||
if [ "${{ needs.build-ios.result }}" = "success" ] && [ "${{ needs.build-android.result }}" = "success" ]; then
|
||||
COMMIT_MSG="$COMMIT_MSG iOS and Android deployment"
|
||||
elif [ "${{ needs.build-ios.result }}" = "success" ]; then
|
||||
COMMIT_MSG="$COMMIT_MSG iOS deployment"
|
||||
else
|
||||
COMMIT_MSG="$COMMIT_MSG Android deployment"
|
||||
fi
|
||||
COMMIT_MSG="$COMMIT_MSG [skip ci]"
|
||||
|
||||
# Commit and push
|
||||
git commit -m "$COMMIT_MSG"
|
||||
git push
|
||||
echo "✅ Committed version file changes"
|
||||
fi
|
||||
|
||||
@@ -230,7 +230,6 @@ GEM
|
||||
logger (1.6.6)
|
||||
mini_magick (4.13.2)
|
||||
mini_mime (1.1.5)
|
||||
mini_portile2 (2.8.8)
|
||||
minitest (5.25.5)
|
||||
molinillo (0.8.0)
|
||||
multi_json (1.15.0)
|
||||
@@ -241,14 +240,10 @@ GEM
|
||||
naturally (2.2.1)
|
||||
netrc (0.11.0)
|
||||
nkf (0.2.0)
|
||||
nokogiri (1.18.5)
|
||||
mini_portile2 (~> 2.8.2)
|
||||
racc (~> 1.4)
|
||||
optparse (0.6.0)
|
||||
os (1.1.4)
|
||||
plist (3.7.2)
|
||||
public_suffix (4.0.7)
|
||||
racc (1.8.1)
|
||||
rake (13.2.1)
|
||||
representable (3.2.0)
|
||||
declarative (< 0.1.0)
|
||||
@@ -308,7 +303,6 @@ DEPENDENCIES
|
||||
fastlane (~> 2.228.0)
|
||||
fastlane-plugin-increment_version_code
|
||||
fastlane-plugin-versioning_android
|
||||
nokogiri (~> 1.18)
|
||||
|
||||
RUBY VERSION
|
||||
ruby 3.2.7p253
|
||||
|
||||
@@ -58,20 +58,33 @@ platform :ios do
|
||||
end
|
||||
|
||||
desc "Push a new build to TestFlight Internal Testing"
|
||||
lane :internal_test do
|
||||
lane :internal_test do |options|
|
||||
test_mode = options[:test_mode] == true || options[:test_mode] == "true"
|
||||
|
||||
result = prepare_ios_build(prod_release: false)
|
||||
|
||||
upload_to_testflight(
|
||||
api_key: result[:api_key],
|
||||
distribute_external: true,
|
||||
# Only external TestFlight groups are valid here
|
||||
groups: ENV["IOS_TESTFLIGHT_GROUPS"].split(","),
|
||||
changelog: "",
|
||||
skip_waiting_for_build_processing: false,
|
||||
) if result[:should_upload]
|
||||
if test_mode
|
||||
UI.important("🧪 TEST MODE: Skipping TestFlight upload")
|
||||
UI.success("✅ Build completed successfully!")
|
||||
UI.message("📦 IPA path: #{result[:ipa_path]}")
|
||||
else
|
||||
upload_to_testflight(
|
||||
api_key: result[:api_key],
|
||||
distribute_external: true,
|
||||
# Only external TestFlight groups are valid here
|
||||
groups: ENV["IOS_TESTFLIGHT_GROUPS"].split(","),
|
||||
changelog: "",
|
||||
skip_waiting_for_build_processing: false,
|
||||
) if result[:should_upload]
|
||||
end
|
||||
|
||||
# Update deployment timestamp in version.json
|
||||
if result[:should_upload]
|
||||
Fastlane::Helpers.update_deployment_timestamp('ios')
|
||||
end
|
||||
|
||||
# Notify Slack about the new build
|
||||
if ENV["SLACK_CHANNEL_ID"]
|
||||
if ENV["SLACK_CHANNEL_ID"] && !test_mode
|
||||
deploy_source = Fastlane::Helpers.is_ci_environment? ? "GitHub Workflow" : "Local Deploy"
|
||||
Fastlane::Helpers.upload_file_to_slack(
|
||||
file_path: result[:ipa_path],
|
||||
@@ -79,6 +92,8 @@ platform :ios do
|
||||
initial_comment: "🍎 iOS v#{package_version} (Build #{result[:build_number]}) deployed to TestFlight via #{deploy_source}",
|
||||
title: "#{APP_NAME}-#{package_version}-#{result[:build_number]}.ipa",
|
||||
)
|
||||
elsif test_mode
|
||||
UI.important("🧪 TEST MODE: Skipping Slack notification")
|
||||
else
|
||||
UI.important("Skipping Slack notification: SLACK_CHANNEL_ID not set.")
|
||||
end
|
||||
@@ -122,13 +137,16 @@ platform :ios do
|
||||
|
||||
Fastlane::Helpers.verify_env_vars(required_env_vars)
|
||||
|
||||
# Get current build number without auto-incrementing
|
||||
project = Xcodeproj::Project.open(ios_xcode_profile_path)
|
||||
target = project.targets.first
|
||||
config = target.build_configurations.first
|
||||
build_number = config.build_settings["CURRENT_PROJECT_VERSION"]
|
||||
|
||||
# Verify build number is higher than TestFlight (but don't auto-increment)
|
||||
# Get build number from version.json and increment it
|
||||
build_number = Fastlane::Helpers.bump_ios_build_number
|
||||
|
||||
# Update Xcode project with new build number
|
||||
increment_build_number(
|
||||
build_number: build_number,
|
||||
xcodeproj: "ios/#{ENV["IOS_PROJECT_NAME"]}.xcodeproj"
|
||||
)
|
||||
|
||||
# Verify build number is higher than TestFlight
|
||||
Fastlane::Helpers.ios_verify_app_store_build_number(ios_xcode_profile_path)
|
||||
Fastlane::Helpers.ios_verify_provisioning_profile
|
||||
|
||||
@@ -200,8 +218,8 @@ platform :android do
|
||||
end
|
||||
|
||||
desc "Push a new build to Google Play Internal Testing"
|
||||
lane :internal_test do
|
||||
upload_android_build(track: "internal")
|
||||
lane :internal_test do |options|
|
||||
upload_android_build(track: "internal", test_mode: options[:test_mode])
|
||||
end
|
||||
|
||||
desc "Push a new build to Google Play Store"
|
||||
@@ -210,6 +228,7 @@ platform :android do
|
||||
end
|
||||
|
||||
private_lane :upload_android_build do |options|
|
||||
test_mode = options[:test_mode] == true || options[:test_mode] == "true"
|
||||
if local_development
|
||||
if ENV["ANDROID_KEYSTORE_PATH"].nil?
|
||||
ENV["ANDROID_KEYSTORE_PATH"] = Fastlane::Helpers.android_create_keystore(android_keystore_path)
|
||||
@@ -232,10 +251,14 @@ platform :android do
|
||||
|
||||
Fastlane::Helpers.verify_env_vars(required_env_vars)
|
||||
|
||||
# Get current version code without auto-incrementing
|
||||
content = File.read(android_gradle_file_path)
|
||||
match = content.match(/versionCode\s+(\d+)/)
|
||||
version_code = match ? match[1].to_i : 1
|
||||
# Get version code from version.json and increment it
|
||||
version_code = Fastlane::Helpers.bump_android_build_number
|
||||
|
||||
# Update build.gradle with new version code
|
||||
increment_version_code(
|
||||
version_code: version_code,
|
||||
gradle_file_path: android_gradle_file_path.gsub("../", "")
|
||||
)
|
||||
|
||||
# TODO: uncomment when we have the permissions to run this action
|
||||
# Fastlane::Helpers.android_verify_version_code(android_gradle_file_path)
|
||||
@@ -260,19 +283,30 @@ platform :android do
|
||||
)
|
||||
end
|
||||
|
||||
upload_to_play_store(
|
||||
track: options[:track],
|
||||
json_key: ENV["ANDROID_PLAY_STORE_JSON_KEY_PATH"],
|
||||
package_name: ENV["ANDROID_PACKAGE_NAME"],
|
||||
skip_upload_changelogs: true,
|
||||
skip_upload_images: true,
|
||||
skip_upload_screenshots: true,
|
||||
track_promote_release_status: "completed",
|
||||
aab: android_aab_path,
|
||||
) if should_upload && android_has_permissions
|
||||
if test_mode
|
||||
UI.important("🧪 TEST MODE: Skipping Play Store upload")
|
||||
UI.success("✅ Build completed successfully!")
|
||||
UI.message("📦 AAB path: #{android_aab_path}")
|
||||
else
|
||||
upload_to_play_store(
|
||||
track: options[:track],
|
||||
json_key: ENV["ANDROID_PLAY_STORE_JSON_KEY_PATH"],
|
||||
package_name: ENV["ANDROID_PACKAGE_NAME"],
|
||||
skip_upload_changelogs: true,
|
||||
skip_upload_images: true,
|
||||
skip_upload_screenshots: true,
|
||||
track_promote_release_status: "completed",
|
||||
aab: android_aab_path,
|
||||
) if should_upload && android_has_permissions
|
||||
end
|
||||
|
||||
# Update deployment timestamp in version.json
|
||||
if should_upload
|
||||
Fastlane::Helpers.update_deployment_timestamp('android')
|
||||
end
|
||||
|
||||
# Notify Slack about the new build
|
||||
if ENV["SLACK_CHANNEL_ID"]
|
||||
if ENV["SLACK_CHANNEL_ID"] && !test_mode
|
||||
deploy_source = Fastlane::Helpers.is_ci_environment? ? "GitHub Workflow" : "Local Deploy"
|
||||
Fastlane::Helpers.upload_file_to_slack(
|
||||
file_path: android_aab_path,
|
||||
@@ -280,6 +314,8 @@ platform :android do
|
||||
initial_comment: "🤖 Android v#{package_version} (Build #{version_code}) deployed to #{target_platform} via #{deploy_source}",
|
||||
title: "#{APP_NAME}-#{package_version}-#{version_code}.aab",
|
||||
)
|
||||
elsif test_mode
|
||||
UI.important("🧪 TEST MODE: Skipping Slack notification")
|
||||
else
|
||||
UI.important("Skipping Slack notification: SLACK_CHANNEL_ID not set.")
|
||||
end
|
||||
|
||||
@@ -13,6 +13,7 @@ require_relative "helpers/common"
|
||||
require_relative "helpers/ios"
|
||||
require_relative "helpers/android"
|
||||
require_relative "helpers/slack"
|
||||
require_relative "helpers/version_manager"
|
||||
|
||||
module Fastlane
|
||||
module Helpers
|
||||
@@ -20,6 +21,7 @@ module Fastlane
|
||||
extend Fastlane::Helpers::IOS
|
||||
extend Fastlane::Helpers::Android
|
||||
extend Fastlane::Helpers::Slack
|
||||
extend Fastlane::Helpers::VersionManager
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
94
app/fastlane/helpers/version_manager.rb
Normal file
94
app/fastlane/helpers/version_manager.rb
Normal file
@@ -0,0 +1,94 @@
|
||||
# SPDX-License-Identifier: BUSL-1.1
|
||||
require 'json'
|
||||
require 'time'
|
||||
|
||||
module Fastlane
|
||||
module Helpers
|
||||
module VersionManager
|
||||
extend self
|
||||
|
||||
VERSION_FILE_PATH = File.expand_path('../../version.json', __dir__)
|
||||
|
||||
def read_version_file
|
||||
unless File.exist?(VERSION_FILE_PATH)
|
||||
UI.user_error!("version.json not found at #{VERSION_FILE_PATH}")
|
||||
end
|
||||
|
||||
JSON.parse(File.read(VERSION_FILE_PATH))
|
||||
rescue JSON::ParserError => e
|
||||
UI.user_error!("Failed to parse version.json: #{e.message}")
|
||||
end
|
||||
|
||||
def write_version_file(data)
|
||||
File.write(VERSION_FILE_PATH, JSON.pretty_generate(data) + "\n")
|
||||
UI.success("Updated version.json")
|
||||
rescue => e
|
||||
UI.user_error!("Failed to write version.json: #{e.message}")
|
||||
end
|
||||
|
||||
def get_current_version
|
||||
# Version comes from package.json, not version.json
|
||||
package_json_path = File.expand_path('../../package.json', __dir__)
|
||||
package_data = JSON.parse(File.read(package_json_path))
|
||||
package_data['version']
|
||||
end
|
||||
|
||||
def get_ios_build_number
|
||||
data = read_version_file
|
||||
data['ios']['build']
|
||||
end
|
||||
|
||||
def get_android_build_number
|
||||
data = read_version_file
|
||||
data['android']['build']
|
||||
end
|
||||
|
||||
def bump_ios_build_number
|
||||
data = read_version_file
|
||||
current = data['ios']['build']
|
||||
data['ios']['build'] = current + 1
|
||||
write_version_file(data)
|
||||
UI.success("iOS build number bumped from #{current} to #{data['ios']['build']}")
|
||||
data['ios']['build']
|
||||
end
|
||||
|
||||
def bump_android_build_number
|
||||
data = read_version_file
|
||||
current = data['android']['build']
|
||||
data['android']['build'] = current + 1
|
||||
write_version_file(data)
|
||||
UI.success("Android build number bumped from #{current} to #{data['android']['build']}")
|
||||
data['android']['build']
|
||||
end
|
||||
|
||||
def update_deployment_timestamp(platform)
|
||||
unless %w[ios android].include?(platform)
|
||||
UI.user_error!("Invalid platform: #{platform}. Must be 'ios' or 'android'")
|
||||
end
|
||||
|
||||
data = read_version_file
|
||||
timestamp = Time.now.utc.iso8601
|
||||
|
||||
data[platform]['lastDeployed'] = timestamp
|
||||
|
||||
write_version_file(data)
|
||||
UI.success("Updated #{platform} deployment timestamp")
|
||||
end
|
||||
|
||||
def sync_build_numbers_to_native
|
||||
data = read_version_file
|
||||
version = get_current_version
|
||||
|
||||
UI.message("Version #{version} (from package.json)")
|
||||
UI.message("iOS build: #{data['ios']['build']}")
|
||||
UI.message("Android build: #{data['android']['build']}")
|
||||
|
||||
# Return the build numbers for use in Fastlane
|
||||
{
|
||||
ios: data['ios']['build'],
|
||||
android: data['android']['build']
|
||||
}
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -20,6 +20,7 @@ const SUPPORTED_PLATFORMS = Object.values(PLATFORMS);
|
||||
|
||||
const FILE_PATHS = {
|
||||
PACKAGE_JSON: '../package.json',
|
||||
VERSION_JSON: '../version.json',
|
||||
IOS_INFO_PLIST: '../ios/OpenPassport/Info.plist',
|
||||
IOS_PROJECT_PBXPROJ: '../ios/Self.xcodeproj/project.pbxproj',
|
||||
ANDROID_BUILD_GRADLE: '../android/app/build.gradle',
|
||||
@@ -239,15 +240,56 @@ function getAndroidVersion() {
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads version.json for build numbers and deployment history
|
||||
* @returns {Object|null} Version data or null if not found
|
||||
*/
|
||||
function getVersionJsonData() {
|
||||
const versionJsonPath = path.join(__dirname, FILE_PATHS.VERSION_JSON);
|
||||
try {
|
||||
const versionData = JSON.parse(fs.readFileSync(versionJsonPath, 'utf8'));
|
||||
return versionData;
|
||||
} catch (error) {
|
||||
console.warn(`Warning: Could not read version.json: ${error.message}`);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Formats time elapsed since last deployment
|
||||
* @param {string} timestamp - ISO timestamp of last deployment
|
||||
* @returns {string} Human-readable time elapsed
|
||||
*/
|
||||
function getTimeAgo(timestamp) {
|
||||
if (!timestamp) return 'Never deployed';
|
||||
|
||||
const now = new Date();
|
||||
const then = new Date(timestamp);
|
||||
const diffMs = now - then;
|
||||
const diffHours = Math.floor(diffMs / (1000 * 60 * 60));
|
||||
const diffDays = Math.floor(diffHours / 24);
|
||||
|
||||
if (diffDays > 0) {
|
||||
return `${diffDays} day${diffDays > 1 ? 's' : ''} ago`;
|
||||
} else if (diffHours > 0) {
|
||||
return `${diffHours} hour${diffHours > 1 ? 's' : ''} ago`;
|
||||
} else {
|
||||
return 'Less than an hour ago';
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads version information from package.json, iOS Info.plist, and Android build.gradle
|
||||
* @returns {Object} Object containing version information for all platforms
|
||||
*/
|
||||
function getCurrentVersions() {
|
||||
const versionJson = getVersionJsonData();
|
||||
|
||||
return {
|
||||
main: getMainVersion(),
|
||||
ios: getIOSVersion(),
|
||||
android: getAndroidVersion(),
|
||||
versionJson: versionJson,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -308,19 +350,64 @@ function displayPlatformVersions(platform, versions) {
|
||||
console.log(`${CONSOLE_SYMBOLS.PACKAGE} Main Version: ${versions.main}`);
|
||||
|
||||
if (platform === PLATFORMS.IOS || platform === PLATFORMS.BOTH) {
|
||||
const currentBuild = versions.ios.build;
|
||||
const nextBuild = versions.versionJson
|
||||
? versions.versionJson.ios.build + 1
|
||||
: parseInt(currentBuild) + 1;
|
||||
const lastDeployed = versions.versionJson
|
||||
? getTimeAgo(versions.versionJson.ios.lastDeployed)
|
||||
: 'Unknown';
|
||||
|
||||
console.log(
|
||||
`${CONSOLE_SYMBOLS.APPLE} iOS Version: ${versions.ios.version}`,
|
||||
);
|
||||
console.log(`${CONSOLE_SYMBOLS.APPLE} iOS Build: ${versions.ios.build}`);
|
||||
console.log(
|
||||
`${CONSOLE_SYMBOLS.APPLE} iOS Build: ${currentBuild} → ${nextBuild}`,
|
||||
);
|
||||
console.log(`${CONSOLE_SYMBOLS.APPLE} Last iOS Deploy: ${lastDeployed}`);
|
||||
}
|
||||
|
||||
if (platform === PLATFORMS.ANDROID || platform === PLATFORMS.BOTH) {
|
||||
const currentBuild = versions.android.versionCode;
|
||||
const nextBuild = versions.versionJson
|
||||
? versions.versionJson.android.build + 1
|
||||
: parseInt(currentBuild) + 1;
|
||||
const lastDeployed = versions.versionJson
|
||||
? getTimeAgo(versions.versionJson.android.lastDeployed)
|
||||
: 'Unknown';
|
||||
|
||||
console.log(
|
||||
`${CONSOLE_SYMBOLS.ANDROID} Android Version: ${versions.android.version}`,
|
||||
);
|
||||
console.log(
|
||||
`${CONSOLE_SYMBOLS.ANDROID} Android Version Code: ${versions.android.versionCode}`,
|
||||
`${CONSOLE_SYMBOLS.ANDROID} Android Version Code: ${currentBuild} → ${nextBuild}`,
|
||||
);
|
||||
console.log(
|
||||
`${CONSOLE_SYMBOLS.ANDROID} Last Android Deploy: ${lastDeployed}`,
|
||||
);
|
||||
}
|
||||
|
||||
// Check for potential issues
|
||||
if (versions.versionJson) {
|
||||
if (platform === PLATFORMS.IOS || platform === PLATFORMS.BOTH) {
|
||||
const jsonBuild = versions.versionJson.ios.build;
|
||||
const actualBuild = parseInt(versions.ios.build);
|
||||
if (jsonBuild !== actualBuild) {
|
||||
console.log(
|
||||
`\n${CONSOLE_SYMBOLS.WARNING} iOS build mismatch: version.json has ${jsonBuild}, but Xcode has ${actualBuild}`,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
if (platform === PLATFORMS.ANDROID || platform === PLATFORMS.BOTH) {
|
||||
const jsonBuild = versions.versionJson.android.build;
|
||||
const actualBuild = parseInt(versions.android.versionCode);
|
||||
if (jsonBuild !== actualBuild) {
|
||||
console.log(
|
||||
`\n${CONSOLE_SYMBOLS.WARNING} Android build mismatch: version.json has ${jsonBuild}, but gradle has ${actualBuild}`,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
136
app/scripts/version.cjs
Executable file
136
app/scripts/version.cjs
Executable file
@@ -0,0 +1,136 @@
|
||||
#!/usr/bin/env node
|
||||
// SPDX-License-Identifier: BUSL-1.1
|
||||
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
|
||||
const VERSION_FILE = path.join(__dirname, '..', 'version.json');
|
||||
const PACKAGE_JSON = path.join(__dirname, '..', 'package.json');
|
||||
|
||||
function readVersionFile() {
|
||||
try {
|
||||
const data = fs.readFileSync(VERSION_FILE, 'utf8');
|
||||
return JSON.parse(data);
|
||||
} catch (error) {
|
||||
console.error('Error reading version.json:', error);
|
||||
process.exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
function writeVersionFile(data) {
|
||||
try {
|
||||
fs.writeFileSync(VERSION_FILE, JSON.stringify(data, null, 2) + '\n');
|
||||
} catch (error) {
|
||||
console.error('Error writing version.json:', error);
|
||||
process.exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
function getPackageVersion() {
|
||||
try {
|
||||
const packageData = JSON.parse(fs.readFileSync(PACKAGE_JSON, 'utf8'));
|
||||
return packageData.version;
|
||||
} catch (error) {
|
||||
console.error('Error reading package.json:', error);
|
||||
process.exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
function bumpBuild(platform = 'both') {
|
||||
const validPlatforms = ['ios', 'android', 'both'];
|
||||
if (!validPlatforms.includes(platform)) {
|
||||
console.error(
|
||||
`Invalid platform: ${platform}. Must be one of: ${validPlatforms.join(', ')}`,
|
||||
);
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
const versionData = readVersionFile();
|
||||
|
||||
if (platform === 'ios' || platform === 'both') {
|
||||
versionData.ios.build += 1;
|
||||
console.log(`✅ iOS build number bumped to ${versionData.ios.build}`);
|
||||
}
|
||||
|
||||
if (platform === 'android' || platform === 'both') {
|
||||
versionData.android.build += 1;
|
||||
console.log(
|
||||
`✅ Android build number bumped to ${versionData.android.build}`,
|
||||
);
|
||||
}
|
||||
|
||||
writeVersionFile(versionData);
|
||||
}
|
||||
|
||||
function setDeploymentTime(platform) {
|
||||
const validPlatforms = ['ios', 'android', 'both'];
|
||||
if (!validPlatforms.includes(platform)) {
|
||||
console.error(
|
||||
`Invalid platform: ${platform}. Must be one of: ${validPlatforms.join(', ')}`,
|
||||
);
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
const versionData = readVersionFile();
|
||||
const timestamp = new Date().toISOString();
|
||||
|
||||
if (platform === 'ios' || platform === 'both') {
|
||||
versionData.ios.lastDeployed = timestamp;
|
||||
}
|
||||
|
||||
if (platform === 'android' || platform === 'both') {
|
||||
versionData.android.lastDeployed = timestamp;
|
||||
}
|
||||
|
||||
writeVersionFile(versionData);
|
||||
console.log(`✅ Updated ${platform} deployment timestamp`);
|
||||
}
|
||||
|
||||
function getCurrentInfo() {
|
||||
const versionData = readVersionFile();
|
||||
const version = getPackageVersion();
|
||||
|
||||
console.log(`Current version: ${version} (from package.json)`);
|
||||
console.log(`iOS build: ${versionData.ios.build}`);
|
||||
console.log(`Android build: ${versionData.android.build}`);
|
||||
|
||||
if (versionData.ios.lastDeployed) {
|
||||
console.log(`iOS last deployed: ${versionData.ios.lastDeployed}`);
|
||||
}
|
||||
if (versionData.android.lastDeployed) {
|
||||
console.log(`Android last deployed: ${versionData.android.lastDeployed}`);
|
||||
}
|
||||
|
||||
return { version, ...versionData };
|
||||
}
|
||||
|
||||
// CLI handling
|
||||
const command = process.argv[2];
|
||||
const arg = process.argv[3];
|
||||
|
||||
switch (command) {
|
||||
case 'bump-build':
|
||||
bumpBuild(arg || 'both');
|
||||
break;
|
||||
case 'deployed':
|
||||
setDeploymentTime(arg || 'both');
|
||||
break;
|
||||
case 'get':
|
||||
case 'info':
|
||||
getCurrentInfo();
|
||||
break;
|
||||
default:
|
||||
console.log('Usage:');
|
||||
console.log(
|
||||
' node version.cjs bump-build [ios|android|both] - Bump build number',
|
||||
);
|
||||
console.log(
|
||||
' node version.cjs deployed [ios|android|both] - Update deployment timestamp',
|
||||
);
|
||||
console.log(
|
||||
' node version.cjs info - Get current version info',
|
||||
);
|
||||
console.log('');
|
||||
console.log('Note: Version numbers are managed by npm version command');
|
||||
process.exit(1);
|
||||
}
|
||||
10
app/version.json
Normal file
10
app/version.json
Normal file
@@ -0,0 +1,10 @@
|
||||
{
|
||||
"ios": {
|
||||
"build": 148,
|
||||
"lastDeployed": null
|
||||
},
|
||||
"android": {
|
||||
"build": 82,
|
||||
"lastDeployed": null
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user