vxrn 2.0.0-beta.99.1 → 2.6.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.
Files changed (210) hide show
  1. package/dist/backgroundComputation.mjs +145 -0
  2. package/dist/backgroundComputation.mjs.map +1 -0
  3. package/dist/backgroundComputation.native.js +163 -0
  4. package/dist/backgroundComputation.native.js.map +1 -0
  5. package/dist/backgroundComputationNative.mjs +16 -0
  6. package/dist/backgroundComputationNative.mjs.map +1 -0
  7. package/dist/backgroundComputationNative.native.js +18 -0
  8. package/dist/backgroundComputationNative.native.js.map +1 -0
  9. package/dist/config/getBaseViteConfigOnly.mjs +6 -10
  10. package/dist/config/getBaseViteConfigOnly.mjs.map +1 -1
  11. package/dist/config/getBaseViteConfigOnly.native.js +6 -10
  12. package/dist/config/getBaseViteConfigOnly.native.js.map +1 -1
  13. package/dist/config/getOptimizeDeps.mjs +2 -1
  14. package/dist/config/getOptimizeDeps.mjs.map +1 -1
  15. package/dist/config/getOptimizeDeps.native.js +2 -1
  16. package/dist/config/getOptimizeDeps.native.js.map +1 -1
  17. package/dist/config/getViteServerConfig.mjs +13 -0
  18. package/dist/config/getViteServerConfig.mjs.map +1 -1
  19. package/dist/config/getViteServerConfig.native.js +15 -1
  20. package/dist/config/getViteServerConfig.native.js.map +1 -1
  21. package/dist/constants/defaults.mjs +4 -0
  22. package/dist/constants/defaults.mjs.map +1 -1
  23. package/dist/constants/defaults.native.js +4 -0
  24. package/dist/constants/defaults.native.js.map +1 -1
  25. package/dist/exports/build.native.js +1 -1
  26. package/dist/exports/build.native.js.map +1 -1
  27. package/dist/exports/clean.native.js +1 -1
  28. package/dist/exports/clean.native.js.map +1 -1
  29. package/dist/exports/createServer.native.js +2 -2
  30. package/dist/exports/createServer.native.js.map +1 -1
  31. package/dist/exports/dev.mjs +3 -0
  32. package/dist/exports/dev.mjs.map +1 -1
  33. package/dist/exports/dev.native.js +6 -3
  34. package/dist/exports/dev.native.js.map +1 -1
  35. package/dist/exports/loadEnv.native.js +1 -1
  36. package/dist/exports/loadEnv.native.js.map +1 -1
  37. package/dist/exports/patch.native.js +1 -1
  38. package/dist/exports/patch.native.js.map +1 -1
  39. package/dist/exports/prebuild.mjs +0 -22
  40. package/dist/exports/prebuild.mjs.map +1 -1
  41. package/dist/exports/prebuild.native.js +1 -23
  42. package/dist/exports/prebuild.native.js.map +1 -1
  43. package/dist/exports/prebuildWithoutExpo.mjs +1116 -178
  44. package/dist/exports/prebuildWithoutExpo.mjs.map +1 -1
  45. package/dist/exports/prebuildWithoutExpo.native.js +1592 -243
  46. package/dist/exports/prebuildWithoutExpo.native.js.map +1 -1
  47. package/dist/exports/runAndroid.native.js +1 -1
  48. package/dist/exports/runAndroid.native.js.map +1 -1
  49. package/dist/exports/runIos.native.js +1 -1
  50. package/dist/exports/runIos.native.js.map +1 -1
  51. package/dist/exports/serve.native.js +1 -1
  52. package/dist/exports/serve.native.js.map +1 -1
  53. package/dist/exports/serveStaticAssets.native.js +1 -1
  54. package/dist/exports/serveStaticAssets.native.js.map +1 -1
  55. package/dist/index.js +4 -1
  56. package/dist/index.mjs +4 -1
  57. package/dist/index.native.js +4 -1
  58. package/dist/native-source-contract.mjs +3 -0
  59. package/dist/native-source-contract.native.js +3 -0
  60. package/dist/patches/builtInDepPatches.mjs +0 -13
  61. package/dist/patches/builtInDepPatches.mjs.map +1 -1
  62. package/dist/patches/builtInDepPatches.native.js +0 -13
  63. package/dist/patches/builtInDepPatches.native.js.map +1 -1
  64. package/dist/plugins/autoDepOptimizePlugin.mjs +3 -2
  65. package/dist/plugins/autoDepOptimizePlugin.mjs.map +1 -1
  66. package/dist/plugins/autoDepOptimizePlugin.native.js +6 -3
  67. package/dist/plugins/autoDepOptimizePlugin.native.js.map +1 -1
  68. package/dist/plugins/backgroundComputationPlugin.mjs +27 -0
  69. package/dist/plugins/backgroundComputationPlugin.mjs.map +1 -0
  70. package/dist/plugins/backgroundComputationPlugin.native.js +32 -0
  71. package/dist/plugins/backgroundComputationPlugin.native.js.map +1 -0
  72. package/dist/plugins/reactNativeCommonJsPlugin.native.js +3 -3
  73. package/dist/plugins/reactNativeCommonJsPlugin.native.js.map +1 -1
  74. package/dist/plugins/reactNativeDevAssetPlugin.mjs +2 -1
  75. package/dist/plugins/reactNativeDevAssetPlugin.mjs.map +1 -1
  76. package/dist/plugins/reactNativeDevAssetPlugin.native.js +3 -2
  77. package/dist/plugins/reactNativeDevAssetPlugin.native.js.map +1 -1
  78. package/dist/plugins/reactNativeDevServer.mjs +87 -24
  79. package/dist/plugins/reactNativeDevServer.mjs.map +1 -1
  80. package/dist/plugins/reactNativeDevServer.native.js +162 -45
  81. package/dist/plugins/reactNativeDevServer.native.js.map +1 -1
  82. package/dist/plugins/workletImportsPlugin.mjs +9 -30
  83. package/dist/plugins/workletImportsPlugin.mjs.map +1 -1
  84. package/dist/plugins/workletImportsPlugin.native.js +10 -35
  85. package/dist/plugins/workletImportsPlugin.native.js.map +1 -1
  86. package/dist/rn-commands/bundle/buildBundle.mjs +3 -3
  87. package/dist/rn-commands/bundle/buildBundle.mjs.map +1 -1
  88. package/dist/rn-commands/bundle/buildBundle.native.js +3 -3
  89. package/dist/rn-commands/bundle/buildBundle.native.js.map +1 -1
  90. package/dist/runtime/hmr-client.native.js +3 -7
  91. package/dist/runtime/hmr-client.native.js.map +1 -1
  92. package/dist/runtime/hmr-runtime.native.js +32 -40
  93. package/dist/runtime/hmr-runtime.native.js.map +1 -1
  94. package/dist/serve/node.native.js +2 -2
  95. package/dist/serve/node.native.js.map +1 -1
  96. package/dist/swift-package-id.mjs +3 -0
  97. package/dist/swift-package-id.native.js +3 -0
  98. package/dist/user-interface/index.native.js +6 -6
  99. package/dist/user-interface/index.native.js.map +1 -1
  100. package/dist/utils/compileNativeDevBytecode.mjs +93 -0
  101. package/dist/utils/compileNativeDevBytecode.mjs.map +1 -0
  102. package/dist/utils/compileNativeDevBytecode.native.js +98 -0
  103. package/dist/utils/compileNativeDevBytecode.native.js.map +1 -0
  104. package/dist/utils/createNativeDevEngine.mjs +227 -20
  105. package/dist/utils/createNativeDevEngine.mjs.map +1 -1
  106. package/dist/utils/createNativeDevEngine.native.js +249 -37
  107. package/dist/utils/createNativeDevEngine.native.js.map +1 -1
  108. package/dist/utils/execSync.native.js +1 -1
  109. package/dist/utils/execSync.native.js.map +1 -1
  110. package/dist/utils/getServerEntry.native.js +2 -2
  111. package/dist/utils/getServerEntry.native.js.map +1 -1
  112. package/dist/utils/nativeRun.mjs +32 -16
  113. package/dist/utils/nativeRun.mjs.map +1 -1
  114. package/dist/utils/nativeRun.native.js +43 -18
  115. package/dist/utils/nativeRun.native.js.map +1 -1
  116. package/dist/utils/nativeSourceContract.mjs +1823 -0
  117. package/dist/utils/nativeSourceContract.mjs.map +1 -0
  118. package/dist/utils/nativeSourceContract.native.js +2425 -0
  119. package/dist/utils/nativeSourceContract.native.js.map +1 -0
  120. package/dist/utils/patches.native.js +15 -24
  121. package/dist/utils/patches.native.js.map +1 -1
  122. package/dist/utils/printServerUrls.native.js +1 -1
  123. package/dist/utils/printServerUrls.native.js.map +1 -1
  124. package/dist/utils/scanDepsToOptimize.mjs +7 -1
  125. package/dist/utils/scanDepsToOptimize.mjs.map +1 -1
  126. package/dist/utils/scanDepsToOptimize.native.js +27 -2
  127. package/dist/utils/scanDepsToOptimize.native.js.map +1 -1
  128. package/dist/utils/swiftPackageId.mjs +31 -2
  129. package/dist/utils/swiftPackageId.mjs.map +1 -1
  130. package/dist/utils/swiftPackageId.native.js +50 -2
  131. package/dist/utils/swiftPackageId.native.js.map +1 -1
  132. package/dist/vxrn-vite-plugin.native.js +1 -1
  133. package/dist/vxrn-vite-plugin.native.js.map +1 -1
  134. package/expo-plugin.cjs +792 -2
  135. package/native-project-patches.cjs +201 -10
  136. package/package.json +34 -15
  137. package/src/backgroundComputation.ts +220 -0
  138. package/src/backgroundComputationNative.ts +23 -0
  139. package/src/config/getBaseViteConfigOnly.ts +8 -9
  140. package/src/config/getOptimizeDeps.ts +3 -1
  141. package/src/config/getViteServerConfig.ts +16 -0
  142. package/src/config/glbAssets.test.ts +228 -0
  143. package/src/constants/defaults.ts +6 -0
  144. package/src/exports/dev.ts +5 -1
  145. package/src/exports/expoPlugin.test.mjs +337 -15
  146. package/src/exports/prebuild.ts +0 -31
  147. package/src/exports/prebuildWithoutExpo.test.ts +1588 -48
  148. package/src/exports/prebuildWithoutExpo.ts +1573 -213
  149. package/src/index.ts +8 -0
  150. package/src/native-source-contract.ts +16 -0
  151. package/src/patches/builtInDepPatches.ts +0 -21
  152. package/src/plugins/autoDepOptimizePlugin.ts +7 -2
  153. package/src/plugins/backgroundComputationPlugin.test.ts +77 -0
  154. package/src/plugins/backgroundComputationPlugin.ts +29 -0
  155. package/src/plugins/reactNativeDevAssetPlugin.ts +5 -1
  156. package/src/plugins/reactNativeDevServer.test.ts +74 -0
  157. package/src/plugins/reactNativeDevServer.ts +145 -28
  158. package/src/plugins/rnCodegenPlugin.test.ts +30 -0
  159. package/src/plugins/workletImportsPlugin.ts +19 -37
  160. package/src/rn-commands/bundle/buildBundle.ts +5 -3
  161. package/src/swift-package-id.ts +1 -0
  162. package/src/utils/compileNativeDevBytecode.test.ts +168 -0
  163. package/src/utils/compileNativeDevBytecode.ts +117 -0
  164. package/src/utils/createNativeDevEngine.test.ts +643 -18
  165. package/src/utils/createNativeDevEngine.ts +278 -48
  166. package/src/utils/nativeRun.test.ts +125 -23
  167. package/src/utils/nativeRun.ts +51 -23
  168. package/src/utils/nativeSourceArtifacts.test.ts +39 -0
  169. package/src/utils/nativeSourceContract.test.ts +600 -0
  170. package/src/utils/nativeSourceContract.ts +1897 -0
  171. package/src/utils/scanDepsToOptimize.test.ts +80 -0
  172. package/src/utils/scanDepsToOptimize.ts +18 -7
  173. package/src/utils/swiftPackageId.ts +25 -1
  174. package/types/backgroundComputation.d.ts +28 -0
  175. package/types/backgroundComputation.d.ts.map +1 -0
  176. package/types/backgroundComputationNative.d.ts +2 -0
  177. package/types/backgroundComputationNative.d.ts.map +1 -0
  178. package/types/config/getBaseViteConfigOnly.d.ts.map +1 -1
  179. package/types/config/getOptimizeDeps.d.ts.map +1 -1
  180. package/types/config/getViteServerConfig.d.ts.map +1 -1
  181. package/types/constants/defaults.d.ts.map +1 -1
  182. package/types/exports/dev.d.ts.map +1 -1
  183. package/types/exports/prebuild.d.ts.map +1 -1
  184. package/types/exports/prebuildWithoutExpo.d.ts +6 -0
  185. package/types/exports/prebuildWithoutExpo.d.ts.map +1 -1
  186. package/types/index.d.ts +2 -0
  187. package/types/index.d.ts.map +1 -1
  188. package/types/native-source-contract.d.ts +2 -0
  189. package/types/native-source-contract.d.ts.map +1 -0
  190. package/types/patches/builtInDepPatches.d.ts.map +1 -1
  191. package/types/plugins/autoDepOptimizePlugin.d.ts.map +1 -1
  192. package/types/plugins/backgroundComputationPlugin.d.ts +3 -0
  193. package/types/plugins/backgroundComputationPlugin.d.ts.map +1 -0
  194. package/types/plugins/reactNativeDevAssetPlugin.d.ts.map +1 -1
  195. package/types/plugins/reactNativeDevServer.d.ts.map +1 -1
  196. package/types/plugins/workletImportsPlugin.d.ts.map +1 -1
  197. package/types/rn-commands/bundle/buildBundle.d.ts.map +1 -1
  198. package/types/swift-package-id.d.ts +2 -0
  199. package/types/swift-package-id.d.ts.map +1 -0
  200. package/types/utils/compileNativeDevBytecode.d.ts +3 -0
  201. package/types/utils/compileNativeDevBytecode.d.ts.map +1 -0
  202. package/types/utils/createNativeDevEngine.d.ts +7 -22
  203. package/types/utils/createNativeDevEngine.d.ts.map +1 -1
  204. package/types/utils/nativeRun.d.ts.map +1 -1
  205. package/types/utils/nativeSourceContract.d.ts +121 -0
  206. package/types/utils/nativeSourceContract.d.ts.map +1 -0
  207. package/types/utils/scanDepsToOptimize.d.ts +1 -0
  208. package/types/utils/scanDepsToOptimize.d.ts.map +1 -1
  209. package/types/utils/swiftPackageId.d.ts +1 -0
  210. package/types/utils/swiftPackageId.d.ts.map +1 -1
