expo-updates 55.0.29 → 55.0.30
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 +9 -0
- package/android/build.gradle +2 -2
- package/android/src/main/java/expo/modules/updates/launcher/DatabaseLauncher.kt +9 -0
- package/android/src/main/java/expo/modules/updates/loader/Loader.kt +5 -1
- package/ios/EXUpdates/Database/UpdatesDatabase.swift +20 -4
- package/package.json +2 -2
- package/utils/build/createManifestForBuildAsync.js +4 -2
- package/utils/src/createManifestForBuildAsync.ts +4 -2
package/CHANGELOG.md
CHANGED
|
@@ -10,6 +10,15 @@
|
|
|
10
10
|
|
|
11
11
|
### 💡 Others
|
|
12
12
|
|
|
13
|
+
## 55.0.30 — 2026-08-28
|
|
14
|
+
|
|
15
|
+
### 🐛 Bug fixes
|
|
16
|
+
|
|
17
|
+
- [iOS] Propagate database failures from `addNewAssets` instead of swallowing them, which let an update be marked ready without its launch asset and then fail every launch. ([#49456](https://github.com/expo/expo/pull/49456) by [@alanjhughes](https://github.com/alanjhughes))
|
|
18
|
+
- [iOS] Repair ready updates that are missing their launch asset by demoting them to pending during launcher selection, so a corrupted row is skipped and retried instead of failing every launch. ([#49457](https://github.com/expo/expo/pull/49457) by [@alanjhughes](https://github.com/alanjhughes))
|
|
19
|
+
- [Android] Skip and repair updates that are missing their launch asset instead of selecting them for launch, which previously failed every cold start with "Launch asset not found for update". ([#49470](https://github.com/expo/expo/pull/49470) by [@alanjhughes](https://github.com/alanjhughes), based on [#48733](https://github.com/expo/expo/pull/48733) by [@martintreurnicht](https://github.com/martintreurnicht))
|
|
20
|
+
- [iOS] Fix the embedded manifest recording the wrong `packagerHash` for assets that ship scale variants iOS does not allow (such as `@1.5x` and `@4x`): the hashes were read by the filtered scale index instead of the asset's own, so those images resolved to an empty URI and rendered blank in release builds. ([#48811](https://github.com/expo/expo/pull/48811) by [@expo-bot](https://github.com/expo-bot))
|
|
21
|
+
|
|
13
22
|
## 55.0.29 — 2026-08-27
|
|
14
23
|
|
|
15
24
|
### 🐛 Bug fixes
|
package/android/build.gradle
CHANGED
|
@@ -42,7 +42,7 @@ expoModule {
|
|
|
42
42
|
}
|
|
43
43
|
|
|
44
44
|
group = 'host.exp.exponent'
|
|
45
|
-
version = '55.0.
|
|
45
|
+
version = '55.0.30'
|
|
46
46
|
|
|
47
47
|
// Utility method to derive boolean values from the environment or from Java properties,
|
|
48
48
|
// and return them as strings to be used in BuildConfig fields
|
|
@@ -89,7 +89,7 @@ android {
|
|
|
89
89
|
namespace "expo.modules.updates"
|
|
90
90
|
defaultConfig {
|
|
91
91
|
versionCode 31
|
|
92
|
-
versionName '55.0.
|
|
92
|
+
versionName '55.0.30'
|
|
93
93
|
consumerProguardFiles("proguard-rules.pro")
|
|
94
94
|
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
|
|
95
95
|
|
|
@@ -161,6 +161,15 @@ class DatabaseLauncher(
|
|
|
161
161
|
if (!configuration.hasEmbeddedUpdate && embeddedUpdate?.updateEntity?.id == update.id) {
|
|
162
162
|
continue
|
|
163
163
|
}
|
|
164
|
+
|
|
165
|
+
// An update with no launch asset can never launch. Excluding it here lets the loader
|
|
166
|
+
// re-run and repair the row instead of failing every cold start.
|
|
167
|
+
if (update.status != UpdateStatus.DEVELOPMENT &&
|
|
168
|
+
database.updateDao().loadLaunchAssetForUpdate(update.id) == null
|
|
169
|
+
) {
|
|
170
|
+
logger.warn("Skipping launchable update with no launch asset. Debug info: ${update.debugInfo()}")
|
|
171
|
+
continue
|
|
172
|
+
}
|
|
164
173
|
filteredLaunchableUpdates.add(update)
|
|
165
174
|
}
|
|
166
175
|
val manifestFilters = ManifestMetadata.getManifestFilters(database, configuration)
|
|
@@ -166,7 +166,11 @@ abstract class Loader protected constructor(
|
|
|
166
166
|
database.updateDao().setUpdateScopeKey(existingUpdateEntity, newUpdateEntity.scopeKey)
|
|
167
167
|
}
|
|
168
168
|
|
|
169
|
-
|
|
169
|
+
// A READY update with no launch asset is not actually ready. Fall through to
|
|
170
|
+
// downloadAllAssets so its assets are re-registered instead of staying broken forever.
|
|
171
|
+
if (existingUpdateEntity != null && existingUpdateEntity.status == UpdateStatus.READY &&
|
|
172
|
+
database.updateDao().loadLaunchAssetForUpdate(existingUpdateEntity.id) != null
|
|
173
|
+
) {
|
|
170
174
|
// hooray, we already have this update downloaded and ready to go!
|
|
171
175
|
updateEntity = existingUpdateEntity
|
|
172
176
|
return finish()
|
|
@@ -160,7 +160,7 @@ public final class UpdatesDatabase: NSObject {
|
|
|
160
160
|
)
|
|
161
161
|
} catch {
|
|
162
162
|
sqlite3_exec(db, "ROLLBACK;", nil, nil, nil)
|
|
163
|
-
|
|
163
|
+
throw error
|
|
164
164
|
}
|
|
165
165
|
|
|
166
166
|
// statements must stay in precisely this order for last_insert_rowid() to work correctly
|
|
@@ -170,7 +170,7 @@ public final class UpdatesDatabase: NSObject {
|
|
|
170
170
|
_ = try execute(sql: updateSql, withArgs: [updateId])
|
|
171
171
|
} catch {
|
|
172
172
|
sqlite3_exec(db, "ROLLBACK;", nil, nil, nil)
|
|
173
|
-
|
|
173
|
+
throw error
|
|
174
174
|
}
|
|
175
175
|
}
|
|
176
176
|
|
|
@@ -181,7 +181,7 @@ public final class UpdatesDatabase: NSObject {
|
|
|
181
181
|
_ = try execute(sql: updateInsertSql, withArgs: [updateId])
|
|
182
182
|
} catch {
|
|
183
183
|
sqlite3_exec(db, "ROLLBACK;", nil, nil, nil)
|
|
184
|
-
|
|
184
|
+
throw error
|
|
185
185
|
}
|
|
186
186
|
}
|
|
187
187
|
|
|
@@ -444,7 +444,23 @@ public final class UpdatesDatabase: NSObject {
|
|
|
444
444
|
)
|
|
445
445
|
|
|
446
446
|
let rows = try execute(sql: sql, withArgs: [config.scopeKey])
|
|
447
|
-
|
|
447
|
+
|
|
448
|
+
// A ready row with no launch asset is corrupt (e.g. from an interrupted registration) and
|
|
449
|
+
// would fail every launch. Demote it for the loader to retry instead of offering it.
|
|
450
|
+
var launchableRows: [[String: Any?]] = []
|
|
451
|
+
for row in rows {
|
|
452
|
+
let status: NSNumber = row.requiredValue(forKey: "status")
|
|
453
|
+
let launchAssetId: NSNumber? = row.optionalValue(forKey: "launch_asset_id")
|
|
454
|
+
if status.intValue == UpdateStatus.StatusReady.rawValue && launchAssetId == nil {
|
|
455
|
+
let updateId: UUID = row.requiredValue(forKey: "id")
|
|
456
|
+
let demoteSql = "UPDATE updates SET status = ?1 WHERE id = ?2;"
|
|
457
|
+
_ = try execute(sql: demoteSql, withArgs: [UpdateStatus.StatusPending.rawValue, updateId])
|
|
458
|
+
} else {
|
|
459
|
+
launchableRows.append(row)
|
|
460
|
+
}
|
|
461
|
+
}
|
|
462
|
+
|
|
463
|
+
return launchableRows.map { row in
|
|
448
464
|
update(withRow: row, config: config)
|
|
449
465
|
}
|
|
450
466
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "expo-updates",
|
|
3
|
-
"version": "55.0.
|
|
3
|
+
"version": "55.0.30",
|
|
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",
|
|
@@ -71,5 +71,5 @@
|
|
|
71
71
|
"react": "*",
|
|
72
72
|
"react-native": "*"
|
|
73
73
|
},
|
|
74
|
-
"gitHead": "
|
|
74
|
+
"gitHead": "a7b74adf4f2d7ce2753db963616740ceec558a5f"
|
|
75
75
|
}
|
|
@@ -45,12 +45,14 @@ async function createManifestForBuildAsync(platform, projectRoot, destinationDir
|
|
|
45
45
|
if (!asset.fileHashes) {
|
|
46
46
|
throw new Error('The hashAssetFiles Metro plugin is not configured. You need to add a metro.config.js to your project that configures Metro to use this plugin. See https://github.com/expo/expo/blob/main/packages/expo-updates/README.md#metroconfigjs for an example.');
|
|
47
47
|
}
|
|
48
|
-
(0, filterPlatformAssetScales_1.filterPlatformAssetScales)(platform, asset.scales).forEach(function (scale
|
|
48
|
+
(0, filterPlatformAssetScales_1.filterPlatformAssetScales)(platform, asset.scales).forEach(function (scale) {
|
|
49
49
|
const baseAssetInfoForManifest = {
|
|
50
50
|
name: asset.name,
|
|
51
51
|
type: asset.type,
|
|
52
52
|
scale,
|
|
53
|
-
|
|
53
|
+
// `fileHashes` is parallel to the unfiltered `asset.scales`, so it must be indexed by the
|
|
54
|
+
// scale's position there rather than by its position in the filtered list.
|
|
55
|
+
packagerHash: asset.fileHashes[asset.scales.indexOf(scale)],
|
|
54
56
|
subdirectory: asset.httpServerLocation,
|
|
55
57
|
};
|
|
56
58
|
if (platform === 'ios') {
|
|
@@ -64,12 +64,14 @@ export async function createManifestForBuildAsync(
|
|
|
64
64
|
'The hashAssetFiles Metro plugin is not configured. You need to add a metro.config.js to your project that configures Metro to use this plugin. See https://github.com/expo/expo/blob/main/packages/expo-updates/README.md#metroconfigjs for an example.'
|
|
65
65
|
);
|
|
66
66
|
}
|
|
67
|
-
filterPlatformAssetScales(platform, asset.scales).forEach(function (scale
|
|
67
|
+
filterPlatformAssetScales(platform, asset.scales).forEach(function (scale) {
|
|
68
68
|
const baseAssetInfoForManifest = {
|
|
69
69
|
name: asset.name,
|
|
70
70
|
type: asset.type,
|
|
71
71
|
scale,
|
|
72
|
-
|
|
72
|
+
// `fileHashes` is parallel to the unfiltered `asset.scales`, so it must be indexed by the
|
|
73
|
+
// scale's position there rather than by its position in the filtered list.
|
|
74
|
+
packagerHash: asset.fileHashes[asset.scales.indexOf(scale)],
|
|
73
75
|
subdirectory: asset.httpServerLocation,
|
|
74
76
|
};
|
|
75
77
|
if (platform === 'ios') {
|