peachsim 0.1.659 → 0.1.661

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.
Files changed (96) hide show
  1. package/README.md +10 -15
  2. package/cli/cli-update.ts +53 -21
  3. package/cli/commands/compat.ts +6 -2
  4. package/cli/commands/control.ts +12 -3
  5. package/cli/commands/inspect.ts +16 -14
  6. package/cli/commands/upgrade.ts +4 -2
  7. package/cli/current-sim.ts +4 -0
  8. package/cli/drivers/linux-browser-libs.lock.json +1088 -0
  9. package/cli/drivers/linux-browser-libs.ts +182 -0
  10. package/cli/drivers/playwright-provisioning.ts +20 -98
  11. package/cli/drivers/playwright-sim-host.ts +9 -0
  12. package/cli/drivers/playwright.ts +2 -1
  13. package/dist-lib/agent-daemon-client.cjs +1 -1
  14. package/dist-lib/agent-events.cjs +1 -1
  15. package/dist-lib/agent-identity.cjs +1 -1
  16. package/dist-lib/agent-sessions.cjs +1 -1
  17. package/dist-lib/attached-projects.cjs +1 -1
  18. package/dist-lib/auth/shared-session.cjs +1 -1
  19. package/dist-lib/backend-origin.cjs +18 -1
  20. package/dist-lib/beta.cjs +1 -1
  21. package/dist-lib/beta.mjs +1 -1
  22. package/dist-lib/bridge-constants.cjs +1 -1
  23. package/dist-lib/bridge-contract-input.cjs +1 -1
  24. package/dist-lib/bridge-contract-input.mjs +1 -1
  25. package/dist-lib/bridge-contract.cjs +1 -1
  26. package/dist-lib/bridge-contract.mjs +1 -1
  27. package/dist-lib/capture-contract.cjs +1 -1
  28. package/dist-lib/capture-contract.mjs +1 -1
  29. package/dist-lib/cli-constants.cjs +1 -1
  30. package/dist-lib/cloud-contract.cjs +1 -1
  31. package/dist-lib/cloud-contract.mjs +1 -1
  32. package/dist-lib/cloud-test-args.cjs +1 -1
  33. package/dist-lib/cloud-test-args.mjs +1 -1
  34. package/dist-lib/cloud.cjs +1 -1
  35. package/dist-lib/cloud.mjs +1 -1
  36. package/dist-lib/config.cjs +1 -1
  37. package/dist-lib/detox/index.cjs +1 -1
  38. package/dist-lib/dev-bundle-resolution.cjs +1 -1
  39. package/dist-lib/home-paths.cjs +1 -1
  40. package/dist-lib/host/bridge-host.cjs +45 -41
  41. package/dist-lib/host/fetch-proxy-handler.cjs +1 -1
  42. package/dist-lib/host/fetch-proxy-overrides.cjs +1 -1
  43. package/dist-lib/host/fetch-proxy-overrides.mjs +1 -1
  44. package/dist-lib/host/replacement-module-handler.cjs +1 -1
  45. package/dist-lib/host/websocket-proxy.cjs +1 -1
  46. package/dist-lib/index.cjs +5 -1
  47. package/dist-lib/jump-to-source-babel.cjs +1 -1
  48. package/dist-lib/jump-to-source-native.cjs +1 -1
  49. package/dist-lib/kotlin-host.cjs +8 -3
  50. package/dist-lib/menu.cjs +1 -1
  51. package/dist-lib/menu.mjs +1 -1
  52. package/dist-lib/metro-fingerprint-registry.cjs +1 -1
  53. package/dist-lib/metro-fingerprint-registry.mjs +1 -1
  54. package/dist-lib/metro-production-bundle.cjs +1 -1
  55. package/dist-lib/metro-production-bundle.mjs +1 -1
  56. package/dist-lib/metro.cjs +1 -1
  57. package/dist-lib/native-app-identity.cjs +1 -1
  58. package/dist-lib/native-app-identity.mjs +1 -1
  59. package/dist-lib/plist.cjs +1 -1
  60. package/dist-lib/plist.mjs +1 -1
  61. package/dist-lib/profiles.cjs +1 -1
  62. package/dist-lib/public-brand.cjs +1 -1
  63. package/dist-lib/react-native-host-modules.cjs +1 -1
  64. package/dist-lib/react-native-host-modules.mjs +1 -1
  65. package/dist-lib/render-mode.cjs +1 -1
  66. package/dist-lib/scripts/dev-server-scanner.cjs +1 -1
  67. package/dist-lib/sdk.cjs +1 -1
  68. package/dist-lib/sdk.mjs +1 -1
  69. package/dist-lib/skills.cjs +1339 -180
  70. package/dist-lib/swift-compile.cjs +1 -1
  71. package/dist-lib/swift-compile.mjs +1 -1
  72. package/dist-lib/swift-package.cjs +1 -1
  73. package/dist-lib/swift-package.mjs +1 -1
  74. package/dist-lib/swift-submit.cjs +1 -1
  75. package/dist-lib/swift-submit.mjs +1 -1
  76. package/dist-lib/swift.cjs +1 -1
  77. package/dist-lib/vite-swift.cjs +1 -1
  78. package/dist-lib/vite.cjs +1 -1
  79. package/kotlin/AppSources/android/app/Activity.kt +48 -3
  80. package/kotlin/AppSources/android/app/Application.kt +4 -0
  81. package/kotlin/AppSources/android/content/res/Configuration.kt +4 -0
  82. package/kotlin/AppSources/android/content/res/Resources.kt +3 -1
  83. package/kotlin/AppSources/android/view/Window.kt +15 -9
  84. package/kotlin/AppSources/androidx/activity/ComponentActivity.kt +36 -3
  85. package/kotlin/AppSources/dev/peach/app/PeachApp.kt +233 -86
  86. package/kotlin/ComposeSources/PeachComposeHost.kt +15 -3
  87. package/kotlin/fork/README.md +1 -0
  88. package/kotlin/fork/compose-worker-globals.patch +26 -0
  89. package/kotlin/fork/fork.json +1 -1
  90. package/kotlin/toolchain.json +3 -3
  91. package/package.json +2 -1
  92. package/src/android-theme.ts +14 -0
  93. package/src/backend-origin.ts +25 -0
  94. package/src/host/bridge-host.ts +24 -37
  95. package/src/kotlin-app.ts +29 -4
  96. package/src/kotlin-host.ts +7 -2
