peachsim 0.1.1620 → 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 +45 -6
- package/cli/app-config.ts +4 -1
- package/cli/app-fonts.ts +39 -18
- package/cli/app-stubs.ts +224 -9
- package/cli/bridge-flow-runner.ts +248 -156
- package/cli/cloud-client.ts +68 -57
- package/cli/command-registry.ts +1 -0
- package/cli/commands/box/search-command.ts +3 -2
- package/cli/commands/box.ts +10 -29
- package/cli/commands/control.ts +391 -92
- 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 +71 -22
- 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 +105 -67
- package/cli/commands/maestro.ts +9 -1
- package/cli/commands/platform.ts +71 -36
- 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 +51 -8
- package/cli/desktop-companion.ts +102 -17
- package/cli/dev-build-freshness.ts +37 -2
- package/cli/drivers/desktop.ts +14 -1
- package/cli/drivers/playwright-sim-host.ts +44 -8
- package/cli/drivers/playwright.ts +9 -8
- package/cli/drivers/types.ts +5 -0
- package/cli/maestro-accessibility.ts +53 -0
- package/cli/maestro-compare.ts +108 -12
- package/cli/main.ts +2 -1
- package/cli/outbound-endpoints.ts +35 -6
- package/cli/parent-pid.ts +0 -9
- package/cli/pause-aware-timeout.ts +102 -0
- package/cli/remote-simulators.ts +112 -0
- package/cli/remoteSimulatorContract.ts +21 -0
- package/cli/scene-video.ts +89 -0
- package/cli/ws-bridge.ts +23 -14
- package/detox/attempt-events.cjs +4 -0
- package/detox/expectations.ts +5 -1
- package/detox/index.ts +241 -181
- package/detox/motion-change.cjs +73 -0
- package/detox/motion-change.d.cts +21 -0
- package/detox/navigation-lifecycle.ts +22 -4
- package/dist-lib/agent-daemon-client.cjs +3 -2
- package/dist-lib/agent-events.cjs +1 -1
- package/dist-lib/attached-projects.cjs +1 -1
- package/dist-lib/auth/shared-session.cjs +3 -2
- 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 +10480 -7962
- 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 +3 -2
- package/dist-lib/cloud.mjs +3 -2
- package/dist-lib/config.cjs +10 -6
- package/dist-lib/config.mjs +10 -6
- package/dist-lib/detox/index.cjs +1854 -966
- package/dist-lib/dev-bundle-resolution.cjs +64 -24
- package/dist-lib/expo-swift-bundle.cjs +8715 -6271
- package/dist-lib/expo-swift-metadata.cjs +7 -4
- package/dist-lib/expo-swift-metadata.mjs +7 -4
- package/dist-lib/home-paths.cjs +32 -3
- package/dist-lib/host/bridge-host.cjs +10135 -7060
- 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 +1843 -408
- 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 +3 -2
- package/dist-lib/menu.mjs +3 -2
- 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 +5 -4
- package/dist-lib/metro-production-bundle.mjs +5 -4
- package/dist-lib/metro.cjs +1182 -418
- package/dist-lib/native-app-identity-project.cjs +782 -23
- package/dist-lib/native-app-identity-project.mjs +781 -23
- package/dist-lib/native-app-identity.cjs +276 -9
- package/dist-lib/native-app-identity.mjs +273 -8
- package/dist-lib/native-firebase-metadata.cjs +5 -4
- package/dist-lib/native-firebase-metadata.mjs +5 -4
- 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 +3 -2
- package/dist-lib/react-native-host-modules.cjs +8 -1
- package/dist-lib/react-native-host-modules.mjs +8 -1
- package/dist-lib/scripts/dev-server-scanner.cjs +106 -28
- package/dist-lib/sdk.cjs +115 -34
- package/dist-lib/sdk.mjs +115 -33
- package/dist-lib/skills.cjs +15274 -9904
- package/dist-lib/swift-compile.cjs +1165 -173
- package/dist-lib/swift-compile.mjs +1212 -203
- 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 +2648 -862
- package/dist-lib/vite.cjs +70 -14
- package/dist-lib/worklet-executable.cjs +25 -2
- package/dist-lib/worklet-executable.mjs +25 -2
- package/kotlin/AppSources/dev/peach/app/PeachDrawable.kt +21 -14
- package/kotlin/Sources/androidx/annotation/DrawableRes.kt +14 -0
- package/package.json +2 -2
- package/scripts/dev-server-scanner.ts +35 -14
- package/skills/peach-perf/SKILL.md +11 -5
- package/skills/peach-setup/SKILL.md +16 -19
- package/src/admitted-mac-build.ts +34 -8
- package/src/android-app-identity.ts +146 -0
- package/src/android-autolinking.ts +75 -0
- package/src/android-string-value.ts +36 -0
- package/src/android-strings.ts +5 -36
- package/src/androidAutolinkingContract.ts +40 -0
- package/src/bridge-constants.ts +11 -0
- package/src/bridge-contract.ts +22 -34
- package/src/bundleGlobalFooter.ts +26 -7
- package/src/codex-client.ts +3 -2
- package/src/config.ts +15 -0
- package/src/desktop-host-delivery.ts +5 -1
- package/src/dev-bundle-resolution.ts +60 -26
- package/src/expo-kotlin-host.ts +6 -36
- package/src/expo-manifest-body.ts +13 -0
- package/src/expo-swift-bundle.ts +17 -12
- package/src/expo-swift-host.ts +640 -192
- package/src/expo-swift-ready.ts +78 -0
- package/src/expoSwiftContract.ts +19 -0
- package/src/home-paths.ts +48 -2
- package/src/host/bridge-host.ts +12 -57
- package/src/host/staged-host-files.ts +29 -2
- package/src/installed-package-versions.ts +54 -14
- package/src/launchMetadataContract.ts +24 -0
- package/src/metro-plugin.ts +37 -42
- package/src/native-app-identity-project.ts +142 -5
- package/src/native-app-identity.ts +176 -11
- package/src/native-firebase-config.ts +209 -13
- package/src/nitro-compile.ts +2 -5
- package/src/nitro-source.ts +147 -63
- package/src/process-group.ts +114 -61
- package/src/process-output.ts +0 -9
- package/src/public-brand.ts +1 -0
- package/src/react-native-bridge-modules.ts +416 -0
- package/src/react-native-host-modules.ts +7 -0
- package/src/sim-client.ts +6 -4
- package/src/swift-binary-modules.ts +2 -2
- package/src/swift-compile.ts +1007 -108
- package/src/swift-compiler-digest.ts +14 -0
- package/src/swift-distribution.lock.json +40 -12
- package/src/swift-distribution.ts +61 -9
- package/src/swift-host.ts +149 -56
- package/src/swift-icu.ts +14 -6
- package/src/swift-objc-sdk.ts +133 -10
- package/src/swift-objc-standin.ts +38 -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 +185 -25
- package/src/swift-submit.ts +10 -0
- package/src/swift-toolchain.ts +149 -23
- package/src/vite-plugin.ts +20 -20
- package/src/worklet-executable.ts +30 -3
- package/src/worklets-babel.ts +35 -14
- package/src/xcode-project.ts +10 -0
|
@@ -106,6 +106,172 @@ export async function readExpoConfigAsync(
|
|
|
106
106
|
}
|
|
107
107
|
}
|
|
108
108
|
|
|
109
|
+
// a launch method may guard configure() with the safe-configure idiom: an
|
|
110
|
+
// `if` whose comma-separated clauses read the bundled GoogleService-Info.plist
|
|
111
|
+
// and test its API_KEY prefix. every clause is evaluated against the plist the
|
|
112
|
+
// target ships, so the answer is the one native launch reaches. any other
|
|
113
|
+
// condition cannot be proven and stays refused.
|
|
114
|
+
function evaluateFirebaseGuard(header: string, resources: string[]): boolean | null {
|
|
115
|
+
const condition = header.replace(/^\s*if\b/, '').replace(/\{\s*$/, '')
|
|
116
|
+
const clauses: string[] = []
|
|
117
|
+
let depth = 0
|
|
118
|
+
let start = 0
|
|
119
|
+
let quoted = false
|
|
120
|
+
for (let index = 0; index < condition.length; index++) {
|
|
121
|
+
const char = condition[index]
|
|
122
|
+
if (char === '"') quoted = !quoted
|
|
123
|
+
if (quoted) continue
|
|
124
|
+
if (char === '(' || char === '[') depth += 1
|
|
125
|
+
if (char === ')' || char === ']') depth -= 1
|
|
126
|
+
if (char === ',' && depth === 0) {
|
|
127
|
+
clauses.push(condition.slice(start, index).trim())
|
|
128
|
+
start = index + 1
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
clauses.push(condition.slice(start).trim())
|
|
132
|
+
const sources = resources.filter(
|
|
133
|
+
(file) => path.basename(file) === 'GoogleService-Info.plist',
|
|
134
|
+
)
|
|
135
|
+
const bound = new Map<string, unknown>()
|
|
136
|
+
for (const clause of clauses) {
|
|
137
|
+
if (/^FirebaseApp\s*\.\s*app\s*\(\s*\)\s*==\s*nil$/.test(clause)) continue
|
|
138
|
+
const resource =
|
|
139
|
+
/^let\s+(\w+)\s*=\s*Bundle\.main\.path\s*\(\s*forResource:\s*"GoogleService-Info"\s*,\s*ofType:\s*"plist"\s*\)$/.exec(
|
|
140
|
+
clause,
|
|
141
|
+
)
|
|
142
|
+
if (resource) {
|
|
143
|
+
if (sources.length !== 1) return false
|
|
144
|
+
bound.set(resource[1], sources[0])
|
|
145
|
+
continue
|
|
146
|
+
}
|
|
147
|
+
const dictionary =
|
|
148
|
+
/^let\s+(\w+)\s*=\s*NSDictionary\s*\(\s*contentsOfFile:\s*(\w+)\s*\)$/.exec(clause)
|
|
149
|
+
if (dictionary && typeof bound.get(dictionary[2]) === 'string') {
|
|
150
|
+
const parsed = parsePlistSource(
|
|
151
|
+
fs.readFileSync(String(bound.get(dictionary[2])), 'utf8'),
|
|
152
|
+
)
|
|
153
|
+
if (!isRecord(parsed)) return false
|
|
154
|
+
bound.set(dictionary[1], parsed)
|
|
155
|
+
continue
|
|
156
|
+
}
|
|
157
|
+
const entry = /^let\s+(\w+)\s*=\s*(\w+)\s*\[\s*"(\w+)"\s*\]\s+as\?\s+String$/.exec(
|
|
158
|
+
clause,
|
|
159
|
+
)
|
|
160
|
+
const dictionaryValue = entry ? bound.get(entry[2]) : undefined
|
|
161
|
+
if (entry && isRecord(dictionaryValue)) {
|
|
162
|
+
const value = dictionaryValue[entry[3]]
|
|
163
|
+
if (typeof value !== 'string') return false
|
|
164
|
+
bound.set(entry[1], value)
|
|
165
|
+
continue
|
|
166
|
+
}
|
|
167
|
+
const prefix = /^(\w+)\.hasPrefix\s*\(\s*"([^"]*)"\s*\)$/.exec(clause)
|
|
168
|
+
const subject = prefix ? bound.get(prefix[1]) : undefined
|
|
169
|
+
if (prefix && typeof subject === 'string') {
|
|
170
|
+
if (!subject.startsWith(prefix[2])) return false
|
|
171
|
+
continue
|
|
172
|
+
}
|
|
173
|
+
return null
|
|
174
|
+
}
|
|
175
|
+
return true
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
// native source with comments and string contents blanked in place, so brace
|
|
179
|
+
// and keyword scans see only code and offsets still index the raw text.
|
|
180
|
+
function blankSwiftObjcLiterals(raw: string) {
|
|
181
|
+
return raw.replace(/\/\*[\s\S]*?\*\/|\/\/[^\n]*|"(?:\\.|[^"\\])*"/g, (token) =>
|
|
182
|
+
token.startsWith('"')
|
|
183
|
+
? `"${' '.repeat(token.length - 2)}"`
|
|
184
|
+
: token.replace(/[^\n]/g, ' '),
|
|
185
|
+
)
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
// the text of the brace block opening at `open`, and the index after its end.
|
|
189
|
+
function braceBlock(source: string, open: number) {
|
|
190
|
+
let depth = 0
|
|
191
|
+
for (let index = open; index < source.length; index += 1) {
|
|
192
|
+
if (source[index] === '{') depth += 1
|
|
193
|
+
if (source[index] === '}') depth -= 1
|
|
194
|
+
if (depth === 0) return { body: source.slice(open + 1, index), end: index + 1 }
|
|
195
|
+
}
|
|
196
|
+
return null
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
// whether every path through the code configures the default Firebase app: a
|
|
200
|
+
// top-level configure call, or an if/else chain ending in else whose blocks each
|
|
201
|
+
// do.
|
|
202
|
+
function configuresDefaultApp(code: string): boolean {
|
|
203
|
+
const configureCall = /\bFirebaseApp\s*\.\s*configure\s*\(/
|
|
204
|
+
let topLevel = ''
|
|
205
|
+
let depth = 0
|
|
206
|
+
for (const char of code) {
|
|
207
|
+
if (char === '{') depth += 1
|
|
208
|
+
if (depth === 0) topLevel += char
|
|
209
|
+
if (char === '}') depth -= 1
|
|
210
|
+
}
|
|
211
|
+
if (configureCall.test(topLevel)) return true
|
|
212
|
+
for (const chain of code.matchAll(/\bif\b[^{]*\{/g)) {
|
|
213
|
+
let open = chain.index + chain[0].length - 1
|
|
214
|
+
const blocks: string[] = []
|
|
215
|
+
let complete = false
|
|
216
|
+
for (;;) {
|
|
217
|
+
const block = braceBlock(code, open)
|
|
218
|
+
if (!block) break
|
|
219
|
+
blocks.push(block.body)
|
|
220
|
+
const next = /^\s*else\s*(if\b[^{]*)?\{/.exec(code.slice(block.end))
|
|
221
|
+
if (!next) break
|
|
222
|
+
open = block.end + next[0].length - 1
|
|
223
|
+
if (!next[1]) {
|
|
224
|
+
const last = braceBlock(code, open)
|
|
225
|
+
if (last) {
|
|
226
|
+
blocks.push(last.body)
|
|
227
|
+
complete = true
|
|
228
|
+
}
|
|
229
|
+
break
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
if (complete && blocks.every(configuresDefaultApp)) return true
|
|
233
|
+
}
|
|
234
|
+
return false
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
// where a launch method configures the default Firebase app: directly, or by
|
|
238
|
+
// calling a static no-argument function of an app target type whose body
|
|
239
|
+
// configures it on every path. returns a match-like result carrying the call's
|
|
240
|
+
// index, or null.
|
|
241
|
+
function findDefaultConfigureCall(body: string, sources: readonly string[]) {
|
|
242
|
+
const direct =
|
|
243
|
+
/^\s*(?:FirebaseApp\s*\.\s*configure\s*\(\s*\)|\[FIRApp\s+configure\])/m.exec(body)
|
|
244
|
+
if (direct) return direct
|
|
245
|
+
for (const call of body.matchAll(/^\s*([A-Z]\w*)\s*\.\s*(\w+)\s*\(\s*\)/gm)) {
|
|
246
|
+
const [, type, name] = call
|
|
247
|
+
for (const file of sources) {
|
|
248
|
+
if (!file.endsWith('.swift')) continue
|
|
249
|
+
const source = blankSwiftObjcLiterals(fs.readFileSync(file, 'utf8'))
|
|
250
|
+
const declaration = new RegExp(
|
|
251
|
+
`\\b(?:enum|struct|class)\\s+${type}\\b[^{]*\\{`,
|
|
252
|
+
).exec(source)
|
|
253
|
+
if (!declaration) continue
|
|
254
|
+
const owner = braceBlock(source, declaration.index + declaration[0].length - 1)
|
|
255
|
+
const method = owner
|
|
256
|
+
? new RegExp(`\\bstatic\\s+func\\s+${name}\\s*\\(\\s*\\)[^{]*\\{`).exec(
|
|
257
|
+
owner.body,
|
|
258
|
+
)
|
|
259
|
+
: null
|
|
260
|
+
const helper =
|
|
261
|
+
owner && method
|
|
262
|
+
? braceBlock(owner.body, method.index + method[0].length - 1)
|
|
263
|
+
: null
|
|
264
|
+
if (
|
|
265
|
+
helper &&
|
|
266
|
+
!/\b(?:return|throw)\b/.test(helper.body) &&
|
|
267
|
+
configuresDefaultApp(helper.body)
|
|
268
|
+
)
|
|
269
|
+
return call
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
return null
|
|
273
|
+
}
|
|
274
|
+
|
|
109
275
|
// the GoogleService-Info.plist an app target's launch method configures as the
|
|
110
276
|
// default app, relative to the project root, or null when it configures none.
|
|
111
277
|
function readTargetGoogleServicesFile(
|
|
@@ -120,27 +286,43 @@ function readTargetGoogleServicesFile(
|
|
|
120
286
|
/^AppDelegate\.(swift|mm|m)$/.test(path.basename(file)),
|
|
121
287
|
)
|
|
122
288
|
const defaultConfigured = delegates.some((file) => {
|
|
123
|
-
const
|
|
124
|
-
|
|
125
|
-
.replace(/\/\*[\s\S]*?\*\/|\/\/[^\n]*|"(?:\\.|[^"\\])*"/g, (token) =>
|
|
126
|
-
token.startsWith('"') ? '""' : token.replace(/[^\n]/g, ' '),
|
|
127
|
-
)
|
|
289
|
+
const raw = fs.readFileSync(file, 'utf8')
|
|
290
|
+
const source = blankSwiftObjcLiterals(raw)
|
|
128
291
|
const launch =
|
|
129
292
|
/(?:\bfunc\s+application\s*\([\s\S]*?didFinishLaunchingWithOptions[\s\S]*?\)\s*->\s*Bool|^-\s*\(BOOL\)\s*application\s*:[\s\S]*?didFinishLaunchingWithOptions[^{]*)\s*\{/m.exec(
|
|
130
293
|
source,
|
|
131
294
|
)
|
|
132
295
|
if (!launch) return false
|
|
133
|
-
const
|
|
134
|
-
const
|
|
135
|
-
|
|
296
|
+
const bodyStart = launch.index + launch[0].length
|
|
297
|
+
const body = source.slice(bodyStart)
|
|
298
|
+
const configure = findDefaultConfigureCall(body, app.allSources)
|
|
136
299
|
if (!configure) return false
|
|
137
|
-
|
|
300
|
+
let before = body.slice(0, configure.index)
|
|
138
301
|
let depth = 1
|
|
139
|
-
|
|
140
|
-
|
|
302
|
+
let guardOpen = -1
|
|
303
|
+
for (let index = 0; index < before.length; index++) {
|
|
304
|
+
const token = before[index]
|
|
305
|
+
if (token === '{') {
|
|
306
|
+
depth += 1
|
|
307
|
+
if (depth === 2) guardOpen = index
|
|
308
|
+
}
|
|
141
309
|
if (token === '}') depth -= 1
|
|
142
310
|
if (depth === 0) return false
|
|
143
311
|
}
|
|
312
|
+
let guard: string | null = null
|
|
313
|
+
if (depth === 2) {
|
|
314
|
+
const headers = [...before.slice(0, guardOpen).matchAll(/^[ \t]*if\b/gm)]
|
|
315
|
+
const header = headers.at(-1)
|
|
316
|
+
if (header) {
|
|
317
|
+
guard = raw.slice(bodyStart + header.index, bodyStart + guardOpen + 1)
|
|
318
|
+
before = before.slice(0, header.index)
|
|
319
|
+
depth = 1
|
|
320
|
+
for (const token of before) {
|
|
321
|
+
if (token === '{') depth += 1
|
|
322
|
+
if (token === '}') depth -= 1
|
|
323
|
+
}
|
|
324
|
+
}
|
|
325
|
+
}
|
|
144
326
|
let conditionalDepth = 0
|
|
145
327
|
for (const directive of before.matchAll(
|
|
146
328
|
/^\s*#\s*(if|ifdef|ifndef|elseif|elif|else|endif)\b/gm,
|
|
@@ -155,11 +337,25 @@ function readTargetGoogleServicesFile(
|
|
|
155
337
|
conditionalDepth -= 1
|
|
156
338
|
}
|
|
157
339
|
}
|
|
158
|
-
|
|
340
|
+
// a return inside a nested closure leaves the launch method running
|
|
341
|
+
let topLevelBefore = ''
|
|
342
|
+
let nesting = 0
|
|
343
|
+
for (const token of before) {
|
|
344
|
+
if (token === '{') nesting += 1
|
|
345
|
+
if (nesting === 0) topLevelBefore += token
|
|
346
|
+
if (token === '}') nesting -= 1
|
|
347
|
+
}
|
|
348
|
+
const guarded = guard === null ? true : evaluateFirebaseGuard(guard, app.resources)
|
|
349
|
+
if (
|
|
350
|
+
guarded === null ||
|
|
351
|
+
depth !== 1 ||
|
|
352
|
+
conditionalDepth !== 0 ||
|
|
353
|
+
/\b(?:return|throw)\b/.test(topLevelBefore)
|
|
354
|
+
)
|
|
159
355
|
throw new Error(
|
|
160
356
|
'native Firebase initialization must be unconditional in the app launch method',
|
|
161
357
|
)
|
|
162
|
-
return
|
|
358
|
+
return guarded
|
|
163
359
|
})
|
|
164
360
|
if (!defaultConfigured) return null
|
|
165
361
|
const resources = app.resources.filter((file) =>
|
package/src/nitro-compile.ts
CHANGED
|
@@ -20,10 +20,7 @@ export function collectNitroCompileInput(
|
|
|
20
20
|
): { input: SwiftCompileInput; collection: NitroSourceCollection } {
|
|
21
21
|
const collection = collectNitroSources(options)
|
|
22
22
|
const files = { ...collection.files }
|
|
23
|
-
const platform = path.join(
|
|
24
|
-
options.moduleRoot,
|
|
25
|
-
'platform/react-native-nitro-modules',
|
|
26
|
-
)
|
|
23
|
+
const platform = path.join(options.moduleRoot, 'platform/react-native-nitro-modules')
|
|
27
24
|
const read = (file: string) =>
|
|
28
25
|
readPackageFile(
|
|
29
26
|
platform,
|
|
@@ -71,7 +68,7 @@ export function collectNitroCompileInput(
|
|
|
71
68
|
'let package = Package(name: "PeachNativeModule", targets: [',
|
|
72
69
|
...collection.targets.map(
|
|
73
70
|
(target) =>
|
|
74
|
-
`.target(name: ${JSON.stringify(target.name)}, dependencies: ${JSON.stringify(target.dependencies?.map((dependency) => dependency.name) ?? [])}, path: ${JSON.stringify(target.path)}, cxxSettings: [.define("DEBUG")]),`,
|
|
71
|
+
`.target(name: ${JSON.stringify(target.name)}, dependencies: ${JSON.stringify(target.dependencies?.map((dependency) => dependency.name) ?? [])}, path: ${JSON.stringify(target.path)}, ${target.cxxInterop ? 'swiftSettings: [.interoperabilityMode(.Cxx)]' : 'cxxSettings: [.define("DEBUG")]'}),`,
|
|
75
72
|
),
|
|
76
73
|
`.target(name: "${runtime}", dependencies: ["JsiBridge", "NitroCore"], cxxSettings: [.define("DEBUG")]),`,
|
|
77
74
|
`.executableTarget(name: "PeachNitroApp", dependencies: ["${runtime}", "${implementation.name}"], swiftSettings: [.interoperabilityMode(.Cxx)]),`,
|
package/src/nitro-source.ts
CHANGED
|
@@ -55,61 +55,72 @@ function packageInfo(root: string): NitroSourceCollection['packages'][number] {
|
|
|
55
55
|
return { name: value.name, version: value.version, root }
|
|
56
56
|
}
|
|
57
57
|
|
|
58
|
-
function registrations(root: string)
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
const
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
)
|
|
70
|
-
|
|
71
|
-
)
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
58
|
+
function registrations(root: string) {
|
|
59
|
+
// cocoapods compiles this generated initializer through +autolinking.rb.
|
|
60
|
+
// nitro.json is a generator input and need not ship in the package.
|
|
61
|
+
const directory = path.join(root, 'nitrogen/generated/ios')
|
|
62
|
+
const initializers = fs
|
|
63
|
+
.readdirSync(directory)
|
|
64
|
+
.filter((file) => file.endsWith('Autolinking.mm'))
|
|
65
|
+
if (initializers.length !== 1)
|
|
66
|
+
throw new Error(`${root}: expected one generated Nitro iOS initializer`)
|
|
67
|
+
const source = readPackageFile(
|
|
68
|
+
root,
|
|
69
|
+
path.join(directory, initializers[0]!),
|
|
70
|
+
MAX_SWIFT_COMPILE_SOURCE_BYTES,
|
|
71
|
+
).toString('utf8')
|
|
72
|
+
const namespaces = [
|
|
73
|
+
...source.matchAll(/\busing namespace (margelo::nitro(?:::[A-Za-z_]\w*)*)\s*;/g),
|
|
74
|
+
]
|
|
75
|
+
const namespace = namespaces[1]?.[1]
|
|
76
|
+
if (namespaces.length !== 2 || namespaces[0]?.[1] !== 'margelo::nitro' || !namespace)
|
|
77
|
+
throw new Error(`${root}: invalid generated Nitro C++ namespace`)
|
|
78
|
+
|
|
79
|
+
const classes: (
|
|
80
|
+
| { name: string; implementation: string; language: 'c++' }
|
|
81
|
+
| { name: string; language: 'swift' }
|
|
82
|
+
)[] = []
|
|
83
|
+
let swiftModule: string | null = null
|
|
84
|
+
for (const registration of source.matchAll(
|
|
85
|
+
/\bHybridObjectRegistry::registerHybridObjectConstructor\(\s*("(?:[^"\\]|\\.)*")\s*,\s*\[\]\(\)\s*->\s*std::shared_ptr<HybridObject>\s*\{([\s\S]*?)\}\s*\);/g,
|
|
76
86
|
)) {
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
const languages = Object.entries(entry).map(
|
|
86
|
-
([platform, value]) =>
|
|
87
|
-
`${platform}:${object(value) ? String(value.language) : 'invalid'}`,
|
|
88
|
-
)
|
|
89
|
-
throw new Error(
|
|
90
|
-
`${root}: unsupported Nitro module ${name} (${languages.join(', ')}); ` +
|
|
91
|
-
'this collector supports shared all.language=c++ only, not Swift, Kotlin, Java or JNI',
|
|
87
|
+
const name: unknown = JSON.parse(registration[1]!)
|
|
88
|
+
const body = registration[2]!
|
|
89
|
+
if (typeof name !== 'string' || name.length === 0)
|
|
90
|
+
throw new Error(`${root}: invalid generated Nitro registration name`)
|
|
91
|
+
const cpp = /return\s+std::make_shared<([A-Za-z_]\w*)>\(\);/.exec(body)
|
|
92
|
+
const swift =
|
|
93
|
+
/=\s*([A-Za-z_]\w*)::([A-Za-z_]\w*)::create([A-Za-z_]\w*)\(\);\s*return hybridObject;/.exec(
|
|
94
|
+
body,
|
|
92
95
|
)
|
|
96
|
+
if (cpp && !swift) {
|
|
97
|
+
classes.push({ name, implementation: cpp[1]!, language: 'c++' })
|
|
98
|
+
} else if (
|
|
99
|
+
swift &&
|
|
100
|
+
!cpp &&
|
|
101
|
+
swift[2] === `${swift[1]}Autolinking` &&
|
|
102
|
+
swift[3] === name &&
|
|
103
|
+
(swiftModule === null || swiftModule === swift[1])
|
|
104
|
+
) {
|
|
105
|
+
swiftModule = swift[1]!
|
|
106
|
+
classes.push({ name, language: 'swift' })
|
|
107
|
+
} else {
|
|
108
|
+
throw new Error(`${root}: unsupported generated Nitro iOS registration ${name}`)
|
|
93
109
|
}
|
|
94
|
-
const implementation = entry.all.implementationClassName
|
|
95
|
-
if (
|
|
96
|
-
name.length === 0 ||
|
|
97
|
-
typeof implementation !== 'string' ||
|
|
98
|
-
!/^[A-Za-z_][A-Za-z0-9_]*$/.test(implementation)
|
|
99
|
-
)
|
|
100
|
-
throw new Error(`${root}: invalid Nitro C++ implementation for ${name}`)
|
|
101
|
-
classes.push({ name, implementation })
|
|
102
|
-
}
|
|
103
|
-
if (classes.length === 0) throw new Error(`${root}: no Nitro C++ registrations`)
|
|
104
|
-
return {
|
|
105
|
-
namespace: ['margelo', 'nitro', ...config.cxxNamespace].join('::'),
|
|
106
|
-
classes,
|
|
107
110
|
}
|
|
111
|
+
const count = [
|
|
112
|
+
...source.matchAll(/\bHybridObjectRegistry::registerHybridObjectConstructor\s*\(/g),
|
|
113
|
+
].length
|
|
114
|
+
if (classes.length === 0 || classes.length !== count)
|
|
115
|
+
throw new Error(`${root}: incomplete generated Nitro iOS registrations`)
|
|
116
|
+
if (new Set(classes.map(({ name }) => name)).size !== classes.length)
|
|
117
|
+
throw new Error(`${root}: duplicate generated Nitro iOS registration`)
|
|
118
|
+
classes.sort((a, b) => a.name.localeCompare(b.name))
|
|
119
|
+
return { namespace, swiftModule, classes }
|
|
108
120
|
}
|
|
109
121
|
|
|
110
|
-
/** collect installed
|
|
111
|
-
* rewriting package JavaScript.
|
|
112
|
-
* this C++ module collector; the caller must supply real platform bindings. */
|
|
122
|
+
/** collect installed native bodies, generated bridges, Nitro core and RN's
|
|
123
|
+
* JSI without reading or rewriting package JavaScript. */
|
|
113
124
|
export function collectNitroSources(options: NitroSourceOptions): NitroSourceCollection {
|
|
114
125
|
const module = packageInfo(options.packageRoot)
|
|
115
126
|
const config = registrations(module.root)
|
|
@@ -189,6 +200,7 @@ export function collectNitroSources(options: NitroSourceOptions): NitroSourceCol
|
|
|
189
200
|
includeNamespace: string | null,
|
|
190
201
|
skip: ReadonlySet<string> = new Set(),
|
|
191
202
|
select: (file: string) => boolean = () => true,
|
|
203
|
+
extensions: RegExp = /\.(?:c|cc|cpp|cxx|h|hpp|hxx|inl)$/,
|
|
192
204
|
) => {
|
|
193
205
|
const headers = new Map<string, string>()
|
|
194
206
|
const walk = (relative: string) => {
|
|
@@ -204,7 +216,7 @@ export function collectNitroSources(options: NitroSourceOptions): NitroSourceCol
|
|
|
204
216
|
walk(sourceRelative)
|
|
205
217
|
continue
|
|
206
218
|
}
|
|
207
|
-
if (
|
|
219
|
+
if (!extensions.test(entry.name)) continue
|
|
208
220
|
if (!select(entry.name)) continue
|
|
209
221
|
if (/[\p{Cc}"\\]/u.test(sourceRelative))
|
|
210
222
|
throw new Error(`invalid Nitro source path: ${sourceRelative}`)
|
|
@@ -259,13 +271,72 @@ export function collectNitroSources(options: NitroSourceOptions): NitroSourceCol
|
|
|
259
271
|
(file) => (file.endsWith('.h') && file !== 'JSIDynamic.h') || file === 'jsi.cpp',
|
|
260
272
|
)
|
|
261
273
|
collect(nitro.root, 'cpp', coreTarget, 'NitroModules', new Set(['views']))
|
|
262
|
-
const implementationHeaders =
|
|
274
|
+
const implementationHeaders = fs.existsSync(path.join(module.root, 'cpp'))
|
|
275
|
+
? collect(module.root, 'cpp', targetName, null)
|
|
276
|
+
: new Map<string, string>()
|
|
263
277
|
collect(module.root, 'nitrogen/generated/shared/c++', targetName, null)
|
|
264
|
-
for (const
|
|
265
|
-
if (
|
|
278
|
+
for (const entry of config.classes)
|
|
279
|
+
if (
|
|
280
|
+
entry.language === 'c++' &&
|
|
281
|
+
!implementationHeaders.has(`${entry.implementation}.hpp`)
|
|
282
|
+
)
|
|
266
283
|
throw new Error(
|
|
267
|
-
`${module.root}: missing C++ implementation header ${implementation}.hpp`,
|
|
284
|
+
`${module.root}: missing C++ implementation header ${entry.implementation}.hpp`,
|
|
285
|
+
)
|
|
286
|
+
|
|
287
|
+
if (config.swiftModule) {
|
|
288
|
+
for (const directory of ['ios/core', 'ios/utils'])
|
|
289
|
+
collect(
|
|
290
|
+
nitro.root,
|
|
291
|
+
directory,
|
|
292
|
+
coreTarget,
|
|
293
|
+
'NitroModules',
|
|
294
|
+
new Set(),
|
|
295
|
+
(file) => !file.endsWith('.swift'),
|
|
268
296
|
)
|
|
297
|
+
const nitroSwift = target('NitroModules', [coreTarget, jsiTarget])
|
|
298
|
+
nitroSwift.cxxInterop = true
|
|
299
|
+
const packageSwift = target(config.swiftModule, [targetName, 'NitroModules'])
|
|
300
|
+
packageSwift.cxxInterop = true
|
|
301
|
+
result.targets.push(nitroSwift, packageSwift)
|
|
302
|
+
for (const directory of ['ios/core', 'ios/utils', 'ios/views'])
|
|
303
|
+
collect(
|
|
304
|
+
nitro.root,
|
|
305
|
+
directory,
|
|
306
|
+
'NitroModules',
|
|
307
|
+
null,
|
|
308
|
+
new Set(['HybridView.swift']),
|
|
309
|
+
() => true,
|
|
310
|
+
/\.swift$/,
|
|
311
|
+
)
|
|
312
|
+
add(
|
|
313
|
+
`${targetPath('NitroModules')}/PeachNitroExports.swift`,
|
|
314
|
+
`@_exported import ${coreTarget}\n`,
|
|
315
|
+
)
|
|
316
|
+
collect(
|
|
317
|
+
module.root,
|
|
318
|
+
'ios',
|
|
319
|
+
config.swiftModule,
|
|
320
|
+
null,
|
|
321
|
+
new Set(['Tests']),
|
|
322
|
+
() => true,
|
|
323
|
+
/\.swift$/,
|
|
324
|
+
)
|
|
325
|
+
collect(
|
|
326
|
+
module.root,
|
|
327
|
+
'nitrogen/generated/ios',
|
|
328
|
+
config.swiftModule,
|
|
329
|
+
null,
|
|
330
|
+
new Set(),
|
|
331
|
+
() => true,
|
|
332
|
+
/\.swift$/,
|
|
333
|
+
)
|
|
334
|
+
collect(module.root, 'nitrogen/generated/ios', targetName, null)
|
|
335
|
+
add(
|
|
336
|
+
`${targetPath(config.swiftModule)}/PeachNitroExports.swift`,
|
|
337
|
+
`@_exported import ${targetName}\n`,
|
|
338
|
+
)
|
|
339
|
+
}
|
|
269
340
|
|
|
270
341
|
// explicit module maps avoid importing every RN view or private JSI header.
|
|
271
342
|
add(
|
|
@@ -274,7 +345,7 @@ export function collectNitroSources(options: NitroSourceOptions): NitroSourceCol
|
|
|
274
345
|
)
|
|
275
346
|
add(
|
|
276
347
|
`${targetPath(coreTarget)}/include/module.modulemap`,
|
|
277
|
-
`module ${coreTarget} { header "NitroModules/InstallNitro.hpp" export * }\n`,
|
|
348
|
+
`module ${coreTarget} { header "NitroModules/InstallNitro.hpp"${config.swiftModule ? ['ArrayBufferHolder.hpp', 'AnyMapUtils.hpp', 'PromiseHolder.hpp', 'Result.hpp', 'RuntimeError.hpp', 'DateToChronoDate.hpp'].map((header) => ` header "NitroModules/${header}"`).join('') : ''} export * }\n`,
|
|
278
349
|
)
|
|
279
350
|
add(
|
|
280
351
|
result.registration.header,
|
|
@@ -282,7 +353,7 @@ export function collectNitroSources(options: NitroSourceOptions): NitroSourceCol
|
|
|
282
353
|
)
|
|
283
354
|
add(
|
|
284
355
|
`${targetPath(targetName)}/include/module.modulemap`,
|
|
285
|
-
`module ${targetName} { header "PeachNitroRegistration.hpp" export * }\n`,
|
|
356
|
+
`module ${targetName} { header "PeachNitroRegistration.hpp"${config.swiftModule ? ` header "${config.swiftModule}-Swift-Cxx-Bridge.hpp"` : ''} export * }\n`,
|
|
286
357
|
)
|
|
287
358
|
add(
|
|
288
359
|
`${targetPath(targetName)}/PeachNitroRegistration.cpp`,
|
|
@@ -291,15 +362,28 @@ export function collectNitroSources(options: NitroSourceOptions): NitroSourceCol
|
|
|
291
362
|
'#include <NitroModules/HybridObjectRegistry.hpp>',
|
|
292
363
|
'#include <memory>',
|
|
293
364
|
'#include <type_traits>',
|
|
294
|
-
...
|
|
295
|
-
|
|
296
|
-
|
|
365
|
+
...(config.swiftModule
|
|
366
|
+
? [`#include "${config.swiftModule}-Swift-Cxx-Umbrella.hpp"`]
|
|
367
|
+
: []),
|
|
368
|
+
...[
|
|
369
|
+
...new Set(
|
|
370
|
+
config.classes
|
|
371
|
+
.filter((entry) => entry.language === 'c++')
|
|
372
|
+
.map(({ implementation }) => implementation),
|
|
373
|
+
),
|
|
374
|
+
].map((implementation) => `#include "${implementation}.hpp"`),
|
|
297
375
|
`extern "C" void ${result.registration.symbol}() {`,
|
|
298
|
-
...config.classes.flatMap((
|
|
299
|
-
|
|
376
|
+
...config.classes.flatMap((entry) => [
|
|
377
|
+
...(entry.language === 'c++'
|
|
378
|
+
? [
|
|
379
|
+
` static_assert(std::is_default_constructible_v<${config.namespace}::${entry.implementation}>);`,
|
|
380
|
+
]
|
|
381
|
+
: []),
|
|
300
382
|
' margelo::nitro::HybridObjectRegistry::registerHybridObjectConstructor(',
|
|
301
|
-
` ${JSON.stringify(name)}, []() -> std::shared_ptr<margelo::nitro::HybridObject> {`,
|
|
302
|
-
|
|
383
|
+
` ${JSON.stringify(entry.name)}, []() -> std::shared_ptr<margelo::nitro::HybridObject> {`,
|
|
384
|
+
entry.language === 'swift'
|
|
385
|
+
? ` return ${config.swiftModule}::${config.swiftModule}Autolinking::create${entry.name}();`
|
|
386
|
+
: ` return std::make_shared<${config.namespace}::${entry.implementation}>();`,
|
|
303
387
|
' });',
|
|
304
388
|
]),
|
|
305
389
|
'}',
|