peachsim 0.1.527 → 0.1.570
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 +26 -26
- package/cli/app-fonts.ts +1 -1
- package/cli/app-splash.ts +1 -1
- package/cli/bin.ts +1 -1
- package/cli/bridge-diagnostics.ts +1 -1
- package/cli/bridge-flow-runner.ts +57 -5
- package/cli/cloud-client.ts +273 -6
- package/cli/cloud-dispatch.ts +22 -2
- package/cli/cloud-session.ts +60 -4
- package/cli/cloud-test-tunnel.ts +354 -0
- package/cli/commands/box/bundle-plane.ts +2 -2
- package/cli/commands/box/checkout-plane.ts +1 -1
- package/cli/commands/box/rnx-command.ts +2 -2
- package/cli/commands/box/search-command.ts +2 -2
- package/cli/commands/box.ts +8 -8
- package/cli/commands/compat.ts +1 -1
- package/cli/commands/control.ts +22 -3
- package/cli/commands/detox.ts +1 -1
- package/cli/commands/device.ts +1 -1
- package/cli/commands/diagnose.ts +1 -1
- package/cli/commands/film.ts +1 -1
- package/cli/commands/flow.ts +60 -3
- package/cli/commands/inspect/actions.ts +1 -1
- package/cli/commands/inspect/core.ts +6 -7
- package/cli/commands/inspect/find.ts +2 -1
- package/cli/commands/inspect/resolve-target.ts +5 -3
- package/cli/commands/inspect/shared.ts +1 -1
- package/cli/commands/inspect/wait-event.ts +1 -1
- package/cli/commands/inspect/wait-selector.ts +15 -4
- package/cli/commands/inspect.ts +19 -14
- package/cli/commands/maestro.ts +464 -43
- package/cli/commands/permissions.ts +1 -1
- package/cli/commands/platform.ts +205 -8
- package/cli/commands/preview.ts +2 -2
- package/cli/commands/record.ts +2 -2
- package/cli/commands/report-issue.ts +2 -2
- package/cli/commands/screenshot-layers.ts +1 -1
- package/cli/commands/screenshot.ts +1 -1
- package/cli/commands/setup.ts +2 -2
- package/cli/commands/test.ts +440 -115
- package/cli/commands/three-mode.ts +1 -1
- package/cli/commands/upload.ts +5 -5
- package/cli/commands/what-happened.ts +1 -1
- package/cli/desktop-companion.ts +3 -3
- package/cli/drivers/playwright-sim-host.ts +2 -2
- package/cli/flow-file.ts +7 -0
- package/cli/flow-live-status.ts +65 -8
- package/cli/help-core.ts +3 -3
- package/cli/hidden-runtime-alias.ts +1 -1
- package/cli/maestro-report.ts +178 -0
- package/cli/main.ts +1 -1
- package/cli/outbound-endpoints.ts +185 -136
- package/cli/parse-args.ts +1 -1
- package/cli/registry.ts +1 -1
- package/cli/resolve-assets.ts +1 -1
- package/cli/run-registry.ts +8 -0
- package/cli/send-to-box.ts +4 -4
- package/cli/ws-bridge.ts +3 -5
- package/detox/expectations.ts +1 -1
- package/detox/gestures.ts +2 -2
- package/detox/index.ts +71 -29
- package/detox/jest-preset.cjs +3 -3
- package/detox/jest-setup-after-env.cjs +1 -1
- package/dist-lib/agent-daemon-client.cjs +2 -2
- package/dist-lib/agent-events.cjs +1 -1
- package/dist-lib/agent-identity.cjs +1 -1
- package/dist-lib/agent-sessions.cjs +2 -2
- package/dist-lib/attached-projects.cjs +2 -2
- 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/capture-contract.cjs +94 -3
- package/dist-lib/capture-contract.mjs +93 -3
- package/dist-lib/cli-constants.cjs +1 -1
- package/dist-lib/cloud-contract.cjs +1 -1
- package/dist-lib/cloud-contract.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/detox/index.cjs +106 -25
- package/dist-lib/detox/jest-preset.cjs +3 -3
- package/dist-lib/dev-bundle-resolution.cjs +1 -1
- package/dist-lib/home-paths.cjs +1 -1
- package/dist-lib/host/bridge-host.cjs +137 -61
- package/dist-lib/host/fetch-proxy-handler.cjs +28 -2
- package/dist-lib/host/fetch-proxy-overrides.cjs +33 -3
- package/dist-lib/host/fetch-proxy-overrides.mjs +30 -2
- package/dist-lib/host/replacement-module-handler.cjs +1 -1
- package/dist-lib/host/websocket-proxy.cjs +1 -1
- package/dist-lib/index.cjs +209 -16
- package/dist-lib/jump-to-source-babel.cjs +1 -1
- package/dist-lib/jump-to-source-native.cjs +1 -1
- package/dist-lib/menu.cjs +2 -2
- package/dist-lib/menu.mjs +2 -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 +3 -3
- package/dist-lib/metro-production-bundle.mjs +3 -3
- package/dist-lib/metro.cjs +8 -8
- package/dist-lib/profiles.cjs +1 -1
- package/dist-lib/public-brand.cjs +2 -2
- package/dist-lib/react-native-host-modules.cjs +1 -1
- package/dist-lib/react-native-host-modules.mjs +1 -1
- package/dist-lib/render-mode.cjs +1 -1
- package/dist-lib/scripts/dev-server-scanner.cjs +31 -15
- package/dist-lib/sdk.cjs +3 -2
- package/dist-lib/sdk.mjs +3 -2
- package/dist-lib/skills.cjs +362 -163
- package/dist-lib/swift-submit.cjs +1 -1
- package/dist-lib/swift-submit.mjs +1 -1
- package/dist-lib/swift.cjs +583 -16
- package/dist-lib/vite.cjs +29 -14
- package/package.json +9 -9
- package/scripts/dev-server-scanner.ts +41 -14
- package/scripts/optional-demo-registry.ts +3 -3
- package/skills/contrast/SKILL.md +9 -9
- package/skills/rnx-debug/SKILL.md +14 -4
- package/skills/rnx-perf/SKILL.md +5 -5
- package/skills/rnx-setup/SKILL.md +11 -11
- package/skills/rnx-test/SKILL.md +11 -11
- package/skills/rnx-visual/SKILL.md +6 -6
- package/src/agent-sessions.ts +3 -3
- package/src/attached-projects.ts +1 -1
- package/src/bridge-contract-input.ts +1 -1
- package/src/bridge-contract.ts +23 -5
- package/src/capture-contract.ts +145 -3
- package/src/config.ts +2 -2
- package/src/connect.ts +2 -2
- package/src/home-paths.ts +3 -3
- package/src/host/bridge-host.ts +2 -2
- package/src/host/fetch-proxy-handler.ts +4 -3
- package/src/host/fetch-proxy-overrides.ts +41 -0
- package/src/host/plane-host.ts +2 -2
- package/src/index.ts +3 -0
- package/src/metro-plugin.ts +4 -4
- package/src/metro-production-bundle.ts +2 -2
- package/src/native-seam-manifest.ts +6 -6
- package/src/public-brand.ts +1 -1
- package/src/rolldown-plugin-swift.ts +8 -5
- package/src/runtime-assets.ts +8 -6
- package/src/screenshots/frame-compose.ts +24 -56
- package/src/screenshots/org-deck.ts +1 -1
- package/src/screenshots/registry.ts +4 -4
- package/src/screenshots/schema.ts +1 -1
- package/src/sdk.ts +2 -2
- package/src/settings-bundle.ts +1 -1
- package/src/sim-client.ts +2 -4
- package/src/skills/builtin/compat-check.ts +1 -1
- package/src/vite-plugin-one.ts +1 -1
- package/src/vite-plugin-swift.ts +11 -6
- package/src/vite-plugin.ts +30 -17
- package/src/worklets-babel.ts +2 -2
package/cli/parse-args.ts
CHANGED
|
@@ -77,7 +77,7 @@ export const HYPHENATED_GROUPING_VERBS = new Set(['do', 'get', 'wait', 'shell',
|
|
|
77
77
|
|
|
78
78
|
// known commands — if first positional arg matches, it's a command.
|
|
79
79
|
// every entry here must have a `cliCommandMetas` entry in
|
|
80
|
-
// `packages/
|
|
80
|
+
// `packages/peach-skills/src/cli/meta.ts` (or sit on the internal
|
|
81
81
|
// allowlist in `test/sootsimCliRegistry.test.ts`), so help, website docs,
|
|
82
82
|
// and agent skills can never drift from what the parser accepts.
|
|
83
83
|
export const COMMANDS = new Set(RNX_NODE_COMMAND_NAMES)
|
package/cli/registry.ts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
export * from '@
|
|
1
|
+
export * from '@peach/skills/cli'
|
|
2
2
|
export * from './command-registry'
|
package/cli/resolve-assets.ts
CHANGED
|
@@ -13,7 +13,7 @@ import { rnxPublicBrand } from '../src/public-brand'
|
|
|
13
13
|
// when running from source, this file lives at cli/resolve-assets.ts
|
|
14
14
|
const thisDir = dirname(fileURLToPath(import.meta.url))
|
|
15
15
|
|
|
16
|
-
// walk up to find package root: look for package.json with the
|
|
16
|
+
// walk up to find package root: look for package.json with the peachsim package name
|
|
17
17
|
function findPackageRoot(): string {
|
|
18
18
|
let dir = thisDir
|
|
19
19
|
for (let i = 0; i < 5; i++) {
|
package/cli/run-registry.ts
CHANGED
|
@@ -170,6 +170,14 @@ export async function registerRun(args: RegisterRunArgs): Promise<RegisteredRun
|
|
|
170
170
|
const repo = args.repo ?? (auth.kind === 'github' ? auth.repo : null) ?? provenance.repo
|
|
171
171
|
|
|
172
172
|
try {
|
|
173
|
+
// fault injection for the registration-failure control, shared by both
|
|
174
|
+
// drivers through this choke: fail exactly like a dead endpoint,
|
|
175
|
+
// without touching the network. downstream (warn shape, null return,
|
|
176
|
+
// require-registration exit) is byte-identical to a real failure.
|
|
177
|
+
if (process.env.RNX_INJECT_REGISTRATION_FAILURE === '1') {
|
|
178
|
+
console.warn(' warn: run registration failed: injected failure')
|
|
179
|
+
return null
|
|
180
|
+
}
|
|
173
181
|
const res = await fetch(`${args.origin.replace(/\/$/, '')}/api/sootsim/test-runs`, {
|
|
174
182
|
method: 'POST',
|
|
175
183
|
headers: {
|
package/cli/send-to-box.ts
CHANGED
|
@@ -5,16 +5,16 @@
|
|
|
5
5
|
// it directly; both hand it a way to run a bridge command against the sim that
|
|
6
6
|
// is running the app, and both get the same simulator id and URL back.
|
|
7
7
|
|
|
8
|
-
import { isStorageSnapshot } from '../../
|
|
8
|
+
import { isStorageSnapshot } from '../../peach-engine/src/preview/storage-snapshot-contract.ts'
|
|
9
9
|
import {
|
|
10
10
|
DEFAULT_DEVICE_BY_PLATFORM,
|
|
11
11
|
getRuntimePlatform,
|
|
12
12
|
listDeviceModels,
|
|
13
|
-
} from '../../
|
|
13
|
+
} from '../../peach-engine/src/settings/devices.ts'
|
|
14
14
|
import { toRNXProductionBundleUrl } from '../src/metro-production-bundle.ts'
|
|
15
15
|
import { createCloudBox } from './cloud-client.ts'
|
|
16
|
-
import type { StorageSnapshot } from '../../
|
|
17
|
-
import type { DeviceModel } from '../../
|
|
16
|
+
import type { StorageSnapshot } from '../../peach-engine/src/preview/storage-snapshot-contract.ts'
|
|
17
|
+
import type { DeviceModel } from '../../peach-engine/src/settings/devices.ts'
|
|
18
18
|
import type { BridgeCommandType } from '../src/bridge-contract.ts'
|
|
19
19
|
|
|
20
20
|
export interface SendToBoxOptions {
|
package/cli/ws-bridge.ts
CHANGED
|
@@ -30,6 +30,7 @@ import {
|
|
|
30
30
|
readCurrentSimId,
|
|
31
31
|
saveCurrentSimId,
|
|
32
32
|
} from './current-sim'
|
|
33
|
+
import type { WsBridgeCommand } from '../src/bridge-contract'
|
|
33
34
|
|
|
34
35
|
// --- bridge world resolution ---------------------------------------------
|
|
35
36
|
//
|
|
@@ -319,10 +320,7 @@ export interface WsBridge {
|
|
|
319
320
|
// `opts.timeoutMs` overrides the bridge-wide commandTimeoutMs for a single
|
|
320
321
|
// call — used for fast readiness probes so a dead/unresponsive sim fails in
|
|
321
322
|
// seconds instead of stalling the full command timeout.
|
|
322
|
-
send(
|
|
323
|
-
cmd: { type: string; [k: string]: any },
|
|
324
|
-
opts?: { timeoutMs?: number },
|
|
325
|
-
): Promise<any>
|
|
323
|
+
send(cmd: WsBridgeCommand, opts?: { timeoutMs?: number }): Promise<any>
|
|
326
324
|
listSims(): Promise<BridgeSimInfo[]>
|
|
327
325
|
// a guest reload retires the socket and the same page reconnects under a new
|
|
328
326
|
// or restored id. a caller that pins an id resolves that target here; null
|
|
@@ -993,7 +991,7 @@ export async function evalInBridge<T = unknown>(
|
|
|
993
991
|
code: string,
|
|
994
992
|
opts: { acquireLock?: boolean } = {},
|
|
995
993
|
): Promise<T> {
|
|
996
|
-
const payload:
|
|
994
|
+
const payload: WsBridgeCommand = { type: 'evaluate', code }
|
|
997
995
|
if (opts.acquireLock) payload.acquireLock = true
|
|
998
996
|
return bridge.send(payload) as Promise<T>
|
|
999
997
|
}
|
package/detox/expectations.ts
CHANGED
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
import { dragScrollNode } from './gestures'
|
|
6
6
|
import type { SootSimElement } from './element-types'
|
|
7
7
|
import type { Matcher } from './matchers'
|
|
8
|
-
import type { SootSimNodeInfo } from '@
|
|
8
|
+
import type { SootSimNodeInfo } from '@peach/globals'
|
|
9
9
|
import type { Page } from 'playwright'
|
|
10
10
|
|
|
11
11
|
type FindFn = (matcher: Matcher) => Promise<any>
|
package/detox/gestures.ts
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import { pollUntil } from '../src/poll-until'
|
|
2
|
-
// pull in @
|
|
2
|
+
// pull in @peach/globals so window.__sootsimTest and
|
|
3
3
|
// window.__sootsimFindInteractiveCanvas are typed inside page.evaluate
|
|
4
4
|
// bodies. all sootsim-namespaced globals are declared canonically there —
|
|
5
5
|
// add new entries to packages/rnx-globals/src/index.ts, not here.
|
|
6
|
-
import type {} from '@
|
|
6
|
+
import type {} from '@peach/globals'
|
|
7
7
|
import type { Page } from 'playwright'
|
|
8
8
|
|
|
9
9
|
// shape of the surface-metrics bridge response we care about. the bridge
|
package/detox/index.ts
CHANGED
|
@@ -9,7 +9,8 @@ import { launchReapedChromeScoped } from '../../../scripts/lib/reap-browser'
|
|
|
9
9
|
import {
|
|
10
10
|
readBrowserCompositeCaptureRequest,
|
|
11
11
|
resolveBrowserCompositeCdpClip,
|
|
12
|
-
} from '../../
|
|
12
|
+
} from '../../peach-engine/src/capture/browser-composite'
|
|
13
|
+
import { isAppBootTargetUrl } from '../../peach-engine/src/render-worker/app-load'
|
|
13
14
|
import { pollUntil } from '../src/poll-until'
|
|
14
15
|
import {
|
|
15
16
|
createExpect,
|
|
@@ -177,9 +178,22 @@ async function waitForSootsimSurfaceMetrics(page: Page, timeout = 30000): Promis
|
|
|
177
178
|
)
|
|
178
179
|
}
|
|
179
180
|
|
|
180
|
-
async function waitForSootsimShellReady(
|
|
181
|
+
export async function waitForSootsimShellReady(
|
|
182
|
+
page: Page,
|
|
183
|
+
timeout = 30000,
|
|
184
|
+
): Promise<void> {
|
|
185
|
+
// home boots paint shell content through the shell worker; app boots
|
|
186
|
+
// (direct /rn bundle urls, harness fixtures, deep links) paint through
|
|
187
|
+
// the compositor and shellHost.firstContentPainted never settles. classify
|
|
188
|
+
// with the engine's own boot-target predicate — the same one the shell
|
|
189
|
+
// worker and SootSimCanvas agree on — and await the matching boundary:
|
|
190
|
+
// shell first paint for home boots, the latched boot-stage painted
|
|
191
|
+
// receipt (stamped once tenant, shell, and compositor all present) for
|
|
192
|
+
// app boots.
|
|
193
|
+
// exported for the focused regression test.
|
|
194
|
+
const expectsInitialAppPaint = isAppBootTargetUrl(page.url())
|
|
181
195
|
try {
|
|
182
|
-
await page.evaluate(async (timeoutMs) => {
|
|
196
|
+
await page.evaluate(async ({ timeoutMs, expectsInitialAppPaint }) => {
|
|
183
197
|
type ShellHostReadyBridge = {
|
|
184
198
|
ready?: Promise<unknown>
|
|
185
199
|
firstContentPainted?: Promise<unknown>
|
|
@@ -208,9 +222,6 @@ async function waitForSootsimShellReady(page: Page, timeout = 30000): Promise<vo
|
|
|
208
222
|
|
|
209
223
|
const isTenantOnly =
|
|
210
224
|
new URL(window.location.href).searchParams.get('renderMode') === 'tenant-only'
|
|
211
|
-
const currentUrl = new URL(window.location.href)
|
|
212
|
-
const expectsInitialAppPaint =
|
|
213
|
-
currentUrl.searchParams.has('test') || currentUrl.pathname.includes('/app/')
|
|
214
225
|
let shellHost: ShellHostReadyBridge | null = null
|
|
215
226
|
while (Date.now() < deadline) {
|
|
216
227
|
const candidate = (window as any).SootSim?.bridges?.shellHost
|
|
@@ -229,32 +240,63 @@ async function waitForSootsimShellReady(page: Page, timeout = 30000): Promise<vo
|
|
|
229
240
|
if (shellHost.ready && typeof shellHost.ready.then === 'function') {
|
|
230
241
|
await withTimeout('sootsim shell ready', shellHost.ready)
|
|
231
242
|
}
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
)
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
243
|
+
// the tenant restarts into its matching worker artifact when a bundle's
|
|
244
|
+
// actual mode differs from the booted one (production bundle, no url
|
|
245
|
+
// mode params), and any in-flight tenant call then rejects with
|
|
246
|
+
// 'render host disposed'. the receipt poll below is restart-immune
|
|
247
|
+
// (pure dom read), but the transitions drain is a tenant call, so run
|
|
248
|
+
// the paint boundary plus drain together and re-run them when a
|
|
249
|
+
// restart lands mid-sequence. paint timeouts and transition timeouts
|
|
250
|
+
// are genuine failures and are never retried.
|
|
251
|
+
const isRenderHostDisposed = (error: unknown): boolean =>
|
|
252
|
+
error instanceof Error && error.message.includes('render host disposed')
|
|
253
|
+
for (;;) {
|
|
254
|
+
try {
|
|
255
|
+
if (expectsInitialAppPaint) {
|
|
256
|
+
while (Date.now() < deadline) {
|
|
257
|
+
if (
|
|
258
|
+
document.documentElement.dataset.sootsimBootStage === 'painted'
|
|
259
|
+
) {
|
|
260
|
+
break
|
|
261
|
+
}
|
|
262
|
+
await sleep(50)
|
|
263
|
+
}
|
|
264
|
+
if (document.documentElement.dataset.sootsimBootStage !== 'painted') {
|
|
265
|
+
throw new Error(
|
|
266
|
+
`sootsim app first content paint timed out after ${timeoutMs}ms`,
|
|
267
|
+
)
|
|
268
|
+
}
|
|
269
|
+
} else if (
|
|
270
|
+
shellHost.firstContentPainted &&
|
|
271
|
+
typeof shellHost.firstContentPainted.then === 'function'
|
|
272
|
+
) {
|
|
273
|
+
await withTimeout(
|
|
274
|
+
'sootsim shell first content paint',
|
|
275
|
+
shellHost.firstContentPainted,
|
|
276
|
+
)
|
|
277
|
+
}
|
|
242
278
|
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
279
|
+
const waitForScreenTransitions = (window as any).__sootsimTest
|
|
280
|
+
?.waitForScreenTransitions
|
|
281
|
+
if (typeof waitForScreenTransitions === 'function') {
|
|
282
|
+
const transitionResult = await waitForScreenTransitions({
|
|
283
|
+
timeoutMs: Math.min(remaining(), 3000),
|
|
284
|
+
settleMs: 64,
|
|
285
|
+
startWindowMs: 700,
|
|
286
|
+
})
|
|
287
|
+
if (transitionResult?.timedOut === true) {
|
|
288
|
+
throw new Error(
|
|
289
|
+
`sootsim app launch transitions timed out: ${JSON.stringify(transitionResult)}`,
|
|
290
|
+
)
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
break
|
|
294
|
+
} catch (error) {
|
|
295
|
+
if (!isRenderHostDisposed(error) || Date.now() >= deadline) throw error
|
|
296
|
+
await sleep(100)
|
|
255
297
|
}
|
|
256
298
|
}
|
|
257
|
-
}, timeout)
|
|
299
|
+
}, { timeoutMs: timeout, expectsInitialAppPaint })
|
|
258
300
|
} catch (error) {
|
|
259
301
|
throw await describeSootsimBridgeFailure(page, error)
|
|
260
302
|
}
|
package/detox/jest-preset.cjs
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
// jest preset for running existing detox suites against sootsim.
|
|
2
2
|
//
|
|
3
|
-
// users drop `preset: '
|
|
3
|
+
// users drop `preset: 'peachsim/detox'` into their jest config
|
|
4
4
|
// (or `rnx-detox.config.cjs`) and their `import { by, device, element,
|
|
5
5
|
// expect, waitFor } from 'detox'` calls resolve to the sootsim driver.
|
|
6
6
|
|
|
@@ -9,8 +9,8 @@ const path = require('path')
|
|
|
9
9
|
// resolve the published driver entry so the mapping works from any cwd.
|
|
10
10
|
// `require.resolve('sootsim/detox')` honours the subpath export in the
|
|
11
11
|
// sootsim package.json.
|
|
12
|
-
const DRIVER_ENTRY = require.resolve('
|
|
13
|
-
const PACKAGE_ROOT = path.dirname(require.resolve('
|
|
12
|
+
const DRIVER_ENTRY = require.resolve('peachsim/detox')
|
|
13
|
+
const PACKAGE_ROOT = path.dirname(require.resolve('peachsim/package.json'))
|
|
14
14
|
|
|
15
15
|
/** @type {import('@jest/types').Config.InitialOptions} */
|
|
16
16
|
module.exports = {
|
|
@@ -15,7 +15,7 @@
|
|
|
15
15
|
// setup failure from a behavior failure. no-op unless the run asks for it.
|
|
16
16
|
require('./record-block-execution.cjs')
|
|
17
17
|
|
|
18
|
-
const { cleanup } = require('
|
|
18
|
+
const { cleanup } = require('peachsim/detox')
|
|
19
19
|
|
|
20
20
|
afterAll(async () => {
|
|
21
21
|
await cleanup()
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
/*! rnx v0.1.
|
|
1
|
+
/*! rnx v0.1.570 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
|
|
2
2
|
let __sootsim_import_meta_url = ''; try { __sootsim_import_meta_url = require('url').pathToFileURL(__filename).href; } catch {}
|
|
3
3
|
"use strict";
|
|
4
4
|
var __create = Object.create;
|
|
@@ -56,7 +56,7 @@ var import_node_url = require("node:url");
|
|
|
56
56
|
var name = "rnx";
|
|
57
57
|
var rnxPublicBrand = Object.freeze({
|
|
58
58
|
name,
|
|
59
|
-
packageName: "
|
|
59
|
+
packageName: "peachsim",
|
|
60
60
|
commandName: "rnx",
|
|
61
61
|
domain: "rnxsim.com",
|
|
62
62
|
origin: "https://rnxsim.com"
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
/*! rnx v0.1.
|
|
1
|
+
/*! rnx v0.1.570 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
|
|
2
2
|
let __sootsim_import_meta_url = ''; try { __sootsim_import_meta_url = require('url').pathToFileURL(__filename).href; } catch {}
|
|
3
3
|
"use strict";
|
|
4
4
|
var __defProp = Object.defineProperty;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
/*! rnx v0.1.
|
|
1
|
+
/*! rnx v0.1.570 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
|
|
2
2
|
let __sootsim_import_meta_url = ''; try { __sootsim_import_meta_url = require('url').pathToFileURL(__filename).href; } catch {}
|
|
3
3
|
"use strict";
|
|
4
4
|
var __defProp = Object.defineProperty;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
/*! rnx v0.1.
|
|
1
|
+
/*! rnx v0.1.570 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
|
|
2
2
|
let __sootsim_import_meta_url = ''; try { __sootsim_import_meta_url = require('url').pathToFileURL(__filename).href; } catch {}
|
|
3
3
|
"use strict";
|
|
4
4
|
var __create = Object.create;
|
|
@@ -61,7 +61,7 @@ var import_node_url = require("node:url");
|
|
|
61
61
|
var name = "rnx";
|
|
62
62
|
var rnxPublicBrand = Object.freeze({
|
|
63
63
|
name,
|
|
64
|
-
packageName: "
|
|
64
|
+
packageName: "peachsim",
|
|
65
65
|
commandName: "rnx",
|
|
66
66
|
domain: "rnxsim.com",
|
|
67
67
|
origin: "https://rnxsim.com"
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
/*! rnx v0.1.
|
|
1
|
+
/*! rnx v0.1.570 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
|
|
2
2
|
let __sootsim_import_meta_url = ''; try { __sootsim_import_meta_url = require('url').pathToFileURL(__filename).href; } catch {}
|
|
3
3
|
"use strict";
|
|
4
4
|
var __create = Object.create;
|
|
@@ -89,7 +89,7 @@ var init_optional_demo_registry = __esm({
|
|
|
89
89
|
import_node_fs = require("node:fs");
|
|
90
90
|
import_node_path2 = require("node:path");
|
|
91
91
|
import_node_url = require("node:url");
|
|
92
|
-
REGISTRY_REPO_PATH = "packages/
|
|
92
|
+
REGISTRY_REPO_PATH = "packages/peachsim/scripts/demo-app-registry.ts";
|
|
93
93
|
MAX_ANCESTOR_STEPS = 8;
|
|
94
94
|
cached = null;
|
|
95
95
|
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
/*! rnx v0.1.
|
|
1
|
+
/*! rnx v0.1.570 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
|
|
2
2
|
let __sootsim_import_meta_url = ''; try { __sootsim_import_meta_url = require('url').pathToFileURL(__filename).href; } catch {}
|
|
3
3
|
"use strict";
|
|
4
4
|
var __create = Object.create;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
/*! rnx v0.1.
|
|
1
|
+
/*! rnx v0.1.570 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
|
|
2
2
|
let __sootsim_import_meta_url = ''; try { __sootsim_import_meta_url = require('url').pathToFileURL(__filename).href; } catch {}
|
|
3
3
|
"use strict";
|
|
4
4
|
var __defProp = Object.defineProperty;
|
package/dist-lib/beta.cjs
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
/*! rnx v0.1.
|
|
1
|
+
/*! rnx v0.1.570 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
|
|
2
2
|
let __sootsim_import_meta_url = ''; try { __sootsim_import_meta_url = require('url').pathToFileURL(__filename).href; } catch {}
|
|
3
3
|
"use strict";
|
|
4
4
|
var __defProp = Object.defineProperty;
|
package/dist-lib/beta.mjs
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
/*! rnx v0.1.
|
|
1
|
+
/*! rnx v0.1.570 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
|
|
2
2
|
let __sootsim_import_meta_url = ''; try { __sootsim_import_meta_url = require('url').pathToFileURL(__filename).href; } catch {}
|
|
3
3
|
"use strict";
|
|
4
4
|
var __defProp = Object.defineProperty;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
/*! rnx v0.1.
|
|
1
|
+
/*! rnx v0.1.570 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
|
|
2
2
|
let __sootsim_import_meta_url = ''; try { __sootsim_import_meta_url = require('url').pathToFileURL(__filename).href; } catch {}
|
|
3
3
|
"use strict";
|
|
4
4
|
var __defProp = Object.defineProperty;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
/*! rnx v0.1.
|
|
1
|
+
/*! rnx v0.1.570 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
|
|
2
2
|
let __sootsim_import_meta_url = ''; try { __sootsim_import_meta_url = require('url').pathToFileURL(__filename).href; } catch {}
|
|
3
3
|
"use strict";
|
|
4
4
|
var __defProp = Object.defineProperty;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
/*! rnx v0.1.
|
|
1
|
+
/*! rnx v0.1.570 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
|
|
2
2
|
let __sootsim_import_meta_url = ''; try { __sootsim_import_meta_url = require('url').pathToFileURL(__filename).href; } catch {}
|
|
3
3
|
"use strict";
|
|
4
4
|
var __defProp = Object.defineProperty;
|
|
@@ -25,6 +25,7 @@ __export(capture_contract_exports, {
|
|
|
25
25
|
RNX_SCREEN_CAPTURE_VERSION: () => RNX_SCREEN_CAPTURE_VERSION,
|
|
26
26
|
isRnxCaptureRegionAtlas: () => isRnxCaptureRegionAtlas,
|
|
27
27
|
isRnxScreenCapture: () => isRnxScreenCapture,
|
|
28
|
+
packRnxCaptureRegions: () => packRnxCaptureRegions,
|
|
28
29
|
rnxCaptureRasterRegions: () => rnxCaptureRasterRegions,
|
|
29
30
|
rnxRasterFrames: () => rnxRasterFrames,
|
|
30
31
|
selectRnxRasterNodeIds: () => selectRnxRasterNodeIds
|
|
@@ -39,7 +40,7 @@ function selectRnxRasterNodeIds(tree) {
|
|
|
39
40
|
if (!visibleFrame || visibleFrame.width <= 0 || visibleFrame.height <= 0) continue;
|
|
40
41
|
if (node.type === "masked-view" || node.type === "text" && typeof node.text === "string" && (new RegExp("\\p{Emoji_Presentation}", "u").test(node.text) || new RegExp("\\uFE0F|\\u200D|\\u20E3|\\p{Emoji_Modifier}|\\p{Regional_Indicator}", "u").test(
|
|
41
42
|
node.text
|
|
42
|
-
)) || node.style._liquidGlass !== void 0 || node.style._liquidGlassEdge !== void 0) {
|
|
43
|
+
)) || node.style._liquidGlass !== void 0 || node.style._liquidGlassEdge !== void 0 || node.style._liquidGlassMaterialTint !== void 0) {
|
|
43
44
|
unsupported.add(node.nodeId);
|
|
44
45
|
}
|
|
45
46
|
}
|
|
@@ -138,6 +139,95 @@ function rnxCaptureRasterRegions(capture) {
|
|
|
138
139
|
return frame ? [{ key: String(nodeId), nodeId, frame }] : [];
|
|
139
140
|
});
|
|
140
141
|
}
|
|
142
|
+
function packRnxCaptureRegions(regions, pixelRatio, surface) {
|
|
143
|
+
const physical = regions.map((region) => {
|
|
144
|
+
const left = Math.floor(region.frame.x * pixelRatio);
|
|
145
|
+
const top = Math.floor(region.frame.y * pixelRatio);
|
|
146
|
+
const right = Math.ceil((region.frame.x + region.frame.width) * pixelRatio);
|
|
147
|
+
const bottom = Math.ceil((region.frame.y + region.frame.height) * pixelRatio);
|
|
148
|
+
if (left < 0 || top < 0 || right > surface.pixelWidth || bottom > surface.pixelHeight || right <= left || bottom <= top) {
|
|
149
|
+
throw new Error(`capture region ${region.key} is outside the app surface`);
|
|
150
|
+
}
|
|
151
|
+
return {
|
|
152
|
+
key: region.key,
|
|
153
|
+
nodeId: region.nodeId,
|
|
154
|
+
frame: {
|
|
155
|
+
x: left / pixelRatio,
|
|
156
|
+
y: top / pixelRatio,
|
|
157
|
+
width: (right - left) / pixelRatio,
|
|
158
|
+
height: (bottom - top) / pixelRatio
|
|
159
|
+
},
|
|
160
|
+
sourceX: left,
|
|
161
|
+
sourceY: top,
|
|
162
|
+
pixelWidth: right - left,
|
|
163
|
+
pixelHeight: bottom - top
|
|
164
|
+
};
|
|
165
|
+
});
|
|
166
|
+
const largestWidth = Math.max(...physical.map((region) => region.pixelWidth));
|
|
167
|
+
const totalArea = physical.reduce(
|
|
168
|
+
(area, region) => area + region.pixelWidth * region.pixelHeight,
|
|
169
|
+
0
|
|
170
|
+
);
|
|
171
|
+
if (totalArea > 4194304) {
|
|
172
|
+
throw new Error("capture region pixels exceed the atlas budget");
|
|
173
|
+
}
|
|
174
|
+
const candidates = [
|
|
175
|
+
largestWidth,
|
|
176
|
+
Math.max(largestWidth, Math.ceil(Math.sqrt(totalArea))),
|
|
177
|
+
Math.max(largestWidth, surface.pixelWidth),
|
|
178
|
+
4096
|
|
179
|
+
];
|
|
180
|
+
const ordered = [...physical].sort(
|
|
181
|
+
(left, right) => right.pixelHeight - left.pixelHeight || right.pixelWidth - left.pixelWidth || left.key.localeCompare(right.key)
|
|
182
|
+
);
|
|
183
|
+
let best = null;
|
|
184
|
+
for (const candidate of [...new Set(candidates)]) {
|
|
185
|
+
if (candidate > 4096) continue;
|
|
186
|
+
let x = 0;
|
|
187
|
+
let y = 0;
|
|
188
|
+
let rowHeight = 0;
|
|
189
|
+
let usedWidth = 0;
|
|
190
|
+
const packed = [];
|
|
191
|
+
for (const region of ordered) {
|
|
192
|
+
if (x > 0 && x + region.pixelWidth > candidate) {
|
|
193
|
+
y += rowHeight;
|
|
194
|
+
x = 0;
|
|
195
|
+
rowHeight = 0;
|
|
196
|
+
}
|
|
197
|
+
packed.push({ ...region, atlasX: x, atlasY: y });
|
|
198
|
+
x += region.pixelWidth;
|
|
199
|
+
rowHeight = Math.max(rowHeight, region.pixelHeight);
|
|
200
|
+
usedWidth = Math.max(usedWidth, x);
|
|
201
|
+
}
|
|
202
|
+
const height2 = y + rowHeight;
|
|
203
|
+
if (height2 > 4096 || usedWidth * height2 > 4194304) continue;
|
|
204
|
+
const result = { width: usedWidth, height: height2, regions: packed };
|
|
205
|
+
if (!best || result.width * result.height < best.width * best.height) best = result;
|
|
206
|
+
}
|
|
207
|
+
if (!best) throw new Error("capture regions cannot fit in one bounded atlas");
|
|
208
|
+
const width = best.width / pixelRatio;
|
|
209
|
+
const height = best.height / pixelRatio;
|
|
210
|
+
return {
|
|
211
|
+
width,
|
|
212
|
+
height,
|
|
213
|
+
pixelWidth: best.width,
|
|
214
|
+
pixelHeight: best.height,
|
|
215
|
+
regions: best.regions,
|
|
216
|
+
atlasRegions: best.regions.map((region) => ({
|
|
217
|
+
key: region.key,
|
|
218
|
+
nodeId: region.nodeId,
|
|
219
|
+
frame: region.frame,
|
|
220
|
+
// a region flush with the far edge is anchored to the logical edge so
|
|
221
|
+
// its source never runs past the atlas by a rounding hair
|
|
222
|
+
source: {
|
|
223
|
+
x: region.atlasX + region.pixelWidth === best.width ? width - region.frame.width : region.atlasX / pixelRatio,
|
|
224
|
+
y: region.atlasY + region.pixelHeight === best.height ? height - region.frame.height : region.atlasY / pixelRatio,
|
|
225
|
+
width: region.frame.width,
|
|
226
|
+
height: region.frame.height
|
|
227
|
+
}
|
|
228
|
+
}))
|
|
229
|
+
};
|
|
230
|
+
}
|
|
141
231
|
function captureRecord(value) {
|
|
142
232
|
return value !== null && typeof value === "object" && !Array.isArray(value);
|
|
143
233
|
}
|
|
@@ -193,7 +283,7 @@ function isRnxScreenCapture(value) {
|
|
|
193
283
|
return captureRecord(tree.viewport) && captureInteger(tree.viewport.width) && tree.viewport.width > 0 && captureInteger(tree.viewport.height) && tree.viewport.height > 0 && captureInteger(tree.rootNodeId) && (tree.commitRevision === void 0 || captureInteger(tree.commitRevision)) && Array.isArray(tree.nodes) && tree.nodes.every(captureNode) && (visual === void 0 || visual.kind === "png" && (visual.producer === "headless-cpu-app-surface" || visual.producer === "browser-compositor-app-surface") && typeof visual.dataUri === "string" && visual.dataUri.startsWith("data:image/png;base64,") && captureInteger(visual.pixelWidth) && visual.pixelWidth > 0 && captureInteger(visual.pixelHeight) && visual.pixelHeight > 0);
|
|
194
284
|
}
|
|
195
285
|
function isRnxCaptureRegionAtlas(value) {
|
|
196
|
-
if (!captureRecord(value) || value.kind !== "png-atlas" || value.producer !== "headless-cpu-app-surface" || typeof value.dataUri !== "string" || !value.dataUri.startsWith("data:image/png;base64,") || !captureNumber(value.width) || value.width <= 0 || !captureNumber(value.height) || value.height <= 0 || !captureInteger(value.pixelWidth) || value.pixelWidth <= 0 || !captureInteger(value.pixelHeight) || value.pixelHeight <= 0 || !Array.isArray(value.regions) || value.regions.length === 0 || value.regions.length > 256) {
|
|
286
|
+
if (!captureRecord(value) || value.kind !== "png-atlas" || value.producer !== "headless-cpu-app-surface" && value.producer !== "browser-compositor-app-surface" || typeof value.dataUri !== "string" || !value.dataUri.startsWith("data:image/png;base64,") || !captureNumber(value.width) || value.width <= 0 || !captureNumber(value.height) || value.height <= 0 || !captureInteger(value.pixelWidth) || value.pixelWidth <= 0 || !captureInteger(value.pixelHeight) || value.pixelHeight <= 0 || !Array.isArray(value.regions) || value.regions.length === 0 || value.regions.length > 256) {
|
|
197
287
|
return false;
|
|
198
288
|
}
|
|
199
289
|
const scale = value.pixelWidth / value.width;
|
|
@@ -214,6 +304,7 @@ function isRnxCaptureRegionAtlas(value) {
|
|
|
214
304
|
RNX_SCREEN_CAPTURE_VERSION,
|
|
215
305
|
isRnxCaptureRegionAtlas,
|
|
216
306
|
isRnxScreenCapture,
|
|
307
|
+
packRnxCaptureRegions,
|
|
217
308
|
rnxCaptureRasterRegions,
|
|
218
309
|
rnxRasterFrames,
|
|
219
310
|
selectRnxRasterNodeIds
|