peachsim 0.1.939 → 0.1.1399

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 (396) hide show
  1. package/README.md +28 -22
  2. package/cli/app-catalog.ts +29 -14
  3. package/cli/app-fonts.ts +5 -3
  4. package/cli/bridge-flow-runner.ts +40 -29
  5. package/cli/cloud-client.ts +57 -21
  6. package/cli/cloud-test-tunnel.ts +12 -18
  7. package/cli/commands/agent.ts +1 -1
  8. package/cli/commands/app-fonts.ts +3 -3
  9. package/cli/commands/assert.ts +8 -8
  10. package/cli/commands/auth.ts +8 -3
  11. package/cli/commands/box/bundle-plane.ts +13 -1
  12. package/cli/commands/box.ts +16 -5
  13. package/cli/commands/compat.ts +1 -1
  14. package/cli/commands/config.ts +2 -2
  15. package/cli/commands/control.ts +125 -71
  16. package/cli/commands/cpu-profile.ts +45 -1
  17. package/cli/commands/daemon.ts +131 -33
  18. package/cli/commands/debug.ts +24 -17
  19. package/cli/commands/desktop.ts +7 -1
  20. package/cli/commands/detect.ts +101 -0
  21. package/cli/commands/detox.ts +6 -6
  22. package/cli/commands/device.ts +12 -12
  23. package/cli/commands/electron.ts +32 -14
  24. package/cli/commands/flow.ts +49 -41
  25. package/cli/commands/guest-host.ts +39 -1
  26. package/cli/commands/inspect/core.ts +14 -8
  27. package/cli/commands/inspect/list.ts +1 -1
  28. package/cli/commands/inspect/settle.ts +5 -3
  29. package/cli/commands/inspect/settling.ts +229 -44
  30. package/cli/commands/inspect/wait-idle.ts +3 -2
  31. package/cli/commands/inspect.ts +105 -93
  32. package/cli/commands/install-cli.ts +36 -15
  33. package/cli/commands/install-desktop.ts +68 -195
  34. package/cli/commands/kotlin.ts +6 -5
  35. package/cli/commands/login.ts +5 -5
  36. package/cli/commands/maestro-compare.ts +554 -0
  37. package/cli/commands/maestro-generate.ts +6 -3
  38. package/cli/commands/maestro.ts +22 -0
  39. package/cli/commands/mode.ts +2 -2
  40. package/cli/commands/no-bridge-hint.ts +6 -2
  41. package/cli/commands/platform.ts +35 -6
  42. package/cli/commands/preview.ts +6 -0
  43. package/cli/commands/project-report.ts +10 -12
  44. package/cli/commands/record.ts +37 -37
  45. package/cli/commands/runtime.ts +9 -9
  46. package/cli/commands/screenshot-layers.ts +4 -4
  47. package/cli/commands/screenshot-mode.ts +2 -2
  48. package/cli/commands/screenshot.ts +8 -8
  49. package/cli/commands/screenshots-capture.ts +2 -2
  50. package/cli/commands/serve.ts +9 -12
  51. package/cli/commands/setup.ts +29 -14
  52. package/cli/commands/shell-boolean-mode.ts +2 -2
  53. package/cli/commands/show-touches-mode.ts +2 -2
  54. package/cli/commands/skills.ts +11 -11
  55. package/cli/commands/swift.ts +29 -11
  56. package/cli/commands/telemetry.ts +2 -2
  57. package/cli/commands/test.ts +5 -0
  58. package/cli/commands/three-mode.ts +3 -3
  59. package/cli/commands/timeline.ts +4 -4
  60. package/cli/commands/upgrade.ts +2 -2
  61. package/cli/commands/upload.ts +15 -15
  62. package/cli/commands/what-happened.ts +2 -2
  63. package/cli/current-sim.ts +6 -3
  64. package/cli/desktop-companion.ts +55 -149
  65. package/cli/dev-build-freshness.ts +2 -2
  66. package/cli/drivers/desktop.ts +17 -9
  67. package/cli/drivers/electron.ts +1 -2
  68. package/cli/drivers/playwright-sim-host.ts +11 -2
  69. package/cli/drivers/playwright.ts +35 -2
  70. package/cli/drivers/types.ts +8 -0
  71. package/cli/maestro-compare.ts +670 -0
  72. package/cli/outbound-endpoints.ts +46 -8
  73. package/cli/recording-access.ts +6 -6
  74. package/cli/run-registry.ts +1 -1
  75. package/cli/runtime-compat.ts +5 -1
  76. package/cli/send-to-box.ts +3 -3
  77. package/cli/shell-init.ts +2 -1
  78. package/cli/ws-bridge.ts +9 -5
  79. package/detox/element-types.ts +1 -0
  80. package/detox/gestures.ts +3 -3
  81. package/detox/index.ts +67 -38
  82. package/detox/navigation-lifecycle.ts +9 -6
  83. package/detox/proof-frame.cjs +6 -4
  84. package/dist-lib/agent-daemon-client.cjs +1 -1
  85. package/dist-lib/agent-events.cjs +1 -1
  86. package/dist-lib/attached-projects.cjs +1 -1
  87. package/dist-lib/auth/shared-session.cjs +1 -1
  88. package/dist-lib/backend-origin.cjs +1 -1
  89. package/dist-lib/beta.cjs +1 -1
  90. package/dist-lib/beta.mjs +1 -1
  91. package/dist-lib/bridge-constants.cjs +1 -1
  92. package/dist-lib/bridge-contract-input.cjs +42 -17
  93. package/dist-lib/bridge-contract-input.mjs +41 -16
  94. package/dist-lib/bridge-contract.cjs +1 -4
  95. package/dist-lib/bridge-contract.mjs +1 -3
  96. package/dist-lib/bundle-channel.cjs +2918 -550
  97. package/dist-lib/capture-contract.cjs +65 -24
  98. package/dist-lib/capture-contract.mjs +63 -24
  99. package/dist-lib/cloud-contract.cjs +2 -2
  100. package/dist-lib/cloud-contract.mjs +2 -2
  101. package/dist-lib/cloud-test-args.cjs +1 -1
  102. package/dist-lib/cloud-test-args.mjs +1 -1
  103. package/dist-lib/cloud.cjs +3 -3
  104. package/dist-lib/cloud.mjs +3 -3
  105. package/dist-lib/config.cjs +15 -5
  106. package/dist-lib/config.mjs +154 -0
  107. package/dist-lib/detox/index.cjs +495 -139
  108. package/dist-lib/dev-bundle-resolution.cjs +1 -1
  109. package/dist-lib/expo-swift-bundle.cjs +1168 -238
  110. package/dist-lib/expo-swift-metadata.cjs +34 -23
  111. package/dist-lib/expo-swift-metadata.mjs +32 -23
  112. package/dist-lib/home-paths.cjs +2 -2
  113. package/dist-lib/host/bridge-host.cjs +2938 -654
  114. package/dist-lib/host/fetch-proxy-handler.cjs +14 -1
  115. package/dist-lib/host/fetch-proxy-overrides.cjs +7 -1
  116. package/dist-lib/host/fetch-proxy-overrides.mjs +5 -1
  117. package/dist-lib/host/replacement-module-handler.cjs +2 -2
  118. package/dist-lib/host/websocket-proxy.cjs +1 -1
  119. package/dist-lib/index.cjs +999 -353
  120. package/dist-lib/jump-to-source-babel.cjs +1 -1
  121. package/dist-lib/jump-to-source-native.cjs +1 -1
  122. package/dist-lib/kotlin-host.cjs +332 -19
  123. package/dist-lib/menu.cjs +1 -1
  124. package/dist-lib/menu.mjs +1 -1
  125. package/dist-lib/metro-fingerprint-registry.cjs +1 -1
  126. package/dist-lib/metro-fingerprint-registry.mjs +1 -1
  127. package/dist-lib/metro-production-bundle.cjs +7 -7
  128. package/dist-lib/metro-production-bundle.mjs +5 -5
  129. package/dist-lib/metro.cjs +688 -250
  130. package/dist-lib/native-app-identity-project.cjs +2647 -0
  131. package/dist-lib/native-app-identity-project.mjs +1257 -0
  132. package/dist-lib/native-app-identity.cjs +325 -25
  133. package/dist-lib/native-app-identity.mjs +323 -25
  134. package/dist-lib/native-firebase-metadata.cjs +1 -1
  135. package/dist-lib/native-firebase-metadata.mjs +1 -1
  136. package/dist-lib/plist.cjs +1 -1
  137. package/dist-lib/plist.mjs +1 -1
  138. package/dist-lib/profiles.cjs +1 -1
  139. package/dist-lib/public-brand.cjs +1 -1
  140. package/dist-lib/react-native-host-modules.cjs +5 -1
  141. package/dist-lib/react-native-host-modules.mjs +5 -1
  142. package/dist-lib/scripts/dev-server-scanner.cjs +57 -32
  143. package/dist-lib/sdk.cjs +231 -51
  144. package/dist-lib/sdk.mjs +231 -51
  145. package/dist-lib/skills.cjs +4107 -1374
  146. package/dist-lib/swift-compile.cjs +290 -30
  147. package/dist-lib/swift-compile.mjs +290 -30
  148. package/dist-lib/swift-package.cjs +143 -12
  149. package/dist-lib/swift-package.mjs +137 -11
  150. package/dist-lib/swift-submit.cjs +47 -5
  151. package/dist-lib/swift-submit.mjs +42 -5
  152. package/dist-lib/swift-toolchain.cjs +7 -2
  153. package/dist-lib/swift.cjs +1 -1
  154. package/dist-lib/vite-swift.cjs +1392 -458
  155. package/dist-lib/vite.cjs +13 -17
  156. package/dist-lib/worklet-executable.cjs +248 -33
  157. package/dist-lib/worklet-executable.mjs +248 -33
  158. package/install.sh +10 -1
  159. package/kotlin/{AppSources → AndroidViewSources}/android/content/res/Resources.kt +9 -9
  160. package/kotlin/AndroidViewSources/android/graphics/Canvas.kt +20 -0
  161. package/kotlin/AndroidViewSources/android/view/View.kt +211 -0
  162. package/kotlin/AndroidViewSources/android/widget/FrameLayout.kt +61 -0
  163. package/kotlin/AndroidViewSources/android/widget/LinearLayout.kt +49 -0
  164. package/kotlin/AppSources/android/view/Window.kt +36 -0
  165. package/kotlin/AppSources/android/view/WindowInsets.kt +9 -0
  166. package/kotlin/AppSources/android/view/WindowInsetsController.kt +5 -0
  167. package/kotlin/AppSources/android/window/OnBackInvokedDispatcher.kt +48 -0
  168. package/kotlin/AppSources/androidx/activity/OnBackPressedDispatcher.kt +20 -5
  169. package/kotlin/AppSources/androidx/compose/foundation/layout/WindowInsets.kt +15 -0
  170. package/kotlin/AppSources/androidx/compose/ui/platform/ComposeView.kt +2 -1
  171. package/kotlin/AppSources/dev/peach/app/PeachApp.kt +42 -50
  172. package/kotlin/ComposeSources/PeachComposeHost.kt +200 -84
  173. package/kotlin/ExpoComposeSources/PeachAndroidView.kt +87 -0
  174. package/kotlin/ExpoComposeSources/PeachExpoViewMount.kt +24 -0
  175. package/kotlin/ExpoComposeSources/dev/peach/expo/PeachExpoCompose.kt +28 -0
  176. package/kotlin/ExpoComposeSources/expo/modules/kotlin/views/ExpoComposeView.kt +70 -0
  177. package/kotlin/ExpoSources/dev/peach/expo/PeachExpoRuntime.kt +513 -0
  178. package/kotlin/ExpoSources/dev/peach/expo/PeachExpoTypes.kt +173 -0
  179. package/kotlin/ExpoSources/expo/modules/kotlin/AppContext.kt +20 -0
  180. package/kotlin/ExpoSources/expo/modules/kotlin/Promise.kt +19 -0
  181. package/kotlin/ExpoSources/expo/modules/kotlin/classcomponent/ClassComponentBuilder.kt +198 -0
  182. package/kotlin/ExpoSources/expo/modules/kotlin/exception/CodedException.kt +126 -0
  183. package/kotlin/ExpoSources/expo/modules/kotlin/functions/Functions.kt +132 -0
  184. package/kotlin/ExpoSources/expo/modules/kotlin/jni/JavaScriptTypedArray.kt +81 -0
  185. package/kotlin/ExpoSources/expo/modules/kotlin/modules/Module.kt +98 -0
  186. package/kotlin/ExpoSources/expo/modules/kotlin/objects/ObjectDefinitionBuilder.kt +236 -0
  187. package/kotlin/ExpoSources/expo/modules/kotlin/records/Record.kt +12 -0
  188. package/kotlin/ExpoSources/expo/modules/kotlin/runtime/Runtime.kt +9 -0
  189. package/kotlin/ExpoSources/expo/modules/kotlin/sharedobjects/SharedObject.kt +50 -0
  190. package/kotlin/ExpoSources/expo/modules/kotlin/sharedobjects/SharedObjectRegistry.kt +57 -0
  191. package/kotlin/ExpoSources/expo/modules/kotlin/sharedobjects/SharedRef.kt +19 -0
  192. package/kotlin/ExpoSources/expo/modules/kotlin/typedarray/ConcreteTypedArrays.kt +158 -0
  193. package/kotlin/ExpoSources/expo/modules/kotlin/typedarray/GenericTypedArray.kt +8 -0
  194. package/kotlin/ExpoSources/expo/modules/kotlin/typedarray/TypedArray.kt +29 -0
  195. package/kotlin/ExpoSources/expo/modules/kotlin/typedarray/TypedArrayIterator.kt +9 -0
  196. package/kotlin/ExpoSources/expo/modules/kotlin/types/AnyType.kt +155 -0
  197. package/kotlin/ExpoSources/expo/modules/kotlin/types/Either.kt +91 -0
  198. package/kotlin/ExpoSources/expo/modules/kotlin/types/Enumerable.kt +6 -0
  199. package/kotlin/ExpoSources/expo/modules/kotlin/viewevent/ViewEvent.kt +54 -0
  200. package/kotlin/ExpoSources/expo/modules/kotlin/views/ExpoView.kt +23 -0
  201. package/kotlin/ExpoSources/expo/modules/kotlin/views/ViewDefinitionBuilder.kt +71 -0
  202. package/kotlin/ExpoSources/expo/modules/kotlin/views/ViewProps.kt +35 -0
  203. package/kotlin/README.md +13 -3
  204. package/kotlin/Sources/android/os/Build.kt +17 -0
  205. package/kotlin/Sources/android/os/Handler.kt +35 -0
  206. package/kotlin/Sources/android/os/Looper.kt +17 -0
  207. package/kotlin/Sources/android/os/MessageQueue.kt +56 -0
  208. package/kotlin/Sources/android/os/SystemClock.kt +14 -0
  209. package/kotlin/Sources/android/util/Log.kt +64 -0
  210. package/kotlin/Sources/java/security/MessageDigest.kt +114 -0
  211. package/kotlin/Sources/kotlin/text/StringFormat.kt +9 -0
  212. package/kotlin/Sources/org/json/JSON.kt +44 -0
  213. package/kotlin/fixtures/counter/Counter.d.kt.ts +1 -0
  214. package/kotlin/fixtures/counter/Counter.kt +10 -3
  215. package/kotlin/fixtures/counter/CounterApp.native.tsx +1 -1
  216. package/kotlin/fixtures/counter/vite.config.ts +2 -2
  217. package/kotlin/fixtures/headless-counter/Headless.kt +7 -6
  218. package/kotlin/fixtures/import-animation/Animation.d.kt.ts +7 -0
  219. package/kotlin/fixtures/import-animation/Animation.kt +80 -0
  220. package/kotlin/fixtures/import-animation/ImportAnimation.native.tsx +13 -0
  221. package/kotlin/fixtures/import-animation/README.md +24 -0
  222. package/kotlin/fixtures/import-animation/app/_layout.tsx +4 -0
  223. package/kotlin/fixtures/import-animation/app/index+spa.tsx +1 -0
  224. package/kotlin/fixtures/import-animation/app/routes.d.ts +18 -0
  225. package/kotlin/fixtures/import-animation/package.json +23 -0
  226. package/kotlin/fixtures/import-animation/tsconfig.json +20 -0
  227. package/kotlin/fixtures/import-animation/vite.config.ts +20 -0
  228. package/kotlin/fixtures/import-editor/Editor.d.kt.ts +7 -0
  229. package/kotlin/fixtures/import-editor/Editor.kt +59 -0
  230. package/kotlin/fixtures/import-editor/ImportEditor.native.tsx +23 -0
  231. package/kotlin/fixtures/import-editor/README.md +27 -0
  232. package/kotlin/fixtures/import-editor/app/_layout.tsx +4 -0
  233. package/kotlin/fixtures/import-editor/app/index+spa.tsx +1 -0
  234. package/kotlin/fixtures/import-editor/package.json +23 -0
  235. package/kotlin/fixtures/import-editor/tsconfig.json +20 -0
  236. package/kotlin/fixtures/import-editor/vite.config.ts +20 -0
  237. package/kotlin/fixtures/import-effects/Effects.d.kt.ts +7 -0
  238. package/kotlin/fixtures/import-effects/Effects.kt +87 -0
  239. package/kotlin/fixtures/import-effects/ImportEffects.native.tsx +13 -0
  240. package/kotlin/fixtures/import-effects/README.md +19 -0
  241. package/kotlin/fixtures/import-effects/app/_layout.tsx +4 -0
  242. package/kotlin/fixtures/import-effects/app/index+spa.tsx +1 -0
  243. package/kotlin/fixtures/import-effects/package.json +23 -0
  244. package/kotlin/fixtures/import-effects/tsconfig.json +20 -0
  245. package/kotlin/fixtures/import-effects/vite.config.ts +20 -0
  246. package/kotlin/fixtures/import-layout/ImportLayout.native.tsx +13 -0
  247. package/kotlin/fixtures/import-layout/Layout.d.kt.ts +7 -0
  248. package/kotlin/fixtures/import-layout/Layout.kt +72 -0
  249. package/kotlin/fixtures/import-layout/README.md +24 -0
  250. package/kotlin/fixtures/import-layout/app/_layout.tsx +4 -0
  251. package/kotlin/fixtures/import-layout/app/index+spa.tsx +1 -0
  252. package/kotlin/fixtures/import-layout/package.json +23 -0
  253. package/kotlin/fixtures/import-layout/tsconfig.json +20 -0
  254. package/kotlin/fixtures/import-layout/vite.config.ts +20 -0
  255. package/kotlin/fixtures/import-navigation/ImportNavigation.native.tsx +18 -0
  256. package/kotlin/fixtures/import-navigation/Navigation.d.kt.ts +7 -0
  257. package/kotlin/fixtures/import-navigation/Navigation.kt +104 -0
  258. package/kotlin/fixtures/import-navigation/README.md +31 -0
  259. package/kotlin/fixtures/import-navigation/app/_layout.tsx +4 -0
  260. package/kotlin/fixtures/import-navigation/app/index+spa.tsx +1 -0
  261. package/kotlin/fixtures/import-navigation/package.json +23 -0
  262. package/kotlin/fixtures/import-navigation/tsconfig.json +20 -0
  263. package/kotlin/fixtures/import-navigation/vite.config.ts +20 -0
  264. package/kotlin/fixtures/import-overlays/ImportOverlays.native.tsx +29 -0
  265. package/kotlin/fixtures/import-overlays/Overlays.d.kt.ts +7 -0
  266. package/kotlin/fixtures/import-overlays/Overlays.kt +93 -0
  267. package/kotlin/fixtures/import-overlays/README.md +34 -0
  268. package/kotlin/fixtures/import-overlays/app/_layout.tsx +4 -0
  269. package/kotlin/fixtures/import-overlays/app/index+spa.tsx +1 -0
  270. package/kotlin/fixtures/import-overlays/package.json +23 -0
  271. package/kotlin/fixtures/import-overlays/tsconfig.json +20 -0
  272. package/kotlin/fixtures/import-overlays/vite.config.ts +20 -0
  273. package/kotlin/fixtures/import-scroll/ImportScroll.native.tsx +13 -0
  274. package/kotlin/fixtures/import-scroll/README.md +23 -0
  275. package/kotlin/fixtures/import-scroll/Scroll.d.kt.ts +7 -0
  276. package/kotlin/fixtures/import-scroll/Scroll.kt +91 -0
  277. package/kotlin/fixtures/import-scroll/app/_layout.tsx +4 -0
  278. package/kotlin/fixtures/import-scroll/app/index+spa.tsx +1 -0
  279. package/kotlin/fixtures/import-scroll/package.json +23 -0
  280. package/kotlin/fixtures/import-scroll/tsconfig.json +20 -0
  281. package/kotlin/fixtures/import-scroll/vite.config.ts +20 -0
  282. package/kotlin/fixtures/import-selection/ImportSelection.native.tsx +44 -0
  283. package/kotlin/fixtures/import-selection/README.md +32 -0
  284. package/kotlin/fixtures/import-selection/Selection.d.kt.ts +8 -0
  285. package/kotlin/fixtures/import-selection/Selection.kt +78 -0
  286. package/kotlin/fixtures/import-selection/app/_layout.tsx +4 -0
  287. package/kotlin/fixtures/import-selection/app/index+spa.tsx +1 -0
  288. package/kotlin/fixtures/import-selection/package.json +23 -0
  289. package/kotlin/fixtures/import-selection/tsconfig.json +20 -0
  290. package/kotlin/fixtures/import-selection/vite.config.ts +20 -0
  291. package/kotlin/fixtures/import-surfaces/ImportSurfaces.native.tsx +13 -0
  292. package/kotlin/fixtures/import-surfaces/README.md +22 -0
  293. package/kotlin/fixtures/import-surfaces/Surfaces.d.kt.ts +7 -0
  294. package/kotlin/fixtures/import-surfaces/Surfaces.kt +64 -0
  295. package/kotlin/fixtures/import-surfaces/app/_layout.tsx +4 -0
  296. package/kotlin/fixtures/import-surfaces/app/index+spa.tsx +1 -0
  297. package/kotlin/fixtures/import-surfaces/package.json +23 -0
  298. package/kotlin/fixtures/import-surfaces/tsconfig.json +20 -0
  299. package/kotlin/fixtures/import-surfaces/vite.config.ts +20 -0
  300. package/kotlin/fixtures/import-values/ImportValues.native.tsx +35 -0
  301. package/kotlin/fixtures/import-values/README.md +23 -0
  302. package/kotlin/fixtures/import-values/Values.d.kt.ts +11 -0
  303. package/kotlin/fixtures/import-values/Values.kt +66 -0
  304. package/kotlin/fixtures/import-values/app/_layout.tsx +4 -0
  305. package/kotlin/fixtures/import-values/app/index+spa.tsx +1 -0
  306. package/kotlin/fixtures/import-values/package.json +23 -0
  307. package/kotlin/fixtures/import-values/tsconfig.json +20 -0
  308. package/kotlin/fixtures/import-values/vite.config.ts +20 -0
  309. package/kotlin/fixtures/kotlin-app-variants/BarBehavior.kt +38 -0
  310. package/kotlin/fixtures/kotlin-app-variants/BarVisibility.kt +36 -0
  311. package/kotlin/fixtures/kotlin-app-variants/Ime.kt +3 -1
  312. package/kotlin/fixtures/kotlin-app-variants/TransientInsets.kt +50 -0
  313. package/kotlin/fork/NOTICE +6 -1
  314. package/kotlin/fork/README.md +15 -7
  315. package/kotlin/fork/build.sh +2 -0
  316. package/kotlin/fork/compose-worker-globals.patch +186 -4
  317. package/kotlin/fork/fork.json +2 -2
  318. package/kotlin/fork/skiko-worker-clock.patch +12 -0
  319. package/kotlin/toolchain.json +12 -12
  320. package/kotlin/tools/expo-types.main.kts +115 -0
  321. package/package.json +17 -23
  322. package/scripts/dev-server-scanner.ts +92 -66
  323. package/skills/contrast/SKILL.md +2 -2
  324. package/skills/peach-debug/SKILL.md +1 -1
  325. package/skills/peach-perf/SKILL.md +1 -1
  326. package/skills/peach-setup/SKILL.md +2 -1
  327. package/skills/peach-test/SKILL.md +8 -0
  328. package/src/assetCatalogContract.ts +43 -1
  329. package/src/bridge-contract-input.ts +46 -19
  330. package/src/bridge-contract.ts +0 -1
  331. package/src/bundle-channel.mjs +197 -84
  332. package/src/bundleGlobalFooter.ts +38 -0
  333. package/src/capture-contract.ts +107 -33
  334. package/src/cloud-contract.ts +5 -1
  335. package/src/cloud.ts +5 -2
  336. package/src/config.ts +22 -3
  337. package/src/configured-bundle-producer.ts +1 -1
  338. package/src/desktop-host-delivery.ts +95 -1
  339. package/src/expo-kotlin-bundle.ts +29 -0
  340. package/src/expo-kotlin-host.ts +201 -0
  341. package/src/expo-swift-host.ts +96 -76
  342. package/src/expoKotlinContract.ts +70 -0
  343. package/src/expoSwiftContract.ts +4 -25
  344. package/src/home-paths.ts +7 -4
  345. package/src/host/bridge-host.ts +23 -3
  346. package/src/host/fetch-proxy-handler.ts +13 -0
  347. package/src/host/fetch-proxy-overrides.ts +11 -0
  348. package/src/host/replacement-module-handler.ts +5 -2
  349. package/src/index.ts +3 -1
  350. package/src/kotlin-host.ts +463 -19
  351. package/src/metro-plugin.ts +56 -28
  352. package/src/metro-production-bundle.ts +2 -2
  353. package/src/native-app-identity-project.ts +190 -0
  354. package/src/native-app-identity.ts +398 -34
  355. package/src/native-firebase-config.ts +69 -15
  356. package/src/native-seam-manifest.ts +6 -0
  357. package/src/one-widget-discovery.ts +2 -1
  358. package/src/openstep-plist.ts +107 -0
  359. package/src/react-native-host-modules.ts +4 -0
  360. package/src/runtime-assets.ts +2 -2
  361. package/src/sim-client.ts +2 -2
  362. package/src/swift-binary-modules.ts +3 -1
  363. package/src/swift-compile.ts +286 -38
  364. package/src/swift-distribution-contract.ts +4 -0
  365. package/src/swift-distribution.lock.json +123 -12
  366. package/src/swift-distribution.ts +7 -3
  367. package/src/swift-host.ts +298 -83
  368. package/src/swift-package-manifest.ts +29 -2
  369. package/src/swift-package.ts +180 -10
  370. package/src/swift-packages.ts +4 -0
  371. package/src/swift-standin-emit.ts +4 -1
  372. package/src/swift-standins.ts +274 -15
  373. package/src/swift-submit.ts +62 -7
  374. package/src/swift-toolchain.ts +6 -1
  375. package/src/swiftContract.ts +126 -3
  376. package/src/vite-plugin-one.ts +13 -16
  377. package/src/vite-plugin-swift.ts +4 -5
  378. package/src/vite-plugin.ts +360 -144
  379. package/src/vite.d.ts +1 -1
  380. package/src/worker-system-chunks.ts +221 -0
  381. package/src/worklet-executable.ts +348 -38
  382. package/src/worklets-babel.ts +1 -1
  383. package/src/xcode-project.ts +243 -127
  384. package/dist-lib/agent-identity.cjs +0 -39
  385. package/dist-lib/agent-sessions.cjs +0 -760
  386. package/dist-lib/cli-constants.cjs +0 -32
  387. package/dist-lib/render-mode.cjs +0 -44
  388. package/kotlin/AppSources/android/view/View.kt +0 -74
  389. package/kotlin/AppSources/android/widget/FrameLayout.kt +0 -6
  390. package/src/render-mode.ts +0 -15
  391. /package/kotlin/{AppSources → AndroidViewSources}/android/content/Context.kt +0 -0
  392. /package/kotlin/{AppSources → AndroidViewSources}/android/content/res/Configuration.kt +0 -0
  393. /package/kotlin/{AppSources → AndroidViewSources}/android/graphics/Color.kt +0 -0
  394. /package/kotlin/{AppSources → AndroidViewSources}/android/util/DisplayMetrics.kt +0 -0
  395. /package/kotlin/{AppSources → AndroidViewSources}/dev/peach/app/JavaFormat.kt +0 -0
  396. /package/kotlin/{AppSources → Sources}/java/lang/Runnable.kt +0 -0
