peachsim 0.1.1625 → 0.1.1640
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 +15 -2
- package/cli/app-config.ts +18 -6
- package/cli/app-fonts.ts +37 -17
- package/cli/app-stubs.ts +284 -20
- package/cli/bridge-flow-runner.ts +66 -45
- package/cli/cloud-client.ts +60 -40
- package/cli/command-registry.ts +1 -0
- package/cli/commands/control.ts +283 -49
- package/cli/commands/doctor.ts +30 -189
- 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/shared.ts +9 -4
- package/cli/commands/inspect/wait-ready.ts +12 -0
- package/cli/commands/inspect.ts +91 -50
- 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/preview.ts +1 -1
- package/cli/commands/record.ts +118 -48
- 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 +14 -3
- package/cli/commands/upload.ts +93 -48
- package/cli/desktop-companion.ts +102 -17
- package/cli/drivers/desktop.ts +14 -1
- package/cli/drivers/playwright-sim-host.ts +103 -45
- 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 +3 -2
- package/cli/outbound-endpoints.ts +20 -6
- package/cli/pause-aware-timeout.ts +106 -0
- package/cli/scene-video.ts +89 -0
- package/cli/ws-bridge.ts +37 -19
- package/detox/attempt-events.cjs +66 -5
- package/detox/attempt-events.d.cts +7 -0
- package/detox/expectations.ts +7 -3
- package/detox/index.ts +269 -170
- package/detox/motion-change.cjs +206 -21
- package/detox/motion-change.d.cts +39 -2
- package/detox/navigation-lifecycle.ts +35 -3
- 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 +8 -7
- 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 +11887 -7900
- 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 +1379 -725
- package/dist-lib/dev-bundle-resolution.cjs +14 -2
- package/dist-lib/expo-swift-bundle.cjs +9341 -5462
- package/dist-lib/expo-swift-metadata.cjs +11 -7
- package/dist-lib/expo-swift-metadata.mjs +11 -7
- package/dist-lib/home-paths.cjs +32 -3
- package/dist-lib/host/bridge-host.cjs +50204 -7258
- 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 +3541 -448
- 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 +2931 -104
- package/dist-lib/native-app-identity-project.cjs +289 -49
- package/dist-lib/native-app-identity-project.mjs +289 -49
- package/dist-lib/native-app-identity.cjs +77 -26
- package/dist-lib/native-app-identity.mjs +76 -26
- 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 +6 -2
- package/dist-lib/react-native-host-modules.mjs +6 -2
- package/dist-lib/scripts/dev-server-scanner.cjs +22 -8
- package/dist-lib/sdk.cjs +27 -11
- package/dist-lib/sdk.mjs +27 -10
- package/dist-lib/skills.cjs +72201 -26208
- package/dist-lib/swift-compile.cjs +1007 -140
- package/dist-lib/swift-compile.mjs +1049 -174
- package/dist-lib/swift-package.cjs +12 -3
- package/dist-lib/swift-package.mjs +12 -3
- package/dist-lib/swift-submit.cjs +13 -5
- package/dist-lib/swift-submit.mjs +11 -5
- package/dist-lib/swift-toolchain.cjs +423 -33
- package/dist-lib/swift.cjs +1 -1
- package/dist-lib/vite-swift.cjs +8628 -5268
- package/dist-lib/vite.cjs +2859 -12
- package/dist-lib/worklet-executable.cjs +14 -2
- package/dist-lib/worklet-executable.mjs +14 -2
- 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/backend-origin.ts +13 -6
- 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 +196 -49
- package/src/expo-swift-ready.ts +78 -0
- package/src/expoKotlinContract.ts +4 -7
- package/src/expoSwiftContract.ts +17 -6
- package/src/home-paths.ts +48 -2
- package/src/host/bridge-host.ts +5 -2
- package/src/host/staged-host-files.ts +9 -0
- package/src/native-app-identity-project.ts +27 -6
- package/src/native-app-identity.ts +91 -4
- package/src/native-firebase-config.ts +209 -13
- package/src/nativeAppIconContract.ts +1 -4
- package/src/output-lock-stdio.ts +36 -0
- package/src/process-group.ts +25 -2
- package/src/react-native-bridge-modules.ts +431 -0
- package/src/react-native-host-modules.ts +8 -1
- package/src/settings-bundle.ts +1 -4
- package/src/swift-binary-modules.ts +2 -2
- package/src/swift-compile.ts +918 -109
- package/src/swift-compiler-digest.ts +14 -0
- package/src/swift-distribution.lock.json +24 -12
- package/src/swift-distribution.ts +103 -4
- package/src/swift-host.ts +180 -55
- package/src/swift-icu.ts +27 -13
- package/src/swift-objc-compile.ts +355 -0
- package/src/swift-objc-sdk.ts +821 -27
- package/src/swift-objc-standin.ts +148 -14
- package/src/swift-package-manifest.ts +60 -7
- package/src/swift-package.ts +15 -1
- package/src/swift-packages.ts +154 -81
- package/src/swift-standin-emit.ts +96 -15
- package/src/swift-standins.ts +530 -116
- package/src/swift-submit.ts +13 -5
- package/src/swift-toolchain.ts +500 -42
- package/src/vite-plugin.ts +12 -1
- package/src/worker-system-chunks.ts +11 -12
- package/src/worklet-executable.ts +19 -1
- package/src/xcode-project.ts +270 -30
package/cli/commands/record.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
// peach record — drives the engine's built-in canvas recorder over
|
|
2
|
-
// the WS bridge.
|
|
2
|
+
// the WS bridge. scene export opens an isolated viewer; encoding happens in the
|
|
3
3
|
// running peach page (webm via MediaRecorder, mp4 via WebCodecs, gif
|
|
4
4
|
// via gifenc, raw png frames via a shared bitmap sampler).
|
|
5
5
|
|
|
@@ -76,18 +76,41 @@ const STORE_RECORD_START_TIMEOUT_MS = 90_000
|
|
|
76
76
|
// (bitmap-capture.ts), so recovery is simply to retry.
|
|
77
77
|
const CAPTURE_STEP_ENCODE_BUDGET_MS = 30_000
|
|
78
78
|
|
|
79
|
+
// waits out a recording's duration as an in-flight bridge command. the host
|
|
80
|
+
// closes a cli socket that sends nothing for 60s, but never one with a command
|
|
81
|
+
// awaiting its sim, so a client-side sleep through a long capture dropped the
|
|
82
|
+
// connection before the encode/flush step could use it.
|
|
83
|
+
async function holdCapture(bridge: WsBridge, durationMs: number) {
|
|
84
|
+
await bridge.send(
|
|
85
|
+
{ type: 'evaluate', code: `new Promise((r) => setTimeout(r, ${durationMs}))` },
|
|
86
|
+
{ timeoutMs: durationMs + CAPTURE_STEP_ENCODE_BUDGET_MS },
|
|
87
|
+
)
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
// an explicit --timeout is the step's budget. a step that records for the
|
|
91
|
+
// whole duration (a gif) gets that duration on top of it.
|
|
92
|
+
function captureStepTimeoutMs(
|
|
93
|
+
parsed: ParsedBridgeCliArgs,
|
|
94
|
+
durationMs: number,
|
|
95
|
+
spansCapture: boolean,
|
|
96
|
+
): number {
|
|
97
|
+
if (!parsed.explicitTimeout)
|
|
98
|
+
return Math.max(20_000, durationMs + CAPTURE_STEP_ENCODE_BUDGET_MS)
|
|
99
|
+
return spansCapture ? durationMs + parsed.commandTimeoutMs : parsed.commandTimeoutMs
|
|
100
|
+
}
|
|
101
|
+
|
|
79
102
|
async function evalCaptureStep<T>(
|
|
80
103
|
bridge: WsBridge,
|
|
81
104
|
code: string,
|
|
82
105
|
step: string,
|
|
83
106
|
durationMs: number,
|
|
107
|
+
timeoutMs: number,
|
|
84
108
|
): Promise<T> {
|
|
85
|
-
const timeoutMs = Math.max(20_000, durationMs + CAPTURE_STEP_ENCODE_BUDGET_MS)
|
|
86
109
|
try {
|
|
87
110
|
return (await bridge.send({ type: 'evaluate', code }, { timeoutMs })) as T
|
|
88
111
|
} catch (err) {
|
|
89
112
|
const msg = err instanceof Error ? err.message : String(err)
|
|
90
|
-
if (/^command timed out after \d+s$/.test(msg)) {
|
|
113
|
+
if (/^command timed out after \d+(?:\.\d+)?s$/.test(msg)) {
|
|
91
114
|
console.error(
|
|
92
115
|
` ${step} stalled — the render host did not finish within ` +
|
|
93
116
|
`${Math.round(timeoutMs / 1000)}s; it is likely contended or wedged.\n` +
|
|
@@ -143,6 +166,8 @@ const RECORD_VALUE_FLAGS = new Set([
|
|
|
143
166
|
'--origin',
|
|
144
167
|
'--owner',
|
|
145
168
|
'--repo',
|
|
169
|
+
'--scene',
|
|
170
|
+
'--shell-url',
|
|
146
171
|
])
|
|
147
172
|
const RECORD_SUBCOMMANDS = new Set([
|
|
148
173
|
'start',
|
|
@@ -151,6 +176,7 @@ const RECORD_SUBCOMMANDS = new Set([
|
|
|
151
176
|
'status',
|
|
152
177
|
'prelude-start',
|
|
153
178
|
'upload',
|
|
179
|
+
'render',
|
|
154
180
|
])
|
|
155
181
|
|
|
156
182
|
// the record subcommand (start/stop/cancel/status) is the first non-flag
|
|
@@ -195,6 +221,23 @@ export async function runRecord(args: string[], opts: RecordOptions) {
|
|
|
195
221
|
// drop the subcommand token; the rest (incl. any --sim/--port) flows on
|
|
196
222
|
// to the subcommand handlers, which parse bridge flags themselves.
|
|
197
223
|
const rest = [...args.slice(0, sub.index), ...args.slice(sub.index + 1)]
|
|
224
|
+
if (sub.name === 'render') {
|
|
225
|
+
const scene = valueOf(rest, '--scene')
|
|
226
|
+
const shellUrl = valueOf(rest, '--shell-url')
|
|
227
|
+
const output = valueOf(rest, '--output')
|
|
228
|
+
if (!scene || !shellUrl || !output)
|
|
229
|
+
throw new Error('record render requires --scene, --shell-url and --output')
|
|
230
|
+
const { renderSceneToVideo } = await import('../scene-video')
|
|
231
|
+
const result = await renderSceneToVideo({
|
|
232
|
+
scene: readFileSync(scene),
|
|
233
|
+
shellUrl,
|
|
234
|
+
output,
|
|
235
|
+
})
|
|
236
|
+
console.log(
|
|
237
|
+
` video: ${resolve(output)} (${result.frameCount} frames at 60 fps, ${result.sizeBytes} bytes, rendered in ${(result.renderTimeMs / 1000).toFixed(1)}s)`,
|
|
238
|
+
)
|
|
239
|
+
return
|
|
240
|
+
}
|
|
198
241
|
if (sub.name === 'start') {
|
|
199
242
|
await recordStart(rest, opts)
|
|
200
243
|
return
|
|
@@ -222,6 +265,8 @@ export async function runRecord(args: string[], opts: RecordOptions) {
|
|
|
222
265
|
|
|
223
266
|
const parsed = parseBridgeCliArgs(args, {
|
|
224
267
|
port: opts.port,
|
|
268
|
+
// an explicit --timeout wins over the default
|
|
269
|
+
commandTimeoutMs: 60_000,
|
|
225
270
|
stripBooleanFlags: ['--no-shell', '--shell-only', '--open', '--lockstep'],
|
|
226
271
|
stripValueFlags: [
|
|
227
272
|
'--mode',
|
|
@@ -336,7 +381,7 @@ export async function runRecord(args: string[], opts: RecordOptions) {
|
|
|
336
381
|
originOverride,
|
|
337
382
|
allowGitHubAuth: true,
|
|
338
383
|
})
|
|
339
|
-
const bridge = createBridgeFromParsed(
|
|
384
|
+
const bridge = createBridgeFromParsed(parsed)
|
|
340
385
|
try {
|
|
341
386
|
await assertStoreBridgesAvailable(bridge)
|
|
342
387
|
await assertGuestBundleLoaded(bridge, mode)
|
|
@@ -356,7 +401,7 @@ export async function runRecord(args: string[], opts: RecordOptions) {
|
|
|
356
401
|
peachExit(1)
|
|
357
402
|
}
|
|
358
403
|
console.log(` recording ${mode} for ${durationSec}s`)
|
|
359
|
-
await
|
|
404
|
+
await holdCapture(bridge, durationMs)
|
|
360
405
|
await evalStoreStop(bridge)
|
|
361
406
|
const result = await pollStoreUpload(bridge)
|
|
362
407
|
handleStoreUploadResult(result, openAfter, mode)
|
|
@@ -378,7 +423,7 @@ export async function runRecord(args: string[], opts: RecordOptions) {
|
|
|
378
423
|
)
|
|
379
424
|
}
|
|
380
425
|
|
|
381
|
-
const bridge = createBridgeFromParsed(
|
|
426
|
+
const bridge = createBridgeFromParsed(parsed)
|
|
382
427
|
try {
|
|
383
428
|
await assertRecorderAvailable(bridge)
|
|
384
429
|
await assertTabVisibleForCapture(bridge)
|
|
@@ -414,7 +459,7 @@ export async function runRecord(args: string[], opts: RecordOptions) {
|
|
|
414
459
|
console.error(` frame capture start failed: ${start.error ?? 'unknown error'}`)
|
|
415
460
|
peachExit(1)
|
|
416
461
|
}
|
|
417
|
-
await
|
|
462
|
+
await holdCapture(bridge, durationMs)
|
|
418
463
|
const deadline = Date.now() + Math.max(5_000, durationMs)
|
|
419
464
|
// frames come back a few at a time: one message holding every base64 png
|
|
420
465
|
// exceeds the bridge socket's message cap and closes it.
|
|
@@ -470,6 +515,7 @@ export async function runRecord(args: string[], opts: RecordOptions) {
|
|
|
470
515
|
}${layers ? `, layers: ${JSON.stringify(layers)}` : ''} })`,
|
|
471
516
|
'gif encode',
|
|
472
517
|
durationMs,
|
|
518
|
+
captureStepTimeoutMs(parsed, durationMs, true),
|
|
473
519
|
)
|
|
474
520
|
if (!result) {
|
|
475
521
|
console.error(' gif capture returned no frames')
|
|
@@ -503,6 +549,7 @@ export async function runRecord(args: string[], opts: RecordOptions) {
|
|
|
503
549
|
`window.__sootsimRecorder.start(${JSON.stringify(startOpts)})`,
|
|
504
550
|
`${format} start`,
|
|
505
551
|
durationMs,
|
|
552
|
+
captureStepTimeoutMs(parsed, durationMs, false),
|
|
506
553
|
)
|
|
507
554
|
if (!startResult.ok) {
|
|
508
555
|
console.error(` start failed: ${startResult.error ?? 'unknown error'}`)
|
|
@@ -523,7 +570,7 @@ export async function runRecord(args: string[], opts: RecordOptions) {
|
|
|
523
570
|
}
|
|
524
571
|
|
|
525
572
|
console.log(` recording ${format} for ${durationSec}s → ${out}`)
|
|
526
|
-
await
|
|
573
|
+
await holdCapture(bridge, durationMs)
|
|
527
574
|
|
|
528
575
|
const stopResult: {
|
|
529
576
|
ok: boolean
|
|
@@ -537,6 +584,7 @@ export async function runRecord(args: string[], opts: RecordOptions) {
|
|
|
537
584
|
`window.__sootsimRecorder.stop()`,
|
|
538
585
|
`${format} encode/flush`,
|
|
539
586
|
durationMs,
|
|
587
|
+
captureStepTimeoutMs(parsed, durationMs, false),
|
|
540
588
|
)
|
|
541
589
|
if (!stopResult.ok) {
|
|
542
590
|
console.error(` stop failed: ${stopResult.error ?? 'unknown error'}`)
|
|
@@ -570,11 +618,11 @@ async function probeSim(bridge: WsBridge, code: string): Promise<boolean> {
|
|
|
570
618
|
try {
|
|
571
619
|
return await bridge.send(
|
|
572
620
|
{ type: 'evaluate', code },
|
|
573
|
-
{
|
|
621
|
+
{ defaultTimeoutMs: SIM_PROBE_TIMEOUT_MS },
|
|
574
622
|
)
|
|
575
623
|
} catch (err) {
|
|
576
624
|
const msg = err instanceof Error ? err.message : String(err)
|
|
577
|
-
if (/^command timed out after \d+s$/.test(msg)) {
|
|
625
|
+
if (/^command timed out after \d+(?:\.\d+)?s$/.test(msg)) {
|
|
578
626
|
console.error(
|
|
579
627
|
' sim did not respond — it is connected to the bridge but has not\n' +
|
|
580
628
|
' loaded an app (or its page is unresponsive). run `peach list`\n' +
|
|
@@ -766,7 +814,7 @@ async function refuseLiveRecording(parsed: ParsedBridgeCliArgs) {
|
|
|
766
814
|
...parsed,
|
|
767
815
|
simId: existing.simId ?? parsed.simId,
|
|
768
816
|
simIdSource: existing.simId ? 'saved' : parsed.simIdSource,
|
|
769
|
-
commandTimeoutMs: 8_000,
|
|
817
|
+
commandTimeoutMs: parsed.explicitTimeout ? parsed.commandTimeoutMs : 8_000,
|
|
770
818
|
})
|
|
771
819
|
try {
|
|
772
820
|
const sims = await bridge.listSims()
|
|
@@ -788,8 +836,12 @@ async function refuseLiveRecording(parsed: ParsedBridgeCliArgs) {
|
|
|
788
836
|
}
|
|
789
837
|
|
|
790
838
|
async function recordStart(args: string[], opts: RecordOptions) {
|
|
839
|
+
const mode = parseMode(valueOf(args, '--mode'))
|
|
791
840
|
const parsed = parseBridgeCliArgs(args, {
|
|
792
841
|
port: opts.port,
|
|
842
|
+
// an explicit --timeout wins over the per-mode default
|
|
843
|
+
commandTimeoutMs:
|
|
844
|
+
mode === 'live' || mode === 'combined' ? STORE_RECORD_START_TIMEOUT_MS : 15_000,
|
|
793
845
|
stripBooleanFlags: ['--no-shell', '--shell-only', '--lockstep'],
|
|
794
846
|
stripValueFlags: [
|
|
795
847
|
'--mode',
|
|
@@ -803,7 +855,6 @@ async function recordStart(args: string[], opts: RecordOptions) {
|
|
|
803
855
|
],
|
|
804
856
|
})
|
|
805
857
|
await refuseLiveRecording(parsed)
|
|
806
|
-
const mode = parseMode(valueOf(args, '--mode'))
|
|
807
858
|
const layers: 'tenant' | 'shell' | undefined = args.includes('--shell-only')
|
|
808
859
|
? 'shell'
|
|
809
860
|
: args.includes('--no-shell')
|
|
@@ -820,11 +871,19 @@ async function recordStart(args: string[], opts: RecordOptions) {
|
|
|
820
871
|
const fps = Number(valueOf(args, '--fps') ?? '30')
|
|
821
872
|
const repoScope = readRepoScope(args)
|
|
822
873
|
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
874
|
+
// the session is recorded before the engine is asked to start, so a start
|
|
875
|
+
// that times out on this side while the engine records stays stoppable. a
|
|
876
|
+
// refusal clears it.
|
|
877
|
+
const session: RecordingState = {
|
|
878
|
+
simId: parsed.simId ?? null,
|
|
879
|
+
mode,
|
|
880
|
+
format,
|
|
881
|
+
fps,
|
|
882
|
+
layers,
|
|
883
|
+
startedAt: new Date().toISOString(),
|
|
884
|
+
}
|
|
885
|
+
|
|
886
|
+
const bridge = createBridgeFromParsed(parsed)
|
|
828
887
|
try {
|
|
829
888
|
if (mode === 'live' || mode === 'combined') {
|
|
830
889
|
// live mode captures events only — --no-shell / --shell-only have no
|
|
@@ -849,6 +908,7 @@ async function recordStart(args: string[], opts: RecordOptions) {
|
|
|
849
908
|
await assertGuestBundleLoaded(bridge, mode)
|
|
850
909
|
if (mode === 'combined') await assertTabVisibleForCapture(bridge)
|
|
851
910
|
await injectPreviewSessionAuth(bridge, valueOf(args, '--origin'), repoScope)
|
|
911
|
+
writeRecordingState(session)
|
|
852
912
|
const started = await evalStoreStart(
|
|
853
913
|
bridge,
|
|
854
914
|
mode,
|
|
@@ -856,6 +916,7 @@ async function recordStart(args: string[], opts: RecordOptions) {
|
|
|
856
916
|
mode === 'combined' ? layers : undefined,
|
|
857
917
|
)
|
|
858
918
|
if (!started.ok) {
|
|
919
|
+
clearRecordingState()
|
|
859
920
|
console.error(
|
|
860
921
|
` start failed: recording store refused to start (${mode})` +
|
|
861
922
|
(started.error ? `: ${started.error}` : ''),
|
|
@@ -878,23 +939,17 @@ async function recordStart(args: string[], opts: RecordOptions) {
|
|
|
878
939
|
} = { format, fps }
|
|
879
940
|
if (layers) startOpts.layers = layers
|
|
880
941
|
if (lockstep) startOpts.lockstep = true
|
|
942
|
+
writeRecordingState(session)
|
|
881
943
|
const result: { ok: boolean; error?: string } = await bridge.send({
|
|
882
944
|
type: 'evaluate',
|
|
883
945
|
code: `window.__sootsimRecorder.start(${JSON.stringify(startOpts)})`,
|
|
884
946
|
})
|
|
885
947
|
if (!result.ok) {
|
|
948
|
+
clearRecordingState()
|
|
886
949
|
console.error(` start failed: ${result.error ?? 'unknown error'}`)
|
|
887
950
|
peachExit(1)
|
|
888
951
|
}
|
|
889
952
|
}
|
|
890
|
-
writeRecordingState({
|
|
891
|
-
simId: parsed.simId ?? null,
|
|
892
|
-
mode,
|
|
893
|
-
format,
|
|
894
|
-
fps,
|
|
895
|
-
layers,
|
|
896
|
-
startedAt: new Date().toISOString(),
|
|
897
|
-
})
|
|
898
953
|
if (mode === 'video') {
|
|
899
954
|
console.log(
|
|
900
955
|
` recording ${format} @ ${fps}fps${layers ? ` (${layers})` : ''} — run \`peach record stop --output <path>\` when done`,
|
|
@@ -916,7 +971,7 @@ async function recordPreludeStart(args: string[], opts: RecordOptions) {
|
|
|
916
971
|
stripValueFlags: ['--origin'],
|
|
917
972
|
})
|
|
918
973
|
await refuseLiveRecording(parsed)
|
|
919
|
-
const bridge = createBridgeFromParsed(
|
|
974
|
+
const bridge = createBridgeFromParsed(parsed)
|
|
920
975
|
try {
|
|
921
976
|
await assertStoreBridgesAvailable(bridge)
|
|
922
977
|
await assertTabVisibleForCapture(bridge)
|
|
@@ -1049,8 +1104,8 @@ async function recordStatus(args: string[], opts: RecordOptions) {
|
|
|
1049
1104
|
}
|
|
1050
1105
|
// no `record start` session — but an atomic run or the rail button may
|
|
1051
1106
|
// still be recording. ask the engine before claiming nothing is happening.
|
|
1052
|
-
const parsed = parseBridgeCliArgs(args, { port: opts.port })
|
|
1053
|
-
const bridge = createBridgeFromParsed(
|
|
1107
|
+
const parsed = parseBridgeCliArgs(args, { port: opts.port, commandTimeoutMs: 8_000 })
|
|
1108
|
+
const bridge = createBridgeFromParsed(parsed)
|
|
1054
1109
|
try {
|
|
1055
1110
|
const engineState = await probeEngineRecordingState(bridge)
|
|
1056
1111
|
if (
|
|
@@ -1091,11 +1146,11 @@ async function recordCancel(args: string[], opts: RecordOptions) {
|
|
|
1091
1146
|
if (!state) {
|
|
1092
1147
|
// no `record start` session to cancel — but check the engine so we
|
|
1093
1148
|
// don't falsely claim nothing is happening while an atomic run records.
|
|
1094
|
-
const probeParsed = parseBridgeCliArgs(args, {
|
|
1095
|
-
|
|
1096
|
-
...probeParsed,
|
|
1149
|
+
const probeParsed = parseBridgeCliArgs(args, {
|
|
1150
|
+
port: opts.port,
|
|
1097
1151
|
commandTimeoutMs: 8_000,
|
|
1098
1152
|
})
|
|
1153
|
+
const probeBridge = createBridgeFromParsed(probeParsed)
|
|
1099
1154
|
try {
|
|
1100
1155
|
const engineState = await probeEngineRecordingState(probeBridge)
|
|
1101
1156
|
if (
|
|
@@ -1132,18 +1187,14 @@ async function recordCancel(args: string[], opts: RecordOptions) {
|
|
|
1132
1187
|
}
|
|
1133
1188
|
return
|
|
1134
1189
|
}
|
|
1135
|
-
const parsed = parseBridgeCliArgs(args, { port: opts.port })
|
|
1190
|
+
const parsed = parseBridgeCliArgs(args, { port: opts.port, commandTimeoutMs: 15_000 })
|
|
1136
1191
|
noteSimMismatch(parsed.simId, state)
|
|
1137
1192
|
// the recorder (and its buffered blob) lives in the page of the sim the
|
|
1138
1193
|
// recording was *started* on — `state.simId` wins over the requested
|
|
1139
1194
|
// --sim, otherwise cancel would stop an idle recorder on the wrong sim
|
|
1140
1195
|
// and leave the real recording running (F11-1).
|
|
1141
1196
|
const simId = state.simId ?? parsed.simId ?? undefined
|
|
1142
|
-
const bridge = createBridgeFromParsed({
|
|
1143
|
-
...parsed,
|
|
1144
|
-
simId,
|
|
1145
|
-
commandTimeoutMs: 15_000,
|
|
1146
|
-
})
|
|
1197
|
+
const bridge = createBridgeFromParsed({ ...parsed, simId })
|
|
1147
1198
|
try {
|
|
1148
1199
|
if (state.mode === 'live' || state.mode === 'combined') {
|
|
1149
1200
|
await bridge.send({
|
|
@@ -1174,6 +1225,9 @@ async function recordStop(args: string[], opts: RecordOptions) {
|
|
|
1174
1225
|
|
|
1175
1226
|
const parsed = parseBridgeCliArgs(args, {
|
|
1176
1227
|
port: opts.port,
|
|
1228
|
+
// an explicit --timeout wins over the per-mode default
|
|
1229
|
+
commandTimeoutMs:
|
|
1230
|
+
state.mode === 'live' || state.mode === 'combined' ? UPLOAD_TIMEOUT_MS : 60_000,
|
|
1177
1231
|
stripBooleanFlags: ['--open'],
|
|
1178
1232
|
stripValueFlags: ['--output'],
|
|
1179
1233
|
})
|
|
@@ -1184,15 +1238,17 @@ async function recordStop(args: string[], opts: RecordOptions) {
|
|
|
1184
1238
|
const simId = state.simId ?? parsed.simId ?? undefined
|
|
1185
1239
|
const openAfter = args.includes('--open')
|
|
1186
1240
|
|
|
1187
|
-
const bridge = createBridgeFromParsed({
|
|
1188
|
-
...parsed,
|
|
1189
|
-
simId,
|
|
1190
|
-
commandTimeoutMs:
|
|
1191
|
-
state.mode === 'live' || state.mode === 'combined' ? UPLOAD_TIMEOUT_MS : 60_000,
|
|
1192
|
-
})
|
|
1241
|
+
const bridge = createBridgeFromParsed({ ...parsed, simId })
|
|
1193
1242
|
try {
|
|
1194
1243
|
if (state.mode === 'live' || state.mode === 'combined') {
|
|
1195
|
-
await evalStoreStop(bridge)
|
|
1244
|
+
if (!(await evalStoreStop(bridge))) {
|
|
1245
|
+
clearRecordingState()
|
|
1246
|
+
console.error(
|
|
1247
|
+
` stop failed: the ${state.mode} start still in flight never began a take, so there is nothing to upload.\n` +
|
|
1248
|
+
' run `peach record start` again.',
|
|
1249
|
+
)
|
|
1250
|
+
peachExit(1)
|
|
1251
|
+
}
|
|
1196
1252
|
const result = await pollStoreUpload(bridge)
|
|
1197
1253
|
clearRecordingState()
|
|
1198
1254
|
handleStoreUploadResult(result, openAfter, state.mode)
|
|
@@ -1215,7 +1271,17 @@ async function recordStop(args: string[], opts: RecordOptions) {
|
|
|
1215
1271
|
})
|
|
1216
1272
|
if (!stopResult.ok) {
|
|
1217
1273
|
console.error(` stop failed: ${stopResult.error ?? 'unknown error'}`)
|
|
1218
|
-
|
|
1274
|
+
// a start that timed out on the client can still be on its way to the
|
|
1275
|
+
// engine. keep the state so a later stop finds the recording instead of
|
|
1276
|
+
// leaving it running with nothing to stop it.
|
|
1277
|
+
if (stopResult.error === 'not recording') {
|
|
1278
|
+
console.error(
|
|
1279
|
+
' the start may not have reached the engine yet. retry `peach record stop`,\n' +
|
|
1280
|
+
' or run `peach record cancel` to give up on it.',
|
|
1281
|
+
)
|
|
1282
|
+
} else {
|
|
1283
|
+
clearRecordingState()
|
|
1284
|
+
}
|
|
1219
1285
|
peachExit(1)
|
|
1220
1286
|
}
|
|
1221
1287
|
if (!stopResult.size) {
|
|
@@ -1376,11 +1442,15 @@ async function evalStoreStart(
|
|
|
1376
1442
|
return { ok: false, error: error ?? null }
|
|
1377
1443
|
}
|
|
1378
1444
|
|
|
1379
|
-
|
|
1380
|
-
|
|
1445
|
+
// the engine stop waits for a start still in flight. false means that start
|
|
1446
|
+
// never began a take, so no upload will come and the store's last one belongs
|
|
1447
|
+
// to an earlier take.
|
|
1448
|
+
async function evalStoreStop(bridge: WsBridge): Promise<boolean> {
|
|
1449
|
+
const stopped: boolean | null = await bridge.send({
|
|
1381
1450
|
type: 'evaluate',
|
|
1382
|
-
code: `
|
|
1451
|
+
code: `window.SootSim.bridges.stopRecording()`,
|
|
1383
1452
|
})
|
|
1453
|
+
return stopped !== false
|
|
1384
1454
|
}
|
|
1385
1455
|
|
|
1386
1456
|
interface StoreUploadResult {
|
|
@@ -140,7 +140,15 @@ examples:
|
|
|
140
140
|
// --id / --text, missing device model). surface them as a one-line
|
|
141
141
|
// failure instead of an uncaught internal stack trace.
|
|
142
142
|
try {
|
|
143
|
-
await captureViaBridge(
|
|
143
|
+
await captureViaBridge(
|
|
144
|
+
parsed,
|
|
145
|
+
outputArg,
|
|
146
|
+
rect,
|
|
147
|
+
withFrame,
|
|
148
|
+
layers,
|
|
149
|
+
allowLoading,
|
|
150
|
+
opts.verbose === true || args.includes('--verbose') || args.includes('-v'),
|
|
151
|
+
)
|
|
144
152
|
} catch (err) {
|
|
145
153
|
console.error(
|
|
146
154
|
` screenshot failed: ${err instanceof Error ? err.message : String(err)}`,
|
|
@@ -206,7 +214,16 @@ async function captureViaBridge(
|
|
|
206
214
|
withFrame: boolean,
|
|
207
215
|
layers: 'full' | 'tenant' | 'shell' | undefined,
|
|
208
216
|
allowLoading: boolean,
|
|
217
|
+
verbose: boolean,
|
|
209
218
|
) {
|
|
219
|
+
// --verbose names the phase a slow capture spent its time in.
|
|
220
|
+
const phases: string[] = []
|
|
221
|
+
let phaseStart = Date.now()
|
|
222
|
+
const phase = (name: string) => {
|
|
223
|
+
const now = Date.now()
|
|
224
|
+
phases.push(`${name} ${now - phaseStart}ms`)
|
|
225
|
+
phaseStart = now
|
|
226
|
+
}
|
|
210
227
|
// 45s, not 10s: a first-paint capture on a headless CI runner with software
|
|
211
228
|
// GL (SwiftShader) is bounded but slow. forceRenderAll caps at ~6s (two
|
|
212
229
|
// 3s-bounded passes) and the WebGL→2D drawImage readback + PNG encode of a
|
|
@@ -225,6 +242,7 @@ async function captureViaBridge(
|
|
|
225
242
|
// affects screenshot fidelity directly, so call it out explicitly so the
|
|
226
243
|
// user doesn't accept a stale frame as ground truth.
|
|
227
244
|
const health = await checkSimHealth(bridge)
|
|
245
|
+
phase('health')
|
|
228
246
|
if (health.hidden) {
|
|
229
247
|
process.stderr.write(
|
|
230
248
|
' ⚠ screenshot will capture the LAST PAINTED canvas — likely stale.\n' +
|
|
@@ -267,6 +285,7 @@ async function captureViaBridge(
|
|
|
267
285
|
}
|
|
268
286
|
}
|
|
269
287
|
|
|
288
|
+
phase('ready')
|
|
270
289
|
let resolvedRect: SampleRect | null = rect
|
|
271
290
|
const matcher = (rect as { __matcher?: { id?: string; text?: string } })?.__matcher
|
|
272
291
|
if (matcher) {
|
|
@@ -297,6 +316,7 @@ async function captureViaBridge(
|
|
|
297
316
|
if (layers) request.layers = layers
|
|
298
317
|
if (cropRect) request.crop = cropRect
|
|
299
318
|
const dataUrl: string = await bridge.send(request)
|
|
319
|
+
phase('capture')
|
|
300
320
|
const pngPrefix = 'data:image/png;base64,'
|
|
301
321
|
// never write a 0-byte "saved" file from an empty or malformed payload —
|
|
302
322
|
// the sim may be alive but not painting (check `peach get errors`)
|
|
@@ -326,6 +346,8 @@ async function captureViaBridge(
|
|
|
326
346
|
} else {
|
|
327
347
|
await writeScreenshotOutput(outputPath, rawBuffer)
|
|
328
348
|
}
|
|
349
|
+
phase('write')
|
|
350
|
+
if (verbose) console.log(` timing: ${phases.join(', ')}`)
|
|
329
351
|
console.log(` saved: ${outputPath}`)
|
|
330
352
|
} finally {
|
|
331
353
|
bridge.close()
|
package/cli/commands/serve.ts
CHANGED
|
@@ -8,7 +8,10 @@
|
|
|
8
8
|
// run in the foreground (ctrl-c to stop). the persistent launchd/systemd
|
|
9
9
|
// daemon installation is handled by `peach daemon install`.
|
|
10
10
|
|
|
11
|
-
import {
|
|
11
|
+
import {
|
|
12
|
+
assertPeachPortEnvironment,
|
|
13
|
+
DEFAULT_SOOTSIM_BRIDGE_PORT,
|
|
14
|
+
} from '../../src/bridge-constants'
|
|
12
15
|
import {
|
|
13
16
|
ensureRnxHome,
|
|
14
17
|
isDaemonLockfileFresh,
|
|
@@ -58,6 +61,7 @@ examples:
|
|
|
58
61
|
peachExit(0)
|
|
59
62
|
}
|
|
60
63
|
|
|
64
|
+
assertPeachPortEnvironment(process.env)
|
|
61
65
|
const portArgIdx = args.indexOf('--port')
|
|
62
66
|
const port =
|
|
63
67
|
portArgIdx >= 0 && args[portArgIdx + 1]
|
|
@@ -147,7 +151,8 @@ examples:
|
|
|
147
151
|
if (sims.length > 0) lastUsedAt = Date.now()
|
|
148
152
|
else if (
|
|
149
153
|
starterGoneAt &&
|
|
150
|
-
Date.now() - Math.max(starterGoneAt, lastUsedAt, host.lastRequestAt) >=
|
|
154
|
+
Date.now() - Math.max(starterGoneAt, lastUsedAt, host.lastRequestAt) >=
|
|
155
|
+
UNUSED_EXIT_MS
|
|
151
156
|
) {
|
|
152
157
|
void shutdown('unused')
|
|
153
158
|
return
|
package/cli/commands/setup.ts
CHANGED
|
@@ -17,7 +17,7 @@ import {
|
|
|
17
17
|
writePrivacyPreferences,
|
|
18
18
|
} from '../../src/home-paths'
|
|
19
19
|
import { rnxRuntime } from '../../src/runtime-delivery'
|
|
20
|
-
import { findDesktopCompanion } from '../desktop-companion'
|
|
20
|
+
import { findDesktopCompanion, nativeDesktopIsDefault } from '../desktop-companion'
|
|
21
21
|
import { isCi, PRIVACY_NOTICE } from '../privacy'
|
|
22
22
|
import { confirm, select } from '../prompt'
|
|
23
23
|
import {
|
|
@@ -155,12 +155,7 @@ export async function runSetup(
|
|
|
155
155
|
serviceInstalled = true
|
|
156
156
|
}
|
|
157
157
|
|
|
158
|
-
if (
|
|
159
|
-
simulatorDriver !== 'playwright' &&
|
|
160
|
-
process.platform !== 'darwin' &&
|
|
161
|
-
!options.dryRun &&
|
|
162
|
-
(simulatorDriver === 'desktop' || !findDesktopCompanion({ native: false }))
|
|
163
|
-
) {
|
|
158
|
+
if (simulatorDriver !== 'playwright' && !options.dryRun && !findDesktopCompanion()) {
|
|
164
159
|
await runInstallDesktop(
|
|
165
160
|
simulatorDriver === 'desktop' ? ['--yes'] : ['--yes', '--electron'],
|
|
166
161
|
)
|
|
@@ -272,7 +267,7 @@ function readSimulatorDriverFlag(args: string[]): SimulatorDriverPreference | un
|
|
|
272
267
|
if (desktop && browser) {
|
|
273
268
|
throw new Error('--desktop and --browser cannot be used together')
|
|
274
269
|
}
|
|
275
|
-
if (desktop) return desktopHostPlatform()
|
|
270
|
+
if (desktop) return desktopHostPlatform() ? 'desktop' : 'electron'
|
|
276
271
|
if (browser) return 'playwright'
|
|
277
272
|
return undefined
|
|
278
273
|
}
|
|
@@ -331,46 +326,29 @@ async function decideService(
|
|
|
331
326
|
return confirm('Enable the background service?', true)
|
|
332
327
|
}
|
|
333
328
|
|
|
329
|
+
// the native app is the simulator window wherever it ships, and setup installs
|
|
330
|
+
// it without asking. Windows chooses between the browser and its Electron app.
|
|
334
331
|
async function decideSimulatorDriver(
|
|
335
332
|
options: SetupOptions,
|
|
336
333
|
): Promise<SimulatorDriverPreference> {
|
|
337
|
-
if (process.platform === 'darwin' && !findDesktopCompanion()) {
|
|
338
|
-
if (options.simulatorDriver && options.simulatorDriver !== 'playwright') {
|
|
339
|
-
throw new Error('install the Mac app with `peach desktop install --native` first')
|
|
340
|
-
}
|
|
341
|
-
return 'playwright'
|
|
342
|
-
}
|
|
343
334
|
if (options.simulatorDriver) return options.simulatorDriver
|
|
344
335
|
const current = readSimulatorDriverPreference()
|
|
345
336
|
if (current) return current
|
|
337
|
+
if (isCi()) return 'playwright'
|
|
338
|
+
if (nativeDesktopIsDefault()) return 'desktop'
|
|
339
|
+
if (process.platform !== 'win32') return 'playwright'
|
|
346
340
|
if (options.ci || options.yes || options.dryRun) return 'playwright'
|
|
347
341
|
requireInteractive(options, '--desktop or --browser')
|
|
348
|
-
const desktopDescription =
|
|
349
|
-
process.platform === 'darwin'
|
|
350
|
-
? 'Desktop opens your installed native runtime (beta).'
|
|
351
|
-
: process.platform === 'win32'
|
|
352
|
-
? 'Desktop downloads and runs the peach installer.'
|
|
353
|
-
: 'Desktop installs the native .deb with sudo apt-get and needs a desktop session.'
|
|
354
|
-
const desktopChoice =
|
|
355
|
-
process.platform === 'darwin'
|
|
356
|
-
? 'Native app (beta) Faster, may have more issues'
|
|
357
|
-
: process.platform === 'win32'
|
|
358
|
-
? 'Desktop app Runs the peach installer'
|
|
359
|
-
: 'Native app (beta) Faster, may have more issues'
|
|
360
342
|
printPromptBlock('Simulator window', [
|
|
361
343
|
'Browser opens immediately in an isolated Chrome window.',
|
|
362
|
-
|
|
363
|
-
'The Desktop app opens in a dedicated
|
|
344
|
+
'Desktop downloads and runs the peach installer.',
|
|
345
|
+
'The Desktop app opens in a dedicated window.',
|
|
364
346
|
])
|
|
365
347
|
const choice = await select('Where should peach open?', [
|
|
366
348
|
'Browser (default) No additional download',
|
|
367
|
-
|
|
349
|
+
'Desktop app Runs the peach installer',
|
|
368
350
|
])
|
|
369
|
-
return choice === 0
|
|
370
|
-
? 'playwright'
|
|
371
|
-
: process.platform === 'darwin'
|
|
372
|
-
? 'desktop'
|
|
373
|
-
: 'electron'
|
|
351
|
+
return choice === 0 ? 'playwright' : 'electron'
|
|
374
352
|
}
|
|
375
353
|
|
|
376
354
|
// diagnostics and crash reports are on unless turned off; setup says so once
|
|
@@ -433,15 +411,14 @@ function printIntro(options: SetupOptions): void {
|
|
|
433
411
|
if (options.json) return
|
|
434
412
|
printBrand('Run any React Native app in a local simulator. No project setup required.')
|
|
435
413
|
if (desktopHostPlatform()) {
|
|
436
|
-
printPromptBlock('
|
|
437
|
-
'
|
|
438
|
-
'
|
|
439
|
-
'Open
|
|
440
|
-
'Open a browser window: peach open <port> --driver playwright --headed',
|
|
414
|
+
printPromptBlock('Native desktop app', [
|
|
415
|
+
'peach opens your app in its native desktop app, which is faster than the browser.',
|
|
416
|
+
'Setup installs it, and peach open <port> uses it.',
|
|
417
|
+
'Open a browser window instead: peach open <port> --driver playwright --headed',
|
|
441
418
|
...(process.platform === 'linux'
|
|
442
|
-
? ['Linux
|
|
419
|
+
? ['On Linux the app installs with sudo apt-get and needs a desktop session.']
|
|
443
420
|
: []),
|
|
444
|
-
'The background service is a separate choice that keeps the bridge ready for either
|
|
421
|
+
'The background service is a separate choice that keeps the bridge ready for either window.',
|
|
445
422
|
])
|
|
446
423
|
}
|
|
447
424
|
}
|
|
@@ -451,7 +428,7 @@ function printHelp(): void {
|
|
|
451
428
|
peach setup - configure optional local conveniences
|
|
452
429
|
|
|
453
430
|
peach works without project setup. This command can enable the faster background
|
|
454
|
-
service,
|
|
431
|
+
service, install the native desktop app, configure private diagnostics, and optionally
|
|
455
432
|
add an exact peachsim dependency plus a peach script for teammates.
|
|
456
433
|
|
|
457
434
|
usage:
|
package/cli/commands/swift.ts
CHANGED
|
@@ -5,6 +5,7 @@ import { WebSocket, WebSocketServer } from 'ws'
|
|
|
5
5
|
import { resolveContrastBackendOrigin } from '../../src/backend-origin'
|
|
6
6
|
import { produceConfiguredBundle } from '../../src/configured-bundle-producer'
|
|
7
7
|
import { swiftGuestBundle } from '../../src/guest-bundle'
|
|
8
|
+
import { insertNativeAppIdentityFooter } from '../../src/native-app-identity'
|
|
8
9
|
import {
|
|
9
10
|
resolveSwiftModule,
|
|
10
11
|
SWIFT_ENDPOINT_ENV,
|
|
@@ -73,6 +74,11 @@ export async function runSwift(
|
|
|
73
74
|
} else if (arg === '--scheme') {
|
|
74
75
|
scheme = input[++index]
|
|
75
76
|
if (!scheme || scheme.startsWith('-')) throw new Error('--scheme requires a value')
|
|
77
|
+
} else if (arg === '--swift-sdks-path') {
|
|
78
|
+
const value = input[++index]
|
|
79
|
+
if (!value || value.startsWith('-'))
|
|
80
|
+
throw new Error('--swift-sdks-path requires a value')
|
|
81
|
+
process.env.PEACH_SWIFT_SDKS_PATH = path.resolve(value)
|
|
76
82
|
} else if (arg === '--host-port' || valueFlags.has(arg)) {
|
|
77
83
|
const value = input[++index]
|
|
78
84
|
if (!value || value.startsWith('-')) throw new Error(`${arg} requires a value`)
|
|
@@ -163,7 +169,9 @@ export async function runSwift(
|
|
|
163
169
|
const packageName = path.basename(packagePath)
|
|
164
170
|
const appInfo = host.swiftProjectAppInfo(packagePath)
|
|
165
171
|
const guestSource = swiftGuestBundle(packageName)
|
|
166
|
-
|
|
172
|
+
const withIdentity = (source: string) =>
|
|
173
|
+
appInfo.identity ? insertNativeAppIdentityFooter(source, appInfo.identity) : source
|
|
174
|
+
let guest = withIdentity(guestSource)
|
|
167
175
|
const controller = new AbortController()
|
|
168
176
|
const resources: OpenCommandResources = {}
|
|
169
177
|
let metadata: (SwiftEntryModuleArgs & { hmrPath: string }) | null = null
|
|
@@ -187,7 +195,9 @@ export async function runSwift(
|
|
|
187
195
|
try {
|
|
188
196
|
const config = await loadRNXAppConfig(appProject)
|
|
189
197
|
host.configureSwiftStandinBehavior(packagePath, config)
|
|
190
|
-
guest =
|
|
198
|
+
guest = withIdentity(
|
|
199
|
+
await produceConfiguredBundle(guestSource, config, new Set()),
|
|
200
|
+
)
|
|
191
201
|
const next = await host.swiftPackageMetadata(
|
|
192
202
|
packagePath,
|
|
193
203
|
packageName,
|
|
@@ -343,7 +353,8 @@ export async function runSwift(
|
|
|
343
353
|
console.log(` Swift preview host: ${origin}`)
|
|
344
354
|
await refresh()
|
|
345
355
|
controller.signal.throwIfAborted()
|
|
346
|
-
|
|
356
|
+
// refresh already printed the error
|
|
357
|
+
if (!metadata || compileError) throw new Error('Swift compilation failed, see above')
|
|
347
358
|
if (opts.device) openArgs.push('--device', opts.device)
|
|
348
359
|
if (opts.driver) openArgs.push('--driver', opts.driver)
|
|
349
360
|
if (opts.headless) openArgs.push('--headless')
|