@@ -0,0 +1,182 @@
1
+ // the shared libraries the linux browser links. a host that lacks any of them
2
+ // gets peach's own bundle of ubuntu 20.04's builds (scripts/linux-browser-libs.ts
3
+ // pins, builds and publishes it), unpacked once into the peach home and put on
4
+ // the browser's library path, so no host needs a package manager.
5
+
6
+ import { spawnSync } from 'node:child_process'
7
+ import { existsSync } from 'node:fs'
8
+ import { join } from 'node:path'
9
+ import { installVerifiedTarball } from '@contrast/runtime-delivery'
10
+ import { rnxHomeDir } from '../../src/home-paths'
11
+ import { isRecord } from '../../src/is-record'
12
+ import { DEFAULT_CLI_RELEASE_ORIGIN } from '../cli-update'
13
+ import lockJson from './linux-browser-libs.lock.json' with { type: 'json' }
14
+
15
+ // the debian package providing each shared library chromium and its headless
16
+ // shell link (readelf NEEDED, linux x64 and arm64), apart from glibc's own.
17
+ export const LIBRARY_PACKAGES: Record<string, string> = {
18
+ 'libasound.so.2': 'libasound2',
19
+ 'libatk-1.0.so.0': 'libatk1.0-0',
20
+ 'libatk-bridge-2.0.so.0': 'libatk-bridge2.0-0',
21
+ 'libatspi.so.0': 'libatspi2.0-0',
22
+ 'libcairo.so.2': 'libcairo2',
23
+ 'libcups.so.2': 'libcups2',
24
+ 'libdbus-1.so.3': 'libdbus-1-3',
25
+ 'libexpat.so.1': 'libexpat1',
26
+ 'libgbm.so.1': 'libgbm1',
27
+ 'libgio-2.0.so.0': 'libglib2.0-0',
28
+ 'libglib-2.0.so.0': 'libglib2.0-0',
29
+ 'libgobject-2.0.so.0': 'libglib2.0-0',
30
+ 'libnspr4.so': 'libnspr4',
31
+ 'libnss3.so': 'libnss3',
32
+ 'libnssutil3.so': 'libnss3',
33
+ 'libpango-1.0.so.0': 'libpango-1.0-0',
34
+ 'libsmime3.so': 'libnss3',
35
+ 'libudev.so.1': 'libudev1',
36
+ 'libX11.so.6': 'libx11-6',
37
+ 'libxcb.so.1': 'libxcb1',
38
+ 'libXcomposite.so.1': 'libxcomposite1',
39
+ 'libXdamage.so.1': 'libxdamage1',
40
+ 'libXext.so.6': 'libxext6',
41
+ 'libXfixes.so.3': 'libxfixes3',
42
+ 'libxkbcommon.so.0': 'libxkbcommon0',
43
+ 'libXrandr.so.2': 'libxrandr2',
44
+ }
45
+
46
+ export const LINUX_BROWSER_LIBS_ARCHES = ['x64', 'arm64'] as const
47
+ export type LinuxBrowserLibsArch = (typeof LINUX_BROWSER_LIBS_ARCHES)[number]
48
+ export const LINUX_BROWSER_LIBS_MARKER = 'peach-linux-libs.json'
49
+
50
+ export type LinuxBrowserLibsPackage = {
51
+ name: string
52
+ version: string
53
+ url: string
54
+ sha256: string
55
+ size: number
56
+ }
57
+
58
+ export type LinuxBrowserLibsLock = {
59
+ id: string
60
+ suite: string
61
+ arches: Record<LinuxBrowserLibsArch, LinuxBrowserLibsPackage[]>
62
+ }
63
+
64
+ function isLockPackage(value: unknown): value is LinuxBrowserLibsPackage {
65
+ return (
66
+ isRecord(value) &&
67
+ typeof value.name === 'string' &&
68
+ typeof value.version === 'string' &&
69
+ typeof value.url === 'string' &&
70
+ typeof value.sha256 === 'string' &&
71
+ /^[a-f0-9]{64}$/.test(value.sha256) &&
72
+ typeof value.size === 'number'
73
+ )
74
+ }
75
+
76
+ export function parseLinuxBrowserLibsLock(value: unknown): LinuxBrowserLibsLock {
77
+ if (
78
+ !isRecord(value) ||
79
+ typeof value.id !== 'string' ||
80
+ !/^[a-f0-9]{16}$/.test(value.id) ||
81
+ typeof value.suite !== 'string' ||
82
+ !isRecord(value.arches)
83
+ ) {
84
+ throw new Error('invalid linux browser library lock')
85
+ }
86
+ const { x64, arm64 } = value.arches
87
+ if (
88
+ !Array.isArray(x64) ||
89
+ !x64.every(isLockPackage) ||
90
+ !Array.isArray(arm64) ||
91
+ !arm64.every(isLockPackage)
92
+ ) {
93
+ throw new Error('invalid linux browser library lock packages')
94
+ }
95
+ return { id: value.id, suite: value.suite, arches: { x64, arm64 } }
96
+ }
97
+
98
+ /** the bucket path under the cli release origin. */
99
+ export function linuxBrowserLibsKey(id: string, arch: LinuxBrowserLibsArch): string {
100
+ return `linux-libs/${id}/${arch}.tar.gz`
101
+ }
102
+
103
+ function missingLibraries(executablePath: string, libraryPath: string | null): string[] {
104
+ const ldd = spawnSync('ldd', [executablePath], {
105
+ encoding: 'utf8',
106
+ timeout: 30_000,
107
+ env: libraryPath
108
+ ? {
109
+ ...process.env,
110
+ LD_LIBRARY_PATH: [libraryPath, process.env.LD_LIBRARY_PATH]
111
+ .filter(Boolean)
112
+ .join(':'),
113
+ }
114
+ : process.env,
115
+ })
116
+ return [...(ldd.stdout ?? '').matchAll(/^\s*(\S+) => not found$/gm)].map(
117
+ (match) => match[1]!,
118
+ )
119
+ }
120
+
121
+ export type LinuxBrowserLibrariesResult =
122
+ | { ok: true; libraryPath: string | null }
123
+ | { ok: false; message: string }
124
+
125
+ /** a linux browser whose shared libraries are missing exits 127 before
126
+ * playwright can say why. when any is missing, install peach's bundle and
127
+ * return the library path the browser must launch with. */
128
+ export async function ensureLinuxBrowserLibraries(
129
+ executablePath: string,
130
+ browser: string,
131
+ ): Promise<LinuxBrowserLibrariesResult> {
132
+ if (process.platform !== 'linux') return { ok: true, libraryPath: null }
133
+ const missing = missingLibraries(executablePath, null)
134
+ if (!missing.length) return { ok: true, libraryPath: null }
135
+ const arch = LINUX_BROWSER_LIBS_ARCHES.find((candidate) => candidate === process.arch)
136
+ if (!arch) {
137
+ return {
138
+ ok: false,
139
+ message: `${browser} at ${executablePath} cannot start: this system is missing shared libraries it links (${missing.join(', ')}), and peach has no library bundle for ${process.arch}.`,
140
+ }
141
+ }
142
+
143
+ const lock = parseLinuxBrowserLibsLock(lockJson)
144
+ const dir = join(rnxHomeDir(), 'linux-libs', `${lock.id}-${arch}`)
145
+ if (!existsSync(join(dir, LINUX_BROWSER_LIBS_MARKER))) {
146
+ const url = `${DEFAULT_CLI_RELEASE_ORIGIN}/${linuxBrowserLibsKey(lock.id, arch)}`
147
+ process.stderr.write(
148
+ `peach: this system lacks ${missing.length} shared libraries ${browser} links; downloading them…\n`,
149
+ )
150
+ try {
151
+ const sidecar = await fetch(`${url}.sha256`)
152
+ if (!sidecar.ok)
153
+ throw new Error(`${url}.sha256: ${sidecar.status} ${sidecar.statusText}`)
154
+ await installVerifiedTarball({
155
+ url,
156
+ digest: {
157
+ algorithm: 'sha256',
158
+ encoding: 'hex',
159
+ value: (await sidecar.text()).trim(),
160
+ },
161
+ destDir: dir,
162
+ installedMarker: LINUX_BROWSER_LIBS_MARKER,
163
+ label: `linux browser libraries ${lock.id} (${arch})`,
164
+ })
165
+ } catch (error) {
166
+ return {
167
+ ok: false,
168
+ message: `${browser} at ${executablePath} cannot start: this system is missing shared libraries it links (${missing.join(', ')}), and downloading them failed: ${error instanceof Error ? error.message : String(error)}`,
169
+ }
170
+ }
171
+ }
172
+
173
+ const libraryPath = [join(dir, 'lib'), join(dir, 'lib', 'nss')].join(':')
174
+ const stillMissing = missingLibraries(executablePath, libraryPath)
175
+ if (stillMissing.length) {
176
+ return {
177
+ ok: false,
178
+ message: `${browser} at ${executablePath} cannot start: neither this system nor peach's library bundle (${dir}) provides ${stillMissing.join(', ')}.`,
179
+ }
180
+ }
181
+ return { ok: true, libraryPath }
182
+ }
@@ -7,6 +7,7 @@ import { playwrightCoreDir } from '../../src/home-paths'
7
7
  import { isRecord } from '../../src/is-record'
