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.
- package/dist/backgroundComputation.mjs +145 -0
- package/dist/backgroundComputation.mjs.map +1 -0
- package/dist/backgroundComputation.native.js +163 -0
- package/dist/backgroundComputation.native.js.map +1 -0
- package/dist/backgroundComputationNative.mjs +16 -0
- package/dist/backgroundComputationNative.mjs.map +1 -0
- package/dist/backgroundComputationNative.native.js +18 -0
- package/dist/backgroundComputationNative.native.js.map +1 -0
- package/dist/config/getBaseViteConfigOnly.mjs +6 -10
- package/dist/config/getBaseViteConfigOnly.mjs.map +1 -1
- package/dist/config/getBaseViteConfigOnly.native.js +6 -10
- package/dist/config/getBaseViteConfigOnly.native.js.map +1 -1
- package/dist/config/getOptimizeDeps.mjs +2 -1
- package/dist/config/getOptimizeDeps.mjs.map +1 -1
- package/dist/config/getOptimizeDeps.native.js +2 -1
- package/dist/config/getOptimizeDeps.native.js.map +1 -1
- package/dist/config/getViteServerConfig.mjs +13 -0
- package/dist/config/getViteServerConfig.mjs.map +1 -1
- package/dist/config/getViteServerConfig.native.js +15 -1
- package/dist/config/getViteServerConfig.native.js.map +1 -1
- package/dist/constants/defaults.mjs +4 -0
- package/dist/constants/defaults.mjs.map +1 -1
- package/dist/constants/defaults.native.js +4 -0
- package/dist/constants/defaults.native.js.map +1 -1
- package/dist/exports/build.native.js +1 -1
- package/dist/exports/build.native.js.map +1 -1
- package/dist/exports/clean.native.js +1 -1
- package/dist/exports/clean.native.js.map +1 -1
- package/dist/exports/createServer.native.js +2 -2
- package/dist/exports/createServer.native.js.map +1 -1
- package/dist/exports/dev.mjs +3 -0
- package/dist/exports/dev.mjs.map +1 -1
- package/dist/exports/dev.native.js +6 -3
- package/dist/exports/dev.native.js.map +1 -1
- package/dist/exports/loadEnv.native.js +1 -1
- package/dist/exports/loadEnv.native.js.map +1 -1
- package/dist/exports/patch.native.js +1 -1
- package/dist/exports/patch.native.js.map +1 -1
- package/dist/exports/prebuild.mjs +0 -22
- package/dist/exports/prebuild.mjs.map +1 -1
- package/dist/exports/prebuild.native.js +1 -23
- package/dist/exports/prebuild.native.js.map +1 -1
- package/dist/exports/prebuildWithoutExpo.mjs +1133 -182
- package/dist/exports/prebuildWithoutExpo.mjs.map +1 -1
- package/dist/exports/prebuildWithoutExpo.native.js +1606 -244
- package/dist/exports/prebuildWithoutExpo.native.js.map +1 -1
- package/dist/exports/runAndroid.native.js +1 -1
- package/dist/exports/runAndroid.native.js.map +1 -1
- package/dist/exports/runIos.native.js +1 -1
- package/dist/exports/runIos.native.js.map +1 -1
- package/dist/exports/serve.native.js +1 -1
- package/dist/exports/serve.native.js.map +1 -1
- package/dist/exports/serveStaticAssets.native.js +1 -1
- package/dist/exports/serveStaticAssets.native.js.map +1 -1
- package/dist/index.js +4 -1
- package/dist/index.mjs +4 -1
- package/dist/index.native.js +4 -1
- package/dist/native-source-contract.mjs +3 -0
- package/dist/native-source-contract.native.js +3 -0
- package/dist/patches/builtInDepPatches.mjs +0 -13
- package/dist/patches/builtInDepPatches.mjs.map +1 -1
- package/dist/patches/builtInDepPatches.native.js +0 -13
- package/dist/patches/builtInDepPatches.native.js.map +1 -1
- package/dist/plugins/autoDepOptimizePlugin.mjs +3 -2
- package/dist/plugins/autoDepOptimizePlugin.mjs.map +1 -1
- package/dist/plugins/autoDepOptimizePlugin.native.js +6 -3
- package/dist/plugins/autoDepOptimizePlugin.native.js.map +1 -1
- package/dist/plugins/backgroundComputationPlugin.mjs +27 -0
- package/dist/plugins/backgroundComputationPlugin.mjs.map +1 -0
- package/dist/plugins/backgroundComputationPlugin.native.js +32 -0
- package/dist/plugins/backgroundComputationPlugin.native.js.map +1 -0
- package/dist/plugins/reactNativeCommonJsPlugin.native.js +3 -3
- package/dist/plugins/reactNativeCommonJsPlugin.native.js.map +1 -1
- package/dist/plugins/reactNativeDevAssetPlugin.mjs +2 -1
- package/dist/plugins/reactNativeDevAssetPlugin.mjs.map +1 -1
- package/dist/plugins/reactNativeDevAssetPlugin.native.js +3 -2
- package/dist/plugins/reactNativeDevAssetPlugin.native.js.map +1 -1
- package/dist/plugins/reactNativeDevServer.mjs +87 -24
- package/dist/plugins/reactNativeDevServer.mjs.map +1 -1
- package/dist/plugins/reactNativeDevServer.native.js +162 -45
- package/dist/plugins/reactNativeDevServer.native.js.map +1 -1
- package/dist/plugins/workletImportsPlugin.mjs +9 -30
- package/dist/plugins/workletImportsPlugin.mjs.map +1 -1
- package/dist/plugins/workletImportsPlugin.native.js +10 -35
- package/dist/plugins/workletImportsPlugin.native.js.map +1 -1
- package/dist/rn-commands/bundle/buildBundle.mjs +3 -3
- package/dist/rn-commands/bundle/buildBundle.mjs.map +1 -1
- package/dist/rn-commands/bundle/buildBundle.native.js +3 -3
- package/dist/rn-commands/bundle/buildBundle.native.js.map +1 -1
- package/dist/runtime/hmr-client.native.js +3 -7
- package/dist/runtime/hmr-client.native.js.map +1 -1
- package/dist/runtime/hmr-runtime.native.js +32 -40
- package/dist/runtime/hmr-runtime.native.js.map +1 -1
- package/dist/serve/node.native.js +2 -2
- package/dist/serve/node.native.js.map +1 -1
- package/dist/swift-package-id.mjs +3 -0
- package/dist/swift-package-id.native.js +3 -0
- package/dist/user-interface/index.native.js +6 -6
- package/dist/user-interface/index.native.js.map +1 -1
- package/dist/utils/compileNativeDevBytecode.mjs +93 -0
- package/dist/utils/compileNativeDevBytecode.mjs.map +1 -0
- package/dist/utils/compileNativeDevBytecode.native.js +98 -0
- package/dist/utils/compileNativeDevBytecode.native.js.map +1 -0
- package/dist/utils/createNativeDevEngine.mjs +227 -20
- package/dist/utils/createNativeDevEngine.mjs.map +1 -1
- package/dist/utils/createNativeDevEngine.native.js +249 -37
- package/dist/utils/createNativeDevEngine.native.js.map +1 -1
- package/dist/utils/execSync.native.js +1 -1
- package/dist/utils/execSync.native.js.map +1 -1
- package/dist/utils/getServerEntry.native.js +2 -2
- package/dist/utils/getServerEntry.native.js.map +1 -1
- package/dist/utils/nativeRun.mjs +32 -16
- package/dist/utils/nativeRun.mjs.map +1 -1
- package/dist/utils/nativeRun.native.js +43 -18
- package/dist/utils/nativeRun.native.js.map +1 -1
- package/dist/utils/nativeSourceContract.mjs +1823 -0
- package/dist/utils/nativeSourceContract.mjs.map +1 -0
- package/dist/utils/nativeSourceContract.native.js +2425 -0
- package/dist/utils/nativeSourceContract.native.js.map +1 -0
- package/dist/utils/patches.native.js +15 -24
- package/dist/utils/patches.native.js.map +1 -1
- package/dist/utils/printServerUrls.native.js +1 -1
- package/dist/utils/printServerUrls.native.js.map +1 -1
- package/dist/utils/scanDepsToOptimize.mjs +7 -1
- package/dist/utils/scanDepsToOptimize.mjs.map +1 -1
- package/dist/utils/scanDepsToOptimize.native.js +27 -2
- package/dist/utils/scanDepsToOptimize.native.js.map +1 -1
- package/dist/utils/swiftPackageId.mjs +31 -2
- package/dist/utils/swiftPackageId.mjs.map +1 -1
- package/dist/utils/swiftPackageId.native.js +50 -2
- package/dist/utils/swiftPackageId.native.js.map +1 -1
- package/dist/vxrn-vite-plugin.native.js +1 -1
- package/dist/vxrn-vite-plugin.native.js.map +1 -1
- package/expo-plugin.cjs +792 -2
- package/native-project-patches.cjs +201 -10
- package/package.json +34 -15
- package/src/backgroundComputation.ts +220 -0
- package/src/backgroundComputationNative.ts +23 -0
- package/src/config/getBaseViteConfigOnly.ts +8 -9
- package/src/config/getOptimizeDeps.ts +3 -1
- package/src/config/getViteServerConfig.ts +16 -0
- package/src/config/glbAssets.test.ts +228 -0
- package/src/constants/defaults.ts +6 -0
- package/src/exports/dev.ts +5 -1
- package/src/exports/expoPlugin.test.mjs +337 -15
- package/src/exports/prebuild.ts +0 -31
- package/src/exports/prebuildWithoutExpo.test.ts +1623 -46
- package/src/exports/prebuildWithoutExpo.ts +1606 -216
- package/src/index.ts +8 -0
- package/src/native-source-contract.ts +16 -0
- package/src/patches/builtInDepPatches.ts +0 -21
- package/src/plugins/autoDepOptimizePlugin.ts +7 -2
- package/src/plugins/backgroundComputationPlugin.test.ts +77 -0
- package/src/plugins/backgroundComputationPlugin.ts +29 -0
- package/src/plugins/reactNativeDevAssetPlugin.ts +5 -1
- package/src/plugins/reactNativeDevServer.test.ts +74 -0
- package/src/plugins/reactNativeDevServer.ts +145 -28
- package/src/plugins/rnCodegenPlugin.test.ts +30 -0
- package/src/plugins/workletImportsPlugin.ts +19 -37
- package/src/rn-commands/bundle/buildBundle.ts +5 -3
- package/src/swift-package-id.ts +1 -0
- package/src/utils/compileNativeDevBytecode.test.ts +168 -0
- package/src/utils/compileNativeDevBytecode.ts +117 -0
- package/src/utils/createNativeDevEngine.test.ts +643 -18
- package/src/utils/createNativeDevEngine.ts +278 -48
- package/src/utils/nativeRun.test.ts +125 -23
- package/src/utils/nativeRun.ts +51 -23
- package/src/utils/nativeSourceArtifacts.test.ts +39 -0
- package/src/utils/nativeSourceContract.test.ts +600 -0
- package/src/utils/nativeSourceContract.ts +1897 -0
- package/src/utils/scanDepsToOptimize.test.ts +80 -0
- package/src/utils/scanDepsToOptimize.ts +18 -7
- package/src/utils/swiftPackageId.ts +25 -1
- package/types/backgroundComputation.d.ts +28 -0
- package/types/backgroundComputation.d.ts.map +1 -0
- package/types/backgroundComputationNative.d.ts +2 -0
- package/types/backgroundComputationNative.d.ts.map +1 -0
- package/types/config/getBaseViteConfigOnly.d.ts.map +1 -1
- package/types/config/getOptimizeDeps.d.ts.map +1 -1
- package/types/config/getViteServerConfig.d.ts.map +1 -1
- package/types/constants/defaults.d.ts.map +1 -1
- package/types/exports/dev.d.ts.map +1 -1
- package/types/exports/prebuild.d.ts.map +1 -1
- package/types/exports/prebuildWithoutExpo.d.ts +6 -0
- package/types/exports/prebuildWithoutExpo.d.ts.map +1 -1
- package/types/index.d.ts +2 -0
- package/types/index.d.ts.map +1 -1
- package/types/native-source-contract.d.ts +2 -0
- package/types/native-source-contract.d.ts.map +1 -0
- package/types/patches/builtInDepPatches.d.ts.map +1 -1
- package/types/plugins/autoDepOptimizePlugin.d.ts.map +1 -1
- package/types/plugins/backgroundComputationPlugin.d.ts +3 -0
- package/types/plugins/backgroundComputationPlugin.d.ts.map +1 -0
- package/types/plugins/reactNativeDevAssetPlugin.d.ts.map +1 -1
- package/types/plugins/reactNativeDevServer.d.ts.map +1 -1
- package/types/plugins/workletImportsPlugin.d.ts.map +1 -1
- package/types/rn-commands/bundle/buildBundle.d.ts.map +1 -1
- package/types/swift-package-id.d.ts +2 -0
- package/types/swift-package-id.d.ts.map +1 -0
- package/types/utils/compileNativeDevBytecode.d.ts +3 -0
- package/types/utils/compileNativeDevBytecode.d.ts.map +1 -0
- package/types/utils/createNativeDevEngine.d.ts +7 -22
- package/types/utils/createNativeDevEngine.d.ts.map +1 -1
- package/types/utils/nativeRun.d.ts.map +1 -1
- package/types/utils/nativeSourceContract.d.ts +121 -0
- package/types/utils/nativeSourceContract.d.ts.map +1 -0
- package/types/utils/scanDepsToOptimize.d.ts +1 -0
- package/types/utils/scanDepsToOptimize.d.ts.map +1 -1
- package/types/utils/swiftPackageId.d.ts +1 -0
- 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 {
|
|
9
|
+
import { swiftPackageDirectories } from "../utils/swiftPackageId.mjs";
|
|
9
10
|
|
|
10
|
-
const
|
|
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
|
|
77
|
-
|
|
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
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
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
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
371
|
-
|
|
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"),
|
|
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
|
|
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
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
|
|
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
|
|
943
|
-
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
|
|
949
|
-
|
|
950
|
-
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
|
|
954
|
-
|
|
955
|
-
|
|
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
|
-
|
|
958
|
-
|
|
959
|
-
const
|
|
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="
|
|
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"
|
|
978
|
-
<constraints
|
|
979
|
-
<constraint firstItem="splash-
|
|
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
|
-
|
|
1000
|
-
|
|
1001
|
-
|
|
1002
|
-
const
|
|
1003
|
-
|
|
1004
|
-
|
|
1005
|
-
|
|
1006
|
-
|
|
1007
|
-
|
|
1008
|
-
|
|
1009
|
-
|
|
1010
|
-
|
|
1011
|
-
|
|
1012
|
-
|
|
1013
|
-
|
|
1014
|
-
|
|
1015
|
-
|
|
1016
|
-
|
|
1017
|
-
|
|
1018
|
-
|
|
1019
|
-
|
|
1020
|
-
|
|
1021
|
-
|
|
1022
|
-
|
|
1023
|
-
|
|
1024
|
-
|
|
1025
|
-
|
|
1026
|
-
|
|
1027
|
-
|
|
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
|
-
|
|
1048
|
-
|
|
1049
|
-
|
|
1050
|
-
|
|
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
|
|
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
|
|
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.
|
|
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,
|
|
1132
|
-
|
|
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>",
|
|
1138
|
-
|
|
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 =
|
|
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>",
|
|
1148
|
-
|
|
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 =
|
|
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(
|
|
1930
|
+
throw new Error(`[vxrn] cannot stamp ${additions[0][0]}: expected LSRequiresIPhoneOS in Info.plist`);
|
|
1207
1931
|
}
|
|
1208
|
-
rendered = rendered.replace(anchor, ` <key
|
|
1209
|
-
<string>${escapeXml(
|
|
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.
|
|
1235
|
-
|
|
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
|
-
|
|
1278
|
-
|
|
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 =
|
|
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
|
-
|
|
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
|
|
1362
|
-
const
|
|
1363
|
-
|
|
1364
|
-
|
|
1365
|
-
|
|
1366
|
-
|
|
1367
|
-
|
|
1368
|
-
|
|
1369
|
-
|
|
1370
|
-
|
|
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
|
-
|
|
1374
|
-
|
|
1375
|
-
|
|
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 = '
|
|
2345
|
+
s.swift_version = '${manifest.languageMode}.0'
|
|
1401
2346
|
s.source_files = 'Sources/**/*.swift', 'Register.m'
|
|
1402
|
-
s.dependency '
|
|
2347
|
+
s.dependency 'One'
|
|
1403
2348
|
s.pod_target_xcconfig = {
|
|
1404
|
-
'OTHER_SWIFT_FLAGS' => '$(inherited) -Xfrontend -import-module -Xfrontend
|
|
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
|
|
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")}`], {
|
|
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
|