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.
- package/LICENSE +1 -1
- package/README.md +10 -1
- package/cli/app-config.ts +4 -1
- package/cli/app-fonts.ts +37 -17
- package/cli/app-stubs.ts +224 -9
- package/cli/bridge-flow-runner.ts +64 -43
- package/cli/command-registry.ts +1 -0
- package/cli/commands/control.ts +283 -49
- package/cli/commands/doctor.ts +17 -184
- package/cli/commands/flow.ts +79 -11
- package/cli/commands/inspect/core.ts +10 -5
- package/cli/commands/inspect/settling.ts +14 -2
- package/cli/commands/inspect/wait-ready.ts +12 -0
- package/cli/commands/inspect.ts +26 -3
- package/cli/commands/install-desktop.ts +1 -0
- package/cli/commands/maestro-compare.ts +97 -38
- package/cli/commands/maestro.ts +9 -1
- package/cli/commands/record.ts +21 -1
- package/cli/commands/screenshot.ts +23 -1
- package/cli/commands/serve.ts +7 -2
- package/cli/commands/setup.ts +19 -42
- package/cli/commands/swift.ts +5 -0
- package/cli/commands/upload.ts +46 -7
- package/cli/desktop-companion.ts +102 -17
- package/cli/drivers/desktop.ts +14 -1
- package/cli/drivers/playwright-sim-host.ts +38 -3
- package/cli/drivers/playwright.ts +9 -8
- package/cli/drivers/types.ts +5 -0
- package/cli/maestro-accessibility.ts +1 -1
- package/cli/maestro-compare.ts +84 -12
- package/cli/main.ts +2 -1
- package/cli/outbound-endpoints.ts +13 -1
- package/cli/pause-aware-timeout.ts +102 -0
- package/cli/scene-video.ts +89 -0
- package/cli/ws-bridge.ts +23 -14
- package/detox/attempt-events.cjs +4 -0
- package/detox/index.ts +167 -142
- package/detox/motion-change.cjs +57 -21
- package/detox/motion-change.d.cts +7 -2
- package/dist-lib/agent-daemon-client.cjs +1 -1
- package/dist-lib/agent-events.cjs +1 -1
- package/dist-lib/attached-projects.cjs +1 -1
- package/dist-lib/auth/shared-session.cjs +1 -1
- package/dist-lib/backend-origin.cjs +1 -1
- package/dist-lib/beta.cjs +1 -1
- package/dist-lib/beta.mjs +1 -1
- package/dist-lib/bridge-constants.cjs +13 -1
- package/dist-lib/bridge-contract-input.cjs +1 -1
- package/dist-lib/bridge-contract-input.mjs +1 -1
- package/dist-lib/bridge-contract.cjs +14 -8
- package/dist-lib/bridge-contract.mjs +13 -7
- package/dist-lib/bundle-channel.cjs +9536 -7604
- package/dist-lib/capture-contract.cjs +1 -1
- package/dist-lib/capture-contract.mjs +1 -1
- package/dist-lib/cloud-contract.cjs +1 -1
- package/dist-lib/cloud-contract.mjs +1 -1
- package/dist-lib/cloud-test-args.cjs +1 -1
- package/dist-lib/cloud-test-args.mjs +1 -1
- package/dist-lib/cloud.cjs +1 -1
- package/dist-lib/cloud.mjs +1 -1
- package/dist-lib/config.cjs +6 -3
- package/dist-lib/config.mjs +6 -3
- package/dist-lib/detox/index.cjs +895 -664
- package/dist-lib/dev-bundle-resolution.cjs +14 -2
- package/dist-lib/expo-swift-bundle.cjs +7818 -5916
- package/dist-lib/expo-swift-metadata.cjs +3 -1
- package/dist-lib/expo-swift-metadata.mjs +3 -1
- package/dist-lib/home-paths.cjs +32 -3
- package/dist-lib/host/bridge-host.cjs +8340 -6392
- package/dist-lib/host/fetch-proxy-handler.cjs +1 -1
- package/dist-lib/host/fetch-proxy-overrides.cjs +1 -1
- package/dist-lib/host/fetch-proxy-overrides.mjs +1 -1
- package/dist-lib/host/replacement-module-handler.cjs +1 -1
- package/dist-lib/host/websocket-proxy.cjs +1 -1
- package/dist-lib/index.cjs +812 -339
- package/dist-lib/jump-to-source-babel.cjs +1 -1
- package/dist-lib/jump-to-source-native.cjs +1 -1
- package/dist-lib/kotlin-host.cjs +1 -1
- package/dist-lib/menu.cjs +1 -1
- package/dist-lib/menu.mjs +1 -1
- package/dist-lib/metro-fingerprint-registry.cjs +1 -1
- package/dist-lib/metro-fingerprint-registry.mjs +1 -1
- package/dist-lib/metro-production-bundle.cjs +1 -1
- package/dist-lib/metro-production-bundle.mjs +1 -1
- package/dist-lib/metro.cjs +391 -28
- package/dist-lib/native-app-identity-project.cjs +55 -2
- package/dist-lib/native-app-identity-project.mjs +55 -2
- package/dist-lib/native-app-identity.cjs +53 -1
- package/dist-lib/native-app-identity.mjs +52 -1
- package/dist-lib/native-firebase-metadata.cjs +1 -1
- package/dist-lib/native-firebase-metadata.mjs +1 -1
- package/dist-lib/plist.cjs +1 -1
- package/dist-lib/plist.mjs +1 -1
- package/dist-lib/profiles.cjs +1 -1
- package/dist-lib/public-brand.cjs +1 -1
- package/dist-lib/react-native-host-modules.cjs +5 -1
- package/dist-lib/react-native-host-modules.mjs +5 -1
- package/dist-lib/scripts/dev-server-scanner.cjs +15 -3
- package/dist-lib/sdk.cjs +27 -11
- package/dist-lib/sdk.mjs +27 -10
- package/dist-lib/skills.cjs +12276 -8947
- package/dist-lib/swift-compile.cjs +924 -118
- package/dist-lib/swift-compile.mjs +966 -152
- package/dist-lib/swift-package.cjs +12 -3
- package/dist-lib/swift-package.mjs +12 -3
- package/dist-lib/swift-submit.cjs +7 -1
- package/dist-lib/swift-submit.mjs +5 -1
- package/dist-lib/swift-toolchain.cjs +133 -18
- package/dist-lib/swift.cjs +1 -1
- package/dist-lib/vite-swift.cjs +1955 -498
- package/dist-lib/vite.cjs +3 -1
- package/dist-lib/worklet-executable.cjs +1 -1
- package/dist-lib/worklet-executable.mjs +1 -1
- package/kotlin/AppSources/dev/peach/app/PeachDrawable.kt +21 -14
- package/kotlin/Sources/androidx/annotation/DrawableRes.kt +14 -0
- package/package.json +1 -1
- package/scripts/dev-server-scanner.ts +2 -1
- package/skills/peach-perf/SKILL.md +11 -5
- package/skills/peach-setup/SKILL.md +16 -19
- package/src/bridge-constants.ts +11 -0
- package/src/bridge-contract.ts +22 -34
- package/src/config.ts +15 -0
- package/src/desktop-host-delivery.ts +5 -1
- package/src/dev-bundle-resolution.ts +2 -1
- package/src/expo-manifest-body.ts +13 -0
- package/src/expo-swift-host.ts +191 -47
- package/src/expo-swift-ready.ts +78 -0
- package/src/expoSwiftContract.ts +13 -0
- package/src/home-paths.ts +48 -2
- package/src/host/bridge-host.ts +5 -2
- package/src/native-app-identity-project.ts +26 -2
- package/src/native-app-identity.ts +90 -0
- package/src/native-firebase-config.ts +209 -13
- package/src/process-group.ts +25 -2
- package/src/react-native-bridge-modules.ts +416 -0
- package/src/react-native-host-modules.ts +4 -0
- package/src/swift-binary-modules.ts +2 -2
- package/src/swift-compile.ts +835 -84
- package/src/swift-compiler-digest.ts +14 -0
- package/src/swift-distribution.lock.json +14 -12
- package/src/swift-host.ts +91 -29
- package/src/swift-icu.ts +14 -6
- package/src/swift-objc-sdk.ts +133 -10
- package/src/swift-objc-standin.ts +25 -3
- package/src/swift-package-manifest.ts +21 -6
- package/src/swift-package.ts +15 -1
- package/src/swift-packages.ts +119 -60
- package/src/swift-standin-emit.ts +57 -9
- package/src/swift-standins.ts +168 -20
- package/src/swift-submit.ts +10 -0
- package/src/swift-toolchain.ts +149 -23
- 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(
|
|
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
|