package/expo-plugin.cjs CHANGED
@@ -3,7 +3,35 @@ const { createRequire } = require('node:module')
3
3
  const path = require('node:path')
4
4
  const nativeProjectPatches = require('./native-project-patches.cjs')
5
5
 
6
- module.exports = function withVxrn(config) {
6
+ // options: { notifications: { push?: boolean, mode?: 'development' | 'production' } }
7
+ // turns on One.Notifications the way native.app.notifications does for one
8
+ // prebuild. mode is the aps-environment, as expo-notifications takes it.
9
+ //
10
+ // options: { updates: { url?: string, runtimeVersion: string } } turns on
11
+ // One.Updates on iOS the way native.app.updates does for one prebuild: the
12
+ // release bundleURL asks the launcher, and the bundle phase writes the
13
+ // embedded manifest. Expo's Android host builds its own ReactHost delegate,
14
+ // which One.Updates cannot re-point, so Android needs one prebuild.
15
+ //
16
+ // options: { holdLaunchScreen: true } keeps the launch storyboard over the
17
+ // react native root until its first content, and MainActivity's content
18
+ // from drawing until then on Android, as one prebuild always does. leave it
19
+ // off while expo-splash-screen owns the splash.
20
+ // options: { location: { whenInUse: string, background?: boolean } } sets the ios
21
+ // location prompt and optional background mode.
22
+ // options: { audio: { microphone?: string, background?: boolean } } sets the
23
+ // ios recording prompt and background audio mode.
24
+ // options: { photoLibrary: { addOnly?: string, readWrite?: string } } sets ios photos prompts.
25
+ // options: { contacts: { usage: string } } sets the ios contacts prompt.
26
+ // options: { calendar: { usage?: string, remindersUsage?: string } } sets EventKit prompts.
27
+ // options: { nativeSource: true } generates the One native-source glue into
28
+ // the Expo prebuilt project the way one prebuild does: a local pod per Swift
29
+ // package under ios/OneSwiftPackages (plus the Podfile glob one prebuild
30
+ // writes) and the Kotlin sources plus their glue under
31
+ // android/app/src/main/java/one/source. Swift and Kotlin imports then bundle
32
+ // the same host modules; without this the bundler keeps failing fast with
33
+ // the one-prebuild error.
34
+ module.exports = function withVxrn(config, options = {}) {
7
35
  const projectRoot = config?._internal?.projectRoot
8
36
  if (!projectRoot) {
9
37
  throw new Error('[vxrn/expo-plugin] Expo config is missing _internal.projectRoot')
@@ -11,14 +39,749 @@ module.exports = function withVxrn(config) {
11
39
 
12
40
  const projectRequire = createRequire(path.join(projectRoot, 'package.json'))
13
41
  const {
42
+ AndroidConfig,
43
+ withAndroidManifest,
14
44
  withAppBuildGradle,
45
+ withAppDelegate,
15
46
  withDangerousMod,
47
+ withEntitlementsPlist,
48
+ withGradleProperties,
49
+ withInfoPlist,
16
50
  withMainActivity,
51
+ withMainApplication,
17
52
  withPlugins,
18
53
  withXcodeProject,
19
54
  } = projectRequire('@expo/config-plugins')
20
55
 
56
+ const notifications = options.notifications
57
+ const location = options.location
58
+ const audio = options.audio
59
+ const photoLibrary = options.photoLibrary
60
+ const contacts = options.contacts
61
+ const calendar = options.calendar
62
+ const nativeSource = options.nativeSource
63
+ if (nativeSource !== undefined && typeof nativeSource !== 'boolean') {
64
+ throw new Error('[vxrn/expo-plugin] nativeSource must be a boolean')
65
+ }
66
+ if (
67
+ location &&
68
+ (typeof location.whenInUse !== 'string' || !location.whenInUse.trim())
69
+ ) {
70
+ throw new Error('[vxrn/expo-plugin] location.whenInUse must be a non-empty string')
71
+ }
72
+ if (location?.background !== undefined && typeof location.background !== 'boolean') {
73
+ throw new Error('[vxrn/expo-plugin] location.background must be a boolean')
74
+ }
75
+ if (audio !== undefined) {
76
+ if (!audio || typeof audio !== 'object') {
77
+ throw new Error('[vxrn/expo-plugin] audio must configure microphone or background playback')
78
+ }
79
+ if (audio.microphone !== undefined &&
80
+ (typeof audio.microphone !== 'string' || !audio.microphone.trim())) {
81
+ throw new Error('[vxrn/expo-plugin] audio.microphone must be a non-empty string')
82
+ }
83
+ if (audio.background !== undefined && typeof audio.background !== 'boolean') {
84
+ throw new Error('[vxrn/expo-plugin] audio.background must be a boolean')
85
+ }
86
+ if (audio.microphone === undefined && audio.background !== true) {
87
+ throw new Error('[vxrn/expo-plugin] audio must configure microphone or background playback')
88
+ }
89
+ }
90
+ if (
91
+ photoLibrary !== undefined &&
92
+ (!photoLibrary ||
93
+ (photoLibrary.addOnly === undefined && photoLibrary.readWrite === undefined))
94
+ ) {
95
+ throw new Error('[vxrn/expo-plugin] photoLibrary must configure addOnly or readWrite')
96
+ }
97
+ if (photoLibrary?.addOnly !== undefined &&
98
+ (typeof photoLibrary.addOnly !== 'string' || !photoLibrary.addOnly.trim())) {
99
+ throw new Error('[vxrn/expo-plugin] photoLibrary.addOnly must be a non-empty string')
100
+ }
101
+ if (photoLibrary?.readWrite !== undefined &&
102
+ (typeof photoLibrary.readWrite !== 'string' || !photoLibrary.readWrite.trim())) {
103
+ throw new Error('[vxrn/expo-plugin] photoLibrary.readWrite must be a non-empty string')
104
+ }
105
+ if (
106
+ contacts !== undefined &&
107
+ (!contacts || typeof contacts.usage !== 'string' || !contacts.usage.trim())
108
+ ) {
109
+ throw new Error('[vxrn/expo-plugin] contacts.usage must be a non-empty string')
110
+ }
111
+ if (calendar !== undefined) {
112
+ if (!calendar || (calendar.usage === undefined && calendar.remindersUsage === undefined)) {
113
+ throw new Error('[vxrn/expo-plugin] calendar.usage or calendar.remindersUsage is required')
114
+ }
115
+ if (calendar.usage !== undefined &&
116
+ (typeof calendar.usage !== 'string' || !calendar.usage.trim())) {
117
+ throw new Error('[vxrn/expo-plugin] calendar.usage must be a non-empty string')
118
+ }
119
+ if (calendar.remindersUsage !== undefined &&
120
+ (typeof calendar.remindersUsage !== 'string' || !calendar.remindersUsage.trim())) {
121
+ throw new Error('[vxrn/expo-plugin] calendar.remindersUsage must be a non-empty string')
122
+ }
123
+ }
124
+ const locationPlugins = !location
125
+ ? []
126
+ : [
127
+ [
128
+ withInfoPlist,
129
+ (nextConfig) => {
130
+ nextConfig.modResults.NSLocationWhenInUseUsageDescription = location.whenInUse
131
+ if (location.background === true) {
132
+ const modes = nextConfig.modResults.UIBackgroundModes || []
133
+ nextConfig.modResults.UIBackgroundModes = [...new Set([...modes, 'location'])]
134
+ }
135
+ return nextConfig
136
+ },
137
+ ],
138
+ [
139
+ withAndroidManifest,
140
+ (nextConfig) => {
141
+ const manifest = nextConfig.modResults.manifest
142
+ manifest['uses-permission'] ??= []
143
+ const names = [
144
+ 'android.permission.ACCESS_COARSE_LOCATION',
145
+ 'android.permission.ACCESS_FINE_LOCATION',
146
+ ]
147
+ if (location.background === true) {
148
+ names.push('android.permission.ACCESS_BACKGROUND_LOCATION')
149
+ }
150
+ for (const name of names) {
151
+ if (!manifest['uses-permission'].some((p) => p.$['android:name'] === name)) {
152
+ manifest['uses-permission'].push({ $: { 'android:name': name } })
153
+ }
154
+ }
155
+ return nextConfig
156
+ },
157
+ ],
158
+ ]
159
+ const audioPlugins = !audio
160
+ ? []
161
+ : [
162
+ [
163
+ withInfoPlist,
164
+ (nextConfig) => {
165
+ if (audio.microphone !== undefined) {
166
+ nextConfig.modResults.NSMicrophoneUsageDescription = audio.microphone
167
+ }
168
+ if (audio.background === true) {
169
+ const modes = nextConfig.modResults.UIBackgroundModes || []
170
+ nextConfig.modResults.UIBackgroundModes = [...new Set([...modes, 'audio'])]
171
+ }
172
+ return nextConfig
173
+ },
174
+ ],
175
+ ]
176
+ const photoLibraryPlugins = !photoLibrary
177
+ ? []
178
+ : [
179
+ [
180
+ withInfoPlist,
181
+ (nextConfig) => {
182
+ if (photoLibrary.addOnly !== undefined)
183
+ nextConfig.modResults.NSPhotoLibraryAddUsageDescription = photoLibrary.addOnly
184
+ if (photoLibrary.readWrite !== undefined) {
185
+ nextConfig.modResults.NSPhotoLibraryUsageDescription = photoLibrary.readWrite
186
+ nextConfig.modResults.PHPhotoLibraryPreventAutomaticLimitedAccessAlert = true
187
+ }
188
+ return nextConfig
189
+ },
190
+ ],
191
+ ]
192
+ const contactsPlugins = !contacts
193
+ ? []
194
+ : [
195
+ [
196
+ withInfoPlist,
197
+ (nextConfig) => {
198
+ nextConfig.modResults.NSContactsUsageDescription = contacts.usage
199
+ return nextConfig
200
+ },
201
+ ],
202
+ ]
203
+ const calendarPlugins = !calendar
204
+ ? []
205
+ : [
206
+ [
207
+ withInfoPlist,
208
+ (nextConfig) => {
209
+ if (calendar.usage) {
210
+ nextConfig.modResults.NSCalendarsFullAccessUsageDescription = calendar.usage
211
+ }
212
+ if (calendar.remindersUsage) {
213
+ nextConfig.modResults.NSRemindersFullAccessUsageDescription = calendar.remindersUsage
214
+ }
215
+ return nextConfig
216
+ },
217
+ ],
218
+ ]
219
+ // mirrors one prebuild's Android media stamps: the same permissions,
220
+ // meta-data markers and audio service from the same option fields.
221
+ const mediaPlugins =
222
+ audio === undefined &&
223
+ photoLibrary === undefined &&
224
+ contacts === undefined &&
225
+ calendar === undefined
226
+ ? []
227
+ : [
228
+ [
229
+ withAndroidManifest,
230
+ (nextConfig) => {
231
+ const manifest = nextConfig.modResults.manifest
232
+ manifest['uses-permission'] ??= []
233
+ const ensurePermission = (name, maxSdkVersion) => {
234
+ if (
235
+ manifest['uses-permission'].some((p) => p.$['android:name'] === name)
236
+ )
237
+ return
238
+ manifest['uses-permission'].push({
239
+ $: {
240
+ 'android:name': name,
241
+ ...(maxSdkVersion === undefined
242
+ ? {}
243
+ : { 'android:maxSdkVersion': String(maxSdkVersion) }),
244
+ },
245
+ })
246
+ }
247
+ if (audio?.microphone !== undefined)
248
+ ensurePermission('android.permission.RECORD_AUDIO')
249
+ if (photoLibrary?.readWrite !== undefined) {
250
+ ensurePermission('android.permission.READ_MEDIA_IMAGES')
251
+ ensurePermission('android.permission.READ_MEDIA_VIDEO')
252
+ ensurePermission('android.permission.READ_EXTERNAL_STORAGE', 32)
253
+ }
254
+ if (photoLibrary?.addOnly !== undefined)
255
+ ensurePermission('android.permission.WRITE_EXTERNAL_STORAGE', 28)
256
+ if (contacts !== undefined) {
257
+ ensurePermission('android.permission.READ_CONTACTS')
258
+ ensurePermission('android.permission.WRITE_CONTACTS')
259
+ }
260
+ if (calendar?.usage !== undefined) {
261
+ ensurePermission('android.permission.READ_CALENDAR')
262
+ ensurePermission('android.permission.WRITE_CALENDAR')
263
+ }
264
+ if (audio?.background === true) {
265
+ ensurePermission('android.permission.FOREGROUND_SERVICE')
266
+ ensurePermission('android.permission.FOREGROUND_SERVICE_MEDIA_PLAYBACK')
267
+ }
268
+ const application =
269
+ AndroidConfig.Manifest.getMainApplicationOrThrow(nextConfig.modResults)
270
+ const ensureMetaData = (name) => {
271
+ application['meta-data'] = (application['meta-data'] ?? []).filter(
272
+ (entry) => entry.$['android:name'] !== name
273
+ )
274
+ application['meta-data'].push({
275
+ $: { 'android:name': name, 'android:value': 'true' },
276
+ })
277
+ }
278
+ if (photoLibrary?.addOnly !== undefined)
279
+ ensureMetaData('one.photoLibrary.addOnly')
280
+ if (audio?.background === true) {
281
+ ensureMetaData('one.audio.background')
282
+ const serviceName = 'com.margelo.nitro.one.OneAudioService'
283
+ application['service'] = (application['service'] ?? []).filter(
284
+ (entry) => entry.$['android:name'] !== serviceName
285
+ )
286
+ application['service'].push({
287
+ $: {
288
+ 'android:name': serviceName,
289
+ 'android:exported': 'false',
290
+ 'android:foregroundServiceType': 'mediaPlayback',
291
+ },
292
+ })
293
+ }
294
+ return nextConfig
295
+ },
296
+ ],
297
+ ]
298
+ const host = nativeProjectPatches.ONE_NOTIFICATIONS
299
+ const notificationPlugins = !notifications
300
+ ? []
301
+ : [
302
+ [
303
+ withInfoPlist,
304
+ (nextConfig) => {
305
+ nextConfig.modResults[host.enabledInfoPlistKey] = true
306
+ if (notifications.push) nextConfig.modResults[host.pushInfoPlistKey] = true
307
+ return nextConfig
308
+ },
309
+ ],
310
+ [
311
+ withEntitlementsPlist,
312
+ (nextConfig) => {
313
+ if (notifications.push) {
314
+ nextConfig.modResults['aps-environment'] =
315
+ notifications.mode || host.apsEnvironment
316
+ }
317
+ return nextConfig
318
+ },
319
+ ],
320
+ [
321
+ withAndroidManifest,
322
+ (nextConfig) => {
323
+ const manifest = nextConfig.modResults.manifest
324
+ manifest['uses-permission'] ??= []
325
+ for (const name of host.androidPermissions) {
326
+ if (!manifest['uses-permission'].some((p) => p.$['android:name'] === name)) {
327
+ manifest['uses-permission'].push({ $: { 'android:name': name } })
328
+ }
329
+ }
330
+ const application =
331
+ AndroidConfig.Manifest.getMainApplicationOrThrow(nextConfig.modResults)
332
+ const component = (kind, name, action) => {
333
+ application[kind] = (application[kind] ?? []).filter(
334
+ (entry) => entry.$['android:name'] !== name
335
+ )
336
+ application[kind].push({
337
+ $: { 'android:name': name, 'android:exported': 'false' },
338
+ 'intent-filter': [{ action: [{ $: { 'android:name': action } }] }],
339
+ })
340
+ }
341
+ component('receiver', host.receiver, host.receiverAction)
342
+ if (notifications.push) {
343
+ component('service', host.pushService, host.pushServiceAction)
344
+ }
345
+ return nextConfig
346
+ },
347
+ ],
348
+ [
349
+ withGradleProperties,
350
+ (nextConfig) => {
351
+ if (!notifications.push) return nextConfig
352
+ const properties = nextConfig.modResults.filter(
353
+ (item) => !(item.type === 'property' && item.key === host.pushGradleProperty)
354
+ )
355
+ properties.push({ type: 'property', key: host.pushGradleProperty, value: 'true' })
356
+ nextConfig.modResults = properties
357
+ return nextConfig
358
+ },
359
+ ],
360
+ ]
361
+
362
+ // appends one import to the app's bridging header, which is how the app
363
+ // target reaches One's c entry points: it cannot import the One module.
364
+ const importIntoBridgingHeader = (option, importLine) => [
365
+ withDangerousMod,
366
+ [
367
+ 'ios',
368
+ (nextConfig) => {
369
+ const { platformProjectRoot, projectName } = nextConfig.modRequest
370
+ const header = path.join(
371
+ platformProjectRoot,
372
+ projectName,
373
+ `${projectName}-Bridging-Header.h`
374
+ )
375
+ if (!fs.existsSync(header)) {
376
+ throw new Error(
377
+ `[vxrn/expo-plugin] ${option}: expected the app's bridging header at ${header}`
378
+ )
379
+ }
380
+ const contents = fs.readFileSync(header, 'utf8')
381
+ if (!contents.includes(importLine)) {
382
+ fs.writeFileSync(header, `${contents.trimEnd()}\n${importLine}\n`)
383
+ }
384
+ return nextConfig
385
+ },
386
+ ],
387
+ ]
388
+
389
+ // the built contract parser; the src twin ships too but node cannot run
390
+ // typescript, so Expo prebuild reads the same dist build the bundler uses.
391
+ const loadNativeSourceContract = async () => {
392
+ try {
393
+ const [contract, ids] = await Promise.all([
394
+ import('vxrn/native-source-contract'),
395
+ import('vxrn/swift-package-id'),
396
+ ])
397
+ return { ...contract, ...ids }
398
+ } catch (error) {
399
+ throw new Error(
400
+ `[vxrn/expo-plugin] nativeSource: cannot load the vxrn native-source-contract build: ${error.message}`
401
+ )
402
+ }
403
+ }
404
+
405
+ // mirrors one prebuild's generateSwiftPackages, but copies (never symlinks:
406
+ // eas uploads and expo merges must stay self-contained) into the Expo ios
407
+ // project. the podspec and Register.m bodies match one prebuild so the same
408
+ // sources compile to the same pod.
409
+ const writeExpoSwiftPackages = ({ platformProjectRoot, contract }) => {
410
+ const skip = new Set(['node_modules', 'ios', 'android', 'dist', 'types', 'build'])
411
+ const packages = contract.swiftPackageDirectories(projectRoot)
412
+ for (const [id, packageDir] of packages) {
413
+ const manifest = contract.swiftPodManifest(
414
+ path.join(packageDir, 'Package.swift'),
415
+ fs.readFileSync(path.join(packageDir, 'Package.swift'), 'utf8')
416
+ )
417
+ const podDir = path.join(platformProjectRoot, 'OneSwiftPackages', id)
418
+ fs.rmSync(podDir, { recursive: true, force: true })
419
+ const sources = []
420
+ const collect = (dir) => {
421
+ for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {
422
+ if (entry.name.startsWith('.') || skip.has(entry.name)) continue
423
+ const full = path.join(dir, entry.name)
424
+ if (entry.isDirectory()) collect(full)
425
+ else if (entry.name.endsWith('.swift') && entry.name !== 'Package.swift')
426
+ sources.push(full)
427
+ }
428
+ }
429
+ collect(packageDir)
430
+ const artifacts = contract.writeSwiftPackageArtifacts(packageDir)
431
+ const contracts = artifacts.contracts
432
+ const hasView = contracts.some((entry) => entry.defaultView)
433
+ if (contracts.filter((entry) => entry.defaultView).length > 1) {
434
+ throw new Error(
435
+ `[vxrn/expo-plugin] nativeSource: swift package ${packageDir} has more than one @main type`
436
+ )
437
+ }
438
+ for (const source of sources) {
439
+ const target = path.join(podDir, 'Sources', path.relative(packageDir, source))
440
+ fs.mkdirSync(path.dirname(target), { recursive: true })
441
+ fs.copyFileSync(source, target)
442
+ }
443
+ if (artifacts.glue) {
444
+ fs.writeFileSync(
445
+ path.join(podDir, 'Sources', 'OneNativeSource.generated.swift'),
446
+ artifacts.glue
447
+ )
448
+ }
449
+ fs.writeFileSync(
450
+ path.join(podDir, `${id}.podspec`),
451
+ `Pod::Spec.new do |s|
452
+ s.name = '${id}'
453
+ s.version = '0.0.0'
454
+ s.summary = 'swift package ${path.relative(projectRoot, packageDir) || '.'}'
455
+ s.homepage = 'https://onestack.dev'
456
+ s.license = 'MIT'
457
+ s.author = 'one'
458
+ s.source = { :path => '.' }
459
+ s.swift_version = '${manifest.languageMode}.0'
460
+ s.source_files = 'Sources/**/*.swift', 'Register.m'
461
+ s.dependency 'One'
462
+ s.pod_target_xcconfig = {
463
+ 'OTHER_SWIFT_FLAGS' => '$(inherited) -cxx-interoperability-mode=default -Xcc -std=c++20 -Xfrontend -import-module -Xfrontend One${manifest.mainActorIsolation ? ' -default-isolation MainActor' : ''}${hasView ? ` -Xfrontend -entry-point-function-name -Xfrontend ${id}_main` : ''}',
464
+ }
465
+ end
466
+ `
467
+ )
468
+ fs.writeFileSync(
469
+ path.join(podDir, 'Register.m'),
470
+ hasView ? `#import <Foundation/Foundation.h>
471
+
472
+ extern int ${id}_main(int argc, char **argv);
473
+ extern void OneSwiftRegisterPackage(const char *name, int (*entry)(int, char **));
474
+
475
+ @interface OneSwiftPackage_${id} : NSObject
476
+ @end
477
+
478
+ @implementation OneSwiftPackage_${id}
479
+ + (void)load {
480
+ OneSwiftRegisterPackage("${id}", ${id}_main);
481
+ }
482
+ @end
483
+ ` : '#import <Foundation/Foundation.h>\n'
484
+ )
485
+ }
486
+ return packages.size
487
+ }
488
+
489
+ const insertAfterLine = (haystack, anchor, insertion) => {
490
+ const anchorIndex = haystack.indexOf(anchor)
491
+ if (anchorIndex === -1) return haystack
492
+ const lineEnd = haystack.indexOf('\n', anchorIndex)
493
+ if (lineEnd === -1) return `${haystack}\n${insertion}`
494
+ return `${haystack.slice(0, lineEnd + 1)}${insertion}\n${haystack.slice(lineEnd + 1)}`
495
+ }
496
+
497
+ // mirrors one prebuild's kotlin import artifact rules: each import prefix
498
+ // names the gradle artifact that supplies it, so an imported source
499
+ // compiles without the app declaring the dependency.
500
+ const kotlinImportArtifacts = [
501
+ {
502
+ importPrefix: 'androidx.compose.material.icons',
503
+ artifact: 'androidx.compose.material:material-icons-core',
504
+ versionKey: 'materialIconsVersion',
505
+ version: '1.7.6',
506
+ },
507
+ ]
508
+
509
+ const kotlinSourceImports = (dir) => {
510
+ const imports = new Set()
511
+ if (!fs.existsSync(dir)) return imports
512
+ const visit = (entry) => {
513
+ if (fs.statSync(entry).isDirectory()) {
514
+ for (const child of fs.readdirSync(entry)) visit(path.join(entry, child))
515
+ return
516
+ }
517
+ if (!entry.endsWith('.kt')) return
518
+ for (const line of fs.readFileSync(entry, 'utf8').split('\n')) {
519
+ const match = /^\s*import\s+([A-Za-z0-9_.]+)/.exec(line)
520
+ if (match) imports.add(match[1])
521
+ }
522
+ }
523
+ visit(dir)
524
+ return imports
525
+ }
526
+
527
+ // mirrors one prebuild's enableAppComposeIntegration on the Expo android
528
+ // project. one prebuild owns its template so its anchors always hit; the
529
+ // Expo template may differ, so a missed anchor fails fast with the manual
530
+ // lines instead of leaving views uncompilable.
531
+ const enableExpoComposeIntegration = (androidRoot) => {
532
+ const manual =
533
+ 'add the compose gradle plugin, buildFeatures { compose true }, and the androidx.compose dependencies from one prebuild'
534
+ const rootBuildGradlePath = path.join(androidRoot, 'build.gradle')
535
+ if (fs.existsSync(rootBuildGradlePath)) {
536
+ let content = fs.readFileSync(rootBuildGradlePath, 'utf8')
537
+ const composePluginClasspath =
538
+ ' classpath("org.jetbrains.kotlin.plugin.compose:org.jetbrains.kotlin.plugin.compose.gradle.plugin:$kotlinVersion")'
539
+ if (!content.includes('org.jetbrains.kotlin.plugin.compose')) {
540
+ const anchor = content.includes('classpath("org.jetbrains.kotlin:kotlin-gradle-plugin")')
541
+ ? 'classpath("org.jetbrains.kotlin:kotlin-gradle-plugin")'
542
+ : 'classpath "org.jetbrains.kotlin:kotlin-gradle-plugin"'
543
+ if (!content.includes(anchor)) {
544
+ throw new Error(`[vxrn/expo-plugin] nativeSource: no kotlin classpath anchor in android/build.gradle; ${manual}`)
545
+ }
546
+ content = insertAfterLine(content, anchor, composePluginClasspath)
547
+ fs.writeFileSync(rootBuildGradlePath, content, 'utf8')
548
+ }
549
+ }
550
+ const appBuildGradlePath = path.join(androidRoot, 'app/build.gradle')
551
+ if (!fs.existsSync(appBuildGradlePath)) {
552
+ throw new Error(`[vxrn/expo-plugin] nativeSource: expected app/build.gradle; ${manual}`)
553
+ }
554
+ let content = fs.readFileSync(appBuildGradlePath, 'utf8')
555
+ if (!content.includes('org.jetbrains.kotlin.plugin.compose')) {
556
+ const pluginAnchor = content.includes('apply plugin: "org.jetbrains.kotlin.android"')
557
+ ? 'apply plugin: "org.jetbrains.kotlin.android"'
558
+ : "apply plugin: 'org.jetbrains.kotlin.android'"
559
+ if (!content.includes(pluginAnchor)) {
560
+ throw new Error(`[vxrn/expo-plugin] nativeSource: no kotlin android plugin anchor in app/build.gradle; ${manual}`)
561
+ }
562
+ content = insertAfterLine(
563
+ content,
564
+ pluginAnchor,
565
+ 'apply plugin: "org.jetbrains.kotlin.plugin.compose"'
566
+ )
567
+ }
568
+ if (!content.includes('compose true')) {
569
+ if (!content.includes('android {')) {
570
+ throw new Error(`[vxrn/expo-plugin] nativeSource: no android block anchor in app/build.gradle; ${manual}`)
571
+ }
572
+ content = insertAfterLine(
573
+ content,
574
+ 'android {',
575
+ ' buildFeatures {\n compose true\n }'
576
+ )
577
+ }
578
+ if (!content.includes('androidx.compose.ui:ui')) {
579
+ const composeDeps = [
580
+ ' def composeUiVersion = rootProject.ext.has("composeUiVersion") ? rootProject.ext.get("composeUiVersion") : "1.11.4"',
581
+ ' def material3Version = rootProject.ext.has("material3Version") ? rootProject.ext.get("material3Version") : "1.5.0-alpha17"',
582
+ ' implementation "androidx.compose.ui:ui:$composeUiVersion"',
583
+ ' implementation "androidx.compose.foundation:foundation:$composeUiVersion"',
584
+ ' implementation "androidx.compose.material3:material3:$material3Version"',
585
+ ' def navigationComposeVersion = rootProject.ext.has("navigationComposeVersion") ? rootProject.ext.get("navigationComposeVersion") : "2.10.2"',
586
+ ' implementation "androidx.navigation:navigation-compose:$navigationComposeVersion"',
587
+ ].join('\n')
588
+ const depsAnchor = content.includes('implementation("com.facebook.react:react-android")')
589
+ ? 'implementation("com.facebook.react:react-android")'
590
+ : 'implementation "com.facebook.react:react-android"'
591
+ if (!content.includes(depsAnchor)) {
592
+ throw new Error(`[vxrn/expo-plugin] nativeSource: no react-android dependency anchor in app/build.gradle; ${manual}`)
593
+ }
594
+ content = insertAfterLine(content, depsAnchor, composeDeps)
595
+ }
596
+ // each import rule carries its own guard, so a re-prebuild adds a newly
597
+ // needed artifact without touching the rest of the file.
598
+ const imports = kotlinSourceImports(path.join(androidRoot, 'app/src/main/java/one/source'))
599
+ for (const rule of kotlinImportArtifacts) {
600
+ const needed = [...imports].some(
601
+ (name) => name === rule.importPrefix || name.startsWith(`${rule.importPrefix}.`)
602
+ )
603
+ if (!needed || content.includes(rule.artifact)) continue
604
+ const reactAnchor = content.includes('implementation("com.facebook.react:react-android")')
605
+ ? 'implementation("com.facebook.react:react-android")'
606
+ : 'implementation "com.facebook.react:react-android"'
607
+ const anchor = content.includes('androidx.compose.material3:material3')
608
+ ? 'androidx.compose.material3:material3'
609
+ : reactAnchor
610
+ if (!content.includes(anchor)) {
611
+ throw new Error(`[vxrn/expo-plugin] nativeSource: no dependency anchor for ${rule.artifact} in app/build.gradle; ${manual}`)
612
+ }
613
+ content = insertAfterLine(
614
+ content,
615
+ anchor,
616
+ [
617
+ ` def ${rule.versionKey} = rootProject.ext.has("${rule.versionKey}") ? rootProject.ext.get("${rule.versionKey}") : "${rule.version}"`,
618
+ ` implementation "${rule.artifact}:$${rule.versionKey}"`,
619
+ ].join('\n')
620
+ )
621
+ }
622
+ fs.writeFileSync(appBuildGradlePath, content, 'utf8')
623
+ }
624
+
625
+ // mirrors one prebuild's generateKotlinSources collection rule (a Kotlin
626
+ // file anywhere outside the skipped dirs and non-workspace nested
627
+ // packages) into the Expo android project. the generator owns
628
+ // app/src/main/java/one/source and rebuilds it, so a moved or deleted
629
+ // source cannot leave a stale copy behind.
630
+ const writeExpoKotlinSources = ({ platformProjectRoot, contract }) => {
631
+ const skip = new Set(['node_modules', 'ios', 'android', 'dist', 'types', 'build', 'tests', '__tests__', 'scripts', 'evidence'])
632
+ const sources = []
633
+ const manifest = fs.existsSync(path.join(projectRoot, 'package.json'))
634
+ ? JSON.parse(fs.readFileSync(path.join(projectRoot, 'package.json'), 'utf8'))
635
+ : {}
636
+ const workspacePatterns = Array.isArray(manifest.workspaces)
637
+ ? manifest.workspaces
638
+ : manifest.workspaces?.packages ?? []
639
+ const fastGlob = require('fast-glob')
640
+ const workspaceRoots = new Set(fastGlob.sync(workspacePatterns, {
641
+ cwd: projectRoot, absolute: true, onlyDirectories: true,
642
+ ignore: ['**/node_modules/**', '**/ios/**', '**/android/**'],
643
+ }).map((directory) => path.resolve(directory)))
644
+ const collect = (dir) => {
645
+ if (dir !== projectRoot && fs.existsSync(path.join(dir, 'package.json')) && !workspaceRoots.has(path.resolve(dir))) return
646
+ for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {
647
+ if (entry.name.startsWith('.') || skip.has(entry.name)) continue
648
+ const source = path.join(dir, entry.name)
649
+ if (entry.isDirectory()) collect(source)
650
+ else if (entry.isFile() && entry.name.endsWith('.kt')) sources.push(source)
651
+ }
652
+ }
653
+ collect(projectRoot)
654
+ const output = path.join(platformProjectRoot, 'app/src/main/java/one/source')
655
+ fs.rmSync(output, { recursive: true, force: true })
656
+ if (sources.length === 0) return { sources: 0, hasViews: false }
657
+ let hasViews = false
658
+ for (const source of sources) {
659
+ const id = contract.kotlinSourceId(projectRoot, source)
660
+ const target = path.join(output, id)
661
+ fs.mkdirSync(target, { recursive: true })
662
+ fs.copyFileSync(source, path.join(target, path.basename(source)))
663
+ const parsed = contract.writeNativeSourceDeclaration(source)
664
+ if (parsed.views.length > 0) hasViews = true
665
+ if (parsed.modules.length > 0 || parsed.views.length > 0) {
666
+ fs.writeFileSync(
667
+ path.join(target, `OneNativeSource_${id}.kt`),
668
+ contract.renderKotlinSourceGlue(id, parsed).source
669
+ )
670
+ }
671
+ }
672
+ if (hasViews) enableExpoComposeIntegration(platformProjectRoot)
673
+ return { sources: sources.length, hasViews }
674
+ }
675
+
676
+ const nativeSourcePlugins = !nativeSource
677
+ ? []
678
+ : [
679
+ [
680
+ withDangerousMod,
681
+ [
682
+ 'ios',
683
+ async (nextConfig) => {
684
+ const { platformProjectRoot } = nextConfig.modRequest
685
+ const contract = await loadNativeSourceContract()
686
+ const count = writeExpoSwiftPackages({ platformProjectRoot, contract })
687
+ if (count > 0) {
688
+ const podfilePath = path.join(platformProjectRoot, 'Podfile')
689
+ if (fs.existsSync(podfilePath)) {
690
+ let podfile = fs.readFileSync(podfilePath, 'utf8')
691
+ podfile = nativeProjectPatches.injectOneSwiftPackagesIntoPodfile(podfile)
692
+ fs.writeFileSync(podfilePath, podfile, 'utf8')
693
+ }
694
+ }
695
+ return nextConfig
696
+ },
697
+ ],
698
+ ],
699
+ [
700
+ withDangerousMod,
701
+ [
702
+ 'android',
703
+ async (nextConfig) => {
704
+ const { platformProjectRoot } = nextConfig.modRequest
705
+ const contract = await loadNativeSourceContract()
706
+ writeExpoKotlinSources({ platformProjectRoot, contract })
707
+ return nextConfig
708
+ },
709
+ ],
710
+ ],
711
+ ]
712
+
713
+ const updates = options.updates
714
+ if (updates && (typeof updates.runtimeVersion !== 'string' || !updates.runtimeVersion)) {
715
+ throw new Error('[vxrn/expo-plugin] updates.runtimeVersion must be a non-empty string')
716
+ }
717
+ const updatesHost = nativeProjectPatches.ONE_UPDATES
718
+ const updatesPlugins = !updates
719
+ ? []
720
+ : [
721
+ [
722
+ withInfoPlist,
723
+ (nextConfig) => {
724
+ // without the url the embedded bundle launches with updates
725
+ // disabled, as in one prebuild.
726
+ if (updates.url !== undefined) {
727
+ nextConfig.modResults[updatesHost.urlInfoPlistKey] = updates.url
728
+ }
729
+ nextConfig.modResults[updatesHost.runtimeVersionInfoPlistKey] =
730
+ updates.runtimeVersion
731
+ return nextConfig
732
+ },
733
+ ],
734
+ [
735
+ withAppDelegate,
736
+ (nextConfig) => {
737
+ nextConfig.modResults.contents =
738
+ nativeProjectPatches.pointReleaseBundleURLAtOneUpdates(
739
+ nextConfig.modResults.contents
740
+ )
741
+ return nextConfig
742
+ },
743
+ ],
744
+ importIntoBridgingHeader('updates', updatesHost.bridgingHeaderImport),
745
+ [
746
+ withMainActivity,
747
+ () => {
748
+ throw new Error(
749
+ '[vxrn/expo-plugin] updates: One.Updates on Android needs one prebuild; Expo prebuild supports it on iOS only'
750
+ )
751
+ },
752
+ ],
753
+ ]
754
+
755
+ const launchScreenPlugins = !options.holdLaunchScreen
756
+ ? []
757
+ : [
758
+ [
759
+ withAppDelegate,
760
+ (nextConfig) => {
761
+ nextConfig.modResults.contents =
762
+ nativeProjectPatches.holdLaunchScreenOverRootView(
763
+ nextConfig.modResults.contents
764
+ )
765
+ return nextConfig
766
+ },
767
+ ],
768
+ importIntoBridgingHeader(
769
+ 'holdLaunchScreen',
770
+ nativeProjectPatches.ONE_LAUNCH_SCREEN.bridgingHeaderImport
771
+ ),
772
+ ]
773
+
21
774
  return withPlugins(config, [
775
+ ...locationPlugins,
776
+ ...audioPlugins,
777
+ ...photoLibraryPlugins,
778
+ ...contactsPlugins,
779
+ ...calendarPlugins,
780
+ ...mediaPlugins,
781
+ ...notificationPlugins,
782
+ ...updatesPlugins,
783
+ ...launchScreenPlugins,
784
+ ...nativeSourcePlugins,
22
785
  [
23
786
  withXcodeProject,
24
787
  (nextConfig) => {
@@ -34,6 +797,12 @@ module.exports = function withVxrn(config) {
34
797
  script = nativeProjectPatches.addSetCliPathToBundleReactNativeShellScript(script)
35
798
  script = nativeProjectPatches.addPodHermescToBundleReactNativeShellScript(script)
36
799
  script = nativeProjectPatches.addDepsPatchToBundleReactNativeShellScript(script)
800
+ if (updates) {
801
+ script = nativeProjectPatches.addEmbeddedUpdatesManifestToBundleReactNativeShellScript(
802
+ script,
803
+ updates.runtimeVersion
804
+ )
805
+ }
37
806
  phase.shellScript = JSON.stringify(script)
38
807
  return nextConfig
39
808
  },
@@ -49,12 +818,32 @@ module.exports = function withVxrn(config) {
49
818
  return nextConfig
50
819
  },
51
820
  ],
821
+ [
822
+ withMainApplication,
823
+ (nextConfig) => {
824
+ const anchor = 'context = applicationContext,'
825
+ const contents = nextConfig.modResults.contents
826
+ if (!contents.includes('ExpoReactHostFactory.getDefaultReactHost(') || !contents.includes(anchor)) {
827
+ throw new Error('[vxrn/expo-plugin] expected ExpoReactHostFactory in MainApplication')
828
+ }
829
+ const configured = `${anchor}\n jsMainModulePath = "index",\n useDevSupport = BuildConfig.DEBUG,`
830
+ // the prebuilt react library has DEBUG=false. use the application build
831
+ // flag and One's entry instead of Expo's virtual Metro entry.
832
+ nextConfig.modResults.contents = contents.includes(configured)
833
+ ? contents
834
+ : contents.replace(anchor, configured)
835
+ return nextConfig
836
+ },
837
+ ],
52
838
  [
53
839
  withMainActivity,
54
840
  (nextConfig) => {
55
- nextConfig.modResults.contents = nativeProjectPatches.addReactNativeScreensFix(
841
+ const contents = nativeProjectPatches.addReactNativeScreensFix(
56
842
  nextConfig.modResults.contents
57
843
  )
844
+ nextConfig.modResults.contents = options.holdLaunchScreen
845
+ ? nativeProjectPatches.holdLaunchScreenInMainActivity(contents)
846
+ : contents
58
847
  return nextConfig
59
848
  },
60
849
  ],
@@ -71,6 +860,7 @@ module.exports = function withVxrn(config) {
71
860
 
72
861
  let podfile = fs.readFileSync(podfilePath, 'utf8')
73
862
  podfile = nativeProjectPatches.injectFmtCxx17FixIntoPodfile(podfile)
863
+ podfile = nativeProjectPatches.injectWebGPUHeaderPathsIntoPodfile(podfile)
74
864
  podfile = nativeProjectPatches.injectHermesMinificationPatchIntoPodfile(podfile)
75
865
  podfile = nativeProjectPatches.injectReactNativeScreensGammaIntoPodfile(podfile)
76
866
  if (nativeProjectPatches.hasNitroWebImage(projectRoot)) {