peachsim 0.1.631 → 0.1.633

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 (75) hide show
  1. package/README.md +9 -1
  2. package/cli/commands/maestro.ts +5 -1
  3. package/cli/commands/test.ts +29 -1
  4. package/cli/drivers/playwright-core-pin.ts +10 -0
  5. package/cli/drivers/playwright-provisioning.ts +36 -1
  6. package/cli/drivers/playwright.ts +26 -19
  7. package/cli/shell-init.ts +6 -1
  8. package/cli/ws-bridge.ts +8 -2
  9. package/dist-lib/agent-daemon-client.cjs +1 -1
  10. package/dist-lib/agent-events.cjs +1 -1
  11. package/dist-lib/agent-identity.cjs +1 -1
  12. package/dist-lib/agent-sessions.cjs +1 -1
  13. package/dist-lib/attached-projects.cjs +1 -1
  14. package/dist-lib/auth/shared-session.cjs +1 -1
  15. package/dist-lib/backend-origin.cjs +1 -1
  16. package/dist-lib/beta.cjs +1 -1
  17. package/dist-lib/beta.mjs +1 -1
  18. package/dist-lib/bridge-constants.cjs +1 -1
  19. package/dist-lib/bridge-contract-input.cjs +1 -1
  20. package/dist-lib/bridge-contract-input.mjs +1 -1
  21. package/dist-lib/bridge-contract.cjs +1 -1
  22. package/dist-lib/bridge-contract.mjs +1 -1
  23. package/dist-lib/capture-contract.cjs +1 -1
  24. package/dist-lib/capture-contract.mjs +1 -1
  25. package/dist-lib/cli-constants.cjs +1 -1
  26. package/dist-lib/cloud-contract.cjs +1 -1
  27. package/dist-lib/cloud-contract.mjs +1 -1
  28. package/dist-lib/cloud-test-args.cjs +17 -5
  29. package/dist-lib/cloud-test-args.mjs +17 -5
  30. package/dist-lib/cloud.cjs +1 -1
  31. package/dist-lib/cloud.mjs +1 -1
  32. package/dist-lib/config.cjs +1 -1
  33. package/dist-lib/detox/index.cjs +1 -1
  34. package/dist-lib/dev-bundle-resolution.cjs +1 -1
  35. package/dist-lib/home-paths.cjs +6 -1
  36. package/dist-lib/host/bridge-host.cjs +48 -47
  37. package/dist-lib/host/fetch-proxy-handler.cjs +1 -1
  38. package/dist-lib/host/fetch-proxy-overrides.cjs +1 -1
  39. package/dist-lib/host/fetch-proxy-overrides.mjs +1 -1
  40. package/dist-lib/host/replacement-module-handler.cjs +1 -1
  41. package/dist-lib/host/websocket-proxy.cjs +1 -1
  42. package/dist-lib/index.cjs +7 -2
  43. package/dist-lib/jump-to-source-babel.cjs +1 -1
  44. package/dist-lib/jump-to-source-native.cjs +1 -1
  45. package/dist-lib/kotlin-host.cjs +1 -1
  46. package/dist-lib/menu.cjs +1 -1
  47. package/dist-lib/menu.mjs +1 -1
  48. package/dist-lib/metro-fingerprint-registry.cjs +1 -1
  49. package/dist-lib/metro-fingerprint-registry.mjs +1 -1
  50. package/dist-lib/metro-production-bundle.cjs +1 -1
  51. package/dist-lib/metro-production-bundle.mjs +1 -1
  52. package/dist-lib/metro.cjs +1 -1
  53. package/dist-lib/native-app-identity.cjs +1 -1
  54. package/dist-lib/native-app-identity.mjs +1 -1
  55. package/dist-lib/profiles.cjs +1 -1
  56. package/dist-lib/public-brand.cjs +1 -1
  57. package/dist-lib/react-native-host-modules.cjs +1 -1
  58. package/dist-lib/react-native-host-modules.mjs +1 -1
  59. package/dist-lib/render-mode.cjs +1 -1
  60. package/dist-lib/scripts/dev-server-scanner.cjs +1 -1
  61. package/dist-lib/sdk.cjs +1 -1
  62. package/dist-lib/sdk.mjs +1 -1
  63. package/dist-lib/skills.cjs +136 -83
  64. package/dist-lib/swift-compile.cjs +1 -1
  65. package/dist-lib/swift-compile.mjs +1 -1
  66. package/dist-lib/swift-package.cjs +1 -1
  67. package/dist-lib/swift-package.mjs +1 -1
  68. package/dist-lib/swift-submit.cjs +1 -1
  69. package/dist-lib/swift-submit.mjs +1 -1
  70. package/dist-lib/swift.cjs +9 -8
  71. package/dist-lib/vite-swift.cjs +1 -1
  72. package/dist-lib/vite.cjs +1 -1
  73. package/package.json +1 -1
  74. package/src/cloud-test-args.ts +18 -6
  75. package/src/home-paths.ts +8 -0
@@ -1,4 +1,4 @@
1
- /*! rnx v0.1.631 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
1
+ /*! rnx v0.1.633 | (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;
@@ -4380,7 +4380,7 @@ var init_supported_native_packages = __esm({
4380
4380
  "@dariyd/react-native-document-scanner",
4381
4381
  // seam
4382
4382
  "@datadog/mobile-react-native",
4383
- // coverage
4383
+ // seam+coverage
4384
4384
  "@dbkable/react-native-speech-to-text",
4385
4385
  // seam
4386
4386
  "@dimaportenko/react-native-shadow-view",
@@ -4551,6 +4551,8 @@ var init_supported_native_packages = __esm({
4551
4551
  // seam
4552
4552
  "@segment/analytics-react-native",
4553
4553
  // coverage
4554
+ "@segment/sovran-react-native",
4555
+ // seam
4554
4556
  "@sentry/react-native",
4555
4557
  // seam+coverage
4556
4558
  "@shakebugs/react-native-shake",
@@ -4658,7 +4660,7 @@ var init_supported_native_packages = __esm({
4658
4660
  "expo-image",
4659
4661
  // visual-proof+seam+coverage
4660
4662
  "expo-image-manipulator",
4661
- // coverage
4663
+ // seam+coverage
4662
4664
  "expo-image-picker",
4663
4665
  // seam+coverage
4664
4666
  "expo-insights",
@@ -4678,7 +4680,7 @@ var init_supported_native_packages = __esm({
4678
4680
  "expo-localization",
4679
4681
  // coverage
4680
4682
  "expo-mail-composer",
4681
- // semantic-proof
4683
+ // semantic-proof+seam
4682
4684
  "expo-maps",
4683
4685
  // visual-proof+seam
4684
4686
  "expo-media-control",
@@ -4708,13 +4710,13 @@ var init_supported_native_packages = __esm({
4708
4710
  "expo-secure-store",
4709
4711
  // semantic-proof+coverage
4710
4712
  "expo-sensors",
4711
- // semantic-proof
4713
+ // semantic-proof+seam
4712
4714
  "expo-sharing",
4713
4715
  // semantic-proof+coverage
4714
4716
  "expo-sms",
4715
4717
  // semantic-proof
4716
4718
  "expo-speech",
4717
- // semantic-proof
4719
+ // semantic-proof+seam
4718
4720
  "expo-speech-recognition",
4719
4721
  // seam
4720
4722
  "expo-splash-screen",
@@ -7252,7 +7254,7 @@ var init_registry = __esm({
7252
7254
  {
7253
7255
  range: ">=18.0.0",
7254
7256
  coverage: 0.95,
7255
- note: "upstream @react-native-firebase/crashlytics JS runs unchanged against a noop RNFBCrashlyticsModule native seam",
7257
+ note: "upstream @react-native-firebase/crashlytics JS runs unchanged against the NativeRNFBTurboCrashlytics (v26) and RNFBCrashlyticsModule native seam; collection reports disabled in a development bundle, as an iOS debug build does without crashlytics_debug_enabled",
7256
7258
  working: "default callable export, getCrashlytics, modular helpers, crash, log, recordError, setAttribute, setAttributes, setUserId, collection enablement, unsent report helpers, didCrashOnPreviousExecution",
7257
7259
  missing: "native crash capture, report persistence, upload to Firebase Crashlytics"
7258
7260
  }
@@ -7616,9 +7618,9 @@ var init_registry = __esm({
7616
7618
  {
7617
7619
  range: ">=13.0.0",
7618
7620
  coverage: 0.85,
7619
- note: "package-local wrapper registers only expo-sensors native modules; upstream expo-sensors JS runs unchanged.",
7620
- working: "upstream DeviceSensor/Pedometer wrappers, listener bookkeeping, update intervals, permissions, availability booleans, Gravity constant, ExponentAccelerometer/Gyroscope/Magnetometer/MagnetometerUncalibrated/DeviceMotion streams, ExpoBarometer stream, and ExponentPedometer counts",
7621
- missing: "real CoreMotion/browser sensor hardware, true step counting, and physical-device motion variability"
7621
+ note: "upstream expo-sensors JS runs unchanged over native modules that follow ios/*Module.swift on the reference iOS Simulator, where CoreMotion reports every sensor unavailable and delivers no samples",
7622
+ working: "upstream DeviceSensor/Pedometer wrappers, listener bookkeeping, update intervals, motion permissions, Gravity constant, isAvailableAsync false for every sensor as on the simulator, getStepCountAsync rejecting with ERR_PEDOMETER_QUERY",
7623
+ missing: "sensor samples: the simulator has no motion hardware, so a physical-device stream is out of scope"
7622
7624
  }
7623
7625
  ]
7624
7626
  },
@@ -7669,9 +7671,9 @@ var init_registry = __esm({
7669
7671
  {
7670
7672
  range: ">=13.0.0",
7671
7673
  coverage: 0.93,
7672
- note: "native-seam only. real expo-av package runs from the bundle (Audio.Sound + Audio.Recording classes, AVPlaybackStatus shape, PitchCorrectionQuality enum, RecordingOptionsPresets, Video component). sootsim only stubs the ExponentAV native module via web-faithful upstream-ported element-keyed methods (loadForSound/setStatusForSound/replaySound + prepare/start/pause/stop/unloadAudioRecorder + permissions via getUserMedia probe).",
7673
- working: "Audio.Sound (createAsync/loadAsync/unloadAsync/play/pause/stop/seek/setRate/setVolume/setMuted/setLooping/setProgressUpdateInterval/setOnPlaybackStatusUpdate via element-keyed seam), Audio.Recording (prepareToRecordAsync/startAsync/pauseAsync/stopAndUnloadAsync/getStatusAsync/getURI via singleton MediaRecorder), Video ref API (playAsync/pauseAsync/setPositionAsync/setStatusAsync/setRate/setVolume/setIsMuted/setIsLooping), real getUserMedia permission probe with PermissionStatus reflection, didUpdatePlaybackStatus + ExponentAV.onError events via DeviceEventEmitter, RecordingOptionsPresets, PitchCorrectionQuality enum (Low/Medium/High), ResizeMode",
7674
- missing: "setAudioModeAsync is noop (no browser equivalent for AVAudioSession), audio metering/analyzer data absent (would need AnalyserNode), Video fullscreen helpers no-op (no Fullscreen API integration), shouldDuckAndroid / playThroughEarpieceAndroid ignored"
7674
+ note: "native-seam only. real expo-av package runs from the bundle (Audio.Sound + Audio.Recording classes, AVPlaybackStatus shape, PitchCorrectionQuality enum, RecordingOptionsPresets, Video component). audio: the ExponentAV native module keeps the web-ported element-keyed sound and recorder methods. video: the ExpoVideoView module and view follow ios/EXAV/Video (tag-addressed loadForVideo/setStatusForVideo/replayVideo/getStatusForVideo, the EXAVPlayerData status dictionary, onLoadStart/onLoad/onReadyForDisplay/onStatusUpdate/onError) over the shared video channel.",
7675
+ working: "Audio.Sound (createAsync/loadAsync/unloadAsync/play/pause/stop/seek/setRate/setVolume/setMuted/setLooping/setProgressUpdateInterval/setOnPlaybackStatusUpdate via element-keyed seam), Audio.Recording (prepareToRecordAsync/startAsync/pauseAsync/stopAndUnloadAsync/getStatusAsync/getURI via singleton MediaRecorder), Video component playback of bundled and remote sources with the ref API (loadAsync/unloadAsync/playAsync/pauseAsync/setPositionAsync/setStatusAsync/replayAsync/setRate/setVolume/setIsMuted/setIsLooping) and its events, real getUserMedia permission probe with PermissionStatus reflection, didUpdatePlaybackStatus + ExponentAV.onError events via DeviceEventEmitter, RecordingOptionsPresets, PitchCorrectionQuality enum (Low/Medium/High), ResizeMode",
7676
+ missing: "setAudioModeAsync is noop (no browser equivalent for AVAudioSession), audio metering/analyzer data absent (would need AnalyserNode), Video fullscreen rejects (no system AVPlayerViewController) and useNativeControls draws no controls, sound keys are elements where iOS uses numbers, shouldDuckAndroid / playThroughEarpieceAndroid ignored"
7675
7677
  }
7676
7678
  ]
7677
7679
  },
@@ -8038,7 +8040,7 @@ var init_registry = __esm({
8038
8040
  coverage: 0.85,
8039
8041
  note: "upstream expo-mail-composer JS runs unchanged; package-local wrapper only registers the ExpoMailComposer native module with clean unavailable simulator state.",
8040
8042
  working: "isAvailableAsync, getClients, composeAsync, MailComposerStatus enum, and types through upstream MailComposer.ts/MailComposer.types; simulator state reports unavailable with no installed mail clients",
8041
- missing: "cannot present native MFMailComposeViewController or query installed mail apps from the worker; composeAsync rejects when unavailable, matching iOS simulator behavior"
8043
+ missing: "cannot present native MFMailComposeViewController or query installed mail apps from the worker; composeAsync rejects with ERR_CANNOT_SEND_MAIL, matching iOS simulator behavior"
8042
8044
  }
8043
8045
  ]
8044
8046
  },
@@ -8117,9 +8119,9 @@ var init_registry = __esm({
8117
8119
  {
8118
8120
  range: ">=11.0.0",
8119
8121
  coverage: 0.93,
8120
- note: "real worker-safe implementation via OffscreenCanvas + createImageBitmap + convertToBlob; both legacy manipulateAsync and v12+ class-based ImageManipulator/Context/Image APIs are wired through to actual pixel work. Output bytes are real PNG/JPEG/WEBP, base64 is real, Context chaining produces correct intermediate frames, SaveFormat/FlipType enums match upstream identity. The remaining gap is SharedObject identity (not a functional difference in pixel output) and a slightly different resize algorithm (OffscreenCanvas drawImage with imageSmoothingQuality=high vs. upstream JS Hermite resampler \u2014 outputs match iOS bicubic in practice).",
8121
- working: "manipulateAsync (resize/rotate/flip/crop/extent + JPEG/PNG/WEBP output + base64), ImageManipulator.manipulate, useImageManipulator hook, Context chaining (resize/rotate/flip/crop/extent/reset/renderAsync/release), Image.saveAsync with quality, FlipType/SaveFormat enums, all type exports",
8122
- missing: "SharedRef identity across cross-module boundaries (expo-modules-core SharedObject) \u2014 we mirror the public API surface but consumer code that compares ref identity across modules will see different objects"
8122
+ note: "the real package JS runs over an ExpoImageManipulator native seam that follows ios/ImageManipulatorModule.swift: manipulate takes a url or an image ref, the Context chains resize/rotate/flip/crop/reset onto one render task, and Image.saveAsync encodes and writes Caches/ImageManipulator/<UUID>.<ext>. Pixels go through OffscreenCanvas with the transformers sizing rules (resize rounds up at scale 1, rotate floors the rotated bounds, crop rejects out-of-range rects and clips the integral rect). iOS has no extent, so upstream skips it as on device.",
8123
+ working: "manipulateAsync and ImageManipulator.manipulate over data, file and http urls and expo-image refs, useImageManipulator, Context resize/rotate/flip/crop/reset/renderAsync, Image width/height/saveAsync with jpeg/png/webp, compress and base64, native exception codes and messages",
8124
+ missing: "ph:// and assets-library:// photo library sources reject as not found, decoding uses the browser codecs (webp output depends on the browser encoder), and EXIF orientation is applied at decode again after reset"
8123
8125
  }
8124
8126
  ]
8125
8127
  },
@@ -8144,8 +8146,9 @@ var init_registry = __esm({
8144
8146
  {
8145
8147
  range: ">=1.0.0",
8146
8148
  coverage: 0.95,
8147
- note: "full noop \u2014 Datadog RUM/Logs/Trace require native agent, no web SDK bridged",
8148
- missing: "all DdSdkReactNative, DdLogs, DdRum, DdTrace methods present but discard all data; DatadogProvider renders children without initialization"
8149
+ note: "the published JS runs unchanged over DdSdk, DdRum, DdLogs, DdTrace and DdFlags native seams from src/specs; each call settles as ios/Sources/*Implementation.swift does, with no Datadog SDK behind it",
8150
+ working: "DdSdkReactNative and DatadogProvider initialization, CoreConfiguration and every exported class and enum, DdRum views/actions/resources/errors/timings/attributes, DdLogs, DdTrace spans with UUID ids, user and account info, tracking consent, the fetch/XHR/error/navigation instrumentation",
8151
+ missing: "no events are recorded or uploaded, getCurrentSessionId resolves null because no RUM session runs, and DdFlags never receives a flags snapshot"
8149
8152
  }
8150
8153
  ]
8151
8154
  },
@@ -8902,9 +8905,9 @@ var init_registry = __esm({
8902
8905
  {
8903
8906
  range: ">=1.0.0",
8904
8907
  coverage: 0.85,
8905
- note: "JS store runs unmodified; optional native bridge is absent and upstream already degrades to a warning",
8906
- working: "createStore, dispatch/getState/subscribe queue, registerBridgeStore, AsyncStorage persistor through async-storage stub",
8907
- missing: "native-to-JS store action bridge events from Sovran native module"
8908
+ note: "JS store runs unmodified over a Sovran native seam that follows ios/Sovran.swift: ON_STORE_ACTION constant and the RCTEventEmitter listener contract",
8909
+ working: "createStore, dispatch/getState/subscribe queue, registerBridgeStore, the native bridge listener, AsyncStorage persistor through async-storage stub",
8910
+ missing: "store actions dispatched from other native SDK code, since a simulated app runs none"
8908
8911
  }
8909
8912
  ]
8910
8913
  },
@@ -20487,6 +20490,7 @@ __export(home_paths_exports, {
20487
20490
  isDevWorkstation: () => isDevWorkstation,
20488
20491
  isOnboardingComplete: () => isOnboardingComplete,
20489
20492
  isSootsimDevCheckout: () => isSootsimDevCheckout,
20493
+ playwrightCoreDir: () => playwrightCoreDir,
20490
20494
  profilesDir: () => profilesDir,
20491
20495
  readActiveRuntime: () => readActiveRuntime,
20492
20496
  readDaemonLockfile: () => readDaemonLockfile,
@@ -20557,6 +20561,9 @@ function electronUserDataDir() {
20557
20561
  function electronVersionDir(version) {
20558
20562
  return import_node_path4.default.join(electronDir(), version);
20559
20563
  }
20564
+ function playwrightCoreDir(version) {
20565
+ return import_node_path4.default.join(rnxHomeDir(), "playwright-core", version);
20566
+ }
20560
20567
  function profilesDir() {
20561
20568
  return import_node_path4.default.join(rnxHomeDir(), "profiles");
20562
20569
  }
@@ -21215,48 +21222,16 @@ function createRuntimeDelivery(product) {
21215
21222
  manifest
21216
21223
  };
21217
21224
  }
21218
- const tarUrl = entry.tarball || tarballUrl(version, cdnOrigin);
21219
- const downloadDir = import_node_fs7.default.mkdtempSync(
21220
- import_node_path7.default.join(import_node_os2.default.tmpdir(), `${product.id}-runtime-download-`)
21221
- );
21222
- const runtimeArchivePath = import_node_path7.default.join(
21223
- downloadDir,
21224
- runtimeTarballCacheName(product, version)
21225
- );
21226
- const tmpDir = import_node_path7.default.join(
21227
- import_node_path7.default.dirname(destDir),
21228
- `.installing-${version}-${process.pid}`
21229
- );
21230
- import_node_fs7.default.rmSync(tmpDir, { recursive: true, force: true });
21231
- try {
21232
- if (!opts.quiet)
21233
- process.stderr.write(`${product.id}: downloading runtime ${version}\u2026
21225
+ if (!opts.quiet)
21226
+ process.stderr.write(`${product.id}: downloading runtime ${version}\u2026
21234
21227
  `);
21235
- await downloadToFile(tarUrl, runtimeArchivePath);
21236
- if (!opts.quiet)
21237
- process.stderr.write(`${product.id}: extracting runtime ${version}\u2026
21238
- `);
21239
- const actualSha = await sha256File(runtimeArchivePath);
21240
- if (actualSha !== entry.sha256) {
21241
- throw new Error(
21242
- `sha256 mismatch for runtime ${version}: expected ${entry.sha256}, actual ${actualSha}`
21243
- );
21244
- }
21245
- import_node_fs7.default.mkdirSync(tmpDir, { recursive: true });
21246
- await extractTarball(runtimeArchivePath, tmpDir);
21247
- if (!import_node_fs7.default.existsSync(import_node_path7.default.join(tmpDir, product.installedMarker))) {
21248
- throw new Error(
21249
- `extracted tarball for runtime ${version} is missing ${product.installedMarker}`
21250
- );
21251
- }
21252
- import_node_fs7.default.rmSync(destDir, { recursive: true, force: true });
21253
- import_node_fs7.default.renameSync(tmpDir, destDir);
21254
- } catch (err) {
21255
- import_node_fs7.default.rmSync(tmpDir, { recursive: true, force: true });
21256
- throw err;
21257
- } finally {
21258
- import_node_fs7.default.rmSync(downloadDir, { recursive: true, force: true });
21259
- }
21228
+ await installVerifiedTarball({
21229
+ url: entry.tarball || tarballUrl(version, cdnOrigin),
21230
+ digest: { algorithm: "sha256", encoding: "hex", value: entry.sha256 },
21231
+ destDir,
21232
+ installedMarker: product.installedMarker,
21233
+ label: `runtime ${version}`
21234
+ });
21260
21235
  if (setActive) product.writeActiveRuntime(version);
21261
21236
  pruneLocalRetention({ protectVersions });
21262
21237
  return {
@@ -21446,6 +21421,37 @@ function createRuntimeDelivery(product) {
21446
21421
  listInstalled: () => listInstalledRuntimes(product)
21447
21422
  };
21448
21423
  }
21424
+ async function installVerifiedTarball(tarball) {
21425
+ const { url, digest, destDir, installedMarker, label } = tarball;
21426
+ const downloadDir = import_node_fs7.default.mkdtempSync(import_node_path7.default.join(import_node_os2.default.tmpdir(), "runtime-delivery-download-"));
21427
+ const archivePath = import_node_path7.default.join(downloadDir, "archive.tar.gz");
21428
+ const tmpDir = import_node_path7.default.join(
21429
+ import_node_path7.default.dirname(destDir),
21430
+ `.installing-${import_node_path7.default.basename(destDir)}-${process.pid}`
21431
+ );
21432
+ import_node_fs7.default.rmSync(tmpDir, { recursive: true, force: true });
21433
+ try {
21434
+ await downloadToFile(url, archivePath);
21435
+ const actual = await digestFile(archivePath, digest.algorithm, digest.encoding);
21436
+ if (actual !== digest.value) {
21437
+ throw new Error(
21438
+ `${digest.algorithm} mismatch for ${label}: expected ${digest.value}, actual ${actual}`
21439
+ );
21440
+ }
21441
+ import_node_fs7.default.mkdirSync(tmpDir, { recursive: true });
21442
+ await extractTarball(archivePath, tmpDir, tarball.stripComponents ?? 0);
21443
+ if (!import_node_fs7.default.existsSync(import_node_path7.default.join(tmpDir, installedMarker))) {
21444
+ throw new Error(`extracted tarball for ${label} is missing ${installedMarker}`);
21445
+ }
21446
+ import_node_fs7.default.rmSync(destDir, { recursive: true, force: true });
21447
+ import_node_fs7.default.renameSync(tmpDir, destDir);
21448
+ } catch (err) {
21449
+ import_node_fs7.default.rmSync(tmpDir, { recursive: true, force: true });
21450
+ throw err;
21451
+ } finally {
21452
+ import_node_fs7.default.rmSync(downloadDir, { recursive: true, force: true });
21453
+ }
21454
+ }
21449
21455
  async function downloadToFile(url, destPath) {
21450
21456
  const res = await fetch(url);
21451
21457
  if (!res.ok || !res.body) {
@@ -21467,18 +21473,20 @@ async function downloadToFile(url, destPath) {
21467
21473
  throw err;
21468
21474
  }
21469
21475
  }
21470
- function sha256File(filePath) {
21476
+ function digestFile(filePath, algorithm, encoding) {
21471
21477
  return new Promise((resolve9, reject) => {
21472
- const hash = import_node_crypto2.default.createHash("sha256");
21478
+ const hash = import_node_crypto2.default.createHash(algorithm);
21473
21479
  const stream = import_node_fs7.default.createReadStream(filePath);
21474
21480
  stream.on("data", (chunk) => hash.update(chunk));
21475
21481
  stream.on("error", reject);
21476
- stream.on("end", () => resolve9(hash.digest("hex")));
21482
+ stream.on("end", () => resolve9(hash.digest(encoding)));
21477
21483
  });
21478
21484
  }
21479
- function extractTarball(tarPath, destDir) {
21485
+ function extractTarball(tarPath, destDir, stripComponents) {
21480
21486
  return new Promise((resolve9, reject) => {
21481
- const child = (0, import_node_child_process3.spawn)("tar", ["-xzf", tarPath, "-C", destDir], {
21487
+ const args = ["-xzf", tarPath, "-C", destDir];
21488
+ if (stripComponents > 0) args.push(`--strip-components=${stripComponents}`);
21489
+ const child = (0, import_node_child_process3.spawn)("tar", args, {
21482
21490
  stdio: ["ignore", "inherit", "inherit"]
21483
21491
  });
21484
21492
  child.on("error", reject);
@@ -31690,7 +31698,12 @@ function createBridge(wsPort, opts = {}) {
31690
31698
  });
31691
31699
  ws.on("error", (err) => {
31692
31700
  const cause = err.message ? `: ${err.message}` : "";
31693
- reject(new Error(`could not connect to ${bridgeWsUrl(wsPort)}${cause}`));
31701
+ reject(
31702
+ new Error(
31703
+ `could not connect to ${bridgeWsUrl(wsPort)}${cause}
31704
+ for sandboxed or agent runners, keep \`peach serve --port ${wsPort}\` running in a persistent terminal or background task.`
31705
+ )
31706
+ );
31694
31707
  });
31695
31708
  });
31696
31709
  ready.catch(() => {
@@ -34132,11 +34145,24 @@ var init_internal_child = __esm({
34132
34145
  }
34133
34146
  });
34134
34147
 
34148
+ // cli/drivers/playwright-core-pin.ts
34149
+ var PINNED_PLAYWRIGHT_CORE;
34150
+ var init_playwright_core_pin = __esm({
34151
+ "cli/drivers/playwright-core-pin.ts"() {
34152
+ "use strict";
34153
+ PINNED_PLAYWRIGHT_CORE = {
34154
+ version: "1.61.0",
34155
+ integrity: "sha512-caX7TrY3Ml6egyDX0WUcTHDxodl/b51y5wJOdCEA36QviK/s2g081hvmGs8eaE3DWb6NYZQ6BjO/QkNRPenoPA=="
34156
+ };
34157
+ }
34158
+ });
34159
+
34135
34160
  // cli/drivers/playwright-provisioning.ts
34136
34161
  function resolvePlaywright() {
34137
34162
  const roots = [`${process.cwd()}/`];
34138
34163
  const self = rnxSelfInvocation();
34139
34164
  roots.push(self.prefixArgs[0] ?? self.executable);
34165
+ roots.push(`${playwrightCoreDir(PINNED_PLAYWRIGHT_CORE.version)}/`);
34140
34166
  for (const root of roots) {
34141
34167
  try {
34142
34168
  const requireFromRoot = (0, import_node_module5.createRequire)(root);
@@ -34172,6 +34198,33 @@ function resolvePlaywright() {
34172
34198
  }
34173
34199
  return null;
34174
34200
  }
34201
+ async function provisionPlaywright() {
34202
+ const existing = resolvePlaywright();
34203
+ if (existing) return existing;
34204
+ const { version, integrity } = PINNED_PLAYWRIGHT_CORE;
34205
+ const [algorithm, digest] = integrity.split("-", 2);
34206
+ if (algorithm !== "sha512" || !digest) {
34207
+ throw new Error(`pinned playwright-core integrity is not sha512: ${integrity}`);
34208
+ }
34209
+ process.stderr.write(`peach: installing playwright-core ${version}\u2026
34210
+ `);
34211
+ await installVerifiedTarball({
34212
+ url: `https://registry.npmjs.org/playwright-core/-/playwright-core-${version}.tgz`,
34213
+ digest: { algorithm, encoding: "base64", value: digest },
34214
+ destDir: (0, import_node_path25.join)(playwrightCoreDir(version), "node_modules", "playwright-core"),
34215
+ installedMarker: "package.json",
34216
+ // npm tarballs hold the package under a leading `package/`
34217
+ stripComponents: 1,
34218
+ label: `playwright-core ${version}`
34219
+ });
34220
+ const installed2 = resolvePlaywright();
34221
+ if (!installed2) {
34222
+ throw new Error(
34223
+ `playwright-core ${version} installed to ${playwrightCoreDir(version)} but does not resolve`
34224
+ );
34225
+ }
34226
+ return installed2;
34227
+ }
34175
34228
  function probeBrowser(resolved, env) {
34176
34229
  const self = rnxSelfInvocation();
34177
34230
  const probe3 = (0, import_node_child_process6.spawnSync)(
@@ -34260,25 +34313,22 @@ var init_playwright_provisioning = __esm({
34260
34313
  import_node_fs23 = require("node:fs");
34261
34314
  import_node_module5 = require("node:module");
34262
34315
  import_node_path25 = require("node:path");
34316
+ init_src();
34317
+ init_home_paths();
34263
34318
  init_is_record();
34264
34319
  init_internal_child();
34265
34320
  init_self_invocation();
34321
+ init_playwright_core_pin();
34266
34322
  }
34267
34323
  });
34268
34324
 
34269
34325
  // cli/drivers/playwright.ts
34270
34326
  function probe2() {
34271
34327
  const resolved = resolvePlaywright();
34272
- if (!resolved) {
34273
- return {
34274
- available: false,
34275
- reason: "playwright not installed in the current workspace"
34276
- };
34277
- }
34278
34328
  return {
34279
34329
  available: true,
34280
34330
  reason: null,
34281
- detail: `resolved via ${resolved.spec}@${resolved.version}`
34331
+ detail: resolved ? `resolved via ${resolved.spec}@${resolved.version}` : `installs playwright-core@${PINNED_PLAYWRIGHT_CORE.version} on first launch`
34282
34332
  };
34283
34333
  }
34284
34334
  function headlessHintForLinuxFailure(opts, detail) {
@@ -34300,13 +34350,6 @@ function resolveUserDataDir(opts) {
34300
34350
  return "";
34301
34351
  }
34302
34352
  async function launch2(opts) {
34303
- const resolved = resolvePlaywright();
34304
- if (!resolved) {
34305
- return {
34306
- launched: false,
34307
- message: "playwright not installed \u2014 run `bun add -D playwright` first"
34308
- };
34309
- }
34310
34353
  if (!opts.url) {
34311
34354
  return { launched: false, message: "playwright driver requires a target url" };
34312
34355
  }
@@ -34317,6 +34360,15 @@ async function launch2(opts) {
34317
34360
  message: "playwright driver requires a valid owning session process"
34318
34361
  };
34319
34362
  }
34363
+ let resolved;
34364
+ try {
34365
+ resolved = await provisionPlaywright();
34366
+ } catch (error) {
34367
+ return {
34368
+ launched: false,
34369
+ message: `playwright provisioning failed: ${error instanceof Error ? error.message : String(error)}`
34370
+ };
34371
+ }
34320
34372
  const playwrightEnv = { ...process.env };
34321
34373
  const provisioning = ensurePlaywrightBrowser(resolved, playwrightEnv);
34322
34374
  if (!provisioning.ok) {
@@ -34407,6 +34459,7 @@ var init_playwright = __esm({
34407
34459
  init_internal_child();
34408
34460
  init_parent_pid();
34409
34461
  init_self_invocation();
34462
+ init_playwright_core_pin();
34410
34463
  init_playwright_provisioning();
34411
34464
  playwrightDriver = {
34412
34465
  id: "playwright",
@@ -1,4 +1,4 @@
1
- /*! rnx v0.1.631 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
1
+ /*! rnx v0.1.633 | (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.631 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
1
+ /*! rnx v0.1.633 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
2
2
 
3
3
  // src/swift-compile.ts
4
4
  import { execFile as execFile2 } from "node:child_process";
@@ -1,4 +1,4 @@
1
- /*! rnx v0.1.631 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
1
+ /*! rnx v0.1.633 | (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.631 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
1
+ /*! rnx v0.1.633 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
2
2
 
3
3
  // src/swift-package.ts
4
4
  function isSwiftManifestFile(file) {
@@ -1,4 +1,4 @@
1
- /*! rnx v0.1.631 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
1
+ /*! rnx v0.1.633 | (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.631 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
1
+ /*! rnx v0.1.633 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
2
2
 
3
3
  // src/swift-submit.ts
4
4
  var SWIFT_COMPILE_PATH = "/api/swift-compile";
@@ -1,4 +1,4 @@
1
- /*! rnx v0.1.631 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
1
+ /*! rnx v0.1.633 | (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;
@@ -7388,6 +7388,9 @@ var LANGUAGE_FONT_SUBSETS = [
7388
7388
  }
7389
7389
  ];
7390
7390
 
7391
+ // ../peach-engine/src/engine/generated/sf-pro-font.ts
7392
+ var SF_PRO_FONT_URL = "/fonts/sf-pro.ttf?sha256=ba43865c3955d7d30888927bfdfef2476c932f9b930b5ad6adb50c33ac6a8de0";
7393
+
7391
7394
  // ../peach-engine/src/engine/font-config.ts
7392
7395
  var DEFAULT_FONT_FAMILY = "SF Pro";
7393
7396
  var SF_PRO_VARIANT_FONT_FAMILY = "SF Pro Variant";
@@ -7414,7 +7417,7 @@ function normalizeFontWeight(fontWeight) {
7414
7417
  }
7415
7418
  return 400;
7416
7419
  }
7417
- var BASE_FONT_URLS = ["/fonts/sf-pro.ttf"];
7420
+ var BASE_FONT_URLS = [SF_PRO_FONT_URL];
7418
7421
  var BOOT_FONT_URLS = [...BASE_FONT_URLS];
7419
7422
  function activePlatform() {
7420
7423
  return peekDeviceSpec()?.platform ?? "ios";
@@ -9424,15 +9427,13 @@ function resetTextureLayerFlattenState(state5) {
9424
9427
  }
9425
9428
  function releaseRasterImage(chunk) {
9426
9429
  if (chunk.kind !== "picture") return;
9427
- if (chunk.rasterImage) {
9430
+ if (chunk.rasterImageHandle) {
9428
9431
  LIVE.rasterImages--;
9429
9432
  LIVE.rasterBytes -= chunk.rasterWidth * chunk.rasterHeight * 4;
9430
- try {
9431
- chunk.rasterImage.delete();
9432
- } catch {
9433
- }
9433
+ chunk.rasterNative?._peach_raster_release(chunk.rasterImageHandle);
9434
9434
  }
9435
- chunk.rasterImage = null;
9435
+ chunk.rasterImageHandle = 0;
9436
+ chunk.rasterNative = null;
9436
9437
  chunk.rasterWidth = 0;
9437
9438
  chunk.rasterHeight = 0;
9438
9439
  chunk.rasterDpr = 0;
@@ -1,4 +1,4 @@
1
- /*! rnx v0.1.631 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
1
+ /*! rnx v0.1.633 | (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;
package/dist-lib/vite.cjs CHANGED
@@ -1,4 +1,4 @@
1
- /*! rnx v0.1.631 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
1
+ /*! rnx v0.1.633 | (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;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "peachsim",
3
- "version": "0.1.631",
3
+ "version": "0.1.633",
4
4
  "description": "Vite and Metro plugins, testing drivers, and SDK exports for peach.",
5
5
  "author": "Tamagui LLC",
6
6
  "license": "SEE LICENSE IN LICENSE",
@@ -1,8 +1,8 @@
1
- // hosted `rnx test` selection args: the argv slice a cloud submitter
1
+ // hosted `peach test` selection and explicit environment arguments: the argv slice a cloud submitter
2
2
  // forwards and the recorder appends verbatim to its executor invocation.
3
3
  //
4
- // selection only. the flags below choose WHICH tests run; everything
5
- // about HOW they run (workers, timeouts, shards, devices, environment)
4
+ // the flags below choose tests and provide explicit flow variables. everything
5
+ // about HOW they run (workers, timeouts, shards, devices)
6
6
  // stays with the recorder that owns the execution environment, and
7
7
  // everything about WHERE they run (app, origin, tunnel) is consumed by
8
8
  // the submitter. a flag outside this list never reaches the executor:
@@ -22,6 +22,8 @@ export const CLOUD_TEST_VALUE_FLAGS: readonly string[] = [
22
22
  '--include-tags',
23
23
  '--exclude-tags',
24
24
  '--grep',
25
+ '--env',
26
+ '-e',
25
27
  ]
26
28
 
27
29
  const CLOUD_TEST_VALUE_FLAG_SET = new Set<string>(CLOUD_TEST_VALUE_FLAGS)
@@ -57,7 +59,7 @@ export function parseCloudTestArgs(value: unknown): CloudTestArgsVerdict {
57
59
  return { ok: false, message: 'testArgs entries must be non-empty strings' }
58
60
  }
59
61
  if (entry.length > CLOUD_TEST_ARG_MAX_LENGTH || entry.includes('\0')) {
60
- return { ok: false, message: `testArgs entry is too long or carries a NUL byte: ${entry.slice(0, 60)}` }
62
+ return { ok: false, message: 'testArgs entry is too long or carries a NUL byte' }
61
63
  }
62
64
  bytes += encoder.encode(entry).length
63
65
  if (bytes > CLOUD_TEST_ARGS_MAX_BYTES) {
@@ -80,12 +82,19 @@ export function parseCloudTestArgs(value: unknown): CloudTestArgsVerdict {
80
82
  const eq = entry.indexOf('=')
81
83
  const head = eq > 0 ? entry.slice(0, eq) : entry
82
84
  if (!isCloudTestValueFlag(head)) {
83
- return { ok: false, message: `testArgs carries an unknown flag: ${head.slice(0, 60)}` }
85
+ return {
86
+ ok: false,
87
+ message: `testArgs carries an unknown flag: ${head.slice(0, 60)}`,
88
+ }
84
89
  }
85
90
  if (eq > 0) {
86
- if (!entry.slice(eq + 1)) {
91
+ const value = entry.slice(eq + 1)
92
+ if (!value) {
87
93
  return { ok: false, message: `${head} expects a value` }
88
94
  }
95
+ if ((head === '--env' || head === '-e') && value.indexOf('=') < 1) {
96
+ return { ok: false, message: `${head} expects KEY=VALUE with a non-empty key` }
97
+ }
89
98
  args.push(entry)
90
99
  continue
91
100
  }
@@ -93,6 +102,9 @@ export function parseCloudTestArgs(value: unknown): CloudTestArgsVerdict {
93
102
  if (val === undefined || val.startsWith('-')) {
94
103
  return { ok: false, message: `${head} expects a value` }
95
104
  }
105
+ if ((head === '--env' || head === '-e') && val.indexOf('=') < 1) {
106
+ return { ok: false, message: `${head} expects KEY=VALUE with a non-empty key` }
107
+ }
96
108
  args.push(entry, val)
97
109
  i += 1
98
110
  }
package/src/home-paths.ts CHANGED
@@ -9,6 +9,10 @@
9
9
  // │ alternative to a symlink)
10
10
  // ├── electron/
11
11
  // │ └── <version>/ pinned electron binary (future: playwright-style)
12
+ // ├── playwright-core/
13
+ // │ └── <version>/node_modules/playwright-core
14
+ // │ the pinned package, provisioned on first need when
15
+ // │ neither the app nor the CLI's install tree has one
12
16
  // ├── profiles/
13
17
  // │ └── profiles.json storage profile metadata
14
18
  // ├── cache/
@@ -126,6 +130,10 @@ export function electronVersionDir(version: string): string {
126
130
  return path.join(electronDir(), version)
127
131
  }
128
132
 
133
+ export function playwrightCoreDir(version: string): string {
134
+ return path.join(rnxHomeDir(), 'playwright-core', version)
135
+ }
136
+
129
137
  export function profilesDir(): string {
130
138
  return path.join(rnxHomeDir(), 'profiles')
131
139
  }