expo-updates 1.0.0-canary-20250306-d9d3e02 → 1.0.0-canary-20250331-817737a
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +21 -1
- package/android/build.gradle +5 -0
- package/android/src/main/java/expo/modules/updates/IUpdatesController.kt +1 -1
- package/android/src/main/java/expo/modules/updates/UpdatesController.kt +1 -1
- package/android/src/main/java/expo/modules/updates/UpdatesPackage.kt +9 -6
- package/android/src/main/java/expo/modules/updates/errorrecovery/ErrorRecovery.kt +3 -5
- package/cli/build/syncConfigurationToNativeAsync.js +4 -2
- package/cli/src/syncConfigurationToNativeAsync.ts +8 -2
- package/e2e/fixtures/custom_init/AppDelegate.swift +141 -0
- package/e2e/fixtures/custom_init/MainActivity.kt +49 -0
- package/e2e/fixtures/custom_init/MainApplication.kt +48 -0
- package/e2e/fixtures/project_files/eas.json +2 -7
- package/e2e/setup/create-eas-project-custom-init.ts +42 -0
- package/e2e/setup/project.ts +103 -8
- package/expo-updates-gradle-plugin/src/main/kotlin/expo/modules/updates/ExpoUpdatesPlugin.kt +9 -1
- package/ios/EXUpdates/ReactDelegateHandler/ExpoUpdatesReactDelegateHandler.swift +9 -2
- package/ios/EXUpdates/UpdatesUtils.swift +9 -1
- package/ios/EXUpdates.podspec +30 -9
- package/package.json +10 -11
package/CHANGELOG.md
CHANGED
|
@@ -10,6 +10,7 @@
|
|
|
10
10
|
### 🎉 New features
|
|
11
11
|
|
|
12
12
|
- Add new state machine context about startup procedure. ([#32433](https://github.com/expo/expo/pull/32433) by [@wschurman](https://github.com/wschurman))
|
|
13
|
+
- Support for updates.useNativeDebug. ([#35468](https://github.com/expo/expo/pull/35468) by [@douglowder](https://github.com/douglowder))
|
|
13
14
|
|
|
14
15
|
### 🐛 Bug fixes
|
|
15
16
|
|
|
@@ -25,10 +26,29 @@
|
|
|
25
26
|
- Fixed build error on iOS Expo Go. ([#34485](https://github.com/expo/expo/pull/34485) by [@kudo](https://github.com/kudo))
|
|
26
27
|
- Fixed Android unit test errors in BuilDataTest. ([#34510](https://github.com/expo/expo/pull/34510) by [@kudo](https://github.com/kudo))
|
|
27
28
|
- [Android] Started using expo modules gradle plugin. ([#34806](https://github.com/expo/expo/pull/34806) by [@lukmccall](https://github.com/lukmccall))
|
|
28
|
-
- Add update id headers to asset requests ([#34453](https://github.com/expo/expo/pull/34453) by [@gabrieldonadel](https://github.com/gabrieldonadel))
|
|
29
29
|
- Drop `fs-extra` in favor of `fs`. ([#35036](https://github.com/expo/expo/pull/35036) by [@kitten](https://github.com/kitten))
|
|
30
30
|
- Drop `fast-glob` in favor of `glob`. ([#35082](https://github.com/expo/expo/pull/35082) by [@kitten](https://github.com/kitten))
|
|
31
31
|
- Drop `fbemitter` in favor of custom emitter. ([#35317](https://github.com/expo/expo/pull/35317) by [@kitten](https://github.com/kitten))
|
|
32
|
+
- E2E tests for custom init. ([#35569](https://github.com/expo/expo/pull/35569) by [@douglowder](https://github.com/douglowder))
|
|
33
|
+
- Refactored `RCTReactNativeFactory` integration. ([#35679](https://github.com/expo/expo/pull/35679) by [@kudo](https://github.com/kudo))
|
|
34
|
+
|
|
35
|
+
## 0.27.4 - 2025-03-18
|
|
36
|
+
|
|
37
|
+
### 🎉 New features
|
|
38
|
+
|
|
39
|
+
- Support brownfield apps with EX_UPDATES_CUSTOM_INIT flag. ([#35391](https://github.com/expo/expo/pull/35391) by [@douglowder](https://github.com/douglowder))
|
|
40
|
+
|
|
41
|
+
## 0.27.3 - 2025-03-11
|
|
42
|
+
|
|
43
|
+
### 🐛 Bug fixes
|
|
44
|
+
|
|
45
|
+
- Pass through the package version to config plugin sync utilities ([#35372](https://github.com/expo/expo/pull/35372) by [@brentvatne](https://github.com/brentvatne))
|
|
46
|
+
|
|
47
|
+
## 0.27.2 - 2025-02-26
|
|
48
|
+
|
|
49
|
+
### 💡 Others
|
|
50
|
+
|
|
51
|
+
- Add update id headers to asset requests ([#34453](https://github.com/expo/expo/pull/34453) by [@gabrieldonadel](https://github.com/gabrieldonadel))
|
|
32
52
|
|
|
33
53
|
## 0.27.1 - 2025-02-21
|
|
34
54
|
|
package/android/build.gradle
CHANGED
|
@@ -60,6 +60,10 @@ def getBoolStringFromPropOrEnv(String name, Boolean defaultValue) {
|
|
|
60
60
|
// debug builds. (default false)
|
|
61
61
|
def exUpdatesNativeDebug = getBoolStringFromPropOrEnv("EX_UPDATES_NATIVE_DEBUG", false)
|
|
62
62
|
|
|
63
|
+
// If true, app is using custom code to initialize expo-updates, so default initialization code
|
|
64
|
+
// will be disabled.
|
|
65
|
+
def exUpdatesCustomInit = getBoolStringFromPropOrEnv("EX_UPDATES_CUSTOM_INIT", false)
|
|
66
|
+
|
|
63
67
|
// If true, code will run that delays app loading until updates is initialized, to prevent ANR issues.
|
|
64
68
|
// (default true)
|
|
65
69
|
def exUpdatesAndroidDelayLoadApp = getBoolStringFromPropOrEnv("EX_UPDATES_ANDROID_DELAY_LOAD_APP", true)
|
|
@@ -79,6 +83,7 @@ android {
|
|
|
79
83
|
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
|
|
80
84
|
|
|
81
85
|
buildConfigField("boolean", "EX_UPDATES_NATIVE_DEBUG", exUpdatesNativeDebug)
|
|
86
|
+
buildConfigField("boolean", "EX_UPDATES_CUSTOM_INIT", exUpdatesCustomInit)
|
|
82
87
|
buildConfigField("boolean", "EX_UPDATES_ANDROID_DELAY_LOAD_APP", exUpdatesAndroidDelayLoadApp)
|
|
83
88
|
buildConfigField("boolean", "USE_DEV_CLIENT", useDevClient.toString())
|
|
84
89
|
}
|
|
@@ -108,7 +108,7 @@ interface IUpdatesController {
|
|
|
108
108
|
this["runtimeVersion"] = runtimeVersion ?: ""
|
|
109
109
|
this["checkAutomatically"] = checkOnLaunch.toJSString()
|
|
110
110
|
this["channel"] = requestHeaders["expo-channel-name"] ?: ""
|
|
111
|
-
this["shouldDeferToNativeForAPIMethodAvailabilityInDevelopment"] = shouldDeferToNativeForAPIMethodAvailabilityInDevelopment ||
|
|
111
|
+
this["shouldDeferToNativeForAPIMethodAvailabilityInDevelopment"] = shouldDeferToNativeForAPIMethodAvailabilityInDevelopment || UpdatesPackage.isUsingNativeDebug
|
|
112
112
|
this["initialContext"] = initialContext.bundle
|
|
113
113
|
|
|
114
114
|
if (launchedUpdate != null) {
|
|
@@ -35,7 +35,7 @@ object UpdatesController {
|
|
|
35
35
|
}
|
|
36
36
|
val useDeveloperSupport =
|
|
37
37
|
(context as? ReactApplication)?.reactNativeHost?.useDeveloperSupport ?: false
|
|
38
|
-
if (useDeveloperSupport && !
|
|
38
|
+
if (useDeveloperSupport && !UpdatesPackage.isUsingNativeDebug) {
|
|
39
39
|
if (BuildConfig.USE_DEV_CLIENT) {
|
|
40
40
|
val devLauncherController = initializeAsDevLauncherWithoutStarting(context)
|
|
41
41
|
singletonInstance = devLauncherController
|
|
@@ -22,7 +22,6 @@ import kotlinx.coroutines.withContext
|
|
|
22
22
|
* applicable environments.
|
|
23
23
|
*/
|
|
24
24
|
class UpdatesPackage : Package {
|
|
25
|
-
private val useNativeDebug = BuildConfig.EX_UPDATES_NATIVE_DEBUG
|
|
26
25
|
|
|
27
26
|
override fun createReactNativeHostHandlers(context: Context): List<ReactNativeHostHandler> {
|
|
28
27
|
val handler: ReactNativeHostHandler = object : ReactNativeHostHandler {
|
|
@@ -57,12 +56,12 @@ class UpdatesPackage : Package {
|
|
|
57
56
|
override fun createReactActivityHandlers(activityContext: Context): List<ReactActivityHandler> {
|
|
58
57
|
val handler = object : ReactActivityHandler {
|
|
59
58
|
override fun getDelayLoadAppHandler(activity: ReactActivity, reactNativeHost: ReactNativeHost): ReactActivityHandler.DelayLoadAppHandler? {
|
|
60
|
-
if (!BuildConfig.EX_UPDATES_ANDROID_DELAY_LOAD_APP) {
|
|
59
|
+
if (!BuildConfig.EX_UPDATES_ANDROID_DELAY_LOAD_APP || isUsingCustomInit) {
|
|
61
60
|
return null
|
|
62
61
|
}
|
|
63
62
|
val context = activity.applicationContext
|
|
64
63
|
val useDeveloperSupport = reactNativeHost.useDeveloperSupport
|
|
65
|
-
if (!useDeveloperSupport ||
|
|
64
|
+
if (!useDeveloperSupport || isUsingNativeDebug) {
|
|
66
65
|
return ReactActivityHandler.DelayLoadAppHandler { whenReadyRunnable ->
|
|
67
66
|
CoroutineScope(Dispatchers.IO).launch {
|
|
68
67
|
startUpdatesController(context)
|
|
@@ -76,9 +75,11 @@ class UpdatesPackage : Package {
|
|
|
76
75
|
@WorkerThread
|
|
77
76
|
private suspend fun startUpdatesController(context: Context) {
|
|
78
77
|
withContext(Dispatchers.IO) {
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
78
|
+
if (!UpdatesPackage.isUsingCustomInit) {
|
|
79
|
+
UpdatesController.initialize(context)
|
|
80
|
+
// Call the synchronous `launchAssetFile()` function to wait for updates ready
|
|
81
|
+
UpdatesController.instance.launchAssetFile
|
|
82
|
+
}
|
|
82
83
|
}
|
|
83
84
|
}
|
|
84
85
|
|
|
@@ -119,5 +120,7 @@ class UpdatesPackage : Package {
|
|
|
119
120
|
|
|
120
121
|
companion object {
|
|
121
122
|
private val TAG = UpdatesPackage::class.java.simpleName
|
|
123
|
+
val isUsingNativeDebug = BuildConfig.EX_UPDATES_NATIVE_DEBUG
|
|
124
|
+
internal val isUsingCustomInit = BuildConfig.EX_UPDATES_CUSTOM_INIT
|
|
122
125
|
}
|
|
123
126
|
}
|
|
@@ -3,6 +3,7 @@ package expo.modules.updates.errorrecovery
|
|
|
3
3
|
import android.os.Handler
|
|
4
4
|
import android.os.HandlerThread
|
|
5
5
|
import com.facebook.react.bridge.DefaultJSExceptionHandler
|
|
6
|
+
import com.facebook.react.bridge.JSExceptionHandler
|
|
6
7
|
import com.facebook.react.bridge.ReactMarker
|
|
7
8
|
import com.facebook.react.bridge.ReactMarker.MarkerListener
|
|
8
9
|
import com.facebook.react.bridge.ReactMarkerConstants
|
|
@@ -114,11 +115,8 @@ class ErrorRecovery(
|
|
|
114
115
|
return
|
|
115
116
|
}
|
|
116
117
|
|
|
117
|
-
val defaultJSExceptionHandler =
|
|
118
|
-
|
|
119
|
-
this@ErrorRecovery.handleException(e)
|
|
120
|
-
}
|
|
121
|
-
}
|
|
118
|
+
val defaultJSExceptionHandler = JSExceptionHandler { e -> this@ErrorRecovery.handleException(e) }
|
|
119
|
+
|
|
122
120
|
val devSupportManagerClass = devSupportManager.javaClass
|
|
123
121
|
previousExceptionHandler = devSupportManagerClass.getDeclaredField("defaultJSExceptionHandler").let { field ->
|
|
124
122
|
field.isAccessible = true
|
|
@@ -32,13 +32,14 @@ async function syncConfigurationToNativeAndroidAsync(options) {
|
|
|
32
32
|
isPublicConfig: false,
|
|
33
33
|
skipSDKVersionRequirement: true,
|
|
34
34
|
});
|
|
35
|
+
const packageVersion = require('../../package.json').version;
|
|
35
36
|
// sync AndroidManifest.xml
|
|
36
37
|
const androidManifestPath = await config_plugins_1.AndroidConfig.Paths.getAndroidManifestAsync(options.projectRoot);
|
|
37
38
|
if (!androidManifestPath) {
|
|
38
39
|
throw new Error(`Could not find AndroidManifest.xml in project directory: "${options.projectRoot}"`);
|
|
39
40
|
}
|
|
40
41
|
const androidManifest = await config_plugins_1.AndroidConfig.Manifest.readAndroidManifestAsync(androidManifestPath);
|
|
41
|
-
const updatedAndroidManifest = await config_plugins_1.AndroidConfig.Updates.setUpdatesConfigAsync(options.projectRoot, exp, androidManifest);
|
|
42
|
+
const updatedAndroidManifest = await config_plugins_1.AndroidConfig.Updates.setUpdatesConfigAsync(options.projectRoot, exp, androidManifest, packageVersion);
|
|
42
43
|
await config_plugins_1.AndroidConfig.Manifest.writeAndroidManifestAsync(androidManifestPath, updatedAndroidManifest);
|
|
43
44
|
// sync strings.xml
|
|
44
45
|
const stringsJSONPath = await config_plugins_1.AndroidConfig.Strings.getProjectStringsXMLPathAsync(options.projectRoot);
|
|
@@ -53,8 +54,9 @@ async function syncConfigurationToNativeIosAsync(options) {
|
|
|
53
54
|
isPublicConfig: false,
|
|
54
55
|
skipSDKVersionRequirement: true,
|
|
55
56
|
});
|
|
57
|
+
const packageVersion = require('../../package.json').version;
|
|
56
58
|
const expoPlist = await readExpoPlistAsync(options.projectRoot);
|
|
57
|
-
const updatedExpoPlist = await config_plugins_1.IOSConfig.Updates.setUpdatesConfigAsync(options.projectRoot, exp, expoPlist);
|
|
59
|
+
const updatedExpoPlist = await config_plugins_1.IOSConfig.Updates.setUpdatesConfigAsync(options.projectRoot, exp, expoPlist, packageVersion);
|
|
58
60
|
await writeExpoPlistAsync(options.projectRoot, updatedExpoPlist);
|
|
59
61
|
}
|
|
60
62
|
async function readExpoPlistAsync(projectDir) {
|
|
@@ -41,6 +41,8 @@ async function syncConfigurationToNativeAndroidAsync(
|
|
|
41
41
|
skipSDKVersionRequirement: true,
|
|
42
42
|
});
|
|
43
43
|
|
|
44
|
+
const packageVersion = require('../../package.json').version;
|
|
45
|
+
|
|
44
46
|
// sync AndroidManifest.xml
|
|
45
47
|
const androidManifestPath = await AndroidConfig.Paths.getAndroidManifestAsync(
|
|
46
48
|
options.projectRoot
|
|
@@ -56,7 +58,8 @@ async function syncConfigurationToNativeAndroidAsync(
|
|
|
56
58
|
const updatedAndroidManifest = await AndroidConfig.Updates.setUpdatesConfigAsync(
|
|
57
59
|
options.projectRoot,
|
|
58
60
|
exp,
|
|
59
|
-
androidManifest
|
|
61
|
+
androidManifest,
|
|
62
|
+
packageVersion
|
|
60
63
|
);
|
|
61
64
|
await AndroidConfig.Manifest.writeAndroidManifestAsync(
|
|
62
65
|
androidManifestPath,
|
|
@@ -88,11 +91,14 @@ async function syncConfigurationToNativeIosAsync(
|
|
|
88
91
|
skipSDKVersionRequirement: true,
|
|
89
92
|
});
|
|
90
93
|
|
|
94
|
+
const packageVersion = require('../../package.json').version;
|
|
95
|
+
|
|
91
96
|
const expoPlist = await readExpoPlistAsync(options.projectRoot);
|
|
92
97
|
const updatedExpoPlist = await IOSConfig.Updates.setUpdatesConfigAsync(
|
|
93
98
|
options.projectRoot,
|
|
94
99
|
exp,
|
|
95
|
-
expoPlist
|
|
100
|
+
expoPlist,
|
|
101
|
+
packageVersion
|
|
96
102
|
);
|
|
97
103
|
await writeExpoPlistAsync(options.projectRoot, updatedExpoPlist);
|
|
98
104
|
}
|
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
import Expo
|
|
2
|
+
import EXUpdates
|
|
3
|
+
import React
|
|
4
|
+
import UIKit
|
|
5
|
+
|
|
6
|
+
@UIApplicationMain
|
|
7
|
+
class AppDelegate: ExpoAppDelegate {
|
|
8
|
+
var launchOptions: [UIApplication.LaunchOptionsKey: Any]?
|
|
9
|
+
// AppDelegate keeps a nullable reference to the updates controller
|
|
10
|
+
var updatesController: (any InternalAppControllerInterface)?
|
|
11
|
+
|
|
12
|
+
let packagerUrl = URL(string: "http://localhost:8081/index.bundle?platform=ios&dev=true")
|
|
13
|
+
let bundledUrl = Bundle.main.url(forResource: "main", withExtension: "jsbundle")
|
|
14
|
+
|
|
15
|
+
static func shared() -> AppDelegate {
|
|
16
|
+
guard let delegate = UIApplication.shared.delegate as? AppDelegate else {
|
|
17
|
+
fatalError("Could not get app delegate")
|
|
18
|
+
}
|
|
19
|
+
return delegate
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
override func bundleURL() -> URL? {
|
|
23
|
+
if AppDelegate.isRunningWithPackager() {
|
|
24
|
+
return packagerUrl
|
|
25
|
+
}
|
|
26
|
+
if let updatesUrl = updatesController?.launchAssetUrl() {
|
|
27
|
+
return updatesUrl
|
|
28
|
+
}
|
|
29
|
+
return bundledUrl
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
// If this is a debug build, and native debugging not enabled,
|
|
33
|
+
// then this returns true.
|
|
34
|
+
static func isRunningWithPackager() -> Bool {
|
|
35
|
+
return EXAppDefines.APP_DEBUG && !UpdatesUtils.isNativeDebuggingEnabled()
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
// Required initialization of react-native and expo-updates
|
|
39
|
+
private func initializeReactNativeAndUpdates(_ launchOptions: [UIApplication.LaunchOptionsKey: Any]?) {
|
|
40
|
+
self.launchOptions = launchOptions
|
|
41
|
+
self.moduleName = "main"
|
|
42
|
+
self.initialProps = [:]
|
|
43
|
+
self.reactNativeFactory = ExpoReactNativeFactory(delegate: self, reactDelegate: self.reactDelegate)
|
|
44
|
+
// AppController instance must always be created first.
|
|
45
|
+
// expo-updates creates a different type of controller
|
|
46
|
+
// depending on whether updates is enabled, and whether
|
|
47
|
+
// we are running in development mode or not.
|
|
48
|
+
AppController.initializeWithoutStarting()
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
Application launch initializes the custom view controller: all React Native
|
|
53
|
+
and updates initialization is handled there
|
|
54
|
+
*/
|
|
55
|
+
override func application(
|
|
56
|
+
_ application: UIApplication,
|
|
57
|
+
didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? = nil
|
|
58
|
+
) -> Bool {
|
|
59
|
+
initializeReactNativeAndUpdates(launchOptions)
|
|
60
|
+
|
|
61
|
+
// Create custom view controller, where the React Native view will be created
|
|
62
|
+
self.window = UIWindow(frame: UIScreen.main.bounds)
|
|
63
|
+
let controller = CustomViewController()
|
|
64
|
+
controller.view.clipsToBounds = true
|
|
65
|
+
self.window?.rootViewController = controller
|
|
66
|
+
window?.makeKeyAndVisible()
|
|
67
|
+
|
|
68
|
+
return true
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
override func application(_ app: UIApplication, open url: URL, options: [UIApplication.OpenURLOptionsKey: Any] = [:]) -> Bool {
|
|
72
|
+
return super.application(app, open: url, options: options) ||
|
|
73
|
+
RCTLinkingManager.application(app, open: url, options: options)
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
Custom view controller that handles React Native and expo-updates initialization
|
|
79
|
+
*/
|
|
80
|
+
public class CustomViewController: UIViewController, AppControllerDelegate {
|
|
81
|
+
let appDelegate = AppDelegate.shared()
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
If updates is enabled, the initializer starts the expo-updates system,
|
|
85
|
+
and view initialization is deferred to the expo-updates completion handler (onSuccess())
|
|
86
|
+
*/
|
|
87
|
+
public convenience init() {
|
|
88
|
+
self.init(nibName: nil, bundle: nil)
|
|
89
|
+
self.view.backgroundColor = .clear
|
|
90
|
+
if AppDelegate.isRunningWithPackager() {
|
|
91
|
+
// No expo-updates, just create the view
|
|
92
|
+
createView()
|
|
93
|
+
} else {
|
|
94
|
+
// Set the updatesController property in AppDelegate so its bundleURL() method
|
|
95
|
+
// works as expected
|
|
96
|
+
appDelegate.updatesController = AppController.sharedInstance
|
|
97
|
+
AppController.sharedInstance.delegate = self
|
|
98
|
+
AppController.sharedInstance.start()
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
required public override init(nibName nibNameOrNil: String?, bundle nibBundleOrNil: Bundle?) {
|
|
103
|
+
super.init(nibName: nibNameOrNil, bundle: nibBundleOrNil)
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
@available(*, unavailable)
|
|
107
|
+
required public init?(coder aDecoder: NSCoder) {
|
|
108
|
+
fatalError("init(coder:) has not been implemented")
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/**
|
|
112
|
+
expo-updates completion handler creates the root view and adds it to the controller's view
|
|
113
|
+
*/
|
|
114
|
+
public func appController(
|
|
115
|
+
_ appController: AppControllerInterface,
|
|
116
|
+
didStartWithSuccess success: Bool
|
|
117
|
+
) {
|
|
118
|
+
createView()
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
private func createView() {
|
|
122
|
+
guard let rootViewFactory: RCTRootViewFactory = appDelegate.reactNativeFactory?.rootViewFactory else {
|
|
123
|
+
fatalError("rootViewFactory has not been initialized")
|
|
124
|
+
}
|
|
125
|
+
let rootView = rootViewFactory.view(
|
|
126
|
+
withModuleName: appDelegate.moduleName,
|
|
127
|
+
initialProperties: appDelegate.initialProps,
|
|
128
|
+
launchOptions: appDelegate.launchOptions
|
|
129
|
+
)
|
|
130
|
+
let controller = self
|
|
131
|
+
controller.view.clipsToBounds = true
|
|
132
|
+
controller.view.addSubview(rootView)
|
|
133
|
+
rootView.translatesAutoresizingMaskIntoConstraints = false
|
|
134
|
+
NSLayoutConstraint.activate([
|
|
135
|
+
rootView.topAnchor.constraint(equalTo: controller.view.safeAreaLayoutGuide.topAnchor),
|
|
136
|
+
rootView.bottomAnchor.constraint(equalTo: controller.view.safeAreaLayoutGuide.bottomAnchor),
|
|
137
|
+
rootView.leadingAnchor.constraint(equalTo: controller.view.safeAreaLayoutGuide.leadingAnchor),
|
|
138
|
+
rootView.trailingAnchor.constraint(equalTo: controller.view.safeAreaLayoutGuide.trailingAnchor)
|
|
139
|
+
])
|
|
140
|
+
}
|
|
141
|
+
}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
package dev.expo.updatese2e
|
|
2
|
+
|
|
3
|
+
import android.content.Context
|
|
4
|
+
import android.os.Bundle
|
|
5
|
+
import com.facebook.react.ReactActivity
|
|
6
|
+
import com.facebook.react.ReactActivityDelegate
|
|
7
|
+
import com.facebook.react.defaults.DefaultNewArchitectureEntryPoint.fabricEnabled
|
|
8
|
+
import com.facebook.react.defaults.DefaultReactActivityDelegate
|
|
9
|
+
import expo.modules.ReactActivityDelegateWrapper
|
|
10
|
+
import expo.modules.updates.UpdatesController
|
|
11
|
+
import kotlinx.coroutines.CoroutineScope
|
|
12
|
+
import kotlinx.coroutines.Dispatchers
|
|
13
|
+
import kotlinx.coroutines.launch
|
|
14
|
+
|
|
15
|
+
class MainActivity : ReactActivity() {
|
|
16
|
+
override fun onCreate(savedInstanceState: Bundle?) {
|
|
17
|
+
super.onCreate(savedInstanceState)
|
|
18
|
+
CoroutineScope(Dispatchers.IO).launch {
|
|
19
|
+
startUpdatesController(applicationContext)
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
private fun startUpdatesController(context: Context) {
|
|
24
|
+
UpdatesController.initialize(context)
|
|
25
|
+
// Call the synchronous `launchAssetFile()` function to wait for updates ready
|
|
26
|
+
UpdatesController.instance.launchAssetFile
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Returns the name of the main component registered from JavaScript. This is used to schedule
|
|
31
|
+
* rendering of the component.
|
|
32
|
+
*/
|
|
33
|
+
override fun getMainComponentName(): String = "main"
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Returns the instance of the [ReactActivityDelegate]. We use [DefaultReactActivityDelegate]
|
|
37
|
+
* which allows you to enable New Architecture with a single boolean flags [fabricEnabled]
|
|
38
|
+
*/
|
|
39
|
+
override fun createReactActivityDelegate(): ReactActivityDelegate {
|
|
40
|
+
return ReactActivityDelegateWrapper(
|
|
41
|
+
this,
|
|
42
|
+
BuildConfig.IS_NEW_ARCHITECTURE_ENABLED,
|
|
43
|
+
object : DefaultReactActivityDelegate(
|
|
44
|
+
this,
|
|
45
|
+
mainComponentName,
|
|
46
|
+
fabricEnabled
|
|
47
|
+
) {})
|
|
48
|
+
}
|
|
49
|
+
}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
package dev.expo.updatese2e
|
|
2
|
+
|
|
3
|
+
import android.app.Application
|
|
4
|
+
import com.facebook.react.PackageList
|
|
5
|
+
import com.facebook.react.ReactApplication
|
|
6
|
+
import com.facebook.react.ReactHost
|
|
7
|
+
import com.facebook.react.ReactNativeHost
|
|
8
|
+
import com.facebook.react.ReactPackage
|
|
9
|
+
import com.facebook.react.defaults.DefaultNewArchitectureEntryPoint.load
|
|
10
|
+
import com.facebook.react.defaults.DefaultReactNativeHost
|
|
11
|
+
import com.facebook.react.soloader.OpenSourceMergedSoMapping
|
|
12
|
+
import com.facebook.soloader.SoLoader
|
|
13
|
+
import expo.modules.ReactNativeHostWrapper
|
|
14
|
+
import expo.modules.updates.UpdatesPackage
|
|
15
|
+
|
|
16
|
+
class MainApplication : Application(), ReactApplication {
|
|
17
|
+
|
|
18
|
+
override val reactNativeHost: ReactNativeHost = ReactNativeHostWrapper(
|
|
19
|
+
this,
|
|
20
|
+
object : DefaultReactNativeHost(this) {
|
|
21
|
+
override fun getPackages(): List<ReactPackage> {
|
|
22
|
+
val packages = PackageList(this).packages
|
|
23
|
+
// Packages that cannot be autolinked yet can be added manually here, for example:
|
|
24
|
+
// packages.add(new MyReactNativePackage());
|
|
25
|
+
return packages
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
override fun getJSMainModuleName(): String = ".expo/.virtual-metro-entry"
|
|
29
|
+
|
|
30
|
+
override fun getUseDeveloperSupport(): Boolean = BuildConfig.DEBUG && !UpdatesPackage.isUsingNativeDebug
|
|
31
|
+
|
|
32
|
+
override val isNewArchEnabled: Boolean = BuildConfig.IS_NEW_ARCHITECTURE_ENABLED
|
|
33
|
+
override val isHermesEnabled: Boolean = BuildConfig.IS_HERMES_ENABLED
|
|
34
|
+
}
|
|
35
|
+
)
|
|
36
|
+
|
|
37
|
+
override val reactHost: ReactHost
|
|
38
|
+
get() = ReactNativeHostWrapper.createReactHost(applicationContext, reactNativeHost)
|
|
39
|
+
|
|
40
|
+
override fun onCreate() {
|
|
41
|
+
super.onCreate()
|
|
42
|
+
SoLoader.init(this, OpenSourceMergedSoMapping)
|
|
43
|
+
if (BuildConfig.IS_NEW_ARCHITECTURE_ENABLED) {
|
|
44
|
+
// If you opted-in for the New Architecture, we load the native entry point for this app.
|
|
45
|
+
load()
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
}
|
|
@@ -6,7 +6,8 @@
|
|
|
6
6
|
"base": {
|
|
7
7
|
"node": "20.14.0",
|
|
8
8
|
"android": {
|
|
9
|
-
"image": "
|
|
9
|
+
"image": "ubuntu-22.04-jdk-17-ndk-r21e",
|
|
10
|
+
"resourceClass": "large"
|
|
10
11
|
},
|
|
11
12
|
"ios": {
|
|
12
13
|
"image": "latest"
|
|
@@ -24,9 +25,6 @@
|
|
|
24
25
|
},
|
|
25
26
|
"updates_testing_debug": {
|
|
26
27
|
"extends": "base",
|
|
27
|
-
"env": {
|
|
28
|
-
"EX_UPDATES_NATIVE_DEBUG": "1"
|
|
29
|
-
},
|
|
30
28
|
"android": {
|
|
31
29
|
"applicationArchivePath": "eas.json",
|
|
32
30
|
"gradleCommand": ":app:assembleDebug :app:assembleAndroidTest -DtestBuildType=debug",
|
|
@@ -42,9 +40,6 @@
|
|
|
42
40
|
},
|
|
43
41
|
"updates_testing_release": {
|
|
44
42
|
"extends": "base",
|
|
45
|
-
"env": {
|
|
46
|
-
"EX_UPDATES_NATIVE_DEBUG": "1"
|
|
47
|
-
},
|
|
48
43
|
"android": {
|
|
49
44
|
"gradleCommand": ":app:assembleRelease :app:assembleAndroidTest -DtestBuildType=release",
|
|
50
45
|
"withoutCredentials": true
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
#!/usr/bin/env yarn --silent ts-node --transpile-only
|
|
2
|
+
|
|
3
|
+
import nullthrows from 'nullthrows';
|
|
4
|
+
import path from 'path';
|
|
5
|
+
|
|
6
|
+
import { initAsync, setupE2EAppAsync, transformAppJsonForE2EWithCustomInit } from './project';
|
|
7
|
+
|
|
8
|
+
const repoRoot = nullthrows(process.env.EXPO_REPO_ROOT, 'EXPO_REPO_ROOT is not defined');
|
|
9
|
+
const workingDir = path.resolve(repoRoot, '..');
|
|
10
|
+
const runtimeVersion = '1.0.0';
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
*
|
|
14
|
+
* This generates a project at the location TEST_PROJECT_ROOT,
|
|
15
|
+
* that is configured to build a test app and run both suites
|
|
16
|
+
* of updates E2E tests in the Detox environment.
|
|
17
|
+
*
|
|
18
|
+
* This test project will use the custom init flow for updates, using
|
|
19
|
+
* the expo-template-custom-init native files.
|
|
20
|
+
*
|
|
21
|
+
* See `packages/expo-updates/e2e/README.md` for instructions on how
|
|
22
|
+
* to run these tests locally.
|
|
23
|
+
*
|
|
24
|
+
*/
|
|
25
|
+
|
|
26
|
+
(async function () {
|
|
27
|
+
if (!process.env.EXPO_REPO_ROOT || !process.env.UPDATES_HOST || !process.env.UPDATES_PORT) {
|
|
28
|
+
throw new Error('Missing one or more environment variables; see instructions in e2e/README.md');
|
|
29
|
+
}
|
|
30
|
+
const projectRoot = process.env.TEST_PROJECT_ROOT || path.join(workingDir, 'updates-e2e');
|
|
31
|
+
const localCliBin = path.join(repoRoot, 'packages/@expo/cli/build/bin/cli');
|
|
32
|
+
|
|
33
|
+
await initAsync(projectRoot, {
|
|
34
|
+
repoRoot,
|
|
35
|
+
runtimeVersion,
|
|
36
|
+
localCliBin,
|
|
37
|
+
useCustomInit: true,
|
|
38
|
+
transformAppJson: transformAppJsonForE2EWithCustomInit,
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
await setupE2EAppAsync(projectRoot, { localCliBin, repoRoot });
|
|
42
|
+
})();
|
package/e2e/setup/project.ts
CHANGED
|
@@ -17,9 +17,11 @@ const dirName = __dirname; /* eslint-disable-line */
|
|
|
17
17
|
function getExpoDependencyChunks({
|
|
18
18
|
includeDevClient,
|
|
19
19
|
includeTV,
|
|
20
|
+
includeSplashScreen,
|
|
20
21
|
}: {
|
|
21
22
|
includeDevClient: boolean;
|
|
22
23
|
includeTV: boolean;
|
|
24
|
+
includeSplashScreen: boolean;
|
|
23
25
|
}) {
|
|
24
26
|
return [
|
|
25
27
|
['@expo/config-types', '@expo/env', '@expo/json-file'],
|
|
@@ -39,12 +41,12 @@ function getExpoDependencyChunks({
|
|
|
39
41
|
'expo-font',
|
|
40
42
|
'expo-json-utils',
|
|
41
43
|
'expo-keep-awake',
|
|
42
|
-
'expo-splash-screen',
|
|
43
44
|
'expo-status-bar',
|
|
44
45
|
'expo-structured-headers',
|
|
45
46
|
'expo-updates',
|
|
46
47
|
'expo-updates-interface',
|
|
47
48
|
],
|
|
49
|
+
...(includeSplashScreen ? [['expo-splash-screen']] : []),
|
|
48
50
|
...(includeDevClient
|
|
49
51
|
? [['expo-dev-menu-interface'], ['expo-dev-menu'], ['expo-dev-launcher'], ['expo-dev-client']]
|
|
50
52
|
: []),
|
|
@@ -252,13 +254,18 @@ async function preparePackageJson(
|
|
|
252
254
|
configureE2E: boolean,
|
|
253
255
|
isTV: boolean,
|
|
254
256
|
shouldGenerateTestUpdateBundles: boolean,
|
|
255
|
-
includeDevClient: boolean
|
|
257
|
+
includeDevClient: boolean,
|
|
258
|
+
useCustomInit: boolean
|
|
256
259
|
) {
|
|
257
260
|
// Create the project subfolder to hold NPM tarballs built from the current state of the repo
|
|
258
261
|
const dependenciesPath = path.join(projectRoot, 'dependencies');
|
|
259
262
|
await fs.mkdir(dependenciesPath);
|
|
260
263
|
|
|
261
|
-
const allDependencyChunks = getExpoDependencyChunks({
|
|
264
|
+
const allDependencyChunks = getExpoDependencyChunks({
|
|
265
|
+
includeDevClient,
|
|
266
|
+
includeTV: isTV,
|
|
267
|
+
includeSplashScreen: !useCustomInit,
|
|
268
|
+
});
|
|
262
269
|
|
|
263
270
|
console.time('Done packing dependencies');
|
|
264
271
|
for (const dependencyChunk of allDependencyChunks) {
|
|
@@ -368,7 +375,7 @@ async function preparePackageJson(
|
|
|
368
375
|
...packageJson,
|
|
369
376
|
dependencies: {
|
|
370
377
|
...packageJson.dependencies,
|
|
371
|
-
'react-native': 'npm:react-native-tvos
|
|
378
|
+
'react-native': 'npm:react-native-tvos@0.79.0-0rc2',
|
|
372
379
|
'@react-native-tvos/config-tv': '^0.1.1',
|
|
373
380
|
},
|
|
374
381
|
expo: {
|
|
@@ -495,6 +502,7 @@ function transformAppJsonForE2E(
|
|
|
495
502
|
...appJson.expo.updates,
|
|
496
503
|
url: `http://${process.env.UPDATES_HOST}:${process.env.UPDATES_PORT}/update`,
|
|
497
504
|
assetPatternsToBeBundled: ['includedAssets/*'],
|
|
505
|
+
useNativeDebug: true,
|
|
498
506
|
},
|
|
499
507
|
extra: {
|
|
500
508
|
eas: {
|
|
@@ -505,6 +513,22 @@ function transformAppJsonForE2E(
|
|
|
505
513
|
};
|
|
506
514
|
}
|
|
507
515
|
|
|
516
|
+
export function transformAppJsonForE2EWithCustomInit(
|
|
517
|
+
appJson: any,
|
|
518
|
+
projectName: string,
|
|
519
|
+
runtimeVersion: string,
|
|
520
|
+
isTV: boolean
|
|
521
|
+
) {
|
|
522
|
+
const transformedForE2E = transformAppJsonForE2E(appJson, projectName, runtimeVersion, isTV);
|
|
523
|
+
return {
|
|
524
|
+
...transformedForE2E,
|
|
525
|
+
expo: {
|
|
526
|
+
...transformedForE2E.expo,
|
|
527
|
+
newArchEnabled: true,
|
|
528
|
+
},
|
|
529
|
+
};
|
|
530
|
+
}
|
|
531
|
+
|
|
508
532
|
/**
|
|
509
533
|
* Modifies app.json in the E2E test app to add the properties we need, and sets the runtime version policy to fingerprint
|
|
510
534
|
*/
|
|
@@ -605,6 +629,10 @@ export function transformAppJsonForUpdatesDisabledE2E(
|
|
|
605
629
|
newArchEnabled: false,
|
|
606
630
|
android: { ...appJson.expo.android, package: 'dev.expo.updatese2e' },
|
|
607
631
|
ios: { ...appJson.expo.ios, bundleIdentifier: 'dev.expo.updatese2e' },
|
|
632
|
+
updates: {
|
|
633
|
+
enabled: false,
|
|
634
|
+
useNativeDebug: true,
|
|
635
|
+
},
|
|
608
636
|
extra: {
|
|
609
637
|
eas: {
|
|
610
638
|
projectId: '55685a57-9cf3-442d-9ba8-65c7b39849ef',
|
|
@@ -664,6 +692,7 @@ export async function initAsync(
|
|
|
664
692
|
shouldGenerateTestUpdateBundles = true,
|
|
665
693
|
shouldConfigureCodeSigning = true,
|
|
666
694
|
includeDevClient = false,
|
|
695
|
+
useCustomInit = false,
|
|
667
696
|
}: {
|
|
668
697
|
repoRoot: string;
|
|
669
698
|
runtimeVersion: string;
|
|
@@ -679,6 +708,7 @@ export async function initAsync(
|
|
|
679
708
|
shouldGenerateTestUpdateBundles?: boolean;
|
|
680
709
|
shouldConfigureCodeSigning?: boolean;
|
|
681
710
|
includeDevClient?: boolean;
|
|
711
|
+
useCustomInit?: boolean;
|
|
682
712
|
}
|
|
683
713
|
) {
|
|
684
714
|
console.log('Creating expo app');
|
|
@@ -730,7 +760,8 @@ export async function initAsync(
|
|
|
730
760
|
configureE2E,
|
|
731
761
|
isTV,
|
|
732
762
|
shouldGenerateTestUpdateBundles,
|
|
733
|
-
includeDevClient
|
|
763
|
+
includeDevClient,
|
|
764
|
+
useCustomInit
|
|
734
765
|
);
|
|
735
766
|
|
|
736
767
|
// configure app.json
|
|
@@ -760,7 +791,6 @@ export async function initAsync(
|
|
|
760
791
|
await spawnAsync(localCliBin, ['prebuild', '--no-install', '--template', localTemplatePathName], {
|
|
761
792
|
env: {
|
|
762
793
|
...process.env,
|
|
763
|
-
EX_UPDATES_NATIVE_DEBUG: '1',
|
|
764
794
|
EXPO_DEBUG: '1',
|
|
765
795
|
CI: '1',
|
|
766
796
|
},
|
|
@@ -784,10 +814,10 @@ export async function initAsync(
|
|
|
784
814
|
stdio: 'inherit',
|
|
785
815
|
});
|
|
786
816
|
|
|
787
|
-
// enable proguard on Android
|
|
817
|
+
// enable proguard on Android, and custom init if needed
|
|
788
818
|
await fs.appendFile(
|
|
789
819
|
path.join(projectRoot, 'android', 'gradle.properties'),
|
|
790
|
-
|
|
820
|
+
`\nandroid.enableProguardInReleaseBuilds=true${useCustomInit ? '\nEX_UPDATES_CUSTOM_INIT=true' : ''}`,
|
|
791
821
|
'utf-8'
|
|
792
822
|
);
|
|
793
823
|
|
|
@@ -807,6 +837,71 @@ export async function initAsync(
|
|
|
807
837
|
].join('\n'),
|
|
808
838
|
'utf-8'
|
|
809
839
|
);
|
|
840
|
+
|
|
841
|
+
// Add custom init to iOS Podfile.properties.json if needed
|
|
842
|
+
if (useCustomInit) {
|
|
843
|
+
const podfilePropertiesJsonPath = path.join(projectRoot, 'ios', 'Podfile.properties.json');
|
|
844
|
+
const podfilePropertiesJsonString = await fs.readFile(podfilePropertiesJsonPath, {
|
|
845
|
+
encoding: 'utf-8',
|
|
846
|
+
});
|
|
847
|
+
const podfilePropertiesJson: any = JSON.parse(podfilePropertiesJsonString);
|
|
848
|
+
podfilePropertiesJson.updatesCustomInit = 'true';
|
|
849
|
+
await fs.writeFile(podfilePropertiesJsonPath, JSON.stringify(podfilePropertiesJson, null, 2), {
|
|
850
|
+
encoding: 'utf-8',
|
|
851
|
+
});
|
|
852
|
+
}
|
|
853
|
+
|
|
854
|
+
const customInitSourcesDirectory = path.join(
|
|
855
|
+
repoRoot,
|
|
856
|
+
'packages',
|
|
857
|
+
'expo-updates',
|
|
858
|
+
'e2e',
|
|
859
|
+
'fixtures',
|
|
860
|
+
'custom_init'
|
|
861
|
+
);
|
|
862
|
+
// If custom init, copy native source files
|
|
863
|
+
if (useCustomInit) {
|
|
864
|
+
const filesToCopyForCustomInit = [
|
|
865
|
+
{
|
|
866
|
+
sourcePath: path.join(customInitSourcesDirectory, 'AppDelegate.swift'),
|
|
867
|
+
destPath: path.join(projectRoot, 'ios', 'updatese2e', 'AppDelegate.swift'),
|
|
868
|
+
},
|
|
869
|
+
{
|
|
870
|
+
sourcePath: path.join(customInitSourcesDirectory, 'MainApplication.kt'),
|
|
871
|
+
destPath: path.join(
|
|
872
|
+
projectRoot,
|
|
873
|
+
'android',
|
|
874
|
+
'app',
|
|
875
|
+
'src',
|
|
876
|
+
'main',
|
|
877
|
+
'java',
|
|
878
|
+
'dev',
|
|
879
|
+
'expo',
|
|
880
|
+
'updatese2e',
|
|
881
|
+
'MainApplication.kt'
|
|
882
|
+
),
|
|
883
|
+
},
|
|
884
|
+
{
|
|
885
|
+
sourcePath: path.join(customInitSourcesDirectory, 'MainActivity.kt'),
|
|
886
|
+
destPath: path.join(
|
|
887
|
+
projectRoot,
|
|
888
|
+
'android',
|
|
889
|
+
'app',
|
|
890
|
+
'src',
|
|
891
|
+
'main',
|
|
892
|
+
'java',
|
|
893
|
+
'dev',
|
|
894
|
+
'expo',
|
|
895
|
+
'updatese2e',
|
|
896
|
+
'MainActivity.kt'
|
|
897
|
+
),
|
|
898
|
+
},
|
|
899
|
+
];
|
|
900
|
+
for (const fileToCopy of filesToCopyForCustomInit) {
|
|
901
|
+
await fs.copyFile(fileToCopy.sourcePath, fileToCopy.destPath);
|
|
902
|
+
}
|
|
903
|
+
}
|
|
904
|
+
|
|
810
905
|
await fs.appendFile(
|
|
811
906
|
path.join(projectRoot, 'android', 'app', 'build.gradle'),
|
|
812
907
|
[
|
package/expo-updates-gradle-plugin/src/main/kotlin/expo/modules/updates/ExpoUpdatesPlugin.kt
CHANGED
|
@@ -16,6 +16,7 @@ import org.slf4j.LoggerFactory
|
|
|
16
16
|
import java.io.ByteArrayOutputStream
|
|
17
17
|
import java.io.File
|
|
18
18
|
import java.util.Locale
|
|
19
|
+
import java.util.Properties
|
|
19
20
|
|
|
20
21
|
abstract class ExpoUpdatesPlugin : Plugin<Project> {
|
|
21
22
|
override fun apply(project: Project) {
|
|
@@ -26,7 +27,7 @@ abstract class ExpoUpdatesPlugin : Plugin<Project> {
|
|
|
26
27
|
val entryFile = detectedEntryFile(reactExtension)
|
|
27
28
|
val androidComponents = project.extensions.getByType(AndroidComponentsExtension::class.java)
|
|
28
29
|
|
|
29
|
-
if (
|
|
30
|
+
if (isNativeDebuggingEnabled(project)) {
|
|
30
31
|
logger.warn("Disable all react.debuggableVariants because EX_UPDATES_NATIVE_DEBUG=1")
|
|
31
32
|
reactExtension.debuggableVariants.set(listOf())
|
|
32
33
|
}
|
|
@@ -126,3 +127,10 @@ private fun detectedEntryFile(config: ReactExtension): File {
|
|
|
126
127
|
else -> File(reactRoot, "index.js")
|
|
127
128
|
}
|
|
128
129
|
}
|
|
130
|
+
|
|
131
|
+
private fun isNativeDebuggingEnabled(project: Project): Boolean {
|
|
132
|
+
if (System.getenv("EX_UPDATES_NATIVE_DEBUG") == "1") {
|
|
133
|
+
return true
|
|
134
|
+
}
|
|
135
|
+
return project.findProperty("EX_UPDATES_NATIVE_DEBUG") == "true"
|
|
136
|
+
}
|
|
@@ -22,6 +22,10 @@ public final class ExpoUpdatesReactDelegateHandler: ExpoReactDelegateHandler, Ap
|
|
|
22
22
|
initialProperties: [AnyHashable: Any]?,
|
|
23
23
|
launchOptions: [UIApplication.LaunchOptionsKey: Any]?
|
|
24
24
|
) -> UIView? {
|
|
25
|
+
if UpdatesUtils.isUsingCustomInitialization() {
|
|
26
|
+
return nil
|
|
27
|
+
}
|
|
28
|
+
|
|
25
29
|
AppController.initializeWithoutStarting()
|
|
26
30
|
let controller = AppController.sharedInstance
|
|
27
31
|
if !controller.isActiveController {
|
|
@@ -59,12 +63,15 @@ public final class ExpoUpdatesReactDelegateHandler: ExpoReactDelegateHandler, Ap
|
|
|
59
63
|
// MARK: AppControllerDelegate implementations
|
|
60
64
|
|
|
61
65
|
public func appController(_ appController: AppControllerInterface, didStartWithSuccess success: Bool) {
|
|
66
|
+
if UpdatesUtils.isUsingCustomInitialization() {
|
|
67
|
+
return
|
|
68
|
+
}
|
|
62
69
|
guard let reactDelegate = self.reactDelegate else {
|
|
63
70
|
fatalError("`reactDelegate` should not be nil")
|
|
64
71
|
}
|
|
65
72
|
|
|
66
|
-
guard let appDelegate = (UIApplication.shared.delegate as? ReactNativeFactoryProvider) ??
|
|
67
|
-
((UIApplication.shared.delegate as? NSObject)?.value(forKey: "_expoAppDelegate") as? ReactNativeFactoryProvider) else {
|
|
73
|
+
guard let appDelegate = (UIApplication.shared.delegate as? (any ReactNativeFactoryProvider)) ??
|
|
74
|
+
((UIApplication.shared.delegate as? NSObject)?.value(forKey: "_expoAppDelegate") as? (any ReactNativeFactoryProvider)) else {
|
|
68
75
|
fatalError("`UIApplication.shared.delegate` must be an `ExpoAppDelegate` or `EXAppDelegateWrapper`")
|
|
69
76
|
}
|
|
70
77
|
|
|
@@ -133,7 +133,7 @@ public final class UpdatesUtils: NSObject {
|
|
|
133
133
|
}
|
|
134
134
|
}
|
|
135
135
|
|
|
136
|
-
|
|
136
|
+
public static func isNativeDebuggingEnabled() -> Bool {
|
|
137
137
|
#if EX_UPDATES_NATIVE_DEBUG
|
|
138
138
|
return true
|
|
139
139
|
#else
|
|
@@ -141,6 +141,14 @@ public final class UpdatesUtils: NSObject {
|
|
|
141
141
|
#endif
|
|
142
142
|
}
|
|
143
143
|
|
|
144
|
+
internal static func isUsingCustomInitialization() -> Bool {
|
|
145
|
+
#if EX_UPDATES_CUSTOM_INIT
|
|
146
|
+
return true
|
|
147
|
+
#else
|
|
148
|
+
return false
|
|
149
|
+
#endif
|
|
150
|
+
}
|
|
151
|
+
|
|
144
152
|
internal static func runBlockOnMainThread(_ block: @escaping () -> Void) {
|
|
145
153
|
if Thread.isMainThread {
|
|
146
154
|
block()
|
package/ios/EXUpdates.podspec
CHANGED
|
@@ -3,9 +3,19 @@ require 'json'
|
|
|
3
3
|
package = JSON.parse(File.read(File.join(__dir__, '..', 'package.json')))
|
|
4
4
|
podfile_properties = JSON.parse(File.read("#{Pod::Config.instance.installation_root}/Podfile.properties.json")) rescue {}
|
|
5
5
|
|
|
6
|
+
if ENV['EX_UPDATES_NATIVE_DEBUG'] != '1'
|
|
7
|
+
ENV['EX_UPDATES_NATIVE_DEBUG'] = podfile_properties['updatesNativeDebug'] == 'true' ? '1' : '0'
|
|
8
|
+
end
|
|
9
|
+
if ENV['EX_UPDATES_CUSTOM_INIT'] != '1'
|
|
10
|
+
ENV['EX_UPDATES_CUSTOM_INIT'] = podfile_properties['updatesCustomInit'] == 'true' ? '1' : '0'
|
|
11
|
+
end
|
|
12
|
+
|
|
6
13
|
use_dev_client = false
|
|
7
14
|
begin
|
|
8
|
-
|
|
15
|
+
# No dev client if we are using native debug
|
|
16
|
+
if ENV['EX_UPDATES_NATIVE_DEBUG'] != '1'
|
|
17
|
+
use_dev_client = `node --print "require('expo-dev-client/package.json').version" 2>/dev/null`.length > 0
|
|
18
|
+
end
|
|
9
19
|
rescue
|
|
10
20
|
use_dev_client = false
|
|
11
21
|
end
|
|
@@ -43,17 +53,26 @@ Pod::Spec.new do |s|
|
|
|
43
53
|
end
|
|
44
54
|
install_modules_dependencies(s)
|
|
45
55
|
|
|
46
|
-
|
|
47
|
-
|
|
56
|
+
other_debug_c_flags = '$(inherited)'
|
|
57
|
+
other_debug_swift_flags = '$(inherited)'
|
|
58
|
+
other_release_c_flags = '$(inherited)'
|
|
59
|
+
other_release_swift_flags = '$(inherited)'
|
|
48
60
|
|
|
49
61
|
ex_updates_native_debug = ENV['EX_UPDATES_NATIVE_DEBUG'] == '1'
|
|
62
|
+
ex_updates_custom_init = ENV['EX_UPDATES_CUSTOM_INIT'] == '1'
|
|
50
63
|
if ex_updates_native_debug
|
|
51
|
-
|
|
52
|
-
|
|
64
|
+
other_debug_c_flags << ' -DEX_UPDATES_NATIVE_DEBUG=1'
|
|
65
|
+
other_debug_swift_flags << ' -DEX_UPDATES_NATIVE_DEBUG'
|
|
66
|
+
end
|
|
67
|
+
if ex_updates_custom_init
|
|
68
|
+
other_debug_c_flags << ' -DEX_UPDATES_CUSTOM_INIT=1'
|
|
69
|
+
other_debug_swift_flags << ' -DEX_UPDATES_CUSTOM_INIT'
|
|
70
|
+
other_release_c_flags << ' -DEX_UPDATES_CUSTOM_INIT=1'
|
|
71
|
+
other_release_swift_flags << ' -DEX_UPDATES_CUSTOM_INIT'
|
|
53
72
|
end
|
|
54
73
|
if use_dev_client
|
|
55
|
-
|
|
56
|
-
|
|
74
|
+
other_debug_c_flags << ' -DUSE_DEV_CLIENT=1'
|
|
75
|
+
other_debug_swift_flags << ' -DUSE_DEV_CLIENT'
|
|
57
76
|
end
|
|
58
77
|
|
|
59
78
|
s.pod_target_xcconfig = {
|
|
@@ -61,8 +80,10 @@ Pod::Spec.new do |s|
|
|
|
61
80
|
'GCC_TREAT_IMPLICIT_FUNCTION_DECLARATIONS_AS_ERRORS' => 'YES',
|
|
62
81
|
'DEFINES_MODULE' => 'YES',
|
|
63
82
|
'SWIFT_COMPILATION_MODE' => 'wholemodule',
|
|
64
|
-
'OTHER_CFLAGS[config=*Debug*]' =>
|
|
65
|
-
'OTHER_SWIFT_FLAGS[config=*Debug*]' =>
|
|
83
|
+
'OTHER_CFLAGS[config=*Debug*]' => other_debug_c_flags,
|
|
84
|
+
'OTHER_SWIFT_FLAGS[config=*Debug*]' => other_debug_swift_flags,
|
|
85
|
+
'OTHER_CFLAGS[config=*Release*]' => other_release_c_flags,
|
|
86
|
+
'OTHER_SWIFT_FLAGS[config=*Release*]' => other_release_swift_flags
|
|
66
87
|
}
|
|
67
88
|
s.user_target_xcconfig = {
|
|
68
89
|
'HEADER_SEARCH_PATHS' => '"${PODS_CONFIGURATION_BUILD_DIR}/EXUpdates/Swift Compatibility Header"',
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "expo-updates",
|
|
3
|
-
"version": "1.0.0-canary-
|
|
3
|
+
"version": "1.0.0-canary-20250331-817737a",
|
|
4
4
|
"description": "Fetches and manages remotely-hosted assets and updates to your app's JS bundle.",
|
|
5
5
|
"main": "build/index.js",
|
|
6
6
|
"types": "build/index.d.ts",
|
|
@@ -39,15 +39,15 @@
|
|
|
39
39
|
},
|
|
40
40
|
"dependencies": {
|
|
41
41
|
"@expo/code-signing-certificates": "0.0.5",
|
|
42
|
-
"@expo/config": "11.0.0-canary-
|
|
43
|
-
"@expo/config-plugins": "9.1.0-canary-
|
|
42
|
+
"@expo/config": "11.0.0-canary-20250331-817737a",
|
|
43
|
+
"@expo/config-plugins": "9.1.0-canary-20250331-817737a",
|
|
44
44
|
"@expo/spawn-async": "^1.7.2",
|
|
45
45
|
"arg": "4.1.0",
|
|
46
46
|
"chalk": "^4.1.2",
|
|
47
|
-
"expo-eas-client": "0.13.4-canary-
|
|
48
|
-
"expo-manifests": "0.15.8-canary-
|
|
49
|
-
"expo-structured-headers": "4.0.1-canary-
|
|
50
|
-
"expo-updates-interface": "1.0.1-canary-
|
|
47
|
+
"expo-eas-client": "0.13.4-canary-20250331-817737a",
|
|
48
|
+
"expo-manifests": "0.15.8-canary-20250331-817737a",
|
|
49
|
+
"expo-structured-headers": "4.0.1-canary-20250331-817737a",
|
|
50
|
+
"expo-updates-interface": "1.0.1-canary-20250331-817737a",
|
|
51
51
|
"glob": "^10.4.2",
|
|
52
52
|
"ignore": "^5.3.1",
|
|
53
53
|
"resolve-from": "^5.0.0"
|
|
@@ -56,15 +56,14 @@
|
|
|
56
56
|
"@types/jest": "^29.2.1",
|
|
57
57
|
"@types/node": "^18.19.34",
|
|
58
58
|
"@types/node-forge": "^1.0.0",
|
|
59
|
-
"expo-module-scripts": "4.0.5-canary-
|
|
59
|
+
"expo-module-scripts": "4.0.5-canary-20250331-817737a",
|
|
60
60
|
"express": "^4.21.1",
|
|
61
61
|
"form-data": "^4.0.0",
|
|
62
62
|
"memfs": "^3.2.0",
|
|
63
63
|
"xstate": "^4.37.2"
|
|
64
64
|
},
|
|
65
65
|
"peerDependencies": {
|
|
66
|
-
"expo": "53.0.0-canary-
|
|
66
|
+
"expo": "53.0.0-canary-20250331-817737a",
|
|
67
67
|
"react": "*"
|
|
68
|
-
}
|
|
69
|
-
"gitHead": "d9d3e024d8742099c307754673f17117a20c1dea"
|
|
68
|
+
}
|
|
70
69
|
}
|