peachsim 0.1.1619 → 0.1.1625
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/README.md +35 -5
- package/cli/app-fonts.ts +2 -1
- package/cli/bridge-flow-runner.ts +222 -175
- package/cli/cloud-client.ts +81 -48
- package/cli/commands/box/search-command.ts +3 -2
- package/cli/commands/box.ts +10 -29
- package/cli/commands/control.ts +108 -43
- package/cli/commands/inspect/settling.ts +57 -20
- package/cli/commands/maestro-compare.ts +11 -32
- package/cli/commands/platform.ts +74 -17
- package/cli/commands/record.ts +20 -15
- package/cli/commands/upload.ts +5 -1
- package/cli/dev-build-freshness.ts +37 -2
- package/cli/drivers/playwright-sim-host.ts +18 -6
- package/cli/maestro-accessibility.ts +53 -0
- package/cli/maestro-compare.ts +24 -0
- package/cli/outbound-endpoints.ts +27 -8
- package/cli/parent-pid.ts +0 -9
- package/cli/remote-simulators.ts +112 -0
- package/cli/remoteSimulatorContract.ts +21 -0
- package/detox/expectations.ts +5 -1
- package/detox/index.ts +133 -98
- package/detox/motion-change.cjs +37 -0
- package/detox/motion-change.d.cts +16 -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 +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 +1528 -937
- 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 +4 -3
- package/dist-lib/cloud.mjs +4 -3
- package/dist-lib/config.cjs +5 -4
- package/dist-lib/config.mjs +5 -4
- package/dist-lib/detox/index.cjs +1021 -364
- package/dist-lib/dev-bundle-resolution.cjs +51 -23
- package/dist-lib/expo-swift-bundle.cjs +1351 -804
- package/dist-lib/expo-swift-metadata.cjs +5 -4
- package/dist-lib/expo-swift-metadata.mjs +5 -4
- package/dist-lib/home-paths.cjs +1 -1
- package/dist-lib/host/bridge-host.cjs +2475 -1343
- 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 +1269 -300
- 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 +806 -398
- package/dist-lib/native-app-identity-project.cjs +729 -21
- package/dist-lib/native-app-identity-project.mjs +728 -21
- package/dist-lib/native-app-identity.cjs +232 -9
- package/dist-lib/native-app-identity.mjs +230 -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 +4 -1
- package/dist-lib/react-native-host-modules.mjs +4 -1
- package/dist-lib/scripts/dev-server-scanner.cjs +94 -28
- package/dist-lib/sdk.cjs +89 -24
- package/dist-lib/sdk.mjs +89 -24
- package/dist-lib/skills.cjs +4471 -2394
- package/dist-lib/swift-compile.cjs +336 -145
- package/dist-lib/swift-compile.mjs +350 -150
- 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 +891 -557
- package/dist-lib/vite.cjs +68 -14
- package/dist-lib/worklet-executable.cjs +25 -2
- package/dist-lib/worklet-executable.mjs +25 -2
- package/package.json +2 -2
- package/scripts/dev-server-scanner.ts +33 -13
- 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/bundleGlobalFooter.ts +26 -7
- package/src/cloud.ts +2 -2
- package/src/codex-client.ts +3 -2
- package/src/dev-bundle-resolution.ts +58 -25
- package/src/expo-kotlin-host.ts +6 -36
- package/src/expo-swift-bundle.ts +17 -12
- package/src/expo-swift-host.ts +477 -173
- package/src/expoSwiftContract.ts +6 -0
- package/src/host/bridge-host.ts +7 -55
- 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 +118 -3
- package/src/native-app-identity.ts +103 -11
- package/src/nitro-compile.ts +2 -5
- package/src/nitro-source.ts +147 -63
- package/src/process-group.ts +90 -60
- package/src/process-output.ts +0 -9
- package/src/public-brand.ts +1 -0
- package/src/react-native-host-modules.ts +3 -0
- package/src/sim-client.ts +6 -4
- package/src/swift-compile.ts +185 -31
- package/src/swift-distribution.lock.json +37 -11
- package/src/swift-distribution.ts +61 -9
- package/src/swift-host.ts +58 -27
- package/src/swift-objc-standin.ts +13 -0
- package/src/swift-standin-emit.ts +1 -1
- package/src/swift-standins.ts +17 -5
- package/src/vite-plugin.ts +8 -19
- package/src/worklet-executable.ts +30 -3
- package/src/worklets-babel.ts +35 -14
- package/src/xcode-project.ts +10 -0
|
@@ -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,
|
|
@@ -37,6 +37,7 @@ import {
|
|
|
37
37
|
webViewAutomationHelpersSource,
|
|
38
38
|
} from '../../../peach-engine/src/webview-automation-host'
|
|
39
39
|
import { DISPOSABLE_BROWSER_CACHE_DIR_NAMES } from '../../src/browser-cache'
|
|
40
|
+
import { listProcessChildren } from '../../src/process-group'
|
|
40
41
|
|
|
41
42
|
// a person's tap on the device canvas is a trusted input, which gives the page
|
|
42
43
|
// the user activation browsers require before media plays with sound, and an
|
|
@@ -113,8 +114,9 @@ const completionFile = process.env.PEACH_PW_COMPLETION_FILE || '';
|
|
|
113
114
|
const disposableBrowserCacheDirs = new Set(${JSON.stringify(
|
|
114
115
|
DISPOSABLE_BROWSER_CACHE_DIR_NAMES,
|
|
115
116
|
)});
|
|
116
|
-
const { existsSync, mkdtempSync, readdirSync, rmSync, unlinkSync, writeFileSync } = require('fs');
|
|
117
|
-
const { execSync, spawn } = require('child_process');
|
|
117
|
+
const { existsSync, mkdtempSync, readdirSync, readFileSync, rmSync, unlinkSync, writeFileSync } = require('fs');
|
|
118
|
+
const { execSync, spawn, spawnSync } = require('child_process');
|
|
119
|
+
const listProcessChildren = ${listProcessChildren.toString()};
|
|
118
120
|
const { tmpdir } = require('os');
|
|
119
121
|
const { join } = require('path');
|
|
120
122
|
const createBrowserPointerDispatcher = ${createBrowserPointerDispatcher.toString()};
|
|
@@ -193,10 +195,9 @@ const assertNoUnsafeSwiftShaderFlag = () => {
|
|
|
193
195
|
const killTree = (pid) => {
|
|
194
196
|
let kids = [];
|
|
195
197
|
try {
|
|
196
|
-
kids =
|
|
197
|
-
.toString().split('\\n').filter(Boolean);
|
|
198
|
+
kids = listProcessChildren([pid]);
|
|
198
199
|
} catch {}
|
|
199
|
-
for (const k of kids) killTree(
|
|
200
|
+
for (const k of kids) killTree(k);
|
|
200
201
|
try { process.kill(pid, 'SIGKILL'); } catch {}
|
|
201
202
|
};
|
|
202
203
|
const reapChrome = () => { for (const pid of chromePidsByProfile()) killTree(pid); };
|
|
@@ -213,6 +214,16 @@ const removeDisposableCaches = (root) => {
|
|
|
213
214
|
removeDisposableCaches(childPath);
|
|
214
215
|
}
|
|
215
216
|
};
|
|
217
|
+
// chrome treats a SingletonLock whose recorded hostname differs from the
|
|
218
|
+
// current one as held by another machine, and macOS renames the host with the
|
|
219
|
+
// network, so a lock left by a killed host blocks every relaunch of a named
|
|
220
|
+
// profile. no process runs this profile here, so its lock files are stale.
|
|
221
|
+
const clearStaleSingletonLock = () => {
|
|
222
|
+
if (chromePidsByProfile().length > 0) return;
|
|
223
|
+
for (const name of ['SingletonLock', 'SingletonSocket', 'SingletonCookie']) {
|
|
224
|
+
try { rmSync(join(profileDir, name), { force: true }); } catch {}
|
|
225
|
+
}
|
|
226
|
+
};
|
|
216
227
|
const cleanupProfile = () => {
|
|
217
228
|
try { rmSync(diskCacheDir, { recursive: true, force: true }); } catch {}
|
|
218
229
|
if (ephemeralProfile) {
|
|
@@ -538,6 +549,7 @@ const cleanupProfile = () => {
|
|
|
538
549
|
label: 'peach sim host ' + profileDir,
|
|
539
550
|
readQueue: readChromiumQueue,
|
|
540
551
|
});
|
|
552
|
+
clearStaleSingletonLock();
|
|
541
553
|
let launchErr = null;
|
|
542
554
|
let launchedChannel = null;
|
|
543
555
|
for (const channel of channelsToTry) {
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import type { PeachAccessibilityNode } from '@peach/globals'
|
|
2
|
+
|
|
3
|
+
// maestro reads ios frames as integer edges (AXElement.boundsString).
|
|
4
|
+
export function accessibilityBounds(node: PeachAccessibilityNode): {
|
|
5
|
+
x: number
|
|
6
|
+
y: number
|
|
7
|
+
width: number
|
|
8
|
+
height: number
|
|
9
|
+
} {
|
|
10
|
+
const left = Math.trunc(node.frame.x)
|
|
11
|
+
const top = Math.trunc(node.frame.y)
|
|
12
|
+
return {
|
|
13
|
+
x: left,
|
|
14
|
+
y: top,
|
|
15
|
+
width: Math.trunc(node.frame.x + node.frame.width) - left,
|
|
16
|
+
height: Math.trunc(node.frame.y + node.frame.height) - top,
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
// upstream Filters.containsChild compares the resolved child TreeNode against
|
|
21
|
+
// a candidate's immediate children by structural equality: the native
|
|
22
|
+
// attribute map, the boolean flags, and the children, recursively. the native
|
|
23
|
+
// hintText attribute is the placeholder (accessibilityHint is not a TreeNode
|
|
24
|
+
// attribute), and engine identity (elementId/nodeId) plus the projection's own
|
|
25
|
+
// metadata (type, role, isAccessibilityElement, state) are not part of that
|
|
26
|
+
// native shape.
|
|
27
|
+
// bounds is explicit so serialized page evaluation survives cli minification.
|
|
28
|
+
export function sameNativeAccessibilityShape(
|
|
29
|
+
a: PeachAccessibilityNode,
|
|
30
|
+
b: PeachAccessibilityNode,
|
|
31
|
+
bounds: typeof accessibilityBounds,
|
|
32
|
+
): boolean {
|
|
33
|
+
if (
|
|
34
|
+
(a.testID ?? '') !== (b.testID ?? '') ||
|
|
35
|
+
(a.label ?? '') !== (b.label ?? '') ||
|
|
36
|
+
(a.placeholder ?? '') !== (b.placeholder ?? '') ||
|
|
37
|
+
(a.value ?? '') !== (b.value ?? '') ||
|
|
38
|
+
a.enabled !== b.enabled ||
|
|
39
|
+
a.selected !== b.selected ||
|
|
40
|
+
a.checked !== b.checked ||
|
|
41
|
+
a.clickable !== b.clickable ||
|
|
42
|
+
(a.hasFocus ?? false) !== (b.hasFocus ?? false) ||
|
|
43
|
+
JSON.stringify(bounds(a)) !== JSON.stringify(bounds(b)) ||
|
|
44
|
+
a.children.length !== b.children.length
|
|
45
|
+
) {
|
|
46
|
+
return false
|
|
47
|
+
}
|
|
48
|
+
for (const [index, child] of a.children.entries()) {
|
|
49
|
+
const other = b.children[index]
|
|
50
|
+
if (!other || !sameNativeAccessibilityShape(child, other, bounds)) return false
|
|
51
|
+
}
|
|
52
|
+
return true
|
|
53
|
+
}
|
package/cli/maestro-compare.ts
CHANGED
|
@@ -455,6 +455,30 @@ export interface CompareSide {
|
|
|
455
455
|
errors: string[]
|
|
456
456
|
}
|
|
457
457
|
|
|
458
|
+
// flow verdicts own status; diagnostics do not reject a pass.
|
|
459
|
+
export function buildCompareSide(
|
|
460
|
+
steps: CompareStep[],
|
|
461
|
+
outcome: CompareSide['status'],
|
|
462
|
+
extraErrors: string[],
|
|
463
|
+
runFailure?: string,
|
|
464
|
+
): CompareSide {
|
|
465
|
+
const failed = steps.find((step) => step.status === 'failed')
|
|
466
|
+
const executed = steps.some((step) => step.status !== 'skipped')
|
|
467
|
+
const errors = [
|
|
468
|
+
...(failed ? [`step "${failed.label}" failed: ${failed.error ?? 'no message'}`] : []),
|
|
469
|
+
...(runFailure ? [runFailure] : []),
|
|
470
|
+
...extraErrors,
|
|
471
|
+
...(!executed && outcome !== 'not run' ? ['flow executed no steps'] : []),
|
|
472
|
+
]
|
|
473
|
+
return {
|
|
474
|
+
status:
|
|
475
|
+
outcome === 'passed' && (failed || runFailure || !executed) ? 'failed' : outcome,
|
|
476
|
+
durationMs: steps.reduce((sum, step) => sum + step.durationMs, 0),
|
|
477
|
+
steps,
|
|
478
|
+
errors,
|
|
479
|
+
}
|
|
480
|
+
}
|
|
481
|
+
|
|
458
482
|
export interface CompareScreen {
|
|
459
483
|
name: string
|
|
460
484
|
step: number
|
|
@@ -147,7 +147,8 @@ export const OUTBOUND_ENDPOINTS: Record<string, OutboundEndpointDeclaration> = {
|
|
|
147
147
|
},
|
|
148
148
|
remote_display_server: {
|
|
149
149
|
category: 'explicit-user-action',
|
|
150
|
-
destination:
|
|
150
|
+
destination:
|
|
151
|
+
'display-server WebSocket URL, or scene recording URL, configured by the embedding host',
|
|
151
152
|
preConsent: true,
|
|
152
153
|
payload:
|
|
153
154
|
'display protocol identity, local input events, scroll lifecycle, resync requests, and applied/painted acknowledgements',
|
|
@@ -159,13 +160,11 @@ export const OUTBOUND_ENDPOINTS: Record<string, OutboundEndpointDeclaration> = {
|
|
|
159
160
|
payload: 'preview objects selected by the user for upload',
|
|
160
161
|
},
|
|
161
162
|
// its own entry, deliberately not folded into contrast_user_action. this is
|
|
162
|
-
// the
|
|
163
|
-
// and to an operator-chosen host rather than a Contrast one, so it must not
|
|
164
|
-
// be readable as "the usual Contrast call".
|
|
163
|
+
// the egress that sends the contents of the user's project to a cloud host.
|
|
165
164
|
peach_cloud_box: {
|
|
166
165
|
category: 'explicit-user-action',
|
|
167
166
|
destination:
|
|
168
|
-
'the Peach Cloud box service
|
|
167
|
+
'the deployed Peach Cloud box service by default, overridden by --endpoint or PEACH_BOX_ENDPOINT',
|
|
169
168
|
preConsent: false,
|
|
170
169
|
payload:
|
|
171
170
|
'every file git reports for the current checkout, then shell commands and file reads and writes for as long as the box shell is open',
|
|
@@ -252,6 +251,10 @@ export interface OutboundCallDeclaration {
|
|
|
252
251
|
// a new fetch, websocket, node HTTP request, or TCP connection has no entry and
|
|
253
252
|
// fails scripts/check/outbound-endpoints.ts until its destination and consent behavior are declared.
|
|
254
253
|
export const DECLARED_OUTBOUND_CALLS: Record<string, OutboundCallDeclaration> = {
|
|
254
|
+
'packages/peach-engine/src/launch-metadata.ts :: fetch :: reference': {
|
|
255
|
+
category: 'local_development',
|
|
256
|
+
count: 1,
|
|
257
|
+
},
|
|
255
258
|
'packages/peach-engine/src/engine/barcode-reader.worker.ts :: fetch :: wasmUrl': {
|
|
256
259
|
category: 'same_origin_runtime',
|
|
257
260
|
count: 1,
|
|
@@ -275,13 +278,17 @@ export const DECLARED_OUTBOUND_CALLS: Record<string, OutboundCallDeclaration> =
|
|
|
275
278
|
// driving a box sends each command to its own box.
|
|
276
279
|
'packages/peachsim/cli/cloud-client.ts :: fetch :: `${this.session.apiOrigin}/v1/boxes/${encodeURIComponent()}/commands`':
|
|
277
280
|
{ category: 'peach_cloud_sim', count: 1 },
|
|
278
|
-
//
|
|
279
|
-
'packages/peachsim/cli/cloud-client.ts :: fetch :: `${session.apiOrigin}/v1/boxes/${encodeURIComponent()}/
|
|
281
|
+
// closing a remote simulator deletes its box with the account's authority.
|
|
282
|
+
'packages/peachsim/cli/cloud-client.ts :: fetch :: `${session.apiOrigin}/v1/boxes/${encodeURIComponent()}/delete`':
|
|
280
283
|
{ category: 'peach_cloud_sim', count: 1 },
|
|
281
284
|
// the display grant is polled until the box hands one back; the body is
|
|
282
285
|
// empty, so the request carries no project or app data.
|
|
283
286
|
'packages/peachsim/cli/cloud-client.ts :: fetch :: `${options.apiOrigin}/v1/boxes/${encodeURIComponent()}/display-grant`':
|
|
284
287
|
{ category: 'peach_cloud_sim', count: 1 },
|
|
288
|
+
// a fresh box's boot receipts are read until its page loads or reports an
|
|
289
|
+
// error; the request carries no body.
|
|
290
|
+
'packages/peachsim/cli/cloud-client.ts :: fetch :: `${options.apiOrigin}/v1/boxes/${encodeURIComponent()}/lifecycle`':
|
|
291
|
+
{ category: 'peach_cloud_sim', count: 1 },
|
|
285
292
|
// `peach remote` creates through the instance api and lists what it made.
|
|
286
293
|
// the create carries labels and wait=true in the query; the list filters
|
|
287
294
|
// by the remote label.
|
|
@@ -379,6 +386,8 @@ export const DECLARED_OUTBOUND_CALLS: Record<string, OutboundCallDeclaration> =
|
|
|
379
386
|
'packages/peach-cloud-box/src/browser-run.ts :: fetch :: `https://browser.invalid/v1/devtools/browser/${sessionId}`':
|
|
380
387
|
{ category: 'peach_cloud_box', count: 2 },
|
|
381
388
|
// list the account sessions a cloud box is billed against. no project content.
|
|
389
|
+
"packages/peachsim/cli/remote-simulators.ts :: fetch :: `${account.apiOrigin.replace(/\\/+$/, '')}/api/v1/rnx/sessions?${query}`":
|
|
390
|
+
{ category: 'contrast_user_action', count: 1 },
|
|
382
391
|
"packages/peach-cloud-box/src/client.ts :: fetch :: `${options.apiOrigin.replace(/\\/+$/, '')}/api/v1/rnx/sessions?${search}`":
|
|
383
392
|
{ category: 'contrast_user_action', count: 1 },
|
|
384
393
|
// `peach box connect` asks the account service which box that ID or name is
|
|
@@ -525,6 +534,8 @@ export const DECLARED_OUTBOUND_CALLS: Record<string, OutboundCallDeclaration> =
|
|
|
525
534
|
},
|
|
526
535
|
'packages/peach-engine/src/mcp/ws-bridge.ts :: websocket :: resolveSootsimBridgeWebSocketUrl({ bridgePort, href: window.location.href, })':
|
|
527
536
|
{ category: 'local_development', count: 1 },
|
|
537
|
+
'packages/peach-engine/src/remote-stream/browser-tenant-proxy.ts :: fetch :: this.config.url':
|
|
538
|
+
{ category: 'remote_display_server', count: 1 },
|
|
528
539
|
'packages/peach-engine/src/remote-stream/browser-tenant-proxy.ts :: websocket :: this.config.url':
|
|
529
540
|
{ category: 'remote_display_server', count: 1 },
|
|
530
541
|
'packages/peach-engine/src/menu/menuActions.ts :: fetch :: screenshot': {
|
|
@@ -612,6 +623,10 @@ export const DECLARED_OUTBOUND_CALLS: Record<string, OutboundCallDeclaration> =
|
|
|
612
623
|
category: 'guest_app_network',
|
|
613
624
|
count: 1,
|
|
614
625
|
},
|
|
626
|
+
'packages/peach-engine/src/render-worker/map-host.ts :: fetch-alias :: request.url': {
|
|
627
|
+
category: 'guest_app_network',
|
|
628
|
+
count: 1,
|
|
629
|
+
},
|
|
615
630
|
'packages/peach-engine/src/render-worker/map-host.ts :: fetch-alias :: url': {
|
|
616
631
|
category: 'map_style_delivery',
|
|
617
632
|
count: 1,
|
|
@@ -764,6 +779,8 @@ export const DECLARED_OUTBOUND_CALLS: Record<string, OutboundCallDeclaration> =
|
|
|
764
779
|
category: 'local_development',
|
|
765
780
|
count: 1,
|
|
766
781
|
},
|
|
782
|
+
'packages/peach-engine/src/components/native-ui/ShareSheetView.tsx :: fetch-alias :: `/__fetch-proxy?url=${encodeURIComponent()}`':
|
|
783
|
+
{ category: 'guest_app_network', count: 1 },
|
|
767
784
|
'packages/peachsim/src/dev-bundle-resolution.ts :: fetch :: `/__fetch-proxy?url=${encodeURIComponent()}`':
|
|
768
785
|
{ category: 'guest_app_network', count: 1 },
|
|
769
786
|
'packages/peachsim/src/dev-bundle-resolution.ts :: fetch :: targetUrl': {
|
|
@@ -1079,7 +1096,9 @@ function callKind(
|
|
|
1079
1096
|
source: string,
|
|
1080
1097
|
): string | null {
|
|
1081
1098
|
if (node.type === 'NewExpression' && node.callee.type === 'Identifier') {
|
|
1082
|
-
if (node.callee.name === 'WebSocket'
|
|
1099
|
+
if (node.callee.name === 'WebSocket' || node.callee.name === 'NativeWebSocket') {
|
|
1100
|
+
return 'websocket'
|
|
1101
|
+
}
|
|
1083
1102
|
if (node.callee.name === 'EventSource') return 'event-source'
|
|
1084
1103
|
if (node.callee.name === 'XMLHttpRequest') return 'xml-http-request'
|
|
1085
1104
|
return null
|
package/cli/parent-pid.ts
CHANGED
|
@@ -68,15 +68,6 @@ function buildComm(): CommLookup {
|
|
|
68
68
|
}
|
|
69
69
|
|
|
70
70
|
function build(): Lookup {
|
|
71
|
-
// on any platform, if the shell exports $PPID and it differs from the
|
|
72
|
-
// node-level process.ppid, it's the shell's own parent — one level up
|
|
73
|
-
// from where we need to be. this is the cheapest and most portable
|
|
74
|
-
// grand-ppid source since it needs no subprocess or FFI.
|
|
75
|
-
const envPpid = Number(process.env.PPID)
|
|
76
|
-
if (Number.isFinite(envPpid) && envPpid > 1 && envPpid !== process.ppid) {
|
|
77
|
-
return () => envPpid
|
|
78
|
-
}
|
|
79
|
-
|
|
80
71
|
if (process.platform === 'linux') return buildLinux()
|
|
81
72
|
if (process.platform === 'darwin') {
|
|
82
73
|
// on Bun: libproc via FFI is ~0.8µs/call. on Node: one `ps` snapshot of
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
import {
|
|
2
|
+
cloudBoxConnectUrl,
|
|
3
|
+
deleteCloudBox,
|
|
4
|
+
resolveCloudBoxReference,
|
|
5
|
+
} from 'peach-cloud-box/client'
|
|
6
|
+
import { isRecord } from '../src/is-record'
|
|
7
|
+
import { deleteRemoteSim, listRemoteSims } from './cloud-client'
|
|
8
|
+
import type {
|
|
9
|
+
AccountRemoteSimulator,
|
|
10
|
+
HostedRemoteSimulator,
|
|
11
|
+
RemoteSimulatorAccount,
|
|
12
|
+
} from './remoteSimulatorContract'
|
|
13
|
+
|
|
14
|
+
// the account's meter is the existing index of hosted simulators. shell-only
|
|
15
|
+
// boxes have no simulator session and do not belong in this list.
|
|
16
|
+
async function listHostedSimulators(
|
|
17
|
+
account: RemoteSimulatorAccount,
|
|
18
|
+
): Promise<HostedRemoteSimulator[]> {
|
|
19
|
+
const query = new URLSearchParams({ kind: 'sim', status: 'running' })
|
|
20
|
+
if (account.accountId) query.set('accountId', account.accountId)
|
|
21
|
+
const response = await fetch(
|
|
22
|
+
`${account.apiOrigin.replace(/\/+$/, '')}/api/v1/rnx/sessions?${query}`,
|
|
23
|
+
{
|
|
24
|
+
headers: { authorization: account.authorization },
|
|
25
|
+
signal: AbortSignal.timeout(30_000),
|
|
26
|
+
},
|
|
27
|
+
)
|
|
28
|
+
if (!response.ok) {
|
|
29
|
+
throw new Error(`hosted simulator list ${response.status}: ${await response.text()}`)
|
|
30
|
+
}
|
|
31
|
+
const parsed: unknown = await response.json()
|
|
32
|
+
if (!isRecord(parsed) || !Array.isArray(parsed.sessions)) {
|
|
33
|
+
throw new Error('hosted simulator list returned invalid JSON')
|
|
34
|
+
}
|
|
35
|
+
const sims: HostedRemoteSimulator[] = []
|
|
36
|
+
for (const session of parsed.sessions) {
|
|
37
|
+
if (!isRecord(session))
|
|
38
|
+
throw new Error('hosted simulator list returned an invalid session')
|
|
39
|
+
if (
|
|
40
|
+
session.kind !== 'sim' ||
|
|
41
|
+
session.size !== 'micro' ||
|
|
42
|
+
session.status !== 'running'
|
|
43
|
+
)
|
|
44
|
+
continue
|
|
45
|
+
if (
|
|
46
|
+
typeof session.boxId !== 'string' ||
|
|
47
|
+
!session.boxId ||
|
|
48
|
+
(session.name !== null && typeof session.name !== 'string') ||
|
|
49
|
+
(session.device !== null && typeof session.device !== 'string') ||
|
|
50
|
+
typeof session.startedAt !== 'number' ||
|
|
51
|
+
!Number.isFinite(session.startedAt) ||
|
|
52
|
+
typeof session.meteredMs !== 'number' ||
|
|
53
|
+
!Number.isFinite(session.meteredMs)
|
|
54
|
+
) {
|
|
55
|
+
throw new Error('hosted simulator list returned an invalid simulator')
|
|
56
|
+
}
|
|
57
|
+
sims.push({
|
|
58
|
+
service: 'box',
|
|
59
|
+
simId: session.boxId,
|
|
60
|
+
name: session.name,
|
|
61
|
+
status: 'running',
|
|
62
|
+
device: session.device,
|
|
63
|
+
startedAt: session.startedAt,
|
|
64
|
+
meteredMs: session.meteredMs,
|
|
65
|
+
})
|
|
66
|
+
}
|
|
67
|
+
return sims
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
export async function listAccountRemoteSimulators(
|
|
71
|
+
account: RemoteSimulatorAccount,
|
|
72
|
+
): Promise<AccountRemoteSimulator[]> {
|
|
73
|
+
const [hosted, nano] = await Promise.all([
|
|
74
|
+
listHostedSimulators(account),
|
|
75
|
+
listRemoteSims({
|
|
76
|
+
authorization: account.authorization,
|
|
77
|
+
accountId: account.accountId,
|
|
78
|
+
}),
|
|
79
|
+
])
|
|
80
|
+
return [
|
|
81
|
+
...hosted,
|
|
82
|
+
...nano.map((sim): AccountRemoteSimulator => ({ ...sim, service: 'sim' })),
|
|
83
|
+
]
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
export async function stopAccountRemoteSimulator(
|
|
87
|
+
account: RemoteSimulatorAccount,
|
|
88
|
+
simId: string,
|
|
89
|
+
): Promise<{ simId: string } | null> {
|
|
90
|
+
const hosted = await listHostedSimulators(account)
|
|
91
|
+
const sim = hosted.find((candidate) => candidate.simId === simId)
|
|
92
|
+
if (!sim)
|
|
93
|
+
return deleteRemoteSim({
|
|
94
|
+
authorization: account.authorization,
|
|
95
|
+
accountId: account.accountId,
|
|
96
|
+
simId,
|
|
97
|
+
})
|
|
98
|
+
|
|
99
|
+
// resolve through the account service before addressing a host. another
|
|
100
|
+
// account's id must never turn into a provider request with this key.
|
|
101
|
+
const reference = await resolveCloudBoxReference({
|
|
102
|
+
apiOrigin: account.apiOrigin,
|
|
103
|
+
apiKey: account.authorization.replace(/^Bearer /, ''),
|
|
104
|
+
accountId: account.accountId,
|
|
105
|
+
boxId: sim.simId,
|
|
106
|
+
})
|
|
107
|
+
await deleteCloudBox({
|
|
108
|
+
connectUrl: cloudBoxConnectUrl(reference.endpoint, reference.boxId),
|
|
109
|
+
accessToken: account.authorization.replace(/^Bearer /, ''),
|
|
110
|
+
})
|
|
111
|
+
return { simId: sim.simId }
|
|
112
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import type { RnxCloudSimView } from '../src/cloud-contract'
|
|
2
|
+
|
|
3
|
+
export interface HostedRemoteSimulator {
|
|
4
|
+
service: 'box'
|
|
5
|
+
simId: string
|
|
6
|
+
name: string | null
|
|
7
|
+
status: 'running'
|
|
8
|
+
device: string | null
|
|
9
|
+
startedAt: number
|
|
10
|
+
meteredMs: number
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export type AccountRemoteSimulator =
|
|
14
|
+
| (RnxCloudSimView & { service: 'sim' })
|
|
15
|
+
| HostedRemoteSimulator
|
|
16
|
+
|
|
17
|
+
export interface RemoteSimulatorAccount {
|
|
18
|
+
apiOrigin: string
|
|
19
|
+
authorization: string
|
|
20
|
+
accountId: string | null
|
|
21
|
+
}
|
package/detox/expectations.ts
CHANGED
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
// waitFor(element).toExist().withTimeout(ms)
|
|
4
4
|
|
|
5
5
|
import { dragScrollNode } from './gestures'
|
|
6
|
+
import { getSootsimNavigationDiagnostics } from './navigation-lifecycle'
|
|
6
7
|
import type { SootSimElement } from './element-types'
|
|
7
8
|
import type { Matcher } from './matchers'
|
|
8
9
|
import type { SootSimFocusedNodeInfo, SootSimNodeInfo } from '@peach/globals'
|
|
@@ -349,7 +350,10 @@ class SootSimWaitForChain {
|
|
|
349
350
|
const node = await this.findNode(this.matcher)
|
|
350
351
|
const exists = node !== null && node !== undefined
|
|
351
352
|
if (this._negated ? exists : !exists) {
|
|
352
|
-
|
|
353
|
+
// a guest that never mounts leaves its console and page errors only here
|
|
354
|
+
throw new Error(
|
|
355
|
+
`waitFor toExist failed: ${JSON.stringify(this.matcher)}\nrecent page diagnostics:\n${JSON.stringify(getSootsimNavigationDiagnostics(this.getPage()))}`,
|
|
356
|
+
)
|
|
353
357
|
}
|
|
354
358
|
}
|
|
355
359
|
return new SootSimWaitForAction(this.assertionFn, this.findNode, this.getPage)
|