Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion app/Gemfile
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ gem "cocoapods", ">= 1.13", "!= 1.15.0", "!= 1.15.1"
gem "activesupport", ">= 6.1.7.5", "!= 7.1.0"

# Add fastlane for CI/CD
gem "fastlane", "~> 2.228.0"
gem "fastlane", "~> 2.230.0"

group :development do
gem "dotenv"
Expand Down
29 changes: 19 additions & 10 deletions app/Gemfile.lock
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ GEM
remote: https://rubygems.org/
specs:
CFPropertyList (3.0.8)
abbrev (0.1.2)
activesupport (7.2.3)
base64
benchmark (>= 0.3)
Expand All @@ -22,26 +23,26 @@ GEM
artifactory (3.0.17)
atomos (0.1.3)
aws-eventstream (1.4.0)
aws-partitions (1.1198.0)
aws-sdk-core (3.240.0)
aws-partitions (1.1200.0)
aws-sdk-core (3.241.1)
aws-eventstream (~> 1, >= 1.3.0)
aws-partitions (~> 1, >= 1.992.0)
aws-sigv4 (~> 1.9)
base64
bigdecimal
jmespath (~> 1, >= 1.6.1)
logger
aws-sdk-kms (1.118.0)
aws-sdk-core (~> 3, >= 3.239.1)
aws-sdk-kms (1.119.0)
aws-sdk-core (~> 3, >= 3.241.0)
aws-sigv4 (~> 1.5)
aws-sdk-s3 (1.209.0)
aws-sdk-core (~> 3, >= 3.234.0)
aws-sdk-s3 (1.210.1)
aws-sdk-core (~> 3, >= 3.241.0)
aws-sdk-kms (~> 1)
aws-sigv4 (~> 1.5)
aws-sigv4 (1.12.1)
aws-eventstream (~> 1, >= 1.0.2)
babosa (1.0.4)
base64 (0.3.0)
base64 (0.2.0)
benchmark (0.5.0)
bigdecimal (4.0.1)
claide (1.1.0)
Expand Down Expand Up @@ -88,6 +89,7 @@ GEM
highline (~> 2.0.0)
concurrent-ruby (1.3.6)
connection_pool (3.0.2)
csv (3.3.5)
declarative (0.0.20)
digest-crc (0.7.0)
rake (>= 12.0.0, < 14.0.0)
Expand Down Expand Up @@ -128,15 +130,18 @@ GEM
faraday_middleware (1.2.1)
faraday (~> 1.0)
fastimage (2.4.0)
fastlane (2.228.0)
fastlane (2.230.0)
CFPropertyList (>= 2.3, < 4.0.0)
abbrev (~> 0.1.2)
addressable (>= 2.8, < 3.0.0)
artifactory (~> 3.0)
aws-sdk-s3 (~> 1.0)
babosa (>= 1.0.3, < 2.0.0)
base64 (~> 0.2.0)
bundler (>= 1.12.0, < 3.0.0)
colored (~> 1.2)
commander (~> 4.6)
csv (~> 3.3)
dotenv (>= 2.1.1, < 3.0.0)
emoji_regex (>= 0.1, < 4.0)
excon (>= 0.71.0, < 1.0.0)
Expand All @@ -154,9 +159,12 @@ GEM
http-cookie (~> 1.0.5)
json (< 3.0.0)
jwt (>= 2.1.0, < 3)
logger (>= 1.6, < 2.0)
mini_magick (>= 4.9.4, < 5.0.0)
multipart-post (>= 2.0.0, < 3.0.0)
mutex_m (~> 0.3.0)
naturally (~> 2.2)
nkf (~> 0.2.0)
optparse (>= 0.1.1, < 1.0.0)
plist (>= 3.1.0, < 4.0.0)
rubyzip (>= 2.0.0, < 3.0.0)
Expand Down Expand Up @@ -232,13 +240,14 @@ GEM
minitest (6.0.0)
prism (~> 1.5)
molinillo (0.8.0)
multi_json (1.18.0)
multi_json (1.19.1)
multipart-post (2.4.1)
mutex_m (0.3.0)
nanaimo (0.4.0)
nap (1.1.0)
naturally (2.3.0)
netrc (0.11.0)
nkf (0.2.0)
nokogiri (1.18.10)
mini_portile2 (~> 2.8.2)
racc (~> 1.4)
Expand Down Expand Up @@ -304,7 +313,7 @@ DEPENDENCIES
activesupport (>= 6.1.7.5, != 7.1.0)
cocoapods (>= 1.13, != 1.15.1, != 1.15.0)
dotenv
fastlane (~> 2.228.0)
fastlane (~> 2.230.0)
fastlane-plugin-increment_version_code
fastlane-plugin-versioning_android
nokogiri (~> 1.18)
Expand Down
28 changes: 13 additions & 15 deletions app/fastlane/Fastfile
Original file line number Diff line number Diff line change
Expand Up @@ -172,6 +172,10 @@ platform :ios do

