From 9828bbdcc7974173e445f5aa550178191cf7e103 Mon Sep 17 00:00:00 2001 From: Jayaditya Gupta Date: Thu, 10 Jul 2025 19:00:08 +0200 Subject: [PATCH 01/21] 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 --- app/fastlane/helpers.rb | 2 + app/fastlane/helpers/version_manager.rb | 97 ++++++++++++++++++++ app/scripts/version.cjs | 112 ++++++++++++++++++++++++ app/version.json | 10 +++ 4 files changed, 221 insertions(+) create mode 100644 app/fastlane/helpers/version_manager.rb create mode 100755 app/scripts/version.cjs create mode 100644 app/version.json diff --git a/app/fastlane/helpers.rb b/app/fastlane/helpers.rb index 2ef4946d3..12b4e49f8 100644 --- a/app/fastlane/helpers.rb +++ b/app/fastlane/helpers.rb @@ -13,6 +13,7 @@ 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 Helpers extend Fastlane::Helpers::IOS extend Fastlane::Helpers::Android extend Fastlane::Helpers::Slack + extend Fastlane::Helpers::VersionManager end end diff --git a/app/fastlane/helpers/version_manager.rb b/app/fastlane/helpers/version_manager.rb new file mode 100644 index 000000000..8896bc9c3 --- /dev/null +++ b/app/fastlane/helpers/version_manager.rb @@ -0,0 +1,97 @@ +# 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) + data = read_version_file + timestamp = Time.now.utc.iso8601 + + case platform + when 'ios' + data['ios']['lastDeployed'] = timestamp + when 'android' + data['android']['lastDeployed'] = timestamp + else + UI.user_error!("Invalid platform: #{platform}") + end + + 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 \ No newline at end of file diff --git a/app/scripts/version.cjs b/app/scripts/version.cjs new file mode 100755 index 000000000..3b4d9e7e3 --- /dev/null +++ b/app/scripts/version.cjs @@ -0,0 +1,112 @@ +#!/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 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 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); +} \ No newline at end of file diff --git a/app/version.json b/app/version.json new file mode 100644 index 000000000..79d1e28db --- /dev/null +++ b/app/version.json @@ -0,0 +1,10 @@ +{ + "ios": { + "build": 148, + "lastDeployed": null + }, + "android": { + "build": 82, + "lastDeployed": null + } +} From c84e27a9f45f383b42ea90f8673dc92009e74ddc Mon Sep 17 00:00:00 2001 From: Jayaditya Gupta Date: Thu, 10 Jul 2025 20:01:41 +0200 Subject: [PATCH 02/21] feat: integrate version.json with Fastlane deployment process MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## 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 --- app/fastlane/Fastfile | 39 ++++++++++++++++++++++++++++----------- 1 file changed, 28 insertions(+), 11 deletions(-) diff --git a/app/fastlane/Fastfile b/app/fastlane/Fastfile index 085bf26a6..3dc764d20 100644 --- a/app/fastlane/Fastfile +++ b/app/fastlane/Fastfile @@ -69,6 +69,11 @@ platform :ios do changelog: "", skip_waiting_for_build_processing: false, ) if result[:should_upload] + + # 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"] @@ -122,13 +127,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_xcode_profile_path + ) + + # 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 @@ -232,10 +240,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: android_gradle_file_path + ) # TODO: uncomment when we have the permissions to run this action # Fastlane::Helpers.android_verify_version_code(android_gradle_file_path) @@ -270,6 +282,11 @@ platform :android do track_promote_release_status: "completed", aab: android_aab_path, ) if should_upload && android_has_permissions + + # 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"] From 673da068f1750003752b83b6559d73974ecebdaa Mon Sep 17 00:00:00 2001 From: Jayaditya Gupta Date: Thu, 10 Jul 2025 20:42:49 +0200 Subject: [PATCH 03/21] feat: enhance deploy confirmation with version.json info --- app/scripts/mobile-deploy-confirm.cjs | 75 ++++++++++++++++++++++++++- 1 file changed, 73 insertions(+), 2 deletions(-) diff --git a/app/scripts/mobile-deploy-confirm.cjs b/app/scripts/mobile-deploy-confirm.cjs index 928c2616e..af3ee8a78 100755 --- a/app/scripts/mobile-deploy-confirm.cjs +++ b/app/scripts/mobile-deploy-confirm.cjs @@ -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, }; } @@ -309,19 +351,48 @@ 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}`); + } + } } } From 0bf0cacd99160c3c89a7677c2a429e742955f9a4 Mon Sep 17 00:00:00 2001 From: Jayaditya Gupta Date: Thu, 10 Jul 2025 21:18:53 +0200 Subject: [PATCH 04/21] fix: use ENV variable directly in increment_build_number to avoid secret masking --- app/fastlane/Fastfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/fastlane/Fastfile b/app/fastlane/Fastfile index 3dc764d20..535ddeae6 100644 --- a/app/fastlane/Fastfile +++ b/app/fastlane/Fastfile @@ -133,7 +133,7 @@ platform :ios do # Update Xcode project with new build number increment_build_number( build_number: build_number, - xcodeproj: ios_xcode_profile_path + xcodeproj: "../ios/#{ENV["IOS_PROJECT_NAME"]}.xcodeproj" ) # Verify build number is higher than TestFlight From 95768fc0418f2e37aefe46c05237895017e0b751 Mon Sep 17 00:00:00 2001 From: Jayaditya Gupta Date: Thu, 10 Jul 2025 21:26:06 +0200 Subject: [PATCH 05/21] fix: correct xcodeproj path for GitHub Actions workflow --- app/fastlane/Fastfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/fastlane/Fastfile b/app/fastlane/Fastfile index 535ddeae6..4ef74bd83 100644 --- a/app/fastlane/Fastfile +++ b/app/fastlane/Fastfile @@ -133,7 +133,7 @@ platform :ios do # Update Xcode project with new build number increment_build_number( build_number: build_number, - xcodeproj: "../ios/#{ENV["IOS_PROJECT_NAME"]}.xcodeproj" + xcodeproj: "ios/#{ENV["IOS_PROJECT_NAME"]}.xcodeproj" ) # Verify build number is higher than TestFlight From a89200ac7031fbd6242089a9d9eb008ed698f66c Mon Sep 17 00:00:00 2001 From: Jayaditya Gupta Date: Thu, 10 Jul 2025 23:36:46 +0200 Subject: [PATCH 06/21] 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 --- .cursor/mcp.json | 11 + .cursorignore | 298 +++++++++++++++++++++ .github/workflows/mobile-deploy.yml | 23 +- app/fastlane/Fastfile | 57 ++-- deployment-automation-summary.md | 108 ++++++++ deployment-meeting-questions.md | 158 +++++++++++ pipeline.md | 397 ++++++++++++++++++++++++++++ 7 files changed, 1027 insertions(+), 25 deletions(-) create mode 100644 .cursor/mcp.json create mode 100644 .cursorignore create mode 100644 deployment-automation-summary.md create mode 100644 deployment-meeting-questions.md create mode 100644 pipeline.md diff --git a/.cursor/mcp.json b/.cursor/mcp.json new file mode 100644 index 000000000..89bcd1d72 --- /dev/null +++ b/.cursor/mcp.json @@ -0,0 +1,11 @@ +{ + "mcpServers": { + "self-mcp": { + "command": "/Users/nightmare/Projects/self-work/self-mcp/venv/bin/python", + "args": [ + "/Users/nightmare/Projects/self-work/self-mcp/self-mcp/server.py" + ], + "cwd": "/Users/nightmare/Projects/self-work/self-mcp/self-mcp" + } + } +} diff --git a/.cursorignore b/.cursorignore new file mode 100644 index 000000000..49e5795b6 --- /dev/null +++ b/.cursorignore @@ -0,0 +1,298 @@ +# .cursorignore - Cursor AI editor ignore file +# This file prevents Cursor from accessing sensitive files and improves performance +# Note: .gitignore files are automatically included + +# ======================================== +# Security & Sensitive Files +# ======================================== + +# Environment and secrets +**/.env +**/.env.* +**/env.sample +**/*secrets* +**/*credentials* +**/*private* +**/google-services.json +**/GoogleService-Info.plist + +# Keys and certificates +**/*.key +**/*.pem +**/*.p12 +**/*.jks +**/*.keystore +**/*.cer +**/*.crt +**/*.cert +**/mock_*.key +**/mock_*.pem +**/debug.keystore +**/dev-keystore + +# Mobile app sensitive files +app/android/app/google-services.json +app/ios/GoogleService-Info.plist +app/android/app/debug.keystore +app/android/dev-keystore + +# Deployment scripts and CI/CD +circuits/scripts/server/*.sh +**/*.sh +!node_modules/**/*.sh + +# Fastlane configuration (may contain secrets) +app/fastlane/Fastfile +app/fastlane/helpers.rb +app/Gemfile.lock + +# Test wallets and mock data +app/ios/passport.json +app/ios/OpenPassport/passport.json + +# Environment sample files +app/env.sample +sdk/.env + +# ======================================== +# Build Outputs & Generated Files +# ======================================== + +# General build outputs +**/dist/ +**/build/ +**/out/ +**/.next/ +**/*.tsbuildinfo + +# Mobile build outputs +app/android/app/build/ +app/android/build/ +app/ios/build/ +app/ios/Pods/ +app/ios/DerivedData/ +app/android/.gradle/ +app/android/gradle/ +app/android/gradlew +app/android/gradlew.bat + +# Circuit build outputs +circuits/build/ +circuits/**/*.r1cs +circuits/**/*.sym +circuits/**/*.json +circuits/**/*.wasm +circuits/**/*.zkey +circuits/**/*.vkey +circuits/**/*.wtns +circuits/ptau/ + +# Contract artifacts +contracts/artifacts/ +contracts/cache/ +contracts/typechain-types/ +contracts/ignition/deployments/ + +# ======================================== +# Dependencies & Package Management +# ======================================== + +# Node modules - everywhere +**/node_modules/ +**/pnpm-lock.yaml +**/yarn.lock +**/package-lock.json +**/.yarn/ +**/.pnp.* + +# Mobile specific +app/ios/Podfile.lock +app/android/link-assets-manifest.json +app/ios/link-assets-manifest.json + +# ======================================== +# Large Data Files +# ======================================== + +# Circuit deployment artifacts (large JSON files) +app/deployments/artifacts/ + +# Public keys and trees (large JSON files) +common/pubkeys/public_keys_parsed.json +common/pubkeys/serialized_*.json +common/pubkeys/serialized_csca_tree.json +common/pubkeys/serialized_dsc_tree.json +common/pubkeys/serialized_tree.json + +# OFAC and sanctions data +common/ofacdata/ +common/sanctionedCountries/ +common/ofacdata/original/*.csv +common/ofacdata/scripts/cleaned_sdn.csv +common/sanctionedCountries/outputs/sc_SMT.json + +# Mock certificates (numerous files) +common/src/mock_certificates/ + +# Large SMT (Sparse Merkle Tree) files +contracts/test/utils/smt.json + +# Circuit power files +circuits/circuits/utils/crypto/ec/powers/ + +# Test data files +app/android/android-passport-reader/app/src/main/assets/tessdata/ + +# ======================================== +# Development & Testing +# ======================================== + +# Test coverage +**/coverage/ +**/.nyc_output/ + +# Test files (optional - you might want AI to see tests) +# **/*.test.ts +# **/*.test.tsx +# **/*.spec.ts +# **/*.spec.tsx + +# Temporary files +**/.tmp/ +**/tmp/ +**/.cache/ +**/*.log +**/*.tmp + +# ======================================== +# IDE & Editor Files +# ======================================== + +.vscode/ +.idea/ +*.swp +*.swo +.DS_Store +.cursor/ + +# ======================================== +# Documentation Build +# ======================================== + +# Documentation builds (if any) +docs/build/ +docs/.docusaurus/ +documentation/build/ +documentation/.docusaurus/ + +# Development documentation +app/fastlane/DEV.md +common/ofacdata/original/dataspec.txt + +# Generated constants from certificates +common/src/constants/skiPem.ts + +# Circuit test cases (large) +circuits/tests/**/test_cases.ts + +# ======================================== +# Platform Specific +# ======================================== + +# iOS +*.xcworkspace/ +*.xcodeproj/ +*.pbxproj +app/ios/App Thinning Size Report.txt + +# Android +*.iml +.gradle/ +local.properties +app/android/android-passport-reader/examples/ + +# React Native config +app/react-native.config.cjs + +# ======================================== +# Miscellaneous +# ======================================== + +# License files (AI doesn't need these) +LICENSE +NOTICE + +# Large media files +**/*.mp4 +**/*.mov +**/*.avi +**/*.zip +**/*.tar.gz + +# Patch files +patches/ + +# ======================================== +# Project Specific Patterns +# ======================================== + +# Deployment addresses (might contain sensitive info) +contracts/deployed_addresses.json +contracts/ignition/deployments/staging/deployed_addresses.json +contracts/ignition/deployments/*/artifacts/ +contracts/deployments/*/ +!contracts/deployments/prod/ + +# Error selectors (generated) +contracts/error-selectors.json + +# Hardhat network fork data +contracts/.hardhat_fork_cache/ + +# Lottie animation files (large JSON) +app/src/assets/animations/*.json + +# Font files +app/src/assets/fonts/ + +# Yarn configuration +.yarnrc.yml +**/.yarn/ + +# Ruby dependencies +app/Gemfile +app/Gemfile.lock + +# Certificate generation config +common/src/scripts/extensions.cnf + +# Powers of Tau files +circuits/ptau/ + +# ======================================== +# Allow Important Files +# ======================================== + +# Ensure important config files are accessible +!tsconfig.json +!**/tsconfig.json +!package.json +!**/package.json +!hardhat.config.ts +!jest.config.* +!babel.config.* +!metro.config.* +!tamagui.config.ts + +# Ensure source code is accessible +!**/*.ts +!**/*.tsx +!**/*.js +!**/*.jsx +!**/*.sol +!**/*.circom + +# But exclude generated TypeScript declaration files +**/*.d.ts +!**/types/*.d.ts +!**/src/types/*.d.ts \ No newline at end of file diff --git a/.github/workflows/mobile-deploy.yml b/.github/workflows/mobile-deploy.yml index ba78e37a4..baaad4dfa 100644 --- a/.github/workflows/mobile-deploy.yml +++ b/.github/workflows/mobile-deploy.yml @@ -36,6 +36,11 @@ on: - ios - android - both + test_mode: + description: "Test mode (skip upload to stores)" + required: false + type: boolean + default: false jobs: build-ios: @@ -310,8 +315,13 @@ 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 - name: Remove project.pbxproj updates we don't want to commit if: github.event_name == 'workflow_dispatch' && github.event.inputs.platform != 'android' @@ -495,8 +505,13 @@ 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 - name: Get version from package.json if: github.event_name == 'workflow_dispatch' && github.event.inputs.platform != 'ios' diff --git a/app/fastlane/Fastfile b/app/fastlane/Fastfile index 4ef74bd83..568641ab6 100644 --- a/app/fastlane/Fastfile +++ b/app/fastlane/Fastfile @@ -58,17 +58,25 @@ 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" + 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] @@ -208,8 +216,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" @@ -218,6 +226,7 @@ platform :android do end private_lane :upload_android_build do |options| + test_mode = 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) @@ -272,16 +281,22 @@ 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 diff --git a/deployment-automation-summary.md b/deployment-automation-summary.md new file mode 100644 index 000000000..8938d2ea3 --- /dev/null +++ b/deployment-automation-summary.md @@ -0,0 +1,108 @@ +# Mobile Deployment Automation - Executive Summary + +## 🎯 Project Goal +Transform our clunky manual mobile deployment process into a fully automated CI/CD pipeline that supports targeted testing and branch-based releases. + +## 📊 Current State Analysis + +### What's Working +- Basic GitHub Actions workflow exists (but disabled) +- Fastlane configured for both platforms +- Certificates and secrets properly stored +- iOS deployment is mostly automated + +### What's Broken +- **Workflows disabled**: `if: false` blocks all deployments +- **Version mismatch**: Android (2.5.4) behind iOS/package.json (2.5.5) +- **Manual hell**: Must enter versions manually, download from Slack for Android +- **No API access**: Can't query Play Store versions (missing permissions) +- **No targeted testing**: Can't deploy to NFC testers without public release + +## 🚀 Implementation Strategy + +### Week 1: Foundation +Fix permissions, implement version tracking, enable basic automation + +### Week 2: Testing Groups +Enable targeted deployments for Seshanth's NFC testing needs + +### Week 3: Full Automation +Complete Android automation after 48-hour permission wait + +### Week 4: Polish +Notifications, cleanup scripts, documentation + +## 🔑 Key Technical Decisions + +### Google Play Permissions +- **Solution**: Grant "Release Manager" role (not full admin) +- **Timeline**: 48-hour propagation wait +- **Fallback**: version.json tracking works immediately + +### Version Management +- **Single source**: version.json file +- **Branch logic**: dev = build bump, main = version bump +- **Sync**: Automatic across all platforms + +### Testing Distribution +- **Config-driven**: YAML files for group management +- **Dynamic**: Pass groups via workflow parameters +- **Flexible**: Support multiple testing scenarios + +## 🏗️ Architecture Changes + +### New Components +- `version.json` - Central version tracking +- `version_manager.rb` - Fastlane helper for versions +- `test_groups.yml` - Testing group configuration +- Health check scripts for pre-deployment validation + +### Modified Components +- GitHub workflows - Remove blocks, add automation +- Fastlane files - Dynamic group support +- Helper scripts - Uncomment API calls after permissions + +## 📈 Success Metrics + +| Metric | Before | After | +|--------|---------|--------| +| Deploy Time | 30-45 min | < 15 min | +| Manual Steps | 5-7 | 0 | +| Version Errors | Common | Impossible | +| Test Distribution | All or nothing | Targeted groups | +| Android Upload | Manual via Slack | Fully automated | + +## ⚡ Quick Wins (Day 1) +1. Fix version sync issue +2. Re-enable workflows +3. Remove manual inputs +4. Test automated builds + +## 🛡️ Risk Mitigation +- **Rollback**: Keep previous builds, implement halt capability +- **Testing**: Start with dev branch, staged rollouts +- **Monitoring**: Slack notifications, deployment tracking +- **Documentation**: Clear runbooks for emergencies + +## 🎓 Team Impact +- **Developers**: Push and forget - automation handles the rest +- **QA**: Direct access to test builds via groups +- **Release Manager**: Monitor, don't manually deploy +- **Seshanth**: Finally gets NFC-only test builds + +## 💰 ROI +- **Time saved**: 2-3 hours per week +- **Error reduction**: ~90% fewer deployment failures +- **Team satisfaction**: No more manual version tracking +- **Faster releases**: Deploy anytime, not just when someone's available + +## 🚦 Go/No-Go Checklist +- [ ] Release Manager permissions granted +- [ ] 48-hour wait acknowledged +- [ ] Team trained on new workflow +- [ ] Rollback plan documented +- [ ] First test on dev branch successful + +--- + +*This automation will transform our mobile deployment from a dreaded manual chore into a smooth, reliable process that just works.* \ No newline at end of file diff --git a/deployment-meeting-questions.md b/deployment-meeting-questions.md new file mode 100644 index 000000000..5a9715287 --- /dev/null +++ b/deployment-meeting-questions.md @@ -0,0 +1,158 @@ +# Mobile Deployment Automation - Team Meeting Questions + +## 🔑 Critical Decisions + +### 1. **Google Play Permissions** +**Current Situation**: We can't query Play Store versions or fully automate Android deployments due to service account permission limitations. + +**Solution Found**: Service account needs "Release Manager" role in Google Play Console (not "Admin/God mode"). + +**Options**: +- a) Grant "Release Manager" role to service account + - Allows version querying without excessive permissions + - Requires 24-48 hour wait for propagation + - This is the recommended approach +- b) Use version.json as primary tracking with API as verification + - Implement immediately, add API verification after permissions granted +- c) Keep current manual process as permanent fallback + - Only if security team rejects Release Manager role + +**Question**: Can we grant "Release Manager" role and wait 48 hours for propagation? + +### 2. **Version Bumping Policy** +**Current Situation**: Manual version entry is error-prone and slows down deployments. + +**Options**: +- a) Auto-increment patch version on main branch merges (2.5.5 → 2.5.6) + - Fully automated, no developer input needed +- b) Require developers to explicitly bump version before merging + - More control but adds manual step +- c) Make it configurable per deployment + - Flexible but more complex + +**Question**: What's our preferred balance between automation and version control? + +### 3. **Testing Groups Configuration** +**Current Situation**: Seshanth needs NFC testing without public deployment. Others may have similar needs. + +**Options**: +- a) Hardcode groups in GitHub workflow + - Simple but requires PR to change groups +- b) Use a config file (test_groups.yml) + - Can be updated without changing workflow +- c) Pass groups as parameters each time + - Most flexible but requires manual input + +**Question**: How often do we expect testing groups to change? + +### 4. **Android Deployment Flow** +**Current Situation**: Android AAB must be downloaded from Slack and manually uploaded to Play Store. + +**Options**: +- a) Keep Slack download as permanent part of the flow + - Maintains human verification step +- b) Try to fully automate with workarounds + - Use version.json + minimal API permissions +- c) Make it optional (auto when possible, manual fallback) + - Best of both worlds but more complex + +**Question**: Is the manual step a feature (for safety) or a bug (to be fixed)? + +## 📊 Quick Context + +### Current Pain Points: +- Manual version/build number entry +- Android requires manual download from Slack and upload to Play Store +- Cannot query current Play Store version due to permissions +- No support for targeted external testing groups +- iOS build artifacts cleanup may not be working +- Overall process is clunky and error-prone + +### Proposed Solution: +- Automated versioning with version.json +- Branch-based deployments (dev → internal, main → production) +- Test group support for targeted deployments +- Improved notifications and deployment confirmation + +### Timeline: +- Week 1: Core automation and version management +- Week 2: Testing groups implementation +- Week 3: Google Play integration (within constraints) +- Week 4: Polish and cleanup + +## 🚀 Immediate Actions (Week 1) + +If approved, I can start immediately with: +1. Fix version sync issue (Android 2.5.4 vs iOS/package.json 2.5.5) +2. Re-enable the disabled workflows (currently `if: false`) +3. Remove manual version input requirement +4. Fix TestFlight groups configuration for NFC testing + +## ⚠️ Risk & Compliance Questions + +1. **Security**: Are there any compliance/security concerns with automating deployments? +2. **Approval Gates**: Do we need manual approval steps for production deployments? +3. **Rollback**: What's our rollback strategy if automated deployment goes wrong? +4. **Access Control**: Who should be able to trigger deployments? + +## 📝 Additional Considerations + +1. **Monitoring**: Should we add deployment metrics and alerting? +2. **Documentation**: Where should deployment procedures be documented? +3. **Training**: Who needs to be trained on the new workflow? +4. **Migration**: How do we transition from current manual process? + +## 🎯 Success Criteria + +- Zero manual input for standard deployments +- Targeted testing without public release +- Both iOS and Android fully automated (within permission constraints) +- Clear notifications and deployment status +- Faster deployment times through caching and parallelization + +--- + +**Note**: Please come prepared to discuss which options best fit our team's workflow and security requirements. The implementation details can be adjusted based on these decisions. + +## 📚 Research Findings (2025) + +### Google Play Console API Permissions (Clarified) +Based on 2025 documentation and Google's guidance: +- **Who Can Grant Access**: Account owner OR users with "Admin" privileges (specifically "Manage users and permissions") +- **Required Role**: "Release Manager" is sufficient (not "Admin all permissions") +- **Permissions Needed**: + - View app information and download bulk reports + - Create, edit, and roll out releases to testing tracks +- **API Scope**: `https://www.googleapis.com/auth/androidpublisher` (full scope required, not read-only) +- **Critical**: Wait 24-48 hours after granting permissions for propagation +- **Security**: Release Manager role avoids risks of full admin access (app deletion, data modification) + +### TestFlight External Groups (2025) +Current capabilities: +- Support for up to 10,000 external testers +- Groups must be created in App Store Connect before automation +- External builds require App Review approval +- Automation options: + - App Store Connect API (official) + - Fastlane/Pilot (add testers via CSV, manage groups) + - Xcode Cloud (native integration) +- **Limitation**: Can't add test notes during automated publishing + +### Mobile Deployment Best Practices (2025) +Industry standards from GitHub and others: +- **Release Cycle**: Weekly automated releases (GitHub's approach) +- **Architecture**: Parallel workflows for build, testing, notes, versioning +- **Security**: Base64 encode certificates, use GitHub secrets +- **Certificate Storage**: Consider Google Cloud Storage for Fastlane Match +- **Release Management**: Rotating "release captain", automated tracking issues +- **Testing**: Automated cherry-pick process for beta fixes + +### Staged/Phased Rollouts (2025) +Latest capabilities: +- **iOS**: Still automatic 7-day rollout with fixed percentages +- **Android**: + - Manual percentage control remains default + - **NEW**: Can halt fully live releases via API/Console (I/O 2025) + - API supports programmatic percentage control + - Third-party tools (Instabug, Bitrise) offer automated scheduling +- **Google Play API**: Updated as of July 2025 with new halt capabilities \ No newline at end of file diff --git a/pipeline.md b/pipeline.md new file mode 100644 index 000000000..0f7f77e5a --- /dev/null +++ b/pipeline.md @@ -0,0 +1,397 @@ +# Mobile Deployment Workflow Improvements Plan + +## Overview +Streamline the mobile deployment workflow to eliminate manual steps, support targeted testing groups, and enable fully automated deployments based on branch merges. + +## Current Pain Points +1. Manual version/build number entry +2. Android requires manual download from Slack and upload to Play Store +3. Cannot query current Play Store version due to permissions +4. No support for targeted external testing groups +5. iOS build artifacts cleanup may not be working +6. Overall process is clunky and error-prone + +## Proposed Improvements + +### 1. **Automated Version Management** + +#### Auto-increment Logic +- `dev` branch: Increment build number only +- `main` branch: Increment patch version + build number +- Manual trigger: Allow major/minor version bumps + +#### Implementation Details +- Create `app/version.json` to track current version state +- Update package.json automatically +- Sync with native project files (Info.plist, build.gradle) +- Use git tags for version tracking + +#### Version File Structure +```json +{ + "version": "2.5.5", + "ios": { + "build": 145, + "lastDeployed": "2024-01-10T12:00:00Z" + }, + "android": { + "build": 145, + "lastDeployed": "2024-01-10T12:00:00Z" + } +} +``` + +This simplified structure tracks only the essential information needed for version management. Full deployment history is maintained through git tags and CI/CD logs. + +### 2. **Branch-based Automation** + +#### Workflow Triggers +- **Push to `dev`**: + - iOS → TestFlight Internal Testing + - Android → Play Store Internal Track + - Auto-increment build number + +- **Push to `main`**: + - iOS → App Store (Ready for Sale) + - Android → Play Store Production + - Auto-increment patch version and build + +- **Manual Dispatch**: + - Select platforms (iOS/Android/Both) + - Choose deployment track + - Specify test groups + - Option for version bump type + +#### Workflow Improvements +- Add matrix builds for parallel iOS/Android execution +- Implement proper caching for dependencies (Pods, Gradle) +- Add approval gates for production deployments +- Better error handling and retry logic +- Pre-deployment health checks (app builds, certificates valid, API keys working) + +#### Pre-Deployment Health Checks +Before initiating any deployment, the workflow will validate: +- **Certificates & Provisioning**: Check expiration dates, validate signatures +- **API Keys**: Verify App Store Connect and Google Play API access +- **Build Environment**: Ensure all dependencies are available +- **Version Conflicts**: Confirm version numbers are valid and not duplicated +- **Secrets**: Validate all required secrets are present and properly formatted + +```yaml +# Example health check implementation +- name: Pre-deployment Health Check + run: | + yarn deploy:health-check --platform=${{ matrix.platform }} + # Checks: + # - Certificate expiration (> 30 days) + # - API key validity + # - Build tools availability + # - Version number conflicts + # - Required secrets presence +``` + +### 3. **Targeted External Testing** + +#### Configuration Structure +```yaml +test_groups: + ios: + default: ["Internal Testers"] + nfc_testing: ["NFC Test Group", "QA Team"] + beta: ["Beta Testers", "External QA"] + android: + default: ["internal"] + nfc_testing: ["nfc-testers", "qa-team"] + beta: ["beta", "external-qa"] +``` + +#### Implementation +- **iOS**: Use TestFlight `groups` parameter in Fastlane +- **Android**: Use Play Console tracks or closed testing groups +- Add workflow_dispatch input for group selection +- Environment variable override: `DEPLOYMENT_GROUPS` + +### 4. **Google Play Automation Solutions** + +#### Option A - Release Manager Role (Recommended) +Grant the service account "Release Manager" role in Google Play Console: +- Provides necessary permissions without full admin access +- Allows querying version codes and managing releases +- Requires 24-48 hour wait for permissions to propagate +- Much safer than "Admin (all permissions)" + +Implementation after permissions granted: +```javascript +// With Release Manager role, the service account can: +// - Query current version codes +// - Create and manage releases +// - Upload APKs/AABs to tracks + +const { google } = require('googleapis'); +const androidpublisher = google.androidpublisher('v3'); + +async function getCurrentVersion() { + const auth = new google.auth.GoogleAuth({ + keyFile: 'play-store-key.json', + scopes: ['https://www.googleapis.com/auth/androidpublisher'] + }); + + const authClient = await auth.getClient(); + const res = await androidpublisher.edits.tracks.list({ + auth: authClient, + packageName: 'com.self.app', + editId: await createEdit() + }); + + return extractLatestVersion(res.data); +} +``` + +#### Option B - Version.json with API Verification +- Use `app/version.json` as primary source of truth +- After Release Manager permissions granted, add API verification +- Provides immediate solution while waiting for permissions +- Acts as fallback if API is unavailable + +### 5. **Enhanced Deployment Confirmation** + +#### Features +- Unified status checker for both platforms +- Real-time deployment monitoring via APIs +- Rich Slack notifications with: + - Platform icons (🍎 iOS, 🤖 Android) + - Version and build numbers + - Deployment track/environment + - Direct store links + - QR codes for easy testing + - Time elapsed + - Success/failure status + +#### Notification Template +``` +🚀 Deployment Complete + +Platform: 🍎 iOS +Version: 2.5.6 (Build 146) +Track: TestFlight Internal +Status: ✅ Success +Duration: 12m 34s +Groups: Internal Testers, NFC Test Group + +📱 Install: [TestFlight Link] +📊 Dashboard: [App Store Connect] +``` + +### 6. **iOS Artifacts Cleanup** + +#### Cleanup Tasks +```ruby +# In Fastfile +private_lane :cleanup_ios_artifacts do + # Remove temporary certificates + sh "rm -f #{ios_dist_cert_path}" + sh "rm -f #{ios_prov_profile_path}" + sh "rm -f #{ios_connect_api_key_path}" + + # Clear provisioning profiles + sh "rm -rf ~/Library/MobileDevice/Provisioning\\ Profiles/*.mobileprovision" + + # Delete derived data + clear_derived_data + + # Clean build folders + sh "rm -rf ./build" + sh "rm -rf ./ios/build" + + # Remove keychain (CI only) + if is_ci + sh "security delete-keychain build.keychain || true" + end +end +``` + +### 7. **Developer Experience Improvements** + +#### New Commands +```bash +# Local development +yarn deploy:ios:local # Deploy iOS locally with prompts +yarn deploy:android:local # Deploy Android locally with prompts +yarn deploy:test --group nfc # Deploy to specific test group + +# Version management +yarn version:bump:patch # Bump patch version +yarn version:bump:minor # Bump minor version +yarn version:bump:major # Bump major version +yarn version:sync # Sync versions across platforms + +# Status and monitoring +yarn deploy:status # Check deployment status +yarn deploy:history # View deployment history +``` + +#### Improved Error Messages +- Clear, actionable error messages +- Suggestion for fixes +- Links to documentation +- Automatic issue creation for failures + +## Implementation Plan + +### Phase 1: Core Automation (Week 1) +1. **Day 1-2**: Implement version.json and version management system +2. **Day 3-4**: Update GitHub workflow for branch-based triggers +3. **Day 5**: Add basic deployment confirmation and notifications + +### Phase 2: Testing Groups (Week 2) +1. **Day 1-2**: Add test group configuration system +2. **Day 3-4**: Update Fastfile to support dynamic groups +3. **Day 5**: Test with NFC testing scenario + +### Phase 3: Google Play Integration (Week 3) +1. **Day 1**: Request Release Manager role for service account +2. **Day 2**: Implement version.json as immediate solution +3. **Day 3-4**: Wait for permissions propagation (24-48 hours) +4. **Day 5**: Enable API verification and complete automation + +### Phase 4: Polish and Cleanup (Week 4) +1. **Day 1-2**: Fix iOS artifacts cleanup +2. **Day 3-4**: Enhance notifications and error handling +3. **Day 5**: Documentation and testing + +## File Structure Changes + +``` +app/ +├── fastlane/ +│ ├── Fastfile (updated) +│ ├── helpers.rb (updated) +│ ├── version_manager.rb (new) +│ └── test_groups.yml (new) +├── scripts/ +│ ├── deploy.js (new) +│ ├── version.js (new) +│ └── cleanup.js (updated) +├── version.json (new) +└── .github/ + └── workflows/ + ├── mobile-deploy.yml (updated) + └── mobile-deploy-manual.yml (new) +``` + +## Configuration Examples + +### GitHub Workflow Dispatch Inputs +```yaml +workflow_dispatch: + inputs: + platform: + description: 'Platform to deploy' + required: true + default: 'both' + type: choice + options: + - ios + - android + - both + deployment_track: + description: 'Deployment track' + required: true + default: 'internal' + type: choice + options: + - internal + - beta + - production + test_groups: + description: 'Test groups (comma-separated)' + required: false + default: 'default' + version_bump: + description: 'Version bump type' + required: false + default: 'build' + type: choice + options: + - build + - patch + - minor + - major +``` + +### Environment Variables +```bash +# Required +IOS_APP_IDENTIFIER=com.self.app +ANDROID_PACKAGE_NAME=com.self.app + +# Testing Groups +IOS_TEST_GROUPS_DEFAULT="Internal Testers" +IOS_TEST_GROUPS_NFC="NFC Test Group,QA Team" +ANDROID_TEST_GROUPS_DEFAULT="internal" +ANDROID_TEST_GROUPS_NFC="nfc-testers,qa-team" + +# Deployment Configuration +AUTO_DEPLOY_ON_MERGE=true +REQUIRE_APPROVAL_FOR_PROD=true +CLEANUP_ARTIFACTS_POST_BUILD=true + +# Notifications +SLACK_DEPLOYMENT_CHANNEL="#deployments" +ENABLE_DEPLOYMENT_METRICS=true +``` + +## Success Criteria + +1. **Zero Manual Input**: Standard deployments require no manual version entry +2. **Targeted Testing**: Can deploy to specific groups without public release +3. **Full Automation**: Both iOS and Android deploy without manual steps +4. **Clean Codebase**: Well-organized, maintainable deployment code +5. **Robust Error Handling**: Clear errors, automatic retries, and fallbacks +6. **Comprehensive Notifications**: Team stays informed of all deployments +7. **Fast Deployments**: Improved build times through caching and parallelization + +## Security Considerations + +1. **Secrets Management**: + - Use GitHub Environments for production secrets + - Implement secret rotation + - Audit access logs + +2. **Deployment Gates**: + - Require approval for production + - Implement rollback mechanism + - Add deployment windows + +3. **Access Control**: + - Limit who can trigger deployments + - Use branch protection rules + - Implement CODEOWNERS for workflow files + +## Monitoring and Metrics + +1. **Deployment Metrics**: + - Build duration trends + - Success/failure rates + - Deployment frequency + - Time to production + +2. **Alerts**: + - Failed deployments + - Unusual deployment patterns + - Certificate expiration warnings + - Service degradation + +## Rollback Strategy + +1. **Immediate Rollback**: + - Keep previous builds available + - One-click rollback in Fastlane + - Automated rollback on critical failures + +2. **Version Control**: + - Tag all deployments + - Keep deployment history + - Document rollback procedures + +This plan transforms the mobile deployment process from a manual, error-prone workflow to a streamlined, automated system that supports various deployment scenarios while maintaining security and reliability. \ No newline at end of file From f68b1608e503f0740028fbf039cd1ce635055a88 Mon Sep 17 00:00:00 2001 From: hackertron Date: Thu, 10 Jul 2025 23:51:28 +0200 Subject: [PATCH 07/21] fix: use gradle_file_path instead of gradle_file for increment_version_code --- app/fastlane/Fastfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/fastlane/Fastfile b/app/fastlane/Fastfile index 568641ab6..80cd218f1 100644 --- a/app/fastlane/Fastfile +++ b/app/fastlane/Fastfile @@ -255,7 +255,7 @@ platform :android do # Update build.gradle with new version code increment_version_code( version_code: version_code, - gradle_file: android_gradle_file_path + gradle_file_path: android_gradle_file_path ) # TODO: uncomment when we have the permissions to run this action From 04ec74a73df21dd61a54c6324b6fed938bc6c150 Mon Sep 17 00:00:00 2001 From: hackertron Date: Fri, 11 Jul 2025 00:03:03 +0200 Subject: [PATCH 08/21] fix: use gsub to remove ../ prefix for CI compatibility --- app/fastlane/Fastfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/fastlane/Fastfile b/app/fastlane/Fastfile index 80cd218f1..2860dc5b4 100644 --- a/app/fastlane/Fastfile +++ b/app/fastlane/Fastfile @@ -255,7 +255,7 @@ platform :android do # Update build.gradle with new version code increment_version_code( version_code: version_code, - gradle_file_path: android_gradle_file_path + gradle_file_path: android_gradle_file_path.gsub("../", "") ) # TODO: uncomment when we have the permissions to run this action From 795e58cc61df0baa3aeba01d791f8aa502b8fad7 Mon Sep 17 00:00:00 2001 From: hackertron Date: Fri, 11 Jul 2025 00:27:35 +0200 Subject: [PATCH 09/21] chore: remove accidentally committed files - Remove .cursor/mcp.json - Remove .cursorignore - Remove deployment-automation-summary.md - Remove deployment-meeting-questions.md - Remove pipeline.md --- .cursor/mcp.json | 11 - .cursorignore | 298 ----------------------- deployment-automation-summary.md | 108 --------- deployment-meeting-questions.md | 158 ------------ pipeline.md | 397 ------------------------------- 5 files changed, 972 deletions(-) delete mode 100644 .cursor/mcp.json delete mode 100644 .cursorignore delete mode 100644 deployment-automation-summary.md delete mode 100644 deployment-meeting-questions.md delete mode 100644 pipeline.md diff --git a/.cursor/mcp.json b/.cursor/mcp.json deleted file mode 100644 index 89bcd1d72..000000000 --- a/.cursor/mcp.json +++ /dev/null @@ -1,11 +0,0 @@ -{ - "mcpServers": { - "self-mcp": { - "command": "/Users/nightmare/Projects/self-work/self-mcp/venv/bin/python", - "args": [ - "/Users/nightmare/Projects/self-work/self-mcp/self-mcp/server.py" - ], - "cwd": "/Users/nightmare/Projects/self-work/self-mcp/self-mcp" - } - } -} diff --git a/.cursorignore b/.cursorignore deleted file mode 100644 index 49e5795b6..000000000 --- a/.cursorignore +++ /dev/null @@ -1,298 +0,0 @@ -# .cursorignore - Cursor AI editor ignore file -# This file prevents Cursor from accessing sensitive files and improves performance -# Note: .gitignore files are automatically included - -# ======================================== -# Security & Sensitive Files -# ======================================== - -# Environment and secrets -**/.env -**/.env.* -**/env.sample -**/*secrets* -**/*credentials* -**/*private* -**/google-services.json -**/GoogleService-Info.plist - -# Keys and certificates -**/*.key -**/*.pem -**/*.p12 -**/*.jks -**/*.keystore -**/*.cer -**/*.crt -**/*.cert -**/mock_*.key -**/mock_*.pem -**/debug.keystore -**/dev-keystore - -# Mobile app sensitive files -app/android/app/google-services.json -app/ios/GoogleService-Info.plist -app/android/app/debug.keystore -app/android/dev-keystore - -# Deployment scripts and CI/CD -circuits/scripts/server/*.sh -**/*.sh -!node_modules/**/*.sh - -# Fastlane configuration (may contain secrets) -app/fastlane/Fastfile -app/fastlane/helpers.rb -app/Gemfile.lock - -# Test wallets and mock data -app/ios/passport.json -app/ios/OpenPassport/passport.json - -# Environment sample files -app/env.sample -sdk/.env - -# ======================================== -# Build Outputs & Generated Files -# ======================================== - -# General build outputs -**/dist/ -**/build/ -**/out/ -**/.next/ -**/*.tsbuildinfo - -# Mobile build outputs -app/android/app/build/ -app/android/build/ -app/ios/build/ -app/ios/Pods/ -app/ios/DerivedData/ -app/android/.gradle/ -app/android/gradle/ -app/android/gradlew -app/android/gradlew.bat - -# Circuit build outputs -circuits/build/ -circuits/**/*.r1cs -circuits/**/*.sym -circuits/**/*.json -circuits/**/*.wasm -circuits/**/*.zkey -circuits/**/*.vkey -circuits/**/*.wtns -circuits/ptau/ - -# Contract artifacts -contracts/artifacts/ -contracts/cache/ -contracts/typechain-types/ -contracts/ignition/deployments/ - -# ======================================== -# Dependencies & Package Management -# ======================================== - -# Node modules - everywhere -**/node_modules/ -**/pnpm-lock.yaml -**/yarn.lock -**/package-lock.json -**/.yarn/ -**/.pnp.* - -# Mobile specific -app/ios/Podfile.lock -app/android/link-assets-manifest.json -app/ios/link-assets-manifest.json - -# ======================================== -# Large Data Files -# ======================================== - -# Circuit deployment artifacts (large JSON files) -app/deployments/artifacts/ - -# Public keys and trees (large JSON files) -common/pubkeys/public_keys_parsed.json -common/pubkeys/serialized_*.json -common/pubkeys/serialized_csca_tree.json -common/pubkeys/serialized_dsc_tree.json -common/pubkeys/serialized_tree.json - -# OFAC and sanctions data -common/ofacdata/ -common/sanctionedCountries/ -common/ofacdata/original/*.csv -common/ofacdata/scripts/cleaned_sdn.csv -common/sanctionedCountries/outputs/sc_SMT.json - -# Mock certificates (numerous files) -common/src/mock_certificates/ - -# Large SMT (Sparse Merkle Tree) files -contracts/test/utils/smt.json - -# Circuit power files -circuits/circuits/utils/crypto/ec/powers/ - -# Test data files -app/android/android-passport-reader/app/src/main/assets/tessdata/ - -# ======================================== -# Development & Testing -# ======================================== - -# Test coverage -**/coverage/ -**/.nyc_output/ - -# Test files (optional - you might want AI to see tests) -# **/*.test.ts -# **/*.test.tsx -# **/*.spec.ts -# **/*.spec.tsx - -# Temporary files -**/.tmp/ -**/tmp/ -**/.cache/ -**/*.log -**/*.tmp - -# ======================================== -# IDE & Editor Files -# ======================================== - -.vscode/ -.idea/ -*.swp -*.swo -.DS_Store -.cursor/ - -# ======================================== -# Documentation Build -# ======================================== - -# Documentation builds (if any) -docs/build/ -docs/.docusaurus/ -documentation/build/ -documentation/.docusaurus/ - -# Development documentation -app/fastlane/DEV.md -common/ofacdata/original/dataspec.txt - -# Generated constants from certificates -common/src/constants/skiPem.ts - -# Circuit test cases (large) -circuits/tests/**/test_cases.ts - -# ======================================== -# Platform Specific -# ======================================== - -# iOS -*.xcworkspace/ -*.xcodeproj/ -*.pbxproj -app/ios/App Thinning Size Report.txt - -# Android -*.iml -.gradle/ -local.properties -app/android/android-passport-reader/examples/ - -# React Native config -app/react-native.config.cjs - -# ======================================== -# Miscellaneous -# ======================================== - -# License files (AI doesn't need these) -LICENSE -NOTICE - -# Large media files -**/*.mp4 -**/*.mov -**/*.avi -**/*.zip -**/*.tar.gz - -# Patch files -patches/ - -# ======================================== -# Project Specific Patterns -# ======================================== - -# Deployment addresses (might contain sensitive info) -contracts/deployed_addresses.json -contracts/ignition/deployments/staging/deployed_addresses.json -contracts/ignition/deployments/*/artifacts/ -contracts/deployments/*/ -!contracts/deployments/prod/ - -# Error selectors (generated) -contracts/error-selectors.json - -# Hardhat network fork data -contracts/.hardhat_fork_cache/ - -# Lottie animation files (large JSON) -app/src/assets/animations/*.json - -# Font files -app/src/assets/fonts/ - -# Yarn configuration -.yarnrc.yml -**/.yarn/ - -# Ruby dependencies -app/Gemfile -app/Gemfile.lock - -# Certificate generation config -common/src/scripts/extensions.cnf - -# Powers of Tau files -circuits/ptau/ - -# ======================================== -# Allow Important Files -# ======================================== - -# Ensure important config files are accessible -!tsconfig.json -!**/tsconfig.json -!package.json -!**/package.json -!hardhat.config.ts -!jest.config.* -!babel.config.* -!metro.config.* -!tamagui.config.ts - -# Ensure source code is accessible -!**/*.ts -!**/*.tsx -!**/*.js -!**/*.jsx -!**/*.sol -!**/*.circom - -# But exclude generated TypeScript declaration files -**/*.d.ts -!**/types/*.d.ts -!**/src/types/*.d.ts \ No newline at end of file diff --git a/deployment-automation-summary.md b/deployment-automation-summary.md deleted file mode 100644 index 8938d2ea3..000000000 --- a/deployment-automation-summary.md +++ /dev/null @@ -1,108 +0,0 @@ -# Mobile Deployment Automation - Executive Summary - -## 🎯 Project Goal -Transform our clunky manual mobile deployment process into a fully automated CI/CD pipeline that supports targeted testing and branch-based releases. - -## 📊 Current State Analysis - -### What's Working -- Basic GitHub Actions workflow exists (but disabled) -- Fastlane configured for both platforms -- Certificates and secrets properly stored -- iOS deployment is mostly automated - -### What's Broken -- **Workflows disabled**: `if: false` blocks all deployments -- **Version mismatch**: Android (2.5.4) behind iOS/package.json (2.5.5) -- **Manual hell**: Must enter versions manually, download from Slack for Android -- **No API access**: Can't query Play Store versions (missing permissions) -- **No targeted testing**: Can't deploy to NFC testers without public release - -## 🚀 Implementation Strategy - -### Week 1: Foundation -Fix permissions, implement version tracking, enable basic automation - -### Week 2: Testing Groups -Enable targeted deployments for Seshanth's NFC testing needs - -### Week 3: Full Automation -Complete Android automation after 48-hour permission wait - -### Week 4: Polish -Notifications, cleanup scripts, documentation - -## 🔑 Key Technical Decisions - -### Google Play Permissions -- **Solution**: Grant "Release Manager" role (not full admin) -- **Timeline**: 48-hour propagation wait -- **Fallback**: version.json tracking works immediately - -### Version Management -- **Single source**: version.json file -- **Branch logic**: dev = build bump, main = version bump -- **Sync**: Automatic across all platforms - -### Testing Distribution -- **Config-driven**: YAML files for group management -- **Dynamic**: Pass groups via workflow parameters -- **Flexible**: Support multiple testing scenarios - -## 🏗️ Architecture Changes - -### New Components -- `version.json` - Central version tracking -- `version_manager.rb` - Fastlane helper for versions -- `test_groups.yml` - Testing group configuration -- Health check scripts for pre-deployment validation - -### Modified Components -- GitHub workflows - Remove blocks, add automation -- Fastlane files - Dynamic group support -- Helper scripts - Uncomment API calls after permissions - -## 📈 Success Metrics - -| Metric | Before | After | -|--------|---------|--------| -| Deploy Time | 30-45 min | < 15 min | -| Manual Steps | 5-7 | 0 | -| Version Errors | Common | Impossible | -| Test Distribution | All or nothing | Targeted groups | -| Android Upload | Manual via Slack | Fully automated | - -## ⚡ Quick Wins (Day 1) -1. Fix version sync issue -2. Re-enable workflows -3. Remove manual inputs -4. Test automated builds - -## 🛡️ Risk Mitigation -- **Rollback**: Keep previous builds, implement halt capability -- **Testing**: Start with dev branch, staged rollouts -- **Monitoring**: Slack notifications, deployment tracking -- **Documentation**: Clear runbooks for emergencies - -## 🎓 Team Impact -- **Developers**: Push and forget - automation handles the rest -- **QA**: Direct access to test builds via groups -- **Release Manager**: Monitor, don't manually deploy -- **Seshanth**: Finally gets NFC-only test builds - -## 💰 ROI -- **Time saved**: 2-3 hours per week -- **Error reduction**: ~90% fewer deployment failures -- **Team satisfaction**: No more manual version tracking -- **Faster releases**: Deploy anytime, not just when someone's available - -## 🚦 Go/No-Go Checklist -- [ ] Release Manager permissions granted -- [ ] 48-hour wait acknowledged -- [ ] Team trained on new workflow -- [ ] Rollback plan documented -- [ ] First test on dev branch successful - ---- - -*This automation will transform our mobile deployment from a dreaded manual chore into a smooth, reliable process that just works.* \ No newline at end of file diff --git a/deployment-meeting-questions.md b/deployment-meeting-questions.md deleted file mode 100644 index 5a9715287..000000000 --- a/deployment-meeting-questions.md +++ /dev/null @@ -1,158 +0,0 @@ -# Mobile Deployment Automation - Team Meeting Questions - -## 🔑 Critical Decisions - -### 1. **Google Play Permissions** -**Current Situation**: We can't query Play Store versions or fully automate Android deployments due to service account permission limitations. - -**Solution Found**: Service account needs "Release Manager" role in Google Play Console (not "Admin/God mode"). - -**Options**: -- a) Grant "Release Manager" role to service account - - Allows version querying without excessive permissions - - Requires 24-48 hour wait for propagation - - This is the recommended approach -- b) Use version.json as primary tracking with API as verification - - Implement immediately, add API verification after permissions granted -- c) Keep current manual process as permanent fallback - - Only if security team rejects Release Manager role - -**Question**: Can we grant "Release Manager" role and wait 48 hours for propagation? - -### 2. **Version Bumping Policy** -**Current Situation**: Manual version entry is error-prone and slows down deployments. - -**Options**: -- a) Auto-increment patch version on main branch merges (2.5.5 → 2.5.6) - - Fully automated, no developer input needed -- b) Require developers to explicitly bump version before merging - - More control but adds manual step -- c) Make it configurable per deployment - - Flexible but more complex - -**Question**: What's our preferred balance between automation and version control? - -### 3. **Testing Groups Configuration** -**Current Situation**: Seshanth needs NFC testing without public deployment. Others may have similar needs. - -**Options**: -- a) Hardcode groups in GitHub workflow - - Simple but requires PR to change groups -- b) Use a config file (test_groups.yml) - - Can be updated without changing workflow -- c) Pass groups as parameters each time - - Most flexible but requires manual input - -**Question**: How often do we expect testing groups to change? - -### 4. **Android Deployment Flow** -**Current Situation**: Android AAB must be downloaded from Slack and manually uploaded to Play Store. - -**Options**: -- a) Keep Slack download as permanent part of the flow - - Maintains human verification step -- b) Try to fully automate with workarounds - - Use version.json + minimal API permissions -- c) Make it optional (auto when possible, manual fallback) - - Best of both worlds but more complex - -**Question**: Is the manual step a feature (for safety) or a bug (to be fixed)? - -## 📊 Quick Context - -### Current Pain Points: -- Manual version/build number entry -- Android requires manual download from Slack and upload to Play Store -- Cannot query current Play Store version due to permissions -- No support for targeted external testing groups -- iOS build artifacts cleanup may not be working -- Overall process is clunky and error-prone - -### Proposed Solution: -- Automated versioning with version.json -- Branch-based deployments (dev → internal, main → production) -- Test group support for targeted deployments -- Improved notifications and deployment confirmation - -### Timeline: -- Week 1: Core automation and version management -- Week 2: Testing groups implementation -- Week 3: Google Play integration (within constraints) -- Week 4: Polish and cleanup - -## 🚀 Immediate Actions (Week 1) - -If approved, I can start immediately with: -1. Fix version sync issue (Android 2.5.4 vs iOS/package.json 2.5.5) -2. Re-enable the disabled workflows (currently `if: false`) -3. Remove manual version input requirement -4. Fix TestFlight groups configuration for NFC testing - -## ⚠️ Risk & Compliance Questions - -1. **Security**: Are there any compliance/security concerns with automating deployments? -2. **Approval Gates**: Do we need manual approval steps for production deployments? -3. **Rollback**: What's our rollback strategy if automated deployment goes wrong? -4. **Access Control**: Who should be able to trigger deployments? - -## 📝 Additional Considerations - -1. **Monitoring**: Should we add deployment metrics and alerting? -2. **Documentation**: Where should deployment procedures be documented? -3. **Training**: Who needs to be trained on the new workflow? -4. **Migration**: How do we transition from current manual process? - -## 🎯 Success Criteria - -- Zero manual input for standard deployments -- Targeted testing without public release -- Both iOS and Android fully automated (within permission constraints) -- Clear notifications and deployment status -- Faster deployment times through caching and parallelization - ---- - -**Note**: Please come prepared to discuss which options best fit our team's workflow and security requirements. The implementation details can be adjusted based on these decisions. - -## 📚 Research Findings (2025) - -### Google Play Console API Permissions (Clarified) -Based on 2025 documentation and Google's guidance: -- **Who Can Grant Access**: Account owner OR users with "Admin" privileges (specifically "Manage users and permissions") -- **Required Role**: "Release Manager" is sufficient (not "Admin all permissions") -- **Permissions Needed**: - - View app information and download bulk reports - - Create, edit, and roll out releases to testing tracks -- **API Scope**: `https://www.googleapis.com/auth/androidpublisher` (full scope required, not read-only) -- **Critical**: Wait 24-48 hours after granting permissions for propagation -- **Security**: Release Manager role avoids risks of full admin access (app deletion, data modification) - -### TestFlight External Groups (2025) -Current capabilities: -- Support for up to 10,000 external testers -- Groups must be created in App Store Connect before automation -- External builds require App Review approval -- Automation options: - - App Store Connect API (official) - - Fastlane/Pilot (add testers via CSV, manage groups) - - Xcode Cloud (native integration) -- **Limitation**: Can't add test notes during automated publishing - -### Mobile Deployment Best Practices (2025) -Industry standards from GitHub and others: -- **Release Cycle**: Weekly automated releases (GitHub's approach) -- **Architecture**: Parallel workflows for build, testing, notes, versioning -- **Security**: Base64 encode certificates, use GitHub secrets -- **Certificate Storage**: Consider Google Cloud Storage for Fastlane Match -- **Release Management**: Rotating "release captain", automated tracking issues -- **Testing**: Automated cherry-pick process for beta fixes - -### Staged/Phased Rollouts (2025) -Latest capabilities: -- **iOS**: Still automatic 7-day rollout with fixed percentages -- **Android**: - - Manual percentage control remains default - - **NEW**: Can halt fully live releases via API/Console (I/O 2025) - - API supports programmatic percentage control - - Third-party tools (Instabug, Bitrise) offer automated scheduling -- **Google Play API**: Updated as of July 2025 with new halt capabilities \ No newline at end of file diff --git a/pipeline.md b/pipeline.md deleted file mode 100644 index 0f7f77e5a..000000000 --- a/pipeline.md +++ /dev/null @@ -1,397 +0,0 @@ -# Mobile Deployment Workflow Improvements Plan - -## Overview -Streamline the mobile deployment workflow to eliminate manual steps, support targeted testing groups, and enable fully automated deployments based on branch merges. - -## Current Pain Points -1. Manual version/build number entry -2. Android requires manual download from Slack and upload to Play Store -3. Cannot query current Play Store version due to permissions -4. No support for targeted external testing groups -5. iOS build artifacts cleanup may not be working -6. Overall process is clunky and error-prone - -## Proposed Improvements - -### 1. **Automated Version Management** - -#### Auto-increment Logic -- `dev` branch: Increment build number only -- `main` branch: Increment patch version + build number -- Manual trigger: Allow major/minor version bumps - -#### Implementation Details -- Create `app/version.json` to track current version state -- Update package.json automatically -- Sync with native project files (Info.plist, build.gradle) -- Use git tags for version tracking - -#### Version File Structure -```json -{ - "version": "2.5.5", - "ios": { - "build": 145, - "lastDeployed": "2024-01-10T12:00:00Z" - }, - "android": { - "build": 145, - "lastDeployed": "2024-01-10T12:00:00Z" - } -} -``` - -This simplified structure tracks only the essential information needed for version management. Full deployment history is maintained through git tags and CI/CD logs. - -### 2. **Branch-based Automation** - -#### Workflow Triggers -- **Push to `dev`**: - - iOS → TestFlight Internal Testing - - Android → Play Store Internal Track - - Auto-increment build number - -- **Push to `main`**: - - iOS → App Store (Ready for Sale) - - Android → Play Store Production - - Auto-increment patch version and build - -- **Manual Dispatch**: - - Select platforms (iOS/Android/Both) - - Choose deployment track - - Specify test groups - - Option for version bump type - -#### Workflow Improvements -- Add matrix builds for parallel iOS/Android execution -- Implement proper caching for dependencies (Pods, Gradle) -- Add approval gates for production deployments -- Better error handling and retry logic -- Pre-deployment health checks (app builds, certificates valid, API keys working) - -#### Pre-Deployment Health Checks -Before initiating any deployment, the workflow will validate: -- **Certificates & Provisioning**: Check expiration dates, validate signatures -- **API Keys**: Verify App Store Connect and Google Play API access -- **Build Environment**: Ensure all dependencies are available -- **Version Conflicts**: Confirm version numbers are valid and not duplicated -- **Secrets**: Validate all required secrets are present and properly formatted - -```yaml -# Example health check implementation -- name: Pre-deployment Health Check - run: | - yarn deploy:health-check --platform=${{ matrix.platform }} - # Checks: - # - Certificate expiration (> 30 days) - # - API key validity - # - Build tools availability - # - Version number conflicts - # - Required secrets presence -``` - -### 3. **Targeted External Testing** - -#### Configuration Structure -```yaml -test_groups: - ios: - default: ["Internal Testers"] - nfc_testing: ["NFC Test Group", "QA Team"] - beta: ["Beta Testers", "External QA"] - android: - default: ["internal"] - nfc_testing: ["nfc-testers", "qa-team"] - beta: ["beta", "external-qa"] -``` - -#### Implementation -- **iOS**: Use TestFlight `groups` parameter in Fastlane -- **Android**: Use Play Console tracks or closed testing groups -- Add workflow_dispatch input for group selection -- Environment variable override: `DEPLOYMENT_GROUPS` - -### 4. **Google Play Automation Solutions** - -#### Option A - Release Manager Role (Recommended) -Grant the service account "Release Manager" role in Google Play Console: -- Provides necessary permissions without full admin access -- Allows querying version codes and managing releases -- Requires 24-48 hour wait for permissions to propagate -- Much safer than "Admin (all permissions)" - -Implementation after permissions granted: -```javascript -// With Release Manager role, the service account can: -// - Query current version codes -// - Create and manage releases -// - Upload APKs/AABs to tracks - -const { google } = require('googleapis'); -const androidpublisher = google.androidpublisher('v3'); - -async function getCurrentVersion() { - const auth = new google.auth.GoogleAuth({ - keyFile: 'play-store-key.json', - scopes: ['https://www.googleapis.com/auth/androidpublisher'] - }); - - const authClient = await auth.getClient(); - const res = await androidpublisher.edits.tracks.list({ - auth: authClient, - packageName: 'com.self.app', - editId: await createEdit() - }); - - return extractLatestVersion(res.data); -} -``` - -#### Option B - Version.json with API Verification -- Use `app/version.json` as primary source of truth -- After Release Manager permissions granted, add API verification -- Provides immediate solution while waiting for permissions -- Acts as fallback if API is unavailable - -### 5. **Enhanced Deployment Confirmation** - -#### Features -- Unified status checker for both platforms -- Real-time deployment monitoring via APIs -- Rich Slack notifications with: - - Platform icons (🍎 iOS, 🤖 Android) - - Version and build numbers - - Deployment track/environment - - Direct store links - - QR codes for easy testing - - Time elapsed - - Success/failure status - -#### Notification Template -``` -🚀 Deployment Complete - -Platform: 🍎 iOS -Version: 2.5.6 (Build 146) -Track: TestFlight Internal -Status: ✅ Success -Duration: 12m 34s -Groups: Internal Testers, NFC Test Group - -📱 Install: [TestFlight Link] -📊 Dashboard: [App Store Connect] -``` - -### 6. **iOS Artifacts Cleanup** - -#### Cleanup Tasks -```ruby -# In Fastfile -private_lane :cleanup_ios_artifacts do - # Remove temporary certificates - sh "rm -f #{ios_dist_cert_path}" - sh "rm -f #{ios_prov_profile_path}" - sh "rm -f #{ios_connect_api_key_path}" - - # Clear provisioning profiles - sh "rm -rf ~/Library/MobileDevice/Provisioning\\ Profiles/*.mobileprovision" - - # Delete derived data - clear_derived_data - - # Clean build folders - sh "rm -rf ./build" - sh "rm -rf ./ios/build" - - # Remove keychain (CI only) - if is_ci - sh "security delete-keychain build.keychain || true" - end -end -``` - -### 7. **Developer Experience Improvements** - -#### New Commands -```bash -# Local development -yarn deploy:ios:local # Deploy iOS locally with prompts -yarn deploy:android:local # Deploy Android locally with prompts -yarn deploy:test --group nfc # Deploy to specific test group - -# Version management -yarn version:bump:patch # Bump patch version -yarn version:bump:minor # Bump minor version -yarn version:bump:major # Bump major version -yarn version:sync # Sync versions across platforms - -# Status and monitoring -yarn deploy:status # Check deployment status -yarn deploy:history # View deployment history -``` - -#### Improved Error Messages -- Clear, actionable error messages -- Suggestion for fixes -- Links to documentation -- Automatic issue creation for failures - -## Implementation Plan - -### Phase 1: Core Automation (Week 1) -1. **Day 1-2**: Implement version.json and version management system -2. **Day 3-4**: Update GitHub workflow for branch-based triggers -3. **Day 5**: Add basic deployment confirmation and notifications - -### Phase 2: Testing Groups (Week 2) -1. **Day 1-2**: Add test group configuration system -2. **Day 3-4**: Update Fastfile to support dynamic groups -3. **Day 5**: Test with NFC testing scenario - -### Phase 3: Google Play Integration (Week 3) -1. **Day 1**: Request Release Manager role for service account -2. **Day 2**: Implement version.json as immediate solution -3. **Day 3-4**: Wait for permissions propagation (24-48 hours) -4. **Day 5**: Enable API verification and complete automation - -### Phase 4: Polish and Cleanup (Week 4) -1. **Day 1-2**: Fix iOS artifacts cleanup -2. **Day 3-4**: Enhance notifications and error handling -3. **Day 5**: Documentation and testing - -## File Structure Changes - -``` -app/ -├── fastlane/ -│ ├── Fastfile (updated) -│ ├── helpers.rb (updated) -│ ├── version_manager.rb (new) -│ └── test_groups.yml (new) -├── scripts/ -│ ├── deploy.js (new) -│ ├── version.js (new) -│ └── cleanup.js (updated) -├── version.json (new) -└── .github/ - └── workflows/ - ├── mobile-deploy.yml (updated) - └── mobile-deploy-manual.yml (new) -``` - -## Configuration Examples - -### GitHub Workflow Dispatch Inputs -```yaml -workflow_dispatch: - inputs: - platform: - description: 'Platform to deploy' - required: true - default: 'both' - type: choice - options: - - ios - - android - - both - deployment_track: - description: 'Deployment track' - required: true - default: 'internal' - type: choice - options: - - internal - - beta - - production - test_groups: - description: 'Test groups (comma-separated)' - required: false - default: 'default' - version_bump: - description: 'Version bump type' - required: false - default: 'build' - type: choice - options: - - build - - patch - - minor - - major -``` - -### Environment Variables -```bash -# Required -IOS_APP_IDENTIFIER=com.self.app -ANDROID_PACKAGE_NAME=com.self.app - -# Testing Groups -IOS_TEST_GROUPS_DEFAULT="Internal Testers" -IOS_TEST_GROUPS_NFC="NFC Test Group,QA Team" -ANDROID_TEST_GROUPS_DEFAULT="internal" -ANDROID_TEST_GROUPS_NFC="nfc-testers,qa-team" - -# Deployment Configuration -AUTO_DEPLOY_ON_MERGE=true -REQUIRE_APPROVAL_FOR_PROD=true -CLEANUP_ARTIFACTS_POST_BUILD=true - -# Notifications -SLACK_DEPLOYMENT_CHANNEL="#deployments" -ENABLE_DEPLOYMENT_METRICS=true -``` - -## Success Criteria - -1. **Zero Manual Input**: Standard deployments require no manual version entry -2. **Targeted Testing**: Can deploy to specific groups without public release -3. **Full Automation**: Both iOS and Android deploy without manual steps -4. **Clean Codebase**: Well-organized, maintainable deployment code -5. **Robust Error Handling**: Clear errors, automatic retries, and fallbacks -6. **Comprehensive Notifications**: Team stays informed of all deployments -7. **Fast Deployments**: Improved build times through caching and parallelization - -## Security Considerations - -1. **Secrets Management**: - - Use GitHub Environments for production secrets - - Implement secret rotation - - Audit access logs - -2. **Deployment Gates**: - - Require approval for production - - Implement rollback mechanism - - Add deployment windows - -3. **Access Control**: - - Limit who can trigger deployments - - Use branch protection rules - - Implement CODEOWNERS for workflow files - -## Monitoring and Metrics - -1. **Deployment Metrics**: - - Build duration trends - - Success/failure rates - - Deployment frequency - - Time to production - -2. **Alerts**: - - Failed deployments - - Unusual deployment patterns - - Certificate expiration warnings - - Service degradation - -## Rollback Strategy - -1. **Immediate Rollback**: - - Keep previous builds available - - One-click rollback in Fastlane - - Automated rollback on critical failures - -2. **Version Control**: - - Tag all deployments - - Keep deployment history - - Document rollback procedures - -This plan transforms the mobile deployment process from a manual, error-prone workflow to a streamlined, automated system that supports various deployment scenarios while maintaining security and reliability. \ No newline at end of file From 1bb3c7e2832a0278e3daf0016b9fb78d555f62b7 Mon Sep 17 00:00:00 2001 From: hackertron Date: Fri, 11 Jul 2025 00:27:46 +0200 Subject: [PATCH 10/21] 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 --- .github/workflows/mobile-deploy.yml | 34 +++++++++++++++++++++++++++++ 1 file changed, 34 insertions(+) diff --git a/.github/workflows/mobile-deploy.yml b/.github/workflows/mobile-deploy.yml index baaad4dfa..2b4e84ee3 100644 --- a/.github/workflows/mobile-deploy.yml +++ b/.github/workflows/mobile-deploy.yml @@ -323,6 +323,23 @@ jobs: bundle exec fastlane ios internal_test --verbose fi + - name: Commit version.json changes + if: success() && github.event.inputs.test_mode != 'true' + run: | + cd ${{ github.workspace }} + git config user.name "github-actions[bot]" + git config user.email "github-actions[bot]@users.noreply.github.com" + + # Check if version.json has changes + if git diff --quiet app/version.json; then + echo "No changes to version.json, skipping commit" + else + git add app/version.json + git commit -m "chore: update version.json after iOS deployment [skip ci]" + git push + echo "✅ Committed version.json changes" + fi + - name: Remove project.pbxproj updates we don't want to commit if: github.event_name == 'workflow_dispatch' && github.event.inputs.platform != 'android' run: | @@ -513,6 +530,23 @@ jobs: bundle exec fastlane android internal_test --verbose fi + - name: Commit version.json changes + if: success() && github.event.inputs.test_mode != 'true' + run: | + cd ${{ github.workspace }} + git config user.name "github-actions[bot]" + git config user.email "github-actions[bot]@users.noreply.github.com" + + # Check if version.json has changes + if git diff --quiet app/version.json; then + echo "No changes to version.json, skipping commit" + else + git add app/version.json + git commit -m "chore: update version.json after Android deployment [skip ci]" + git push + echo "✅ Committed version.json changes" + fi + - name: Get version from package.json if: github.event_name == 'workflow_dispatch' && github.event.inputs.platform != 'ios' uses: ./.github/actions/get-version From feaca68bd4818d3345fd42a852e8dd10dbea4891 Mon Sep 17 00:00:00 2001 From: hackertron Date: Mon, 14 Jul 2025 10:28:57 +0200 Subject: [PATCH 11/21] feat : update package.json in build step using npm version --- .github/workflows/mobile-deploy.yml | 48 ++++++++++++++++++++--------- 1 file changed, 34 insertions(+), 14 deletions(-) diff --git a/.github/workflows/mobile-deploy.yml b/.github/workflows/mobile-deploy.yml index 2b4e84ee3..7daf14211 100644 --- a/.github/workflows/mobile-deploy.yml +++ b/.github/workflows/mobile-deploy.yml @@ -323,21 +323,31 @@ jobs: bundle exec fastlane ios internal_test --verbose fi - - name: Commit version.json changes + - name: Update package.json version + if: success() && github.event.inputs.test_mode != 'true' + run: | + cd ${{ env.APP_PATH }} + # Use jq to extract the version from version.json + NEW_VERSION=$(jq -r .version version.json) + echo "Updating package.json to version $NEW_VERSION" + # Use yarn to update package.json and the lockfile + yarn version --new-version $NEW_VERSION --no-git-tag-version + + - name: Commit version files if: success() && github.event.inputs.test_mode != 'true' run: | cd ${{ github.workspace }} git config user.name "github-actions[bot]" git config user.email "github-actions[bot]@users.noreply.github.com" - # Check if version.json has changes - if git diff --quiet app/version.json; then - echo "No changes to version.json, skipping commit" + # Check if version files have changes + if git diff --quiet app/version.json app/package.json yarn.lock; then + echo "No changes to version files, skipping commit" else - git add app/version.json - git commit -m "chore: update version.json after iOS deployment [skip ci]" + git add app/version.json app/package.json yarn.lock + git commit -m "chore: update version files after iOS deployment [skip ci]" git push - echo "✅ Committed version.json changes" + echo "✅ Committed version file changes" fi - name: Remove project.pbxproj updates we don't want to commit @@ -530,21 +540,31 @@ jobs: bundle exec fastlane android internal_test --verbose fi - - name: Commit version.json changes + - name: Update package.json version + if: success() && github.event.inputs.test_mode != 'true' + run: | + cd ${{ env.APP_PATH }} + # Use jq to extract the version from version.json + NEW_VERSION=$(jq -r .version version.json) + echo "Updating package.json to version $NEW_VERSION" + # Use yarn to update package.json and the lockfile + yarn version --new-version $NEW_VERSION --no-git-tag-version + + - name: Commit version files if: success() && github.event.inputs.test_mode != 'true' run: | cd ${{ github.workspace }} git config user.name "github-actions[bot]" git config user.email "github-actions[bot]@users.noreply.github.com" - # Check if version.json has changes - if git diff --quiet app/version.json; then - echo "No changes to version.json, skipping commit" + # Check if version files have changes + if git diff --quiet app/version.json app/package.json yarn.lock; then + echo "No changes to version files, skipping commit" else - git add app/version.json - git commit -m "chore: update version.json after Android deployment [skip ci]" + git add app/version.json app/package.json yarn.lock + git commit -m "chore: update version files after Android deployment [skip ci]" git push - echo "✅ Committed version.json changes" + echo "✅ Committed version file changes" fi - name: Get version from package.json From 69a3f42fdc703a12d07d5e2fc470940f4148a353 Mon Sep 17 00:00:00 2001 From: hackertron Date: Mon, 14 Jul 2025 11:10:24 +0200 Subject: [PATCH 12/21] 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) --- .github/actions/mobile-setup/action.yml | 19 ++- .github/workflows/mobile-deploy.yml | 182 +++++++++++++++++++++++- app/fastlane/Fastfile | 8 +- 3 files changed, 199 insertions(+), 10 deletions(-) diff --git a/.github/actions/mobile-setup/action.yml b/.github/actions/mobile-setup/action.yml index 4fb15a340..d238b2368 100644 --- a/.github/actions/mobile-setup/action.yml +++ b/.github/actions/mobile-setup/action.yml @@ -51,7 +51,24 @@ runs: shell: bash run: | cd ${{ inputs.app_path }} + + # Configure Yarn corepack enable yarn set version 4.6.0 - yarn install + + # Check if node_modules exists and is valid + if [ -d "node_modules" ] && [ -f "node_modules/.yarn-integrity" ]; then + echo "✅ node_modules found - checking if up to date..." + # Yarn will only install if needed + yarn install --immutable + else + echo "📦 Installing dependencies from scratch..." + yarn install + fi + + # Run mobile-specific installation yarn install-app:mobile-deploy + + # Install Ruby gems with bundler (respecting cache) + echo "📦 Installing Ruby gems..." + bundle install --jobs 4 --retry 3 diff --git a/.github/workflows/mobile-deploy.yml b/.github/workflows/mobile-deploy.yml index 7daf14211..b69a63752 100644 --- a/.github/workflows/mobile-deploy.yml +++ b/.github/workflows/mobile-deploy.yml @@ -7,6 +7,13 @@ env: JAVA_VERSION: 17 ANDROID_API_LEVEL: 35 ANDROID_NDK_VERSION: 26.1.10909125 + + # Cache versioning - increment these to bust caches when needed + CACHE_VERSION: v1 # Global cache version + YARN_CACHE_VERSION: v1 # Yarn-specific cache version + GEMS_CACHE_VERSION: v1 # Ruby gems cache version + PODS_CACHE_VERSION: v1 # CocoaPods cache version + GRADLE_CACHE_VERSION: v1 # Gradle cache version # Path configuration WORKSPACE: ${{ github.workspace }} @@ -57,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 @@ -64,8 +74,50 @@ 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.CACHE_VERSION }}-${{ env.YARN_CACHE_VERSION }}-${{ hashFiles('app/yarn.lock') }} + restore-keys: | + ${{ runner.os }}-yarn-${{ env.CACHE_VERSION }}-${{ env.YARN_CACHE_VERSION }}- + ${{ runner.os }}-yarn-${{ env.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.CACHE_VERSION }}-${{ env.GEMS_CACHE_VERSION }}-${{ hashFiles('app/Gemfile.lock') }} + restore-keys: | + ${{ runner.os }}-gems-${{ env.CACHE_VERSION }}-${{ env.GEMS_CACHE_VERSION }}- + ${{ runner.os }}-gems-${{ env.CACHE_VERSION }}- + + - name: Cache CocoaPods + id: pods-cache + uses: actions/cache@v4 + with: + path: ${{ env.APP_PATH }}/ios/Pods + key: ${{ runner.os }}-pods-${{ env.CACHE_VERSION }}-${{ env.PODS_CACHE_VERSION }}-${{ hashFiles('app/ios/Podfile.lock') }} + restore-keys: | + ${{ runner.os }}-pods-${{ env.CACHE_VERSION }}-${{ env.PODS_CACHE_VERSION }}- + ${{ runner.os }}-pods-${{ env.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: Configure bundler to use cached gems + run: | + cd ${{ env.APP_PATH }} + bundle config set --local path 'vendor/bundle' + bundle config set --local deployment 'true' - name: Install Mobile Dependencies if: github.event_name == 'workflow_dispatch' && github.event.inputs.platform != 'android' @@ -327,9 +379,13 @@ jobs: if: success() && github.event.inputs.test_mode != 'true' run: | cd ${{ env.APP_PATH }} - # Use jq to extract the version from version.json + # Get current version from package.json + CURRENT_VERSION=$(node -p "require('./package.json').version") + # Get new version from version.json NEW_VERSION=$(jq -r .version version.json) - echo "Updating package.json to version $NEW_VERSION" + 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 @@ -406,6 +462,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') @@ -423,6 +503,61 @@ 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.CACHE_VERSION }}-${{ env.YARN_CACHE_VERSION }}-${{ hashFiles('app/yarn.lock') }} + restore-keys: | + ${{ runner.os }}-yarn-${{ env.CACHE_VERSION }}-${{ env.YARN_CACHE_VERSION }}- + ${{ runner.os }}-yarn-${{ env.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.CACHE_VERSION }}-${{ env.GEMS_CACHE_VERSION }}-${{ hashFiles('app/Gemfile.lock') }} + restore-keys: | + ${{ runner.os }}-gems-${{ env.CACHE_VERSION }}-${{ env.GEMS_CACHE_VERSION }}- + ${{ runner.os }}-gems-${{ env.CACHE_VERSION }}- + + - name: Cache Gradle dependencies + id: gradle-cache + uses: actions/cache@v4 + with: + path: | + ~/.gradle/caches + ~/.gradle/wrapper + key: ${{ runner.os }}-gradle-${{ env.CACHE_VERSION }}-${{ env.GRADLE_CACHE_VERSION }}-${{ hashFiles('app/android/**/*.gradle*', 'app/android/gradle/wrapper/gradle-wrapper.properties') }} + restore-keys: | + ${{ runner.os }}-gradle-${{ env.CACHE_VERSION }}-${{ env.GRADLE_CACHE_VERSION }}- + ${{ runner.os }}-gradle-${{ env.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: Configure bundler to use cached gems + run: | + cd ${{ env.APP_PATH }} + bundle config set --local path 'vendor/bundle' + bundle config set --local deployment 'true' + - name: Install Mobile Dependencies if: github.event_name == 'workflow_dispatch' && github.event.inputs.platform != 'ios' uses: ./.github/actions/mobile-setup @@ -447,7 +582,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 @@ -544,9 +679,13 @@ jobs: if: success() && github.event.inputs.test_mode != 'true' run: | cd ${{ env.APP_PATH }} - # Use jq to extract the version from version.json + # Get current version from package.json + CURRENT_VERSION=$(node -p "require('./package.json').version") + # Get new version from version.json NEW_VERSION=$(jq -r .version version.json) - echo "Updating package.json to version $NEW_VERSION" + 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 @@ -580,3 +719,32 @@ 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" diff --git a/app/fastlane/Fastfile b/app/fastlane/Fastfile index 2860dc5b4..7637d1243 100644 --- a/app/fastlane/Fastfile +++ b/app/fastlane/Fastfile @@ -84,7 +84,7 @@ platform :ios do 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], @@ -92,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 @@ -304,7 +306,7 @@ platform :android do 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, @@ -312,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 From 28c8267d817288a1f06061b2aa5a0dafdf78fa4d Mon Sep 17 00:00:00 2001 From: hackertron Date: Mon, 14 Jul 2025 11:13:13 +0200 Subject: [PATCH 13/21] fix: move bundler config after Ruby setup in mobile-setup action --- .github/actions/mobile-setup/action.yml | 7 +++++++ .github/workflows/mobile-deploy.yml | 12 ------------ 2 files changed, 7 insertions(+), 12 deletions(-) diff --git a/.github/actions/mobile-setup/action.yml b/.github/actions/mobile-setup/action.yml index d238b2368..27e145fae 100644 --- a/.github/actions/mobile-setup/action.yml +++ b/.github/actions/mobile-setup/action.yml @@ -47,6 +47,13 @@ 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' + - name: Install app dependencies shell: bash run: | diff --git a/.github/workflows/mobile-deploy.yml b/.github/workflows/mobile-deploy.yml index b69a63752..d966d7ba9 100644 --- a/.github/workflows/mobile-deploy.yml +++ b/.github/workflows/mobile-deploy.yml @@ -113,12 +113,6 @@ jobs: echo "- Gems cache hit: ${{ steps.gems-cache.outputs.cache-hit }}" echo "- Pods cache hit: ${{ steps.pods-cache.outputs.cache-hit }}" - - name: Configure bundler to use cached gems - run: | - cd ${{ env.APP_PATH }} - bundle config set --local path 'vendor/bundle' - bundle config set --local deployment 'true' - - name: Install Mobile Dependencies if: github.event_name == 'workflow_dispatch' && github.event.inputs.platform != 'android' uses: ./.github/actions/mobile-setup @@ -552,12 +546,6 @@ jobs: echo "- Gradle cache hit: ${{ steps.gradle-cache.outputs.cache-hit }}" echo "- NDK cache hit: ${{ steps.ndk-cache.outputs.cache-hit }}" - - name: Configure bundler to use cached gems - run: | - cd ${{ env.APP_PATH }} - bundle config set --local path 'vendor/bundle' - bundle config set --local deployment 'true' - - name: Install Mobile Dependencies if: github.event_name == 'workflow_dispatch' && github.event.inputs.platform != 'ios' uses: ./.github/actions/mobile-setup From 555defc6c53d1d985d8839584bc310635144e32b Mon Sep 17 00:00:00 2001 From: hackertron Date: Mon, 14 Jul 2025 11:16:52 +0200 Subject: [PATCH 14/21] 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. --- .github/workflows/mobile-deploy.yml | 46 ++++++++++++++--------------- 1 file changed, 23 insertions(+), 23 deletions(-) diff --git a/.github/workflows/mobile-deploy.yml b/.github/workflows/mobile-deploy.yml index d966d7ba9..a896bb19e 100644 --- a/.github/workflows/mobile-deploy.yml +++ b/.github/workflows/mobile-deploy.yml @@ -9,11 +9,11 @@ env: ANDROID_NDK_VERSION: 26.1.10909125 # Cache versioning - increment these to bust caches when needed - CACHE_VERSION: v1 # Global cache version - YARN_CACHE_VERSION: v1 # Yarn-specific cache version - GEMS_CACHE_VERSION: v1 # Ruby gems cache version - PODS_CACHE_VERSION: v1 # CocoaPods cache version - GRADLE_CACHE_VERSION: v1 # Gradle cache version + 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 }} @@ -81,30 +81,30 @@ jobs: path: | ${{ env.APP_PATH }}/node_modules ~/.cache/yarn - key: ${{ runner.os }}-yarn-${{ env.CACHE_VERSION }}-${{ env.YARN_CACHE_VERSION }}-${{ hashFiles('app/yarn.lock') }} + key: ${{ runner.os }}-yarn-${{ env.GH_CACHE_VERSION }}-${{ env.GH_YARN_CACHE_VERSION }}-${{ hashFiles('app/yarn.lock') }} restore-keys: | - ${{ runner.os }}-yarn-${{ env.CACHE_VERSION }}-${{ env.YARN_CACHE_VERSION }}- - ${{ runner.os }}-yarn-${{ env.CACHE_VERSION }}- + ${{ 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.CACHE_VERSION }}-${{ env.GEMS_CACHE_VERSION }}-${{ hashFiles('app/Gemfile.lock') }} + key: ${{ runner.os }}-gems-${{ env.GH_CACHE_VERSION }}-${{ env.GH_GEMS_CACHE_VERSION }}-${{ hashFiles('app/Gemfile.lock') }} restore-keys: | - ${{ runner.os }}-gems-${{ env.CACHE_VERSION }}-${{ env.GEMS_CACHE_VERSION }}- - ${{ runner.os }}-gems-${{ env.CACHE_VERSION }}- + ${{ 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.CACHE_VERSION }}-${{ env.PODS_CACHE_VERSION }}-${{ hashFiles('app/ios/Podfile.lock') }} + key: ${{ runner.os }}-pods-${{ env.GH_CACHE_VERSION }}-${{ env.GH_PODS_CACHE_VERSION }}-${{ hashFiles('app/ios/Podfile.lock') }} restore-keys: | - ${{ runner.os }}-pods-${{ env.CACHE_VERSION }}-${{ env.PODS_CACHE_VERSION }}- - ${{ runner.os }}-pods-${{ env.CACHE_VERSION }}- + ${{ runner.os }}-pods-${{ env.GH_CACHE_VERSION }}-${{ env.GH_PODS_CACHE_VERSION }}- + ${{ runner.os }}-pods-${{ env.GH_CACHE_VERSION }}- - name: Log cache status run: | @@ -504,20 +504,20 @@ jobs: path: | ${{ env.APP_PATH }}/node_modules ~/.cache/yarn - key: ${{ runner.os }}-yarn-${{ env.CACHE_VERSION }}-${{ env.YARN_CACHE_VERSION }}-${{ hashFiles('app/yarn.lock') }} + key: ${{ runner.os }}-yarn-${{ env.GH_CACHE_VERSION }}-${{ env.GH_YARN_CACHE_VERSION }}-${{ hashFiles('app/yarn.lock') }} restore-keys: | - ${{ runner.os }}-yarn-${{ env.CACHE_VERSION }}-${{ env.YARN_CACHE_VERSION }}- - ${{ runner.os }}-yarn-${{ env.CACHE_VERSION }}- + ${{ 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.CACHE_VERSION }}-${{ env.GEMS_CACHE_VERSION }}-${{ hashFiles('app/Gemfile.lock') }} + key: ${{ runner.os }}-gems-${{ env.GH_CACHE_VERSION }}-${{ env.GH_GEMS_CACHE_VERSION }}-${{ hashFiles('app/Gemfile.lock') }} restore-keys: | - ${{ runner.os }}-gems-${{ env.CACHE_VERSION }}-${{ env.GEMS_CACHE_VERSION }}- - ${{ runner.os }}-gems-${{ env.CACHE_VERSION }}- + ${{ 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 @@ -526,10 +526,10 @@ jobs: path: | ~/.gradle/caches ~/.gradle/wrapper - key: ${{ runner.os }}-gradle-${{ env.CACHE_VERSION }}-${{ env.GRADLE_CACHE_VERSION }}-${{ hashFiles('app/android/**/*.gradle*', 'app/android/gradle/wrapper/gradle-wrapper.properties') }} + 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.CACHE_VERSION }}-${{ env.GRADLE_CACHE_VERSION }}- - ${{ runner.os }}-gradle-${{ env.CACHE_VERSION }}- + ${{ 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 From 47cdb5ea60919f0ed2cc7308c8a5ed7fe46b9bf2 Mon Sep 17 00:00:00 2001 From: hackertron Date: Mon, 14 Jul 2025 11:20:09 +0200 Subject: [PATCH 15/21] 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. --- .github/actions/mobile-setup/action.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/.github/actions/mobile-setup/action.yml b/.github/actions/mobile-setup/action.yml index 27e145fae..3a9339f65 100644 --- a/.github/actions/mobile-setup/action.yml +++ b/.github/actions/mobile-setup/action.yml @@ -52,7 +52,6 @@ runs: run: | cd ${{ inputs.app_path }} bundle config set --local path 'vendor/bundle' - bundle config set --local deployment 'true' - name: Install app dependencies shell: bash From 8d30040b5acf876fdb26aa6cd64536adc7637705 Mon Sep 17 00:00:00 2001 From: hackertron Date: Mon, 14 Jul 2025 11:29:57 +0200 Subject: [PATCH 16/21] 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 --- .github/actions/mobile-setup/action.yml | 41 +++++++++++++++++++------ .github/workflows/mobile-deploy.yml | 40 ++++++++++++++++++++++++ 2 files changed, 71 insertions(+), 10 deletions(-) diff --git a/.github/actions/mobile-setup/action.yml b/.github/actions/mobile-setup/action.yml index 3a9339f65..e1419e5d1 100644 --- a/.github/actions/mobile-setup/action.yml +++ b/.github/actions/mobile-setup/action.yml @@ -52,6 +52,8 @@ runs: 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 @@ -62,19 +64,38 @@ runs: corepack enable yarn set version 4.6.0 - # Check if node_modules exists and is valid - if [ -d "node_modules" ] && [ -f "node_modules/.yarn-integrity" ]; then - echo "✅ node_modules found - checking if up to date..." - # Yarn will only install if needed - yarn install --immutable - else - echo "📦 Installing dependencies from scratch..." - 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..." - bundle install --jobs 4 --retry 3 + 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 diff --git a/.github/workflows/mobile-deploy.yml b/.github/workflows/mobile-deploy.yml index a896bb19e..7f9ad330a 100644 --- a/.github/workflows/mobile-deploy.yml +++ b/.github/workflows/mobile-deploy.yml @@ -113,6 +113,26 @@ jobs: 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: | + cd ${{ env.APP_PATH }} + echo "🔍 Checking if lock files are in sync with dependency files..." + + # Quick check for obvious issues + if [ ! -f "yarn.lock" ]; then + echo "❌ ERROR: yarn.lock file is missing!" + echo "Run 'yarn install' locally and commit the yarn.lock file." + exit 1 + fi + + if [ ! -f "Gemfile.lock" ]; then + echo "❌ ERROR: Gemfile.lock file is missing!" + echo "Run 'bundle install' locally 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' uses: ./.github/actions/mobile-setup @@ -546,6 +566,26 @@ jobs: 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: | + cd ${{ env.APP_PATH }} + echo "🔍 Checking if lock files are in sync with dependency files..." + + # Quick check for obvious issues + if [ ! -f "yarn.lock" ]; then + echo "❌ ERROR: yarn.lock file is missing!" + echo "Run 'yarn install' locally and commit the yarn.lock file." + exit 1 + fi + + if [ ! -f "Gemfile.lock" ]; then + echo "❌ ERROR: Gemfile.lock file is missing!" + echo "Run 'bundle install' locally 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 From 0076db03a15375e647a16b5504f337e3045b7d7d Mon Sep 17 00:00:00 2001 From: hackertron Date: Mon, 14 Jul 2025 11:45:35 +0200 Subject: [PATCH 17/21] 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. --- app/Gemfile.lock | 6 ------ 1 file changed, 6 deletions(-) diff --git a/app/Gemfile.lock b/app/Gemfile.lock index 4197a637e..6ae234a60 100644 --- a/app/Gemfile.lock +++ b/app/Gemfile.lock @@ -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 From 13a76e0b1030c9a24b9ccf780f40463e1fb6ed90 Mon Sep 17 00:00:00 2001 From: hackertron Date: Mon, 14 Jul 2025 11:53:24 +0200 Subject: [PATCH 18/21] 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. --- .github/workflows/mobile-deploy.yml | 32 ++++++++++++++--------------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/.github/workflows/mobile-deploy.yml b/.github/workflows/mobile-deploy.yml index 7f9ad330a..678ef49e5 100644 --- a/.github/workflows/mobile-deploy.yml +++ b/.github/workflows/mobile-deploy.yml @@ -81,7 +81,7 @@ jobs: path: | ${{ env.APP_PATH }}/node_modules ~/.cache/yarn - key: ${{ runner.os }}-yarn-${{ env.GH_CACHE_VERSION }}-${{ env.GH_YARN_CACHE_VERSION }}-${{ hashFiles('app/yarn.lock') }} + 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 }}- @@ -115,19 +115,19 @@ jobs: - name: Verify lock files are up to date run: | - cd ${{ env.APP_PATH }} echo "🔍 Checking if lock files are in sync with dependency files..." - # Quick check for obvious issues - if [ ! -f "yarn.lock" ]; then - echo "❌ ERROR: yarn.lock file is missing!" - echo "Run 'yarn install' locally and commit the yarn.lock file." + # 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 - if [ ! -f "Gemfile.lock" ]; then + # 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' locally and commit the Gemfile.lock file." + echo "Run 'bundle install' in the app directory and commit the Gemfile.lock file." exit 1 fi @@ -524,7 +524,7 @@ jobs: path: | ${{ env.APP_PATH }}/node_modules ~/.cache/yarn - key: ${{ runner.os }}-yarn-${{ env.GH_CACHE_VERSION }}-${{ env.GH_YARN_CACHE_VERSION }}-${{ hashFiles('app/yarn.lock') }} + 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 }}- @@ -568,19 +568,19 @@ jobs: - name: Verify lock files are up to date run: | - cd ${{ env.APP_PATH }} echo "🔍 Checking if lock files are in sync with dependency files..." - # Quick check for obvious issues - if [ ! -f "yarn.lock" ]; then - echo "❌ ERROR: yarn.lock file is missing!" - echo "Run 'yarn install' locally and commit the yarn.lock file." + # 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 - if [ ! -f "Gemfile.lock" ]; then + # 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' locally and commit the Gemfile.lock file." + echo "Run 'bundle install' in the app directory and commit the Gemfile.lock file." exit 1 fi From d7f5dfc7cd732b09fae94e570496caf3d80f2da1 Mon Sep 17 00:00:00 2001 From: hackertron Date: Mon, 14 Jul 2025 13:11:56 +0200 Subject: [PATCH 19/21] 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. --- app/fastlane/Fastfile | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/fastlane/Fastfile b/app/fastlane/Fastfile index 7637d1243..1bfdfcb77 100644 --- a/app/fastlane/Fastfile +++ b/app/fastlane/Fastfile @@ -59,7 +59,7 @@ platform :ios do desc "Push a new build to TestFlight Internal Testing" lane :internal_test do |options| - test_mode = options[:test_mode] == "true" + test_mode = options[:test_mode] == true || options[:test_mode] == "true" result = prepare_ios_build(prod_release: false) @@ -228,7 +228,7 @@ platform :android do end private_lane :upload_android_build do |options| - test_mode = options[:test_mode] == "true" + 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) From 07a68594fc1af460143d4f7ecb2313fc0f315765 Mon Sep 17 00:00:00 2001 From: hackertron Date: Tue, 15 Jul 2025 12:11:52 +0200 Subject: [PATCH 20/21] 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 --- .github/workflows/mobile-deploy.yml | 147 ++++++++++++++---------- app/fastlane/helpers/version_manager.rb | 13 +-- app/scripts/version.cjs | 12 ++ 3 files changed, 102 insertions(+), 70 deletions(-) diff --git a/.github/workflows/mobile-deploy.yml b/.github/workflows/mobile-deploy.yml index 678ef49e5..a1b243c9d 100644 --- a/.github/workflows/mobile-deploy.yml +++ b/.github/workflows/mobile-deploy.yml @@ -381,7 +381,7 @@ 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 ---" - if [ "${{ github.event.inputs.test_mode }}" == "true" ]; then + 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 @@ -389,36 +389,7 @@ jobs: bundle exec fastlane ios internal_test --verbose fi - - name: Update package.json version - if: success() && github.event.inputs.test_mode != 'true' - 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 - NEW_VERSION=$(jq -r .version version.json) - 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 - - - name: Commit version files - if: success() && github.event.inputs.test_mode != 'true' - run: | - cd ${{ github.workspace }} - git config user.name "github-actions[bot]" - git config user.email "github-actions[bot]@users.noreply.github.com" - - # Check if version files have changes - if git diff --quiet app/version.json app/package.json yarn.lock; then - echo "No changes to version files, skipping commit" - else - git add app/version.json app/package.json yarn.lock - git commit -m "chore: update version files after iOS deployment [skip ci]" - git push - echo "✅ Committed version file changes" - 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' @@ -695,7 +666,7 @@ jobs: SLACK_ANNOUNCE_CHANNEL_NAME: ${{ secrets.SLACK_ANNOUNCE_CHANNEL_NAME }} run: | cd ${{ env.APP_PATH }} - if [ "${{ github.event.inputs.test_mode }}" == "true" ]; then + 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 @@ -703,36 +674,7 @@ jobs: bundle exec fastlane android internal_test --verbose fi - - name: Update package.json version - if: success() && github.event.inputs.test_mode != 'true' - 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 - NEW_VERSION=$(jq -r .version version.json) - 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 - - - name: Commit version files - if: success() && github.event.inputs.test_mode != 'true' - run: | - cd ${{ github.workspace }} - git config user.name "github-actions[bot]" - git config user.email "github-actions[bot]@users.noreply.github.com" - - # Check if version files have changes - if git diff --quiet app/version.json app/package.json yarn.lock; then - echo "No changes to version files, skipping commit" - else - git add app/version.json app/package.json yarn.lock - git commit -m "chore: update version files after Android deployment [skip ci]" - git push - echo "✅ Committed version file changes" - 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' @@ -776,3 +718,84 @@ jobs: 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 + 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 diff --git a/app/fastlane/helpers/version_manager.rb b/app/fastlane/helpers/version_manager.rb index 8896bc9c3..8a107fa5a 100644 --- a/app/fastlane/helpers/version_manager.rb +++ b/app/fastlane/helpers/version_manager.rb @@ -62,17 +62,14 @@ def bump_android_build_number 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 - case platform - when 'ios' - data['ios']['lastDeployed'] = timestamp - when 'android' - data['android']['lastDeployed'] = timestamp - else - UI.user_error!("Invalid platform: #{platform}") - end + data[platform]['lastDeployed'] = timestamp write_version_file(data) UI.success("Updated #{platform} deployment timestamp") diff --git a/app/scripts/version.cjs b/app/scripts/version.cjs index 3b4d9e7e3..1bcc07175 100755 --- a/app/scripts/version.cjs +++ b/app/scripts/version.cjs @@ -37,6 +37,12 @@ function getPackageVersion() { } 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') { @@ -53,6 +59,12 @@ function bumpBuild(platform = 'both') { } 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(); From 290cd3d8af9fac6f9f2cf834223b2c8403792968 Mon Sep 17 00:00:00 2001 From: hackertron Date: Tue, 15 Jul 2025 17:23:15 +0200 Subject: [PATCH 21/21] 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 --- .github/workflows/mobile-deploy.yml | 52 +++++++++++++-------------- app/scripts/mobile-deploy-confirm.cjs | 46 ++++++++++++++++-------- app/scripts/version.cjs | 48 +++++++++++++++---------- 3 files changed, 87 insertions(+), 59 deletions(-) diff --git a/.github/workflows/mobile-deploy.yml b/.github/workflows/mobile-deploy.yml index a1b243c9d..194940c20 100644 --- a/.github/workflows/mobile-deploy.yml +++ b/.github/workflows/mobile-deploy.yml @@ -7,7 +7,7 @@ env: JAVA_VERSION: 17 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 @@ -116,21 +116,21 @@ jobs: - 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 @@ -452,22 +452,22 @@ jobs: 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" @@ -540,21 +540,21 @@ jobs: - 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 @@ -695,27 +695,27 @@ jobs: 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" @@ -725,7 +725,7 @@ jobs: runs-on: ubuntu-latest needs: [build-ios, build-android] if: | - always() && + always() && github.event.inputs.test_mode != 'true' && (needs.build-ios.result == 'success' || needs.build-android.result == 'success') env: @@ -744,10 +744,10 @@ jobs: - 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 "") @@ -755,15 +755,15 @@ jobs: # 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 + 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 @@ -771,18 +771,18 @@ jobs: - 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 @@ -793,7 +793,7 @@ jobs: COMMIT_MSG="$COMMIT_MSG Android deployment" fi COMMIT_MSG="$COMMIT_MSG [skip ci]" - + # Commit and push git commit -m "$COMMIT_MSG" git push diff --git a/app/scripts/mobile-deploy-confirm.cjs b/app/scripts/mobile-deploy-confirm.cjs index af3ee8a78..4fe00b500 100755 --- a/app/scripts/mobile-deploy-confirm.cjs +++ b/app/scripts/mobile-deploy-confirm.cjs @@ -262,13 +262,13 @@ function getVersionJsonData() { */ 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) { @@ -284,7 +284,7 @@ function getTimeAgo(timestamp) { */ function getCurrentVersions() { const versionJson = getVersionJsonData(); - + return { main: getMainVersion(), ios: getIOSVersion(), @@ -352,45 +352,61 @@ function displayPlatformVersions(platform, versions) { 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'; - + 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: ${currentBuild} → ${nextBuild}`); + 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'; - + 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: ${currentBuild} → ${nextBuild}`, ); - console.log(`${CONSOLE_SYMBOLS.ANDROID} Last Android Deploy: ${lastDeployed}`); + 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}`); + 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}`); + console.log( + `\n${CONSOLE_SYMBOLS.WARNING} Android build mismatch: version.json has ${jsonBuild}, but gradle has ${actualBuild}`, + ); } } } diff --git a/app/scripts/version.cjs b/app/scripts/version.cjs index 1bcc07175..7fca51140 100755 --- a/app/scripts/version.cjs +++ b/app/scripts/version.cjs @@ -39,43 +39,49 @@ function getPackageVersion() { function bumpBuild(platform = 'both') { const validPlatforms = ['ios', 'android', 'both']; if (!validPlatforms.includes(platform)) { - console.error(`Invalid platform: ${platform}. Must be one of: ${validPlatforms.join(', ')}`); + 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}`); + 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(', ')}`); + 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`); } @@ -83,18 +89,18 @@ function setDeploymentTime(platform) { 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 }; } @@ -115,10 +121,16 @@ switch (command) { 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( + ' 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); -} \ No newline at end of file +}