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
|
@@ -1,11 +1,13 @@
|
|
|
1
1
|
import fs from 'node:fs'
|
|
2
2
|
import { createRequire } from 'node:module'
|
|
3
3
|
import path from 'node:path'
|
|
4
|
+
import { androidMergedResourceValuesDirectory } from './android-app-identity'
|
|
4
5
|
import {
|
|
5
|
-
|
|
6
|
+
extractNativeAppIdentityFromFiles,
|
|
6
7
|
type NativeAppIdentity,
|
|
7
|
-
type NativeAppIdentityPlatform,
|
|
8
8
|
readRootModuleName,
|
|
9
|
+
readGoogleSignInConfiguration,
|
|
10
|
+
selectNativeAppIdentityReader,
|
|
9
11
|
} from './native-app-identity'
|
|
10
12
|
import { resolveNativeAppIconArtwork } from './nativeAppIconContract'
|
|
11
13
|
import { parsePlistSource } from './plist'
|
|
@@ -89,6 +91,11 @@ export function readNativeAppIdentityFromXcodeTarget(
|
|
|
89
91
|
permissionUsageDescriptions[key] = value
|
|
90
92
|
}
|
|
91
93
|
|
|
94
|
+
const infoPlistStrings: Record<string, string> = {}
|
|
95
|
+
for (const [key, value] of Object.entries(dictionary)) {
|
|
96
|
+
if (typeof value === 'string') infoPlistStrings[key] = value
|
|
97
|
+
}
|
|
98
|
+
|
|
92
99
|
const querySchemes = dictionary.LSApplicationQueriesSchemes
|
|
93
100
|
const urlQuerySchemes = Array.isArray(querySchemes) ? querySchemes : []
|
|
94
101
|
|
|
@@ -129,6 +136,10 @@ export function readNativeAppIdentityFromXcodeTarget(
|
|
|
129
136
|
)
|
|
130
137
|
const shareIntoAppGroupId = nonEmptyString(dictionary.ExpoShareIntoAppGroupId)
|
|
131
138
|
const rtcAppGroupIdentifier = nonEmptyString(dictionary.RTCAppGroupIdentifier)
|
|
139
|
+
const screenShareExtensionBundleId =
|
|
140
|
+
typeof dictionary.ScreenShareExtensionBundleId === 'string'
|
|
141
|
+
? dictionary.ScreenShareExtensionBundleId
|
|
142
|
+
: undefined
|
|
132
143
|
const rootModuleName = readRootModuleName(
|
|
133
144
|
app.allSources
|
|
134
145
|
.filter((file) => /\.(?:swift|mm|m)$/.test(file) && fs.existsSync(file))
|
|
@@ -141,6 +152,15 @@ export function readNativeAppIdentityFromXcodeTarget(
|
|
|
141
152
|
...(rootModuleName ? { rootModuleName } : {}),
|
|
142
153
|
...(rootViewBackgroundColor !== undefined ? { rootViewBackgroundColor } : {}),
|
|
143
154
|
...(designRequiresCompatibility ? { designRequiresCompatibility } : {}),
|
|
155
|
+
...(typeof dictionary.UIStatusBarHidden === 'boolean'
|
|
156
|
+
? { statusBarHidden: dictionary.UIStatusBarHidden }
|
|
157
|
+
: {}),
|
|
158
|
+
...(typeof dictionary.UIViewControllerBasedStatusBarAppearance === 'boolean'
|
|
159
|
+
? {
|
|
160
|
+
viewControllerBasedStatusBarAppearance:
|
|
161
|
+
dictionary.UIViewControllerBasedStatusBarAppearance,
|
|
162
|
+
}
|
|
163
|
+
: {}),
|
|
144
164
|
...(typeof dictionary.DTPlatformVersion === 'string'
|
|
145
165
|
? { dtPlatformVersion: dictionary.DTPlatformVersion }
|
|
146
166
|
: {}),
|
|
@@ -149,6 +169,14 @@ export function readNativeAppIdentityFromXcodeTarget(
|
|
|
149
169
|
buildVersion,
|
|
150
170
|
bundleIdentifier,
|
|
151
171
|
platform: 'ios',
|
|
172
|
+
googleSignIn: readGoogleSignInConfiguration(
|
|
173
|
+
dictionary,
|
|
174
|
+
Object.fromEntries(
|
|
175
|
+
app.resources
|
|
176
|
+
.filter((file) => file.endsWith('.plist'))
|
|
177
|
+
.map((file) => [file, parseInfoPlist(fs.readFileSync(file, 'utf8'))]),
|
|
178
|
+
),
|
|
179
|
+
),
|
|
152
180
|
supportedInterfaceOrientations,
|
|
153
181
|
ipadSupportedInterfaceOrientations,
|
|
154
182
|
...(defaultScreenOrientationMask ? { defaultScreenOrientationMask } : {}),
|
|
@@ -172,9 +200,13 @@ export function readNativeAppIdentityFromXcodeTarget(
|
|
|
172
200
|
(value) => value,
|
|
173
201
|
),
|
|
174
202
|
permissionUsageDescriptions,
|
|
203
|
+
infoPlistStrings,
|
|
175
204
|
urlQuerySchemes,
|
|
176
205
|
...(shareIntoAppGroupId ? { shareIntoAppGroupId } : {}),
|
|
177
206
|
...(rtcAppGroupIdentifier ? { rtcAppGroupIdentifier } : {}),
|
|
207
|
+
...(screenShareExtensionBundleId === undefined
|
|
208
|
+
? {}
|
|
209
|
+
: { screenShareExtensionBundleId }),
|
|
178
210
|
backgroundTaskIdentifiers,
|
|
179
211
|
backgroundModes,
|
|
180
212
|
appIntentIdentifiers,
|
|
@@ -190,8 +222,113 @@ export function readNativeAppIdentityFromProject(
|
|
|
190
222
|
platform: unknown,
|
|
191
223
|
parseInfoPlist: (source: string) => unknown = parsePlistSource,
|
|
192
224
|
iosTarget?: string,
|
|
225
|
+
androidMergedManifest?: string,
|
|
226
|
+
): NativeAppIdentity | null {
|
|
227
|
+
const read = selectNativeAppIdentityReader(platform, {
|
|
228
|
+
android: () => readAndroidNativeAppIdentity(projectRoot, androidMergedManifest),
|
|
229
|
+
ios: () => readIosNativeAppIdentity(projectRoot, parseInfoPlist, iosTarget),
|
|
230
|
+
})
|
|
231
|
+
return read ? read() : null
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
// a project whose Xcode build settings do not resolve still names its app in
|
|
235
|
+
// the checked-in Info.plist and AppDelegate sources; android ships no such
|
|
236
|
+
// source identity.
|
|
237
|
+
export function readNativeAppIdentityFromSources(
|
|
238
|
+
projectRoot: string,
|
|
239
|
+
platform: unknown,
|
|
240
|
+
parseInfoPlist: (source: string) => unknown,
|
|
241
|
+
iosTarget: string | undefined,
|
|
242
|
+
): NativeAppIdentity | null {
|
|
243
|
+
const read = selectNativeAppIdentityReader(platform, {
|
|
244
|
+
android: () => null,
|
|
245
|
+
ios: () => {
|
|
246
|
+
const iosRoot = path.join(projectRoot, 'ios')
|
|
247
|
+
if (!fs.existsSync(iosRoot)) return null
|
|
248
|
+
const files: Record<string, string> = {}
|
|
249
|
+
for (const entry of fs.readdirSync(iosRoot, { withFileTypes: true })) {
|
|
250
|
+
if (entry.name === 'Pods') continue
|
|
251
|
+
if (entry.isDirectory()) {
|
|
252
|
+
const names = entry.name.endsWith('.xcodeproj')
|
|
253
|
+
? ['project.pbxproj']
|
|
254
|
+
: ['Info.plist', 'AppDelegate.swift', 'AppDelegate.mm', 'AppDelegate.m']
|
|
255
|
+
for (const name of names) {
|
|
256
|
+
const relative = `ios/${entry.name}/${name}`
|
|
257
|
+
const file = path.join(projectRoot, relative)
|
|
258
|
+
if (fs.existsSync(file)) files[relative] = fs.readFileSync(file, 'utf8')
|
|
259
|
+
}
|
|
260
|
+
} else if (/AppDelegate\.(?:swift|mm|m)$/.test(entry.name)) {
|
|
261
|
+
const relative = `ios/${entry.name}`
|
|
262
|
+
const file = path.join(projectRoot, relative)
|
|
263
|
+
if (fs.existsSync(file)) files[relative] = fs.readFileSync(file, 'utf8')
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
return extractNativeAppIdentityFromFiles(files, 'ios', parseInfoPlist, iosTarget)
|
|
267
|
+
},
|
|
268
|
+
})
|
|
269
|
+
return read ? read() : null
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
function readAndroidNativeAppIdentity(
|
|
273
|
+
projectRoot: string,
|
|
274
|
+
androidMergedManifest: string | undefined,
|
|
275
|
+
): NativeAppIdentity | null {
|
|
276
|
+
const files: Record<string, string> = {}
|
|
277
|
+
if (androidMergedManifest) {
|
|
278
|
+
const file = path.resolve(projectRoot, androidMergedManifest)
|
|
279
|
+
if (!fs.existsSync(file)) return null
|
|
280
|
+
files[androidMergedManifest] = fs.readFileSync(file, 'utf8')
|
|
281
|
+
} else {
|
|
282
|
+
const androidRoot = path.join(projectRoot, 'android')
|
|
283
|
+
if (!fs.existsSync(androidRoot)) return null
|
|
284
|
+
const collect = (directory: string): void => {
|
|
285
|
+
for (const entry of fs.readdirSync(directory, { withFileTypes: true })) {
|
|
286
|
+
const file = path.join(directory, entry.name)
|
|
287
|
+
if (entry.isDirectory()) collect(file)
|
|
288
|
+
else if (entry.isFile() && entry.name === 'AndroidManifest.xml')
|
|
289
|
+
files[path.relative(projectRoot, file).split(path.sep).join('/')] =
|
|
290
|
+
fs.readFileSync(file, 'utf8')
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
for (const module of fs.readdirSync(androidRoot, { withFileTypes: true })) {
|
|
294
|
+
if (!module.isDirectory()) continue
|
|
295
|
+
const outputs = path.join(
|
|
296
|
+
androidRoot,
|
|
297
|
+
module.name,
|
|
298
|
+
'build/intermediates/merged_manifests',
|
|
299
|
+
)
|
|
300
|
+
if (fs.existsSync(outputs)) collect(outputs)
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
for (const manifest of Object.keys(files)) {
|
|
304
|
+
const resourceDirectory = androidMergedResourceValuesDirectory(manifest)
|
|
305
|
+
if (!resourceDirectory) continue
|
|
306
|
+
const absolute = path.resolve(projectRoot, resourceDirectory)
|
|
307
|
+
if (!fs.existsSync(absolute)) continue
|
|
308
|
+
for (const entry of fs.readdirSync(absolute, { withFileTypes: true })) {
|
|
309
|
+
if (!entry.isDirectory() || !/^values(?:-|$)/.test(entry.name)) continue
|
|
310
|
+
for (const resource of fs.readdirSync(path.join(absolute, entry.name))) {
|
|
311
|
+
if (!resource.endsWith('.xml')) continue
|
|
312
|
+
const file = `${resourceDirectory}/${entry.name}/${resource}`
|
|
313
|
+
files[file] = fs.readFileSync(path.resolve(projectRoot, file), 'utf8')
|
|
314
|
+
}
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
return extractNativeAppIdentityFromFiles(
|
|
318
|
+
files,
|
|
319
|
+
'android',
|
|
320
|
+
parsePlistSource,
|
|
321
|
+
undefined,
|
|
322
|
+
undefined,
|
|
323
|
+
androidMergedManifest,
|
|
324
|
+
)
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
function readIosNativeAppIdentity(
|
|
328
|
+
projectRoot: string,
|
|
329
|
+
parseInfoPlist: (source: string) => unknown,
|
|
330
|
+
iosTarget: string | undefined,
|
|
193
331
|
): NativeAppIdentity | null {
|
|
194
|
-
if (!canExtractNativeAppIdentity(platform)) return null
|
|
195
332
|
const iosRoot = path.join(projectRoot, 'ios')
|
|
196
333
|
if (!fs.existsSync(iosRoot)) return null
|
|
197
334
|
const projects = fs
|
|
@@ -251,8 +388,8 @@ export function readNativeAppIdentityFromProject(
|
|
|
251
388
|
? nonEmptyString(installedManifest.version)
|
|
252
389
|
: undefined
|
|
253
390
|
if (!installedVersion) throw new Error('Reanimated package version is malformed')
|
|
254
|
-
//
|
|
255
|
-
if (
|
|
391
|
+
// the static feature flag schema starts with reanimated 4.
|
|
392
|
+
if (Number.parseInt(installedVersion, 10) < 4) return identity
|
|
256
393
|
const defaults: unknown = JSON.parse(
|
|
257
394
|
fs.readFileSync(
|
|
258
395
|
path.join(
|
|
@@ -1,13 +1,14 @@
|
|
|
1
1
|
// generic native app identity: the values an installed iOS/Android binary
|
|
2
2
|
// carries, read from the app's own native project (`Info.plist` resolved
|
|
3
|
-
// against
|
|
4
|
-
// a simulator tenant reports the
|
|
3
|
+
// against Xcode Release build settings, or a completed Android app manifest
|
|
4
|
+
// merger output), so a simulator tenant reports the selected native build.
|
|
5
5
|
//
|
|
6
6
|
// identity travels in a bundle footer or response header. the one updates
|
|
7
7
|
// receipt travels only in the response header so publisher JavaScript stays
|
|
8
8
|
// unchanged. tenant boot validates both before evaluation and clears the realm
|
|
9
9
|
// store when neither carries identity.
|
|
10
10
|
|
|
11
|
+
import { nativeAppIdentityFromAndroidManifest } from './android-app-identity'
|
|
11
12
|
import { findBundleFooter } from './bundleGlobalFooter'
|
|
12
13
|
import {
|
|
13
14
|
resolveNativeAppIconArtwork,
|
|
@@ -27,8 +28,14 @@ export interface NativeOneUpdatesReceipt {
|
|
|
27
28
|
embeddedManifestJson: string | null
|
|
28
29
|
}
|
|
29
30
|
|
|
31
|
+
export interface AndroidManifestPermission {
|
|
32
|
+
name: string
|
|
33
|
+
minSdkVersion: number
|
|
34
|
+
maxSdkVersion?: number
|
|
35
|
+
}
|
|
36
|
+
|
|
30
37
|
export interface NativeAppIdentity {
|
|
31
|
-
// the user-facing name (
|
|
38
|
+
// the user-facing name (iOS bundle name or a resolved Android app label).
|
|
32
39
|
applicationName: string | null
|
|
33
40
|
// the literal CFBundleDisplayName, without a bundle-name fallback.
|
|
34
41
|
applicationDisplayName?: string
|
|
@@ -41,6 +48,9 @@ export interface NativeAppIdentity {
|
|
|
41
48
|
// UIDesignRequiresCompatibility: UIKit draws its pre-liquid-glass chrome.
|
|
42
49
|
// carried only when the plist sets it true.
|
|
43
50
|
designRequiresCompatibility?: true
|
|
51
|
+
// initial UIKit visibility and the owner of subsequent status bar preferences.
|
|
52
|
+
statusBarHidden?: boolean
|
|
53
|
+
viewControllerBasedStatusBarAppearance?: boolean
|
|
44
54
|
// the literal DTPlatformVersion stamp when the app plist supplies one.
|
|
45
55
|
dtPlatformVersion?: string
|
|
46
56
|
// CFBundleName, which CFNetwork stamps into the default User-Agent and iOS's
|
|
@@ -51,7 +61,14 @@ export interface NativeAppIdentity {
|
|
|
51
61
|
buildVersion: string
|
|
52
62
|
bundleIdentifier: string
|
|
53
63
|
platform: NativeAppIdentityPlatform
|
|
64
|
+
// declarations from the selected app manifest merger output; install-time
|
|
65
|
+
// SDK filtering belongs to the evaluation's device, not the bundle builder.
|
|
66
|
+
androidManifestPermissions?: readonly AndroidManifestPermission[]
|
|
54
67
|
permissionUsageDescriptions?: Readonly<Record<string, unknown>>
|
|
68
|
+
// every top-level string the app's Info.plist declares, build settings
|
|
69
|
+
// resolved, for native code that reads its own plist keys.
|
|
70
|
+
infoPlistStrings?: Readonly<Record<string, string>>
|
|
71
|
+
googleSignIn?: Readonly<Record<string, unknown>>
|
|
55
72
|
urlQuerySchemes?: readonly unknown[]
|
|
56
73
|
appIntentIdentifiers?: readonly string[]
|
|
57
74
|
backgroundTaskIdentifiers?: readonly string[]
|
|
@@ -61,6 +78,7 @@ export interface NativeAppIdentity {
|
|
|
61
78
|
defaultScreenOrientationMask?: string
|
|
62
79
|
shareIntoAppGroupId?: string
|
|
63
80
|
rtcAppGroupIdentifier?: string
|
|
81
|
+
screenShareExtensionBundleId?: string
|
|
64
82
|
alternateIconNames?: readonly string[]
|
|
65
83
|
appIconArtwork?: NativeAppIconArtwork
|
|
66
84
|
// resolved Reanimated compile flags from the native build inputs.
|
|
@@ -91,6 +109,9 @@ function nonEmptyString(value: unknown): string | null {
|
|
|
91
109
|
|
|
92
110
|
export function isNativeAppIdentity(value: unknown): value is NativeAppIdentity {
|
|
93
111
|
if (!isRecord(value)) return false
|
|
112
|
+
for (const key of ['statusBarHidden', 'viewControllerBasedStatusBarAppearance']) {
|
|
113
|
+
if (value[key] !== undefined && typeof value[key] !== 'boolean') return false
|
|
114
|
+
}
|
|
94
115
|
const oneUpdates = value.oneUpdates
|
|
95
116
|
if (
|
|
96
117
|
oneUpdates !== undefined &&
|
|
@@ -114,6 +135,25 @@ export function isNativeAppIdentity(value: unknown): value is NativeAppIdentity
|
|
|
114
135
|
typeof value.dtPlatformVersion !== 'string'
|
|
115
136
|
)
|
|
116
137
|
return false
|
|
138
|
+
if (
|
|
139
|
+
value.androidManifestPermissions !== undefined &&
|
|
140
|
+
(!Array.isArray(value.androidManifestPermissions) ||
|
|
141
|
+
!value.androidManifestPermissions.every(
|
|
142
|
+
(permission: unknown) =>
|
|
143
|
+
isRecord(permission) &&
|
|
144
|
+
typeof permission.name === 'string' &&
|
|
145
|
+
permission.name.length > 0 &&
|
|
146
|
+
Number.isInteger(permission.minSdkVersion) &&
|
|
147
|
+
typeof permission.minSdkVersion === 'number' &&
|
|
148
|
+
permission.minSdkVersion > 0 &&
|
|
149
|
+
(permission.maxSdkVersion === undefined ||
|
|
150
|
+
(typeof permission.maxSdkVersion === 'number' &&
|
|
151
|
+
Number.isInteger(permission.maxSdkVersion) &&
|
|
152
|
+
permission.maxSdkVersion >= 0)),
|
|
153
|
+
))
|
|
154
|
+
)
|
|
155
|
+
return false
|
|
156
|
+
if (value.googleSignIn !== undefined && !isRecord(value.googleSignIn)) return false
|
|
117
157
|
const descriptions = value.permissionUsageDescriptions
|
|
118
158
|
if (
|
|
119
159
|
value.reanimatedStaticFeatureFlags !== undefined &&
|
|
@@ -124,6 +164,12 @@ export function isNativeAppIdentity(value: unknown): value is NativeAppIdentity
|
|
|
124
164
|
)
|
|
125
165
|
return false
|
|
126
166
|
if (descriptions !== undefined && !isRecord(descriptions)) return false
|
|
167
|
+
if (
|
|
168
|
+
value.infoPlistStrings !== undefined &&
|
|
169
|
+
(!isRecord(value.infoPlistStrings) ||
|
|
170
|
+
!Object.values(value.infoPlistStrings).every((entry) => typeof entry === 'string'))
|
|
171
|
+
)
|
|
172
|
+
return false
|
|
127
173
|
if (value.urlQuerySchemes !== undefined && !Array.isArray(value.urlQuerySchemes))
|
|
128
174
|
return false
|
|
129
175
|
if (
|
|
@@ -156,6 +202,11 @@ export function isNativeAppIdentity(value: unknown): value is NativeAppIdentity
|
|
|
156
202
|
typeof value.rtcAppGroupIdentifier !== 'string'
|
|
157
203
|
)
|
|
158
204
|
return false
|
|
205
|
+
if (
|
|
206
|
+
value.screenShareExtensionBundleId !== undefined &&
|
|
207
|
+
typeof value.screenShareExtensionBundleId !== 'string'
|
|
208
|
+
)
|
|
209
|
+
return false
|
|
159
210
|
if (
|
|
160
211
|
value.applicationDisplayName !== undefined &&
|
|
161
212
|
typeof value.applicationDisplayName !== 'string'
|
|
@@ -233,6 +284,15 @@ export function readNativeAppIdentity(value: unknown): NativeAppIdentity | null
|
|
|
233
284
|
...(value.designRequiresCompatibility === undefined
|
|
234
285
|
? {}
|
|
235
286
|
: { designRequiresCompatibility: value.designRequiresCompatibility }),
|
|
287
|
+
...(value.statusBarHidden === undefined
|
|
288
|
+
? {}
|
|
289
|
+
: { statusBarHidden: value.statusBarHidden }),
|
|
290
|
+
...(value.viewControllerBasedStatusBarAppearance === undefined
|
|
291
|
+
? {}
|
|
292
|
+
: {
|
|
293
|
+
viewControllerBasedStatusBarAppearance:
|
|
294
|
+
value.viewControllerBasedStatusBarAppearance,
|
|
295
|
+
}),
|
|
236
296
|
...(value.dtPlatformVersion === undefined
|
|
237
297
|
? {}
|
|
238
298
|
: { dtPlatformVersion: value.dtPlatformVersion }),
|
|
@@ -250,11 +310,24 @@ export function readNativeAppIdentity(value: unknown): NativeAppIdentity | null
|
|
|
250
310
|
...(value.alternateIconNames === undefined
|
|
251
311
|
? {}
|
|
252
312
|
: { alternateIconNames: [...value.alternateIconNames] }),
|
|
313
|
+
...(value.androidManifestPermissions === undefined
|
|
314
|
+
? {}
|
|
315
|
+
: {
|
|
316
|
+
androidManifestPermissions: value.androidManifestPermissions.map(
|
|
317
|
+
(permission) => ({ ...permission }),
|
|
318
|
+
),
|
|
319
|
+
}),
|
|
320
|
+
...(value.googleSignIn === undefined
|
|
321
|
+
? {}
|
|
322
|
+
: { googleSignIn: structuredClone(value.googleSignIn) }),
|
|
253
323
|
...(value.permissionUsageDescriptions === undefined
|
|
254
324
|
? {}
|
|
255
325
|
: {
|
|
256
326
|
permissionUsageDescriptions: { ...value.permissionUsageDescriptions },
|
|
257
327
|
}),
|
|
328
|
+
...(value.infoPlistStrings === undefined
|
|
329
|
+
? {}
|
|
330
|
+
: { infoPlistStrings: { ...value.infoPlistStrings } }),
|
|
258
331
|
...(value.appIntentIdentifiers === undefined
|
|
259
332
|
? {}
|
|
260
333
|
: { appIntentIdentifiers: [...value.appIntentIdentifiers] }),
|
|
@@ -285,6 +358,9 @@ export function readNativeAppIdentity(value: unknown): NativeAppIdentity | null
|
|
|
285
358
|
...(value.rtcAppGroupIdentifier === undefined
|
|
286
359
|
? {}
|
|
287
360
|
: { rtcAppGroupIdentifier: value.rtcAppGroupIdentifier }),
|
|
361
|
+
...(value.screenShareExtensionBundleId === undefined
|
|
362
|
+
? {}
|
|
363
|
+
: { screenShareExtensionBundleId: value.screenShareExtensionBundleId }),
|
|
288
364
|
...(value.urlQuerySchemes === undefined
|
|
289
365
|
? {}
|
|
290
366
|
: { urlQuerySchemes: [...value.urlQuerySchemes] }),
|
|
@@ -452,6 +528,39 @@ export function insertNativeAppIdentityFooter(
|
|
|
452
528
|
|
|
453
529
|
// ── native project extraction ────────────────────────────────────────────────
|
|
454
530
|
|
|
531
|
+
// Info.plist and bundled service plists are native configuration, independent of Firebase initialization.
|
|
532
|
+
export function readGoogleSignInConfiguration(
|
|
533
|
+
plist: Record<string, unknown>,
|
|
534
|
+
resources: Readonly<Record<string, unknown>>,
|
|
535
|
+
): Record<string, unknown> {
|
|
536
|
+
const clientIds: Record<string, string> = {}
|
|
537
|
+
for (const [file, value] of Object.entries(resources)) {
|
|
538
|
+
if (isRecord(value) && typeof value.CLIENT_ID === 'string')
|
|
539
|
+
clientIds[
|
|
540
|
+
file
|
|
541
|
+
.split('/')
|
|
542
|
+
.pop()
|
|
543
|
+
?.replace(/\.plist$/, '') ?? file
|
|
544
|
+
] = value.CLIENT_ID
|
|
545
|
+
}
|
|
546
|
+
const urlSchemes: string[] = []
|
|
547
|
+
for (const entry of Array.isArray(plist.CFBundleURLTypes)
|
|
548
|
+
? plist.CFBundleURLTypes
|
|
549
|
+
: []) {
|
|
550
|
+
if (!isRecord(entry) || !Array.isArray(entry.CFBundleURLSchemes)) continue
|
|
551
|
+
for (const scheme of entry.CFBundleURLSchemes)
|
|
552
|
+
if (typeof scheme === 'string') urlSchemes.push(scheme)
|
|
553
|
+
}
|
|
554
|
+
return {
|
|
555
|
+
clientId: plist.GIDClientID,
|
|
556
|
+
serverClientId: plist.GIDServerClientID,
|
|
557
|
+
hostedDomain: plist.GIDHostedDomain,
|
|
558
|
+
openIDRealm: plist.GIDOpenIDRealm,
|
|
559
|
+
urlSchemes,
|
|
560
|
+
clientIds,
|
|
561
|
+
}
|
|
562
|
+
}
|
|
563
|
+
|
|
455
564
|
function plistString(plist: string, key: string): string | null {
|
|
456
565
|
const match = plist.match(new RegExp(`<key>${key}</key>\\s*<string>([^<]*)</string>`))
|
|
457
566
|
return match ? match[1].trim() : null
|
|
@@ -873,6 +982,13 @@ function nativeAppIdentityFromIos(
|
|
|
873
982
|
: undefined
|
|
874
983
|
if (rtcAppGroupIdentifier === null)
|
|
875
984
|
throw new Error('native app Info.plist RTCAppGroupIdentifier is unresolved')
|
|
985
|
+
const declaredScreenShareExtension = dictionary.ScreenShareExtensionBundleId
|
|
986
|
+
const screenShareExtensionBundleId =
|
|
987
|
+
typeof declaredScreenShareExtension === 'string'
|
|
988
|
+
? resolveBuildSetting(declaredScreenShareExtension, settings)
|
|
989
|
+
: undefined
|
|
990
|
+
if (screenShareExtensionBundleId === null)
|
|
991
|
+
throw new Error('native app Info.plist ScreenShareExtensionBundleId is unresolved')
|
|
876
992
|
const querySchemes = dictionary.LSApplicationQueriesSchemes
|
|
877
993
|
if (querySchemes !== undefined && !Array.isArray(querySchemes)) {
|
|
878
994
|
throw new Error('native app Info.plist LSApplicationQueriesSchemes is not an array')
|
|
@@ -901,6 +1017,13 @@ function nativeAppIdentityFromIos(
|
|
|
901
1017
|
permissionUsageDescriptions[key] = resolved
|
|
902
1018
|
}
|
|
903
1019
|
|
|
1020
|
+
const infoPlistStrings: Record<string, string> = {}
|
|
1021
|
+
for (const [key, value] of Object.entries(dictionary)) {
|
|
1022
|
+
if (typeof value !== 'string') continue
|
|
1023
|
+
const resolved = resolveBuildSetting(value, settings)
|
|
1024
|
+
if (resolved !== null) infoPlistStrings[key] = resolved
|
|
1025
|
+
}
|
|
1026
|
+
|
|
904
1027
|
// Xcode's generated Info.plist uses these build settings only when enabled.
|
|
905
1028
|
// source: developer.apple.com/documentation/xcode/build-settings-reference.
|
|
906
1029
|
if (settings.GENERATE_INFOPLIST_FILE === 'YES') {
|
|
@@ -974,12 +1097,29 @@ function nativeAppIdentityFromIos(
|
|
|
974
1097
|
...(rootModuleName === undefined ? {} : { rootModuleName }),
|
|
975
1098
|
...(rootViewBackgroundColor === undefined ? {} : { rootViewBackgroundColor }),
|
|
976
1099
|
...(designRequiresCompatibility ? { designRequiresCompatibility } : {}),
|
|
1100
|
+
...(typeof dictionary.UIStatusBarHidden === 'boolean'
|
|
1101
|
+
? { statusBarHidden: dictionary.UIStatusBarHidden }
|
|
1102
|
+
: {}),
|
|
1103
|
+
...(typeof dictionary.UIViewControllerBasedStatusBarAppearance === 'boolean'
|
|
1104
|
+
? {
|
|
1105
|
+
viewControllerBasedStatusBarAppearance:
|
|
1106
|
+
dictionary.UIViewControllerBasedStatusBarAppearance,
|
|
1107
|
+
}
|
|
1108
|
+
: {}),
|
|
977
1109
|
...(dtPlatformVersion === undefined ? {} : { dtPlatformVersion }),
|
|
978
1110
|
bundleName,
|
|
979
1111
|
appVersion,
|
|
980
1112
|
buildVersion,
|
|
981
1113
|
bundleIdentifier,
|
|
982
1114
|
platform: 'ios',
|
|
1115
|
+
googleSignIn: readGoogleSignInConfiguration(
|
|
1116
|
+
dictionary,
|
|
1117
|
+
Object.fromEntries(
|
|
1118
|
+
[...resources]
|
|
1119
|
+
.filter((file) => file.endsWith('.plist') && files[file])
|
|
1120
|
+
.map((file) => [file, parseInfoPlist(files[file])]),
|
|
1121
|
+
),
|
|
1122
|
+
),
|
|
983
1123
|
supportedInterfaceOrientations,
|
|
984
1124
|
ipadSupportedInterfaceOrientations,
|
|
985
1125
|
...(defaultScreenOrientationMask === undefined
|
|
@@ -1005,9 +1145,13 @@ function nativeAppIdentityFromIos(
|
|
|
1005
1145
|
}
|
|
1006
1146
|
: {}),
|
|
1007
1147
|
permissionUsageDescriptions,
|
|
1148
|
+
infoPlistStrings,
|
|
1008
1149
|
urlQuerySchemes,
|
|
1009
1150
|
...(shareIntoAppGroupId === undefined ? {} : { shareIntoAppGroupId }),
|
|
1010
1151
|
...(rtcAppGroupIdentifier === undefined ? {} : { rtcAppGroupIdentifier }),
|
|
1152
|
+
...(screenShareExtensionBundleId === undefined
|
|
1153
|
+
? {}
|
|
1154
|
+
: { screenShareExtensionBundleId }),
|
|
1011
1155
|
backgroundTaskIdentifiers: backgroundTaskIdentifiers.every((value) => value !== null)
|
|
1012
1156
|
? backgroundTaskIdentifiers
|
|
1013
1157
|
: [],
|
|
@@ -1020,11 +1164,6 @@ function nativeAppIdentityFromIos(
|
|
|
1020
1164
|
}
|
|
1021
1165
|
}
|
|
1022
1166
|
|
|
1023
|
-
// android's shipped identity requires resolving the selected flavor and
|
|
1024
|
-
// buildType overrides (`productFlavors.prod`, `buildTypes.release`), which a
|
|
1025
|
-
// text reader of `defaultConfig` cannot distinguish from the defaults it
|
|
1026
|
-
// overrides. an Android project therefore ships no footer until that
|
|
1027
|
-
// resolution exists. unsupported or absent bundle platforms ship no footer.
|
|
1028
1167
|
// the module an app's own sources start react native with: the template
|
|
1029
1168
|
// `startReactNative(withModuleName: "X")`, RCTAppDelegate's
|
|
1030
1169
|
// `self.moduleName = @"X"` and RCTRootView's `moduleName:@"X"`. a target that
|
|
@@ -1039,8 +1178,19 @@ export function readRootModuleName(sources: readonly string[]): string | undefin
|
|
|
1039
1178
|
return names.size === 1 ? [...names][0] : undefined
|
|
1040
1179
|
}
|
|
1041
1180
|
|
|
1042
|
-
export function canExtractNativeAppIdentity(
|
|
1043
|
-
|
|
1181
|
+
export function canExtractNativeAppIdentity(
|
|
1182
|
+
platform: unknown,
|
|
1183
|
+
): platform is NativeAppIdentityPlatform {
|
|
1184
|
+
return platform === 'ios' || platform === 'android'
|
|
1185
|
+
}
|
|
1186
|
+
|
|
1187
|
+
// the bundle platform's identity reader. selecting it here keeps every native
|
|
1188
|
+
// project reader free of platform branches.
|
|
1189
|
+
export function selectNativeAppIdentityReader<Reader>(
|
|
1190
|
+
platform: unknown,
|
|
1191
|
+
readers: Readonly<Record<NativeAppIdentityPlatform, Reader>>,
|
|
1192
|
+
): Reader | null {
|
|
1193
|
+
return canExtractNativeAppIdentity(platform) ? readers[platform] : null
|
|
1044
1194
|
}
|
|
1045
1195
|
|
|
1046
1196
|
// read the identity a bundle builder transports. files are project-relative
|
|
@@ -1053,8 +1203,23 @@ export function extractNativeAppIdentityFromFiles(
|
|
|
1053
1203
|
parseInfoPlist: (source: string) => unknown,
|
|
1054
1204
|
iosTarget?: string,
|
|
1055
1205
|
resourceUri?: (file: string) => string | undefined,
|
|
1206
|
+
androidMergedManifest?: string,
|
|
1056
1207
|
): NativeAppIdentity | null {
|
|
1057
|
-
|
|
1208
|
+
if (platform === 'android') {
|
|
1209
|
+
const candidates = androidMergedManifest
|
|
1210
|
+
? [androidMergedManifest]
|
|
1211
|
+
: Object.keys(files).filter((file) =>
|
|
1212
|
+
/^android\/[^/]+\/build\/intermediates\/merged_manifests\/.+\/AndroidManifest\.xml$/.test(
|
|
1213
|
+
file,
|
|
1214
|
+
),
|
|
1215
|
+
)
|
|
1216
|
+
if (candidates.length !== 1) return null
|
|
1217
|
+
const source = files[candidates[0]!]
|
|
1218
|
+
return source === undefined
|
|
1219
|
+
? null
|
|
1220
|
+
: nativeAppIdentityFromAndroidManifest(source, candidates[0]!, files)
|
|
1221
|
+
}
|
|
1222
|
+
return platform === 'ios'
|
|
1058
1223
|
? nativeAppIdentityFromIos(files, parseInfoPlist, iosTarget, resourceUri)
|
|
1059
1224
|
: null
|
|
1060
1225
|
}
|