package/README.md CHANGED
@@ -95,7 +95,7 @@ graph TD
95
95
  TENANTW["tenant worker (guest RN app tree)"]
96
96
  end
97
97
  subgraph build["build-time plugins (src/)"]
98
- VITEONE["peachsim/vite = vite-plugin-one.ts (serve runtime at /__soot/)"]
98
+ VITEONE["peachsim/vite = vite-plugin-one.ts (serve runtime at /__peachsim/)"]
99
99
  VITERN["sootsim() = vite-plugin.ts (RN resolver + native stubs)"]
100
100
  METRO["peachsim/metro = metro-plugin.ts"]
101
101
  COMPAT["@sootsim/compat stub-manifest (native seams)"]
@@ -139,8 +139,8 @@ graph TD
139
139
  RTHTTP -->|"manifest + tarball"| CDN
140
140
  DRIVERS -->|"open sim page URL"| SHELLW
141
141
  SHELLW -->|"NativeUIRequest"| TENANTW
142
- VITEONE -->|"serve /__soot/ from"| RTDIR
143
- METRO -->|"serve /__soot/ from"| RTDIR
142
+ VITEONE -->|"serve /__peachsim/ from"| RTDIR
143
+ METRO -->|"serve /__peachsim/ from"| RTDIR
144
144
  VITEONE -.->|"launch electron app"| SHELLW
145
145
  VITERN -->|"alias react-native"| ENGINESHIM
146
146
  VITERN -->|"alias native pkgs"| COMPAT
@@ -165,7 +165,7 @@ An explicit `--base-url` keeps that shell's origin and uses its assets without
165
165
  installing an active runtime. A bridge started for that shell skips automatic
166
166
  runtime updates. An app's configured `runtimeVersion` still selects its installed,
167
167
  version-owned bridge origin.
168
- Electron is the native desktop surface. Team Machine sessions (`TM_SESSION`)
168
+ The native host supplies the desktop surface on Mac and Linux; Electron is Windows-only. Team Machine sessions (`TM_SESSION`)
169
169
  default to headless Playwright; `--headed` explicitly opens an isolated headed
170
170
  browser. System browser launches, including share links, login, box links and
171
171
  repo scripts, go through `src/host/open-url.ts`. That owner refuses a system
@@ -203,9 +203,9 @@ bridge always addresses the **shell worker**, never an undifferentiated sim.
203
203
 
204
204
  **Two distinct plugin families** round it out, and they must not be conflated:
205
205
 
206
- - The published **`peachsim/vite`** (`src/vite-plugin-one.ts`, `rnxPlugin`)
206
+ - The published **`peachsim/vite`** (`src/vite-plugin-one.ts`, `peachPlugin`)
207
207
  and **`peachsim/metro`** plugins do one thing: serve the installed runtime at
208
- `/__soot/` so an app's own dev server can host the sim shell. The Vite plugin accepts an
208
+ `/__peachsim/` so an app's own dev server can host the sim shell. The Vite plugin accepts an
209
209
  opt-in `open` option that launches a named Electron window when the dev server
210
210
  starts (skipped when `CI` or `PEACH_NO_OPEN` is set).
211
211
  - The internal **`sootsim()`** plugin (`src/vite-plugin.ts`) is a separate, much
@@ -236,13 +236,13 @@ the later trace URL notifies viewers without extending that duration.
236
236
  Maestro YAML header `onFlowStart` and `onFlowComplete` hooks reach the same
237
237
  runner as body hooks, including nested `runFlow` files. A header reset flow
238
238
  therefore runs before assertions in each flow.
239
- | sim drivers | Launch or attach the actual sim page. Playwright owns isolated browser profiles; Electron owns the native desktop surface. The current sim is reused unless `--new`, `--profile`, or `--ephemeral` requests a separate one. | `cli/drivers/index.ts`, `cli/drivers/registry.ts`, `cli/drivers/electron.ts`, `cli/drivers/playwright.ts` |
239
+ | sim drivers | Launch or attach the actual sim page. Playwright owns isolated browser profiles; the native host owns Mac and Linux desktop windows; Electron owns Windows windows. The current sim is reused unless `--new`, `--profile`, or `--ephemeral` requests a separate one. | `cli/drivers/index.ts`, `cli/drivers/registry.ts`, `cli/drivers/electron.ts`, `cli/drivers/playwright.ts` |
240
240
  | SootSimBridgeHost (serve/daemon) | The bridge process (`peach serve`, or autostarted as the daemon via launchd/systemd). HTTP+WS hub on `:7668`: relays commands between CLI clients and registered sims; also serves runtime HTTP, `/__server-scan`, and fetch/WS proxies. | `src/host/bridge-host.ts`, `cli/commands/serve.ts`, `cli/commands/daemon.ts`, `src/home-paths.ts` |
241
241
  | AgentHost + sessions | Agent-routing extension of the host: owns the single FIFO reader per agent session and fans `agent:event`/`session-status` pushes to every WS subscriber. Backed by agent-sessions + attached-projects stores. | `src/host/agent-host.ts`, `src/agent-sessions.ts`, `src/attached-projects.ts`, `src/agent-daemon-client.ts` |
242
242
  | host proxies + dev-server scan | Host-side helpers so a cross-origin tenant worker can reach local dev servers and same-origin APIs: `/__server-scan` discovers running metro/expo/vxrn/one servers; fetch + websocket proxies relay guest-app network through the daemon. | `src/host/fetch-proxy-handler.ts`, `src/host/websocket-proxy.ts`, `scripts/dev-server-scanner.ts`, `src/dev-bundle-resolution.ts` |
243
243
  | runtime delivery + Peach home | Versioned engine runtime management. The shared machinery owns manifest fetch, sha256 verification, channels, and auto-update. Peach binds it to `https://contrast.dev` and `~/.peach/runtimes/<version>`. A repo can select `runtimeVersion` without changing the machine default. | `src/runtime-delivery.ts`, `packages/contrast-runtime-delivery/`, `src/home-paths.ts`, `src/runtime-assets.ts` |
244
244
  | standalone CLI delivery | Cached stable-version checks, bounded interactive notices, and verified atomic executable replacement. | `cli/cli-update.ts`, `cli/commands/upgrade.ts`, `cli/commands/version.ts`, `src/cli-version.ts` |
245
- | `peachsim/vite` + `peachsim/metro` (runtime serving) | Published bundler plugins. Both serve the installed engine runtime at `/__soot/` from `~/.peach/runtimes/<version>` so an app's own dev server can host the sim shell; the Vite plugin can opt into a named Electron window. | `src/vite-plugin-one.ts`, `src/metro-plugin.ts`, `src/runtime-assets.ts` |
245
+ | `peachsim/vite` + `peachsim/metro` (runtime serving) | Published bundler plugins. Both serve the installed engine runtime at `/__peachsim/` from `~/.peach/runtimes/<version>` so an app's own dev server can host the sim shell; the Vite plugin can opt into a named Electron window. | `src/vite-plugin-one.ts`, `src/metro-plugin.ts`, `src/runtime-assets.ts` |
246
246
  | `sootsim()` vite resolver plugin (internal) | The large RN-resolution / native-stub vite plugin: aliases `react-native` to the engine shim, native packages to `@sootsim/compat` stubs, applies worklets/babel transforms, and instantiates `SootSimBridgeHost` in its dev server. Distinct from the published `peachsim/vite` export. | `src/vite-plugin.ts`, `src/worklets-babel.ts`, `packages/peach-compat/src/stub-manifest.ts` |
247
247
  | skills registry | Peach agent skills (`peach-setup`, `peach-debug`, `peach-test`, `peach-visual`, and the generated `contrast` index) installable with `peach skill`. | `skills/*.md`, `src/skills/registry.ts`, `src/skills/types.ts` |
248
248
 
@@ -418,7 +418,7 @@ peach detox # run Detox/Jest suites against Peach
418
418
  ```
419
419
 
420
420
  See the full command reference via `peach --help` or at
421
- `src/features/site/docs/rnx/cli/` in the Contrast repo.
421
+ `src/features/site/docs/peach/cli/` in the Contrast repo.
422
422
 
423
423
  Sim-scoped commands use the primary fallback only when one driveable sim is
424
424
  connected. With multiple driveable sims, the bridge lists their ids and refuses
@@ -441,21 +441,21 @@ The Vite plugin serves the installed Peach runtime from your existing dev server
441
441
 
442
442
  ```ts
443
443
  // vite.config.ts
444
- import { rnxPlugin } from 'peachsim/vite'
444
+ import { peachPlugin } from 'peachsim/vite'
445
445
 
446
446
  export default {
447
- plugins: [rnxPlugin({ open: { appName: 'My App' } })],
447
+ plugins: [peachPlugin({ open: { appName: 'My App' } })],
448
448
  }
449
449
  ```
450
450
 
451
451
  ```js
452
452
  // metro.config.js
453
453
  const { getDefaultConfig } = require('expo/metro-config')
454
- const { withRNX } = require('peachsim/metro')
454
+ const { withPeach } = require('peachsim/metro')
455
455
 
456
456
  const config = getDefaultConfig(__dirname)
457
457
 
458
- module.exports = withRNX(config)
458
+ module.exports = withPeach(config)
459
459
  ```
460
460
 
461
461
  Peach works directly with regular, unannotated Metro production JavaScript bundles
@@ -465,7 +465,7 @@ a supported input; pass Peach the JavaScript bundle produced before Hermes
465
465
  compilation.
466
466
 
467
467
  If an app has unusual dependencies or fingerprint inference fails, you can
468
- optionally wrap your Metro configuration with `withRNX(config)`. It takes no
468
+ optionally wrap your Metro configuration with `withPeach(config)`. It takes no
469
469
  options and enables two integrations:
470
470
 
471
471
  1. **Deterministic module identity:** appends Metro's exact module identity map
@@ -473,7 +473,7 @@ options and enables two integrations:
473
473
  bundle. Resolution, transforms, minification, and source maps stay the app's
474
474
  own, so the annotated bundle is the exact bundle you ship: the footer assigns
475
475
  one unused global on a device, and Hermes drops it during bytecode compilation.
476
- 2. **In-browser dev shell:** serves the Peach simulator shell at `/__soot/` on
476
+ 2. **In-browser dev shell:** serves the Peach simulator shell at `/__peachsim/` on
477
477
  the Metro dev server.
478
478
 
479
479
  The URL helpers request ordinary Metro graphs: `dev=false&minify=true` for
@@ -481,14 +481,14 @@ production and `dev=true&minify=false` for development.
481
481
 
482
482
  ```js
483
483
  const {
484
- toRNXProductionBundleUrl,
485
- toRNXDevelopmentBundleUrl,
484
+ toPeachProductionBundleUrl,
485
+ toPeachDevelopmentBundleUrl,
486
486
  } = require('peachsim/metro')
487
487
 
488
- const productionUrl = toRNXProductionBundleUrl(
488
+ const productionUrl = toPeachProductionBundleUrl(
489
489
  'http://localhost:8081/index.bundle?platform=ios',
490
490
  )
491
- const developmentUrl = toRNXDevelopmentBundleUrl(
491
+ const developmentUrl = toPeachDevelopmentBundleUrl(
492
492
  'http://localhost:8081/index.bundle?platform=ios',
493
493
  )
494
494
  ```
@@ -629,12 +629,12 @@ the generated `contrast` command index. The published CLI registry and generated
629
629
  website docs both come from `packages/peach-skills/`:
630
630
 
631
631
  - `packages/peachsim/skills/contrast/SKILL.md` is generated from that registry
632
- - `src/features/site/docs/rnx/cli/*` is generated output, not hand-edited
632
+ - `src/features/site/docs/peach/cli/*` is generated output, not hand-edited
633
633
 
634
634
  When command docs or examples drift, update `packages/peach-skills/` and run:
635
635
 
636
636
  ```sh
637
- bun run generate:sootsim-docs
637
+ bun run generate:peach-docs
638
638
  ```
639
639
 
640
640
  ## Runtime delivery (the Peach home and CDN)
@@ -650,7 +650,7 @@ verifies its sha256, and unpacks it under `~/.peach`:
650
650
  ```
651
651
  ~/.peach/
652
652
  ├── runtimes/
653
- │ ├── <active>/ unpacked engine assets (served at /__soot/)
653
+ │ ├── <active>/ unpacked engine assets (served at /__peachsim/)
654
654
  │ └── <rollback>/ previous version for rollback
655
655
  ├── cache/ legacy downloads; empty after cleanup
656
656
  ├── profiles/ persistent named app storage, without browser caches
@@ -691,6 +691,12 @@ CLI's cwd is never an asset/config fallback. Asset wires stay on each opened
691
691
  project URL, so one daemon can serve several attached projects without sharing
692
692
  font or splash state between them.
693
693
 
694
+ Bare iOS projects carry default Firebase options from their selected app
695
+ target's Google services build resource when the app delegate initializes
696
+ Firebase unconditionally during launch. Completed compile conditional blocks
697
+ before that call are allowed. Initialization inside an open conditional block
698
+ or after a possible early return is rejected.
699
+
694
700
  The first interactive CLI command after this cleanup generation finishes its
695
701
  requested work, then launches the same safe default plan in a detached
696
702
  maintenance process. The app and terminal command are usable before the legacy
@@ -1,6 +1,9 @@
1
1
  import fs from 'node:fs'
2
2
  import path from 'node:path'
3
- import { enumerateBundleColors } from '../../sootsim-swift/src/bundleAssets'
3
+ import {
4
+ enumerateBundleColors,
5
+ enumerateBundleImages,
6
+ } from '../../sootsim-swift/src/bundleAssets'
4
7
  import type { AppCatalog } from 'peach-engine/engine/app-catalog-wire'
5
8
 
6
9
  // the xcode build setting actool turns into the built Info.plist's
@@ -33,12 +36,16 @@ function catalogFiles(dir: string, found: string[]) {
33
36
  }
34
37
  }
35
38
 
36
- // the app's asset-catalog colors and its declared accent, read off the ios
37
- // project a prebuild or a bare app keeps on disk: the app target is the
38
- // folder holding the AppDelegate, the catalogs are its .xcassets, and the
39
- // accent is what its Info.plist or project build settings name. null when
40
- // the app has no ios project on disk or declares neither.
41
- export function discoverAppCatalog(appDir: string): AppCatalog | null {
39
+ // the app's asset-catalog colors and images and its declared accent, read off
40
+ // the ios project a prebuild or a bare app keeps on disk: the app target is
41
+ // the folder holding the AppDelegate, the catalogs are its .xcassets, and the
42
+ // accent is what its Info.plist or project build settings name. metro serves
43
+ // each picked raster at `<origin>/assets/<project-relative-path>`. null when
44
+ // the app has no ios project on disk or declares none of them.
45
+ export function discoverAppCatalog(
46
+ appDir: string,
47
+ metroOrigin: string,
48
+ ): AppCatalog | null {
42
49
  const iosDir = path.join(appDir, 'ios')
43
50
  const target = listDir(iosDir).find(
44
51
  (entry) =>
@@ -58,11 +65,14 @@ export function discoverAppCatalog(appDir: string): AppCatalog | null {
58
65
  const relative = files.map((file) =>
59
66
  path.relative(targetDir, file).split(path.sep).join(path.posix.sep),
60
67
  )
61
- const colors = enumerateBundleColors(relative, (candidate) =>
62
- candidate.endsWith('Contents.json')
63
- ? readText(path.join(targetDir, candidate))
64
- : null,
65
- )
68
+ const readContents = (candidate: string) =>
69
+ candidate.endsWith('Contents.json') ? readText(path.join(targetDir, candidate)) : null
70
+ const colors = enumerateBundleColors(relative, readContents)
71
+ const served = path.relative(appDir, targetDir).split(path.sep).join(path.posix.sep)
72
+ const images: AppCatalog['images'] = {}
73
+ for (const { name, file, scale } of enumerateBundleImages(relative, readContents)) {
74
+ ;(images[name] ??= []).push({ uri: `${metroOrigin}/assets/${served}/${file}`, scale })
75
+ }
66
76
  const pbxproj = listDir(iosDir).find((entry) => entry.name.endsWith('.xcodeproj'))
67
77
  const accentName =
68
78
  readText(path.join(targetDir, 'Info.plist'))?.match(PLIST_ACCENT)?.[1] ??
@@ -72,6 +82,11 @@ export function discoverAppCatalog(appDir: string): AppCatalog | null {
72
82
  )?.[1]
73
83
  : undefined) ??
74
84
  null
75
- if (accentName === null && Object.keys(colors).length === 0) return null
76
- return { colors, accentName }
85
+ if (
86
+ accentName === null &&
87
+ Object.keys(colors).length === 0 &&
88
+ Object.keys(images).length === 0
89
+ )
90
+ return null
91
+ return { colors, images, accentName }
77
92
  }
package/cli/app-fonts.ts CHANGED
@@ -305,10 +305,12 @@ export async function resolveMetroProjectRoot(
305
305
  const controller = new AbortController()
306
306
  const timer = setTimeout(() => controller.abort(), opts?.timeoutMs ?? 1500)
307
307
  let servedManifest = false
308
+ let metroRunning = false
308
309
  try {
309
310
  const status = await fetch(`${origin}/status`, { signal: controller.signal })
310
311
  const advertisedRoot = status.headers.get('X-React-Native-Project-Root')
311
312
  if (status.ok && advertisedRoot) return decodeURI(advertisedRoot)
313
+ metroRunning = status.ok && (await status.text()).trim() === 'packager-status:running'
312
314
  const res = await fetch(`${origin}/`, {
313
315
  headers: { 'expo-platform': 'ios', Accept: 'application/json' },
314
316
  signal: controller.signal,
@@ -330,9 +332,9 @@ export async function resolveMetroProjectRoot(
330
332
  // knows. if it did not name a project root there is none to learn, and asking
331
333
  // the OS about the listening process would answer with whatever directory
332
334
  // that process happens to sit in — which is the CLI's own cwd trap this
333
- // function exists to avoid. only a server that served no manifest at all gets
334
- // the process lookup.
335
- if (servedManifest) return null
335
+ // function exists to avoid. only a server whose status identifies Metro
336
+ // and which served no manifest gets the process lookup.
337
+ if (servedManifest || !metroRunning) return null
336
338
 
337
339
  const port = Number(new URL(origin).port)
338
340
  if (!Number.isFinite(port) || port <= 0) return null
@@ -548,6 +548,12 @@ export type SootSimFlowTraceStep = {
548
548
  screenshotPath?: string
549
549
  }
550
550
 
551
+ export type SootSimFlowConsole = {
552
+ errors: unknown[]
553
+ warnings: unknown[]
554
+ requests: unknown[]
555
+ }
556
+
551
557
  export interface MaestroStep {
552
558
  tapOn?:
553
559
  | string
@@ -2135,6 +2141,24 @@ export class SootSimBridgeFlowRunner {
2135
2141
  return model as DeviceModel
2136
2142
  }
2137
2143
 
2144
+ // the app's console errors and warnings plus failed requests, captured by
2145
+ // peach's native-globals hook and request recorder (same shape as
2146
+ // `peach get errors`).
2147
+ async readConsole(): Promise<SootSimFlowConsole> {
2148
+ return (await this.bridge.send({
2149
+ type: 'evaluate',
2150
+ simId: this.opts.simId,
2151
+ code: `(() => {
2152
+ const c = window.__sootsimConsole
2153
+ return {
2154
+ errors: c?.getErrors?.() ?? [],
2155
+ warnings: c?.getWarnings?.() ?? [],
2156
+ requests: (window.__sootsimGetFailedRequests?.() ?? []),
2157
+ }
2158
+ })()`,
2159
+ })) as SootSimFlowConsole
2160
+ }
2161
+
2138
2162
  async captureScreenshot(outputPath: string) {
2139
2163
  await this.takeScreenshot({ path: outputPath })
2140
2164
  }
@@ -2262,20 +2286,8 @@ export class SootSimBridgeFlowRunner {
2262
2286
  )
2263
2287
  if (typeof tree === 'string') write('tree.txt', tree)
2264
2288
 
2265
- // 6. console buffer + failed requests (captured by peach's native-globals
2266
- // hook / request recorder; same shape as `peach get errors` etc.).
2267
- const consoleBuf = await safeEval<{
2268
- errors: unknown[]
2269
- warnings: unknown[]
2270
- requests: unknown[]
2271
- } | null>(`(() => {
2272
- const c = window.__sootsimConsole
2273
- return {
2274
- errors: c?.getErrors?.() ?? [],
2275
- warnings: c?.getWarnings?.() ?? [],
2276
- requests: (window.__sootsimGetFailedRequests?.() ?? []),
2277
- }
2278
- })()`)
2289
+ // 6. console buffer + failed requests
2290
+ const consoleBuf = await this.readConsole().catch(() => null)
2279
2291
  if (consoleBuf) {
2280
2292
  write('console.json', JSON.stringify(consoleBuf, null, 2))
2281
2293
  }
@@ -2418,20 +2430,21 @@ export class SootSimBridgeFlowRunner {
2418
2430
  }
2419
2431
  }
2420
2432
 
2433
+ // maestro's waitForAnimationToEnd compares screenshots until the screen holds
2434
+ // still, so motion the screen does not show (an endless loop under a
2435
+ // full-screen sheet) never holds it; visible shell motion changes the pixels.
2436
+ // like maestro, a screen that never holds still (a looping illustration)
2437
+ // ends the wait at the timeout and the flow continues
2421
2438
  async waitForAnimationToEnd(timeoutMs: number = 2000) {
2422
2439
  const maxMs = Math.max(0, Math.round(timeoutMs))
2423
2440
  if (maxMs <= 0) return
2424
- const result = await waitForSootsimIdle({
2441
+ await waitForSootsimIdle({
2425
2442
  bridge: this.bridge,
2426
2443
  simId: this.opts.simId,
2427
2444
  maxMs,
2428
2445
  pollMs: 32,
2429
- stablePolls: 2,
2430
- strict: true,
2446
+ paintStill: true,
2431
2447
  })
2432
- if (!result.settled) {
2433
- throw new Error(`animation did not settle within ${maxMs}ms`)
2434
- }
2435
2448
  }
2436
2449
 
2437
2450
  async back() {
@@ -3411,7 +3424,7 @@ export class SootSimBridgeFlowRunner {
3411
3424
  this.lastFailedStep = null
3412
3425
  }
3413
3426
 
3414
- extractFlowBody(steps: MaestroStep[]) {
3427
+ extractFlowBody(steps: (MaestroStep | string)[]) {
3415
3428
  // maestro `onFlowStart` / `onFlowComplete` lifecycle hooks: if any step
3416
3429
  // in the array is a sole-key entry whose key is one of these names and
3417
3430
  // whose value is a list of sub-steps, pull them out and run them as
@@ -3419,7 +3432,9 @@ export class SootSimBridgeFlowRunner {
3419
3432
  const onStart: MaestroStep[] = []
3420
3433
  const onComplete: MaestroStep[] = []
3421
3434
  const body: MaestroStep[] = []
3422
- for (const step of steps) {
3435
+ for (const rawStep of steps) {
3436
+ const step =
3437
+ typeof rawStep === 'string' ? normalizeBareStringStep(rawStep) : rawStep
3423
3438
  const keys = Object.keys(step)
3424
3439
  if (
3425
3440
  keys.length === 1 &&
@@ -3442,15 +3457,11 @@ export class SootSimBridgeFlowRunner {
3442
3457
  return { onStart, onComplete, body }
3443
3458
  }
3444
3459
 
3445
- async planFlow(steps: MaestroStep[], options: { dryRun?: boolean } = {}) {
3460
+ async planFlow(steps: (MaestroStep | string)[], options: { dryRun?: boolean } = {}) {
3446
3461
  const { body } = this.extractFlowBody(steps)
3447
3462
  if (this.liveStatus) {
3448
3463
  await this.liveStatus.plan(
3449
- body.map((rawStep, index) => {
3450
- // yaml parses bare-string steps (`- back`) as strings; mirror
3451
- // runStep's normalization so the rail shows the verb, not chars.
3452
- const step =
3453
- typeof rawStep === 'string' ? normalizeBareStringStep(rawStep) : rawStep
3464
+ body.map((step, index) => {
3454
3465
  const name = Object.keys(step).find((k) => k !== 'when') ?? Object.keys(step)[0]
3455
3466
  return { index, name, target: this.flowStepTargetLabel(step, name) }
3456
3467
  }),
@@ -3459,7 +3470,7 @@ export class SootSimBridgeFlowRunner {
3459
3470
  }
3460
3471
  }
3461
3472
 
3462
- async runFlow(steps: MaestroStep[], options: { skipPlan?: boolean } = {}) {
3473
+ async runFlow(steps: (MaestroStep | string)[], options: { skipPlan?: boolean } = {}) {
3463
3474
  const { onStart, onComplete, body } = this.extractFlowBody(steps)
3464
3475
  this.lastInteractionAt = Date.now()
3465
3476
 
@@ -8,6 +8,7 @@ import {
8
8
  readFileSync,
9
9
  } from 'node:fs'
10
10
  import { dirname, extname, join, resolve } from 'node:path'
11
+ import { Script } from 'node:vm'
11
12
  import {
12
13
  embedMetroAssetFiles,
13
14
  extractAssetDescriptors,
@@ -256,10 +257,21 @@ async function loadBundleSource(bundleInput: string): Promise<string> {
256
257
  if (bundleInput.startsWith('http://') && !isLoopbackBundleUrl(bundleInput)) {
257
258
  throw new Error(`${REMOTE_COMMAND} bundle URL must use HTTPS`)
258
259
  }
259
- if (isRemoteBundleInput(bundleInput)) {
260
- return fetchBundleSource(bundleInput)
260
+ const source = isRemoteBundleInput(bundleInput)
261
+ ? await fetchBundleSource(bundleInput)
262
+ : readImmutableBundleSource(bundleInput)
263
+ // a box evaluates the bundle as a script; refuse invalid syntax before
264
+ // allocating a billable host. compiling does not execute guest code.
265
+ try {
266
+ new Script(source, { filename: bundleInput })
267
+ } catch (error) {
268
+ throw new Error(
269
+ `${REMOTE_COMMAND} bundle contains invalid JavaScript: ${
270
+ error instanceof Error ? error.message : String(error)
271
+ }`,
272
+ )
261
273
  }
262
- return readImmutableBundleSource(bundleInput)
274
+ return source
263
275
  }
264
276
 
265
277
  // `react-native bundle --assets-dest` dest paths are not always the JS
@@ -536,16 +548,19 @@ function unverifiedContentKey(
536
548
  function searchMetroAssetDest(
537
549
  bundlePath: string,
538
550
  descriptors: readonly MetroAssetDescriptor[],
551
+ assetsDirectory?: string,
539
552
  ): MetroAssetSearch {
540
553
  const bundleDirectory = dirname(resolve(bundlePath))
541
- const candidates = [
542
- join(bundleDirectory, 'assets'),
543
- bundleDirectory,
544
- resolve(bundleDirectory, '..', 'assets'),
545
- ]
554
+ const candidates = assetsDirectory
555
+ ? [resolve(assetsDirectory)]
556
+ : [
557
+ join(bundleDirectory, 'assets'),
558
+ bundleDirectory,
559
+ resolve(bundleDirectory, '..', 'assets'),
560
+ ]
546
561
  const cwd = process.cwd()
547
562
  let projectDirectory: string | null = null
548
- for (const start of [bundleDirectory, cwd]) {
563
+ for (const start of assetsDirectory ? [] : [bundleDirectory, cwd]) {
549
564
  let directory = resolve(start)
550
565
  while (true) {
551
566
  if (detectProject(directory)?.hasMetroConfig) {
@@ -653,7 +668,7 @@ async function inferCloudModuleIdentity(
653
668
  throw new Error(
654
669
  `${REMOTE_COMMAND} could not load the Metro fingerprint registry needed to identify this bundle: ${
655
670
  error instanceof Error ? error.message : String(error)
656
- }. Build it through the published Metro plugin: withRNX(config).`,
671
+ }. Build it through the published Metro plugin: withPeach(config).`,
657
672
  )
658
673
  }
659
674
  try {
@@ -672,7 +687,7 @@ async function inferCloudModuleIdentity(
672
687
  throw new Error(
673
688
  `${REMOTE_COMMAND} could not infer module identity for this Metro bundle: ${
674
689
  error instanceof Error ? error.message : String(error)
675
- }. Build it through the published Metro plugin: withRNX(config).`,
690
+ }. Build it through the published Metro plugin: withPeach(config).`,
676
691
  )
677
692
  }
678
693
  }
@@ -682,7 +697,7 @@ async function inferCloudModuleIdentity(
682
697
  * the runner executes: every asset scale is read from the asset graph and
683
698
  * embedded, module identity is normalized, and the JavaScript is minified.
684
699
  * an ordinary unannotated Metro production bundle is identified through the
685
- * published fingerprint registry; a withRNX footer remains the fast path.
700
+ * published fingerprint registry; a withPeach footer remains the fast path.
686
701
  */
687
702
  export interface CloudArtifactProduction {
688
703
  bytes: Buffer
@@ -701,8 +716,15 @@ export async function produceCloudArtifact(
701
716
  * the default; a box that accepts a larger artifact passes its own.
702
717
  */
703
718
  maxBytes: number = MAX_RNX_CLOUD_BUNDLE_BYTES,
719
+ assetsDirectory?: string,
704
720
  ): Promise<CloudArtifactProduction> {
705
- return produceArtifact(bundlePath, maxBytes, REMOTE_COMMAND, 'allow-placeholders')
721
+ return produceArtifact(
722
+ bundlePath,
723
+ maxBytes,
724
+ REMOTE_COMMAND,
725
+ assetsDirectory ? 'require-assets' : 'allow-placeholders',
726
+ assetsDirectory,
727
+ )
706
728
  }
707
729
 
708
730
  /** prepares a standalone preview whose bytes must represent every Metro asset. */
@@ -726,11 +748,13 @@ export interface CloudBoxArtifactProduction {
726
748
  /** prepares a prebuilt bundle for a Box by embedding any beside-the-bundle Metro assets. */
727
749
  export async function produceCloudBoxArtifact(
728
750
  bundlePath: string,
751
+ assetsDirectory?: string,
729
752
  ): Promise<CloudBoxArtifactProduction> {
730
753
  const prepared = await embedBundleAssets(
731
754
  bundlePath,
732
755
  'peach box create',
733
- 'allow-placeholders',
756
+ assetsDirectory ? 'require-assets' : 'allow-placeholders',
757
+ assetsDirectory,
734
758
  )
735
759
  const warnings: string[] = []
736
760
  if (prepared.search.unconfirmed && prepared.dest) {
@@ -754,6 +778,7 @@ async function embedBundleAssets(
754
778
  bundlePath: string,
755
779
  command: string,
756
780
  assetPolicy: 'allow-placeholders' | 'require-assets',
781
+ assetsDirectory?: string,
757
782
  ): Promise<{
758
783
  source: string
759
784
  code: string
@@ -774,11 +799,11 @@ async function embedBundleAssets(
774
799
  substituted: [],
775
800
  }
776
801
  }
777
- // an https bundle has no local tree to search, and a bundle with no assets
778
- // has nothing to search for.
779
- const search: MetroAssetSearch = !isRemoteBundleInput(bundlePath)
780
- ? searchMetroAssetDest(bundlePath, descriptors)
781
- : { dest: null, searched: [], truncated: false, unconfirmed: false }
802
+ // an https bundle uses only an explicit local asset destination.
803
+ const search: MetroAssetSearch =
804
+ assetsDirectory || !isRemoteBundleInput(bundlePath)
805
+ ? searchMetroAssetDest(bundlePath, descriptors, assetsDirectory)
806
+ : { dest: null, searched: [], truncated: false, unconfirmed: false }
782
807
  const dest = search.dest
783
808
  // `embedMetroAssetFiles` re-parses the bundle, so its descriptors are equal
784
809
  // to the ones the search placed but not the same objects.
@@ -854,8 +879,14 @@ async function produceArtifact(
854
879
  maxBytes: number,
855
880
  command: string,
856
881
  assetPolicy: 'allow-placeholders' | 'require-assets',
882
+ assetsDirectory?: string,
857
883
  ): Promise<CloudArtifactProduction> {
858
- const prepared = await embedBundleAssets(bundlePath, command, assetPolicy)
884
+ const prepared = await embedBundleAssets(
885
+ bundlePath,
886
+ command,
887
+ assetPolicy,
888
+ assetsDirectory,
889
+ )
859
890
  const artifact = await buildRnxCloudArtifact(prepared.code, {
860
891
  inferModuleIdentity: inferCloudModuleIdentity,
861
892
  })
@@ -1040,6 +1071,7 @@ export async function createCloudBox(options: CreateCloudBoxOptions): Promise<{
1040
1071
 
1041
1072
  export interface CreateRemoteSimOptions {
1042
1073
  bundlePath: string
1074
+ assetsDirectory?: string
1043
1075
  device: string
1044
1076
  authorization: string
1045
1077
  /** the account to bill when the authorization is a session token; an api key names its own */
@@ -1110,7 +1142,11 @@ export async function createRemoteSim(options: CreateRemoteSimOptions): Promise<
1110
1142
  reused: boolean
1111
1143
  }> {
1112
1144
  const apiOrigin = resolveCloudOrigin(options.apiOrigin)
1113
- const artifact = await produceCloudArtifact(options.bundlePath)
1145
+ const artifact = await produceCloudArtifact(
1146
+ options.bundlePath,
1147
+ undefined,
1148
+ options.assetsDirectory,
1149
+ )
1114
1150
  const query = new URLSearchParams({
1115
1151
  labels: `peach.remote=1,peach.platform=${options.platform},peach.bundle=${artifact.sha256.slice(0, 12)}`,
1116
1152
  wait: 'true',
@@ -110,7 +110,7 @@ export function startCloudTestTokenProxy(
110
110
  upstreamPort: number,
111
111
  sidecarPorts: readonly number[],
112
112
  token: string,
113
- ): Promise<{ close: () => void } | null> {
113
+ ): Promise<{ port: number; close: () => void } | null> {
114
114
  const server = createServer((req, res) => {
115
115
  const auth = req.headers.authorization || ''
116
116
  if (auth !== `Bearer ${token}`) {
@@ -177,7 +177,11 @@ export function startCloudTestTokenProxy(
177
177
  return new Promise((resolve) => {
178
178
  server.on('error', () => resolve(null))
179
179
  server.listen(listenPort, '127.0.0.1', () => {
180
- resolve({ close: () => server.close() })
180
+ const address = server.address()
181
+ if (!address || typeof address !== 'object') {
182
+ throw new Error('token proxy has no listening address')
183
+ }
184
+ resolve({ port: address.port, close: () => server.close() })
181
185
  })
182
186
  })
183
187
  }
@@ -187,7 +191,7 @@ function cloudflaredAvailable(): boolean {
187
191
  return probe.status === 0
188
192
  }
189
193
 
190
- async function ensureCloudflared(): Promise<boolean> {
194
+ export async function ensureCloudflared(): Promise<boolean> {
191
195
  if (cloudflaredAvailable()) return true
192
196
  const archMap: Record<string, string> = { x64: 'amd64', arm64: 'arm64' }
193
197
  const arch = archMap[process.arch]
@@ -232,7 +236,7 @@ function childAlive(child: ChildProcess): boolean {
232
236
  return child.exitCode === null && child.signalCode === null
233
237
  }
234
238
 
235
- async function launchQuickTunnel(
239
+ export async function launchQuickTunnel(
236
240
  originUrl: string,
237
241
  hostHeader: string,
238
242
  logFile: string,
@@ -297,23 +301,13 @@ export async function publishLiveDevStack(args: {
297
301
  }): Promise<PublishedLiveDevStack> {
298
302
  const { devPort, sidecarPorts } = args
299
303
  const jobToken = randomBytes(16).toString('hex')
300
- // free loopback port for the token proxy; the advertised authority stays
304
+ // let the token proxy own its port; the advertised authority stays
301
305
  // the dev server's own port so Metro URLs resolve on the recorder.
302
- const { Server } = await import('node:net')
303
- const proxyPort = await new Promise<number>((resolve, reject) => {
304
- const probe = new Server()
305
- probe.once('error', reject)
306
- probe.listen(0, '127.0.0.1', () => {
307
- const address = probe.address()
308
- const port = address && typeof address === 'object' ? address.port : 0
309
- probe.close(() => resolve(port))
310
- })
311
- })
312
- if (!proxyPort) throw new Error('could not allocate a tunnel proxy port')
313
- const proxy = await startCloudTestTokenProxy(proxyPort, devPort, sidecarPorts, jobToken)
306
+ const proxy = await startCloudTestTokenProxy(0, devPort, sidecarPorts, jobToken)
314
307
  if (!proxy) {
315
- throw new Error(`could not start the tunnel token proxy on 127.0.0.1:${proxyPort}`)
308
+ throw new Error('could not start the tunnel token proxy on 127.0.0.1')
316
309
  }
310
+ const proxyPort = proxy.port
317
311
  const closeProxy = proxy.close
318
312
  const hostHeader = `127.0.0.1:${devPort}`
319
313
  if (!(await ensureCloudflared())) {
@@ -37,7 +37,7 @@ import type { AgentEvent } from '../../src/agent-events'
37
37
  import type { AgentInspectMessage } from '../../src/agent-prompt'
38
38
 
39
39
  // the bridge this invocation talks to: `-p`, else the same dev-bridge or
40
- // daemon pick every other command makes (PORT_OFFSET and SOOTSIM_BRIDGE_PORT
40
+ // daemon pick every other command makes (PORT_OFFSET and PEACH_BRIDGE_PORT
41
41
  // included).
42
42
  let daemonPort = 0
43
43