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