Fastlane::Helpers.verify_env_vars(required_env_vars)

if local_development && version_bump != "skip"
Fastlane::Helpers.bump_local_build_number("ios")
end

# Read build number from version.json (already set by CI or local version-manager.cjs)
build_number = Fastlane::Helpers.get_ios_build_number
UI.message("📦 Using iOS build number: #{build_number}")
Expand Down Expand Up @@ -310,18 +314,14 @@ platform :android do
# Uploads must be done by CI/CD machines with proper authentication
if local_development && !skip_upload
skip_upload = true
UI.important("🏠 LOCAL DEVELOPMENT: Automatically skipping Play Store upload")
UI.important(" Uploads require CI/CD machine permissions and will be handled automatically")
UI.important("🏠 LOCAL DEVELOPMENT: Play Store uploads are disabled")
UI.important(" Upload the AAB manually in the Play Console after the build finishes")
end

if local_development
if ENV["ANDROID_KEYSTORE_PATH"].nil?
ENV["ANDROID_KEYSTORE_PATH"] = Fastlane::Helpers.android_create_keystore(android_keystore_path)
end

if ENV["ANDROID_PLAY_STORE_JSON_KEY_PATH"].nil?
ENV["ANDROID_PLAY_STORE_JSON_KEY_PATH"] = Fastlane::Helpers.android_create_play_store_key(android_play_store_json_key_path)
end
end

required_env_vars = [
Expand All @@ -332,11 +332,13 @@ platform :android do
"ANDROID_KEY_PASSWORD",
"ANDROID_PACKAGE_NAME",
]
# Only require JSON key path when not running in CI (local development)
required_env_vars << "ANDROID_PLAY_STORE_JSON_KEY_PATH" if local_development

Fastlane::Helpers.verify_env_vars(required_env_vars)

if local_development && version_bump != "skip"
Fastlane::Helpers.bump_local_build_number("android")
end

# Read version code from version.json (already set by CI or local version-manager.cjs)
version_code = Fastlane::Helpers.get_android_build_number
UI.message("📦 Using Android build number: #{version_code}")
Expand All @@ -353,13 +355,6 @@ platform :android do
target_platform = options[:track] == "production" ? "Google Play" : "Internal Testing"
should_upload = Fastlane::Helpers.should_upload_app(target_platform)

# Validate JSON key only in local development; CI uses Workload Identity Federation (ADC)
if local_development
validate_play_store_json_key(
json_key: ENV["ANDROID_PLAY_STORE_JSON_KEY_PATH"],
)
end

