Skip to content

Instantly share code, notes, and snippets.

@cristiangu
Created March 29, 2025 12:51
Show Gist options
  • Save cristiangu/17b2be4759634f0eef7b56bca514e383 to your computer and use it in GitHub Desktop.
Save cristiangu/17b2be4759634f0eef7b56bca514e383 to your computer and use it in GitHub Desktop.
Update an IPA file with a new React Native JS bundle
module Fastlane
module Actions
module SharedValues
REFRESH_IPA_JS_BUNDLE_CUSTOM_VALUE = :REFRESH_IPA_JS_BUNDLE_CUSTOM_VALUE
end
class RefreshIpaJsBundleAction < Action
def self.run(params)
require 'fastlane/actions/resign'
unzipped_path = "./ipa_unzipped"
js_bundle_dir = "#{unzipped_path}/Payload/mobile_app.app"
js_bundle_path = "#{js_bundle_dir}/main.jsbundle"
sh("unzip mobile_app.ipa -d #{unzipped_path}")
sh("rm -rf mobile_app.ipa")
sh("rm -rf #{js_bundle_path}")
UI.message("Creating a new unminified JS bundle")
sh("yarn react-native bundle \
--dev false \
--minify false \
--platform ios \
--entry-file index.js \
--reset-cache \
--bundle-output main.jsbundle \
--sourcemap-output main.jsbundle.map")
UI.message("Compiling JS bundle to Hermes bytecode")
sh("ios/Pods/hermes-engine/destroot/bin/hermesc \
-O -emit-binary \
-output-source-map \
-out=main.jsbundle.hbc \
main.jsbundle")
sh("rm -f main.jsbundle")
sh("mv main.jsbundle.hbc main.jsbundle")
UI.message("Copying JS bundle to IPA")
sh("cp main.jsbundle #{js_bundle_path}")
#other_action.sentry_upload_sourcemaps(
# bundle_name: "main.jsbundle",
# release: "mobile-app@#{ENV["APP_VERSION"]}+#{params[:build_number]}",
#)
sh("cd #{unzipped_path} && zip -r ../mobile_app.ipa ./*")
UI.message("Resigning IPA, setting app version and incrementing build number")
UI.message("IPA file: mobile_app.ipa")
UI.message("App version: #{ENV["APP_VERSION"]}")
UI.message("Build number: #{params[:build_number]}")
Fastlane::Actions::ResignAction.run(
ipa: "./mobile_app.ipa",
short_version: ENV["APP_VERSION"],
bundle_version: params[:build_number],
signing_identity: ENV["sigh_com.bundle.identifier_#{params[:match_type]}_certificate-name"],
provisioning_profile: {
"com.bundle.identifier" => ENV["sigh_com.bundle.identifier_#{params[:match_type]}_profile-path"],
"com.bundle.identifier.NotificationService" => ENV["sigh_com.bundle.identifier.NotificationService_#{params[:match_type]}_profile-path"]
},
# Use the local app entitlement file(s), useful for multiple entitlement files. (App + Extensions)
use_app_entitlements: true
)
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_IPA_JS_BUNDLE_BUILD_NUMBER',
# a short description of this parameter
description: 'Build number for the new IPA',
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),
FastlaneCore::ConfigItem.new(key: :match_type,
# The name of the environment variable
env_name: 'FL_REFRESH_IPA_JS_BUNDLE_MATCH_TYPE',
# a short description of this parameter
description: 'Match type for the new IPA, can be adhoc or appstore',
verify_block: proc do |value|
unless value && !value.empty?
UI.user_error!("No match type for RefreshIpaJsBundleAction given, pass using `match_type: 'adhoc'`")
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_IPA_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)
# you can do things like
#
# true
#
# platform == :ios
#
# [:ios, :mac].include?(platform)
#
platform == :ios
end
end
end
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment