peachsim 0.1.527 → 0.1.571

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 (169) hide show
  1. package/README.md +28 -28
  2. package/cli/app-fonts.ts +1 -1
  3. package/cli/app-splash.ts +1 -1
  4. package/cli/bin.ts +1 -1
  5. package/cli/bridge-diagnostics.ts +1 -1
  6. package/cli/bridge-flow-runner.ts +72 -12
  7. package/cli/cloud-client.ts +273 -6
  8. package/cli/cloud-dispatch.ts +23 -2
  9. package/cli/cloud-session.ts +60 -4
  10. package/cli/cloud-test-result-files.ts +97 -0
  11. package/cli/cloud-test-tunnel.ts +354 -0
  12. package/cli/commands/box/bundle-plane.ts +2 -2
  13. package/cli/commands/box/checkout-plane.ts +1 -1
  14. package/cli/commands/box/rnx-command.ts +2 -2
  15. package/cli/commands/box/search-command.ts +2 -2
  16. package/cli/commands/box.ts +8 -8
  17. package/cli/commands/compat.ts +1 -1
  18. package/cli/commands/control.ts +22 -3
  19. package/cli/commands/detox.ts +1 -1
  20. package/cli/commands/device.ts +1 -1
  21. package/cli/commands/diagnose.ts +1 -1
  22. package/cli/commands/film.ts +1 -1
  23. package/cli/commands/flow.ts +60 -3
  24. package/cli/commands/inspect/actions.ts +1 -1
  25. package/cli/commands/inspect/core.ts +6 -7
  26. package/cli/commands/inspect/find.ts +2 -1
  27. package/cli/commands/inspect/resolve-target.ts +5 -3
  28. package/cli/commands/inspect/shared.ts +1 -1
  29. package/cli/commands/inspect/wait-event.ts +1 -1
  30. package/cli/commands/inspect/wait-selector.ts +15 -4
  31. package/cli/commands/inspect.ts +19 -14
  32. package/cli/commands/maestro.ts +582 -69
  33. package/cli/commands/permissions.ts +1 -1
  34. package/cli/commands/platform.ts +205 -8
  35. package/cli/commands/preview.ts +2 -2
  36. package/cli/commands/record.ts +2 -2
  37. package/cli/commands/report-issue.ts +2 -2
  38. package/cli/commands/screenshot-layers.ts +1 -1
  39. package/cli/commands/screenshot.ts +1 -1
  40. package/cli/commands/setup.ts +2 -2
  41. package/cli/commands/test.ts +600 -117
  42. package/cli/commands/three-mode.ts +1 -1
  43. package/cli/commands/upload.ts +5 -5
  44. package/cli/commands/what-happened.ts +1 -1
  45. package/cli/desktop-companion.ts +3 -3
  46. package/cli/drivers/playwright-sim-host.ts +2 -2
  47. package/cli/flow-file.ts +7 -0
  48. package/cli/flow-live-status.ts +65 -8
  49. package/cli/help-core.ts +3 -3
  50. package/cli/hidden-runtime-alias.ts +1 -1
  51. package/cli/maestro-report.ts +180 -0
  52. package/cli/main.ts +1 -1
  53. package/cli/outbound-endpoints.ts +187 -136
  54. package/cli/parse-args.ts +1 -1
  55. package/cli/registry.ts +1 -1
  56. package/cli/resolve-assets.ts +1 -1
  57. package/cli/run-registry.ts +10 -1
  58. package/cli/send-to-box.ts +4 -4
  59. package/cli/ws-bridge.ts +3 -5
  60. package/detox/expectations.ts +1 -1
  61. package/detox/gestures.ts +2 -2
  62. package/detox/index.ts +71 -29
  63. package/detox/jest-preset.cjs +5 -5
  64. package/detox/jest-setup-after-env.cjs +1 -1
  65. package/dist-lib/agent-daemon-client.cjs +2 -2
  66. package/dist-lib/agent-events.cjs +1 -1
  67. package/dist-lib/agent-identity.cjs +1 -1
  68. package/dist-lib/agent-sessions.cjs +2 -2
  69. package/dist-lib/attached-projects.cjs +2 -2
  70. package/dist-lib/auth/shared-session.cjs +1 -1
  71. package/dist-lib/backend-origin.cjs +1 -1
  72. package/dist-lib/beta.cjs +1 -1
  73. package/dist-lib/beta.mjs +1 -1
  74. package/dist-lib/bridge-constants.cjs +1 -1
  75. package/dist-lib/bridge-contract-input.cjs +1 -1
  76. package/dist-lib/bridge-contract-input.mjs +1 -1
  77. package/dist-lib/bridge-contract.cjs +1 -1
  78. package/dist-lib/bridge-contract.mjs +1 -1
  79. package/dist-lib/capture-contract.cjs +94 -3
  80. package/dist-lib/capture-contract.mjs +93 -3
  81. package/dist-lib/cli-constants.cjs +1 -1
  82. package/dist-lib/cloud-contract.cjs +1 -1
  83. package/dist-lib/cloud-contract.mjs +1 -1
  84. package/dist-lib/cloud-test-args.cjs +116 -0
  85. package/dist-lib/cloud-test-args.mjs +86 -0
  86. package/dist-lib/cloud.cjs +2 -2
  87. package/dist-lib/cloud.mjs +2 -2
  88. package/dist-lib/config.cjs +1 -1
  89. package/dist-lib/detox/index.cjs +106 -25
  90. package/dist-lib/detox/jest-preset.cjs +5 -5
  91. package/dist-lib/dev-bundle-resolution.cjs +1 -1
  92. package/dist-lib/home-paths.cjs +1 -1
  93. package/dist-lib/host/bridge-host.cjs +150 -65
  94. package/dist-lib/host/fetch-proxy-handler.cjs +28 -2
  95. package/dist-lib/host/fetch-proxy-overrides.cjs +33 -3
  96. package/dist-lib/host/fetch-proxy-overrides.mjs +30 -2
  97. package/dist-lib/host/replacement-module-handler.cjs +1 -1
  98. package/dist-lib/host/websocket-proxy.cjs +1 -1
  99. package/dist-lib/index.cjs +224 -41
  100. package/dist-lib/jump-to-source-babel.cjs +1 -1
  101. package/dist-lib/jump-to-source-native.cjs +1 -1
  102. package/dist-lib/menu.cjs +2 -2
  103. package/dist-lib/menu.mjs +2 -2
  104. package/dist-lib/metro-fingerprint-registry.cjs +1 -1
  105. package/dist-lib/metro-fingerprint-registry.mjs +1 -1
  106. package/dist-lib/metro-production-bundle.cjs +3 -3
  107. package/dist-lib/metro-production-bundle.mjs +3 -3
  108. package/dist-lib/metro.cjs +8 -8
  109. package/dist-lib/profiles.cjs +1 -1
  110. package/dist-lib/public-brand.cjs +2 -2
  111. package/dist-lib/react-native-host-modules.cjs +1 -1
  112. package/dist-lib/react-native-host-modules.mjs +1 -1
  113. package/dist-lib/render-mode.cjs +1 -1
  114. package/dist-lib/scripts/dev-server-scanner.cjs +31 -15
  115. package/dist-lib/sdk.cjs +3 -2
  116. package/dist-lib/sdk.mjs +3 -2
  117. package/dist-lib/skills.cjs +478 -266
  118. package/dist-lib/swift-compile.cjs +372 -0
  119. package/dist-lib/swift-compile.mjs +339 -0
  120. package/dist-lib/swift-package.cjs +78 -0
  121. package/dist-lib/swift-package.mjs +52 -0
  122. package/dist-lib/swift-submit.cjs +1 -1
  123. package/dist-lib/swift-submit.mjs +1 -1
  124. package/dist-lib/swift.cjs +583 -16
  125. package/dist-lib/vite.cjs +483 -150
  126. package/package.json +30 -9
  127. package/scripts/dev-server-scanner.ts +41 -14
  128. package/scripts/optional-demo-registry.ts +3 -3
  129. package/skills/contrast/SKILL.md +9 -9
  130. package/skills/rnx-debug/SKILL.md +14 -4
  131. package/skills/rnx-perf/SKILL.md +5 -5
  132. package/skills/rnx-setup/SKILL.md +11 -11
  133. package/skills/rnx-test/SKILL.md +14 -14
  134. package/skills/rnx-visual/SKILL.md +6 -6
  135. package/src/agent-sessions.ts +3 -3
  136. package/src/attached-projects.ts +1 -1
  137. package/src/bridge-contract-input.ts +1 -1
  138. package/src/bridge-contract.ts +23 -5
  139. package/src/capture-contract.ts +145 -3
  140. package/src/cloud-test-args.ts +100 -0
  141. package/src/config.ts +2 -2
  142. package/src/connect.ts +2 -2
  143. package/src/home-paths.ts +3 -3
  144. package/src/host/bridge-host.ts +2 -2
  145. package/src/host/fetch-proxy-handler.ts +4 -3
  146. package/src/host/fetch-proxy-overrides.ts +41 -0
  147. package/src/host/plane-host.ts +2 -2
  148. package/src/index.ts +3 -0
  149. package/src/metro-plugin.ts +4 -4
  150. package/src/metro-production-bundle.ts +2 -2
  151. package/src/native-seam-manifest.ts +6 -6
  152. package/src/public-brand.ts +1 -1
  153. package/src/rolldown-plugin-swift.ts +16 -12
  154. package/src/runtime-assets.ts +8 -6
  155. package/src/screenshots/frame-compose.ts +24 -56
  156. package/src/screenshots/org-deck.ts +1 -1
  157. package/src/screenshots/registry.ts +4 -4
  158. package/src/screenshots/schema.ts +1 -1
  159. package/src/sdk.ts +2 -2
  160. package/src/settings-bundle.ts +1 -1
  161. package/src/sim-client.ts +2 -4
  162. package/src/skills/builtin/compat-check.ts +1 -1
  163. package/src/swift-compile.ts +359 -0
  164. package/src/swift-package.ts +69 -0
  165. package/src/swift-submit.ts +3 -2
  166. package/src/vite-plugin-one.ts +1 -1
  167. package/src/vite-plugin-swift.ts +114 -113
  168. package/src/vite-plugin.ts +30 -17
  169. package/src/worklets-babel.ts +2 -2
