Last active
March 29, 2025 12:49
Update an APK file with a new React Native JS bundle
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
module Fastlane | |
module Actions | |
module SharedValues | |
REFRESH_APK_JS_BUNDLE_CUSTOM_VALUE = :REFRESH_APK_JS_BUNDLE_CUSTOM_VALUE | |
end | |
class RefreshApkJsBundleAction < Action | |
def self.run(params) | |
# fastlane will take care of reading in the parameter and fetching the environment variable: | |
require 'fastlane/actions/gradle' | |
sh('bash ./fastlane/install-tools-android.sh') | |
unzipped_path = "./mobile_app" | |
js_bundle_path = "#{unzipped_path}/assets/index.android.bundle" | |
sh("mv ./android/app/build/outputs/apk/release/mobile_app.apk ./mobile_app.apk") | |
sh("rm -rf ./android/app/build/outputs/apk/release/mobile_app.apk") | |
sh("apktool d mobile_app.apk") | |
sh("rm -rf mobile_app.apk") | |
sh("rm -rf #{js_bundle_path}") | |
UI.message("Creating a new unminified JS bundle") | |
sh("yarn react-native bundle \ | |
--dev false \ | |
--minify false \ | |
--platform android \ | |
--entry-file index.js \ | |
--reset-cache \ | |
--bundle-output index.android.bundle \ | |
--sourcemap-output index.android.bundle.map") | |
UI.message("Compiling JS bundle to Hermes bytecode") | |
sh("node_modules/react-native/sdks/hermesc/linux64-bin/hermesc \ | |
-O -emit-binary \ | |
-output-source-map \ | |
-out=index.android.bundle.hbc \ | |
index.android.bundle") | |
sh("rm -f index.android.bundle") | |
sh("mv index.android.bundle.hbc index.android.bundle") | |
UI.message("Copying JS bundle to APK assets") | |
sh("cp index.android.bundle #{js_bundle_path}") | |
# Upload the new source maps to a crash reporting tool. | |
#other_action.sentry_upload_sourcemaps( | |
# bundle_name: "index.android.bundle", | |
# release: "mobile-app@#{ENV["APP_VERSION"]}+#{params[:build_number]}", | |
#) | |
UI.message("Setting app version and build number") | |
UI.message("App version: #{ENV["APP_VERSION"]}") | |
UI.message("Build number: #{params[:build_number]}") | |
sh("cd #{unzipped_path} && yq -i '.versionInfo.versionName = \"#{ENV["APP_VERSION"]}\"' apktool.yml") | |
sh("cd #{unzipped_path} && yq -i '.versionInfo.versionCode = #{params[:build_number]}' apktool.yml") | |
UI.message("Zipping APK") | |
sh("apktool b #{unzipped_path} -o mobile_app.apk") | |
UI.message("Resigning APK") | |
# TODO: Update to zipalign -P 16 when upgrading to a newer React Native | |
# https://github.com/facebook/react-native/issues/45054 | |
sh("zipalign -P 4 -f -v 4 ./mobile_app.apk ./mobile_app-aligned.apk") | |
sh("rm -rf ./mobile_app.apk") | |
sh("mv './mobile_app-aligned.apk' './mobile_app.apk'") | |
# Make sure to use the same signing configuration as the one used in the build.gradle | |
sh("apksigner sign -v --v2-signing-enabled true --ks ./android/app/release.keystore --ks-key-alias #{ENV['ANDROID_KEYSTORE_ALIAS']} --ks-pass pass:'#{ENV['ANDROID_KEYSTORE_PRIVATE_KEY_PASSWORD']}' --key-pass pass:'#{ENV['ANDROID_KEYSTORE_PASSWORD']}' mobile_app.apk") | |
# Copy APK to the android build folder so it can cached by the CI at the end of the build | |
sh("cp ./mobile_app.apk ./android/app/build/outputs/apk/release/mobile_app.apk") | |
end | |
##################################################### | |
# @!group Documentation | |
##################################################### | |
def self.description | |
'A short description with <= 80 characters of what this action does' | |
end | |
def self.details | |
# Optional: | |
# this is your chance to provide a more detailed description of this action | |
'You can use this action to do cool things...' | |
end | |
def self.available_options | |
# Define all options your action supports. | |
# Below a few examples | |
[ | |
FastlaneCore::ConfigItem.new(key: :build_number, | |
# The name of the environment variable | |
env_name: 'FL_REFRESH_APK_JS_BUNDLE_BUILD_NUMBER', | |
# a short description of this parameter | |
description: 'Build number for the new APK', | |
verify_block: proc do |value| | |
unless value && !value.empty? | |
UI.user_error!("No build number for RefreshIpaJsBundleAction given, pass using `build_number: '231'`") | |
end | |
# UI.user_error!("Couldn't find file at path '#{value}'") unless File.exist?(value) | |
end), | |
] | |
end | |
def self.output | |
# Define the shared values you are going to provide | |
# Example | |
[ | |
['REFRESH_APK_JS_BUNDLE_CUSTOM_VALUE', 'A description of what this value contains'] | |
] | |
end | |
def self.return_value | |
# If your method provides a return value, you can describe here what it does | |
end | |
def self.authors | |
# So no one will ever forget your contribution to fastlane :) You are awesome btw! | |
['https://github.com/cristiangu'] | |
end | |
def self.is_supported?(platform) | |
platform == :android | |
end | |
end | |
end | |
end |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment