vxrn 2.0.0-beta.98.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 +1133 -182
  44. package/dist/exports/prebuildWithoutExpo.mjs.map +1 -1
  45. package/dist/exports/prebuildWithoutExpo.native.js +1606 -244
  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 +1623 -46
  148. package/src/exports/prebuildWithoutExpo.ts +1606 -216
  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
@@ -1,13 +1,19 @@
1
1
  import { execFileSync } from "node:child_process";
2
+ import { createHash } from "node:crypto";
2
3
  import module from "node:module";
3
4
  import path from "node:path";
4
5
  import { pathToFileURL } from "node:url";
5
6
  import { validateNativeApp } from "@vxrn/utils/nativeAppManifest";
6
7
  import FSExtra from "fs-extra";
7
8
  import sharp from "sharp";
8
- import { swiftPackageId } from "../utils/swiftPackageId.mjs";
9
+ import { swiftPackageDirectories } from "../utils/swiftPackageId.mjs";
9
10
 
10
- const nativeProjectPatches = module.createRequire(import.meta.url)("../../native-project-patches.cjs");
11
+ const requireVxrn = module.createRequire(import.meta.url);
12
+ const nativeProjectPatches = requireVxrn("../../native-project-patches.cjs");
13
+ const notificationsHost = nativeProjectPatches.ONE_NOTIFICATIONS;
14
+ const updatesHost = nativeProjectPatches.ONE_UPDATES;
15
+ const PORTRAIT_ORIENTATIONS = ["UIInterfaceOrientationPortrait", "UIInterfaceOrientationPortraitUpsideDown"];
16
+ const LANDSCAPE_ORIENTATIONS = ["UIInterfaceOrientationLandscapeLeft", "UIInterfaceOrientationLandscapeRight"];
11
17
  const validatePrebuildApp = validateNativeApp;
