peachsim 0.1.1625 → 0.1.1635

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 (152) hide show
  1. package/LICENSE +1 -1
  2. package/README.md +10 -1
  3. package/cli/app-config.ts +4 -1
  4. package/cli/app-fonts.ts +37 -17
  5. package/cli/app-stubs.ts +224 -9
  6. package/cli/bridge-flow-runner.ts +64 -43
  7. package/cli/command-registry.ts +1 -0
  8. package/cli/commands/control.ts +283 -49
  9. package/cli/commands/doctor.ts +17 -184
  10. package/cli/commands/flow.ts +79 -11
  11. package/cli/commands/inspect/core.ts +10 -5
  12. package/cli/commands/inspect/settling.ts +14 -2
  13. package/cli/commands/inspect/wait-ready.ts +12 -0
  14. package/cli/commands/inspect.ts +26 -3
  15. package/cli/commands/install-desktop.ts +1 -0
  16. package/cli/commands/maestro-compare.ts +97 -38
  17. package/cli/commands/maestro.ts +9 -1
  18. package/cli/commands/record.ts +21 -1
  19. package/cli/commands/screenshot.ts +23 -1
  20. package/cli/commands/serve.ts +7 -2
  21. package/cli/commands/setup.ts +19 -42
  22. package/cli/commands/swift.ts +5 -0
  23. package/cli/commands/upload.ts +46 -7
  24. package/cli/desktop-companion.ts +102 -17
  25. package/cli/drivers/desktop.ts +14 -1
  26. package/cli/drivers/playwright-sim-host.ts +38 -3
  27. package/cli/drivers/playwright.ts +9 -8
  28. package/cli/drivers/types.ts +5 -0
  29. package/cli/maestro-accessibility.ts +1 -1
  30. package/cli/maestro-compare.ts +84 -12
  31. package/cli/main.ts +2 -1
  32. package/cli/outbound-endpoints.ts +13 -1
  33. package/cli/pause-aware-timeout.ts +102 -0
  34. package/cli/scene-video.ts +89 -0
  35. package/cli/ws-bridge.ts +23 -14
  36. package/detox/attempt-events.cjs +4 -0
  37. package/detox/index.ts +167 -142
  38. package/detox/motion-change.cjs +57 -21
  39. package/detox/motion-change.d.cts +7 -2
  40. package/dist-lib/agent-daemon-client.cjs +1 -1
  41. package/dist-lib/agent-events.cjs +1 -1
  42. package/dist-lib/attached-projects.cjs +1 -1
  43. package/dist-lib/auth/shared-session.cjs +1 -1
  44. package/dist-lib/backend-origin.cjs +1 -1
  45. package/dist-lib/beta.cjs +1 -1
  46. package/dist-lib/beta.mjs +1 -1
  47. package/dist-lib/bridge-constants.cjs +13 -1
  48. package/dist-lib/bridge-contract-input.cjs +1 -1
  49. package/dist-lib/bridge-contract-input.mjs +1 -1
  50. package/dist-lib/bridge-contract.cjs +14 -8
  51. package/dist-lib/bridge-contract.mjs +13 -7
  52. package/dist-lib/bundle-channel.cjs +9536 -7604
  53. package/dist-lib/capture-contract.cjs +1 -1
  54. package/dist-lib/capture-contract.mjs +1 -1
  55. package/dist-lib/cloud-contract.cjs +1 -1
  56. package/dist-lib/cloud-contract.mjs +1 -1
  57. package/dist-lib/cloud-test-args.cjs +1 -1
  58. package/dist-lib/cloud-test-args.mjs +1 -1
  59. package/dist-lib/cloud.cjs +1 -1
  60. package/dist-lib/cloud.mjs +1 -1
  61. package/dist-lib/config.cjs +6 -3
  62. package/dist-lib/config.mjs +6 -3
  63. package/dist-lib/detox/index.cjs +895 -664
  64. package/dist-lib/dev-bundle-resolution.cjs +14 -2
  65. package/dist-lib/expo-swift-bundle.cjs +7818 -5916
  66. package/dist-lib/expo-swift-metadata.cjs +3 -1
  67. package/dist-lib/expo-swift-metadata.mjs +3 -1
  68. package/dist-lib/home-paths.cjs +32 -3
  69. package/dist-lib/host/bridge-host.cjs +8340 -6392
  70. package/dist-lib/host/fetch-proxy-handler.cjs +1 -1
  71. package/dist-lib/host/fetch-proxy-overrides.cjs +1 -1
  72. package/dist-lib/host/fetch-proxy-overrides.mjs +1 -1
  73. package/dist-lib/host/replacement-module-handler.cjs +1 -1
  74. package/dist-lib/host/websocket-proxy.cjs +1 -1
  75. package/dist-lib/index.cjs +812 -339
  76. package/dist-lib/jump-to-source-babel.cjs +1 -1
  77. package/dist-lib/jump-to-source-native.cjs +1 -1
  78. package/dist-lib/kotlin-host.cjs +1 -1
  79. package/dist-lib/menu.cjs +1 -1
  80. package/dist-lib/menu.mjs +1 -1
  81. package/dist-lib/metro-fingerprint-registry.cjs +1 -1
  82. package/dist-lib/metro-fingerprint-registry.mjs +1 -1
  83. package/dist-lib/metro-production-bundle.cjs +1 -1
  84. package/dist-lib/metro-production-bundle.mjs +1 -1
  85. package/dist-lib/metro.cjs +391 -28
  86. package/dist-lib/native-app-identity-project.cjs +55 -2
  87. package/dist-lib/native-app-identity-project.mjs +55 -2
  88. package/dist-lib/native-app-identity.cjs +53 -1
  89. package/dist-lib/native-app-identity.mjs +52 -1
  90. package/dist-lib/native-firebase-metadata.cjs +1 -1
  91. package/dist-lib/native-firebase-metadata.mjs +1 -1
  92. package/dist-lib/plist.cjs +1 -1
  93. package/dist-lib/plist.mjs +1 -1
  94. package/dist-lib/profiles.cjs +1 -1
  95. package/dist-lib/public-brand.cjs +1 -1
  96. package/dist-lib/react-native-host-modules.cjs +5 -1
  97. package/dist-lib/react-native-host-modules.mjs +5 -1
  98. package/dist-lib/scripts/dev-server-scanner.cjs +15 -3
  99. package/dist-lib/sdk.cjs +27 -11
  100. package/dist-lib/sdk.mjs +27 -10
  101. package/dist-lib/skills.cjs +12276 -8947
  102. package/dist-lib/swift-compile.cjs +924 -118
  103. package/dist-lib/swift-compile.mjs +966 -152
  104. package/dist-lib/swift-package.cjs +12 -3
  105. package/dist-lib/swift-package.mjs +12 -3
  106. package/dist-lib/swift-submit.cjs +7 -1
  107. package/dist-lib/swift-submit.mjs +5 -1
  108. package/dist-lib/swift-toolchain.cjs +133 -18
  109. package/dist-lib/swift.cjs +1 -1
  110. package/dist-lib/vite-swift.cjs +1955 -498
  111. package/dist-lib/vite.cjs +3 -1
  112. package/dist-lib/worklet-executable.cjs +1 -1
  113. package/dist-lib/worklet-executable.mjs +1 -1
  114. package/kotlin/AppSources/dev/peach/app/PeachDrawable.kt +21 -14
  115. package/kotlin/Sources/androidx/annotation/DrawableRes.kt +14 -0
  116. package/package.json +1 -1
  117. package/scripts/dev-server-scanner.ts +2 -1
  118. package/skills/peach-perf/SKILL.md +11 -5
  119. package/skills/peach-setup/SKILL.md +16 -19
  120. package/src/bridge-constants.ts +11 -0
  121. package/src/bridge-contract.ts +22 -34
  122. package/src/config.ts +15 -0
  123. package/src/desktop-host-delivery.ts +5 -1
  124. package/src/dev-bundle-resolution.ts +2 -1
  125. package/src/expo-manifest-body.ts +13 -0
  126. package/src/expo-swift-host.ts +191 -47
  127. package/src/expo-swift-ready.ts +78 -0
  128. package/src/expoSwiftContract.ts +13 -0
  129. package/src/home-paths.ts +48 -2
  130. package/src/host/bridge-host.ts +5 -2
  131. package/src/native-app-identity-project.ts +26 -2
  132. package/src/native-app-identity.ts +90 -0
  133. package/src/native-firebase-config.ts +209 -13
  134. package/src/process-group.ts +25 -2
  135. package/src/react-native-bridge-modules.ts +416 -0
  136. package/src/react-native-host-modules.ts +4 -0
  137. package/src/swift-binary-modules.ts +2 -2
  138. package/src/swift-compile.ts +835 -84
  139. package/src/swift-compiler-digest.ts +14 -0
  140. package/src/swift-distribution.lock.json +14 -12
  141. package/src/swift-host.ts +91 -29
  142. package/src/swift-icu.ts +14 -6
  143. package/src/swift-objc-sdk.ts +133 -10
  144. package/src/swift-objc-standin.ts +25 -3
  145. package/src/swift-package-manifest.ts +21 -6
  146. package/src/swift-package.ts +15 -1
  147. package/src/swift-packages.ts +119 -60
  148. package/src/swift-standin-emit.ts +57 -9
  149. package/src/swift-standins.ts +168 -20
  150. package/src/swift-submit.ts +10 -0
  151. package/src/swift-toolchain.ts +149 -23
  152. package/src/vite-plugin.ts +12 -1