@@ -188,7 +188,8 @@ export function selectRnxRasterNodeIds(tree: RnxRasterRegionTree): Set<number> {
188
188
  node.text,
189
189
  ))) ||
190
190
  node.style._liquidGlass !== undefined ||
191
- node.style._liquidGlassEdge !== undefined
191
+ node.style._liquidGlassEdge !== undefined ||
192
+ node.style._liquidGlassMaterialTint !== undefined
192
193
  ) {
193
194
  unsupported.add(node.nodeId)
194
195
  }
@@ -318,6 +319,146 @@ export function rnxCaptureRasterRegions(
318
319
  })
319
320
  }
320
321
 
322
+ /** a region request snapped to the surface pixel grid and placed in the atlas */
323
+ export interface RnxPackedCaptureRegion extends RnxCaptureRegionRequest {
324
+ /** top-left of the region on the app surface, in pixels */
325
+ sourceX: number
326
+ sourceY: number
327
+ pixelWidth: number
328
+ pixelHeight: number
329
+ /** top-left of the region inside the atlas, in pixels */
330
+ atlasX: number
331
+ atlasY: number
332
+ }
333
+
334
+ export interface RnxPackedCaptureAtlas {
335
+ /** logical atlas dimensions; pixel dimensions are these times the pixel ratio */
336
+ width: number
337
+ height: number
338
+ pixelWidth: number
339
+ pixelHeight: number
340
+ regions: RnxPackedCaptureRegion[]
341
+ /** the wire regions a producer publishes beside the encoded atlas */
342
+ atlasRegions: RnxCaptureAtlasRegion[]
343
+ }
344
+
345
+ // the bounded selective-layer atlas every producer ships: regions snapped to
346
+ // whole surface pixels, shelf-packed into the smallest of a few candidate
347
+ // widths, and mapped back to logical atlas coordinates. one packer for the
348
+ // headless renderer (which draws isolated subtrees into the slots) and the
349
+ // browser producer (which crops them out of a screen-sized screenshot), so a
350
+ // consumer never learns which one made the artifact from the layout.
351
+ export function packRnxCaptureRegions(
352
+ regions: readonly RnxCaptureRegionRequest[],
353
+ pixelRatio: number,
354
+ surface: { pixelWidth: number; pixelHeight: number },
355
+ ): RnxPackedCaptureAtlas {
356
+ const physical = regions.map((region) => {
357
+ const left = Math.floor(region.frame.x * pixelRatio)
358
+ const top = Math.floor(region.frame.y * pixelRatio)
359
+ const right = Math.ceil((region.frame.x + region.frame.width) * pixelRatio)
360
+ const bottom = Math.ceil((region.frame.y + region.frame.height) * pixelRatio)
361
+ if (
362
+ left < 0 ||
363
+ top < 0 ||
364
+ right > surface.pixelWidth ||
365
+ bottom > surface.pixelHeight ||
366
+ right <= left ||
367
+ bottom <= top
368
+ ) {
369
+ throw new Error(`capture region ${region.key} is outside the app surface`)
370
+ }
371
+ return {
372
+ key: region.key,
373
+ nodeId: region.nodeId,
374
+ frame: {
375
+ x: left / pixelRatio,
376
+ y: top / pixelRatio,
377
+ width: (right - left) / pixelRatio,
378
+ height: (bottom - top) / pixelRatio,
379
+ },
380
+ sourceX: left,
381
+ sourceY: top,
382
+ pixelWidth: right - left,
383
+ pixelHeight: bottom - top,
384
+ }
385
+ })
386
+ const largestWidth = Math.max(...physical.map((region) => region.pixelWidth))
387
+ const totalArea = physical.reduce(
388
+ (area, region) => area + region.pixelWidth * region.pixelHeight,
389
+ 0,
390
+ )
391
+ if (totalArea > 4_194_304) {
392
+ throw new Error('capture region pixels exceed the atlas budget')
393
+ }
394
+ const candidates = [
395
+ largestWidth,
396
+ Math.max(largestWidth, Math.ceil(Math.sqrt(totalArea))),
397
+ Math.max(largestWidth, surface.pixelWidth),
398
+ 4_096,
399
+ ]
400
+ const ordered = [...physical].sort(
401
+ (left, right) =>
402
+ right.pixelHeight - left.pixelHeight ||
403
+ right.pixelWidth - left.pixelWidth ||
404
+ left.key.localeCompare(right.key),
405
+ )
406
+ let best: { width: number; height: number; regions: RnxPackedCaptureRegion[] } | null =
407
+ null
408
+ for (const candidate of [...new Set(candidates)]) {
409
+ if (candidate > 4_096) continue
410
+ let x = 0
411
+ let y = 0
412
+ let rowHeight = 0
413
+ let usedWidth = 0
414
+ const packed: RnxPackedCaptureRegion[] = []
415
+ for (const region of ordered) {
416
+ if (x > 0 && x + region.pixelWidth > candidate) {
417
+ y += rowHeight
418
+ x = 0
419
+ rowHeight = 0
420
+ }
421
+ packed.push({ ...region, atlasX: x, atlasY: y })
422
+ x += region.pixelWidth
423
+ rowHeight = Math.max(rowHeight, region.pixelHeight)
424
+ usedWidth = Math.max(usedWidth, x)
425
+ }
426
+ const height = y + rowHeight
427
+ if (height > 4_096 || usedWidth * height > 4_194_304) continue
428
+ const result = { width: usedWidth, height, regions: packed }
429
+ if (!best || result.width * result.height < best.width * best.height) best = result
430
+ }
431
+ if (!best) throw new Error('capture regions cannot fit in one bounded atlas')
432
+ const width = best.width / pixelRatio
433
+ const height = best.height / pixelRatio
434
+ return {
435
+ width,
436
+ height,
437
+ pixelWidth: best.width,
438
+ pixelHeight: best.height,
439
+ regions: best.regions,
440
+ atlasRegions: best.regions.map((region) => ({
441
+ key: region.key,
442
+ nodeId: region.nodeId,
443
+ frame: region.frame,
444
+ // a region flush with the far edge is anchored to the logical edge so
445
+ // its source never runs past the atlas by a rounding hair
446
+ source: {
447
+ x:
448
+ region.atlasX + region.pixelWidth === best.width
449
+ ? width - region.frame.width
450
+ : region.atlasX / pixelRatio,
451
+ y:
452
+ region.atlasY + region.pixelHeight === best.height
453
+ ? height - region.frame.height
454
+ : region.atlasY / pixelRatio,
455
+ width: region.frame.width,
456
+ height: region.frame.height,
457
+ },
458
+ })),
459
+ }
460
+ }
461
+
321
462
  export interface RnxCaptureAtlasRegion extends RnxCaptureRegionRequest {
322
463
  /** source rectangle inside the packed atlas, in logical atlas coordinates */
323
464
  source: RnxCaptureRect
@@ -325,7 +466,7 @@ export interface RnxCaptureAtlasRegion extends RnxCaptureRegionRequest {
325
466
 
326
467
  export interface RnxCaptureRegionAtlas {
327
468
  kind: 'png-atlas'
328
- producer: 'headless-cpu-app-surface'
469
+ producer: 'headless-cpu-app-surface' | 'browser-compositor-app-surface'
329
470
  dataUri: string
330
471
  /** logical atlas dimensions; the encoded PNG uses pixelWidth × pixelHeight */
331
472
  width: number
@@ -533,7 +674,8 @@ export function isRnxCaptureRegionAtlas(value: unknown): value is RnxCaptureRegi
533
674
  if (
534
675
  !captureRecord(value) ||
535
676
  value.kind !== 'png-atlas' ||
536
- value.producer !== 'headless-cpu-app-surface' ||
677
+ (value.producer !== 'headless-cpu-app-surface' &&
678
+ value.producer !== 'browser-compositor-app-surface') ||
537
679
  typeof value.dataUri !== 'string' ||
538
680
  !value.dataUri.startsWith('data:image/png;base64,') ||
539
681
  !captureNumber(value.width) ||
@@ -0,0 +1,100 @@
1
+ // hosted `rnx test` selection args: the argv slice a cloud submitter
2
+ // forwards and the recorder appends verbatim to its executor invocation.
3
+ //
4
+ // selection only. the flags below choose WHICH tests run; everything
5
+ // about HOW they run (workers, timeouts, shards, devices, environment)
6
+ // stays with the recorder that owns the execution environment, and
7
+ // everything about WHERE they run (app, origin, tunnel) is consumed by
8
+ // the submitter. a flag outside this list never reaches the executor:
9
+ // the submitter warns and the server refuses, so a filter is never
10
+ // silently dropped. pure: the gpu recorder daemon imports this too, so
11
+ // no ~/ aliases, no server-only, no node builtins.
12
+ export const CLOUD_TEST_ARGS_MAX_COUNT = 32
13
+ export const CLOUD_TEST_ARG_MAX_LENGTH = 1000
14
+ export const CLOUD_TEST_ARGS_MAX_BYTES = 16 * 1024
15
+
16
+ // every flag a hosted job honors from the submitting command line. each
17
+ // takes exactly one value, as a separate token or --flag=value.
18
+ export const CLOUD_TEST_VALUE_FLAGS: readonly string[] = [
19
+ '-t',
20
+ '--testNamePattern',
21
+ '--testPathPattern',
22
+ '--include-tags',
23
+ '--exclude-tags',
24
+ '--grep',
25
+ ]
26
+
27
+ const CLOUD_TEST_VALUE_FLAG_SET = new Set<string>(CLOUD_TEST_VALUE_FLAGS)
28
+
29
+ export function isCloudTestValueFlag(name: string): boolean {
30
+ return CLOUD_TEST_VALUE_FLAG_SET.has(name)
31
+ }
32
+
33
+ export type CloudTestArgsVerdict =
34
+ | { ok: true; args: string[] }
35
+ | { ok: false; message: string }
36
+
37
+ // the submit payload's testArgs field, or null when the submission
38
+ // carries no selection. every violation names the offending arg.
39
+ export function parseCloudTestArgs(value: unknown): CloudTestArgsVerdict {
40
+ if (value === undefined || value === null) return { ok: true, args: [] }
41
+ if (!Array.isArray(value)) {
42
+ return { ok: false, message: 'testArgs must be an array of strings' }
43
+ }
44
+ if (value.length > CLOUD_TEST_ARGS_MAX_COUNT) {
45
+ return {
46
+ ok: false,
47
+ message: `testArgs may contain at most ${CLOUD_TEST_ARGS_MAX_COUNT} entries`,
48
+ }
49
+ }
50
+ // utf-8 bytes, not utf-16 code units: the bound names bytes, and a
51
+ // multibyte pattern must not slip twice its size past the gate.
52
+ const encoder = new TextEncoder()
53
+ let bytes = 0
54
+ const entries: string[] = []
55
+ for (const entry of value) {
56
+ if (typeof entry !== 'string' || entry.length === 0) {
57
+ return { ok: false, message: 'testArgs entries must be non-empty strings' }
58
+ }
59
+ 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)}` }
61
+ }
62
+ bytes += encoder.encode(entry).length
63
+ if (bytes > CLOUD_TEST_ARGS_MAX_BYTES) {
64
+ return {
65
+ ok: false,
66
+ message: `testArgs may contain at most ${CLOUD_TEST_ARGS_MAX_BYTES} bytes`,
67
+ }
68
+ }
69
+ entries.push(entry)
70
+ }
71
+ const args: string[] = []
72
+ for (let i = 0; i < entries.length; i++) {
73
+ const entry = entries[i]
74
+ if (!entry.startsWith('-')) {
75
+ return {
76
+ ok: false,
77
+ message: `testArgs carries a positional argument (selection flags only): ${entry.slice(0, 60)}`,
78
+ }
79
+ }
80
+ const eq = entry.indexOf('=')
81
+ const head = eq > 0 ? entry.slice(0, eq) : entry
82
+ if (!isCloudTestValueFlag(head)) {
83
+ return { ok: false, message: `testArgs carries an unknown flag: ${head.slice(0, 60)}` }
84
+ }
85
+ if (eq > 0) {
86
+ if (!entry.slice(eq + 1)) {
87
+ return { ok: false, message: `${head} expects a value` }
88
+ }
89
+ args.push(entry)
90
+ continue
91
+ }
92
+ const val = entries[i + 1]
93
+ if (val === undefined || val.startsWith('-')) {
94
+ return { ok: false, message: `${head} expects a value` }
95
+ }
96
+ args.push(entry, val)
97
+ i += 1
98
+ }
99
+ return { ok: true, args }
100
+ }
package/src/config.ts CHANGED
@@ -1,10 +1,10 @@
1
1
  // rnx.config.ts — per-app module resolution, turbo modules, env, state, and settings.
2
2
  //
3
3
  // usage:
4
- // import { defineConfig } from 'rnxsim/config'
4
+ // import { defineConfig } from 'peachsim/config'
5
5
  // export default defineConfig({ modules: { ... }, env: { ... } })
6
6
 
7
- import type { RevenueCatTestStoreConfig } from '@sootsim/compat/revenuecat-test-store'
7
+ import type { RevenueCatTestStoreConfig } from '@peach/compat/revenuecat-test-store'
8
8
 
9
9
  export type ModuleResolution =
10
10
  // an empty module under `modules`; under `nativeModules` / `turboModules` a
package/src/connect.ts CHANGED
@@ -1,4 +1,4 @@
1
- // `connect()` — the node entrypoint of the rnxsim SDK. resolves the running
1
+ // `connect()` — the node entrypoint of the peachsim SDK. resolves the running
2
2
  // bridge world the same way the CLI does (development bridge registry, then
3
3
  // daemon, then default port), picks a sim, and returns a connected SimClient
4
4
  // over the shared ws transport. node-only by design: sim-client.ts stays
@@ -67,7 +67,7 @@ export async function connect(options: ConnectOptions = {}): Promise<SimClient>
67
67
  const bridge = createCloudBridgeForParsed(target)
68
68
  if (!bridge)
69
69
  throw new SimConnectError('remote simulator session disappeared while connecting')
70
- return new SimClient(bridge, cloud.simId, {
70
+ return new SimClient(bridge, cloud.service === 'sim' ? cloud.simId : cloud.boxId, {
71
71
  commandTimeoutMs: options.timeoutMs ?? 15_000,
72
72
  })
73
73
  }
package/src/home-paths.ts CHANGED
@@ -56,10 +56,10 @@ export function rnxHomeDir(): string {
56
56
  // checkout, for exercising the
57
57
  // install path from this repo
58
58
  //
59
- // signal: realpath of process.argv[1] lands inside a `packages/sootsim/`
59
+ // signal: realpath of process.argv[1] lands inside a `packages/peachsim/`
60
60
  // directory. workspace bin symlinks (`node_modules/.bin/sootsim` →
61
- // `packages/sootsim/dist-cli/bin.js`) and bun-direct invocations
62
- // (`bun packages/sootsim/cli/bin.ts ...`) both match; published installs
61
+ // `packages/peachsim/dist-cli/bin.js`) and bun-direct invocations
62
+ // (`bun packages/peachsim/cli/bin.ts ...`) both match; published installs
63
63
  // resolve under `node_modules/sootsim/` instead.
64
64
  export function isSootsimDevCheckout(): boolean {
65
65
  if (process.env.SOOTSIM_FORCE_DAEMON_INSTALL === '1') return false
@@ -228,7 +228,7 @@ export interface BridgeHostOptions {
228
228
  port?: number
229
229
  openUrl?: (url: string, options?: OpenUrlOptions) => Promise<void> | void
230
230
  /** Contrast-specific excludes for the agent host's dev-server scan. passed
231
- * through to AgentHost; see packages/sootsim-engine/src/dev-scan-excludes.ts
231
+ * through to AgentHost; see packages/peach-engine/src/dev-scan-excludes.ts
232
232
  * for the canonical list. when omitted, AgentHost reads env vars directly. */
233
233
  agentScanExcludes?: AgentHostOptions['getExcludePorts']
234
234
  /** try preferred port, then port+1, port+2, …, up to this many attempts
@@ -1769,7 +1769,7 @@ export class SootSimBridgeHost {
1769
1769
 
1770
1770
  // /__server-scan — list local metro/expo/vxrn/one dev servers. tenant
1771
1771
  // worker ConnectRN polls this every few seconds to populate the device
1772
- // picker. mirrors packages/sootsim-shell/src/dev-middleware.ts so both
1772
+ // picker. mirrors packages/peach-shell/src/dev-middleware.ts so both
1773
1773
  // host environments (vite dev and the prod CLI daemon) return identical
1774
1774
  // JSON. icon URLs are rewritten through /__bundle-proxy so the browser
1775
1775
  // doesn't hit cross-origin loads against the discovered dev server.
@@ -1,6 +1,6 @@
1
1
  // shared fetch-proxy + app-api handlers for the sootsim dev/daemon HTTP
2
- // surface. both the vite dev middleware (packages/sootsim-shell/src/
3
- // dev-middleware.ts) and the daemon bridge (packages/sootsim/src/host/
2
+ // surface. both the vite dev middleware (packages/peach-shell/src/
3
+ // dev-middleware.ts) and the daemon bridge (packages/peachsim/src/host/
4
4
  // bridge-host.ts) mount these on their respective servers so guest bundle
5
5
  // fetches resolve the same way regardless of which surface the sim is
6
6
  // loaded through.
@@ -30,6 +30,7 @@ import { isLoopbackHost, normalizeHostname } from '../backend-origin.ts'
30
30
  import {
31
31
  FETCH_PROXY_BROWSER_USER_AGENT,
32
32
  getFetchProxyTargetHeaders,
33
+ resolveFetchProxyTargetUrl,
33
34
  } from './fetch-proxy-overrides.ts'
34
35
 
35
36
  const STRIP_FETCH_PROXY_HEADERS = new Set([
@@ -184,7 +185,7 @@ export async function handleFetchProxyRequest(
184
185
 
185
186
  let upstreamUrl: URL
186
187
  try {
187
- upstreamUrl = new URL(targetUrl)
188
+ upstreamUrl = resolveFetchProxyTargetUrl(new URL(targetUrl))
188
189
  } catch {
189
190
  applyFetchProxyCors(res)
190
191
  res.writeHead(400, { 'Content-Type': 'text/plain' })
@@ -39,6 +39,47 @@ function headerOverridesFor(hostname: string):
39
39
  return undefined
40
40
  }
41
41
 
42
+ // an app that builds its rpc provider hostname out of a per-tenant key cannot be
43
+ // pointed at another node by configuration alone. demo tooling injects this
44
+ // tenant name when a checkout carries no usable provider credentials, and the
45
+ // proxy resolves it to a public node for the chain. a real tenant never matches.
46
+ export const PUBLIC_RPC_TENANT = 'rnx-public-rpc'
47
+
48
+ const QUICKNODE_HOST_SUFFIX = '.quiknode.pro'
49
+
50
+ // quicknode encodes the chain in the subdomain and leaves mainnet off entirely.
51
+ const PUBLIC_RPC_BY_QUICKNODE_CHAIN: Record<string, string> = {
52
+ '': 'https://ethereum-rpc.publicnode.com',
53
+ 'arbitrum-mainnet': 'https://arbitrum-one-rpc.publicnode.com',
54
+ 'avalanche-mainnet': 'https://avalanche-c-chain-rpc.publicnode.com',
55
+ 'base-mainnet': 'https://base-rpc.publicnode.com',
56
+ 'blast-mainnet': 'https://blast-rpc.publicnode.com',
57
+ bsc: 'https://bsc-rpc.publicnode.com',
58
+ 'celo-mainnet': 'https://celo-rpc.publicnode.com',
59
+ matic: 'https://polygon-bor-rpc.publicnode.com',
60
+ optimism: 'https://optimism-rpc.publicnode.com',
61
+ 'soneium-mainnet': 'https://soneium-rpc.publicnode.com',
62
+ 'unichain-mainnet': 'https://unichain-rpc.publicnode.com',
63
+ }
64
+
65
+ // the url the proxy should actually fetch, which differs from the requested one
66
+ // only for the sentinel tenant above
67
+ export function resolveFetchProxyTargetUrl(requested: URL): URL {
68
+ const host = requested.hostname.toLowerCase()
69
+ if (!host.endsWith(QUICKNODE_HOST_SUFFIX)) return requested
70
+
71
+ const tenant = host.slice(0, -QUICKNODE_HOST_SUFFIX.length)
72
+ if (tenant !== PUBLIC_RPC_TENANT && !tenant.startsWith(`${PUBLIC_RPC_TENANT}.`)) {
73
+ return requested
74
+ }
75
+
76
+ const chain =
77
+ tenant === PUBLIC_RPC_TENANT ? '' : tenant.slice(PUBLIC_RPC_TENANT.length + 1)
78
+ const replacement = PUBLIC_RPC_BY_QUICKNODE_CHAIN[chain]
79
+ // the tenant token rides in the path, which a public node does not want
80
+ return replacement ? new URL(replacement) : requested
81
+ }
82
+
42
83
  export function getFetchProxyTargetHeaders(targetUrl: URL): Record<string, string> {
43
84
  const hostOverrides = headerOverridesFor(targetUrl.hostname)
44
85
  return {
@@ -17,9 +17,9 @@
17
17
 
18
18
  import fs from 'node:fs'
19
19
  import path from 'node:path'
20
- import { dispatchPlaneCommand, indexVersion } from '@rnx/box/plane'
20
+ import { dispatchPlaneCommand, indexVersion } from '@peach/box/plane'
21
21
  import { CheckoutFilePlane } from '../../cli/commands/box/checkout-plane.ts'
22
- import type { ProjectFilePlane } from '@rnx/box/plane'
22
+ import type { ProjectFilePlane } from '@peach/box/plane'
23
23
  import type { WebSocket } from 'ws'
24
24
 
25
25
  const WS_OPEN = 1
package/src/index.ts CHANGED
@@ -39,6 +39,7 @@ export {
39
39
  isRnxCaptureRegionAtlas,
40
40
  isRnxScreenCapture,
41
41
  rnxCaptureRasterRegions,
42
+ packRnxCaptureRegions,
42
43
  rnxRasterFrames,
43
44
  RNX_SCREEN_CAPTURE_VERSION,
44
45
  selectRnxRasterNodeIds,
@@ -54,6 +55,8 @@ export {
54
55
  type RnxCaptureSource,
55
56
  type RnxCaptureStyleTokenBinding,
56
57
  type RnxCaptureTextLine,
58
+ type RnxPackedCaptureAtlas,
59
+ type RnxPackedCaptureRegion,
57
60
  type RnxCaptureTextSegment,
58
61
  type RnxCaptureTree,
59
62
  type RnxCaptureVisualEvidence,
@@ -5,7 +5,7 @@
5
5
  // when active changes, so one loaded shell cannot mix runtime chunk trees.
6
6
  //
7
7
  // usage in metro.config.js:
8
- // const { withRNX } = require('rnxsim/metro')
8
+ // const { withRNX } = require('peachsim/metro')
9
9
  // module.exports = withRNX(getDefaultConfig(__dirname))
10
10
 
11
11
  import fs from 'fs'
@@ -89,7 +89,7 @@ function readCallableMetroExport(
89
89
  ? Reflect.get(moduleValue, 'default')
90
90
  : undefined
91
91
  if (typeof callable !== 'function') {
92
- throw new Error(`rnxsim: Metro ${label} module did not export a function`)
92
+ throw new Error(`peachsim: Metro ${label} module did not export a function`)
93
93
  }
94
94
  return (...args) => Reflect.apply(callable, undefined, args)
95
95
  }
@@ -115,7 +115,7 @@ function createMetroDefaultSerializer(projectRoot: string): MetroSerializer {
115
115
  ? Reflect.get(serialized, 'code')
116
116
  : undefined
117
117
  if (typeof code !== 'string') {
118
- throw new Error('rnxsim: Metro bundleToString did not return bundle code')
118
+ throw new Error('peachsim: Metro bundleToString did not return bundle code')
119
119
  }
120
120
  return code
121
121
  }
@@ -144,7 +144,7 @@ function appendRNXModulePaths(
144
144
  .replaceAll(path.sep, '/')
145
145
  }
146
146
  if (Object.keys(modulePaths).length !== dependencies.size) {
147
- throw new Error('rnxsim: bundle module IDs were not unique')
147
+ throw new Error('peachsim: bundle module IDs were not unique')
148
148
  }
149
149
 
150
150
  const specifiersByModuleId = new Map<string, Set<string>>()
@@ -30,7 +30,7 @@ export interface RNXMetroModuleIdentity extends RNXMetroModuleIdentityMetadata {
30
30
  // from the identity footer the plugin's serializer appends.
31
31
  export function toRNXProductionBundleUrl(bundleUrl: string): string {
32
32
  const relative = bundleUrl.startsWith('/')
33
- const url = new URL(bundleUrl, 'http://rnxsim.local')
33
+ const url = new URL(bundleUrl, 'http://peachsim.local')
34
34
  url.searchParams.set('dev', 'false')
35
35
  url.searchParams.set('hot', 'false')
36
36
  url.searchParams.set('minify', 'true')
@@ -44,7 +44,7 @@ export function toRNXProductionBundleUrl(bundleUrl: string): string {
44
44
  */
45
45
  export function toRNXDevelopmentBundleUrl(bundleUrl: string): string {
46
46
  const relative = bundleUrl.startsWith('/')
47
- const url = new URL(bundleUrl, 'http://rnxsim.local')
47
+ const url = new URL(bundleUrl, 'http://peachsim.local')
48
48
  url.searchParams.set('dev', 'true')
49
49
  url.searchParams.set('hot', 'false')
50
50
  url.searchParams.set('minify', 'false')
@@ -10,13 +10,13 @@
10
10
  // 1. identify upstream's native-module file (look for files calling
11
11
  // `requireNativeModule`, `TurboModuleRegistry.getEnforced`,
12
12
  // `requireNativeComponent`, or in `src/specs/Native*Module.ts`)
13
- // 2. write a thin stub at `packages/compat/src/stubs/native-seams/<pkg>.ts`
13
+ // 2. write a thin stub at `packages/peach-compat/src/stubs/native-seams/<pkg>.ts`
14
14
  // that exports the same default + named exports upstream's seam file
15
15
  // exports — except the implementation calls into sootsim's engine.
16
16
  // 3. append an entry below. the plugin handles the rest.
17
17
  //
18
18
  // canonical example: react-native-reanimated. see
19
- // - `packages/compat/src/stubs/react-native-reanimated.ts` (the seam stub)
19
+ // - `packages/peach-compat/src/stubs/react-native-reanimated.ts` (the seam stub)
20
20
  // - `reanimatedNativeSeamRedirect` in vite-plugin.ts (still hard-coded
21
21
  // because it has package-specific edge cases — fabricUtils,
22
22
  // validate-worklets-version. new entries should use this manifest path
@@ -41,7 +41,7 @@ export interface NativeSeamEntry {
41
41
  seamBasenames: string[]
42
42
  /**
43
43
  * absolute path to the seam stub. typically
44
- * `packages/compat/src/stubs/native-seams/<pkg>.ts` — but a single stub
44
+ * `packages/peach-compat/src/stubs/native-seams/<pkg>.ts` — but a single stub
45
45
  * file may export every native module the package exposes, so the same
46
46
  * `target` can appear in multiple entries if a package has multiple native
47
47
  * modules that share an implementation file.
@@ -61,9 +61,9 @@ import path from 'node:path'
61
61
  import { fileURLToPath } from 'node:url'
62
62
 
63
63
  const HERE = path.dirname(fileURLToPath(import.meta.url))
64
- const NATIVE_SEAMS_DIR = path.resolve(HERE, '../../compat/src/stubs/native-seams')
64
+ const NATIVE_SEAMS_DIR = path.resolve(HERE, '../../peach-compat/src/stubs/native-seams')
65
65
 
66
- /** resolve a stub path under `packages/compat/src/stubs/native-seams/`.
66
+ /** resolve a stub path under `packages/peach-compat/src/stubs/native-seams/`.
67
67
  * resolves to `.tsx` when only that exists (JSX-bearing seams like
68
68
  * expo-image, react-native-webview). preferred order: `.ts`, `.tsx`. */
69
69
  export function seamPath(basename: string): string {
@@ -217,7 +217,7 @@ export const NATIVE_SEAM_MANIFEST: NativeSeamEntry[] = [
217
217
  // onto the class-based seam, whose methods throw.
218
218
  //
219
219
  // registering by module NAME instead is what keeps old and new package
220
- // versions both working: ../../compat/src/stubs/expo-{calendar,contacts}-register.ts
220
+ // versions both working: ../../peach-compat/src/stubs/expo-{calendar,contacts}-register.ts
221
221
  // installs every id either version asks for, and upstream's own
222
222
  // `requireNativeModule` picks the right one per entry point. a file
223
223
  // redirect can only match one layout, so it breaks the versions it was
@@ -2,7 +2,7 @@ const name = 'rnx'
2
2
 
3
3
  export const rnxPublicBrand = Object.freeze({
4
4
  name,
5
- packageName: 'rnxsim',
5
+ packageName: 'peachsim',
6
6
  commandName: 'rnx',
7
7
  domain: 'rnxsim.com',
8
8
  origin: 'https://rnxsim.com',
@@ -2,13 +2,14 @@
2
2
  // swiftpm package to `/api/swift-compile` and answer every `.swift` import
3
3
  // with the module that mounts the published artifact.
4
4
  //
5
- // this is the remote twin of `vite-plugin-swift.ts`. that plugin runs the
6
- // local toolchain and serves the artifact from dev-server middleware; the
7
- // native engine is its own rolldown instance with no middleware seam, so
8
- // this plugin compiles nothing in-process and serves nothing. the artifact
9
- // url the service answers is absolute (compile-cdn), so there is nothing to
10
- // serve. same compiler, same store, same hash, same credential as the
11
- // browser worker — one path, no second toolchain to install for `one dev`.
5
+ // this is the remote twin of `vite-plugin-swift.ts`. that plugin calls the
6
+ // shared project-unit compiler and serves the artifact from dev-server
7
+ // middleware. the native engine is its own rolldown instance with no
8
+ // middleware seam, so this plugin compiles nothing in-process and serves
9
+ // nothing. the artifact url the service answers is absolute (compile-cdn), so
10
+ // there is nothing to serve. same compiler, same store, same hash, same
11
+ // credential as the browser worker — one path, no second toolchain to install
12
+ // for `one dev`.
12
13
  //
13
14
  // an app registers it through One's native plugin seam, not `config.plugins`
14
15
  // (which never reach the native engine):
@@ -39,6 +40,7 @@ import {
39
40
  enumerateBundleImages,
40
41
  enumerateBundleJson,
41
42
  } from '../../sootsim-swift/src/bundleAssets'
43
+ import { swiftEntryModuleEsm } from '../../sootsim-swift/src/entryModule'
42
44
  import { compileSwiftProject, httpSwiftCompileSubmit } from './swift-submit'
43
45
  import { entryRootId } from './vite-plugin-swift'
44
46
  import type { SwiftModuleAddress } from './swift-submit'
@@ -230,11 +232,13 @@ export function swiftRemotePlugin(options: SwiftRemotePluginOptions): Plugin {
230
232
  // every import mounts through the one entry: the artifact answers
231
233
  // whether it is a view or an app root at runtime, so the transform
232
234
  // never re-derives the conformance from source.
233
- return [
234
- `import { createSwiftEntry } from 'rnxsim/swift'`,
235
- `export default createSwiftEntry({ url: ${JSON.stringify(address.url)}, hash: ${JSON.stringify(address.hash)}, rootId: ${JSON.stringify(entryRootId(packagePath, file))}, resources: ${JSON.stringify(address.imageUrls ?? {})}, bundleFiles: ${JSON.stringify(pack.resources)} })`,
236
- '',
237
- ].join('\n')
235
+ return swiftEntryModuleEsm({
236
+ url: address.url,
237
+ hash: address.hash,
238
+ rootId: entryRootId(packagePath, file),
239
+ resources: address.imageUrls ?? {},
240
+ bundleFiles: pack.resources,
241
+ })
238
242
  },
239
243
  }
240
244
  }
@@ -34,11 +34,11 @@ let _monorepoRootCache: string | null | undefined
34
34
  function findMonorepoRoot(): string | null {
35
35
  if (_monorepoRootCache !== undefined) return _monorepoRootCache
36
36
  let dir = path.dirname(fileURLToPath(import.meta.url))
37
- // walk up until we find the Contrast monorepo: it has packages/sootsim-engine and
37
+ // walk up until we find the Contrast monorepo: it has packages/peach-engine and
38
38
  // a public/sootsim dev-build output slot. bounded by the filesystem root.
39
39
  for (let i = 0; i < 12 && dir !== path.dirname(dir); i++) {
40
40
  if (
41
- fs.existsSync(path.join(dir, 'packages/sootsim-engine/package.json')) &&
41
+ fs.existsSync(path.join(dir, 'packages/peach-engine/package.json')) &&
42
42
  fs.existsSync(path.join(dir, 'public/sootsim'))
43
43
  ) {
44
44
  _monorepoRootCache = dir
@@ -66,11 +66,12 @@ export function resolveDevCheckoutRuntimeRoot(): string | null {
66
66
  if (!root) return null
67
67
  const devBuild = path.join(root, 'public/sootsim')
68
68
  // require the runtime shape the installed runtime also has, so serving it is
69
- // a drop-in (index.html shell + engine/ + engine-tenant/worker.js tenant).
69
+ // a drop-in (index.html shell + engine/ + both engine-tenant workers).
70
70
  if (
71
71
  fs.existsSync(path.join(devBuild, 'index.html')) &&
72
72
  fs.existsSync(path.join(devBuild, 'engine/index.html')) &&
73
- fs.existsSync(path.join(devBuild, 'engine-tenant/worker.js'))
73
+ fs.existsSync(path.join(devBuild, 'engine-tenant/worker.js')) &&
74
+ fs.existsSync(path.join(devBuild, 'engine-tenant/worker.prod.js'))
74
75
  ) {
75
76
  return devBuild
76
77
  }
@@ -217,9 +218,10 @@ export function serveRuntimeFile(req: any, res: any, fullPath: string) {
217
218
  return
218
219
  }
219
220
  // these urls are STABLE across dev rebuilds and runtime version switches
220
- // (worker.js, canvaskit-graphite.wasm, the html shells are unhashed), so the
221
+ // (both worker entries, canvaskit-graphite.wasm, and the html shells are
222
+ // unhashed), so the
221
223
  // browser must revalidate every load — the old year-long immutable here made every
222
- // new tab run a stale cached engine-tenant worker.js against a fresh engine,
224
+ // new tab run a stale cached tenant entry against a fresh engine,
223
225
  // which also broke `do reload` ("bridge never reconnected"). no-cache +
224
226
  // last-modified keeps repeat loads cheap 304s on localhost.
225
227
  res.setHeader('cache-control', 'no-cache')