8
8
  import { RNX_INTERNAL_CHILDREN, RNX_INTERNAL_COMMAND } from '../internal-child'
9
9
  import { rnxSelfInvocation } from '../self-invocation'
10
+ import { ensureLinuxBrowserLibraries } from './linux-browser-libs'
10
11
  import { PINNED_PLAYWRIGHT_CORE } from './playwright-core-pin'
11
12
 
12
13
  export interface ResolvedPlaywright {
@@ -25,6 +26,8 @@ export type PlaywrightBrowserProvisioningResult =
25
26
  ok: true
26
27
  executablePath: string
27
28
  installed: boolean
29
+ // the shared library path the browser must launch with, when the system lacks some
30
+ libraryPath: string | null
28
31
  }
29
32
  | {
30
33
  ok: false
@@ -150,20 +153,25 @@ function probeBrowser(
150
153
  }
151
154
  }
152
155
 
153
- export function ensurePlaywrightBrowser(
156
+ export async function ensurePlaywrightBrowser(
154
157
  resolved: ResolvedPlaywright,
155
158
  headless: boolean,
156
159
  env: NodeJS.ProcessEnv = process.env,
157
- ): PlaywrightBrowserProvisioningResult {
160
+ ): Promise<PlaywrightBrowserProvisioningResult> {
158
161
  const headlessShell = headless && process.platform !== 'darwin'
159
162
  const probeEnv = { ...env, SOOTSIM_PW_HEADLESS_SHELL: headlessShell ? '1' : '0' }
160
163
  const browser = headlessShell ? 'chromium-headless-shell' : 'chromium'
161
164
  const before = probeBrowser(resolved, probeEnv)
162
165
  if (!before.ok) return before
163
166
  if (before.exists) {
164
- const missing = missingLibrariesMessage(before.executablePath, browser)
165
- if (missing) return { ok: false, message: missing }
166
- return { ok: true, executablePath: before.executablePath, installed: false }
167
+ const libraries = await ensureLinuxBrowserLibraries(before.executablePath, browser)
168
+ if (!libraries.ok) return libraries
169
+ return {
170
+ ok: true,
171
+ executablePath: before.executablePath,
172
+ installed: false,
173
+ libraryPath: libraries.libraryPath,
174
+ }
167
175
  }
168
176
 
169
177
  // install only when neither an explicit binary nor an installed cache
@@ -211,98 +219,12 @@ export function ensurePlaywrightBrowser(
211
219
  `${browser} executable is still missing at ${after.executablePath}`,
212
220
  }
213
221
  }
214
- const missing = missingLibrariesMessage(after.executablePath, browser)
215
- if (missing) return { ok: false, message: missing }
216
- return { ok: true, executablePath: after.executablePath, installed: true }
217
- }
218
-
219
- // the debian package providing each shared library chromium and its headless
220
- // shell link (readelf NEEDED, linux x64 and arm64), apart from glibc's own.
221
- const LIBRARY_PACKAGES: Record<string, string> = {
222
- 'libasound.so.2': 'libasound2',
223
- 'libatk-1.0.so.0': 'libatk1.0-0',
224
- 'libatk-bridge-2.0.so.0': 'libatk-bridge2.0-0',
225
- 'libatspi.so.0': 'libatspi2.0-0',
226
- 'libcairo.so.2': 'libcairo2',
227
- 'libcups.so.2': 'libcups2',
228
- 'libdbus-1.so.3': 'libdbus-1-3',
229
- 'libexpat.so.1': 'libexpat1',
230
- 'libgbm.so.1': 'libgbm1',
231
- 'libgio-2.0.so.0': 'libglib2.0-0',
232
- 'libglib-2.0.so.0': 'libglib2.0-0',
233
- 'libgobject-2.0.so.0': 'libglib2.0-0',
234
- 'libnspr4.so': 'libnspr4',
235
- 'libnss3.so': 'libnss3',
236
- 'libnssutil3.so': 'libnss3',
237
- 'libpango-1.0.so.0': 'libpango-1.0-0',
238
- 'libsmime3.so': 'libnss3',
239
- 'libudev.so.1': 'libudev1',
240
- 'libX11.so.6': 'libx11-6',
241
- 'libxcb.so.1': 'libxcb1',
242
- 'libXcomposite.so.1': 'libxcomposite1',
243
- 'libXdamage.so.1': 'libxdamage1',
244
- 'libXext.so.6': 'libxext6',
245
- 'libXfixes.so.3': 'libxfixes3',
246
- 'libxkbcommon.so.0': 'libxkbcommon0',
247
- 'libXrandr.so.2': 'libxrandr2',
248
- }
249
-
250
- // packages renamed by the 64-bit time_t transition in ubuntu 24.04 and debian 13.
251
- const TIME64_PACKAGES = new Set([
252
- 'libasound2',
253
- 'libatk1.0-0',
254
- 'libatk-bridge2.0-0',
255
- 'libatspi2.0-0',
256
- 'libcups2',
257
- 'libglib2.0-0',
258
- ])
259
-
260
- function isTime64Distro(): boolean {
261
- if (!existsSync('/etc/os-release')) return false
262
- const fields = Object.fromEntries(
263
- readFileSync('/etc/os-release', 'utf8')
264
- .split('\n')
265
- .map((line) => line.match(/^(\w+)="?([^"]*)"?$/))
266
- .filter((match) => match !== null)
267
- .map((match) => [match[1], match[2]]),
268
- )
269
- const version = Number.parseFloat(fields.VERSION_ID ?? '')
270
- return (
271
- (fields.ID === 'ubuntu' && version >= 24.04) ||
272
- (fields.ID === 'debian' && version >= 13)
273
- )
274
- }
275
-
276
- /** a linux browser whose shared libraries are missing exits 127 before
277
- * playwright can say why, so name them and their packages up front, as
278
- * playwright's own host validation does. */
279
- function missingLibrariesMessage(executablePath: string, browser: string): string | null {
280
- if (process.platform !== 'linux') return null
281
- const ldd = spawnSync('ldd', [executablePath], { encoding: 'utf8', timeout: 30_000 })
282
- const libraries = [...(ldd.stdout ?? '').matchAll(/^\s*(\S+) => not found$/gm)].map(
283
- (match) => match[1]!,
284
- )
285
- if (!libraries.length) return null
286
- const time64 = isTime64Distro()
287
- const packages = new Set<string>()
288
- const unknown: string[] = []
289
- for (const library of libraries) {
290
- const name = LIBRARY_PACKAGES[library]
291
- if (!name) unknown.push(library)
292
- else packages.add(time64 && TIME64_PACKAGES.has(name) ? `${name}t64` : name)
222
+ const libraries = await ensureLinuxBrowserLibraries(after.executablePath, browser)
223
+ if (!libraries.ok) return libraries
224
+ return {
225
+ ok: true,
226
+ executablePath: after.executablePath,
227
+ installed: true,
228
+ libraryPath: libraries.libraryPath,
293
229
  }
294
- const sudo = process.getuid?.() === 0 ? '' : 'sudo '
295
- return [
296
- `${browser} at ${executablePath} cannot start: this system is missing shared libraries it links (${libraries.join(', ')}).`,
297
- ...(packages.size
298
- ? [
299
- 'On Debian or Ubuntu, install them and run the command again:',
300
- '',
301
- ` ${sudo}apt-get update && ${sudo}apt-get install -y --no-install-recommends ${[...packages].join(' ')}`,
302
- ]
303
- : []),
304
- ...(unknown.length
305
- ? ['', `No known Debian package provides: ${unknown.join(', ')}`]
306
- : []),
307
- ].join('\n')
308
230
  }
@@ -27,6 +27,9 @@ import { DISPOSABLE_BROWSER_CACHE_DIR_NAMES } from '../../src/browser-cache'
27
27
  export const PLAYWRIGHT_SIM_HOST = `
28
28
  const modulePath = process.env.SOOTSIM_PW_MODULE;
29
29
  const executablePath = process.env.SOOTSIM_PW_EXECUTABLE;
30
+ // the shared libraries this host lacks, from peach's bundle. only the browser
31
+ // gets them on its library path: the bundled builds are older than the system's.
32
+ const libraryPath = process.env.SOOTSIM_PW_LIBRARY_PATH;
30
33
  if (!executablePath) throw new Error('SOOTSIM_PW_EXECUTABLE is not set');
31
34
  const url = process.env.SOOTSIM_PW_URL;
32
35
  // the WS bridge port the sim page must register with, injected below as
@@ -335,6 +338,12 @@ const cleanupProfile = () => {
335
338
  ? { headless, channel, viewport, deviceScaleFactor }
336
339
  : { headless, viewport, deviceScaleFactor };
337
340
  opts.executablePath = executablePath;
341
+ if (libraryPath) {
342
+ opts.env = {
343
+ ...process.env,
344
+ LD_LIBRARY_PATH: [libraryPath, process.env.LD_LIBRARY_PATH].filter(Boolean).join(':'),
345
+ };
346
+ }
338
347
  // playwright injects --force-color-profile=srgb by default, which reads
339
348
  // visibly washed-out next to a normal chrome window on a wide-gamut mac
340
349
  // display. strip it so driver-launched sims color-match the user's own
@@ -103,7 +103,7 @@ async function launch(opts: DriverLaunchOptions): Promise<DriverLaunchResult> {
103
103
  // keep browser discovery, installation, and the detached host on one exact
104
104
  // cache contract even if another caller mutates process.env during launch.
105
105
  const playwrightEnv = { ...process.env }
106
- const provisioning = ensurePlaywrightBrowser(
106
+ const provisioning = await ensurePlaywrightBrowser(
107
107
  resolved,
108
108
  opts.headless ?? true,
109
109
  playwrightEnv,
@@ -151,6 +151,7 @@ async function launch(opts: DriverLaunchOptions): Promise<DriverLaunchResult> {
151
151
  SOOTSIM_PW_CONNECTED_ACK_FILE: connectAckFile,
152
152
  SOOTSIM_PW_CONNECT_TIMEOUT_MS: String(opts.connectTimeoutMs ?? 120_000),
153
153
  SOOTSIM_PW_OWNER_PID: String(ownerPid),
154
+ SOOTSIM_PW_LIBRARY_PATH: provisioning.libraryPath ?? '',
154
155
  },
155
156
  },
156
157
  )
@@ -1,4 +1,4 @@
1
- /*! rnx v0.1.659 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
1
+ /*! rnx v0.1.661 | (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.659 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
1
+ /*! rnx v0.1.661 | (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.659 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
1
+ /*! rnx v0.1.661 | (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.659 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
1
+ /*! rnx v0.1.661 | (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.659 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
1
+ /*! rnx v0.1.661 | (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.659 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
1
+ /*! rnx v0.1.661 | (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.659 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
1
+ /*! rnx v0.1.661 | (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;
@@ -26,11 +26,13 @@ __export(backend_origin_exports, {
26
26
  LOOPBACK_PROBE_HOSTNAMES: () => LOOPBACK_PROBE_HOSTNAMES,
27
27
  buildBrowserAssetProxyUrl: () => buildBrowserAssetProxyUrl,
28
28
  buildLoopbackProbeBaseUrl: () => buildLoopbackProbeBaseUrl,
29
+ isBrowserAssetProxyUrl: () => isBrowserAssetProxyUrl,
29
30
  isContrastBackendHost: () => isContrastBackendHost,
30
31
  isContrastBackendOrigin: () => isContrastBackendOrigin,
31
32
  isLoopbackHost: () => isLoopbackHost,
32
33
  loopbackOriginOf: () => loopbackOriginOf,
33
34
  normalizeHostname: () => normalizeHostname,
35
+ resolveBrowserAssetProxyUrl: () => resolveBrowserAssetProxyUrl,
34
36
  resolveContrastBackendOrigin: () => resolveContrastBackendOrigin
35
37
  });
36
38
  module.exports = __toCommonJS(backend_origin_exports);
@@ -60,6 +62,19 @@ function buildBrowserAssetProxyUrl(targetUrl) {
60
62
  const proxyPath = isLoopbackHost(new URL(targetUrl).hostname) ? "/__bundle-proxy" : "/__fetch-proxy";
61
63
  return `${proxyPath}?url=${encodeURIComponent(targetUrl)}`;
62
64
  }
65
+ function resolveBrowserAssetProxyUrl(targetUrl, currentOrigin) {
66
+ const proxyUrl = isBrowserAssetProxyUrl(targetUrl) ? targetUrl : buildBrowserAssetProxyUrl(targetUrl);
67
+ if (proxyUrl.startsWith("/__bundle-proxy?")) return proxyUrl;
68
+ return new URL(proxyUrl, resolveContrastBackendOrigin(currentOrigin)).toString();
69
+ }
70
+ function isBrowserAssetProxyUrl(value) {
71
+ try {
72
+ const url = new URL(value, DEFAULT_CONTRAST_BACKEND_ORIGIN);
73
+ return isContrastBackendOrigin(url.origin) && url.pathname === "/__fetch-proxy" && url.searchParams.has("url");
74
+ } catch {
75
+ return false;
76
+ }
77
+ }
63
78
  function isContrastBackendHost(hostname) {
64
79
  const normalized = normalizeHostname(hostname);
65
80
  return isLoopbackHost(normalized) || normalized === "contrast.dev" || normalized.endsWith(".contrast.dev");
@@ -89,10 +104,12 @@ function resolveContrastBackendOrigin(currentOrigin) {
89
104
  LOOPBACK_PROBE_HOSTNAMES,
90
105
  buildBrowserAssetProxyUrl,
91
106
  buildLoopbackProbeBaseUrl,
107
+ isBrowserAssetProxyUrl,
92
108
  isContrastBackendHost,
93
109
  isContrastBackendOrigin,
94
110
  isLoopbackHost,
95
111
  loopbackOriginOf,
96
112
  normalizeHostname,
113
+ resolveBrowserAssetProxyUrl,
97
114
  resolveContrastBackendOrigin
98
115
  });
package/dist-lib/beta.cjs CHANGED
@@ -1,4 +1,4 @@
1
- /*! rnx v0.1.659 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
1
+ /*! rnx v0.1.661 | (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.659 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
1
+ /*! rnx v0.1.661 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
2
2
 
3
3
  // src/beta.ts
4
4
  var IS_BETA = true;
@@ -1,4 +1,4 @@
1
- /*! rnx v0.1.659 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
1
+ /*! rnx v0.1.661 | (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.659 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
1
+ /*! rnx v0.1.661 | (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.659 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
1
+ /*! rnx v0.1.661 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
2
2
 
3
3
  // src/bridge-contract.ts
4
4
  var SIM_LONG_PRESS_MAX_MS = 5e3;
@@ -1,4 +1,4 @@
1
- /*! rnx v0.1.659 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
1
+ /*! rnx v0.1.661 | (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.659 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
1
+ /*! rnx v0.1.661 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
2
2
 
3
3
  // src/bridge-contract.ts
4
4
  var REFUSED_PERFORM_STEP_TYPES = [
@@ -1,4 +1,4 @@
1
- /*! rnx v0.1.659 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
1
+ /*! rnx v0.1.661 | (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.659 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
1
+ /*! rnx v0.1.661 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
2
2
 
3
3
  // src/capture-contract.ts
4
4
  var RNX_SCREEN_CAPTURE_VERSION = 1;
@@ -1,4 +1,4 @@
1
- /*! rnx v0.1.659 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
1
+ /*! rnx v0.1.661 | (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.659 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
1
+ /*! rnx v0.1.661 | (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.659 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
1
+ /*! rnx v0.1.661 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
2
2
 
3
3
  // src/cloud-contract.ts
4
4
  var RNX_CLOUD_MAX_ARTIFACT_BYTES = 20 * 1024 * 1024;
@@ -1,4 +1,4 @@
1
- /*! rnx v0.1.659 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
1
+ /*! rnx v0.1.661 | (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.659 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
1
+ /*! rnx v0.1.661 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
2
2
 
3
3
  // src/cloud-test-args.ts
4
4
  var CLOUD_TEST_ARGS_MAX_COUNT = 32;
@@ -1,4 +1,4 @@
1
- /*! rnx v0.1.659 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
1
+ /*! rnx v0.1.661 | (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.659 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
1
+ /*! rnx v0.1.661 | (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);
@@ -1,4 +1,4 @@
1
- /*! rnx v0.1.659 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
1
+ /*! rnx v0.1.661 | (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.659 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
1
+ /*! rnx v0.1.661 | (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.659 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
1
+ /*! rnx v0.1.661 | (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.659 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
1
+ /*! rnx v0.1.661 | (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;