@@ -0,0 +1,416 @@
1
+ // an app's own legacy React Native modules: the classes its Objective-C
2
+ // sources register with RCT_EXPORT_MODULE or RCT_EXTERN_MODULE, read from the
3
+ // app target Xcode builds. the macros' arguments are what clang records for
4
+ // RCTBridge (RCTMethodInfo's js and objc names); the Swift host parses each
5
+ // signature at runtime as RCTModuleMethod does (sootsim-swift React target).
6
+ import fs from 'node:fs'
7
+ import path from 'node:path'
8
+ import { swiftCodeMask, swiftSourceClosure } from './swift-compile'
9
+ import { listXcodeAppTargets, readXcodeAppTarget } from './xcode-project'
10
+ import type { ExpoSwiftSourceUnit } from './expoSwiftContract'
11
+
12
+ interface MethodInfo {
13
+ jsName: string
14
+ objcName: string
15
+ isSync: boolean
16
+ }
17
+
18
+ interface ModuleDeclaration {
19
+ className: string
20
+ jsName: string
21
+ // RCT_EXTERN_MODULE declares a class implemented elsewhere, usually Swift
22
+ extern: boolean
23
+ file: string
24
+ }
25
+
26
+ export interface ObjectiveCDeclarations {
27
+ modules: ModuleDeclaration[]
28
+ methods: Map<string, MethodInfo[]>
29
+ superclasses: Map<string, string>
30
+ // the instance selectors each class's @implementation blocks define
31
+ implementations: Map<string, Set<string>>
32
+ }
33
+
34
+ // Objective-C with its comments as spaces, offsets kept
35
+ function stripComments(source: string): string {
36
+ let out = ''
37
+ let i = 0
38
+ while (i < source.length) {
39
+ const char = source[i]!
40
+ const next = source[i + 1]
41
+ if (char === '/' && next === '/') {
42
+ const end = source.indexOf('\n', i)
43
+ const stop = end === -1 ? source.length : end
44
+ out += ' '.repeat(stop - i)
45
+ i = stop
46
+ } else if (char === '/' && next === '*') {
47
+ const end = source.indexOf('*/', i + 2)
48
+ const stop = end === -1 ? source.length : end + 2
49
+ out += source.slice(i, stop).replace(/[^\n]/g, ' ')
50
+ i = stop
51
+ } else if (char === '"' || char === "'") {
52
+ let j = i + 1
53
+ while (j < source.length && source[j] !== char && source[j] !== '\n')
54
+ j += source[j] === '\\' ? 2 : 1
55
+ out += source.slice(i, j + 1)
56
+ i = j + 1
57
+ } else {
58
+ out += char
59
+ i++
60
+ }
61
+ }
62
+ return out
63
+ }
64
+
65
+ // a macro's arguments split at top-level commas, as the preprocessor does
66
+ function macroArguments(
67
+ source: string,
68
+ open: number,
69
+ ): { args: string[]; end: number } | null {
70
+ const args: string[] = []
71
+ let depth = 0
72
+ let start = open + 1
73
+ for (let i = open; i < source.length; i++) {
74
+ const char = source[i]
75
+ if (char === '"' || char === "'") {
76
+ let j = i + 1
77
+ while (j < source.length && source[j] !== char) j += source[j] === '\\' ? 2 : 1
78
+ i = j
79
+ } else if (char === '(') depth++
80
+ else if (char === ')') {
81
+ depth--
82
+ if (depth === 0) {
83
+ args.push(source.slice(start, i))
84
+ return { args: args.map((arg) => arg.replace(/\s+/g, ' ').trim()), end: i + 1 }
85
+ }
86
+ } else if (char === ',' && depth === 1) {
87
+ args.push(source.slice(start, i))
88
+ start = i + 1
89
+ }
90
+ }
91
+ return null
92
+ }
93
+
94
+ // the selector a method definition's signature names, skipping its types
95
+ function definitionSelector(signature: string): string {
96
+ let selector = ''
97
+ let rest = signature.trim()
98
+ for (;;) {
99
+ const part = /^([A-Za-z_]\w*)?\s*(:)?\s*/.exec(rest)
100
+ if (!part || (!part[1] && !part[2])) break
101
+ selector += (part[1] ?? '') + (part[2] ?? '')
102
+ rest = rest.slice(part[0].length)
103
+ if (!part[2]) break
104
+ if (rest.startsWith('(')) {
105
+ const type = macroArguments(rest, 0)
106
+ if (!type) break
107
+ rest = rest.slice(type.end).trimStart()
108
+ }
109
+ rest = rest.replace(/^[A-Za-z_]\w*\s*/, '')
110
+ }
111
+ return selector
112
+ }
113
+
114
+ const DIRECTIVE =
115
+ /@interface\b|@implementation\b|@end\b|\b(_RCT_EXTERN_REMAP_METHOD|RCT_EXTERN_REMAP_MODULE|RCT_EXTERN_MODULE|RCT_EXPORT_MODULE_NO_LOAD|RCT_EXPORT_MODULE|RCT_EXTERN_METHOD|RCT_EXTERN__BLOCKING_SYNCHRONOUS_METHOD|RCT_EXPORT_METHOD|RCT_REMAP_METHOD|RCT_EXPORT_BLOCKING_SYNCHRONOUS_METHOD|RCT_EXPORT_SYNCHRONOUS_TYPED_METHOD|RCT_REMAP_BLOCKING_SYNCHRONOUS_METHOD)\s*\(/g
116
+
117
+ export function parseObjectiveCModules(
118
+ sources: { file: string; text: string }[],
119
+ ): ObjectiveCDeclarations {
120
+ const declarations: ObjectiveCDeclarations = {
121
+ modules: [],
122
+ methods: new Map(),
123
+ superclasses: new Map(),
124
+ implementations: new Map(),
125
+ }
126
+ const addMethod = (className: string, method: MethodInfo) => {
127
+ const methods = declarations.methods.get(className) ?? []
128
+ methods.push(method)
129
+ declarations.methods.set(className, methods)
130
+ }
131
+ const addSelector = (className: string, selector: string) => {
132
+ declarations.implementations.get(className)?.add(selector)
133
+ }
134
+ for (const { file, text } of sources) {
135
+ const source = stripComments(text)
136
+ // the class whose @interface or @implementation encloses a macro
137
+ let owner: { className: string; implementation: boolean; start: number } | null = null
138
+ const closeImplementation = (end: number) => {
139
+ if (!owner?.implementation) return
140
+ // the instance methods the implementation defines: `- (type)selector {`
141
+ const body = source.slice(owner.start, end)
142
+ for (const match of body.matchAll(/^[ \t]*-\s*\(/gm)) {
143
+ const open = match.index + match[0].length - 1
144
+ const type = macroArguments(body, open)
145
+ if (!type) continue
146
+ const close = body.slice(type.end).search(/[{;]/)
147
+ if (close === -1) continue
148
+ const selector = definitionSelector(body.slice(type.end, type.end + close))
149
+ if (selector) addSelector(owner.className, selector)
150
+ }
151
+ }
152
+ for (const match of source.matchAll(DIRECTIVE)) {
153
+ const at = match.index
154
+ const directive = match[0]
155
+ if (directive === '@end') {
156
+ closeImplementation(at)
157
+ owner = null
158
+ continue
159
+ }
160
+ if (directive === '@interface' || directive === '@implementation') {
161
+ const rest = source.slice(at + directive.length)
162
+ const extern = /^\s*(RCT_EXTERN_REMAP_MODULE|RCT_EXTERN_MODULE)\s*\(/.exec(rest)
163
+ if (extern) {
164
+ const parsed = macroArguments(rest, extern[0].length - 1)
165
+ if (!parsed) continue
166
+ const [jsName, className, superclass] =
167
+ extern[1] === 'RCT_EXTERN_MODULE' ? ['', ...parsed.args] : parsed.args
168
+ if (!className) continue
169
+ declarations.modules.push({
170
+ className,
171
+ jsName: jsName ?? '',
172
+ extern: true,
173
+ file,
174
+ })
175
+ if (superclass) declarations.superclasses.set(className, superclass)
176
+ owner = { className, implementation: false, start: at }
177
+ continue
178
+ }
179
+ const named = /^\s*([A-Za-z_]\w*)\s*(?::\s*([A-Za-z_]\w*))?/.exec(rest)
180
+ if (!named) continue
181
+ if (named[2]) declarations.superclasses.set(named[1]!, named[2])
182
+ if (
183
+ directive === '@implementation' &&
184
+ !declarations.implementations.has(named[1]!)
185
+ )
186
+ declarations.implementations.set(named[1]!, new Set())
187
+ owner = {
188
+ className: named[1]!,
189
+ implementation: directive === '@implementation',
190
+ start: at + directive.length + named[0].length,
191
+ }
192
+ continue
193
+ }
194
+ if (!owner) continue
195
+ const parsed = macroArguments(source, at + directive.length - 1)
196
+ if (!parsed) continue
197
+ const args = parsed.args
198
+ switch (match[1]) {
199
+ case 'RCT_EXPORT_MODULE':
200
+ if (owner.implementation)
201
+ declarations.modules.push({
202
+ className: owner.className,
203
+ jsName: args[0] ?? '',
204
+ extern: false,
205
+ file,
206
+ })
207
+ break
208
+ case 'RCT_EXPORT_MODULE_NO_LOAD':
209
+ declarations.modules.push({
210
+ className: args[1] || owner.className,
211
+ jsName: args[0] ?? '',
212
+ extern: false,
213
+ file,
214
+ })
215
+ break
216
+ case 'RCT_EXTERN_METHOD':
217
+ case 'RCT_EXPORT_METHOD':
218
+ addMethod(owner.className, {
219
+ jsName: '',
220
+ objcName: args[0] ?? '',
221
+ isSync: false,
222
+ })
223
+ break
224
+ case 'RCT_EXTERN__BLOCKING_SYNCHRONOUS_METHOD':
225
+ case 'RCT_EXPORT_BLOCKING_SYNCHRONOUS_METHOD':
226
+ addMethod(owner.className, {
227
+ jsName: '',
228
+ objcName: args[0] ?? '',
229
+ isSync: true,
230
+ })
231
+ break
232
+ case 'RCT_EXPORT_SYNCHRONOUS_TYPED_METHOD':
233
+ addMethod(owner.className, {
234
+ jsName: '',
235
+ objcName: args[1] ?? '',
236
+ isSync: true,
237
+ })
238
+ break
239
+ case 'RCT_REMAP_METHOD':
240
+ addMethod(owner.className, {
241
+ jsName: args[0] ?? '',
242
+ objcName: args[1] ?? '',
243
+ isSync: false,
244
+ })
245
+ break
246
+ case 'RCT_REMAP_BLOCKING_SYNCHRONOUS_METHOD':
247
+ addMethod(owner.className, {
248
+ jsName: args[0] ?? '',
249
+ objcName: args[2] ?? '',
250
+ isSync: true,
251
+ })
252
+ break
253
+ case '_RCT_EXTERN_REMAP_METHOD':
254
+ addMethod(owner.className, {
255
+ jsName: args[0] ?? '',
256
+ objcName: args[1] ?? '',
257
+ isSync: args[2] === 'YES',
258
+ })
259
+ break
260
+ }
261
+ }
262
+ closeImplementation(source.length)
263
+ }
264
+ return declarations
265
+ }
266
+
267
+ // the superclass Peach compiles that a class's chain reaches
268
+ function compiledSuperclass(
269
+ className: string,
270
+ superclasses: Map<string, string>,
271
+ ): 'RCTEventEmitter' | 'NSObject' | null {
272
+ const seen = new Set<string>()
273
+ let current = superclasses.get(className)
274
+ while (current && !seen.has(current)) {
275
+ if (current === 'RCTEventEmitter' || current === 'NSObject') return current
276
+ seen.add(current)
277
+ current = superclasses.get(current)
278
+ }
279
+ return null
280
+ }
281
+
282
+ const swiftString = (value: string) => JSON.stringify(value)
283
+
284
+ function methodsLiteral(methods: MethodInfo[]): string {
285
+ return `[${methods
286
+ .map(
287
+ (method) =>
288
+ `RCTMethodInfo(jsName: ${swiftString(method.jsName)}, objcName: ${swiftString(method.objcName)}, isSync: ${method.isSync})`,
289
+ )
290
+ .join(', ')}]`
291
+ }
292
+
293
+ // a Swift class exposed to Objective-C as `objcName`: `@objc(objcName)` on it,
294
+ // else a class of that name
295
+ function swiftClassFor(objcName: string, files: Record<string, string>): string | null {
296
+ const modifiers =
297
+ '(?:\\s*(?:@\\w+(?:\\([^)]*\\))?|public|open|final|internal|fileprivate|private))*'
298
+ const exposed = new RegExp(
299
+ `@objc\\s*\\(\\s*${objcName}\\s*\\)${modifiers}\\s*class\\s+([A-Za-z_]\\w*)`,
300
+ )
301
+ const named = new RegExp(`\\bclass\\s+${objcName}\\b`)
302
+ let fallback: string | null = null
303
+ for (const source of Object.values(files)) {
304
+ const code = swiftCodeMask(source)
305
+ const match = exposed.exec(code)
306
+ if (match) return match[1]!
307
+ if (named.test(code)) fallback = objcName
308
+ }
309
+ return fallback
310
+ }
311
+
312
+ export const BRIDGE_MODULE_FILE_PREFIX = '__peach_react_module_'
313
+
314
+ /**
315
+ * the app target's legacy bridge modules, one compile unit each, or null
316
+ * when the app has no iOS project or registers none. a module the host
317
+ * cannot provide is a unit with a diagnostic instead of files.
318
+ */
319
+ export function collectBridgeModuleSources(
320
+ appRoot: string,
321
+ signal?: AbortSignal,
322
+ ): {
323
+ units: ExpoSwiftSourceUnit[]
324
+ failures: { namespace: string; diagnostic: string }[]
325
+ } | null {
326
+ signal?.throwIfAborted()
327
+ const iosRoot = path.join(appRoot, 'ios')
328
+ if (!fs.existsSync(iosRoot)) return null
329
+ const projects = fs
330
+ .readdirSync(iosRoot, { withFileTypes: true })
331
+ .filter((entry) => entry.isDirectory() && entry.name.endsWith('.xcodeproj'))
332
+ .map((entry) => path.join(iosRoot, entry.name))
333
+ const targets = projects.flatMap((project) =>
334
+ listXcodeAppTargets(project, { requireSources: false }).map((name) => ({
335
+ project,
336
+ name,
337
+ })),
338
+ )
339
+ // RCTRegisterModule runs in the one app binary; several targets are
340
+ // several apps, and nothing here says which one this bundle runs in
341
+ if (targets.length !== 1) return null
342
+ const app = readXcodeAppTarget(targets[0]!.project, targets[0]!.name, 'Release', {
343
+ requireSources: false,
344
+ })
345
+ const own = (file: string) =>
346
+ !file.split(path.sep).some((part) => part === 'Pods' || part === 'node_modules')
347
+ const objc = app.allSources.filter((file) => /\.mm?$/.test(file) && own(file))
348
+ if (objc.length === 0) return null
349
+ // headers declare the superclasses; they sit beside the sources that import them
350
+ const headers = [...new Set(objc.map((file) => path.dirname(file)))].flatMap((dir) =>
351
+ fs
352
+ .readdirSync(dir)
353
+ .filter((name) => name.endsWith('.h'))
354
+ .sort()
355
+ .map((name) => path.join(dir, name)),
356
+ )
357
+ const declarations = parseObjectiveCModules(
358
+ [...objc, ...headers].map((file) => ({ file, text: fs.readFileSync(file, 'utf8') })),
359
+ )
360
+ if (declarations.modules.length === 0) return null
361
+ const namespace = app.moduleName.replace(/[^A-Za-z0-9_]/g, '_')
362
+ const swiftFiles: Record<string, string> = {}
363
+ for (const file of app.sources.filter(own)) {
364
+ const relative = path.relative(iosRoot, file)
365
+ if (relative.startsWith('..') || path.isAbsolute(relative))
366
+ throw new Error(`${file}: app Swift source outside ${iosRoot}`)
367
+ swiftFiles[`Sources/${namespace}/${relative.split(path.sep).join('/')}`] =
368
+ `#sourceLocation(file: ${JSON.stringify(file)}, line: 1)\n${fs.readFileSync(file, 'utf8')}`
369
+ }
370
+ const units: ExpoSwiftSourceUnit[] = []
371
+ const failures: { namespace: string; diagnostic: string }[] = []
372
+ const seen = new Set<string>()
373
+ for (const module of declarations.modules) {
374
+ signal?.throwIfAborted()
375
+ if (seen.has(module.className)) continue
376
+ seen.add(module.className)
377
+ const moduleId = `${namespace}.${module.className}`
378
+ const methods = declarations.methods.get(module.className) ?? []
379
+ const swiftClass = module.extern ? swiftClassFor(module.className, swiftFiles) : null
380
+ const unit: ExpoSwiftSourceUnit = {
381
+ namespace,
382
+ moduleId,
383
+ classes: [],
384
+ bridgeModules: [],
385
+ files: {},
386
+ frameworks: [],
387
+ swiftLanguageMode: app.languageMode,
388
+ }
389
+ if (swiftClass) {
390
+ const register = `__peachReactModule_${module.className}`
391
+ unit.files = swiftSourceClosure(swiftFiles, [swiftClass])
392
+ unit.files[
393
+ `Sources/${namespace}/${BRIDGE_MODULE_FILE_PREFIX}${module.className}.swift`
394
+ ] =
395
+ `import React\n\n// ${module.file}: RCT_EXTERN_MODULE(${module.className})\npublic func ${register}() -> RCTModuleHost.Registration {\n RCTModuleHost.Registration(className: ${swiftString(module.className)}, moduleName: ${swiftString(module.jsName)}, methods: ${methodsLiteral(methods)}, make: { ${swiftClass}() })\n}\n`
396
+ unit.bridgeModules = [`${namespace}.${register}()`]
397
+ } else {
398
+ const superclass = compiledSuperclass(module.className, declarations.superclasses)
399
+ const selectors = declarations.implementations.get(module.className)
400
+ if (!selectors || !superclass) {
401
+ failures.push({
402
+ namespace: moduleId,
403
+ diagnostic: !selectors
404
+ ? `${module.file}: no Swift class or Objective-C @implementation provides ${module.className}`
405
+ : `${module.file}: ${module.className}'s superclass ${declarations.superclasses.get(module.className) ?? '(none)'} is Objective-C, which Peach does not compile`,
406
+ })
407
+ continue
408
+ }
409
+ unit.bridgeModules = [
410
+ `RCTModuleHost.Registration(className: ${swiftString(module.className)}, moduleName: ${swiftString(module.jsName)}, methods: ${methodsLiteral(methods)}, objectiveCSelectors: ${JSON.stringify([...selectors].sort())}, superclass: { ${superclass}() })`,
411
+ ]
412
+ }
413
+ units.push(unit)
414
+ }
415
+ return { units, failures }
416
+ }
@@ -18,6 +18,10 @@ export const REACT_NATIVE_PASSTHROUGH_SPECIFIERS: readonly string[] = [
18
18
  // expo's metro-runtime dials metro's message socket with this WebSocket,
19
19
  // which runs against the engine's WebSocketModule and BlobModule.
20
20
  'react-native/Libraries/WebSocket',
21
+ // that WebSocket extends upstream's EventTarget and builds its MessageEvent
22
+ // and CloseEvent from the same dom event classes; a stubbed class there is
23
+ // not a constructor to extend.
24
+ 'react-native/src/private/webapis',
21
25
  'react-native/Libraries/Blob/Blob',
22
26
  'react-native/Libraries/Blob/BlobManager',
23
27
  'react-native/Libraries/Blob/BlobRegistry',
@@ -6,11 +6,11 @@
6
6
 
7
7
  import { createHash } from 'node:crypto'
8
8
  import fs from 'node:fs'
9
- import os from 'node:os'
10
9
  import path from 'node:path'
10
+ import { cacheDir as peachCacheDir } from './home-paths'
11
11
  import type { SwiftPackageBinary } from './swift-packages'
12
12
 
13
- const cacheDir = path.join(os.tmpdir(), 'peach-swift-binary-modules')
13
+ const cacheDir = path.join(peachCacheDir(), 'swift-binary-modules')
14
14
 
15
15
  // the first `<name>.framework` segment of each path: a framework's own
16
16
  // nested frameworks and macOS Versions/ trees are not modules of the target