vxrn 2.0.0-beta.99.1 → 2.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (210) hide show
  1. package/dist/backgroundComputation.mjs +145 -0
  2. package/dist/backgroundComputation.mjs.map +1 -0
  3. package/dist/backgroundComputation.native.js +163 -0
  4. package/dist/backgroundComputation.native.js.map +1 -0
  5. package/dist/backgroundComputationNative.mjs +16 -0
  6. package/dist/backgroundComputationNative.mjs.map +1 -0
  7. package/dist/backgroundComputationNative.native.js +18 -0
  8. package/dist/backgroundComputationNative.native.js.map +1 -0
  9. package/dist/config/getBaseViteConfigOnly.mjs +6 -10
  10. package/dist/config/getBaseViteConfigOnly.mjs.map +1 -1
  11. package/dist/config/getBaseViteConfigOnly.native.js +6 -10
  12. package/dist/config/getBaseViteConfigOnly.native.js.map +1 -1
  13. package/dist/config/getOptimizeDeps.mjs +2 -1
  14. package/dist/config/getOptimizeDeps.mjs.map +1 -1
  15. package/dist/config/getOptimizeDeps.native.js +2 -1
  16. package/dist/config/getOptimizeDeps.native.js.map +1 -1
  17. package/dist/config/getViteServerConfig.mjs +13 -0
  18. package/dist/config/getViteServerConfig.mjs.map +1 -1
  19. package/dist/config/getViteServerConfig.native.js +15 -1
  20. package/dist/config/getViteServerConfig.native.js.map +1 -1
  21. package/dist/constants/defaults.mjs +4 -0
  22. package/dist/constants/defaults.mjs.map +1 -1
  23. package/dist/constants/defaults.native.js +4 -0
  24. package/dist/constants/defaults.native.js.map +1 -1
  25. package/dist/exports/build.native.js +1 -1
  26. package/dist/exports/build.native.js.map +1 -1
  27. package/dist/exports/clean.native.js +1 -1
  28. package/dist/exports/clean.native.js.map +1 -1
  29. package/dist/exports/createServer.native.js +2 -2
  30. package/dist/exports/createServer.native.js.map +1 -1
  31. package/dist/exports/dev.mjs +3 -0
  32. package/dist/exports/dev.mjs.map +1 -1
  33. package/dist/exports/dev.native.js +6 -3
  34. package/dist/exports/dev.native.js.map +1 -1
  35. package/dist/exports/loadEnv.native.js +1 -1
  36. package/dist/exports/loadEnv.native.js.map +1 -1
  37. package/dist/exports/patch.native.js +1 -1
  38. package/dist/exports/patch.native.js.map +1 -1
  39. package/dist/exports/prebuild.mjs +0 -22
  40. package/dist/exports/prebuild.mjs.map +1 -1
  41. package/dist/exports/prebuild.native.js +1 -23
  42. package/dist/exports/prebuild.native.js.map +1 -1
  43. package/dist/exports/prebuildWithoutExpo.mjs +1116 -178
  44. package/dist/exports/prebuildWithoutExpo.mjs.map +1 -1
  45. package/dist/exports/prebuildWithoutExpo.native.js +1592 -243
  46. package/dist/exports/prebuildWithoutExpo.native.js.map +1 -1
  47. package/dist/exports/runAndroid.native.js +1 -1
  48. package/dist/exports/runAndroid.native.js.map +1 -1
  49. package/dist/exports/runIos.native.js +1 -1
  50. package/dist/exports/runIos.native.js.map +1 -1
  51. package/dist/exports/serve.native.js +1 -1
  52. package/dist/exports/serve.native.js.map +1 -1
  53. package/dist/exports/serveStaticAssets.native.js +1 -1
  54. package/dist/exports/serveStaticAssets.native.js.map +1 -1
  55. package/dist/index.js +4 -1
  56. package/dist/index.mjs +4 -1
  57. package/dist/index.native.js +4 -1
  58. package/dist/native-source-contract.mjs +3 -0
  59. package/dist/native-source-contract.native.js +3 -0
  60. package/dist/patches/builtInDepPatches.mjs +0 -13
  61. package/dist/patches/builtInDepPatches.mjs.map +1 -1
  62. package/dist/patches/builtInDepPatches.native.js +0 -13
  63. package/dist/patches/builtInDepPatches.native.js.map +1 -1
  64. package/dist/plugins/autoDepOptimizePlugin.mjs +3 -2
  65. package/dist/plugins/autoDepOptimizePlugin.mjs.map +1 -1
  66. package/dist/plugins/autoDepOptimizePlugin.native.js +6 -3
  67. package/dist/plugins/autoDepOptimizePlugin.native.js.map +1 -1
  68. package/dist/plugins/backgroundComputationPlugin.mjs +27 -0
  69. package/dist/plugins/backgroundComputationPlugin.mjs.map +1 -0
  70. package/dist/plugins/backgroundComputationPlugin.native.js +32 -0
  71. package/dist/plugins/backgroundComputationPlugin.native.js.map +1 -0
  72. package/dist/plugins/reactNativeCommonJsPlugin.native.js +3 -3
  73. package/dist/plugins/reactNativeCommonJsPlugin.native.js.map +1 -1
  74. package/dist/plugins/reactNativeDevAssetPlugin.mjs +2 -1
  75. package/dist/plugins/reactNativeDevAssetPlugin.mjs.map +1 -1
  76. package/dist/plugins/reactNativeDevAssetPlugin.native.js +3 -2
  77. package/dist/plugins/reactNativeDevAssetPlugin.native.js.map +1 -1
  78. package/dist/plugins/reactNativeDevServer.mjs +87 -24
  79. package/dist/plugins/reactNativeDevServer.mjs.map +1 -1
  80. package/dist/plugins/reactNativeDevServer.native.js +162 -45
  81. package/dist/plugins/reactNativeDevServer.native.js.map +1 -1
  82. package/dist/plugins/workletImportsPlugin.mjs +9 -30
  83. package/dist/plugins/workletImportsPlugin.mjs.map +1 -1
  84. package/dist/plugins/workletImportsPlugin.native.js +10 -35
  85. package/dist/plugins/workletImportsPlugin.native.js.map +1 -1
  86. package/dist/rn-commands/bundle/buildBundle.mjs +3 -3
  87. package/dist/rn-commands/bundle/buildBundle.mjs.map +1 -1
  88. package/dist/rn-commands/bundle/buildBundle.native.js +3 -3
  89. package/dist/rn-commands/bundle/buildBundle.native.js.map +1 -1
  90. package/dist/runtime/hmr-client.native.js +3 -7
  91. package/dist/runtime/hmr-client.native.js.map +1 -1
  92. package/dist/runtime/hmr-runtime.native.js +32 -40
  93. package/dist/runtime/hmr-runtime.native.js.map +1 -1
  94. package/dist/serve/node.native.js +2 -2
  95. package/dist/serve/node.native.js.map +1 -1
  96. package/dist/swift-package-id.mjs +3 -0
  97. package/dist/swift-package-id.native.js +3 -0
  98. package/dist/user-interface/index.native.js +6 -6
  99. package/dist/user-interface/index.native.js.map +1 -1
  100. package/dist/utils/compileNativeDevBytecode.mjs +93 -0
  101. package/dist/utils/compileNativeDevBytecode.mjs.map +1 -0
  102. package/dist/utils/compileNativeDevBytecode.native.js +98 -0
  103. package/dist/utils/compileNativeDevBytecode.native.js.map +1 -0
  104. package/dist/utils/createNativeDevEngine.mjs +227 -20
  105. package/dist/utils/createNativeDevEngine.mjs.map +1 -1
  106. package/dist/utils/createNativeDevEngine.native.js +249 -37
  107. package/dist/utils/createNativeDevEngine.native.js.map +1 -1
  108. package/dist/utils/execSync.native.js +1 -1
  109. package/dist/utils/execSync.native.js.map +1 -1
  110. package/dist/utils/getServerEntry.native.js +2 -2
  111. package/dist/utils/getServerEntry.native.js.map +1 -1
  112. package/dist/utils/nativeRun.mjs +32 -16
  113. package/dist/utils/nativeRun.mjs.map +1 -1
  114. package/dist/utils/nativeRun.native.js +43 -18
  115. package/dist/utils/nativeRun.native.js.map +1 -1
  116. package/dist/utils/nativeSourceContract.mjs +1823 -0
  117. package/dist/utils/nativeSourceContract.mjs.map +1 -0
  118. package/dist/utils/nativeSourceContract.native.js +2425 -0
  119. package/dist/utils/nativeSourceContract.native.js.map +1 -0
  120. package/dist/utils/patches.native.js +15 -24
  121. package/dist/utils/patches.native.js.map +1 -1
  122. package/dist/utils/printServerUrls.native.js +1 -1
  123. package/dist/utils/printServerUrls.native.js.map +1 -1
  124. package/dist/utils/scanDepsToOptimize.mjs +7 -1
  125. package/dist/utils/scanDepsToOptimize.mjs.map +1 -1
  126. package/dist/utils/scanDepsToOptimize.native.js +27 -2
  127. package/dist/utils/scanDepsToOptimize.native.js.map +1 -1
  128. package/dist/utils/swiftPackageId.mjs +31 -2
  129. package/dist/utils/swiftPackageId.mjs.map +1 -1
  130. package/dist/utils/swiftPackageId.native.js +50 -2
  131. package/dist/utils/swiftPackageId.native.js.map +1 -1
  132. package/dist/vxrn-vite-plugin.native.js +1 -1
  133. package/dist/vxrn-vite-plugin.native.js.map +1 -1
  134. package/expo-plugin.cjs +792 -2
  135. package/native-project-patches.cjs +201 -10
  136. package/package.json +34 -15
  137. package/src/backgroundComputation.ts +220 -0
  138. package/src/backgroundComputationNative.ts +23 -0
  139. package/src/config/getBaseViteConfigOnly.ts +8 -9
  140. package/src/config/getOptimizeDeps.ts +3 -1
  141. package/src/config/getViteServerConfig.ts +16 -0
  142. package/src/config/glbAssets.test.ts +228 -0
  143. package/src/constants/defaults.ts +6 -0
  144. package/src/exports/dev.ts +5 -1
  145. package/src/exports/expoPlugin.test.mjs +337 -15
  146. package/src/exports/prebuild.ts +0 -31
  147. package/src/exports/prebuildWithoutExpo.test.ts +1588 -48
  148. package/src/exports/prebuildWithoutExpo.ts +1573 -213
  149. package/src/index.ts +8 -0
  150. package/src/native-source-contract.ts +16 -0
  151. package/src/patches/builtInDepPatches.ts +0 -21
  152. package/src/plugins/autoDepOptimizePlugin.ts +7 -2
  153. package/src/plugins/backgroundComputationPlugin.test.ts +77 -0
  154. package/src/plugins/backgroundComputationPlugin.ts +29 -0
  155. package/src/plugins/reactNativeDevAssetPlugin.ts +5 -1
  156. package/src/plugins/reactNativeDevServer.test.ts +74 -0
  157. package/src/plugins/reactNativeDevServer.ts +145 -28
  158. package/src/plugins/rnCodegenPlugin.test.ts +30 -0
  159. package/src/plugins/workletImportsPlugin.ts +19 -37
  160. package/src/rn-commands/bundle/buildBundle.ts +5 -3
  161. package/src/swift-package-id.ts +1 -0
  162. package/src/utils/compileNativeDevBytecode.test.ts +168 -0
  163. package/src/utils/compileNativeDevBytecode.ts +117 -0
  164. package/src/utils/createNativeDevEngine.test.ts +643 -18
  165. package/src/utils/createNativeDevEngine.ts +278 -48
  166. package/src/utils/nativeRun.test.ts +125 -23
  167. package/src/utils/nativeRun.ts +51 -23
  168. package/src/utils/nativeSourceArtifacts.test.ts +39 -0
  169. package/src/utils/nativeSourceContract.test.ts +600 -0
  170. package/src/utils/nativeSourceContract.ts +1897 -0
  171. package/src/utils/scanDepsToOptimize.test.ts +80 -0
  172. package/src/utils/scanDepsToOptimize.ts +18 -7
  173. package/src/utils/swiftPackageId.ts +25 -1
  174. package/types/backgroundComputation.d.ts +28 -0
  175. package/types/backgroundComputation.d.ts.map +1 -0
  176. package/types/backgroundComputationNative.d.ts +2 -0
  177. package/types/backgroundComputationNative.d.ts.map +1 -0
  178. package/types/config/getBaseViteConfigOnly.d.ts.map +1 -1
  179. package/types/config/getOptimizeDeps.d.ts.map +1 -1
  180. package/types/config/getViteServerConfig.d.ts.map +1 -1
  181. package/types/constants/defaults.d.ts.map +1 -1
  182. package/types/exports/dev.d.ts.map +1 -1
  183. package/types/exports/prebuild.d.ts.map +1 -1
  184. package/types/exports/prebuildWithoutExpo.d.ts +6 -0
  185. package/types/exports/prebuildWithoutExpo.d.ts.map +1 -1
  186. package/types/index.d.ts +2 -0
  187. package/types/index.d.ts.map +1 -1
  188. package/types/native-source-contract.d.ts +2 -0
  189. package/types/native-source-contract.d.ts.map +1 -0
  190. package/types/patches/builtInDepPatches.d.ts.map +1 -1
  191. package/types/plugins/autoDepOptimizePlugin.d.ts.map +1 -1
  192. package/types/plugins/backgroundComputationPlugin.d.ts +3 -0
  193. package/types/plugins/backgroundComputationPlugin.d.ts.map +1 -0
  194. package/types/plugins/reactNativeDevAssetPlugin.d.ts.map +1 -1
  195. package/types/plugins/reactNativeDevServer.d.ts.map +1 -1
  196. package/types/plugins/workletImportsPlugin.d.ts.map +1 -1
  197. package/types/rn-commands/bundle/buildBundle.d.ts.map +1 -1
  198. package/types/swift-package-id.d.ts +2 -0
  199. package/types/swift-package-id.d.ts.map +1 -0
  200. package/types/utils/compileNativeDevBytecode.d.ts +3 -0
  201. package/types/utils/compileNativeDevBytecode.d.ts.map +1 -0
  202. package/types/utils/createNativeDevEngine.d.ts +7 -22
  203. package/types/utils/createNativeDevEngine.d.ts.map +1 -1
  204. package/types/utils/nativeRun.d.ts.map +1 -1
  205. package/types/utils/nativeSourceContract.d.ts +121 -0
  206. package/types/utils/nativeSourceContract.d.ts.map +1 -0
  207. package/types/utils/scanDepsToOptimize.d.ts +1 -0
  208. package/types/utils/scanDepsToOptimize.d.ts.map +1 -1
  209. package/types/utils/swiftPackageId.d.ts +1 -0
  210. package/types/utils/swiftPackageId.d.ts.map +1 -1
