peachsim 0.1.1619 → 0.1.1620
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/cli/bridge-flow-runner.ts +39 -63
- package/cli/cloud-client.ts +26 -4
- package/cli/commands/platform.ts +29 -7
- package/cli/commands/record.ts +20 -15
- package/cli/drivers/playwright-sim-host.ts +12 -1
- package/cli/outbound-endpoints.ts +5 -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 +1 -1
- package/dist-lib/beta.cjs +1 -1
- package/dist-lib/beta.mjs +1 -1
- package/dist-lib/bridge-constants.cjs +1 -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 +1 -1
- package/dist-lib/bridge-contract.mjs +1 -1
- package/dist-lib/bundle-channel.cjs +10 -5
- 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 +2 -2
- package/dist-lib/cloud.mjs +2 -2
- package/dist-lib/config.cjs +1 -1
- package/dist-lib/config.mjs +1 -1
- package/dist-lib/detox/index.cjs +1 -1
- package/dist-lib/dev-bundle-resolution.cjs +1 -1
- package/dist-lib/expo-swift-bundle.cjs +10 -5
- package/dist-lib/expo-swift-metadata.cjs +1 -1
- package/dist-lib/expo-swift-metadata.mjs +1 -1
- package/dist-lib/home-paths.cjs +1 -1
- package/dist-lib/host/bridge-host.cjs +10 -5
- 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 +8 -1
- 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 +8 -1
- package/dist-lib/native-app-identity-project.cjs +3 -1
- package/dist-lib/native-app-identity-project.mjs +3 -1
- package/dist-lib/native-app-identity.cjs +9 -1
- package/dist-lib/native-app-identity.mjs +9 -1
- package/dist-lib/native-firebase-metadata.cjs +1 -1
- package/dist-lib/native-firebase-metadata.mjs +1 -1
- package/dist-lib/plist.cjs +1 -1
- package/dist-lib/plist.mjs +1 -1
- package/dist-lib/profiles.cjs +1 -1
- package/dist-lib/public-brand.cjs +1 -1
- package/dist-lib/react-native-host-modules.cjs +1 -1
- package/dist-lib/react-native-host-modules.mjs +1 -1
- package/dist-lib/scripts/dev-server-scanner.cjs +1 -1
- package/dist-lib/sdk.cjs +1 -1
- package/dist-lib/sdk.mjs +1 -1
- package/dist-lib/skills.cjs +85 -49
- package/dist-lib/swift-compile.cjs +10 -5
- package/dist-lib/swift-compile.mjs +10 -5
- package/dist-lib/swift-package.cjs +1 -1
- package/dist-lib/swift-package.mjs +1 -1
- package/dist-lib/swift-submit.cjs +1 -1
- package/dist-lib/swift-submit.mjs +1 -1
- package/dist-lib/swift-toolchain.cjs +1 -1
- package/dist-lib/swift.cjs +1 -1
- package/dist-lib/vite-swift.cjs +10 -5
- package/dist-lib/vite.cjs +1 -1
- package/dist-lib/worklet-executable.cjs +1 -1
- package/dist-lib/worklet-executable.mjs +1 -1
- package/package.json +1 -1
- package/src/cloud.ts +2 -2
- package/src/native-app-identity-project.ts +2 -0
- package/src/native-app-identity.ts +17 -0
- package/src/swift-compile.ts +10 -4
|
@@ -45,6 +45,23 @@ const DEFAULT_TIMEOUT = 10000
|
|
|
45
45
|
const ASSERTION_LOOKUP_MS = 17_000
|
|
46
46
|
const FLOW_TIMEOUT_SCALE = flowTimeoutScale()
|
|
47
47
|
const scaledTimeout = (ms: number) => Math.round(ms * FLOW_TIMEOUT_SCALE)
|
|
48
|
+
|
|
49
|
+
// the ceiling backing a bounded step must never fire before the step's own
|
|
50
|
+
// deadline. waitFor and extendedWaitUntil scale their declared timeout by
|
|
51
|
+
// SOOTSIM_FLOW_TIMEOUT_SCALE, so the ceiling scales it too.
|
|
52
|
+
function stepCeilingMs(step: MaestroStep): number {
|
|
53
|
+
const waitForTimeout =
|
|
54
|
+
typeof step.waitFor?.timeout === 'number' ? step.waitFor.timeout : 0
|
|
55
|
+
const extendedWaitTimeout =
|
|
56
|
+
typeof step.extendedWaitUntil?.timeout === 'number'
|
|
57
|
+
? step.extendedWaitUntil.timeout
|
|
58
|
+
: 0
|
|
59
|
+
return Math.max(
|
|
60
|
+
60_000,
|
|
61
|
+
scaledTimeout(waitForTimeout) + 15_000,
|
|
62
|
+
scaledTimeout(extendedWaitTimeout) + 15_000,
|
|
63
|
+
)
|
|
64
|
+
}
|
|
48
65
|
type FlowScreenshotPathMode = 'dir' | 'flow'
|
|
49
66
|
type ReplayTapTarget = SootSimReplayTarget
|
|
50
67
|
|
|
@@ -3322,6 +3339,10 @@ export class SootSimBridgeFlowRunner {
|
|
|
3322
3339
|
}
|
|
3323
3340
|
}
|
|
3324
3341
|
|
|
3342
|
+
// containers run each child through runStep, so every child keeps its own
|
|
3343
|
+
// deadline and failure. the container has no clock of its own: a parent
|
|
3344
|
+
// clock fails the parent while its children are still making progress.
|
|
3345
|
+
const isContainer = !!(step.runFlow || step.repeat || step.retry)
|
|
3325
3346
|
try {
|
|
3326
3347
|
// containers defer to their executed children: a nested launch must finish
|
|
3327
3348
|
// resetting the guest before the first playable frame is recorded.
|
|
@@ -3329,9 +3350,7 @@ export class SootSimBridgeFlowRunner {
|
|
|
3329
3350
|
this.recordingEnabled &&
|
|
3330
3351
|
this.recordingStartedAtMs == null &&
|
|
3331
3352
|
!step.launchApp &&
|
|
3332
|
-
!
|
|
3333
|
-
!step.repeat &&
|
|
3334
|
-
!step.retry
|
|
3353
|
+
!isContainer
|
|
3335
3354
|
) {
|
|
3336
3355
|
await this.ensureRecordingStarted()
|
|
3337
3356
|
}
|
|
@@ -3341,7 +3360,8 @@ export class SootSimBridgeFlowRunner {
|
|
|
3341
3360
|
startedAtMs = Date.now()
|
|
3342
3361
|
// simple commands have a wall-clock watchdog. bounded operations keep
|
|
3343
3362
|
// their own lookup, settle and bridge deadlines, so the watchdog cannot
|
|
3344
|
-
// report a completed step while its action continues in the background
|
|
3363
|
+
// report a completed step while its action continues in the background;
|
|
3364
|
+
// the step ceiling only backs those deadlines up.
|
|
3345
3365
|
const hasBoundedWait = !!(
|
|
3346
3366
|
// taps combine bounded lookup, settling and delivery. a wall-clock
|
|
3347
3367
|
// watchdog can finish the step while that action is still running.
|
|
@@ -3353,14 +3373,8 @@ export class SootSimBridgeFlowRunner {
|
|
|
3353
3373
|
step.extendedWaitUntil ||
|
|
3354
3374
|
step.waitFor ||
|
|
3355
3375
|
step.scrollUntilVisible ||
|
|
3356
|
-
step.launchApp ||
|
|
3357
3376
|
// deep links own the same bounded guest launch and tree waits.
|
|
3358
3377
|
step.openLink ||
|
|
3359
|
-
step.runFlow ||
|
|
3360
|
-
// retry reruns its body on failure, so its wall time is the body's
|
|
3361
|
-
// times the retry count — the 10s no-progress watchdog is the wrong
|
|
3362
|
-
// budget and would report "bridge probably hung" for a slow retry.
|
|
3363
|
-
step.retry ||
|
|
3364
3378
|
// runScript http calls are synchronous with a 5-minute upstream
|
|
3365
3379
|
// timeout — the 10s watchdog would kill legitimate setup scripts.
|
|
3366
3380
|
step.runScript ||
|
|
@@ -3382,22 +3396,28 @@ export class SootSimBridgeFlowRunner {
|
|
|
3382
3396
|
const STEP_WATCHDOG_MS = 10_000
|
|
3383
3397
|
const body = this.runStepInner(step)
|
|
3384
3398
|
let screenshotPath: string | undefined
|
|
3385
|
-
|
|
3399
|
+
// launchApp self-governs with an internal waitForTree (120s on a
|
|
3400
|
+
// clearState reload): a cache-cleared reload of a heavy bundle re-fetches
|
|
3401
|
+
// every chunk from metro and routinely needs more than the step ceiling.
|
|
3402
|
+
if (isContainer || step.launchApp) {
|
|
3386
3403
|
const result = await body
|
|
3387
3404
|
if (typeof result === 'string') screenshotPath = result
|
|
3388
3405
|
} else {
|
|
3406
|
+
const deadlineMs = hasBoundedWait ? stepCeilingMs(step) : STEP_WATCHDOG_MS
|
|
3389
3407
|
let timer: ReturnType<typeof setTimeout> | null = null
|
|
3390
|
-
const
|
|
3408
|
+
const deadline = new Promise<never>((_, reject) => {
|
|
3391
3409
|
timer = setTimeout(() => {
|
|
3392
3410
|
reject(
|
|
3393
3411
|
new Error(
|
|
3394
|
-
|
|
3412
|
+
hasBoundedWait
|
|
3413
|
+
? `step ${stepIndex + 1} (${stepName}) exceeded ${deadlineMs}ms deadline`
|
|
3414
|
+
: `step watchdog: ${stepName} made no progress in ${STEP_WATCHDOG_MS}ms — bridge or bundle probably hung`,
|
|
3395
3415
|
),
|
|
3396
3416
|
)
|
|
3397
|
-
},
|
|
3417
|
+
}, deadlineMs)
|
|
3398
3418
|
})
|
|
3399
3419
|
try {
|
|
3400
|
-
const result = await Promise.race([body,
|
|
3420
|
+
const result = await Promise.race([body, deadline])
|
|
3401
3421
|
if (typeof result === 'string') screenshotPath = result
|
|
3402
3422
|
} finally {
|
|
3403
3423
|
if (timer) clearTimeout(timer)
|
|
@@ -3786,27 +3806,6 @@ export class SootSimBridgeFlowRunner {
|
|
|
3786
3806
|
private async runFlowBody(steps: MaestroStep[]) {
|
|
3787
3807
|
for (let i = 0; i < steps.length; i++) {
|
|
3788
3808
|
const step = steps[i]
|
|
3789
|
-
// the ceiling must never fire before the step's own deadline. waitFor and
|
|
3790
|
-
// extendedWaitUntil both scale their declared timeout by
|
|
3791
|
-
// SOOTSIM_FLOW_TIMEOUT_SCALE, so the ceiling scales it too — and it has to
|
|
3792
|
-
// count extendedWaitUntil at all. Missing that, a flow declaring
|
|
3793
|
-
// `extendedWaitUntil: {timeout: 90000}` was killed by the race at 60s while
|
|
3794
|
-
// runStep was still waiting, so runStep never reached its own catch and the
|
|
3795
|
-
// step was never recorded: a failed run whose every recorded step is green.
|
|
3796
|
-
const explicitWait = typeof step.wait === 'number' ? step.wait : 0
|
|
3797
|
-
const waitForTimeout =
|
|
3798
|
-
typeof step.waitFor?.timeout === 'number' ? step.waitFor.timeout : 0
|
|
3799
|
-
const extendedWaitTimeout =
|
|
3800
|
-
typeof step.extendedWaitUntil?.timeout === 'number'
|
|
3801
|
-
? step.extendedWaitUntil.timeout
|
|
3802
|
-
: 0
|
|
3803
|
-
const stepCeiling = Math.max(
|
|
3804
|
-
60_000,
|
|
3805
|
-
explicitWait + 15_000,
|
|
3806
|
-
scaledTimeout(waitForTimeout) + 15_000,
|
|
3807
|
-
scaledTimeout(extendedWaitTimeout) + 15_000,
|
|
3808
|
-
)
|
|
3809
|
-
|
|
3810
3809
|
// pause gate + live rail: checked between top-level steps only, so a
|
|
3811
3810
|
// devtools pause never interrupts a step mid-gesture.
|
|
3812
3811
|
if (this.liveStatus) {
|
|
@@ -3843,34 +3842,11 @@ export class SootSimBridgeFlowRunner {
|
|
|
3843
3842
|
}
|
|
3844
3843
|
|
|
3845
3844
|
try {
|
|
3846
|
-
//
|
|
3847
|
-
|
|
3848
|
-
// clearState reload) — a cache-cleared reload of a heavy bundle (bluesky)
|
|
3849
|
-
// re-fetches every chunk from metro and routinely needs >60s, so the
|
|
3850
|
-
// outer per-step race would kill a launch that's legitimately still
|
|
3851
|
-
// booting. let both govern their own timing.
|
|
3852
|
-
if (step.runFlow || step.launchApp) {
|
|
3853
|
-
await this.runStep(step, i, onStepStart)
|
|
3854
|
-
} else {
|
|
3855
|
-
await Promise.race([
|
|
3856
|
-
this.runStep(step, i, onStepStart),
|
|
3857
|
-
new Promise<never>((_, reject) =>
|
|
3858
|
-
setTimeout(
|
|
3859
|
-
() =>
|
|
3860
|
-
reject(
|
|
3861
|
-
new Error(
|
|
3862
|
-
`step ${i + 1} (${Object.keys(step)[0]}) exceeded ${stepCeiling}ms deadline`,
|
|
3863
|
-
),
|
|
3864
|
-
),
|
|
3865
|
-
stepCeiling,
|
|
3866
|
-
),
|
|
3867
|
-
),
|
|
3868
|
-
])
|
|
3869
|
-
}
|
|
3845
|
+
// runStep owns every step's deadline, nested or top level.
|
|
3846
|
+
await this.runStep(step, i, onStepStart)
|
|
3870
3847
|
} catch (error) {
|
|
3871
|
-
// a
|
|
3872
|
-
//
|
|
3873
|
-
// always names the step that failed.
|
|
3848
|
+
// a when predicate that throws fails before runStep records the step.
|
|
3849
|
+
// record it here so the trace always names the step that failed.
|
|
3874
3850
|
const last = this.flowTraceSteps[this.flowTraceSteps.length - 1]
|
|
3875
3851
|
if (!(last && last.stepIndex === i && last.startedAtMs >= liveStartedAtMs)) {
|
|
3876
3852
|
const stepName = Object.keys(step)[0]
|
package/cli/cloud-client.ts
CHANGED
|
@@ -1603,9 +1603,11 @@ export function createCloudBridgeForParsed(
|
|
|
1603
1603
|
: new CloudBridge(session)
|
|
1604
1604
|
}
|
|
1605
1605
|
|
|
1606
|
-
//
|
|
1607
|
-
//
|
|
1608
|
-
//
|
|
1606
|
+
// deletes the box, which ends the simulator it holds. a cloud session box is
|
|
1607
|
+
// the prebuilt bundle `peach ios --remote` created, so it holds nothing but that
|
|
1608
|
+
// bundle: stopping it would only keep its name taken and its storage held. an
|
|
1609
|
+
// account api key deletes with its own authority; a session login deletes with
|
|
1610
|
+
// the login, falling back to the box token the session already holds.
|
|
1609
1611
|
export async function closeCloudBox(session: CloudSession): Promise<void> {
|
|
1610
1612
|
if (session.service !== 'box') {
|
|
1611
1613
|
if (!session.boxId || !session.boxToken) return
|
|
@@ -1632,7 +1634,7 @@ export async function closeCloudBox(session: CloudSession): Promise<void> {
|
|
|
1632
1634
|
let response: Response
|
|
1633
1635
|
try {
|
|
1634
1636
|
response = await fetch(
|
|
1635
|
-
`${session.apiOrigin}/v1/boxes/${encodeURIComponent(session.boxId)}/
|
|
1637
|
+
`${session.apiOrigin}/v1/boxes/${encodeURIComponent(session.boxId)}/delete`,
|
|
1636
1638
|
{
|
|
1637
1639
|
method: 'POST',
|
|
1638
1640
|
headers: { authorization: authHeader },
|
|
@@ -1651,6 +1653,15 @@ export async function closeCloudBox(session: CloudSession): Promise<void> {
|
|
|
1651
1653
|
}
|
|
1652
1654
|
}
|
|
1653
1655
|
|
|
1656
|
+
// the box's page reported that the app threw while booting, so no simulator is
|
|
1657
|
+
// coming and the box should end rather than bill.
|
|
1658
|
+
export class BoxBootFailedError extends Error {
|
|
1659
|
+
constructor(boxId: string, detail: string) {
|
|
1660
|
+
super(`box ${boxId} could not boot the app: ${detail}`)
|
|
1661
|
+
this.name = 'BoxBootFailedError'
|
|
1662
|
+
}
|
|
1663
|
+
}
|
|
1664
|
+
|
|
1654
1665
|
export interface BoxDisplayGrant {
|
|
1655
1666
|
url: string
|
|
1656
1667
|
authToken: string
|
|
@@ -1711,6 +1722,17 @@ export async function requestBoxWatchUrl(options: {
|
|
|
1711
1722
|
}`,
|
|
1712
1723
|
)
|
|
1713
1724
|
}
|
|
1725
|
+
if (response.status === 422) {
|
|
1726
|
+
const text = await response.text()
|
|
1727
|
+
let failure: unknown = null
|
|
1728
|
+
try {
|
|
1729
|
+
failure = JSON.parse(text)
|
|
1730
|
+
} catch {}
|
|
1731
|
+
if (isRecord(failure) && failure.code === 'boot-failed') {
|
|
1732
|
+
throw new BoxBootFailedError(options.boxId, String(failure.error))
|
|
1733
|
+
}
|
|
1734
|
+
throw responseError(response.status, text)
|
|
1735
|
+
}
|
|
1714
1736
|
if (response.status === 409) {
|
|
1715
1737
|
// the page has not attached its simulator yet. this is the normal state
|
|
1716
1738
|
// right after a create, so wait rather than refusing.
|
package/cli/commands/platform.ts
CHANGED
|
@@ -412,7 +412,13 @@ async function runRemoteBox(
|
|
|
412
412
|
const command = `${platform} --remote`
|
|
413
413
|
const input = remoteInputFromArgs(args, platform)
|
|
414
414
|
const [
|
|
415
|
-
{
|
|
415
|
+
{
|
|
416
|
+
BoxBootFailedError,
|
|
417
|
+
closeCloudBox,
|
|
418
|
+
closeCloudSession,
|
|
419
|
+
produceCloudBoxArtifact,
|
|
420
|
+
requestBoxWatchUrl,
|
|
421
|
+
},
|
|
416
422
|
box,
|
|
417
423
|
{ cloudBoxConnectUrl, ensureCloudBoxBrowser, readCloudBoxSession },
|
|
418
424
|
{ authHeaderOrExit, cloudAccountIdOrExit },
|
|
@@ -551,16 +557,32 @@ async function runRemoteBox(
|
|
|
551
557
|
// `peach remote close` cannot end it; name the command that does.
|
|
552
558
|
if (!attachedToShell) {
|
|
553
559
|
console.log(
|
|
554
|
-
` this shell has no Peach integration; the Box bills until: ${rnxPublicBrand.commandName} box
|
|
560
|
+
` this shell has no Peach integration; the Box bills until: ${rnxPublicBrand.commandName} box delete ${created.id}`,
|
|
555
561
|
)
|
|
556
562
|
}
|
|
557
563
|
// the session is published before the grant is minted, so a grant that never
|
|
558
564
|
// arrives still leaves the box addressable: re-running this command mints it.
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
565
|
+
let watchUrl: string
|
|
566
|
+
try {
|
|
567
|
+
watchUrl = await requestBoxWatchUrl({
|
|
568
|
+
apiOrigin: endpoint,
|
|
569
|
+
boxId: created.id,
|
|
570
|
+
boxToken: created.accessToken ?? '',
|
|
571
|
+
})
|
|
572
|
+
} catch (error) {
|
|
573
|
+
// an app that cannot boot never gets a simulator, so the box ends with the
|
|
574
|
+
// error instead of billing until someone finds it.
|
|
575
|
+
if (error instanceof BoxBootFailedError) {
|
|
576
|
+
await closeCloudBox(session)
|
|
577
|
+
try {
|
|
578
|
+
publishCloudSessionToShell(null)
|
|
579
|
+
} catch {
|
|
580
|
+
// no shell update channel is required outside an interactive peach shell
|
|
581
|
+
}
|
|
582
|
+
delete process.env[PEACH_CLOUD_SESSION_ENV]
|
|
583
|
+
}
|
|
584
|
+
throw error
|
|
585
|
+
}
|
|
564
586
|
console.log(` open ${watchUrl}`)
|
|
565
587
|
return openInBrowser(watchUrl)
|
|
566
588
|
}
|
package/cli/commands/record.ts
CHANGED
|
@@ -416,40 +416,45 @@ export async function runRecord(args: string[], opts: RecordOptions) {
|
|
|
416
416
|
}
|
|
417
417
|
await new Promise((r) => setTimeout(r, durationMs))
|
|
418
418
|
const deadline = Date.now() + Math.max(5_000, durationMs)
|
|
419
|
-
|
|
419
|
+
// frames come back a few at a time: one message holding every base64 png
|
|
420
|
+
// exceeds the bridge socket's message cap and closes it.
|
|
421
|
+
const FRAMES_PER_PAGE = 2
|
|
422
|
+
let saved = 0
|
|
420
423
|
for (;;) {
|
|
421
424
|
const result: {
|
|
422
425
|
ok: boolean
|
|
423
426
|
done: boolean
|
|
427
|
+
totalFrames?: number
|
|
424
428
|
frames?: Array<{ data: string; size: number }>
|
|
425
429
|
error?: string
|
|
426
430
|
} | null = await bridge.send({
|
|
427
431
|
type: 'evaluate',
|
|
428
|
-
code: `window.__sootsimRecorder.getFrameCaptureResult(${start.requestId})`,
|
|
432
|
+
code: `window.__sootsimRecorder.getFrameCaptureResult(${start.requestId}, { offset: ${saved}, limit: ${FRAMES_PER_PAGE} })`,
|
|
429
433
|
})
|
|
430
434
|
if (!result) {
|
|
431
435
|
console.error(' frame capture result missing')
|
|
432
436
|
peachExit(1)
|
|
433
437
|
}
|
|
434
|
-
if (result.done) {
|
|
435
|
-
if (
|
|
436
|
-
console.error(
|
|
438
|
+
if (!result.done) {
|
|
439
|
+
if (Date.now() >= deadline) {
|
|
440
|
+
console.error(' frame capture timed out')
|
|
437
441
|
peachExit(1)
|
|
438
442
|
}
|
|
439
|
-
|
|
440
|
-
|
|
443
|
+
await new Promise((r) => setTimeout(r, 100))
|
|
444
|
+
continue
|
|
441
445
|
}
|
|
442
|
-
if (
|
|
443
|
-
console.error(
|
|
446
|
+
if (!result.ok) {
|
|
447
|
+
console.error(` frame capture failed: ${result.error ?? 'unknown error'}`)
|
|
444
448
|
peachExit(1)
|
|
445
449
|
}
|
|
446
|
-
|
|
450
|
+
for (const f of result.frames ?? []) {
|
|
451
|
+
saved++
|
|
452
|
+
const p = `${outDir}/frame-${String(saved).padStart(3, '0')}.png`
|
|
453
|
+
writeFileSync(p, Buffer.from(f.data, 'base64'))
|
|
454
|
+
}
|
|
455
|
+
if (saved >= (result.totalFrames ?? 0)) break
|
|
447
456
|
}
|
|
448
|
-
|
|
449
|
-
const p = `${outDir}/frame-${String(i + 1).padStart(3, '0')}.png`
|
|
450
|
-
writeFileSync(p, Buffer.from(f.data, 'base64'))
|
|
451
|
-
})
|
|
452
|
-
console.log(` saved ${frames.length} frames`)
|
|
457
|
+
console.log(` saved ${saved} frames`)
|
|
453
458
|
return
|
|
454
459
|
}
|
|
455
460
|
|
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import { createBrowserPageTargetReader } from '../../../peach-engine/src/browser-page-target'
|
|
2
1
|
import {
|
|
3
2
|
acquireChromiumAdmission,
|
|
4
3
|
readChromiumQueue,
|
|
@@ -21,6 +20,7 @@ import {
|
|
|
21
20
|
chromeDisabledFeaturesSwitch,
|
|
22
21
|
softwareChromeLaunchOptions,
|
|
23
22
|
} from '../../../../scripts/lib/reap-browser'
|
|
23
|
+
import { createBrowserPageTargetReader } from '../../../peach-engine/src/browser-page-target'
|
|
24
24
|
import {
|
|
25
25
|
readBrowserCompositeCaptureRequest,
|
|
26
26
|
resolveBrowserCompositeCdpClip,
|
|
@@ -213,6 +213,16 @@ const removeDisposableCaches = (root) => {
|
|
|
213
213
|
removeDisposableCaches(childPath);
|
|
214
214
|
}
|
|
215
215
|
};
|
|
216
|
+
// chrome treats a SingletonLock whose recorded hostname differs from the
|
|
217
|
+
// current one as held by another machine, and macOS renames the host with the
|
|
218
|
+
// network, so a lock left by a killed host blocks every relaunch of a named
|
|
219
|
+
// profile. no process runs this profile here, so its lock files are stale.
|
|
220
|
+
const clearStaleSingletonLock = () => {
|
|
221
|
+
if (chromePidsByProfile().length > 0) return;
|
|
222
|
+
for (const name of ['SingletonLock', 'SingletonSocket', 'SingletonCookie']) {
|
|
223
|
+
try { rmSync(join(profileDir, name), { force: true }); } catch {}
|
|
224
|
+
}
|
|
225
|
+
};
|
|
216
226
|
const cleanupProfile = () => {
|
|
217
227
|
try { rmSync(diskCacheDir, { recursive: true, force: true }); } catch {}
|
|
218
228
|
if (ephemeralProfile) {
|
|
@@ -538,6 +548,7 @@ const cleanupProfile = () => {
|
|
|
538
548
|
label: 'peach sim host ' + profileDir,
|
|
539
549
|
readQueue: readChromiumQueue,
|
|
540
550
|
});
|
|
551
|
+
clearStaleSingletonLock();
|
|
541
552
|
let launchErr = null;
|
|
542
553
|
let launchedChannel = null;
|
|
543
554
|
for (const channel of channelsToTry) {
|
|
@@ -275,8 +275,8 @@ export const DECLARED_OUTBOUND_CALLS: Record<string, OutboundCallDeclaration> =
|
|
|
275
275
|
// driving a box sends each command to its own box.
|
|
276
276
|
'packages/peachsim/cli/cloud-client.ts :: fetch :: `${this.session.apiOrigin}/v1/boxes/${encodeURIComponent()}/commands`':
|
|
277
277
|
{ category: 'peach_cloud_sim', count: 1 },
|
|
278
|
-
//
|
|
279
|
-
'packages/peachsim/cli/cloud-client.ts :: fetch :: `${session.apiOrigin}/v1/boxes/${encodeURIComponent()}/
|
|
278
|
+
// closing a remote simulator deletes its box with the account's authority.
|
|
279
|
+
'packages/peachsim/cli/cloud-client.ts :: fetch :: `${session.apiOrigin}/v1/boxes/${encodeURIComponent()}/delete`':
|
|
280
280
|
{ category: 'peach_cloud_sim', count: 1 },
|
|
281
281
|
// the display grant is polled until the box hands one back; the body is
|
|
282
282
|
// empty, so the request carries no project or app data.
|
|
@@ -1079,7 +1079,9 @@ function callKind(
|
|
|
1079
1079
|
source: string,
|
|
1080
1080
|
): string | null {
|
|
1081
1081
|
if (node.type === 'NewExpression' && node.callee.type === 'Identifier') {
|
|
1082
|
-
if (node.callee.name === 'WebSocket'
|
|
1082
|
+
if (node.callee.name === 'WebSocket' || node.callee.name === 'NativeWebSocket') {
|
|
1083
|
+
return 'websocket'
|
|
1084
|
+
}
|
|
1083
1085
|
if (node.callee.name === 'EventSource') return 'event-source'
|
|
1084
1086
|
if (node.callee.name === 'XMLHttpRequest') return 'xml-http-request'
|
|
1085
1087
|
return null
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
/*! peach v0.1.
|
|
1
|
+
/*! peach v0.1.1620 | (c) 2026 Tamagui LLC | Proprietary; see LICENSE */
|
|
2
2
|
let __sootsim_import_meta_url = '', __sootsim_import_meta_dirname = ''; try { __sootsim_import_meta_url = require('url').pathToFileURL(__filename).href; __sootsim_import_meta_dirname = __dirname; } catch {}
|
|
3
3
|
"use strict";
|
|
4
4
|
var __create = Object.create;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
/*! peach v0.1.
|
|
1
|
+
/*! peach v0.1.1620 | (c) 2026 Tamagui LLC | Proprietary; see LICENSE */
|
|
2
2
|
let __sootsim_import_meta_url = '', __sootsim_import_meta_dirname = ''; try { __sootsim_import_meta_url = require('url').pathToFileURL(__filename).href; __sootsim_import_meta_dirname = __dirname; } catch {}
|
|
3
3
|
"use strict";
|
|
4
4
|
var __defProp = Object.defineProperty;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
/*! peach v0.1.
|
|
1
|
+
/*! peach v0.1.1620 | (c) 2026 Tamagui LLC | Proprietary; see LICENSE */
|
|
2
2
|
let __sootsim_import_meta_url = '', __sootsim_import_meta_dirname = ''; try { __sootsim_import_meta_url = require('url').pathToFileURL(__filename).href; __sootsim_import_meta_dirname = __dirname; } catch {}
|
|
3
3
|
"use strict";
|
|
4
4
|
var __create = Object.create;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
/*! peach v0.1.
|
|
1
|
+
/*! peach v0.1.1620 | (c) 2026 Tamagui LLC | Proprietary; see LICENSE */
|
|
2
2
|
let __sootsim_import_meta_url = '', __sootsim_import_meta_dirname = ''; try { __sootsim_import_meta_url = require('url').pathToFileURL(__filename).href; __sootsim_import_meta_dirname = __dirname; } catch {}
|
|
3
3
|
"use strict";
|
|
4
4
|
var __create = Object.create;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
/*! peach v0.1.
|
|
1
|
+
/*! peach v0.1.1620 | (c) 2026 Tamagui LLC | Proprietary; see LICENSE */
|
|
2
2
|
let __sootsim_import_meta_url = '', __sootsim_import_meta_dirname = ''; try { __sootsim_import_meta_url = require('url').pathToFileURL(__filename).href; __sootsim_import_meta_dirname = __dirname; } catch {}
|
|
3
3
|
"use strict";
|
|
4
4
|
var __defProp = Object.defineProperty;
|
package/dist-lib/beta.cjs
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
/*! peach v0.1.
|
|
1
|
+
/*! peach v0.1.1620 | (c) 2026 Tamagui LLC | Proprietary; see LICENSE */
|
|
2
2
|
let __sootsim_import_meta_url = '', __sootsim_import_meta_dirname = ''; try { __sootsim_import_meta_url = require('url').pathToFileURL(__filename).href; __sootsim_import_meta_dirname = __dirname; } catch {}
|
|
3
3
|
"use strict";
|
|
4
4
|
var __defProp = Object.defineProperty;
|
package/dist-lib/beta.mjs
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
/*! peach v0.1.
|
|
1
|
+
/*! peach v0.1.1620 | (c) 2026 Tamagui LLC | Proprietary; see LICENSE */
|
|
2
2
|
let __sootsim_import_meta_url = '', __sootsim_import_meta_dirname = ''; try { __sootsim_import_meta_url = require('url').pathToFileURL(__filename).href; __sootsim_import_meta_dirname = __dirname; } catch {}
|
|
3
3
|
"use strict";
|
|
4
4
|
var __defProp = Object.defineProperty;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
/*! peach v0.1.
|
|
1
|
+
/*! peach v0.1.1620 | (c) 2026 Tamagui LLC | Proprietary; see LICENSE */
|
|
2
2
|
let __sootsim_import_meta_url = '', __sootsim_import_meta_dirname = ''; try { __sootsim_import_meta_url = require('url').pathToFileURL(__filename).href; __sootsim_import_meta_dirname = __dirname; } catch {}
|
|
3
3
|
"use strict";
|
|
4
4
|
var __defProp = Object.defineProperty;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
/*! peach v0.1.
|
|
1
|
+
/*! peach v0.1.1620 | (c) 2026 Tamagui LLC | Proprietary; see LICENSE */
|
|
2
2
|
let __sootsim_import_meta_url = '', __sootsim_import_meta_dirname = ''; try { __sootsim_import_meta_url = require('url').pathToFileURL(__filename).href; __sootsim_import_meta_dirname = __dirname; } catch {}
|
|
3
3
|
"use strict";
|
|
4
4
|
var __defProp = Object.defineProperty;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
/*! peach v0.1.
|
|
1
|
+
/*! peach v0.1.1620 | (c) 2026 Tamagui LLC | Proprietary; see LICENSE */
|
|
2
2
|
let __sootsim_import_meta_url = '', __sootsim_import_meta_dirname = ''; try { __sootsim_import_meta_url = require('url').pathToFileURL(__filename).href; __sootsim_import_meta_dirname = __dirname; } catch {}
|
|
3
3
|
"use strict";
|
|
4
4
|
var __create = Object.create;
|
|
@@ -1618,7 +1618,7 @@ function swiftTargetDependencies(files2, targets) {
|
|
|
1618
1618
|
}
|
|
1619
1619
|
function swiftArtifactHash(input, release, moduleRoot) {
|
|
1620
1620
|
const hash = (0, import_node_crypto2.createHash)("sha256");
|
|
1621
|
-
hash.update(`swift-compile/
|
|
1621
|
+
hash.update(`swift-compile/v47
|
|
1622
1622
|
${release}
|
|
1623
1623
|
`);
|
|
1624
1624
|
if (input.oneModule) hash.update("one-module\n");
|
|
@@ -2143,9 +2143,14 @@ function compiledSwiftSources(files2, targets) {
|
|
|
2143
2143
|
const orderedTargets = [...targets].sort((a, b) => b.path.length - a.path.length);
|
|
2144
2144
|
for (const [file, contents] of Object.entries(files2)) {
|
|
2145
2145
|
if (!file.endsWith(".swift")) continue;
|
|
2146
|
-
const owner = orderedTargets.find(
|
|
2147
|
-
|
|
2148
|
-
|
|
2146
|
+
const owner = orderedTargets.find((candidate) => {
|
|
2147
|
+
const prefix = candidate.path === "." ? "" : `${candidate.path}/`;
|
|
2148
|
+
if (!file.startsWith(prefix)) return false;
|
|
2149
|
+
const relative = file.slice(prefix.length);
|
|
2150
|
+
return !candidate.exclude?.some(
|
|
2151
|
+
(excluded) => relative === excluded || relative.startsWith(`${excluded}/`)
|
|
2152
|
+
);
|
|
2153
|
+
});
|
|
2149
2154
|
if (/(?:^|\/)Package(?:@swift-[\d.]+)?\.swift$/.test(file) && (!owner || /^Package(?:@swift-[\d.]+)?\.swift$/.test(file))) {
|
|
2150
2155
|
compiled.set(file, contents);
|
|
2151
2156
|
continue;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
/*! peach v0.1.
|
|
1
|
+
/*! peach v0.1.1620 | (c) 2026 Tamagui LLC | Proprietary; see LICENSE */
|
|
2
2
|
let __sootsim_import_meta_url = '', __sootsim_import_meta_dirname = ''; try { __sootsim_import_meta_url = require('url').pathToFileURL(__filename).href; __sootsim_import_meta_dirname = __dirname; } catch {}
|
|
3
3
|
"use strict";
|
|
4
4
|
var __defProp = Object.defineProperty;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
/*! peach v0.1.
|
|
1
|
+
/*! peach v0.1.1620 | (c) 2026 Tamagui LLC | Proprietary; see LICENSE */
|
|
2
2
|
let __sootsim_import_meta_url = '', __sootsim_import_meta_dirname = ''; try { __sootsim_import_meta_url = require('url').pathToFileURL(__filename).href; __sootsim_import_meta_dirname = __dirname; } catch {}
|
|
3
3
|
"use strict";
|
|
4
4
|
var __defProp = Object.defineProperty;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
/*! peach v0.1.
|
|
1
|
+
/*! peach v0.1.1620 | (c) 2026 Tamagui LLC | Proprietary; see LICENSE */
|
|
2
2
|
let __sootsim_import_meta_url = '', __sootsim_import_meta_dirname = ''; try { __sootsim_import_meta_url = require('url').pathToFileURL(__filename).href; __sootsim_import_meta_dirname = __dirname; } catch {}
|
|
3
3
|
"use strict";
|
|
4
4
|
var __defProp = Object.defineProperty;
|
package/dist-lib/cloud.cjs
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
/*! peach v0.1.
|
|
1
|
+
/*! peach v0.1.1620 | (c) 2026 Tamagui LLC | Proprietary; see LICENSE */
|
|
2
2
|
let __sootsim_import_meta_url = '', __sootsim_import_meta_dirname = ''; try { __sootsim_import_meta_url = require('url').pathToFileURL(__filename).href; __sootsim_import_meta_dirname = __dirname; } catch {}
|
|
3
3
|
"use strict";
|
|
4
4
|
var __defProp = Object.defineProperty;
|
|
@@ -195,7 +195,7 @@ function sleep(ms) {
|
|
|
195
195
|
var DEFAULT_DEVICE = "iphone-16";
|
|
196
196
|
var DEFAULT_WAIT_READY_MS = 2e4;
|
|
197
197
|
var CLOUD_MAX_RETRIES = 2;
|
|
198
|
-
var CLOUD_NEVER_RETRY_CODES = /* @__PURE__ */ new Set(["concurrency_limit"
|
|
198
|
+
var CLOUD_NEVER_RETRY_CODES = /* @__PURE__ */ new Set(["concurrency_limit"]);
|
|
199
199
|
function normalizeCloudOrigin(source) {
|
|
200
200
|
let parsed;
|
|
201
201
|
try {
|
package/dist-lib/cloud.mjs
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
/*! peach v0.1.
|
|
1
|
+
/*! peach v0.1.1620 | (c) 2026 Tamagui LLC | Proprietary; see LICENSE */
|
|
2
2
|
var __defProp = Object.defineProperty;
|
|
3
3
|
var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
|
|
4
4
|
var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value);
|
|
@@ -165,7 +165,7 @@ function sleep(ms) {
|
|
|
165
165
|
var DEFAULT_DEVICE = "iphone-16";
|
|
166
166
|
var DEFAULT_WAIT_READY_MS = 2e4;
|
|
167
167
|
var CLOUD_MAX_RETRIES = 2;
|
|
168
|
-
var CLOUD_NEVER_RETRY_CODES = /* @__PURE__ */ new Set(["concurrency_limit"
|
|
168
|
+
var CLOUD_NEVER_RETRY_CODES = /* @__PURE__ */ new Set(["concurrency_limit"]);
|
|
169
169
|
function normalizeCloudOrigin(source) {
|
|
170
170
|
let parsed;
|
|
171
171
|
try {
|
package/dist-lib/config.cjs
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
/*! peach v0.1.
|
|
1
|
+
/*! peach v0.1.1620 | (c) 2026 Tamagui LLC | Proprietary; see LICENSE */
|
|
2
2
|
let __sootsim_import_meta_url = '', __sootsim_import_meta_dirname = ''; try { __sootsim_import_meta_url = require('url').pathToFileURL(__filename).href; __sootsim_import_meta_dirname = __dirname; } catch {}
|
|
3
3
|
"use strict";
|
|
4
4
|
var __defProp = Object.defineProperty;
|
package/dist-lib/config.mjs
CHANGED
package/dist-lib/detox/index.cjs
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
/*! peach v0.1.
|
|
1
|
+
/*! peach v0.1.1620 | (c) 2026 Tamagui LLC | Proprietary; see LICENSE */
|
|
2
2
|
let __sootsim_import_meta_url = '', __sootsim_import_meta_dirname = ''; try { __sootsim_import_meta_url = require('url').pathToFileURL(__filename).href; __sootsim_import_meta_dirname = __dirname; } catch {}
|
|
3
3
|
"use strict";
|
|
4
4
|
var __create = Object.create;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
/*! peach v0.1.
|
|
1
|
+
/*! peach v0.1.1620 | (c) 2026 Tamagui LLC | Proprietary; see LICENSE */
|
|
2
2
|
let __sootsim_import_meta_url = '', __sootsim_import_meta_dirname = ''; try { __sootsim_import_meta_url = require('url').pathToFileURL(__filename).href; __sootsim_import_meta_dirname = __dirname; } catch {}
|
|
3
3
|
"use strict";
|
|
4
4
|
var __defProp = Object.defineProperty;
|