@hot-updater/react-native 0.29.8 → 0.30.0
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/android/src/main/java/com/hotupdater/BundleFileStorageService.kt +52 -2
- package/android/src/main/java/com/hotupdater/CohortService.kt +4 -3
- package/android/src/main/java/com/hotupdater/HotUpdaterImpl.kt +24 -3
- package/android/src/test/java/com/hotupdater/BundleFileStorageServiceTest.kt +47 -0
- package/android/src/test/java/com/hotupdater/HotUpdaterImplTest.kt +208 -0
- package/package.json +6 -6
|
@@ -81,6 +81,12 @@ interface BundleStorageService {
|
|
|
81
81
|
*/
|
|
82
82
|
fun getBaseURL(): String
|
|
83
83
|
|
|
84
|
+
/**
|
|
85
|
+
* Gets the base URL for a specific launched bundle.
|
|
86
|
+
* Returns an empty string for the built-in bundle or when the bundle is unavailable.
|
|
87
|
+
*/
|
|
88
|
+
fun getBaseURLForBundle(bundleId: String?): String
|
|
89
|
+
|
|
84
90
|
/**
|
|
85
91
|
* Gets the current active bundle ID from bundle storage.
|
|
86
92
|
* Reads manifest.json first and falls back to older metadata when needed.
|
|
@@ -93,6 +99,12 @@ interface BundleStorageService {
|
|
|
93
99
|
*/
|
|
94
100
|
fun getManifest(): Map<String, Any?>
|
|
95
101
|
|
|
102
|
+
/**
|
|
103
|
+
* Gets the manifest for a specific launched bundle.
|
|
104
|
+
* Returns an empty map for the built-in bundle or when the bundle is unavailable.
|
|
105
|
+
*/
|
|
106
|
+
fun getManifestForBundle(bundleId: String?): Map<String, Any?>
|
|
107
|
+
|
|
96
108
|
/**
|
|
97
109
|
* Restores the original bundle and clears downloaded bundle state.
|
|
98
110
|
* @return true if the reset was successful
|
|
@@ -239,11 +251,13 @@ class BundleFileStorageService(
|
|
|
239
251
|
}
|
|
240
252
|
|
|
241
253
|
private fun getActiveBundleId(): String? {
|
|
254
|
+
extractBundleIdFromCurrentURL()?.let { return it }
|
|
255
|
+
|
|
242
256
|
val metadata = loadMetadataOrNull()
|
|
243
257
|
return when {
|
|
244
|
-
metadata?.stagingBundleId != null -> metadata.stagingBundleId
|
|
258
|
+
metadata?.stagingBundleId != null && !metadata.verificationPending -> metadata.stagingBundleId
|
|
245
259
|
metadata?.stableBundleId != null -> metadata.stableBundleId
|
|
246
|
-
else ->
|
|
260
|
+
else -> null
|
|
247
261
|
}
|
|
248
262
|
}
|
|
249
263
|
|
|
@@ -295,6 +309,19 @@ class BundleFileStorageService(
|
|
|
295
309
|
)
|
|
296
310
|
}
|
|
297
311
|
|
|
312
|
+
private fun getBundleMetadataSnapshot(bundleId: String?): ActiveBundleMetadataSnapshot? {
|
|
313
|
+
if (bundleId.isNullOrBlank()) {
|
|
314
|
+
return null
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
val bundleDir = File(getBundleStoreDir(), bundleId)
|
|
318
|
+
if (!bundleDir.exists()) {
|
|
319
|
+
return null
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
return resolveActiveBundleMetadataSnapshot(bundleDir)
|
|
323
|
+
}
|
|
324
|
+
|
|
298
325
|
private fun readCompatibilityBundleIdFromBundleDir(bundleDir: File): String? {
|
|
299
326
|
val compatibilityBundleIdFile = File(bundleDir, compatibilityBundleIdFilename())
|
|
300
327
|
if (!compatibilityBundleIdFile.exists()) {
|
|
@@ -967,6 +994,21 @@ class BundleFileStorageService(
|
|
|
967
994
|
}
|
|
968
995
|
}
|
|
969
996
|
|
|
997
|
+
override fun getBaseURLForBundle(bundleId: String?): String {
|
|
998
|
+
return try {
|
|
999
|
+
val activeBundleId = bundleId?.takeIf { it.isNotBlank() } ?: return ""
|
|
1000
|
+
val bundleDir = File(getBundleStoreDir(), activeBundleId)
|
|
1001
|
+
if (!bundleDir.exists()) {
|
|
1002
|
+
return ""
|
|
1003
|
+
}
|
|
1004
|
+
|
|
1005
|
+
"file://${bundleDir.absolutePath}"
|
|
1006
|
+
} catch (e: Exception) {
|
|
1007
|
+
Log.e(TAG, "Error getting base URL for bundle $bundleId: ${e.message}")
|
|
1008
|
+
""
|
|
1009
|
+
}
|
|
1010
|
+
}
|
|
1011
|
+
|
|
970
1012
|
override fun getBundleId(): String? =
|
|
971
1013
|
try {
|
|
972
1014
|
getActiveBundleMetadataSnapshot()?.bundleId
|
|
@@ -983,6 +1025,14 @@ class BundleFileStorageService(
|
|
|
983
1025
|
emptyMap()
|
|
984
1026
|
}
|
|
985
1027
|
|
|
1028
|
+
override fun getManifestForBundle(bundleId: String?): Map<String, Any?> =
|
|
1029
|
+
try {
|
|
1030
|
+
getBundleMetadataSnapshot(bundleId)?.manifest ?: emptyMap()
|
|
1031
|
+
} catch (e: Exception) {
|
|
1032
|
+
Log.e(TAG, "Error getting manifest for bundle $bundleId: ${e.message}")
|
|
1033
|
+
emptyMap()
|
|
1034
|
+
}
|
|
1035
|
+
|
|
986
1036
|
override suspend fun resetChannel(): Boolean =
|
|
987
1037
|
withContext(Dispatchers.IO) {
|
|
988
1038
|
if (!setBundleURL(null)) {
|
|
@@ -38,7 +38,7 @@ class CohortService(
|
|
|
38
38
|
}
|
|
39
39
|
|
|
40
40
|
val generated = UUID.randomUUID().toString()
|
|
41
|
-
prefs.edit().putString(FALLBACK_IDENTIFIER_KEY, generated).
|
|
41
|
+
prefs.edit().putString(FALLBACK_IDENTIFIER_KEY, generated).commit()
|
|
42
42
|
return generated
|
|
43
43
|
}
|
|
44
44
|
|
|
@@ -46,7 +46,8 @@ class CohortService(
|
|
|
46
46
|
if (cohort.isEmpty()) {
|
|
47
47
|
return
|
|
48
48
|
}
|
|
49
|
-
|
|
49
|
+
// Cohort changes can be followed immediately by a process restart during OTA flows.
|
|
50
|
+
prefs.edit().putString(COHORT_KEY, cohort).commit()
|
|
50
51
|
}
|
|
51
52
|
|
|
52
53
|
fun getCohort(): String {
|
|
@@ -67,7 +68,7 @@ class CohortService(
|
|
|
67
68
|
defaultNumericCohort(fallbackIdentifier())
|
|
68
69
|
}
|
|
69
70
|
|
|
70
|
-
prefs.edit().putString(COHORT_KEY, initialCohort).
|
|
71
|
+
prefs.edit().putString(COHORT_KEY, initialCohort).commit()
|
|
71
72
|
return initialCohort
|
|
72
73
|
}
|
|
73
74
|
}
|
|
@@ -387,19 +387,40 @@ class HotUpdaterImpl {
|
|
|
387
387
|
* This is used for Expo DOM components to construct full asset paths.
|
|
388
388
|
* @return Base URL string (e.g., "file:///data/.../bundle-store/abc123/") or empty string
|
|
389
389
|
*/
|
|
390
|
-
fun getBaseURL(): String
|
|
390
|
+
fun getBaseURL(): String {
|
|
391
|
+
val launchSelection = currentLaunchSelection
|
|
392
|
+
if (launchSelection != null) {
|
|
393
|
+
return bundleStorage.getBaseURLForBundle(launchSelection.launchedBundleId)
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
return bundleStorage.getBaseURL()
|
|
397
|
+
}
|
|
391
398
|
|
|
392
399
|
/**
|
|
393
400
|
* Gets the current active bundle ID from bundle storage.
|
|
394
401
|
* Reads manifest.json first and falls back to the legacy BUNDLE_ID file.
|
|
395
402
|
* Built-in bundle fallback is handled in JS.
|
|
396
403
|
*/
|
|
397
|
-
fun getBundleId(): String?
|
|
404
|
+
fun getBundleId(): String? {
|
|
405
|
+
val launchSelection = currentLaunchSelection
|
|
406
|
+
if (launchSelection != null) {
|
|
407
|
+
return launchSelection.launchedBundleId
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
return bundleStorage.getBundleId()
|
|
411
|
+
}
|
|
398
412
|
|
|
399
413
|
/**
|
|
400
414
|
* Gets the current manifest from bundle storage.
|
|
401
415
|
*/
|
|
402
|
-
fun getManifest(): Map<String, Any?>
|
|
416
|
+
fun getManifest(): Map<String, Any?> {
|
|
417
|
+
val launchSelection = currentLaunchSelection
|
|
418
|
+
if (launchSelection != null) {
|
|
419
|
+
return bundleStorage.getManifestForBundle(launchSelection.launchedBundleId)
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
return bundleStorage.getManifest()
|
|
423
|
+
}
|
|
403
424
|
|
|
404
425
|
suspend fun resetChannel(): Boolean {
|
|
405
426
|
val success = bundleStorage.resetChannel()
|
|
@@ -182,6 +182,53 @@ class BundleFileStorageServiceTest {
|
|
|
182
182
|
assertEquals(stagingDir.name, report["crashedBundleId"])
|
|
183
183
|
}
|
|
184
184
|
|
|
185
|
+
@Test
|
|
186
|
+
fun `getBundleId falls back to built in while staging verification is pending`() {
|
|
187
|
+
val rootDir = temporaryFolder.newFolder("pending-staging-built-in")
|
|
188
|
+
val service = createService(rootDir)
|
|
189
|
+
|
|
190
|
+
val stagingDir = createBundleDir(rootDir, "staging-bundle")
|
|
191
|
+
writeFile(stagingDir, "index.android.bundle")
|
|
192
|
+
writeManifest(stagingDir, listOf("index.android.bundle"))
|
|
193
|
+
|
|
194
|
+
writeMetadata(
|
|
195
|
+
rootDir,
|
|
196
|
+
BundleMetadata(
|
|
197
|
+
isolationKey = TEST_ISOLATION_KEY,
|
|
198
|
+
stableBundleId = null,
|
|
199
|
+
stagingBundleId = stagingDir.name,
|
|
200
|
+
verificationPending = true,
|
|
201
|
+
),
|
|
202
|
+
)
|
|
203
|
+
|
|
204
|
+
assertNull(service.getBundleId())
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
@Test
|
|
208
|
+
fun `getBundleId returns launched staging bundle while verification is pending`() {
|
|
209
|
+
val rootDir = temporaryFolder.newFolder("pending-staging-active")
|
|
210
|
+
val preferences = InMemoryPreferencesService()
|
|
211
|
+
val service = createService(rootDir, preferences)
|
|
212
|
+
|
|
213
|
+
val stagingDir = createBundleDir(rootDir, "staging-bundle")
|
|
214
|
+
val stagingBundleFile = writeFile(stagingDir, "index.android.bundle")
|
|
215
|
+
writeManifest(stagingDir, listOf("index.android.bundle"))
|
|
216
|
+
|
|
217
|
+
writeMetadata(
|
|
218
|
+
rootDir,
|
|
219
|
+
BundleMetadata(
|
|
220
|
+
isolationKey = TEST_ISOLATION_KEY,
|
|
221
|
+
stableBundleId = null,
|
|
222
|
+
stagingBundleId = stagingDir.name,
|
|
223
|
+
verificationPending = true,
|
|
224
|
+
),
|
|
225
|
+
)
|
|
226
|
+
|
|
227
|
+
preferences.setItem("HotUpdaterBundleURL", stagingBundleFile.absolutePath)
|
|
228
|
+
|
|
229
|
+
assertEquals(stagingDir.name, service.getBundleId())
|
|
230
|
+
}
|
|
231
|
+
|
|
185
232
|
private fun createService(
|
|
186
233
|
rootDir: File,
|
|
187
234
|
preferences: InMemoryPreferencesService = InMemoryPreferencesService(),
|
|
@@ -0,0 +1,208 @@
|
|
|
1
|
+
package com.hotupdater
|
|
2
|
+
|
|
3
|
+
import org.junit.Assert.assertEquals
|
|
4
|
+
import org.junit.Assert.assertNull
|
|
5
|
+
import org.junit.Assert.assertTrue
|
|
6
|
+
import org.junit.Test
|
|
7
|
+
|
|
8
|
+
class HotUpdaterImplTest {
|
|
9
|
+
@Test
|
|
10
|
+
fun `getBundleId falls back to bundle storage without a current launch selection`() {
|
|
11
|
+
val impl = createImpl(storageBundleId = "staged-bundle")
|
|
12
|
+
|
|
13
|
+
assertEquals("staged-bundle", impl.getBundleId())
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
@Test
|
|
17
|
+
fun `getBundleId returns current launched bundle over staged metadata`() {
|
|
18
|
+
val impl = createImpl(storageBundleId = "staged-bundle")
|
|
19
|
+
|
|
20
|
+
setCurrentLaunchSelection(
|
|
21
|
+
impl,
|
|
22
|
+
LaunchSelection(
|
|
23
|
+
bundleUrl = "file:///bundle-store/launched-bundle/index.android.bundle",
|
|
24
|
+
launchedBundleId = "launched-bundle",
|
|
25
|
+
shouldRollbackOnCrash = false,
|
|
26
|
+
),
|
|
27
|
+
)
|
|
28
|
+
|
|
29
|
+
assertEquals("launched-bundle", impl.getBundleId())
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
@Test
|
|
33
|
+
fun `getBundleId returns null for built in launch even when staged metadata exists`() {
|
|
34
|
+
val impl = createImpl(storageBundleId = "staged-bundle")
|
|
35
|
+
|
|
36
|
+
setCurrentLaunchSelection(
|
|
37
|
+
impl,
|
|
38
|
+
LaunchSelection(
|
|
39
|
+
bundleUrl = "assets://index.android.bundle",
|
|
40
|
+
launchedBundleId = null,
|
|
41
|
+
shouldRollbackOnCrash = false,
|
|
42
|
+
),
|
|
43
|
+
)
|
|
44
|
+
|
|
45
|
+
assertNull(impl.getBundleId())
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
@Test
|
|
49
|
+
fun `getManifest and getBaseURL return current launched bundle over staged metadata`() {
|
|
50
|
+
val impl =
|
|
51
|
+
createImpl(
|
|
52
|
+
storageBundleId = "staged-bundle",
|
|
53
|
+
storageManifest = mapOf("bundleId" to "staged-bundle"),
|
|
54
|
+
storageBaseURL = "file:///bundle-store/staged-bundle",
|
|
55
|
+
launchedBundleManifests =
|
|
56
|
+
mapOf(
|
|
57
|
+
"launched-bundle" to mapOf("bundleId" to "launched-bundle"),
|
|
58
|
+
),
|
|
59
|
+
launchedBundleBaseURLs =
|
|
60
|
+
mapOf(
|
|
61
|
+
"launched-bundle" to "file:///bundle-store/launched-bundle",
|
|
62
|
+
),
|
|
63
|
+
)
|
|
64
|
+
|
|
65
|
+
setCurrentLaunchSelection(
|
|
66
|
+
impl,
|
|
67
|
+
LaunchSelection(
|
|
68
|
+
bundleUrl = "file:///bundle-store/launched-bundle/index.android.bundle",
|
|
69
|
+
launchedBundleId = "launched-bundle",
|
|
70
|
+
shouldRollbackOnCrash = false,
|
|
71
|
+
),
|
|
72
|
+
)
|
|
73
|
+
|
|
74
|
+
assertEquals(
|
|
75
|
+
mapOf("bundleId" to "launched-bundle"),
|
|
76
|
+
impl.getManifest(),
|
|
77
|
+
)
|
|
78
|
+
assertEquals("file:///bundle-store/launched-bundle", impl.getBaseURL())
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
@Test
|
|
82
|
+
fun `getManifest and getBaseURL return built in values for built in launch`() {
|
|
83
|
+
val impl =
|
|
84
|
+
createImpl(
|
|
85
|
+
storageBundleId = "staged-bundle",
|
|
86
|
+
storageManifest = mapOf("bundleId" to "staged-bundle"),
|
|
87
|
+
storageBaseURL = "file:///bundle-store/staged-bundle",
|
|
88
|
+
)
|
|
89
|
+
|
|
90
|
+
setCurrentLaunchSelection(
|
|
91
|
+
impl,
|
|
92
|
+
LaunchSelection(
|
|
93
|
+
bundleUrl = "assets://index.android.bundle",
|
|
94
|
+
launchedBundleId = null,
|
|
95
|
+
shouldRollbackOnCrash = false,
|
|
96
|
+
),
|
|
97
|
+
)
|
|
98
|
+
|
|
99
|
+
assertTrue(impl.getManifest().isEmpty())
|
|
100
|
+
assertEquals("", impl.getBaseURL())
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
private fun createImpl(
|
|
104
|
+
storageBundleId: String?,
|
|
105
|
+
storageManifest: Map<String, Any?> = emptyMap(),
|
|
106
|
+
storageBaseURL: String = "",
|
|
107
|
+
launchedBundleManifests: Map<String, Map<String, Any?>> = emptyMap(),
|
|
108
|
+
launchedBundleBaseURLs: Map<String, String> = emptyMap(),
|
|
109
|
+
): HotUpdaterImpl =
|
|
110
|
+
allocateWithoutConstructor<HotUpdaterImpl>().also { impl ->
|
|
111
|
+
setField(
|
|
112
|
+
impl,
|
|
113
|
+
"bundleStorage",
|
|
114
|
+
FakeBundleStorageService(
|
|
115
|
+
bundleId = storageBundleId,
|
|
116
|
+
manifest = storageManifest,
|
|
117
|
+
baseURL = storageBaseURL,
|
|
118
|
+
launchedBundleManifests = launchedBundleManifests,
|
|
119
|
+
launchedBundleBaseURLs = launchedBundleBaseURLs,
|
|
120
|
+
),
|
|
121
|
+
)
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
private fun setCurrentLaunchSelection(
|
|
125
|
+
impl: HotUpdaterImpl,
|
|
126
|
+
selection: LaunchSelection,
|
|
127
|
+
) {
|
|
128
|
+
setField(impl, "currentLaunchSelection", selection)
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
private fun setField(
|
|
132
|
+
target: Any,
|
|
133
|
+
fieldName: String,
|
|
134
|
+
value: Any?,
|
|
135
|
+
) {
|
|
136
|
+
val field = HotUpdaterImpl::class.java.getDeclaredField(fieldName)
|
|
137
|
+
field.isAccessible = true
|
|
138
|
+
field.set(target, value)
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
private inline fun <reified T> allocateWithoutConstructor(): T {
|
|
142
|
+
val field = Class.forName("sun.misc.Unsafe").getDeclaredField("theUnsafe")
|
|
143
|
+
field.isAccessible = true
|
|
144
|
+
val unsafe = field.get(null)
|
|
145
|
+
val allocateInstance = unsafe.javaClass.getMethod("allocateInstance", Class::class.java)
|
|
146
|
+
@Suppress("UNCHECKED_CAST")
|
|
147
|
+
return allocateInstance.invoke(unsafe, T::class.java) as T
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
private class FakeBundleStorageService(
|
|
151
|
+
private val bundleId: String?,
|
|
152
|
+
private val manifest: Map<String, Any?> = emptyMap(),
|
|
153
|
+
private val baseURL: String = "",
|
|
154
|
+
private val launchedBundleManifests: Map<String, Map<String, Any?>> =
|
|
155
|
+
emptyMap(),
|
|
156
|
+
private val launchedBundleBaseURLs: Map<String, String> = emptyMap(),
|
|
157
|
+
) : BundleStorageService {
|
|
158
|
+
override fun setBundleURL(localPath: String?): Boolean = true
|
|
159
|
+
|
|
160
|
+
override fun getCachedBundleURL(): String? = null
|
|
161
|
+
|
|
162
|
+
override fun getFallbackBundleURL(): String = "assets://index.android.bundle"
|
|
163
|
+
|
|
164
|
+
override fun prepareLaunch(pendingRecovery: PendingCrashRecovery?): LaunchSelection =
|
|
165
|
+
LaunchSelection(
|
|
166
|
+
bundleUrl = "assets://index.android.bundle",
|
|
167
|
+
launchedBundleId = null,
|
|
168
|
+
shouldRollbackOnCrash = false,
|
|
169
|
+
)
|
|
170
|
+
|
|
171
|
+
override suspend fun updateBundle(
|
|
172
|
+
bundleId: String,
|
|
173
|
+
fileUrl: String?,
|
|
174
|
+
fileHash: String?,
|
|
175
|
+
progressCallback: (Double) -> Unit,
|
|
176
|
+
) = Unit
|
|
177
|
+
|
|
178
|
+
override fun markLaunchCompleted(currentBundleId: String?) = Unit
|
|
179
|
+
|
|
180
|
+
override fun notifyAppReady(): Map<String, Any?> = mapOf("status" to "STABLE")
|
|
181
|
+
|
|
182
|
+
override fun getCrashHistory(): CrashedHistory = CrashedHistory()
|
|
183
|
+
|
|
184
|
+
override fun clearCrashHistory(): Boolean = true
|
|
185
|
+
|
|
186
|
+
override fun getBaseURL(): String = baseURL
|
|
187
|
+
|
|
188
|
+
override fun getBaseURLForBundle(bundleId: String?): String =
|
|
189
|
+
if (bundleId == null) {
|
|
190
|
+
""
|
|
191
|
+
} else {
|
|
192
|
+
launchedBundleBaseURLs[bundleId] ?: ""
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
override fun getBundleId(): String? = bundleId
|
|
196
|
+
|
|
197
|
+
override fun getManifest(): Map<String, Any?> = manifest
|
|
198
|
+
|
|
199
|
+
override fun getManifestForBundle(bundleId: String?): Map<String, Any?> =
|
|
200
|
+
if (bundleId == null) {
|
|
201
|
+
emptyMap()
|
|
202
|
+
} else {
|
|
203
|
+
launchedBundleManifests[bundleId] ?: emptyMap()
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
override suspend fun resetChannel(): Boolean = true
|
|
207
|
+
}
|
|
208
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@hot-updater/react-native",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.30.0",
|
|
4
4
|
"description": "React Native OTA solution for self-hosted",
|
|
5
5
|
"main": "lib/commonjs/index",
|
|
6
6
|
"module": "lib/module/index",
|
|
@@ -120,14 +120,14 @@
|
|
|
120
120
|
"react-native": "0.79.1",
|
|
121
121
|
"react-native-builder-bob": "^0.40.10",
|
|
122
122
|
"typescript": "^6.0.2",
|
|
123
|
-
"hot-updater": "0.
|
|
123
|
+
"hot-updater": "0.30.0"
|
|
124
124
|
},
|
|
125
125
|
"dependencies": {
|
|
126
126
|
"use-sync-external-store": "1.5.0",
|
|
127
|
-
"@hot-updater/core": "0.
|
|
128
|
-
"@hot-updater/
|
|
129
|
-
"@hot-updater/
|
|
130
|
-
"@hot-updater/
|
|
127
|
+
"@hot-updater/core": "0.30.0",
|
|
128
|
+
"@hot-updater/js": "0.30.0",
|
|
129
|
+
"@hot-updater/plugin-core": "0.30.0",
|
|
130
|
+
"@hot-updater/cli-tools": "0.30.0"
|
|
131
131
|
},
|
|
132
132
|
"scripts": {
|
|
133
133
|
"build": "bob build && tsc -p plugin/tsconfig.build.json",
|