@@ -1,4 +1,14 @@
1
- import { mkdtempSync, mkdirSync, readFileSync, rmSync, writeFileSync } from 'node:fs'
1
+ import { execFileSync } from 'node:child_process'
2
+ import { createHash } from 'node:crypto'
3
+ import {
4
+ existsSync,
5
+ mkdtempSync,
6
+ mkdirSync,
7
+ readFileSync,
8
+ rmSync,
9
+ symlinkSync,
10
+ writeFileSync,
11
+ } from 'node:fs'
2
12
  import { createRequire } from 'node:module'
3
13
  import { tmpdir } from 'node:os'
4
14
  import { join } from 'node:path'
@@ -35,6 +45,34 @@ end
35
45
  `
36
46
 
37
47
  describe('vxrn Expo project patches', () => {
48
+ it('keeps WebGPU header resolution inside its own CocoaPods target', () => {
49
+ const generated = patches.injectWebGPUHeaderPathsIntoPodfile(podfile)
50
+ expect(patches.injectWebGPUHeaderPathsIntoPodfile(generated)).toBe(generated)
51
+ const result = JSON.parse(execFileSync('ruby', ['-rjson', '-e', `
52
+ Config = Struct.new(:build_settings)
53
+ Target = Struct.new(:name, :build_configurations)
54
+ Project = Struct.new(:targets)
55
+ Installer = Struct.new(:pods_project)
56
+ gpu = Config.new({'USE_HEADERMAP' => 'NO', 'HEADER_SEARCH_PATHS' => ['$(inherited)', '/dawn/include']})
57
+ unrelated = Config.new({'HEADER_SEARCH_PATHS' => '/other/include'})
58
+ $installer = Installer.new(Project.new([Target.new('react-native-webgpu', [gpu]), Target.new('other', [unrelated])]))
59
+ def target(name); yield; end
60
+ def use_native_modules!; {}; end
61
+ def post_install; yield $installer; end
62
+ def react_native_post_install(installer); end
63
+ eval(STDIN.read)
64
+ puts JSON.generate([gpu.build_settings, unrelated.build_settings])
65
+ `], { input: generated, encoding: 'utf8' }))
66
+ expect(result[0].USE_HEADERMAP).toBe('NO')
67
+ expect(result[0].HEADER_SEARCH_PATHS).toEqual([
68
+ '$(inherited)', '/dawn/include',
69
+ '$(PODS_TARGET_SRCROOT)/cpp/rnwgpu',
70
+ '$(PODS_TARGET_SRCROOT)/cpp/rnwgpu/api',
71
+ '$(PODS_TARGET_SRCROOT)/cpp/rnwgpu/api/descriptors',
72
+ '$(PODS_TARGET_SRCROOT)/cpp/jsi',
73
+ ])
74
+ expect(result[1]).toEqual({ HEADER_SEARCH_PATHS: '/other/include' })
75
+ })
38
76
  it('replaces Expo bundle defaults with One bundle configuration', () => {
39
77
  let result =
40
78
  patches.removeExpoDefaultsFromBundleReactNativeShellScript(expoBundlePhase)
@@ -56,10 +94,13 @@ describe('vxrn Expo project patches', () => {
56
94
  let result = patches.replaceAppBuildGradleReactBlock(input)
57
95
  result = patches.addDepsPatchToAppBuildGradle(result)
58
96
 
59
- expect(result).toContain('cliFile = new File(')
97
+ expect(result).toContain('cliFile = file(providers.exec {')
60
98
  expect(result).toContain('autolinkLibrariesWithApp()')
61
99
  expect(result).not.toContain('entryFile = file("../index.js")')
62
100
  expect(result).toContain('[vxrn/one] ensure patches are applied')
101
+ expect(patches.addDepsPatchToAppBuildGradle(
102
+ patches.replaceAppBuildGradleReactBlock(result)
103
+ )).toBe(result)
63
104
  })
64
105
 
65
106
  it('applies the shared Podfile patches idempotently', () => {
@@ -92,24 +133,117 @@ class MainActivity : ReactActivity() {
92
133
  })
93
134
  })
94
135
 
136
+ function expoProject() {
137
+ const projectRoot = mkdtempSync(join(tmpdir(), 'vxrn-expo-plugin-'))
138
+ temporaryDirectories.push(projectRoot)
139
+ writeFileSync(join(projectRoot, 'package.json'), '{"private":true}')
140
+ const configPluginsRoot = createRequire(import.meta.url).resolve(
141
+ '@expo/config-plugins/package.json'
142
+ )
143
+ const scopedDirectory = join(projectRoot, 'node_modules', '@expo')
144
+ mkdirSync(scopedDirectory, { recursive: true })
145
+ symlinkSync(
146
+ configPluginsRoot.slice(0, -'/package.json'.length),
147
+ join(scopedDirectory, 'config-plugins'),
148
+ 'dir'
149
+ )
150
+ return projectRoot
151
+ }
152
+
95
153
  describe('vxrn/expo-plugin', () => {
154
+ it('combines background location and audio modes', async () => {
155
+ const projectRoot = expoProject()
156
+ const config = withVxrn(
157
+ { name: 'TestApp', slug: 'test-app', _internal: { projectRoot } },
158
+ { location: { whenInUse: 'Track a hike.', background: true }, audio: { background: true } }
159
+ )
160
+ const result = await config.mods.ios.infoPlist({
161
+ ...config,
162
+ modRequest: { projectRoot, platform: 'ios', modName: 'infoPlist', projectName: 'TestApp', introspect: false },
163
+ modResults: {},
164
+ })
165
+ expect(result.modResults.NSLocationWhenInUseUsageDescription).toBe('Track a hike.')
166
+ expect(result.modResults.UIBackgroundModes).toEqual(['audio', 'location'])
167
+ })
168
+
169
+ it('stamps separate Photos save and read usage descriptions', async () => {
170
+ const projectRoot = expoProject()
171
+ const config = withVxrn(
172
+ { name: 'TestApp', slug: 'test-app', _internal: { projectRoot } },
173
+ { photoLibrary: { addOnly: 'Save a photo.', readWrite: 'Browse photos.' } }
174
+ )
175
+ const result = await config.mods.ios.infoPlist({
176
+ ...config,
177
+ modRequest: { projectRoot, platform: 'ios', modName: 'infoPlist', projectName: 'TestApp', introspect: false },
178
+ modResults: {},
179
+ })
180
+ expect(result.modResults.NSPhotoLibraryAddUsageDescription).toBe('Save a photo.')
181
+ expect(result.modResults.NSPhotoLibraryUsageDescription).toBe('Browse photos.')
182
+ expect(result.modResults.PHPhotoLibraryPreventAutomaticLimitedAccessAlert).toBe(true)
183
+ })
184
+
185
+ it('stamps the same notification entries as one prebuild', async () => {
186
+ const projectRoot = expoProject()
187
+ const config = withVxrn(
188
+ {
189
+ name: 'TestApp',
190
+ slug: 'test-app',
191
+ _internal: { projectRoot },
192
+ },
193
+ { notifications: { push: true } }
194
+ )
195
+ const modRequest = { projectRoot, introspect: false, projectName: 'TestApp' }
196
+ const run = (platform, mod, modResults) =>
197
+ config.mods[platform][mod]({
198
+ ...config,
199
+ modRequest: { ...modRequest, platform, modName: mod },
200
+ modResults,
201
+ })
202
+
203
+ const infoPlist = (await run('ios', 'infoPlist', {})).modResults
204
+ expect(infoPlist[patches.ONE_NOTIFICATIONS.enabledInfoPlistKey]).toBe(true)
205
+ expect(infoPlist[patches.ONE_NOTIFICATIONS.pushInfoPlistKey]).toBe(true)
206
+ const entitlements = (await run('ios', 'entitlements', {})).modResults
207
+ expect(entitlements['aps-environment']).toBe('development')
208
+
209
+ const manifest = {
210
+ manifest: {
211
+ $: {},
212
+ 'uses-permission': [{ $: { 'android:name': 'android.permission.INTERNET' } }],
213
+ application: [{ $: { 'android:name': '.MainApplication' }, activity: [] }],
214
+ },
215
+ }
216
+ const once = (await run('android', 'manifest', manifest)).modResults
217
+ const twice = (await run('android', 'manifest', once)).modResults
218
+ const application = twice.manifest.application[0]
219
+ expect(twice.manifest['uses-permission'].map((p) => p.$['android:name'])).toEqual([
220
+ 'android.permission.INTERNET',
221
+ ...patches.ONE_NOTIFICATIONS.androidPermissions,
222
+ ])
223
+ expect(application.receiver.map((r) => r.$['android:name'])).toEqual([
224
+ patches.ONE_NOTIFICATIONS.receiver,
225
+ ])
226
+ expect(application.service.map((r) => r.$['android:name'])).toEqual([
227
+ patches.ONE_NOTIFICATIONS.pushService,
228
+ ])
229
+
230
+ const properties = (
231
+ await run('android', 'gradleProperties', [
232
+ { type: 'property', key: 'hermesEnabled', value: 'true' },
233
+ ])
234
+ ).modResults
235
+ expect(properties).toContainEqual({
236
+ type: 'property',
237
+ key: patches.ONE_NOTIFICATIONS.pushGradleProperty,
238
+ value: 'true',
239
+ })
240
+ })
241
+
96
242
  it('resolves Expo config plugins from the app and binds the dangerous mod', async () => {
97
- const projectRoot = mkdtempSync(join(tmpdir(), 'vxrn-expo-plugin-'))
98
- temporaryDirectories.push(projectRoot)
243
+ const projectRoot = expoProject()
99
244
  const iosRoot = join(projectRoot, 'ios')
100
245
  mkdirSync(iosRoot, { recursive: true })
101
246
  writeFileSync(join(iosRoot, 'Podfile'), podfile)
102
- writeFileSync(join(projectRoot, 'package.json'), '{"private":true}')
103
- const workspaceRequire = createRequire(import.meta.url)
104
- const configPluginsRoot = workspaceRequire.resolve(
105
- '@expo/config-plugins/package.json'
106
- )
107
- const scopedDirectory = join(projectRoot, 'node_modules', '@expo')
108
- mkdirSync(scopedDirectory, { recursive: true })
109
- const packageDirectory = configPluginsRoot.slice(0, -'/package.json'.length)
110
- await import('node:fs/promises').then(({ symlink }) =>
111
- symlink(packageDirectory, join(scopedDirectory, 'config-plugins'), 'dir')
112
- )
113
247
 
114
248
  const config = withVxrn({
115
249
  name: 'TestApp',
@@ -137,3 +271,191 @@ describe('vxrn/expo-plugin', () => {
137
271
  expect(result).toContain("ENV['RNS_GAMMA_ENABLED'] ||= '1'")
138
272
  })
139
273
  })
274
+
275
+ function nativeSourceProject() {
276
+ const projectRoot = expoProject()
277
+ const packageDir = join(projectRoot, 'native', 'Showcase')
278
+ mkdirSync(packageDir, { recursive: true })
279
+ writeFileSync(
280
+ join(packageDir, 'Package.swift'),
281
+ '// swift-tools-version: 6.2\nimport PackageDescription\n\nlet package = Package(\n name: "Showcase",\n platforms: [.iOS(.v17)],\n targets: [.executableTarget(name: "Showcase", path: ".", swiftSettings: [.swiftLanguageMode(.v6)])],\n)\n'
282
+ )
283
+ writeFileSync(
284
+ join(packageDir, 'Device.swift'),
285
+ '@MainActor final class Device: PeachModule {\n init() {}\n\n func info() -> [String: String] {\n ["model": "expo"]\n }\n}\n'
286
+ )
287
+ writeFileSync(
288
+ join(projectRoot, 'Device.kt'),
289
+ 'object Device : OneModule {\n fun info(): Map<String, String> = mapOf("model" to "expo")\n}\n'
290
+ )
291
+ const iosRoot = join(projectRoot, 'ios')
292
+ const androidRoot = join(projectRoot, 'android')
293
+ mkdirSync(iosRoot, { recursive: true })
294
+ mkdirSync(join(androidRoot, 'app'), { recursive: true })
295
+ writeFileSync(join(iosRoot, 'Podfile'), podfile)
296
+ return { projectRoot, iosRoot, androidRoot, packageDir }
297
+ }
298
+
299
+ function dangerousRequest(platformProjectRoot, projectRoot, platform) {
300
+ return {
301
+ platformProjectRoot,
302
+ projectRoot,
303
+ platform,
304
+ modName: 'dangerous',
305
+ projectName: 'TestApp',
306
+ introspect: false,
307
+ }
308
+ }
309
+
310
+ describe('vxrn/expo-plugin nativeSource', () => {
311
+ it('rejects a non-boolean nativeSource option', () => {
312
+ const projectRoot = expoProject()
313
+ expect(() =>
314
+ withVxrn(
315
+ { name: 'TestApp', slug: 'test-app', _internal: { projectRoot } },
316
+ { nativeSource: 'yes' }
317
+ )
318
+ ).toThrow('nativeSource must be a boolean')
319
+ })
320
+
321
+ it('leaves the project alone when nativeSource is off', async () => {
322
+ const { projectRoot, iosRoot, androidRoot } = nativeSourceProject()
323
+ const config = withVxrn({
324
+ name: 'TestApp',
325
+ slug: 'test-app',
326
+ _internal: { projectRoot },
327
+ })
328
+ await config.mods.ios.dangerous({
329
+ ...config,
330
+ modRequest: dangerousRequest(iosRoot, projectRoot, 'ios'),
331
+ modResults: {},
332
+ })
333
+ expect(existsSync(join(iosRoot, 'OneSwiftPackages'))).toBe(false)
334
+ expect(existsSync(join(androidRoot, 'app/src/main/java/one/source'))).toBe(false)
335
+ expect(readFileSync(join(iosRoot, 'Podfile'), 'utf8')).not.toContain('OneSwiftPackages')
336
+ })
337
+
338
+ it('generates swift pods and kotlin sources into the expo project', async () => {
339
+ const { projectRoot, iosRoot, androidRoot } = nativeSourceProject()
340
+ const config = withVxrn(
341
+ { name: 'TestApp', slug: 'test-app', _internal: { projectRoot } },
342
+ { nativeSource: true }
343
+ )
344
+ const iosMod = (modResults) =>
345
+ config.mods.ios.dangerous({
346
+ ...config,
347
+ modRequest: dangerousRequest(iosRoot, projectRoot, 'ios'),
348
+ modResults,
349
+ })
350
+ await iosMod({})
351
+ await config.mods.android.dangerous({
352
+ ...config,
353
+ modRequest: dangerousRequest(androidRoot, projectRoot, 'android'),
354
+ modResults: {},
355
+ })
356
+
357
+ // the pod exists and carries the module
358
+ const podDir = join(iosRoot, 'OneSwiftPackages', 'Showcase')
359
+ const podspec = readFileSync(join(podDir, 'Showcase.podspec'), 'utf8')
360
+ expect(podspec).toContain("s.dependency 'One'")
361
+ expect(podspec).toContain("s.swift_version = '6.0'")
362
+ // the sources compile as one pod with the generated glue
363
+ expect(readFileSync(join(podDir, 'Sources', 'Device.swift'), 'utf8')).toContain(
364
+ 'PeachModule'
365
+ )
366
+ const glue = readFileSync(join(podDir, 'Sources', 'OneNativeSource.generated.swift'), 'utf8')
367
+ expect(glue).toContain('Showcase.Device')
368
+ expect(glue).toContain('case "info"')
369
+ // the pod is linked into the app target, idempotently
370
+ const once = readFileSync(join(iosRoot, 'Podfile'), 'utf8')
371
+ expect(once).toContain('OneSwiftPackages')
372
+ await iosMod({})
373
+ expect(readFileSync(join(iosRoot, 'Podfile'), 'utf8')).toBe(once)
374
+
375
+ // the kotlin sources land with their glue
376
+ const kotlinId = `K_${createHash('sha256').update('Device.kt').digest('hex').slice(0, 16)}`
377
+ const kotlinDir = join(androidRoot, 'app/src/main/java/one/source', kotlinId)
378
+ expect(readFileSync(join(kotlinDir, 'Device.kt'), 'utf8')).toContain('OneModule')
379
+ const kotlinGlue = readFileSync(join(kotlinDir, `OneNativeSource_${kotlinId}.kt`), 'utf8')
380
+ expect(kotlinGlue).toContain(`${kotlinId}.Device`)
381
+ expect(kotlinGlue).toContain('"info"')
382
+ // declarations sit beside the sources for typescript
383
+ expect(readFileSync(join(projectRoot, 'Device.d.kt.ts'), 'utf8')).toContain('Device')
384
+ expect(readFileSync(join(projectRoot, 'native/Showcase/Device.d.swift.ts'), 'utf8')).toContain(
385
+ 'Device'
386
+ )
387
+ })
388
+
389
+ it('wires compose dependencies for kotlin views and fails fast without anchors', async () => {
390
+ const projectRoot = expoProject()
391
+ writeFileSync(
392
+ join(projectRoot, 'Greeting.kt'),
393
+ 'import androidx.compose.material3.Text\nimport androidx.compose.runtime.Composable\n\n@Composable\nfun Greeting(name: String) {\n Text(name)\n}\n'
394
+ )
395
+ const androidRoot = join(projectRoot, 'android')
396
+ mkdirSync(join(androidRoot, 'app'), { recursive: true })
397
+ writeFileSync(
398
+ join(androidRoot, 'build.gradle'),
399
+ 'buildscript {\n dependencies {\n classpath("org.jetbrains.kotlin:kotlin-gradle-plugin")\n }\n}\n'
400
+ )
401
+ const appGradle = join(androidRoot, 'app/build.gradle')
402
+ writeFileSync(
403
+ appGradle,
404
+ 'apply plugin: "com.android.application"\napply plugin: "org.jetbrains.kotlin.android"\nandroid {\n}\ndependencies {\n implementation("com.facebook.react:react-android")\n}\n'
405
+ )
406
+ const config = withVxrn(
407
+ { name: 'TestApp', slug: 'test-app', _internal: { projectRoot } },
408
+ { nativeSource: true }
409
+ )
410
+ const run = () =>
411
+ config.mods.android.dangerous({
412
+ ...config,
413
+ modRequest: dangerousRequest(androidRoot, projectRoot, 'android'),
414
+ modResults: {},
415
+ })
416
+ await run()
417
+ const once = readFileSync(appGradle, 'utf8')
418
+ expect(once).toContain('org.jetbrains.kotlin.plugin.compose')
419
+ expect(once).toContain('compose true')
420
+ expect(once).toContain('androidx.compose.ui:ui')
421
+ await run()
422
+ expect(readFileSync(appGradle, 'utf8')).toBe(once)
423
+
424
+ writeFileSync(appGradle, 'android {\n}\n')
425
+ await expect(run()).rejects.toThrow('no kotlin android plugin anchor')
426
+ })
427
+
428
+ it('adds material-icons-core only when a kotlin source imports material icons', async () => {
429
+ const projectRoot = expoProject()
430
+ writeFileSync(
431
+ join(projectRoot, 'Greeting.kt'),
432
+ 'import androidx.compose.material.icons.Icons\nimport androidx.compose.material.icons.filled.Add\nimport androidx.compose.runtime.Composable\n\n@Composable\nfun Greeting(name: String) {\n}\n'
433
+ )
434
+ const androidRoot = join(projectRoot, 'android')
435
+ mkdirSync(join(androidRoot, 'app'), { recursive: true })
436
+ writeFileSync(
437
+ join(androidRoot, 'build.gradle'),
438
+ 'buildscript {\n dependencies {\n classpath("org.jetbrains.kotlin:kotlin-gradle-plugin")\n }\n}\n'
439
+ )
440
+ const appGradle = join(androidRoot, 'app/build.gradle')
441
+ writeFileSync(
442
+ appGradle,
443
+ 'apply plugin: "com.android.application"\napply plugin: "org.jetbrains.kotlin.android"\nandroid {\n}\ndependencies {\n implementation("com.facebook.react:react-android")\n}\n'
444
+ )
445
+ const config = withVxrn(
446
+ { name: 'TestApp', slug: 'test-app', _internal: { projectRoot } },
447
+ { nativeSource: true }
448
+ )
449
+ const run = () =>
450
+ config.mods.android.dangerous({
451
+ ...config,
452
+ modRequest: dangerousRequest(androidRoot, projectRoot, 'android'),
453
+ modResults: {},
454
+ })
455
+ await run()
456
+ const once = readFileSync(appGradle, 'utf8')
457
+ expect(once).toContain('androidx.compose.material:material-icons-core:$materialIconsVersion')
458
+ await run()
459
+ expect(readFileSync(appGradle, 'utf8')).toBe(once)
460
+ })
461
+ })
@@ -1,8 +1,6 @@
1
- import { resolvePath } from '@vxrn/resolve'
2
1
  import { detectPackageManager, type PackageManagerName } from '@vxrn/utils'
3
2
  import FSExtra from 'fs-extra'
4
3
  import path from 'node:path'
5
- import colors from 'picocolors'
6
4
  import { fillOptions } from '../config/getOptionsFilled'
7
5
  import { applyBuiltInPatches } from '../utils/patches'
8
6
  import {
@@ -115,35 +113,6 @@ Android:
115
113
  `
116
114
  )
117
115
  }
118
-
119
- // See: https://github.com/facebook/react-native/pull/45464
120
- try {
121
- resolvePath('@react-native-community/cli', root)
122
- } catch (e) {
123
- if (isMissingCliDependency(e)) {
124
- warnMissingCliDependency()
125
- } else {
126
- throw e
127
- }
128
- }
129
- }
130
-
131
- function isMissingCliDependency(error) {
132
- return (
133
- error.code === 'MODULE_NOT_FOUND' &&
134
- /@react-native-community\/cli/.test(error.message)
135
- )
136
- }
137
-
138
- function warnMissingCliDependency() {
139
- console.warn(`
140
- ${colors.red('⚠')}️ To build the app, ${colors.dim('react-native')} depends on ${colors.dim('@react-native-community/cli')} for cli commands. Please update your ${colors.dim('package.json')} to include:
141
- ${colors.white(
142
- colors.bold(`
143
- "devDependencies": {
144
- "@react-native-community/cli": "latest",
145
- }`)
146
- )}`)
147
116
  }
148
117
 
149
118
  export async function replaceInUTF8File(