expo-brownfield 56.0.5 → 56.0.7
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 +10 -0
- package/android/build.gradle +2 -2
- package/gradle-plugins/brownfield/src/main/kotlin/expo/modules/plugin/ExpoBrownfieldSetupPlugin.kt +67 -11
- package/gradle-plugins/brownfield/src/main/kotlin/expo/modules/plugin/utils.kt +105 -0
- package/package.json +5 -5
- package/prebuilds/output/debug/xcframeworks/ExpoBrownfield.tar.gz +0 -0
- package/prebuilds/output/release/xcframeworks/ExpoBrownfield.tar.gz +0 -0
package/CHANGELOG.md
CHANGED
|
@@ -10,6 +10,16 @@
|
|
|
10
10
|
|
|
11
11
|
### 💡 Others
|
|
12
12
|
|
|
13
|
+
## 56.0.7 — 2026-05-13
|
|
14
|
+
|
|
15
|
+
_This version does not introduce any user-facing changes._
|
|
16
|
+
|
|
17
|
+
## 56.0.6 — 2026-05-11
|
|
18
|
+
|
|
19
|
+
### 🐛 Bug fixes
|
|
20
|
+
|
|
21
|
+
- Fix reading updates app.manifest and meta-data tags ([#45655](https://github.com/expo/expo/pull/45655) by [@gabrieldonadel](https://github.com/gabrieldonadel))
|
|
22
|
+
|
|
13
23
|
## 56.0.5 — 2026-05-08
|
|
14
24
|
|
|
15
25
|
_This version does not introduce any user-facing changes._
|
package/android/build.gradle
CHANGED
|
@@ -4,7 +4,7 @@ plugins {
|
|
|
4
4
|
}
|
|
5
5
|
|
|
6
6
|
group = 'expo.modules.brownfield'
|
|
7
|
-
version = '56.0.
|
|
7
|
+
version = '56.0.7'
|
|
8
8
|
|
|
9
9
|
expoModule {
|
|
10
10
|
canBePublished false
|
|
@@ -14,7 +14,7 @@ android {
|
|
|
14
14
|
namespace "expo.modules.brownfield"
|
|
15
15
|
defaultConfig {
|
|
16
16
|
versionCode 1
|
|
17
|
-
versionName '56.0.
|
|
17
|
+
versionName '56.0.7'
|
|
18
18
|
}
|
|
19
19
|
}
|
|
20
20
|
|
package/gradle-plugins/brownfield/src/main/kotlin/expo/modules/plugin/ExpoBrownfieldSetupPlugin.kt
CHANGED
|
@@ -16,9 +16,9 @@ class ExpoBrownfieldSetupPlugin : Plugin<Project> {
|
|
|
16
16
|
project.afterEvaluate { project ->
|
|
17
17
|
setupSourceSets(project)
|
|
18
18
|
setupCopyingAutolinking(project)
|
|
19
|
-
setupBundleDependencyForRelease(project)
|
|
20
19
|
setupCopyingNativeLibsForType(project, "Release")
|
|
21
20
|
setupCopyingNativeLibsForType(project, "Debug")
|
|
21
|
+
setupHostAppArtifactForwardingForRelease(project)
|
|
22
22
|
wireDevLauncherTasks(project)
|
|
23
23
|
}
|
|
24
24
|
}
|
|
@@ -58,7 +58,7 @@ class ExpoBrownfieldSetupPlugin : Plugin<Project> {
|
|
|
58
58
|
|
|
59
59
|
libraryExtension.sourceSets.getByName("release").apply {
|
|
60
60
|
jniLibs.srcDirs("libsRelease")
|
|
61
|
-
assets
|
|
61
|
+
// release assets src dir is wired in setupHostAppArtifactForwardingForRelease
|
|
62
62
|
res.srcDirs("$appBuildDir/generated/res/react/release")
|
|
63
63
|
}
|
|
64
64
|
|
|
@@ -120,18 +120,74 @@ class ExpoBrownfieldSetupPlugin : Plugin<Project> {
|
|
|
120
120
|
}
|
|
121
121
|
|
|
122
122
|
/**
|
|
123
|
-
*
|
|
123
|
+
* Forward the host `:app` module's build-time outputs into the published brownfield AAR so the
|
|
124
|
+
* runtime React Native + expo libraries inside the AAR find the configuration they need.
|
|
124
125
|
*
|
|
125
|
-
*
|
|
126
|
+
* Two pieces are forwarded:
|
|
126
127
|
*
|
|
127
|
-
*
|
|
128
|
-
*
|
|
128
|
+
* 1. `:app:mergeReleaseAssets` output (everything that AGP would bundle into the host APK's
|
|
129
|
+
* `assets/`). This includes the RN JS bundle and hashed assets, expo-updates' `app.manifest`,
|
|
130
|
+
* expo-constants' `app.config`, and any other generated asset emitted by a host-side gradle
|
|
131
|
+
* plugin. Forwarding the merged output (rather than picking specific generator tasks) makes
|
|
132
|
+
* this future-proof: any new expo library that emits an asset on the `:app` side is picked
|
|
133
|
+
* up automatically. Transitive dep: `mergeReleaseAssets` requires `createBundleReleaseJsAndAssets`
|
|
134
|
+
* so the old `setupBundleDependencyForRelease` is no longer needed.
|
|
135
|
+
*
|
|
136
|
+
* 2. Every `<application>` `<meta-data>` entry from `:app/src/main/AndroidManifest.xml`,
|
|
137
|
+
* written into a generated release-variant manifest that AGP merges into the consumer.
|
|
138
|
+
* Covers expo-updates' `EXPO_UPDATE_URL`, expo-notifications' default icon/color, and
|
|
139
|
+
* anything else a config plugin injects.
|
|
140
|
+
*
|
|
141
|
+
* @param brownfieldProject The brownfield project.
|
|
129
142
|
*/
|
|
130
|
-
internal fun
|
|
143
|
+
internal fun setupHostAppArtifactForwardingForRelease(brownfieldProject: Project) {
|
|
131
144
|
val appProject = findAppProject(brownfieldProject)
|
|
145
|
+
val mergeAssetsTask = appProject.tasks.findByName("mergeReleaseAssets") ?: run {
|
|
146
|
+
brownfieldProject.logger.lifecycle(
|
|
147
|
+
"brownfield: \":${appProject.name}:mergeReleaseAssets\" task not found; " +
|
|
148
|
+
"skipping host-app asset forwarding."
|
|
149
|
+
)
|
|
150
|
+
return
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
val libraryExtension = getLibraryExtension(brownfieldProject)
|
|
154
|
+
val moduleBuildDir = brownfieldProject.layout.buildDirectory.get().asFile
|
|
155
|
+
|
|
156
|
+
val hostAssetsDir = File(moduleBuildDir, "generated/assets/hostApp/release")
|
|
157
|
+
val copyHostAssetsTask =
|
|
158
|
+
brownfieldProject.tasks.register("copyHostAppAssetsRelease", Copy::class.java) { task ->
|
|
159
|
+
task.dependsOn(mergeAssetsTask)
|
|
160
|
+
task.from(mergeAssetsTask.outputs.files)
|
|
161
|
+
task.into(hostAssetsDir)
|
|
162
|
+
}
|
|
163
|
+
libraryExtension.sourceSets.getByName("release").assets.srcDirs(hostAssetsDir)
|
|
164
|
+
|
|
165
|
+
val hostManifestFile =
|
|
166
|
+
File(moduleBuildDir, "generated/manifest/hostApp/release/AndroidManifest.xml")
|
|
167
|
+
val appManifest = File(appProject.projectDir, "src/main/AndroidManifest.xml")
|
|
168
|
+
val appStrings = File(appProject.projectDir, "src/main/res/values/strings.xml")
|
|
169
|
+
|
|
170
|
+
val generateHostManifestTask =
|
|
171
|
+
brownfieldProject.tasks.register("generateBrownfieldHostAppManifestRelease") { task ->
|
|
172
|
+
task.inputs.file(appManifest)
|
|
173
|
+
if (appStrings.exists()) {
|
|
174
|
+
task.inputs.file(appStrings)
|
|
175
|
+
}
|
|
176
|
+
task.outputs.file(hostManifestFile)
|
|
177
|
+
task.doLast {
|
|
178
|
+
hostManifestFile.parentFile.mkdirs()
|
|
179
|
+
hostManifestFile.writeText(buildForwardedApplicationManifest(appManifest, appStrings))
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
libraryExtension.sourceSets.getByName("release").manifest.srcFile(hostManifestFile)
|
|
183
|
+
|
|
132
184
|
brownfieldProject.tasks.named("preReleaseBuild").configure { task ->
|
|
133
|
-
task.dependsOn(
|
|
185
|
+
task.dependsOn(copyHostAssetsTask)
|
|
186
|
+
task.dependsOn(generateHostManifestTask)
|
|
134
187
|
}
|
|
188
|
+
brownfieldProject.tasks
|
|
189
|
+
.matching { it.name == "processReleaseManifest" || it.name == "processReleaseMainManifest" }
|
|
190
|
+
.configureEach { task -> task.dependsOn(generateHostManifestTask) }
|
|
135
191
|
}
|
|
136
192
|
|
|
137
193
|
/**
|
|
@@ -266,7 +322,7 @@ class ExpoBrownfieldSetupPlugin : Plugin<Project> {
|
|
|
266
322
|
/**
|
|
267
323
|
* Add explicit dependency between the `sourceDebugJar` and `generateServiceApolloSources`
|
|
268
324
|
* tasks in `expo-dev-launcher` project.
|
|
269
|
-
*
|
|
325
|
+
*
|
|
270
326
|
* @param brownfieldProject The brownfield project
|
|
271
327
|
*/
|
|
272
328
|
private fun wireDevLauncherTasks(brownfieldProject: Project) {
|
|
@@ -275,7 +331,7 @@ class ExpoBrownfieldSetupPlugin : Plugin<Project> {
|
|
|
275
331
|
|
|
276
332
|
val sourceDebugTask = devLauncherProject.tasks.findByName("sourceDebugJar")
|
|
277
333
|
val apolloSourcesTask = devLauncherProject.tasks.findByName("generateServiceApolloSources")
|
|
278
|
-
|
|
334
|
+
|
|
279
335
|
if (sourceDebugTask == null || apolloSourcesTask == null) {
|
|
280
336
|
brownfieldProject.logger.warn("WARNING: Application uses expo-dev-launcher but tasks: sourceDebugJar and generateServiceApolloSources")
|
|
281
337
|
brownfieldProject.logger.warn("Skipping explicitly defining dependency between the tasks...")
|
|
@@ -284,7 +340,7 @@ class ExpoBrownfieldSetupPlugin : Plugin<Project> {
|
|
|
284
340
|
|
|
285
341
|
sourceDebugTask.dependsOn(apolloSourcesTask)
|
|
286
342
|
} catch (e: GradleException) {
|
|
287
|
-
// no-op
|
|
343
|
+
// no-op
|
|
288
344
|
}
|
|
289
345
|
}
|
|
290
346
|
}
|
|
@@ -1,6 +1,11 @@
|
|
|
1
|
+
@file:JvmName("BrownfieldSetupUtilsKt")
|
|
2
|
+
|
|
1
3
|
package expo.modules.plugin
|
|
2
4
|
|
|
5
|
+
import java.io.File
|
|
6
|
+
import javax.xml.parsers.DocumentBuilderFactory
|
|
3
7
|
import org.gradle.api.Project
|
|
8
|
+
import org.w3c.dom.Element
|
|
4
9
|
|
|
5
10
|
/**
|
|
6
11
|
* Find the app project in the root project.
|
|
@@ -14,3 +19,103 @@ internal fun findAppProject(project: Project): Project {
|
|
|
14
19
|
it.plugins.hasPlugin("com.android.application")
|
|
15
20
|
} ?: throw IllegalStateException("App project not found in the root project")
|
|
16
21
|
}
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Build a release-variant AndroidManifest.xml that forwards every `<application>`
|
|
25
|
+
* `<meta-data>` entry from the expo app's manifest into the brownfield library's release
|
|
26
|
+
* manifest, so AGP merges them into the consumer's manifest at AAR consumption time.
|
|
27
|
+
*
|
|
28
|
+
* This covers any expo library whose config plugin injects runtime configuration into
|
|
29
|
+
* the expo app's AndroidManifest at `expo prebuild` time (expo-updates, expo-notifications,
|
|
30
|
+
* etc.). Without forwarding, the brownfield library's runtime modules read empty meta-data
|
|
31
|
+
* and silently disable themselves.
|
|
32
|
+
*
|
|
33
|
+
* Value forms supported:
|
|
34
|
+
* - Literal `android:value="..."` (preserved as-is).
|
|
35
|
+
* - String resource refs `android:value="@string/foo"` are resolved against the expo
|
|
36
|
+
* app's `res/values/strings.xml` and inlined. The brownfield AAR cannot share those
|
|
37
|
+
* strings with the consumer because resources are not currently forwarded.
|
|
38
|
+
* - `android:resource="@drawable/foo"` (or any other resource ref) is preserved as a
|
|
39
|
+
* `resource` attribute. Note: for the consumer to actually resolve it, the referenced
|
|
40
|
+
* resource needs to ship in the brownfield AAR (drawable, color, etc.).
|
|
41
|
+
*/
|
|
42
|
+
fun buildForwardedApplicationManifest(appManifest: File, appStrings: File): String {
|
|
43
|
+
val empty = """<?xml version="1.0" encoding="utf-8"?>
|
|
44
|
+
<manifest xmlns:android="http://schemas.android.com/apk/res/android" />
|
|
45
|
+
"""
|
|
46
|
+
if (!appManifest.exists()) return empty
|
|
47
|
+
|
|
48
|
+
val factory = DocumentBuilderFactory.newInstance().apply { isNamespaceAware = false }
|
|
49
|
+
val doc = factory.newDocumentBuilder().parse(appManifest)
|
|
50
|
+
val applicationNodes = doc.getElementsByTagName("application")
|
|
51
|
+
if (applicationNodes.length == 0) return empty
|
|
52
|
+
val applicationEl = applicationNodes.item(0) as? Element ?: return empty
|
|
53
|
+
|
|
54
|
+
val strings = parseStringResources(appStrings)
|
|
55
|
+
val children = applicationEl.childNodes
|
|
56
|
+
|
|
57
|
+
data class MetaEntry(val name: String, val attr: String, val value: String)
|
|
58
|
+
val entries = mutableListOf<MetaEntry>()
|
|
59
|
+
for (i in 0 until children.length) {
|
|
60
|
+
val el = children.item(i) as? Element ?: continue
|
|
61
|
+
if (el.tagName != "meta-data") continue
|
|
62
|
+
val name = el.getAttribute("android:name")
|
|
63
|
+
if (name.isNullOrEmpty()) continue
|
|
64
|
+
val literalValue = el.getAttribute("android:value")
|
|
65
|
+
val resourceRef = el.getAttribute("android:resource")
|
|
66
|
+
when {
|
|
67
|
+
!literalValue.isNullOrEmpty() ->
|
|
68
|
+
entries.add(MetaEntry(name, "android:value", resolveResourceReference(literalValue, strings)))
|
|
69
|
+
!resourceRef.isNullOrEmpty() ->
|
|
70
|
+
entries.add(MetaEntry(name, "android:resource", resourceRef))
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
if (entries.isEmpty()) return empty
|
|
74
|
+
|
|
75
|
+
return buildString {
|
|
76
|
+
append("<?xml version=\"1.0\" encoding=\"utf-8\"?>\n")
|
|
77
|
+
append("<manifest xmlns:android=\"http://schemas.android.com/apk/res/android\">\n")
|
|
78
|
+
append(" <application>\n")
|
|
79
|
+
entries.forEach { entry ->
|
|
80
|
+
append(" <meta-data android:name=\"")
|
|
81
|
+
append(escapeXmlAttribute(entry.name))
|
|
82
|
+
append("\" ")
|
|
83
|
+
append(entry.attr)
|
|
84
|
+
append("=\"")
|
|
85
|
+
append(escapeXmlAttribute(entry.value))
|
|
86
|
+
append("\" />\n")
|
|
87
|
+
}
|
|
88
|
+
append(" </application>\n")
|
|
89
|
+
append("</manifest>\n")
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
private fun parseStringResources(file: File): Map<String, String> {
|
|
94
|
+
if (!file.exists()) return emptyMap()
|
|
95
|
+
val factory = DocumentBuilderFactory.newInstance().apply { isNamespaceAware = false }
|
|
96
|
+
val doc = factory.newDocumentBuilder().parse(file)
|
|
97
|
+
val list = doc.getElementsByTagName("string")
|
|
98
|
+
val result = mutableMapOf<String, String>()
|
|
99
|
+
for (i in 0 until list.length) {
|
|
100
|
+
val el = list.item(i) as? Element ?: continue
|
|
101
|
+
val name = el.getAttribute("name") ?: continue
|
|
102
|
+
if (name.isEmpty()) continue
|
|
103
|
+
result[name] = el.textContent
|
|
104
|
+
}
|
|
105
|
+
return result
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
private fun resolveResourceReference(raw: String, strings: Map<String, String>): String {
|
|
109
|
+
if (raw.startsWith("@string/")) {
|
|
110
|
+
val key = raw.removePrefix("@string/")
|
|
111
|
+
return strings[key] ?: raw
|
|
112
|
+
}
|
|
113
|
+
return raw
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
private fun escapeXmlAttribute(value: String): String =
|
|
117
|
+
value.replace("&", "&")
|
|
118
|
+
.replace("<", "<")
|
|
119
|
+
.replace(">", ">")
|
|
120
|
+
.replace("\"", """)
|
|
121
|
+
.replace("'", "'")
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "expo-brownfield",
|
|
3
|
-
"version": "56.0.
|
|
3
|
+
"version": "56.0.7",
|
|
4
4
|
"description": "Toolkit and APIs for adding brownfield setup to Expo projects",
|
|
5
5
|
"main": "./build/index.js",
|
|
6
6
|
"types": "./build/index.d.ts",
|
|
@@ -40,7 +40,7 @@
|
|
|
40
40
|
"chalk": "^4.1.2",
|
|
41
41
|
"commander": "^14.0.3",
|
|
42
42
|
"diff": "^5.2.0",
|
|
43
|
-
"expo-build-properties": "~56.0.
|
|
43
|
+
"expo-build-properties": "~56.0.7",
|
|
44
44
|
"expo-manifests": "~56.0.1",
|
|
45
45
|
"ora": "^5.4.1",
|
|
46
46
|
"prompts": "^2.4.2"
|
|
@@ -53,15 +53,15 @@
|
|
|
53
53
|
"@types/prompts": "^2.0.6",
|
|
54
54
|
"@types/react": "~19.2.0",
|
|
55
55
|
"glob": "^13.0.6",
|
|
56
|
-
"expo": "56.0.0-preview.7",
|
|
57
56
|
"expo-module-scripts": "56.0.2",
|
|
58
|
-
"
|
|
57
|
+
"expo": "56.0.0-preview.10",
|
|
58
|
+
"create-expo": "3.8.0"
|
|
59
59
|
},
|
|
60
60
|
"peerDependencies": {
|
|
61
61
|
"expo": "*",
|
|
62
62
|
"react": "*"
|
|
63
63
|
},
|
|
64
|
-
"gitHead": "
|
|
64
|
+
"gitHead": "40f0a6f6711d93762e0506b37e6e077e4bd9a541",
|
|
65
65
|
"scripts": {
|
|
66
66
|
"build": "expo-module build",
|
|
67
67
|
"clean": "expo-module clean",
|
|
Binary file
|
|
Binary file
|