12
18
  const ANDROID_DENSITIES = {
13
19
  mdpi: 1,
@@ -19,7 +25,7 @@ const ANDROID_DENSITIES = {
19
25
  const IOS_BUNDLE_PLACEHOLDER = "org.reactjs.native.example.$(PRODUCT_NAME:rfc1034identifier)";
20
26
  const ANDROID_PACKAGE_PLACEHOLDER = "com.helloworld";
21
27
  const ANDROID_PACKAGE_PATH = "com/helloworld";
22
- function patchIosBundlePhase(project) {
28
+ function patchIosBundlePhase(project, updatesRuntimeVersion) {
23
29
  let patchedBundlePhases = 0;
24
30
  const patchedProject = project.replace(/shellScript = ("(?:\\.|[^"\\])*");/g, (assignment, serializedScript) => {
25
31
  let script;
@@ -38,6 +44,12 @@ function patchIosBundlePhase(project) {
38
44
  if (!patched.includes("[vxrn/one] React Native now defaults CLI_PATH") || !patched.includes("[vxrn/one] use the hermes-engine pod") || !patched.includes("[vxrn/one] ensure patches are applied")) {
39
45
  throw new Error("[vxrn] failed to apply required iOS bundle phase patches");
40
46
  }
47
+ if (updatesRuntimeVersion !== void 0) {
48
+ patched = nativeProjectPatches.addEmbeddedUpdatesManifestToBundleReactNativeShellScript(patched, updatesRuntimeVersion);
49
+ if (!patched.includes("[vxrn/one] the embedded update manifest lands beside the release bundle")) {
50
+ throw new Error("[vxrn] failed to apply the iOS embedded updates manifest patch");
51
+ }
52
+ }
41
53
  patchedBundlePhases++;
42
54
  return `shellScript = ${JSON.stringify(patched)};`;
43
55
  });
@@ -64,21 +76,14 @@ import ReactAppDependencyProvider
64
76
  class SceneDelegate: UIResponder, UIWindowSceneDelegate {
65
77
  var window: UIWindow?
66
78
 
67
- var reactNativeDelegate: ReactNativeDelegate?
68
- var reactNativeFactory: RCTReactNativeFactory?
69
-
70
79
  func scene(
71
80
  _ scene: UIScene,
72
81
  willConnectTo session: UISceneSession,
73
82
  options connectionOptions: UIScene.ConnectionOptions
74
83
  ) {
75
84
  guard let windowScene = scene as? UIWindowScene else { return }
76
- let delegate = ReactNativeDelegate()
77
- let factory = RCTReactNativeFactory(delegate: delegate)
78
- delegate.dependencyProvider = RCTAppDependencyProvider()
79
-
80
- reactNativeDelegate = delegate
81
- reactNativeFactory = factory
85
+ guard let appDelegate = UIApplication.shared.delegate as? AppDelegate,
86
+ let factory = appDelegate.reactNativeFactory else { return }
82
87
 
83
88
  let window = UIWindow(windowScene: windowScene)
84
89
  self.window = window
@@ -154,7 +159,7 @@ function patchIosInfoPlistSceneManifest(rendered) {
154
159
  </dict>
155
160
  ${anchor}`);
156
161
  }
157
- function patchIosAppDelegateSceneLifecycle(rendered, appName) {
162
+ function patchIosAppDelegateSceneLifecycle(rendered, appName, backgroundTasks, appIntents) {
158
163
  const anchor = `@main
159
164
  class AppDelegate: UIResponder, UIApplicationDelegate {
160
165
  var window: UIWindow?
@@ -189,38 +194,48 @@ class AppDelegate: UIResponder, UIApplicationDelegate {
189
194
  }
190
195
  return rendered.replace(anchor, `@main
191
196
  class AppDelegate: UIResponder, UIApplicationDelegate {
197
+ var reactNativeDelegate: ReactNativeDelegate?
198
+ var reactNativeFactory: RCTReactNativeFactory?
199
+
192
200
  func application(
193
201
  _ application: UIApplication,
194
202
  didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? = nil
195
203
  ) -> Bool {
196
- true
197
- }
198
-
199
- // remote push answers land here; the notifications module observes the
200
- // forward. inert unless something calls registerForRemoteNotifications.
201
- func application(
202
- _ application: UIApplication,
203
- didRegisterForRemoteNotificationsWithDeviceToken deviceToken: Data
204
- ) {
205
- NotificationCenter.default.post(
206
- name: NSNotification.Name("OneNativePushTokenDidRegister"),
207
- object: nil,
208
- userInfo: ["deviceToken": deviceToken]
209
- )
210
- }
211
-
212
- func application(
213
- _ application: UIApplication,
214
- didFailToRegisterForRemoteNotificationsWithError error: Error
215
- ) {
216
- NotificationCenter.default.post(
217
- name: NSNotification.Name("OneNativePushTokenDidFail"),
218
- object: nil,
219
- userInfo: ["error": error.localizedDescription]
220
- )
204
+ let delegate = ReactNativeDelegate()
205
+ let factory = RCTReactNativeFactory(delegate: delegate)
206
+ delegate.dependencyProvider = RCTAppDependencyProvider()
207
+ reactNativeDelegate = delegate
208
+ reactNativeFactory = factory
209
+ ${backgroundTasks ? ` OneBackgroundTasksRegisterHost(factory, launchOptions)
210
+ ` : ""}${appIntents ? ` OneAppIntentsRegisterHost(factory, launchOptions)
211
+ ` : ""} return true
221
212
  }
222
213
  }`);
223
214
  }
215
+ const ONE_BRIDGING_HEADER_NAME = "One-Bridging-Header.h";
216
+ function renderOneBridgingHeader(app) {
217
+ const imports = [nativeProjectPatches.ONE_LAUNCH_SCREEN.bridgingHeaderImport];
218
+ if (app.updates !== void 0) imports.push(updatesHost.bridgingHeaderImport);
219
+ if (app.ios?.backgroundTasks !== void 0) imports.push("#import \"OneBackgroundTasksBridge.h\"");
220
+ if (app.ios?.appIntents !== void 0) imports.push("#import \"OneAppIntentsBridge.h\"");
221
+ return `// generated by one prebuild: exposes One's c entry points to AppDelegate
222
+ // without importing the One module.
223
+ ${imports.join("\n")}
224
+ `;
225
+ }
226
+ function generateOneBridgingHeader(dest, app) {
227
+ FSExtra.writeFileSync(path.join(dest, app.name, ONE_BRIDGING_HEADER_NAME), renderOneBridgingHeader(app));
228
+ }
229
+ function patchIosPbxprojOneBridgingHeader(project, appName) {
230
+ const anchor = `INFOPLIST_FILE = ${appName}/Info.plist;`;
231
+ const setting = `SWIFT_OBJC_BRIDGING_HEADER = "${appName}/${ONE_BRIDGING_HEADER_NAME}";`;
232
+ const occurrences = project.split(anchor).length - 1;
233
+ if (occurrences !== 2) {
234
+ throw new Error("[vxrn] prebuild template project.pbxproj changed shape: cannot point the app target at the One bridging header");
235
+ }
236
+ return project.split(anchor).join(`${anchor}
237
+ ${setting}`);
238
+ }
224
239
  function patchIosPbxprojSceneDelegate(rendered, appName) {
225
240
  const edits = [
226
241
  ["/* AppDelegate.swift in Sources */ = {isa = PBXBuildFile;", ` ${SCENE_DELEGATE_BUILD_FILE_ID} /* SceneDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = ${SCENE_DELEGATE_FILE_REF_ID} /* SceneDelegate.swift */; };`],
@@ -238,7 +253,128 @@ function patchIosPbxprojSceneDelegate(rendered, appName) {
238
253
  }
239
254
  return patched;
240
255
  }
241
- function patchIosPbxprojPushEntitlements(rendered, appName) {
256
+ function patchIosPbxprojAppIntents(rendered, appName) {
257
+ const name = "OneAppIntents.swift";
258
+ const fileRef = pbxprojId("one-app-intents-file");
259
+ const buildFile = pbxprojId("one-app-intents-build");
260
+ const edits = [
261
+ ["/* AppDelegate.swift in Sources */ = {isa = PBXBuildFile;", ` ${buildFile} /* ${name} in Sources */ = {isa = PBXBuildFile; fileRef = ${fileRef} /* ${name} */; };`],
262
+ ["/* AppDelegate.swift */ = {isa = PBXFileReference;", ` ${fileRef} /* ${name} */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = ${name}; path = ${appName}/${name}; sourceTree = "<group>"; };`],
263
+ ["/* AppDelegate.swift */,", ` ${fileRef} /* ${name} */,`],
264
+ ["/* AppDelegate.swift in Sources */,", ` ${buildFile} /* ${name} in Sources */,`]
265
+ ];
266
+ let patched = rendered;
267
+ for (const [anchor, insertion] of edits) {
268
+ const next = insertAfterLine(patched, anchor, insertion);
269
+ if (next === patched) {
270
+ throw new Error(`[vxrn] prebuild template project.pbxproj lost its AppDelegate.swift anchor (${anchor})`);
271
+ }
272
+ patched = next;
273
+ }
274
+ return patched;
275
+ }
276
+ function swiftLiteral(value) {
277
+ return JSON.stringify(value).replace(/\\u([0-9a-fA-F]{4})/g, (_match, hex) => `\\u{${hex}}`);
278
+ }
279
+ function swiftShortcutPhrase(value) {
280
+ return `"${value.split("{app}").map((part) => swiftLiteral(part).slice(1, -1)).join("\\(.applicationName)")}"`;
281
+ }
282
+ function renderIosAppIntentsSwift(actions) {
283
+ const types = actions.map((action, index) => {
284
+ const summaryTitle = swiftLiteral(action.title).slice(1, -1);
285
+ const text = action.textParameterTitle === void 0 ? "" : `
286
+ @Parameter(title: ${swiftLiteral(action.textParameterTitle)}) var text: String
287
+
288
+ static var parameterSummary: some ParameterSummary {
289
+ Summary("${summaryTitle} \\(\\.$text)")
290
+ }
291
+ `;
292
+ return `struct OneAppIntent${index}: AppIntent {
293
+ static let title: LocalizedStringResource = ${swiftLiteral(action.title)}
294
+ ${text}
295
+ func perform() async throws -> some IntentResult & ReturnsValue<String> {
296
+ let result = try await oneAppIntentsRun(${swiftLiteral(action.id)}, ${text ? "text" : "nil"})
297
+ return .result(value: result)
298
+ }
299
+ }`;
300
+ });
301
+ const shortcuts = actions.map((action, index) => ` AppShortcut(
302
+ intent: OneAppIntent${index}(),
303
+ phrases: [${swiftShortcutPhrase(action.shortcutPhrase)}],
304
+ shortTitle: ${swiftLiteral(action.title)},
305
+ systemImageName: "bolt.fill"
306
+ )`).join("\n");
307
+ return `import AppIntents
308
+ import Foundation
309
+
310
+ private func oneAppIntentsRun(_ identifier: String, _ text: String?) async throws -> String {
311
+ try await withCheckedThrowingContinuation { continuation in
312
+ OneAppIntentsPerform(identifier, text) { result, error in
313
+ if let error { continuation.resume(throwing: error) }
314
+ else if let result { continuation.resume(returning: result) }
315
+ else { continuation.resume(throwing: NSError(domain: "OneAppIntents", code: 3)) }
316
+ }
317
+ }
318
+ }
319
+
320
+ ${types.join("\n\n")}
321
+
322
+ struct OneAppShortcuts: AppShortcutsProvider {
323
+ static var appShortcuts: [AppShortcut] {
324
+ ${shortcuts}
325
+ }
326
+ }
327
+ `;
328
+ }
329
+ function fontFileName(font) {
330
+ return path.basename(font);
331
+ }
332
+ function pbxprojId(seed) {
333
+ return createHash("sha1").update(seed).digest("hex").slice(0, 24).toUpperCase();
334
+ }
335
+ const GOOGLE_SERVICES_PLIST = "GoogleService-Info.plist";
336
+ const GOOGLE_SERVICES_VERSION = "4.4.4";
337
+ function iosResourceFiles(app) {
338
+ return [...(app.fonts ?? []).map(fontFileName), ...app.ios?.googleServicesFile ? [GOOGLE_SERVICES_PLIST] : []];
339
+ }
340
+ function patchIosPbxprojResources(rendered, appName, names) {
341
+ let patched = rendered;
342
+ for (const name of names) {
343
+ const fileRef = pbxprojId(`one-resource-file:${name}`);
344
+ const buildFile = pbxprojId(`one-resource-build:${name}`);
345
+ const fileType = name.endsWith(".plist") ? "text.plist.xml" : "file";
346
+ const edits = [
347
+ ["/* Images.xcassets in Resources */ = {isa = PBXBuildFile;", ` ${buildFile} /* ${name} in Resources */ = {isa = PBXBuildFile; fileRef = ${fileRef} /* ${name} */; };`],
348
+ ["/* Images.xcassets */ = {isa = PBXFileReference;", ` ${fileRef} /* ${name} */ = {isa = PBXFileReference; lastKnownFileType = ${fileType}; name = ${name}; path = ${appName}/${name}; sourceTree = "<group>"; };`],
349
+ ["/* Images.xcassets */,", ` ${fileRef} /* ${name} */,`],
350
+ ["/* Images.xcassets in Resources */,", ` ${buildFile} /* ${name} in Resources */,`]
351
+ ];
352
+ for (const [anchor, insertion] of edits) {
353
+ const next = insertAfterLine(patched, anchor, insertion);
354
+ if (next === patched) {
355
+ throw new Error(`[vxrn] prebuild template project.pbxproj lost its Images.xcassets anchor (${anchor})`);
356
+ }
357
+ patched = next;
358
+ }
359
+ }
360
+ return patched;
361
+ }
362
+ function copyAppResources({ root, dest, platform, app }) {
363
+ const copies = (app.fonts ?? []).map((font) => [font, platform === "ios" ? path.join(dest, app.name, fontFileName(font)) : path.join(dest, "app", "src", "main", "assets", "fonts", fontFileName(font))]);
364
+ const googleServices = platform === "ios" ? app.ios?.googleServicesFile : app.android?.googleServicesFile;
365
+ if (googleServices) {
366
+ copies.push([googleServices, platform === "ios" ? path.join(dest, app.name, GOOGLE_SERVICES_PLIST) : path.join(dest, "app", "google-services.json")]);
367
+ }
368
+ for (const [file, target] of copies) {
369
+ const source = path.resolve(root, file);
370
+ if (!FSExtra.existsSync(source)) {
371
+ throw new Error(`[vxrn] native.app names ${file}, which does not exist`);
372
+ }
373
+ FSExtra.mkdirSync(path.dirname(target), { recursive: true });
374
+ FSExtra.copyFileSync(source, target);
375
+ }
376
+ }
377
+ function patchIosPbxprojAppEntitlements(rendered, appName) {
242
378
  const edits = [["/* AppDelegate.swift */ = {isa = PBXFileReference;", ` ${PUSH_ENTITLEMENTS_FILE_REF_ID} /* ${appName}.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; name = ${appName}.entitlements; path = ${appName}/${appName}.entitlements; sourceTree = "<group>"; };`], ["/* AppDelegate.swift */,", ` ${PUSH_ENTITLEMENTS_FILE_REF_ID} /* ${appName}.entitlements */,`]];
243
379
  let patched = rendered;
244
380
  for (const [anchor, insertion] of edits) {
@@ -255,17 +391,92 @@ function patchIosPbxprojPushEntitlements(rendered, appName) {
255
391
  return patched.split(setting).join(`${setting}
256
392
  CODE_SIGN_ENTITLEMENTS = ${appName}/${appName}.entitlements;`);
257
393
  }
258
- function renderPushEntitlements() {
394
+ function renderPlistValue(value, indent) {
395
+ if (typeof value === "string") return `${indent}<string>${escapeXml(value)}</string>`;
396
+ if (typeof value === "boolean") return `${indent}<${value}/>`;
397
+ if (typeof value === "number") {
398
+ return Number.isInteger(value) ? `${indent}<integer>${value}</integer>` : `${indent}<real>${value}</real>`;
399
+ }
400
+ if (Array.isArray(value)) {
401
+ return `${indent}<array>
402
+ ${value.map((item) => renderPlistValue(item, `${indent} `)).join("\n")}
403
+ ${indent}</array>`;
404
+ }
405
+ return `${indent}<dict>
406
+ ${renderPlistEntries(value, `${indent} `)}
407
+ ${indent}</dict>`;
408
+ }
409
+ function renderPlistEntries(entries, indent) {
410
+ return Object.entries(entries).map(([key, value]) => `${indent}<key>${escapeXml(key)}</key>
411
+ ${renderPlistValue(value, indent)}`).join("\n");
412
+ }
413
+ function appEntitlements(app) {
414
+ const modeled = {};
415
+ if (app.notifications?.push === true || app.ios?.widgets?.pushNotifications) {
416
+ modeled["aps-environment"] = app.notifications?.apsEnvironment ?? notificationsHost.apsEnvironment;
417
+ }
418
+ if (app.ios?.widgets) {
419
+ modeled["com.apple.security.application-groups"] = [app.ios.widgets.appGroup];
420
+ }
421
+ if (app.ios?.associatedDomains?.length) {
422
+ modeled["com.apple.developer.associated-domains"] = app.ios.associatedDomains;
423
+ }
424
+ if (app.ios?.usesAppleSignIn) {
425
+ modeled["com.apple.developer.applesignin"] = ["Default"];
426
+ }
427
+ for (const key of Object.keys(app.ios?.entitlements ?? {})) {
428
+ if (key in modeled) {
429
+ throw new Error(`[vxrn] native.app.ios.entitlements sets ${key}, which native.app already writes: use its field`);
430
+ }
431
+ }
432
+ return {
433
+ ...modeled,
434
+ ...app.ios?.entitlements
435
+ };
436
+ }
437
+ function renderEntitlements(entries) {
259
438
  return `<?xml version="1.0" encoding="UTF-8"?>
260
439
  <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
261
440
  <plist version="1.0">
262
441
  <dict>
263
- <key>aps-environment</key>
264
- <string>development</string>
442
+ ${renderPlistEntries(entries, " ")}
265
443
  </dict>
266
444
  </plist>
267
445
  `;
268
446
  }
447
+ function patchAndroidMainApplicationUpdatesHost(rendered) {
448
+ const anchor = "import com.facebook.react.defaults.DefaultReactHost.getDefaultReactHost";
449
+ if (!rendered.includes(anchor)) {
450
+ throw new Error("[vxrn] prebuild template MainApplication.kt changed shape: cannot point the ReactHost at One.Updates");
451
+ }
452
+ return rendered.replace(anchor, "import com.margelo.nitro.one.OneUpdatesReactHost.getDefaultReactHost");
453
+ }
454
+ function escapeGradleString(value) {
455
+ return value.replace(/\\/g, "\\\\").replace(/"/g, "\\\"");
456
+ }
457
+ function patchAndroidBuildGradleUpdatesManifest(rendered, runtimeVersion) {
458
+ const anchor = " autolinkLibrariesWithApp()\n}";
459
+ if (!rendered.includes(anchor)) {
460
+ throw new Error("[vxrn] cannot stamp the embedded updates manifest: expected the replaced react block in app/build.gradle");
461
+ }
462
+ const snippet = `${anchor}
463
+
464
+ // [vxrn/one] the embedded update manifest lands beside the release bundle
465
+ tasks.matching { it.name.startsWith("createBundle") && it.name.endsWith("JsAndAssets") }.configureEach { bundleTask ->
466
+ // capture the output directory provider at configuration time, so the
467
+ // action below holds no task reference at execution time.
468
+ def bundleDir = bundleTask.jsBundleDir
469
+ bundleTask.doLast {
470
+ def manifestFile = new File(bundleDir.get().asFile, "one-updates-embedded.json")
471
+ manifestFile.text = groovy.json.JsonOutput.toJson([
472
+ id: java.util.UUID.randomUUID().toString(),
473
+ createdAt: java.time.Instant.now().toString(),
474
+ runtimeVersion: "${escapeGradleString(runtimeVersion)}",
475
+ ])
476
+ }
477
+ }`;
478
+ return rendered.replace(anchor, snippet);
479
+ }
269
480
  function patchIosPbxprojWidgets(project, app) {
270
481
  const name = app.name;
271
482
  const bundleId = app.ios.bundleId;
@@ -367,10 +578,15 @@ function generateSceneDelegate(args) {
367
578
  const { dest, platform, app } = args;
368
579
  if (platform !== "ios") return;
369
580
  FSExtra.writeFileSync(path.join(dest, app.name, "SceneDelegate.swift"), renderSceneDelegateSwift(app.name));
370
- if (app.notifications?.push === true && !app.ios?.widgets) {
371
- FSExtra.writeFileSync(path.join(dest, app.name, `${app.name}.entitlements`), renderPushEntitlements());
581
+ const entitlements = appEntitlements(app);
582
+ if (Object.keys(entitlements).length && !app.ios?.widgets) {
583
+ FSExtra.writeFileSync(path.join(dest, app.name, `${app.name}.entitlements`), renderEntitlements(entitlements));
372
584
  }
373
585
  }
586
+ function generateIosAppIntents(dest, platform, app) {
587
+ if (platform !== "ios" || !app.ios?.appIntents) return;
588
+ FSExtra.writeFileSync(path.join(dest, app.name, "OneAppIntents.swift"), renderIosAppIntentsSwift(app.ios.appIntents.actions));
589
+ }
374
590
  function generateIosWidgets(dest, app) {
375
591
  const widgets = app.ios?.widgets;
376
592
  if (!widgets) return;
@@ -381,7 +597,7 @@ function generateIosWidgets(dest, app) {
381
597
  <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
382
598
  <plist version="1.0"><dict><key>com.apple.security.application-groups</key><array><string>${escapeXml(widgets.appGroup)}</string></array></dict></plist>
383
599
  `;
384
- FSExtra.writeFileSync(path.join(appDir, "OneAppWidgets.entitlements"), widgets.pushNotifications || app.notifications?.push ? entitlements.replace("</dict></plist>", "<key>aps-environment</key><string>development</string></dict></plist>") : entitlements);
600
+ FSExtra.writeFileSync(path.join(appDir, "OneAppWidgets.entitlements"), renderEntitlements(appEntitlements(app)));
385
601
  FSExtra.writeFileSync(path.join(extensionDir, "OneWidgets.entitlements"), entitlements);
386
602
  FSExtra.writeFileSync(path.join(extensionDir, "WidgetInfo.plist"), `<?xml version="1.0" encoding="UTF-8"?>
387
603
  <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
@@ -889,33 +1105,62 @@ class OneWidgetsBridge: RCTEventEmitter {
889
1105
  }
890
1106
  `);
891
1107
  }
1108
+ const IPAD_APP_ICON_SIZES = [
1109
+ ["20x20", ["1x", "2x"]],
1110
+ ["29x29", ["1x", "2x"]],
1111
+ ["40x40", ["1x", "2x"]],
1112
+ ["76x76", ["1x", "2x"]],
1113
+ ["83.5x83.5", ["2x"]]
1114
+ ];
892
1115
  async function generateAppIcons(args) {
893
1116
  const { root, dest, platform, app } = args;
894
1117
  if (!app.icon) return;
1118
+ if (platform === "ios") {
1119
+ const assetsDir = path.join(dest, app.name, "Images.xcassets");
1120
+ const template = JSON.parse(FSExtra.readFileSync(path.join(assetsDir, "AppIcon.appiconset", "Contents.json"), "utf8"));
1121
+ const icons = [["AppIcon", app.icon], ...Object.entries(app.ios?.alternateIcons ?? {})];
1122
+ for (const [name, icon] of icons) {
1123
+ const source2 = path.resolve(root, icon.source);
1124
+ if (!FSExtra.existsSync(source2)) {
1125
+ throw new Error(`[vxrn] native.app icon source does not exist: ${source2}`);
1126
+ }
1127
+ const metadata2 = await sharp(source2).metadata();
1128
+ if (metadata2.width !== metadata2.height || metadata2.width === void 0 || metadata2.width < 1024) {
1129
+ throw new Error(`[vxrn] native.app icon source must be a square image at least 1024px wide: ${source2}`);
1130
+ }
1131
+ const iconDir = path.join(assetsDir, `${name}.appiconset`);
1132
+ FSExtra.mkdirSync(iconDir, { recursive: true });
1133
+ const contents = structuredClone(template);
1134
+ if (app.ios?.tablet) {
1135
+ for (const [size, scales] of IPAD_APP_ICON_SIZES) {
1136
+ for (const scale of scales) contents.images.push({
1137
+ idiom: "ipad",
1138
+ scale,
1139
+ size
1140
+ });
1141
+ }
1142
+ }
1143
+ for (const image of contents.images) {
1144
+ const points = Number.parseFloat(image.size.split("x")[0]);
1145
+ const scale = Number.parseInt(image.scale, 10);
1146
+ const pixels = points * scale;
1147
+ const filename = image.idiom === "ios-marketing" ? "icon-1024.png" : `icon-${points}@${image.scale}.png`;
1148
+ image.filename = filename;
1149
+ await sharp(source2).rotate().resize(pixels, pixels, { fit: "cover" }).flatten({ background: icon.backgroundColor }).png().toFile(path.join(iconDir, filename));
1150
+ }
1151
+ FSExtra.writeFileSync(path.join(iconDir, "Contents.json"), `${JSON.stringify(contents, null, 2)}
1152
+ `);
1153
+ }
1154
+ return;
1155
+ }
895
1156
  const source = path.resolve(root, app.icon.source);
896
1157
  if (!FSExtra.existsSync(source)) {
897
1158
  throw new Error(`[vxrn] native.app.icon source does not exist: ${source}`);
898
1159
  }
899
1160
  const metadata = await sharp(source).metadata();
900
- if (metadata.width === void 0 || metadata.height === void 0 || metadata.width !== metadata.height || metadata.width < 1024) {
1161
+ if (metadata.width !== metadata.height || metadata.width === void 0 || metadata.width < 1024) {
901
1162
  throw new Error("[vxrn] native.app.icon source must be a square image at least 1024px wide");
902
1163
  }
903
- if (platform === "ios") {
904
- const iconDir = path.join(dest, app.name, "Images.xcassets", "AppIcon.appiconset");
905
- const contentsPath = path.join(iconDir, "Contents.json");
906
- const contents = JSON.parse(FSExtra.readFileSync(contentsPath, "utf8"));
907
- for (const image of contents.images) {
908
- const points = Number.parseFloat(image.size.split("x")[0]);
909
- const scale = Number.parseInt(image.scale, 10);
910
- const pixels = points * scale;
911
- const filename = image.idiom === "ios-marketing" ? "icon-1024.png" : `icon-${points}@${image.scale}.png`;
912
- image.filename = filename;
913
- await sharp(source).rotate().resize(pixels, pixels, { fit: "cover" }).flatten({ background: app.icon.backgroundColor }).png().toFile(path.join(iconDir, filename));
914
- }
915
- FSExtra.writeFileSync(contentsPath, `${JSON.stringify(contents, null, 2)}
916
- `);
917
- return;
918
- }
919
1164
  for (const [density, multiplier] of Object.entries(ANDROID_DENSITIES)) {
920
1165
  const pixels = 48 * multiplier;
921
1166
  const iconDir = path.join(dest, "app", "src", "main", "res", `mipmap-${density}`);
@@ -924,39 +1169,175 @@ async function generateAppIcons(args) {
924
1169
  }
925
1170
  }
926
1171
  }
1172
+ async function generateAdaptiveIcon(args) {
1173
+ const { root, dest, app } = args;
1174
+ const adaptiveIcon = app.android?.adaptiveIcon;
1175
+ if (!adaptiveIcon) return;
1176
+ const res = path.join(dest, "app", "src", "main", "res");
1177
+ const layers = {
1178
+ foreground: adaptiveIcon.foreground,
1179
+ background: adaptiveIcon.background,
1180
+ monochrome: adaptiveIcon.monochrome
1181
+ };
1182
+ for (const [layer, file] of Object.entries(layers)) {
1183
+ if (!file) continue;
1184
+ const source = path.resolve(root, file);
1185
+ if (!FSExtra.existsSync(source)) {
1186
+ throw new Error(`[vxrn] native.app.android.adaptiveIcon.${layer} does not exist: ${source}`);
1187
+ }
1188
+ for (const [density, multiplier] of Object.entries(ANDROID_DENSITIES)) {
1189
+ const pixels = 108 * multiplier;
1190
+ await sharp(source).rotate().resize(pixels, pixels, { fit: "cover" }).png().toFile(path.join(res, `mipmap-${density}`, `ic_launcher_${layer}.png`));
1191
+ }
1192
+ }
1193
+ if (!adaptiveIcon.background) {
1194
+ FSExtra.writeFileSync(path.join(res, "values", "ic_launcher_background.xml"), `<?xml version="1.0" encoding="utf-8"?>
1195
+ <resources>
1196
+ <color name="ic_launcher_background">${adaptiveIcon.backgroundColor ?? "#FFFFFF"}</color>
1197
+ </resources>
1198
+ `);
1199
+ }
1200
+ const adaptiveXml = `<?xml version="1.0" encoding="utf-8"?>
1201
+ <adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
1202
+ <background android:drawable="${adaptiveIcon.background ? "@mipmap/ic_launcher_background" : "@color/ic_launcher_background"}"/>
1203
+ <foreground android:drawable="@mipmap/ic_launcher_foreground"/>
1204
+ ${adaptiveIcon.monochrome ? " <monochrome android:drawable=\"@mipmap/ic_launcher_monochrome\"/>\n" : ""}</adaptive-icon>
1205
+ `;
1206
+ const anydpi = path.join(res, "mipmap-anydpi-v26");
1207
+ FSExtra.mkdirSync(anydpi, { recursive: true });
1208
+ for (const filename of ["ic_launcher.xml", "ic_launcher_round.xml"]) {
1209
+ FSExtra.writeFileSync(path.join(anydpi, filename), adaptiveXml);
1210
+ }
1211
+ }
1212
+ const IOS_DARK_APPEARANCE = [{
1213
+ appearance: "luminosity",
1214
+ value: "dark"
1215
+ }];
1216
+ function srgb(hex) {
1217
+ return {
1218
+ red: Number.parseInt(hex.slice(1, 3), 16) / 255,
1219
+ green: Number.parseInt(hex.slice(3, 5), 16) / 255,
1220
+ blue: Number.parseInt(hex.slice(5, 7), 16) / 255
1221
+ };
1222
+ }
1223
+ function xcodeColorSet(light, dark) {
1224
+ const entry = (hex) => {
1225
+ const { red, green, blue } = srgb(hex);
1226
+ return {
1227
+ color: {
1228
+ "color-space": "srgb",
1229
+ components: {
1230
+ red: red.toFixed(3),
1231
+ green: green.toFixed(3),
1232
+ blue: blue.toFixed(3),
1233
+ alpha: "1.000"
1234
+ }
1235
+ },
1236
+ idiom: "universal"
1237
+ };
1238
+ };
1239
+ return `${JSON.stringify({
1240
+ colors: [entry(light), ...dark ? [{
1241
+ appearances: IOS_DARK_APPEARANCE,
1242
+ ...entry(dark)
1243
+ }] : []],
1244
+ info: {
1245
+ author: "xcode",
1246
+ version: 1
1247
+ }
1248
+ }, null, 2)}
1249
+ `;
1250
+ }
1251
+ function generateAccentColor(args) {
1252
+ const accent = args.app.ios?.accentColor;
1253
+ if (!accent) return;
1254
+ const colorDir = path.join(args.dest, args.app.name, "Images.xcassets", "AccentColor.colorset");
1255
+ FSExtra.mkdirSync(colorDir, { recursive: true });
1256
+ FSExtra.writeFileSync(path.join(colorDir, "Contents.json"), xcodeColorSet(accent.light, accent.dark));
1257
+ }
927
1258
  async function generateSplashScreen(args) {
928
1259
  const { root, dest, platform, app } = args;
929
1260
  if (!app.splash) return;
930
- const source = path.resolve(root, app.splash.source);
931
- if (!FSExtra.existsSync(source)) {
932
- throw new Error(`[vxrn] native.app.splash source does not exist: ${source}`);
933
- }
934
- const { data: artwork, info: metadata } = await sharp(source).rotate().trim({ background: app.splash.backgroundColor }).png().toBuffer({ resolveWithObject: true });
935
- if (!metadata.width || !metadata.height) {
936
- throw new Error("[vxrn] native.app.splash source must be an image");
937
- }
1261
+ const cover = app.splash.resizeMode === "cover";
1262
+ const prepareArtwork = async (sourcePath, backgroundColor) => {
1263
+ const source = path.resolve(root, sourcePath);
1264
+ if (!FSExtra.existsSync(source)) {
1265
+ throw new Error(`[vxrn] native.app.splash source does not exist: ${source}`);
1266
+ }
1267
+ const { width: sourceWidth, format } = await sharp(source).metadata();
1268
+ const density = format === "svg" && sourceWidth && !cover ? Math.min(2400, Math.ceil(72 * 4 * (app.splash?.width ?? 200) / sourceWidth)) : void 0;
1269
+ const image = sharp(source, density ? { density } : {}).rotate();
1270
+ const prepared = await (cover ? image : image.trim({ background: backgroundColor })).png().toBuffer({ resolveWithObject: true });
1271
+ if (!prepared.info.width || !prepared.info.height) {
1272
+ throw new Error("[vxrn] native.app.splash source must be an image");
1273
+ }
1274
+ return prepared;
1275
+ };
1276
+ const { data: artwork, info: metadata } = await prepareArtwork(app.splash.source, app.splash.backgroundColor);
1277
+ const dark = app.splash.dark;
1278
+ const darkArtwork = dark?.source ? (await prepareArtwork(dark.source, dark.backgroundColor)).data : void 0;
938
1279
  const artworkWidth = app.splash.width ?? 200;
939
1280
  const artworkHeight = Number((artworkWidth * (metadata.height / metadata.width)).toFixed(3));
1281
+ const readBackground = (sourcePath) => {
1282
+ const source = path.resolve(root, sourcePath);
1283
+ if (!FSExtra.existsSync(source)) {
1284
+ throw new Error(`[vxrn] native.app.splash backgroundImage does not exist: ${source}`);
1285
+ }
1286
+ return sharp(source).rotate().png().toBuffer({ resolveWithObject: true });
1287
+ };
940
1288
  if (platform === "ios") {
941
1289
  const appDir = path.join(dest, app.name);
942
- const splashDir = path.join(appDir, "Images.xcassets", "Splash.imageset");
943
- FSExtra.mkdirSync(splashDir, { recursive: true });
944
- await sharp(artwork).toFile(path.join(splashDir, "splash.png"));
945
- FSExtra.writeFileSync(path.join(splashDir, "Contents.json"), `${JSON.stringify({
946
- images: [{
947
- filename: "splash.png",
948
- idiom: "universal",
949
- scale: "1x"
950
- }],
951
- info: {
952
- author: "xcode",
953
- version: 1
954
- }
955
- }, null, 2)}
1290
+ const assets = path.join(appDir, "Images.xcassets");
1291
+ const writeImageSet = async (name, light, dark2, pointWidth) => {
1292
+ const dir = path.join(assets, `${name}.imageset`);
1293
+ FSExtra.mkdirSync(dir, { recursive: true });
1294
+ const scales = pointWidth === void 0 ? [1] : [
1295
+ 1,
1296
+ 2,
1297
+ 3
1298
+ ];
1299
+ const images = [];
1300
+ for (const [variant, suffix, appearances] of [[
1301
+ light,
1302
+ "",
1303
+ void 0
1304
+ ], [
1305
+ dark2,
1306
+ "-dark",
1307
+ IOS_DARK_APPEARANCE
1308
+ ]]) {
1309
+ if (!variant) continue;
1310
+ for (const scale of scales) {
1311
+ const filename = `${name.toLowerCase()}${suffix}${scale === 1 ? "" : `@${scale}x`}.png`;
1312
+ const output = sharp(variant);
1313
+ await (pointWidth === void 0 ? output : output.resize({ width: Math.round(pointWidth * scale) })).png().toFile(path.join(dir, filename));
1314
+ images.push({
1315
+ ...appearances ? { appearances } : {},
1316
+ filename,
1317
+ idiom: "universal",
1318
+ scale: `${scale}x`
1319
+ });
1320
+ }
1321
+ }
1322
+ FSExtra.writeFileSync(path.join(dir, "Contents.json"), `${JSON.stringify({
1323
+ images,
1324
+ info: {
1325
+ author: "xcode",
1326
+ version: 1
1327
+ }
1328
+ }, null, 2)}
956
1329
  `);
957
- const red = Number.parseInt(app.splash.backgroundColor.slice(1, 3), 16) / 255;
958
- const green = Number.parseInt(app.splash.backgroundColor.slice(3, 5), 16) / 255;
959
- const blue = Number.parseInt(app.splash.backgroundColor.slice(5, 7), 16) / 255;
1330
+ };
1331
+ await writeImageSet("Splash", artwork, darkArtwork, cover ? void 0 : artworkWidth);
1332
+ const background2 = app.splash.backgroundImage ? await readBackground(app.splash.backgroundImage) : void 0;
1333
+ const darkBackground2 = dark?.backgroundImage ? (await readBackground(dark.backgroundImage)).data : void 0;
1334
+ if (background2) {
1335
+ await writeImageSet("SplashBackgroundImage", background2.data, darkBackground2, void 0);
1336
+ }
1337
+ const colorDir = path.join(assets, "SplashBackground.colorset");
1338
+ FSExtra.mkdirSync(colorDir, { recursive: true });
1339
+ FSExtra.writeFileSync(path.join(colorDir, "Contents.json"), xcodeColorSet(app.splash.backgroundColor, dark?.backgroundColor));
1340
+ const { red, green, blue } = srgb(app.splash.backgroundColor);
960
1341
  FSExtra.writeFileSync(path.join(appDir, "LaunchScreen.storyboard"), `<?xml version="1.0" encoding="UTF-8"?>
961
1342
  <document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="15702" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" launchScreen="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES" initialViewController="launch-controller">
962
1343
  <device id="retina6_12" orientation="portrait" appearance="light"/>
@@ -971,15 +1352,23 @@ async function generateSplashScreen(args) {
971
1352
  <viewController id="launch-controller" sceneMemberID="viewController">
972
1353
  <view key="view" contentMode="scaleToFill" id="launch-view">
973
1354
  <rect key="frame" x="0.0" y="0.0" width="390" height="844"/>
974
- <subviews>
975
- <imageView userInteractionEnabled="NO" contentMode="scaleAspectFit" image="Splash" translatesAutoresizingMaskIntoConstraints="NO" id="splash-image"/>
1355
+ <subviews>${background2 ? `
1356
+ <imageView userInteractionEnabled="NO" contentMode="scaleAspectFill" image="SplashBackgroundImage" translatesAutoresizingMaskIntoConstraints="NO" id="splash-background"/>` : ""}
1357
+ <imageView userInteractionEnabled="NO" contentMode="${cover ? "scaleAspectFill" : "scaleAspectFit"}" image="Splash" translatesAutoresizingMaskIntoConstraints="NO" id="splash-image"/>
976
1358
  </subviews>
977
- <color key="backgroundColor" red="${red}" green="${green}" blue="${blue}" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
978
- <constraints>
979
- <constraint firstItem="splash-image" firstAttribute="centerX" secondItem="launch-view" secondAttribute="centerX" id="splash-center-x"/>
1359
+ <color key="backgroundColor" name="SplashBackground"/>
1360
+ <constraints>${background2 ? `
1361
+ <constraint firstItem="splash-background" firstAttribute="leading" secondItem="launch-view" secondAttribute="leading" id="splash-background-leading"/>
1362
+ <constraint firstItem="splash-background" firstAttribute="trailing" secondItem="launch-view" secondAttribute="trailing" id="splash-background-trailing"/>
1363
+ <constraint firstItem="splash-background" firstAttribute="top" secondItem="launch-view" secondAttribute="top" id="splash-background-top"/>
1364
+ <constraint firstItem="splash-background" firstAttribute="bottom" secondItem="launch-view" secondAttribute="bottom" id="splash-background-bottom"/>` : ""}
1365
+ ${cover ? ` <constraint firstItem="splash-image" firstAttribute="leading" secondItem="launch-view" secondAttribute="leading" id="splash-leading"/>
1366
+ <constraint firstItem="splash-image" firstAttribute="trailing" secondItem="launch-view" secondAttribute="trailing" id="splash-trailing"/>
1367
+ <constraint firstItem="splash-image" firstAttribute="top" secondItem="launch-view" secondAttribute="top" id="splash-top"/>
1368
+ <constraint firstItem="splash-image" firstAttribute="bottom" secondItem="launch-view" secondAttribute="bottom" id="splash-bottom"/>` : ` <constraint firstItem="splash-image" firstAttribute="centerX" secondItem="launch-view" secondAttribute="centerX" id="splash-center-x"/>
980
1369
  <constraint firstItem="splash-image" firstAttribute="centerY" secondItem="launch-view" secondAttribute="centerY" id="splash-center-y"/>
981
1370
  <constraint firstItem="splash-image" firstAttribute="width" constant="${artworkWidth}" id="splash-width"/>
982
- <constraint firstItem="splash-image" firstAttribute="height" constant="${artworkHeight}" id="splash-height"/>
1371
+ <constraint firstItem="splash-image" firstAttribute="height" constant="${artworkHeight}" id="splash-height"/>`}
983
1372
  </constraints>
984
1373
  </view>
985
1374
  </viewController>
@@ -988,7 +1377,11 @@ async function generateSplashScreen(args) {
988
1377
  </scene>
989
1378
  </scenes>
990
1379
  <resources>
991
- <image name="Splash" width="${metadata.width}" height="${metadata.height}"/>
1380
+ <image name="Splash" width="${cover ? metadata.width : artworkWidth}" height="${cover ? metadata.height : artworkHeight}"/>${background2 ? `
1381
+ <image name="SplashBackgroundImage" width="${background2.info.width}" height="${background2.info.height}"/>` : ""}
1382
+ <namedColor name="SplashBackground">
1383
+ <color red="${red}" green="${green}" blue="${blue}" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
1384
+ </namedColor>
992
1385
  </resources>
993
1386
  </document>
994
1387
  `);
@@ -996,40 +1389,59 @@ async function generateSplashScreen(args) {
996
1389
  }
997
1390
  const mainRes = path.join(dest, "app", "src", "main", "res");
998
1391
  const drawable = path.join(mainRes, "drawable");
999
- for (const [density, multiplier] of Object.entries(ANDROID_DENSITIES)) {
1000
- const canvasSize = 288 * multiplier;
1001
- const imageSize = Math.round(artworkWidth * multiplier);
1002
- const contained = await sharp(artwork).resize(imageSize, imageSize, {
1003
- fit: "contain",
1004
- background: {
1005
- r: 0,
1006
- g: 0,
1007
- b: 0,
1008
- alpha: 0
1009
- }
1010
- }).png().toBuffer();
1011
- const drawableDensity = path.join(mainRes, `drawable-${density}`);
1012
- FSExtra.mkdirSync(drawableDensity, { recursive: true });
1013
- await sharp({ create: {
1014
- width: canvasSize,
1015
- height: canvasSize,
1016
- channels: 4,
1017
- background: {
1018
- r: 0,
1019
- g: 0,
1020
- b: 0,
1021
- alpha: 0
1022
- }
1023
- } }).composite([{
1024
- input: contained,
1025
- left: Math.round((canvasSize - imageSize) / 2),
1026
- top: Math.round((canvasSize - imageSize) / 2)
1027
- }]).png().toFile(path.join(drawableDensity, "splash.png"));
1392
+ const variants = [[artwork, "drawable"]];
1393
+ if (darkArtwork) variants.push([darkArtwork, "drawable-night"]);
1394
+ for (const [variantArtwork, drawablePrefix] of variants) {
1395
+ for (const [density, multiplier] of Object.entries(ANDROID_DENSITIES)) {
1396
+ const canvasSize = 288 * multiplier;
1397
+ const imageSize = Math.round(artworkWidth * multiplier);
1398
+ const contained = await sharp(variantArtwork).resize(imageSize, imageSize, {
1399
+ fit: "contain",
1400
+ background: {
1401
+ r: 0,
1402
+ g: 0,
1403
+ b: 0,
1404
+ alpha: 0
1405
+ }
1406
+ }).png().toBuffer();
1407
+ const drawableDensity = path.join(mainRes, `${drawablePrefix}-${density}`);
1408
+ FSExtra.mkdirSync(drawableDensity, { recursive: true });
1409
+ await sharp({ create: {
1410
+ width: canvasSize,
1411
+ height: canvasSize,
1412
+ channels: 4,
1413
+ background: {
1414
+ r: 0,
1415
+ g: 0,
1416
+ b: 0,
1417
+ alpha: 0
1418
+ }
1419
+ } }).composite([{
1420
+ input: contained,
1421
+ left: Math.round((canvasSize - imageSize) / 2),
1422
+ top: Math.round((canvasSize - imageSize) / 2)
1423
+ }]).png().toFile(path.join(drawableDensity, "splash.png"));
1424
+ }
1425
+ }
1426
+ const background = app.splash.backgroundImage ? await readBackground(app.splash.backgroundImage) : void 0;
1427
+ const darkBackground = dark?.backgroundImage ? (await readBackground(dark.backgroundImage)).data : void 0;
1428
+ if (background) {
1429
+ const nodpi = path.join(mainRes, "drawable-nodpi");
1430
+ FSExtra.mkdirSync(nodpi, { recursive: true });
1431
+ FSExtra.writeFileSync(path.join(nodpi, "splash_background.png"), background.data);
1432
+ if (darkBackground) {
1433
+ const nightNodpi = path.join(mainRes, "drawable-night-nodpi");
1434
+ FSExtra.mkdirSync(nightNodpi, { recursive: true });
1435
+ FSExtra.writeFileSync(path.join(nightNodpi, "splash_background.png"), darkBackground);
1436
+ }
1028
1437
  }
1029
1438
  FSExtra.writeFileSync(path.join(drawable, "launch_screen.xml"), `<?xml version="1.0" encoding="utf-8"?>
1030
1439
  <layer-list xmlns:android="http://schemas.android.com/apk/res/android">
1031
1440
  <item android:drawable="@color/splash_background" />
1032
- <item>
1441
+ ${background ? ` <item>
1442
+ <bitmap android:gravity="fill" android:src="@drawable/splash_background" />
1443
+ </item>
1444
+ ` : ""} <item>
1033
1445
  <bitmap android:gravity="center" android:src="@drawable/splash" />
1034
1446
  </item>
1035
1447
  </layer-list>
@@ -1039,19 +1451,25 @@ async function generateSplashScreen(args) {
1039
1451
  <color name="splash_background">${app.splash.backgroundColor}</color>
1040
1452
  </resources>
1041
1453
  `);
1454
+ if (dark) {
1455
+ FSExtra.mkdirSync(path.join(mainRes, "values-night"), { recursive: true });
1456
+ FSExtra.writeFileSync(path.join(mainRes, "values-night", "colors.xml"), `<?xml version="1.0" encoding="utf-8"?>
1457
+ <resources>
1458
+ <color name="splash_background">${dark.backgroundColor}</color>
1459
+ </resources>
1460
+ `);
1461
+ }
1042
1462
  const stylesPath = path.join(mainRes, "values", "styles.xml");
1043
1463
  const styles = FSExtra.readFileSync(stylesPath, "utf8").replace(" <!-- Customize your theme here. -->", " <item name=\"android:windowBackground\">@drawable/launch_screen</item>");
1044
1464
  FSExtra.writeFileSync(stylesPath, styles);
1045
1465
  const stylesV31 = path.join(mainRes, "values-v31");
1046
1466
  FSExtra.mkdirSync(stylesV31, { recursive: true });
1047
- FSExtra.writeFileSync(path.join(stylesV31, "styles.xml"), `<?xml version="1.0" encoding="utf-8"?>
1048
- <resources>
1049
- <style name="AppTheme">
1050
- <item name="android:windowSplashScreenBackground">@color/splash_background</item>
1467
+ if (!styles.includes("</style>")) {
1468
+ throw new Error("[vxrn] prebuild template styles.xml lost its AppTheme </style> anchor");
1469
+ }
1470
+ FSExtra.writeFileSync(path.join(stylesV31, "styles.xml"), styles.replace("</style>", ` <item name="android:windowSplashScreenBackground">@color/splash_background</item>
1051
1471
  <item name="android:windowSplashScreenAnimatedIcon">@drawable/splash</item>
1052
- </style>
1053
- </resources>
1054
- `);
1472
+ </style>`));
1055
1473
  }
1056
1474
  function renderPrebuildFile(args) {
1057
1475
  const { relativePath, content, platform, app, nitroWebImage } = args;
@@ -1095,19 +1513,93 @@ ${schemes.map((scheme) => ` <string>${scheme}</string>`).join("\n")}
1095
1513
  stamps.push(` <key>ITSAppUsesNonExemptEncryption</key>
1096
1514
  <${app.ios.usesNonExemptEncryption ? "true" : "false"}/>`);
1097
1515
  }
1516
+ if (app.ios?.accentColor) {
1517
+ stamps.push(" <key>NSAccentColorName</key>\n <string>AccentColor</string>");
1518
+ }
1098
1519
  if (app.ios?.fileSharing) {
1099
1520
  stamps.push(` <key>UIFileSharingEnabled</key>
1100
1521
  <true/>
1101
1522
  <key>LSSupportsOpeningDocumentsInPlace</key>
1102
1523
  <true/>`);
1103
1524
  }
1525
+ if (app.photoLibrary?.readWrite !== void 0) {
1526
+ stamps.push(" <key>PHPhotoLibraryPreventAutomaticLimitedAccessAlert</key>\n <true/>");
1527
+ }
1104
1528
  if (app.notifications !== void 0) {
1105
- stamps.push(` <key>OneNativeNotificationsEnabled</key>
1529
+ stamps.push(` <key>${notificationsHost.enabledInfoPlistKey}</key>
1530
+ <true/>`);
1531
+ }
1532
+ if (app.notifications?.push === true) {
1533
+ stamps.push(` <key>${notificationsHost.pushInfoPlistKey}</key>
1106
1534
  <true/>`);
1107
1535
  }
1108
1536
  if (app.ios?.widgets) {
1109
1537
  stamps.push(" <key>NSSupportsLiveActivities</key>\n <true/>");
1110
1538
  }
1539
+ const refreshTasks = app.ios?.backgroundTasks?.refresh ?? [];
1540
+ const processingTasks = app.ios?.backgroundTasks?.processing ?? [];
1541
+ if (refreshTasks.length || processingTasks.length) {
1542
+ const plistArray = (key, identifiers) => ` <key>${key}</key>
1543
+ <array>
1544
+ ${identifiers.map((identifier) => ` <string>${escapeXml(identifier)}</string>`).join("\n")}
1545
+ </array>`;
1546
+ stamps.push(plistArray("BGTaskSchedulerPermittedIdentifiers", [...refreshTasks, ...processingTasks]));
1547
+ stamps.push(plistArray("OneBackgroundRefreshTaskIdentifiers", refreshTasks));
1548
+ stamps.push(plistArray("OneBackgroundProcessingTaskIdentifiers", processingTasks));
1549
+ }
1550
+ if (app.ios?.appIntents) {
1551
+ stamps.push(` <key>OneAppIntentIdentifiers</key>
1552
+ <array>
1553
+ ${app.ios.appIntents.actions.map((action) => ` <string>${escapeXml(action.id)}</string>`).join("\n")}
1554
+ </array>`);
1555
+ }
1556
+ const backgroundModes = [
1557
+ ...app.pictureInPicture || app.audio?.background ? ["audio"] : [],
1558
+ ...app.location?.background ? ["location"] : [],
1559
+ ...refreshTasks.length ? ["fetch"] : [],
1560
+ ...processingTasks.length ? ["processing"] : []
1561
+ ];
1562
+ if (backgroundModes.length) {
1563
+ stamps.push(` <key>UIBackgroundModes</key>
1564
+ <array>
1565
+ ${backgroundModes.map((mode) => ` <string>${mode}</string>`).join("\n")}
1566
+ </array>`);
1567
+ }
1568
+ if (app.updates !== void 0) {
1569
+ if (app.updates.url !== void 0) {
1570
+ stamps.push(` <key>${updatesHost.urlInfoPlistKey}</key>
1571
+ <string>${escapeXml(app.updates.url)}</string>`);
1572
+ }
1573
+ stamps.push(` <key>${updatesHost.runtimeVersionInfoPlistKey}</key>
1574
+ <string>${escapeXml(app.updates.runtimeVersion)}</string>`);
1575
+ }
1576
+ if (app.fonts?.length) {
1577
+ stamps.push(` <key>UIAppFonts</key>
1578
+ <array>
1579
+ ${app.fonts.map((font) => ` <string>${escapeXml(fontFileName(font))}</string>`).join("\n")}
1580
+ </array>`);
1581
+ }
1582
+ if (app.userInterfaceStyle !== void 0) {
1583
+ stamps.push(` <key>UIUserInterfaceStyle</key>
1584
+ <string>${app.userInterfaceStyle === "light" ? "Light" : app.userInterfaceStyle === "dark" ? "Dark" : "Automatic"}</string>`);
1585
+ }
1586
+ if (app.orientation !== void 0) {
1587
+ const phoneOrientations = app.orientation === "portrait" ? PORTRAIT_ORIENTATIONS : app.orientation === "landscape" ? LANDSCAPE_ORIENTATIONS : [...PORTRAIT_ORIENTATIONS, ...LANDSCAPE_ORIENTATIONS];
1588
+ const phoneList = /\t<key>UISupportedInterfaceOrientations<\/key>\n\t<array>\n[\s\S]*?\t<\/array>/;
1589
+ if (!phoneList.test(rendered)) {
1590
+ throw new Error(`[vxrn] prebuild template ${relativePath} lost its UISupportedInterfaceOrientations list`);
1591
+ }
1592
+ rendered = rendered.replace(phoneList, ` <key>UISupportedInterfaceOrientations</key>
1593
+ <array>
1594
+ ${phoneOrientations.map((orientation) => ` <string>${orientation}</string>`).join("\n")}
1595
+ </array>`);
1596
+ }
1597
+ for (const [key, value] of Object.entries(app.ios?.infoPlist ?? {})) {
1598
+ if (rendered.includes(`<key>${key}</key>`) || stamps.some((stamp) => stamp.includes(`<key>${key}</key>`))) {
1599
+ throw new Error(`[vxrn] native.app.ios.infoPlist sets ${key}, which the template or native.app already writes: use its field`);
1600
+ }
1601
+ stamps.push(renderPlistEntries({ [key]: value }, " "));
1602
+ }
1111
1603
  if (stamps.length) {
1112
1604
  const anchor = " <key>LSRequiresIPhoneOS</key>";
1113
1605
  if (!rendered.includes(anchor)) throw new Error(`[vxrn] prebuild template ${relativePath} lost its LSRequiresIPhoneOS anchor`);
@@ -1115,40 +1607,163 @@ ${schemes.map((scheme) => ` <string>${scheme}</string>`).join("\n")}
1115
1607
  ${anchor}`);
1116
1608
  }
1117
1609
  }
1118
- if (platform === "android" && relativePath === "app/src/main/AndroidManifest.xml" && app.imagePicker?.camera !== void 0) {
1610
+ if (platform === "android" && relativePath === "app/src/main/AndroidManifest.xml" && (app.imagePicker?.camera !== void 0 || app.speech !== void 0)) {
1611
+ const anchor = "<uses-permission android:name=\"android.permission.INTERNET\" />";
1612
+ if (!rendered.includes(anchor)) {
1613
+ throw new Error("[vxrn] cannot stamp the camera or microphone permission: expected the INTERNET permission in app/src/main/AndroidManifest.xml");
1614
+ }
1615
+ const stamps = [];
1616
+ if (app.imagePicker?.camera !== void 0) {
1617
+ stamps.push(" <uses-permission android:name=\"android.permission.CAMERA\" />");
1618
+ }
1619
+ if (app.speech !== void 0) {
1620
+ stamps.push(" <uses-permission android:name=\"android.permission.RECORD_AUDIO\" />", " <queries>\n <intent>\n <action android:name=\"android.speech.RecognitionService\" />\n </intent>\n </queries>");
1621
+ }
1622
+ rendered = rendered.replace(anchor, `${anchor}
1623
+ ${stamps.join("\n")}`);
1624
+ }
1625
+ if (platform === "android" && relativePath === "app/src/main/AndroidManifest.xml" && app.location !== void 0) {
1626
+ const anchor = "<uses-permission android:name=\"android.permission.INTERNET\" />";
1627
+ if (!rendered.includes(anchor)) {
1628
+ throw new Error("[vxrn] cannot stamp location permissions: expected the INTERNET permission in app/src/main/AndroidManifest.xml");
1629
+ }
1630
+ const stamps = [" <uses-permission android:name=\"android.permission.ACCESS_COARSE_LOCATION\" />", " <uses-permission android:name=\"android.permission.ACCESS_FINE_LOCATION\" />"];
1631
+ if (app.location.background) {
1632
+ stamps.push(" <uses-permission android:name=\"android.permission.ACCESS_BACKGROUND_LOCATION\" />");
1633
+ }
1634
+ rendered = rendered.replace(anchor, `${anchor}
1635
+ ${stamps.join("\n")}`);
1636
+ }
1637
+ if (platform === "android" && relativePath === "app/src/main/AndroidManifest.xml" && app.pedometer !== void 0) {
1119
1638
  const anchor = "<uses-permission android:name=\"android.permission.INTERNET\" />";
1120
1639
  if (!rendered.includes(anchor)) {
1121
- throw new Error("[vxrn] cannot stamp the camera permission: expected the INTERNET permission in app/src/main/AndroidManifest.xml");
1640
+ throw new Error("[vxrn] cannot stamp the pedometer permission: expected the INTERNET permission in app/src/main/AndroidManifest.xml");
1122
1641
  }
1123
1642
  rendered = rendered.replace(anchor, `${anchor}
1124
- <uses-permission android:name="android.permission.CAMERA" />`);
1643
+ <uses-permission android:name="android.permission.ACTIVITY_RECOGNITION" />`);
1125
1644
  }
1126
1645
  if (platform === "android" && relativePath === "app/src/main/AndroidManifest.xml" && app.notifications !== void 0) {
1127
1646
  const anchor = "<uses-permission android:name=\"android.permission.INTERNET\" />";
1128
1647
  if (!rendered.includes(anchor)) {
1129
1648
  throw new Error("[vxrn] cannot stamp notification permissions: expected the INTERNET permission in app/src/main/AndroidManifest.xml");
1130
1649
  }
1131
- rendered = rendered.replace(anchor, `${anchor}
1132
- <uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
1133
- <uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />`);
1134
- if (!rendered.includes("android.permission.POST_NOTIFICATIONS")) {
1650
+ rendered = rendered.replace(anchor, [anchor, ...notificationsHost.androidPermissions.map((permission) => ` <uses-permission android:name="${permission}" />`)].join("\n"));
1651
+ if (!rendered.includes(notificationsHost.androidPermissions[0])) {
1135
1652
  throw new Error("[vxrn] failed to stamp notification permissions into app manifest");
1136
1653
  }
1137
- rendered = rendered.replace(" </activity>\n </application>", " </activity>\n <receiver android:name=\"dev.onejs.onenative.OneNativeNotificationsReceiver\" android:exported=\"false\">\n <intent-filter>\n <action android:name=\"android.intent.action.BOOT_COMPLETED\" />\n </intent-filter>\n </receiver>\n </application>");
1138
- if (!rendered.includes("OneNativeNotificationsReceiver")) {
1654
+ rendered = rendered.replace(" </activity>\n </application>", ` </activity>
1655
+ <receiver android:name="${notificationsHost.receiver}" android:exported="false">
1656
+ <intent-filter>
1657
+ <action android:name="${notificationsHost.receiverAction}" />
1658
+ </intent-filter>
1659
+ </receiver>
1660
+ </application>`);
1661
+ if (!rendered.includes(notificationsHost.receiver)) {
1139
1662
  throw new Error("[vxrn] failed to stamp the notification receiver into app manifest");
1140
1663
  }
1141
1664
  }
1142
1665
  if (platform === "android" && relativePath === "app/src/main/AndroidManifest.xml" && app.notifications?.push === true) {
1143
- const anchor = "dev.onejs.onenative.OneNativeNotificationsReceiver";
1666
+ const anchor = notificationsHost.receiver;
1144
1667
  if (!rendered.includes(anchor)) {
1145
1668
  throw new Error("[vxrn] cannot stamp the push service: expected the notification receiver in app/src/main/AndroidManifest.xml");
1146
1669
  }
1147
- rendered = rendered.replace(" </receiver>\n </application>", " </receiver>\n <service android:name=\"dev.onejs.onenative.OneNativePushService\" android:exported=\"false\">\n <intent-filter>\n <action android:name=\"com.google.firebase.MESSAGING_EVENT\" />\n </intent-filter>\n </service>\n </application>");
1148
- if (!rendered.includes("OneNativePushService")) {
1670
+ rendered = rendered.replace(" </receiver>\n </application>", ` </receiver>
1671
+ <service android:name="${notificationsHost.pushService}" android:exported="false">
1672
+ <intent-filter>
1673
+ <action android:name="${notificationsHost.pushServiceAction}" />
1674
+ </intent-filter>
1675
+ </service>
1676
+ </application>`);
1677
+ if (!rendered.includes(notificationsHost.pushService)) {
1149
1678
  throw new Error("[vxrn] failed to stamp the push service into app manifest");
1150
1679
  }
1151
1680
  }
1681
+ if (platform === "android" && relativePath === "app/src/main/AndroidManifest.xml" && (app.audio !== void 0 || app.photoLibrary !== void 0 || app.contacts !== void 0 || app.calendar !== void 0 || app.ios?.backgroundTasks !== void 0)) {
1682
+ const anchor = "<uses-permission android:name=\"android.permission.INTERNET\" />";
1683
+ if (!rendered.includes(anchor)) {
1684
+ throw new Error("[vxrn] cannot stamp media permissions: expected the INTERNET permission in app/src/main/AndroidManifest.xml");
1685
+ }
1686
+ const stamps = [];
1687
+ if (app.audio?.microphone !== void 0 && !rendered.includes("android.permission.RECORD_AUDIO")) {
1688
+ stamps.push(" <uses-permission android:name=\"android.permission.RECORD_AUDIO\" />");
1689
+ }
1690
+ if (app.photoLibrary?.readWrite !== void 0) {
1691
+ stamps.push(" <uses-permission android:name=\"android.permission.READ_MEDIA_IMAGES\" />", " <uses-permission android:name=\"android.permission.READ_MEDIA_VIDEO\" />", " <uses-permission android:name=\"android.permission.READ_EXTERNAL_STORAGE\" android:maxSdkVersion=\"32\" />");
1692
+ }
1693
+ if (app.photoLibrary?.addOnly !== void 0) {
1694
+ stamps.push(" <uses-permission android:name=\"android.permission.WRITE_EXTERNAL_STORAGE\" android:maxSdkVersion=\"28\" />");
1695
+ }
1696
+ if (app.contacts !== void 0) {
1697
+ stamps.push(" <uses-permission android:name=\"android.permission.READ_CONTACTS\" />", " <uses-permission android:name=\"android.permission.WRITE_CONTACTS\" />");
1698
+ }
1699
+ if (app.calendar?.usage !== void 0) {
1700
+ stamps.push(" <uses-permission android:name=\"android.permission.READ_CALENDAR\" />", " <uses-permission android:name=\"android.permission.WRITE_CALENDAR\" />");
1701
+ }
1702
+ if (app.calendar?.remindersUsage !== void 0) {
1703
+ if (!rendered.includes("android.permission.POST_NOTIFICATIONS")) {
1704
+ stamps.push(" <uses-permission android:name=\"android.permission.POST_NOTIFICATIONS\" />");
1705
+ }
1706
+ if (!rendered.includes("android.permission.RECEIVE_BOOT_COMPLETED")) {
1707
+ stamps.push(" <uses-permission android:name=\"android.permission.RECEIVE_BOOT_COMPLETED\" />");
1708
+ }
1709
+ }
1710
+ if (app.audio?.background === true) {
1711
+ stamps.push(" <uses-permission android:name=\"android.permission.FOREGROUND_SERVICE\" />", " <uses-permission android:name=\"android.permission.FOREGROUND_SERVICE_MEDIA_PLAYBACK\" />");
1712
+ }
1713
+ if (stamps.length) {
1714
+ rendered = rendered.replace(anchor, `${anchor}
1715
+ ${stamps.join("\n")}`);
1716
+ }
1717
+ const appStamps = [];
1718
+ if (app.calendar?.remindersUsage !== void 0) {
1719
+ appStamps.push(" <meta-data android:name=\"one.calendar.reminders\" android:value=\"true\" />");
1720
+ }
1721
+ if (app.photoLibrary?.addOnly !== void 0) {
1722
+ appStamps.push(" <meta-data android:name=\"one.photoLibrary.addOnly\" android:value=\"true\" />");
1723
+ }
1724
+ if (app.audio?.background === true) {
1725
+ appStamps.push(" <meta-data android:name=\"one.audio.background\" android:value=\"true\" />", " <service android:name=\"com.margelo.nitro.one.OneAudioService\" android:exported=\"false\" android:foregroundServiceType=\"mediaPlayback\" />");
1726
+ }
1727
+ if (app.ios?.backgroundTasks !== void 0) {
1728
+ const refreshTasks = app.ios.backgroundTasks.refresh ?? [];
1729
+ const processingTasks = app.ios.backgroundTasks.processing ?? [];
1730
+ if (refreshTasks.length) {
1731
+ appStamps.push(` <meta-data android:name="one.backgroundTasks.refresh" android:value="${escapeXml(refreshTasks.join(","))}" />`);
1732
+ }
1733
+ if (processingTasks.length) {
1734
+ appStamps.push(` <meta-data android:name="one.backgroundTasks.processing" android:value="${escapeXml(processingTasks.join(","))}" />`);
1735
+ }
1736
+ }
1737
+ if (appStamps.length) {
1738
+ const appAnchor = " </application>";
1739
+ if (!rendered.includes(appAnchor)) {
1740
+ throw new Error("[vxrn] cannot stamp media components: expected </application> in app/src/main/AndroidManifest.xml");
1741
+ }
1742
+ rendered = rendered.replace(appAnchor, `${appStamps.join("\n")}
1743
+ ${appAnchor}`);
1744
+ }
1745
+ }
1746
+ if (platform === "android" && relativePath === "app/src/main/java/com/helloworld/MainApplication.kt" && app.updates !== void 0) {
1747
+ rendered = patchAndroidMainApplicationUpdatesHost(rendered);
1748
+ }
1749
+ if (platform === "android" && relativePath === "app/src/main/AndroidManifest.xml" && app.updates !== void 0) {
1750
+ const anchor = " </application>";
1751
+ if (!rendered.includes(anchor)) {
1752
+ throw new Error("[vxrn] cannot stamp the updates config: expected </application> in app/src/main/AndroidManifest.xml");
1753
+ }
1754
+ const stamps = [];
1755
+ if (app.updates.url !== void 0) {
1756
+ stamps.push(` <meta-data android:name="dev.onejs.updates.url" android:value="${escapeXml(app.updates.url)}" />`);
1757
+ }
1758
+ stamps.push(" <meta-data android:name=\"dev.onejs.updates.runtimeVersion\" android:value=\"@string/one_updates_runtime_version\" />");
1759
+ rendered = rendered.replace(anchor, `${stamps.join("\n")}
1760
+ ${anchor}`);
1761
+ }
1762
+ if (platform === "android" && relativePath === "app/src/main/res/values/strings.xml" && app.updates !== void 0) {
1763
+ const version = app.updates.runtimeVersion.replace(/\\/g, "\\\\").replace(/"/g, "\\\"");
1764
+ rendered = rendered.replace("</resources>", ` <string name="one_updates_runtime_version" translatable="false">"${escapeXml(version)}"</string>
1765
+ </resources>`);
1766
+ }
1152
1767
  if (platform === "android" && relativePath === "app/src/main/AndroidManifest.xml" && app.android?.googleMapsApiKey !== void 0) {
1153
1768
  const anchor = " </application>";
1154
1769
  if (!rendered.includes(anchor)) {
@@ -1158,7 +1773,7 @@ ${anchor}`);
1158
1773
  ${anchor}`);
1159
1774
  }
1160
1775
  if (platform === "android" && relativePath === "gradle.properties" && app.notifications?.push === true) {
1161
- const line = "oneNativePush=true";
1776
+ const line = `${notificationsHost.pushGradleProperty}=true`;
1162
1777
  if (!rendered.includes(line)) {
1163
1778
  const trailed = rendered.endsWith("\n") ? rendered : `${rendered}
1164
1779
  `;
@@ -1179,6 +1794,52 @@ ${line}
1179
1794
  `;
1180
1795
  }
1181
1796
  }
1797
+ if (platform === "android" && relativePath === "app/src/main/res/values/styles.xml" && (app.userInterfaceStyle === "light" || app.userInterfaceStyle === "dark")) {
1798
+ const dayNight = "parent=\"Theme.AppCompat.DayNight.NoActionBar\"";
1799
+ if (!rendered.includes(dayNight)) {
1800
+ throw new Error(`[vxrn] cannot lock userInterfaceStyle: expected ${dayNight} in app/src/main/res/values/styles.xml`);
1801
+ }
1802
+ rendered = rendered.replace(dayNight, app.userInterfaceStyle === "light" ? "parent=\"Theme.AppCompat.Light.NoActionBar\"" : "parent=\"Theme.AppCompat.NoActionBar\"");
1803
+ }
1804
+ if (platform === "android" && relativePath === "app/src/main/AndroidManifest.xml" && (app.android?.permissions?.length || app.android?.blockedPermissions?.length)) {
1805
+ const anchor = "<uses-permission android:name=\"android.permission.INTERNET\" />";
1806
+ if (!rendered.includes(anchor)) {
1807
+ throw new Error("[vxrn] cannot stamp permissions: expected the INTERNET permission in app/src/main/AndroidManifest.xml");
1808
+ }
1809
+ const permissionName = (name) => name.includes(".") ? name : `android.permission.${name}`;
1810
+ const lines = [...(app.android?.permissions ?? []).map((name) => ` <uses-permission android:name="${permissionName(name)}" />`), ...(app.android?.blockedPermissions ?? []).map((name) => ` <uses-permission android:name="${permissionName(name)}" tools:node="remove" />`)];
1811
+ rendered = rendered.replace(anchor, `${anchor}
1812
+ ${lines.join("\n")}`);
1813
+ if (app.android?.blockedPermissions?.length && !rendered.includes("xmlns:tools=")) {
1814
+ rendered = rendered.replace("xmlns:android=\"http://schemas.android.com/apk/res/android\"", "xmlns:android=\"http://schemas.android.com/apk/res/android\"\n xmlns:tools=\"http://schemas.android.com/tools\"");
1815
+ }
1816
+ }
1817
+ if (platform === "android" && relativePath === "app/src/main/AndroidManifest.xml" && app.android?.appLinks?.length) {
1818
+ rendered = rendered.replace(" </activity>", ` <intent-filter android:autoVerify="true">
1819
+ <action android:name="android.intent.action.VIEW" />
1820
+ <category android:name="android.intent.category.DEFAULT" />
1821
+ <category android:name="android.intent.category.BROWSABLE" />
1822
+ <data android:scheme="https" />
1823
+ ${app.android.appLinks.map((link) => ` <data android:host="${link.host}"${link.pathPrefix ? ` android:pathPrefix="${link.pathPrefix}"` : ""} />`).join("\n")}
1824
+ </intent-filter>
1825
+ </activity>`);
1826
+ }
1827
+ if (platform === "android" && relativePath === "app/src/main/AndroidManifest.xml" && app.orientation !== void 0) {
1828
+ const anchor = "android:name=\".MainActivity\"";
1829
+ if (!rendered.includes(anchor)) {
1830
+ throw new Error("[vxrn] cannot stamp the orientation: expected .MainActivity in app/src/main/AndroidManifest.xml");
1831
+ }
1832
+ rendered = rendered.replace(anchor, `${anchor}
1833
+ android:screenOrientation="${app.orientation === "default" ? "unspecified" : app.orientation}"`);
1834
+ }
1835
+ if (platform === "android" && relativePath === "app/src/main/AndroidManifest.xml" && app.pictureInPicture) {
1836
+ const anchor = "android:name=\".MainActivity\"";
1837
+ if (!rendered.includes(anchor)) {
1838
+ throw new Error("[vxrn] cannot stamp picture in picture: expected .MainActivity in app/src/main/AndroidManifest.xml");
1839
+ }
1840
+ rendered = rendered.replace(anchor, `${anchor}
1841
+ android:supportsPictureInPicture="true"`);
1842
+ }
1182
1843
  if (platform === "android" && relativePath === "app/src/main/AndroidManifest.xml" && schemes.length) {
1183
1844
  rendered = rendered.replace(" </activity>", ` <intent-filter>
1184
1845
  <action android:name="android.intent.action.VIEW" />
@@ -1199,20 +1860,97 @@ ${schemes.map((scheme) => ` <data android:scheme="${scheme}" />`).joi
1199
1860
  rendered = rendered.replace(/CURRENT_PROJECT_VERSION = [^;]+;/g, `CURRENT_PROJECT_VERSION = ${app.ios.buildNumber};`);
1200
1861
  }
1201
1862
  }
1863
+ if (platform === "ios" && relativePath.endsWith(".xcodeproj/project.pbxproj") && app.ios?.alternateIcons !== void 0) {
1864
+ const anchor = "ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;";
1865
+ if (!rendered.includes(anchor)) {
1866
+ throw new Error("[vxrn] prebuild template lost its primary app icon build setting");
1867
+ }
1868
+ const names = Object.keys(app.ios.alternateIcons).sort().join(" ");
1869
+ rendered = rendered.replaceAll(anchor, `${anchor}
1870
+ ASSETCATALOG_COMPILER_ALTERNATE_APPICON_NAMES = "${names}";`);
1871
+ }
1202
1872
  if (platform === "ios" && relativePath.endsWith("/Info.plist")) {
1873
+ const usage = /* @__PURE__ */ new Map();
1203
1874
  if (app.imagePicker?.camera !== void 0) {
1875
+ usage.set("NSCameraUsageDescription", app.imagePicker.camera);
1876
+ }
1877
+ if (app.imagePicker?.microphone !== void 0) {
1878
+ usage.set("NSMicrophoneUsageDescription", app.imagePicker.microphone);
1879
+ }
1880
+ if (app.photoLibrary !== void 0) {
1881
+ if (app.photoLibrary.addOnly !== void 0) {
1882
+ usage.set("NSPhotoLibraryAddUsageDescription", app.photoLibrary.addOnly);
1883
+ }
1884
+ if (app.photoLibrary.readWrite !== void 0) {
1885
+ usage.set("NSPhotoLibraryUsageDescription", app.photoLibrary.readWrite);
1886
+ }
1887
+ }
1888
+ if (app.contacts !== void 0) {
1889
+ usage.set("NSContactsUsageDescription", app.contacts.usage);
1890
+ }
1891
+ if (app.calendar !== void 0) {
1892
+ if (app.calendar.usage !== void 0) {
1893
+ usage.set("NSCalendarsFullAccessUsageDescription", app.calendar.usage);
1894
+ }
1895
+ if (app.calendar.remindersUsage !== void 0) {
1896
+ usage.set("NSRemindersFullAccessUsageDescription", app.calendar.remindersUsage);
1897
+ }
1898
+ }
1899
+ if (app.location !== void 0) {
1900
+ usage.set("NSLocationWhenInUseUsageDescription", app.location.whenInUse);
1901
+ if (app.location.alwaysAndWhenInUse !== void 0) {
1902
+ usage.set("NSLocationAlwaysAndWhenInUseUsageDescription", app.location.alwaysAndWhenInUse);
1903
+ }
1904
+ }
1905
+ if (app.pedometer !== void 0) {
1906
+ usage.set("NSMotionUsageDescription", app.pedometer.usage);
1907
+ }
1908
+ if (app.ios?.faceIdUsageDescription !== void 0) {
1909
+ usage.set("NSFaceIDUsageDescription", app.ios.faceIdUsageDescription);
1910
+ }
1911
+ if (app.speech !== void 0) {
1912
+ usage.set("NSSpeechRecognitionUsageDescription", app.speech.recognition);
1913
+ usage.set("NSMicrophoneUsageDescription", app.speech.microphone);
1914
+ }
1915
+ if (app.audio?.microphone !== void 0) {
1916
+ usage.set("NSMicrophoneUsageDescription", app.audio.microphone);
1917
+ }
1918
+ const additions = [];
1919
+ for (const [key, text] of usage) {
1920
+ const existing = new RegExp(`(<key>${key}</key>\\s*<string>)[^<]*(</string>)`);
1921
+ if (existing.test(rendered)) {
1922
+ rendered = rendered.replace(existing, (_match, before, after) => `${before}${escapeXml(text)}${after}`);
1923
+ } else {
1924
+ additions.push([key, text]);
1925
+ }
1926
+ }
1927
+ if (additions.length) {
1204
1928
  const anchor = " <key>LSRequiresIPhoneOS</key>";
1205
1929
  if (!rendered.includes(anchor)) {
1206
- throw new Error("[vxrn] cannot stamp NSCameraUsageDescription: expected LSRequiresIPhoneOS in Info.plist");
1930
+ throw new Error(`[vxrn] cannot stamp ${additions[0][0]}: expected LSRequiresIPhoneOS in Info.plist`);
1207
1931
  }
1208
- rendered = rendered.replace(anchor, ` <key>NSCameraUsageDescription</key>
1209
- <string>${escapeXml(app.imagePicker.camera)}</string>
1210
- ${anchor}`);
1932
+ rendered = rendered.replace(anchor, `${additions.map(([key, text]) => ` <key>${key}</key>
1933
+ <string>${escapeXml(text)}</string>
1934
+ `).join("")}${anchor}`);
1211
1935
  }
1936
+ rendered = rendered.replace(/\t<key>NS\w+UsageDescription<\/key>\s*<string><\/string>\n/g, "");
1212
1937
  rendered = patchIosInfoPlistSceneManifest(rendered);
1213
1938
  }
1214
1939
  if (platform === "ios" && relativePath.endsWith("/AppDelegate.swift")) {
1215
- rendered = patchIosAppDelegateSceneLifecycle(rendered, appName);
1940
+ rendered = patchIosAppDelegateSceneLifecycle(rendered, appName, !!app.ios?.backgroundTasks, !!app.ios?.appIntents);
1941
+ rendered = nativeProjectPatches.holdLaunchScreenOverRootView(rendered);
1942
+ const bundleURLProvider = "RCTBundleURLProvider.sharedSettings().jsBundleURL(forBundleRoot: \"index\")";
1943
+ if (!rendered.includes(bundleURLProvider)) {
1944
+ throw new Error("[vxrn] cannot request hermes dev bytecode: expected the template bundle URL provider");
1945
+ }
1946
+ rendered = rendered.replace(bundleURLProvider, `guard let bundleURL = RCTBundleURLProvider.sharedSettings().jsBundleURL(forBundleRoot: "index") else { return nil }
1947
+ if bundleURL.isFileURL { return bundleURL }
1948
+ var components = URLComponents(url: bundleURL, resolvingAgainstBaseURL: false)!
1949
+ components.queryItems!.append(URLQueryItem(name: "bytecode", value: "hermes"))
1950
+ return components.url`);
1951
+ if (app.updates !== void 0) {
1952
+ rendered = nativeProjectPatches.pointReleaseBundleURLAtOneUpdates(rendered);
1953
+ }
1216
1954
  }
1217
1955
  if (platform === "ios" && app.ios?.deploymentTarget) {
1218
1956
  rendered = rendered.replace("platform :ios, min_ios_version_supported", `platform :ios, '${app.ios.deploymentTarget}'`).replace(/IPHONEOS_DEPLOYMENT_TARGET = \d+(?:\.\d+)?;/g, `IPHONEOS_DEPLOYMENT_TARGET = ${app.ios.deploymentTarget};`);
@@ -1229,12 +1967,17 @@ end`);
1229
1967
  }
1230
1968
  }
1231
1969
  if (platform === "ios" && relativePath.endsWith(".xcodeproj/project.pbxproj")) {
1232
- rendered = patchIosBundlePhase(rendered);
1970
+ rendered = patchIosBundlePhase(rendered, app.updates?.runtimeVersion);
1233
1971
  rendered = patchIosPbxprojSceneDelegate(rendered, appName);
1234
- if (app.notifications?.push === true && !app.ios?.widgets) {
1235
- rendered = patchIosPbxprojPushEntitlements(rendered, appName);
1972
+ if (app.ios?.appIntents) rendered = patchIosPbxprojAppIntents(rendered, appName);
1973
+ rendered = patchIosPbxprojOneBridgingHeader(rendered, appName);
1974
+ if (Object.keys(appEntitlements(app)).length && !app.ios?.widgets) {
1975
+ rendered = patchIosPbxprojAppEntitlements(rendered, appName);
1236
1976
  }
1237
1977
  if (app.ios?.widgets) rendered = patchIosPbxprojWidgets(rendered, app);
1978
+ if (iosResourceFiles(app).length) {
1979
+ rendered = patchIosPbxprojResources(rendered, appName, iosResourceFiles(app));
1980
+ }
1238
1981
  }
1239
1982
  if (platform === "ios" && relativePath === "Podfile") {
1240
1983
  if (app.ios?.ccache) rendered = `ENV['USE_CCACHE'] ||= '1'
@@ -1247,9 +1990,15 @@ use_frameworks! :linkage => :${app.ios.useFrameworks}`);
1247
1990
  rendered = nativeProjectPatches.injectReactNativeScreensGammaIntoPodfile(rendered);
1248
1991
  }
1249
1992
  rendered = nativeProjectPatches.injectFmtCxx17FixIntoPodfile(rendered);
1993
+ rendered = nativeProjectPatches.injectWebGPUHeaderPathsIntoPodfile(rendered);
1250
1994
  rendered = nativeProjectPatches.injectOneSwiftPackagesIntoPodfile(rendered);
1251
1995
  if (nitroWebImage) rendered = nativeProjectPatches.injectNitroWebImageModularHeaderIntoPodfile(rendered);
1252
1996
  rendered = nativeProjectPatches.injectHermesMinificationPatchIntoPodfile(rendered);
1997
+ const nativeModulesCall = "config = use_native_modules!";
1998
+ if (rendered.split(nativeModulesCall).length !== 2) {
1999
+ throw new Error("[vxrn] expected one iOS native module discovery call");
2000
+ }
2001
+ rendered = rendered.replace(nativeModulesCall, `config = use_native_modules!(['node', '-e', "process.argv=['', '', 'config'];require(require('module').createRequire(require.resolve('vxrn/package.json')).resolve('@react-native-community/cli')).run()"])`);
1253
2002
  if (!rendered.includes("[vxrn/one] fmt c++17 fix") || !rendered.includes("[vxrn/one] minify iOS Hermes Release bundle input")) {
1254
2003
  throw new Error("[vxrn] failed to apply required iOS Podfile patches");
1255
2004
  }
@@ -1260,6 +2009,9 @@ use_frameworks! :linkage => :${app.ios.useFrameworks}`);
1260
2009
  if (!rendered.includes("entryFile = file(\"../../package.json\")") || !rendered.includes("[vxrn/one] ensure patches are applied")) {
1261
2010
  throw new Error("[vxrn] failed to apply required Android Gradle patches");
1262
2011
  }
2012
+ if (app.updates !== void 0) {
2013
+ rendered = patchAndroidBuildGradleUpdatesManifest(rendered, app.updates.runtimeVersion);
2014
+ }
1263
2015
  if (app.version !== void 0) {
1264
2016
  rendered = rendered.replace(/versionName "[^"]*"/g, `versionName "${app.version}"`);
1265
2017
  }
@@ -1273,9 +2025,57 @@ use_frameworks! :linkage => :${app.ios.useFrameworks}`);
1273
2025
  throw new Error("[vxrn] expected two React Native Gradle plugin paths");
1274
2026
  }
1275
2027
  rendered = rendered.replace(hardcodedGradlePlugin, `includeBuild(new File(["node", "--print", "require('module').createRequire(require.resolve('react-native/package.json')).resolve('@react-native/gradle-plugin/package.json')"].execute(null, settingsDir).text.trim()).parentFile.canonicalPath)`);
2028
+ const autolinkCommand = "ex.autolinkLibrariesFromCommand()";
2029
+ if (rendered.split(autolinkCommand).length !== 2) {
2030
+ throw new Error("[vxrn] expected one React Native autolinking command");
2031
+ }
2032
+ rendered = rendered.replace(autolinkCommand, `ex.autolinkLibrariesFromCommand(["node", ["node", "--print", "require('module').createRequire(require.resolve('vxrn/package.json')).resolve('@react-native-community/cli/build/bin.js')"].execute(null, settingsDir).text.trim(), "config"])`);
2033
+ }
2034
+ if (platform === "android" && relativePath === "app/build.gradle" && app.android?.minify) {
2035
+ const minify = "def enableProguardInReleaseBuilds = false";
2036
+ const minifyEnabled = "minifyEnabled enableProguardInReleaseBuilds";
2037
+ if (!rendered.includes(minify) || !rendered.includes(minifyEnabled)) {
2038
+ throw new Error("[vxrn] prebuild template app/build.gradle lost its proguard switch");
2039
+ }
2040
+ rendered = rendered.replace(minify, "def enableProguardInReleaseBuilds = true");
2041
+ if (app.android.shrinkResources) {
2042
+ rendered = rendered.replace(minifyEnabled, `${minifyEnabled}
2043
+ shrinkResources true`);
2044
+ }
2045
+ }
2046
+ if (platform === "android" && app.android?.googleServicesFile) {
2047
+ const edits = [[
2048
+ "build.gradle",
2049
+ "classpath(\"org.jetbrains.kotlin:kotlin-gradle-plugin\")",
2050
+ ` classpath("com.google.gms:google-services:${GOOGLE_SERVICES_VERSION}")`
2051
+ ], [
2052
+ "app/build.gradle",
2053
+ "apply plugin: \"com.facebook.react\"",
2054
+ "apply plugin: \"com.google.gms.google-services\""
2055
+ ]];
2056
+ for (const [file, anchor, insertion] of edits) {
2057
+ if (relativePath !== file) continue;
2058
+ const next = insertAfterLine(rendered, anchor, insertion);
2059
+ if (next === rendered) {
2060
+ throw new Error(`[vxrn] prebuild template ${file} lost its ${anchor} anchor`);
2061
+ }
2062
+ rendered = next;
2063
+ }
2064
+ }
2065
+ if (platform === "android" && relativePath === "app/proguard-rules.pro" && app.android?.proguardRules) {
2066
+ rendered = `${rendered.trimEnd()}
2067
+
2068
+ ${app.android.proguardRules.trim()}
2069
+ `;
1276
2070
  }
1277
- if (platform === "android" && app.android?.minSdk !== void 0) {
1278
- rendered = rendered.replace(/minSdkVersion = \d+/g, `minSdkVersion = ${app.android.minSdk}`);
2071
+ for (const [field, setting] of [
2072
+ ["minSdk", "minSdkVersion"],
2073
+ ["targetSdk", "targetSdkVersion"],
2074
+ ["compileSdk", "compileSdkVersion"]
2075
+ ]) {
2076
+ const value = platform === "android" ? app.android?.[field] : void 0;
2077
+ if (value === void 0) continue;
2078
+ rendered = rendered.replace(new RegExp(`${setting} = \\d+`, "g"), `${setting} = ${value}`);
1279
2079
  }
1280
2080
  }
1281
2081
  return {
@@ -1287,7 +2087,7 @@ const generateForPlatform = async (root, platform, app, outDir = path.resolve(ro
1287
2087
  validatePrebuildApp(app, platform);
1288
2088
  const dest = outDir;
1289
2089
  const require2 = module.createRequire(root + "/");
1290
- const importPath = require2.resolve("@react-native-community/cli/build/tools/walk.js", { paths: [root] });
2090
+ const importPath = requireVxrn.resolve("@react-native-community/cli/build/tools/walk.js");
1291
2091
  const src = path.join(path.dirname(require2.resolve("@react-native-community/template/template/package.json", { paths: [root] })), platform);
1292
2092
  const walkModule = await import(pathToFileURL(importPath).href);
1293
2093
  const walkFn = walkModule?.default?.default ?? walkModule?.default ?? walkModule;
@@ -1332,48 +2132,184 @@ const generateForPlatform = async (root, platform, app, outDir = path.resolve(ro
1332
2132
  platform,
1333
2133
  app
1334
2134
  });
2135
+ if (platform === "android") await generateAdaptiveIcon({
2136
+ root,
2137
+ dest,
2138
+ app
2139
+ });
1335
2140
  await generateSplashScreen({
1336
2141
  root,
1337
2142
  dest,
1338
2143
  platform,
1339
2144
  app
1340
2145
  });
2146
+ if (platform === "ios") generateAccentColor({
2147
+ dest,
2148
+ app
2149
+ });
2150
+ copyAppResources({
2151
+ root,
2152
+ dest,
2153
+ platform,
2154
+ app
2155
+ });
1341
2156
  generateSceneDelegate({
1342
2157
  dest,
1343
2158
  platform,
1344
2159
  app
1345
2160
  });
2161
+ generateIosAppIntents(dest, platform, app);
2162
+ if (platform === "ios") generateOneBridgingHeader(dest, app);
1346
2163
  if (platform === "ios") generateIosWidgets(dest, app);
1347
- if (platform === "ios") generateSwiftPackages({
2164
+ if (platform === "ios") await generateSwiftPackages({
2165
+ root,
2166
+ dest
2167
+ });
2168
+ if (platform === "android") await generateKotlinSources({
1348
2169
  root,
1349
2170
  dest
1350
2171
  });
1351
2172
  };
1352
- function generateSwiftPackages({ root, dest }) {
2173
+ const KOTLIN_IMPORT_ARTIFACTS = [{
2174
+ importPrefix: "androidx.compose.material.icons",
2175
+ artifact: "androidx.compose.material:material-icons-core",
2176
+ versionKey: "materialIconsVersion",
2177
+ version: "1.7.6"
2178
+ }];
2179
+ function kotlinSourceImports(dir) {
2180
+ const imports = /* @__PURE__ */ new Set();
2181
+ if (!FSExtra.existsSync(dir)) return imports;
2182
+ const visit = (entry) => {
2183
+ if (FSExtra.statSync(entry).isDirectory()) {
2184
+ for (const child of FSExtra.readdirSync(entry)) visit(path.join(entry, child));
2185
+ return;
2186
+ }
2187
+ if (!entry.endsWith(".kt")) return;
2188
+ for (const line of FSExtra.readFileSync(entry, "utf8").split("\n")) {
2189
+ const match = /^\s*import\s+([A-Za-z0-9_.]+)/.exec(line);
2190
+ if (match) imports.add(match[1]);
2191
+ }
2192
+ };
2193
+ visit(dir);
2194
+ return imports;
2195
+ }
2196
+ function enableAppComposeIntegration(dest) {
2197
+ const rootBuildGradlePath = path.join(dest, "build.gradle");
2198
+ if (FSExtra.existsSync(rootBuildGradlePath)) {
2199
+ let content = FSExtra.readFileSync(rootBuildGradlePath, "utf8");
2200
+ const composePluginClasspath = " classpath(\"org.jetbrains.kotlin.plugin.compose:org.jetbrains.kotlin.plugin.compose.gradle.plugin:$kotlinVersion\")";
2201
+ if (!content.includes("org.jetbrains.kotlin.plugin.compose")) {
2202
+ const anchor = content.includes("classpath(\"org.jetbrains.kotlin:kotlin-gradle-plugin\")") ? "classpath(\"org.jetbrains.kotlin:kotlin-gradle-plugin\")" : "classpath \"org.jetbrains.kotlin:kotlin-gradle-plugin\"";
2203
+ content = insertAfterLine(content, anchor, composePluginClasspath);
2204
+ FSExtra.writeFileSync(rootBuildGradlePath, content, "utf8");
2205
+ }
2206
+ }
2207
+ const appBuildGradlePath = path.join(dest, "app/build.gradle");
2208
+ if (FSExtra.existsSync(appBuildGradlePath)) {
2209
+ let content = FSExtra.readFileSync(appBuildGradlePath, "utf8");
2210
+ if (!content.includes("org.jetbrains.kotlin.plugin.compose")) {
2211
+ const pluginAnchor = content.includes("apply plugin: \"org.jetbrains.kotlin.android\"") ? "apply plugin: \"org.jetbrains.kotlin.android\"" : "apply plugin: 'org.jetbrains.kotlin.android'";
2212
+ content = insertAfterLine(content, pluginAnchor, "apply plugin: \"org.jetbrains.kotlin.plugin.compose\"");
2213
+ }
2214
+ if (!content.includes("compose true")) {
2215
+ content = insertAfterLine(content, "android {", " buildFeatures {\n compose true\n }");
2216
+ }
2217
+ if (!content.includes("androidx.compose.ui:ui")) {
2218
+ const composeDeps = [
2219
+ " def composeUiVersion = rootProject.ext.has(\"composeUiVersion\") ? rootProject.ext.get(\"composeUiVersion\") : \"1.11.4\"",
2220
+ " def material3Version = rootProject.ext.has(\"material3Version\") ? rootProject.ext.get(\"material3Version\") : \"1.5.0-alpha17\"",
2221
+ " implementation \"androidx.compose.ui:ui:$composeUiVersion\"",
2222
+ " implementation \"androidx.compose.foundation:foundation:$composeUiVersion\"",
2223
+ " implementation \"androidx.compose.material3:material3:$material3Version\"",
2224
+ " def navigationComposeVersion = rootProject.ext.has(\"navigationComposeVersion\") ? rootProject.ext.get(\"navigationComposeVersion\") : \"2.10.2\"",
2225
+ " implementation \"androidx.navigation:navigation-compose:$navigationComposeVersion\""
2226
+ ].join("\n");
2227
+ const depsAnchor = content.includes("implementation(\"com.facebook.react:react-android\")") ? "implementation(\"com.facebook.react:react-android\")" : "implementation \"com.facebook.react:react-android\"";
2228
+ content = insertAfterLine(content, depsAnchor, composeDeps);
2229
+ }
2230
+ const imports = kotlinSourceImports(path.join(dest, "app/src/main/java/one/source"));
2231
+ for (const rule of KOTLIN_IMPORT_ARTIFACTS) {
2232
+ const needed = [...imports].some((name) => name === rule.importPrefix || name.startsWith(`${rule.importPrefix}.`));
2233
+ if (!needed || content.includes(rule.artifact)) continue;
2234
+ const reactAnchor = content.includes("implementation(\"com.facebook.react:react-android\")") ? "implementation(\"com.facebook.react:react-android\")" : "implementation \"com.facebook.react:react-android\"";
2235
+ const anchor = content.includes("androidx.compose.material3:material3") ? "androidx.compose.material3:material3" : reactAnchor;
2236
+ content = insertAfterLine(content, anchor, [` def ${rule.versionKey} = rootProject.ext.has("${rule.versionKey}") ? rootProject.ext.get("${rule.versionKey}") : "${rule.version}"`, ` implementation "${rule.artifact}:$${rule.versionKey}"`].join("\n"));
2237
+ }
2238
+ FSExtra.writeFileSync(appBuildGradlePath, content, "utf8");
2239
+ }
2240
+ }
2241
+ async function generateKotlinSources({ root, dest }) {
1353
2242
  const skip = /* @__PURE__ */ new Set([
1354
2243
  "node_modules",
1355
2244
  "ios",
1356
2245
  "android",
1357
2246
  "dist",
1358
2247
  "types",
1359
- "build"
2248
+ "build",
2249
+ "tests",
2250
+ "__tests__",
2251
+ "scripts",
2252
+ "evidence"
1360
2253
  ]);
1361
- const packageDirs = [];
1362
- const walk = (dir) => {
1363
- const entries = FSExtra.readdirSync(dir, { withFileTypes: true });
1364
- if (entries.some((entry) => entry.isFile() && entry.name === "Package.swift")) {
1365
- packageDirs.push(dir);
1366
- return;
1367
- }
1368
- for (const entry of entries) {
1369
- if (!entry.isDirectory() || entry.name.startsWith(".") || skip.has(entry.name)) continue;
1370
- walk(path.join(dir, entry.name));
2254
+ const sources = [];
2255
+ const manifest = FSExtra.existsSync(path.join(root, "package.json")) ? FSExtra.readJsonSync(path.join(root, "package.json")) : {};
2256
+ const workspacePatterns = Array.isArray(manifest.workspaces) ? manifest.workspaces : manifest.workspaces?.packages ?? [];
2257
+ const { default: glob } = await import("fast-glob");
2258
+ const workspaceRoots = new Set(glob.sync(workspacePatterns, {
2259
+ cwd: root,
2260
+ absolute: true,
2261
+ onlyDirectories: true,
2262
+ ignore: [
2263
+ "**/node_modules/**",
2264
+ "**/ios/**",
2265
+ "**/android/**"
2266
+ ]
2267
+ }).map((directory) => path.resolve(directory)));
2268
+ const collect = (dir) => {
2269
+ if (dir !== root && FSExtra.existsSync(path.join(dir, "package.json")) && !workspaceRoots.has(dir)) return;
2270
+ for (const entry of FSExtra.readdirSync(dir, { withFileTypes: true })) {
2271
+ if (entry.name.startsWith(".") || skip.has(entry.name)) continue;
2272
+ const source = path.join(dir, entry.name);
2273
+ if (entry.isDirectory()) collect(source);
2274
+ else if (entry.isFile() && entry.name.endsWith(".kt")) sources.push(source);
1371
2275
  }
1372
2276
  };
1373
- walk(root);
1374
- for (const packageDir of packageDirs) {
1375
- const id = swiftPackageId(packageDir);
2277
+ collect(root);
2278
+ const output = path.join(dest, "app/src/main/java/one/source");
2279
+ FSExtra.removeSync(output);
2280
+ if (sources.length === 0) return;
2281
+ const { kotlinSourceId, renderKotlinSourceGlue, writeNativeSourceDeclaration } = await import("../utils/nativeSourceContract.mjs");
2282
+ let hasViews = false;
2283
+ for (const source of sources) {
2284
+ const id = kotlinSourceId(root, source);
2285
+ const target = path.join(output, id);
2286
+ FSExtra.mkdirSync(target, { recursive: true });
2287
+ FSExtra.copyFileSync(source, path.join(target, path.basename(source)));
2288
+ const contract = writeNativeSourceDeclaration(source);
2289
+ if (contract.views.length > 0) hasViews = true;
2290
+ if (contract.modules.length > 0 || contract.views.length > 0) {
2291
+ FSExtra.writeFileSync(path.join(target, `OneNativeSource_${id}.kt`), renderKotlinSourceGlue(id, contract).source);
2292
+ }
2293
+ }
2294
+ if (hasViews) {
2295
+ enableAppComposeIntegration(dest);
2296
+ }
2297
+ }
2298
+ async function generateSwiftPackages({ root, dest }) {
2299
+ const skip = /* @__PURE__ */ new Set([
2300
+ "node_modules",
2301
+ "ios",
2302
+ "android",
2303
+ "dist",
2304
+ "types",
2305
+ "build"
2306
+ ]);
2307
+ const packages = swiftPackageDirectories(root);
2308
+ if (packages.size === 0) return;
2309
+ const { swiftPodManifest, writeSwiftPackageArtifacts } = await import("../utils/nativeSourceContract.mjs");
2310
+ for (const [id, packageDir] of packages) {
1376
2311
  const podDir = path.join(dest, "OneSwiftPackages", id);
2312
+ const manifest = swiftPodManifest(path.join(packageDir, "Package.swift"), FSExtra.readFileSync(path.join(packageDir, "Package.swift"), "utf8"));
1377
2313
  const sources = [];
1378
2314
  const collect = (dir) => {
1379
2315
  for (const entry of FSExtra.readdirSync(dir, { withFileTypes: true })) {
@@ -1384,11 +2320,20 @@ function generateSwiftPackages({ root, dest }) {
1384
2320
  }
1385
2321
  };
1386
2322
  collect(packageDir);
2323
+ const artifacts = writeSwiftPackageArtifacts(packageDir);
2324
+ const contracts = artifacts.contracts;
2325
+ const hasView = contracts.some((contract) => contract.defaultView);
2326
+ if (contracts.filter((contract) => contract.defaultView).length > 1) {
2327
+ throw new Error(`[vxrn] swift package ${packageDir} has more than one @main type`);
2328
+ }
1387
2329
  for (const source of sources) {
1388
2330
  const link = path.join(podDir, "Sources", path.relative(packageDir, source));
1389
2331
  FSExtra.mkdirSync(path.dirname(link), { recursive: true });
1390
2332
  FSExtra.symlinkSync(FSExtra.realpathSync(source), link);
1391
2333
  }
2334
+ if (artifacts.glue) {
2335
+ FSExtra.writeFileSync(path.join(podDir, "Sources", "OneNativeSource.generated.swift"), artifacts.glue);
2336
+ }
1392
2337
  FSExtra.writeFileSync(path.join(podDir, `${id}.podspec`), `Pod::Spec.new do |s|
1393
2338
  s.name = '${id}'
1394
2339
  s.version = '0.0.0'
@@ -1397,15 +2342,15 @@ function generateSwiftPackages({ root, dest }) {
1397
2342
  s.license = 'MIT'
1398
2343
  s.author = 'one'
1399
2344
  s.source = { :path => '.' }
1400
- s.swift_version = '6.0'
2345
+ s.swift_version = '${manifest.languageMode}.0'
1401
2346
  s.source_files = 'Sources/**/*.swift', 'Register.m'
1402
- s.dependency 'VxrnNative'
2347
+ s.dependency 'One'
1403
2348
  s.pod_target_xcconfig = {
1404
- 'OTHER_SWIFT_FLAGS' => '$(inherited) -Xfrontend -import-module -Xfrontend VxrnNative -Xfrontend -entry-point-function-name -Xfrontend ${id}_main',
2349
+ '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` : ""}',
1405
2350
  }
1406
2351
  end
1407
2352
  `);
1408
- FSExtra.writeFileSync(path.join(podDir, "Register.m"), `#import <Foundation/Foundation.h>
2353
+ FSExtra.writeFileSync(path.join(podDir, "Register.m"), hasView ? `#import <Foundation/Foundation.h>
1409
2354
 
1410
2355
  extern int ${id}_main(int argc, char **argv);
1411
2356
  extern void OneSwiftRegisterPackage(const char *name, int (*entry)(int, char **));
@@ -1418,7 +2363,7 @@ extern void OneSwiftRegisterPackage(const char *name, int (*entry)(int, char **)
1418
2363
  OneSwiftRegisterPackage("${id}", ${id}_main);
1419
2364
  }
1420
2365
  @end
1421
- `);
2366
+ ` : "#import <Foundation/Foundation.h>\n");
1422
2367
  }
1423
2368
  }
1424
2369
  function applyAndroidDependencyPatches(args) {
@@ -1427,15 +2372,14 @@ function applyAndroidDependencyPatches(args) {
1427
2372
  return;
1428
2373
  }
1429
2374
  const activityPath = path.join(root, "android", "app", "src", "main", "java", ...app.android.applicationId.split("."), "MainActivity.kt");
1430
- const rendered = nativeProjectPatches.addReactNativeScreensFix(FSExtra.readFileSync(activityPath, "utf8"));
2375
+ const rendered = nativeProjectPatches.holdLaunchScreenInMainActivity(nativeProjectPatches.addReactNativeScreensFix(FSExtra.readFileSync(activityPath, "utf8")));
1431
2376
  if (!rendered.includes("RNScreensFragmentFactory")) {
1432
2377
  throw new Error("[vxrn] failed to apply the react-native-screens activity patch");
1433
2378
  }
1434
2379
  FSExtra.writeFileSync(activityPath, rendered);
1435
2380
  }
1436
2381
  async function getNativeDependencyInventory(root) {
1437
- const require2 = module.createRequire(root + "/");
1438
- const cliConfigPath = require2.resolve("@react-native-community/cli-config", { paths: [root] });
2382
+ const cliConfigPath = requireVxrn.resolve("@react-native-community/cli-config");
1439
2383
  const cliConfig = await import(pathToFileURL(cliConfigPath).href);
1440
2384
  const config = await cliConfig.loadConfigAsync({ projectRoot: root });
1441
2385
  const inventory = [];
@@ -1458,12 +2402,19 @@ async function getNativeDependencyInventory(root) {
1458
2402
  function installNativeDependencies(args) {
1459
2403
  const { root, platform } = args;
1460
2404
  if (!platform || platform === "ios") {
1461
- execFileSync("pod", ["install", `--project-directory=${path.join(root, "ios")}`], { stdio: "inherit" });
2405
+ execFileSync("pod", ["install", `--project-directory=${path.join(root, "ios")}`], {
2406
+ stdio: "inherit",
2407
+ env: {
2408
+ ...process.env,
2409
+ LC_ALL: "en_US.UTF-8",
2410
+ RCT_IGNORE_PODS_DEPRECATION: "1"
2411
+ }
2412
+ });
1462
2413
  }
1463
2414
  }
1464
2415
  const transformPath = (filePath) => {
1465
2416
  return filePath.replace("_BUCK", "BUCK").replace("_gitignore", ".gitignore").replace("_gitattributes", ".gitattributes").replace("_babelrc", ".babelrc").replace("_editorconfig", ".editorconfig").replace("_eslintrc.js", ".eslintrc.js").replace("_flowconfig", ".flowconfig").replace("_buckconfig", ".buckconfig").replace("_prettierrc.js", ".prettierrc.js").replace("_bundle", ".bundle").replace("_ruby-version", ".ruby-version").replace("_node-version", ".node-version").replace("_watchmanconfig", ".watchmanconfig").replace("_xcode.env", ".xcode.env");
1466
2417
  };
1467
2418
 
1468
- export { applyAndroidDependencyPatches, generateForPlatform, getNativeDependencyInventory, installNativeDependencies, renderPrebuildFile, renderSceneDelegateSwift, validatePrebuildApp };
2419
+ export { applyAndroidDependencyPatches, enableAppComposeIntegration, generateForPlatform, generateKotlinSources, getNativeDependencyInventory, installNativeDependencies, renderIosAppIntentsSwift, renderPrebuildFile, renderSceneDelegateSwift, validatePrebuildApp };
1469
2420
  //# sourceMappingURL=prebuildWithoutExpo.mjs.map