Fastlane::Helpers.with_retry(max_retries: 3, delay: 10) do
gradle(
task: "clean bundleRelease --stacktrace --info",
Expand All @@ -376,6 +371,9 @@ platform :android do
if test_mode || skip_upload
if skip_upload
UI.important("🔨 BUILD ONLY: Skipping Play Store upload")
if local_development
UI.important("📦 Manual upload required: #{android_aab_path}")
end
else
UI.important("🧪 TEST MODE: Skipping Play Store upload")
end
Expand Down
14 changes: 14 additions & 0 deletions app/fastlane/helpers/version_manager.rb
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,20 @@ def get_android_build_number
data["android"]["build"]
end

def bump_local_build_number(platform)
unless %w[ios android].include?(platform)
UI.user_error!("Invalid platform: #{platform}. Must be 'ios' or 'android'")
end

data = read_version_file
data[platform]["build"] += 1

write_version_file(data)
UI.success("Bumped #{platform} build number to #{data[platform]["build"]}")

data[platform]["build"]
end
Comment on lines +47 to +59
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

Race condition risk when bumping multiple platforms concurrently.

If iOS and Android builds run in parallel locally (e.g., yarn mobile-local-deploy both), both lanes will call bump_local_build_number. Without file locking, the read-modify-write sequence can interleave, causing one platform's increment to be lost.

Scenario:

  1. iOS lane reads version.json: {ios: {build: 100}, android: {build: 200}}
  2. Android lane reads version.json: {ios: {build: 100}, android: {build: 200}}
  3. iOS lane writes: {ios: {build: 101}, android: {build: 200}}
  4. Android lane writes: {ios: {build: 100}, android: {build: 201}} (iOS bump lost)
🔒 Proposed fix using file locking
 def bump_local_build_number(platform)
   unless %w[ios android].include?(platform)
     UI.user_error!("Invalid platform: #{platform}. Must be 'ios' or 'android'")
   end

+  require 'fileutils'
+  lock_file = "#{VERSION_FILE_PATH}.lock"
+  
+  # Acquire exclusive lock
+  File.open(lock_file, File::RDWR|File::CREAT, 0644) do |lock|
+    lock.flock(File::LOCK_EX)
+    
-  data = read_version_file
-  data[platform]["build"] += 1
+    data = read_version_file
+    data[platform]["build"] += 1

-  write_version_file(data)
-  UI.success("Bumped #{platform} build number to #{data[platform]["build"]}")
+    write_version_file(data)
+    UI.success("Bumped #{platform} build number to #{data[platform]["build"]}")

-  data[platform]["build"]
+    data[platform]["build"]
+  end  # lock automatically released
 end

Committable suggestion skipped: line range outside the PR's diff.

🤖 Prompt for AI Agents
In @app/fastlane/helpers/version_manager.rb around lines 47 - 59, The
bump_local_build_number method has a race condition during the read-modify-write
of the version file; wrap the sequence that calls read_version_file, increments
data[platform]["build"], and write_version_file in an exclusive file lock (e.g.,
using File.open + flock) so concurrent processes serialize access; update
bump_local_build_number to acquire the lock on the versions file before reading
and only release it after writing and returning the new build number, ensuring
you still call UI.success and return data[platform]["build"] after the locked
update.


def verify_ci_version_match
# Verify that versions were pre-set by CI
unless ENV["CI_VERSION"] && ENV["CI_IOS_BUILD"] && ENV["CI_ANDROID_BUILD"]
Expand Down
8 changes: 8 additions & 0 deletions app/scripts/mobile-deploy-confirm.cjs
Original file line number Diff line number Diff line change
Expand Up @@ -441,6 +441,14 @@ function displayWarningsAndGitStatus() {
function displayFullConfirmation(platform, versions, deploymentMethod) {
displayDeploymentHeader(platform);
displayDeploymentMethod(deploymentMethod);
if (
deploymentMethod === DEPLOYMENT_METHODS.LOCAL_FASTLANE &&
(platform === PLATFORMS.ANDROID || platform === PLATFORMS.BOTH)
) {
console.log(
`${CONSOLE_SYMBOLS.WARNING} Local Android uploads are disabled. You'll need to manually upload the AAB in Play Console.`,
);
}
displayPlatformVersions(platform, versions);
displayWarningsAndGitStatus();
}
Expand Down
Loading