peachsim 0.1.629 → 0.1.631

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 (128) hide show
  1. package/README.md +8 -4
  2. package/cli/app-fonts.ts +23 -4
  3. package/cli/bridge-flow-runner.ts +45 -31
  4. package/cli/cli-update.ts +2 -7
  5. package/cli/cloud-test-tunnel.ts +16 -3
  6. package/cli/command-registry.ts +1 -0
  7. package/cli/commands/compat.ts +11 -60
  8. package/cli/commands/config.ts +1 -1
  9. package/cli/commands/control.ts +40 -8
  10. package/cli/commands/daemon-mac-app.ts +4 -2
  11. package/cli/commands/daemon.ts +4 -4
  12. package/cli/commands/detect.ts +289 -26
  13. package/cli/commands/device.ts +2 -3
  14. package/cli/commands/inspect/actions.ts +125 -90
  15. package/cli/commands/inspect/core.ts +1 -1
  16. package/cli/commands/inspect.ts +10 -1
  17. package/cli/commands/maestro.ts +3 -1
  18. package/cli/commands/platform.ts +1 -1
  19. package/cli/commands/project-report.ts +386 -0
  20. package/cli/commands/projectReportContract.ts +42 -0
  21. package/cli/commands/report-issue.ts +38 -54
  22. package/cli/commands/screenshot-layers.ts +1 -1
  23. package/cli/commands/screenshot.ts +1 -1
  24. package/cli/commands/setup.ts +6 -4
  25. package/cli/commands/skills.ts +0 -31
  26. package/cli/commands/telemetry.ts +8 -3
  27. package/cli/commands/test.ts +347 -134
  28. package/cli/drivers/playwright-provisioning.ts +3 -2
  29. package/cli/drivers/playwright-sim-host.ts +25 -9
  30. package/cli/main.ts +19 -3
  31. package/cli/outbound-endpoints.ts +2 -5
  32. package/cli/privacy.ts +1 -2
  33. package/cli/runtime-diagnostics.ts +51 -0
  34. package/cli/telemetry.ts +78 -7
  35. package/detox/index.ts +77 -51
  36. package/dist-lib/agent-daemon-client.cjs +1 -1
  37. package/dist-lib/agent-events.cjs +1 -1
  38. package/dist-lib/agent-identity.cjs +1 -1
  39. package/dist-lib/agent-sessions.cjs +1 -1
  40. package/dist-lib/attached-projects.cjs +1 -1
  41. package/dist-lib/auth/shared-session.cjs +1 -1
  42. package/dist-lib/backend-origin.cjs +1 -1
  43. package/dist-lib/beta.cjs +1 -1
  44. package/dist-lib/beta.mjs +1 -1
  45. package/dist-lib/bridge-constants.cjs +1 -1
  46. package/dist-lib/bridge-contract-input.cjs +1 -1
  47. package/dist-lib/bridge-contract-input.mjs +1 -1
  48. package/dist-lib/bridge-contract.cjs +1 -1
  49. package/dist-lib/bridge-contract.mjs +1 -1
  50. package/dist-lib/capture-contract.cjs +1 -1
  51. package/dist-lib/capture-contract.mjs +1 -1
  52. package/dist-lib/cli-constants.cjs +1 -1
  53. package/dist-lib/cloud-contract.cjs +1 -1
  54. package/dist-lib/cloud-contract.mjs +1 -1
  55. package/dist-lib/cloud-test-args.cjs +1 -1
  56. package/dist-lib/cloud-test-args.mjs +1 -1
  57. package/dist-lib/cloud.cjs +1 -1
  58. package/dist-lib/cloud.mjs +1 -1
  59. package/dist-lib/config.cjs +1 -1
  60. package/dist-lib/detox/index.cjs +130 -33
  61. package/dist-lib/dev-bundle-resolution.cjs +1 -1
  62. package/dist-lib/home-paths.cjs +8 -5
  63. package/dist-lib/host/bridge-host.cjs +353 -290
  64. package/dist-lib/host/fetch-proxy-handler.cjs +1 -1
  65. package/dist-lib/host/fetch-proxy-overrides.cjs +1 -1
  66. package/dist-lib/host/fetch-proxy-overrides.mjs +1 -1
  67. package/dist-lib/host/replacement-module-handler.cjs +1 -1
  68. package/dist-lib/host/websocket-proxy.cjs +1 -1
  69. package/dist-lib/index.cjs +257 -121
  70. package/dist-lib/jump-to-source-babel.cjs +1 -1
  71. package/dist-lib/jump-to-source-native.cjs +1 -1
  72. package/dist-lib/{vite-kotlin.cjs → kotlin-host.cjs} +130 -19
  73. package/dist-lib/menu.cjs +11 -1
  74. package/dist-lib/menu.mjs +11 -1
  75. package/dist-lib/metro-fingerprint-registry.cjs +1 -1
  76. package/dist-lib/metro-fingerprint-registry.mjs +1 -1
  77. package/dist-lib/metro-production-bundle.cjs +1 -1
  78. package/dist-lib/metro-production-bundle.mjs +1 -1
  79. package/dist-lib/metro.cjs +132 -9
  80. package/dist-lib/native-app-identity.cjs +278 -0
  81. package/dist-lib/native-app-identity.mjs +245 -0
  82. package/dist-lib/profiles.cjs +1 -1
  83. package/dist-lib/public-brand.cjs +1 -1
  84. package/dist-lib/react-native-host-modules.cjs +1 -1
  85. package/dist-lib/react-native-host-modules.mjs +1 -1
  86. package/dist-lib/render-mode.cjs +1 -1
  87. package/dist-lib/scripts/dev-server-scanner.cjs +1 -1
  88. package/dist-lib/sdk.cjs +112 -87
  89. package/dist-lib/sdk.mjs +111 -87
  90. package/dist-lib/skills.cjs +1270 -709
  91. package/dist-lib/swift-compile.cjs +1 -1
  92. package/dist-lib/swift-compile.mjs +1 -1
  93. package/dist-lib/swift-package.cjs +1 -1
  94. package/dist-lib/swift-package.mjs +1 -1
  95. package/dist-lib/swift-submit.cjs +1 -1
  96. package/dist-lib/swift-submit.mjs +1 -1
  97. package/dist-lib/swift.cjs +672 -275
  98. package/dist-lib/vite-swift.cjs +1 -1
  99. package/dist-lib/vite.cjs +11 -28
  100. package/kotlin/README.md +32 -4
  101. package/kotlin/fixtures/counter/Counter.kt +26 -0
  102. package/kotlin/fixtures/counter/CounterScreen.kt +40 -0
  103. package/kotlin/fixtures/headless-counter/Headless.kt +228 -0
  104. package/kotlin/fixtures/runtime-state/RuntimeState.kt +200 -0
  105. package/kotlin/fork/LICENSE +178 -0
  106. package/kotlin/fork/NOTICE +25 -0
  107. package/kotlin/fork/README.md +59 -0
  108. package/kotlin/fork/build.sh +128 -0
  109. package/kotlin/fork/compose-worker-globals.patch +96 -0
  110. package/kotlin/fork/fork.json +33 -0
  111. package/kotlin/fork/publish.ts +77 -0
  112. package/kotlin/fork/skiko-worker-clock.patch +33 -0
  113. package/kotlin/toolchain.json +288 -0
  114. package/package.json +13 -5
  115. package/skills/contrast/SKILL.md +1 -0
  116. package/src/automatic-update-policy.ts +17 -0
  117. package/src/home-paths.ts +11 -9
  118. package/src/host/bridge-host.ts +3 -1
  119. package/src/{vite-plugin-kotlin.ts → kotlin-host.ts} +155 -12
  120. package/src/menu.ts +11 -0
  121. package/src/metro-plugin.ts +18 -0
  122. package/src/native-app-identity.ts +376 -0
  123. package/src/native-firebase-config.ts +90 -0
  124. package/src/screenshots/frame-compose.ts +1 -1
  125. package/src/screenshots/registry.ts +1 -1
  126. package/src/screenshots/schema.ts +2 -2
  127. package/src/vite-plugin-one.ts +11 -28
  128. package/src/vite-plugin.ts +6 -1
@@ -1,4 +1,4 @@
1
- /*! rnx v0.1.629 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
1
+ /*! rnx v0.1.631 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
2
2
  let __sootsim_import_meta_url = ''; try { __sootsim_import_meta_url = require('url').pathToFileURL(__filename).href; } catch {}
3
3
  "use strict";
4
4
  var __create = Object.create;
@@ -1,4 +1,4 @@
1
- /*! rnx v0.1.629 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
1
+ /*! rnx v0.1.631 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
2
2
  let __sootsim_import_meta_url = ''; try { __sootsim_import_meta_url = require('url').pathToFileURL(__filename).href; } catch {}
3
3
  "use strict";
4
4
  var __defProp = Object.defineProperty;
@@ -1,4 +1,4 @@
1
- /*! rnx v0.1.629 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
1
+ /*! rnx v0.1.631 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
2
2
 
3
3
  // src/host/fetch-proxy-overrides.ts
4
4
  var FETCH_PROXY_BROWSER_USER_AGENT = "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/146.0.0.0 Safari/537.36";
@@ -1,4 +1,4 @@
1
- /*! rnx v0.1.629 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
1
+ /*! rnx v0.1.631 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
2
2
  let __sootsim_import_meta_url = ''; try { __sootsim_import_meta_url = require('url').pathToFileURL(__filename).href; } catch {}
3
3
  "use strict";
4
4
  var __create = Object.create;
@@ -1,4 +1,4 @@
1
- /*! rnx v0.1.629 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
1
+ /*! rnx v0.1.631 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
2
2
  let __sootsim_import_meta_url = ''; try { __sootsim_import_meta_url = require('url').pathToFileURL(__filename).href; } catch {}
3
3
  "use strict";
4
4
  var __defProp = Object.defineProperty;
@@ -1,4 +1,4 @@
1
- /*! rnx v0.1.629 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
1
+ /*! rnx v0.1.631 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
2
2
  let __sootsim_import_meta_url = ''; try { __sootsim_import_meta_url = require('url').pathToFileURL(__filename).href; } catch {}
3
3
  "use strict";
4
4
  var __create = Object.create;
@@ -433,6 +433,10 @@ function readCloudSession(environment = process.env) {
433
433
  return encoded?.trim() ? parseCloudSession(encoded) : null;
434
434
  }
435
435
 
436
+ // cli/commands/detect.ts
437
+ var import_json5 = __toESM(require("json5"), 1);
438
+ var import_yaml = require("yaml");
439
+
436
440
  // cli/cloud-client.ts
437
441
  var DEFAULT_RNX_CLOUD_ORIGIN = rnxPublicBrand.origin;
438
442
  function failureSentence(text) {
@@ -2461,16 +2465,26 @@ function payloadFromResolved(resolved, match, extra = {}) {
2461
2465
  ...extra
2462
2466
  };
2463
2467
  }
2464
- function collectTextCandidates(nodes, ancestors = [], pressableOwnerText = "") {
2468
+ function collectTextCandidates(nodes, ancestors = [], pressableOwnerText = "", actionableAncestor = false, actionableRole) {
2465
2469
  const out = [];
2466
2470
  for (const node of nodes) {
2467
2471
  const text = nodeText(node);
2468
2472
  const caption = !!text && text === pressableOwnerText && !node.pressable;
2469
- if (!caption) out.push({ node, ancestorTestIDs: ancestors });
2473
+ const actionable = !!node.pressable || actionableAncestor;
2474
+ const role = node.role ?? actionableRole;
2475
+ if (!caption) out.push({ node, ancestorTestIDs: ancestors, actionable, role });
2470
2476
  if (node.children?.length) {
2471
2477
  const next = node.testID ? [...ancestors, node.testID] : ancestors;
2472
2478
  const owner = text ? node.pressable ? text : "" : pressableOwnerText;
2473
- out.push(...collectTextCandidates(node.children, next, owner));
2479
+ out.push(
2480
+ ...collectTextCandidates(
2481
+ node.children,
2482
+ next,
2483
+ owner,
2484
+ actionable,
2485
+ node.pressable ? node.role : actionableRole
2486
+ )
2487
+ );
2474
2488
  }
2475
2489
  }
2476
2490
  return out;
@@ -2494,85 +2508,102 @@ async function tapByText(bridge, query, options = {}, opts = {}) {
2494
2508
  agent: opts.agent,
2495
2509
  timing: opts.timing,
2496
2510
  textFallback: query,
2497
- resolve: async () => {
2498
- const { tree } = await inspectTree(bridge, 50);
2499
- if (!Array.isArray(tree)) return { matched: 0, total: 0 };
2500
- const needle = options.exact ? query : query.toLowerCase();
2501
- let candidates = collectTextCandidates(tree).filter(({ node: node2, ancestorTestIDs }) => {
2502
- const text = nodeText(node2);
2503
- if (!text) return false;
2504
- if (options.exact ? text !== needle : !text.toLowerCase().includes(needle)) {
2505
- return false;
2506
- }
2507
- if (options.role && node2.role !== options.role) return false;
2508
- if (options.within && !ancestorTestIDs.includes(options.within)) return false;
2509
- const ax = node2.geometry.x;
2510
- const ay = node2.geometry.y;
2511
- if (options.minX != null && !(ax >= options.minX)) return false;
2512
- if (options.maxX != null && !(ax <= options.maxX)) return false;
2513
- if (options.minY != null && !(ay >= options.minY)) return false;
2514
- if (options.maxY != null && !(ay <= options.maxY)) return false;
2515
- return true;
2516
- });
2517
- if (options.near) {
2518
- const { x, y } = options.near;
2519
- candidates.sort(
2520
- (a, b) => Math.hypot(a.node.geometry.x - x, a.node.geometry.y - y) - Math.hypot(b.node.geometry.x - x, b.node.geometry.y - y)
2521
- );
2522
- } else {
2523
- candidates.sort((a, b) => {
2524
- if (Math.abs(a.node.geometry.y - b.node.geometry.y) > 2) {
2525
- return a.node.geometry.y - b.node.geometry.y;
2526
- }
2527
- return a.node.geometry.x - b.node.geometry.x;
2528
- });
2529
- }
2530
- const total = candidates.length;
2531
- if (total === 0) return { matched: 0, total: 0 };
2532
- let idx = 0;
2533
- if (options.nth != null) {
2534
- idx = options.nth < 0 ? total + options.nth : options.nth;
2535
- if (idx < 0 || idx >= total) {
2536
- return { matched: 0, total, nthOutOfRange: true, nth: options.nth };
2537
- }
2538
- } else if (total > 1 && !options.first && !options.near) {
2539
- return {
2540
- ambiguous: true,
2541
- total,
2542
- candidates: candidates.slice(0, 10).map((candidate, index) => ({
2543
- idx: index,
2544
- nodeId: candidate.node.nodeId,
2545
- type: candidate.node.type,
2546
- testID: candidate.node.testID ?? null,
2547
- text: nodeText(candidate.node).slice(0, 60),
2548
- abs: { x: candidate.node.geometry.x, y: candidate.node.geometry.y },
2549
- layout: {
2550
- width: Math.round(candidate.node.geometry.width),
2551
- height: Math.round(candidate.node.geometry.height)
2552
- },
2553
- ancestorTestIDs: candidate.ancestorTestIDs.slice(0, 5)
2554
- }))
2555
- };
2511
+ resolve: () => resolveTextTapTarget(bridge, query, options)
2512
+ });
2513
+ }
2514
+ async function resolveTextTapTarget(bridge, query, options = {}) {
2515
+ const { tree } = await inspectTree(bridge, 50);
2516
+ if (!Array.isArray(tree)) return { matched: 0, total: 0 };
2517
+ const needle = options.exact ? query : query.toLowerCase();
2518
+ let candidates = collectTextCandidates(tree).filter(
2519
+ ({ node: node2, ancestorTestIDs, role }) => {
2520
+ const text = nodeText(node2);
2521
+ if (!text) return false;
2522
+ if (options.exact ? text !== needle : !text.toLowerCase().includes(needle)) {
2523
+ return false;
2556
2524
  }
2557
- const picked = candidates[idx];
2558
- if (!picked) return { matched: 0, total };
2559
- const node = picked.node;
2560
- let resolved = node.testID ? await resolveTargetCoords(bridge, { mode: "testid", value: node.testID }) : await resolveTargetCoords(bridge, {
2561
- mode: "text",
2562
- value: nodeText(node) || query
2563
- });
2564
- if (!resolved) {
2565
- resolved = {
2566
- x: node.geometry.x + node.geometry.width / 2,
2567
- y: node.geometry.y + node.geometry.height / 2,
2568
- testID: node.testID ?? null,
2569
- text: nodeText(node) || query,
2570
- type: node.type
2571
- };
2525
+ if (options.role && role !== options.role) return false;
2526
+ if (options.within && !ancestorTestIDs.includes(options.within)) return false;
2527
+ const ax = node2.geometry.x;
2528
+ const ay = node2.geometry.y;
2529
+ if (options.minX != null && !(ax >= options.minX)) return false;
2530
+ if (options.maxX != null && !(ax <= options.maxX)) return false;
2531
+ if (options.minY != null && !(ay >= options.minY)) return false;
2532
+ if (options.maxY != null && !(ay <= options.maxY)) return false;
2533
+ return true;
2534
+ }
2535
+ );
2536
+ if (options.nth == null && !options.first && !options.near) {
2537
+ const actionable = candidates.filter((candidate) => candidate.actionable);
2538
+ if (actionable.length > 0) candidates = actionable;
2539
+ }
2540
+ if (options.near) {
2541
+ const { x, y } = options.near;
2542
+ candidates.sort(
2543
+ (a, b) => Math.hypot(a.node.geometry.x - x, a.node.geometry.y - y) - Math.hypot(b.node.geometry.x - x, b.node.geometry.y - y)
2544
+ );
2545
+ } else {
2546
+ candidates.sort((a, b) => {
2547
+ if (Math.abs(a.node.geometry.y - b.node.geometry.y) > 2) {
2548
+ return a.node.geometry.y - b.node.geometry.y;
2572
2549
  }
2573
- return payloadFromResolved(resolved, node, { total, idx });
2550
+ return a.node.geometry.x - b.node.geometry.x;
2551
+ });
2552
+ }
2553
+ const total = candidates.length;
2554
+ if (total === 0) return { matched: 0, total: 0 };
2555
+ let idx = 0;
2556
+ if (options.nth != null) {
2557
+ idx = options.nth < 0 ? total + options.nth : options.nth;
2558
+ if (idx < 0 || idx >= total) {
2559
+ return { matched: 0, total, nthOutOfRange: true, nth: options.nth };
2574
2560
  }
2561
+ } else if (total > 1 && !options.first && !options.near) {
2562
+ return {
2563
+ ambiguous: true,
2564
+ total,
2565
+ candidates: candidates.slice(0, 10).map((candidate, index) => ({
2566
+ idx: index,
2567
+ nodeId: candidate.node.nodeId,
2568
+ type: candidate.node.type,
2569
+ testID: candidate.node.testID ?? null,
2570
+ text: nodeText(candidate.node).slice(0, 60),
2571
+ abs: { x: candidate.node.geometry.x, y: candidate.node.geometry.y },
2572
+ layout: {
2573
+ width: Math.round(candidate.node.geometry.width),
2574
+ height: Math.round(candidate.node.geometry.height)
2575
+ },
2576
+ ancestorTestIDs: candidate.ancestorTestIDs.slice(0, 5)
2577
+ }))
2578
+ };
2579
+ }
2580
+ const picked = candidates[idx];
2581
+ if (!picked) return { matched: 0, total };
2582
+ const node = picked.node;
2583
+ const tap = await bridge.send({
2584
+ type: "call",
2585
+ path: "SootSim.bridges.test.resolveTapTarget",
2586
+ args: [node.nodeId]
2575
2587
  });
2588
+ if (!isTapTargetInfo(tap)) return { matched: 0, total };
2589
+ return payloadFromResolved(
2590
+ {
2591
+ x: tap.cx,
2592
+ y: tap.cy,
2593
+ nodeId: tap.target.nodeId,
2594
+ testID: tap.target.testID,
2595
+ text: tap.target.text ?? tap.target.accessibilityLabel,
2596
+ type: tap.target.type
2597
+ },
2598
+ node,
2599
+ { total, idx }
2600
+ );
2601
+ }
2602
+ function isTapNode(value) {
2603
+ return value !== null && typeof value === "object" && typeof Reflect.get(value, "nodeId") === "number";
2604
+ }
2605
+ function isTapTargetInfo(value) {
2606
+ return value !== null && typeof value === "object" && typeof Reflect.get(value, "cx") === "number" && typeof Reflect.get(value, "cy") === "number" && isTapNode(Reflect.get(value, "target"));
2576
2607
  }
2577
2608
  async function tapBest(bridge, query, opts = {}) {
2578
2609
  return tapResolvedTarget(bridge, {
@@ -2594,17 +2625,19 @@ async function tapBest(bridge, query, opts = {}) {
2594
2625
  }
2595
2626
  };
2596
2627
  }
2597
- const byText = await resolveTargetCoords(bridge, { mode: "text", value: query });
2598
- if (byText) {
2628
+ const byText = await resolveTextTapTarget(bridge, query);
2629
+ if ("ambiguous" in byText) {
2630
+ return {
2631
+ ambiguous: true,
2632
+ error: `multiple text matches for "${query}"; use tap-text with --nth, --within or --near`
2633
+ };
2634
+ }
2635
+ if ("cx" in byText && typeof byText.cx === "number") {
2599
2636
  return {
2600
- ...payloadFromResolved(byText, byText),
2637
+ ...byText,
2601
2638
  strategy: "text",
2602
2639
  node: {
2603
- nodeId: byText.nodeId ?? null,
2604
- id: byText.testID ?? null,
2605
- testID: byText.testID ?? null,
2606
- type: byText.type ?? null,
2607
- text: byText.text ?? query
2640
+ ...byText.target
2608
2641
  }
2609
2642
  };
2610
2643
  }
@@ -4160,7 +4193,7 @@ var swiftHost;
4160
4193
  var loadSwiftHost = () => swiftHost ??= import("peachsim/vite-swift");
4161
4194
  var KOTLIN_ROUTE_PREFIX = "/__peach/kotlin";
4162
4195
  var kotlinHost;
4163
- var loadKotlinHost = () => kotlinHost ??= import("peachsim/vite-kotlin");
4196
+ var loadKotlinHost = () => kotlinHost ??= import("peachsim/kotlin-host");
4164
4197
  function rnxPlugin(options = {}) {
4165
4198
  const prefix2 = options.prefix || "/__soot";
4166
4199
  return {
@@ -4243,34 +4276,17 @@ function rnxPlugin(options = {}) {
4243
4276
  const pathname = (req.url ?? "").split("?")[0];
4244
4277
  if (!pathname.startsWith(KOTLIN_ROUTE_PREFIX)) return next();
4245
4278
  loadKotlinHost().then(async (host) => {
4246
- if (host.serveKotlinArtifact(pathname, res)) return;
4247
- if (pathname !== "/__peach/kotlin-package") return next();
4248
4279
  if (!kotlinModuleRoot) {
4249
- const moduleRoot = import_path3.default.join(
4250
- import_path3.default.dirname(
4251
- (0, import_node_module.createRequire)(import_path3.default.join(server.config.root, "package.json")).resolve(
4252
- "peachsim/package.json"
4253
- )
4254
- ),
4255
- "kotlin"
4256
- );
4257
- kotlinModuleRoot = moduleRoot;
4258
- server.watcher.add(import_path3.default.join(moduleRoot, "Sources"));
4259
- }
4260
- try {
4261
- const artifact = await host.kotlinArtifact(
4262
- server.config.root,
4263
- kotlinModuleRoot,
4264
- devOrigin()
4265
- );
4266
- res.setHeader("access-control-allow-origin", "*");
4267
- res.setHeader("content-type", "application/json");
4268
- res.end(JSON.stringify({ ...artifact, hmrPath }));
4269
- } catch (error) {
4270
- res.statusCode = 500;
4271
- res.setHeader("access-control-allow-origin", "*");
4272
- res.end(error instanceof Error ? error.message : String(error));
4280
+ kotlinModuleRoot = host.peachKotlinModuleRoot(server.config.root);
4281
+ server.watcher.add(import_path3.default.join(kotlinModuleRoot, "Sources"));
4273
4282
  }
4283
+ const served = await host.serveKotlinRequest(pathname, res, {
4284
+ root: server.config.root,
4285
+ moduleRoot: kotlinModuleRoot,
4286
+ origin: devOrigin(),
4287
+ hmrPath
4288
+ });
4289
+ if (!served) next();
4274
4290
  }).catch(next);
4275
4291
  });
4276
4292
  server.watcher.on("change", (file) => {
@@ -4482,6 +4498,110 @@ var import_fs5 = __toESM(require("fs"), 1);
4482
4498
  var import_module2 = require("module");
4483
4499
  var import_node_crypto5 = require("node:crypto");
4484
4500
  var import_path4 = __toESM(require("path"), 1);
4501
+
4502
+ // src/native-firebase-config.ts
4503
+ var import_node_fs5 = __toESM(require("node:fs"), 1);
4504
+ var import_node_module2 = require("node:module");
4505
+ var import_node_path6 = __toESM(require("node:path"), 1);
4506
+
4507
+ // src/plist.ts
4508
+ var import_plist = __toESM(require("@expo/plist"), 1);
4509
+ function resolvePlistParser(value) {
4510
+ if (!isRecord(value)) return null;
4511
+ const direct = value.parse;
4512
+ if (typeof direct === "function") {
4513
+ return (source) => Reflect.apply(direct, value, [source]);
4514
+ }
4515
+ const nested = value.default;
4516
+ if (!isRecord(nested)) return null;
4517
+ const nestedParse = nested.parse;
4518
+ return typeof nestedParse === "function" ? (source) => Reflect.apply(nestedParse, nested, [source]) : null;
4519
+ }
4520
+ var parsePlist = resolvePlistParser(import_plist.default);
4521
+ function parsePlistSource(source) {
4522
+ return parsePlist?.(source) ?? null;
4523
+ }
4524
+
4525
+ // src/native-firebase-config.ts
4526
+ function readNativeFirebaseConfig(projectRoot) {
4527
+ const result = {};
4528
+ if (![
4529
+ "app.json",
4530
+ "app.config.js",
4531
+ "app.config.ts",
4532
+ "app.config.cjs",
4533
+ "app.config.mjs"
4534
+ ].some((name2) => import_node_fs5.default.existsSync(import_node_path6.default.join(projectRoot, name2))))
4535
+ return result;
4536
+ const require2 = (0, import_node_module2.createRequire)(import_node_path6.default.join(projectRoot, "package.json"));
4537
+ const configModule = require2("@expo/config");
4538
+ if (!isRecord(configModule) || typeof configModule.getConfig !== "function") {
4539
+ throw new Error("Expo native configuration is unavailable.");
4540
+ }
4541
+ const resolved = Reflect.apply(configModule.getConfig, configModule, [
4542
+ projectRoot,
4543
+ { skipSDKVersionRequirement: true }
4544
+ ]);
4545
+ const config = isRecord(resolved) && isRecord(resolved.exp) ? resolved.exp : {};
4546
+ for (const platform of ["ios", "android"]) {
4547
+ const native = config[platform];
4548
+ if (!isRecord(native) || typeof native.googleServicesFile !== "string") continue;
4549
+ try {
4550
+ const source = import_node_fs5.default.readFileSync(
4551
+ import_node_path6.default.resolve(projectRoot, native.googleServicesFile),
4552
+ "utf8"
4553
+ );
4554
+ const parsed = platform === "ios" ? parsePlistSource(source) : JSON.parse(source);
4555
+ if (!isRecord(parsed)) throw new Error("Invalid Google services file.");
4556
+ if (platform === "ios") {
4557
+ result[platform] = {
4558
+ apiKey: parsed.API_KEY,
4559
+ appId: parsed.GOOGLE_APP_ID,
4560
+ projectId: parsed.PROJECT_ID,
4561
+ databaseURL: parsed.DATABASE_URL,
4562
+ gaTrackingId: parsed.TRACKING_ID,
4563
+ storageBucket: parsed.STORAGE_BUCKET,
4564
+ messagingSenderId: parsed.GCM_SENDER_ID,
4565
+ clientId: parsed.CLIENT_ID,
4566
+ androidClientID: parsed.ANDROID_CLIENT_ID,
4567
+ deepLinkUrlScheme: parsed.REVERSED_CLIENT_ID
4568
+ };
4569
+ } else {
4570
+ const project = isRecord(parsed.project_info) ? parsed.project_info : {};
4571
+ const clients = Array.isArray(parsed.client) ? parsed.client : [];
4572
+ const client = clients.find((value) => {
4573
+ if (!isRecord(value) || !isRecord(value.client_info)) return false;
4574
+ const android = value.client_info.android_client_info;
4575
+ return isRecord(android) && android.package_name === native.package;
4576
+ });
4577
+ if (!isRecord(client) || !isRecord(client.client_info))
4578
+ throw new Error("No Google services client matches the Android package.");
4579
+ const apiKeys = Array.isArray(client.api_key) ? client.api_key : [];
4580
+ const apiKey = apiKeys.find(
4581
+ (value) => isRecord(value) && typeof value.current_key === "string"
4582
+ );
4583
+ const oauthClients = Array.isArray(client.oauth_client) ? client.oauth_client : [];
4584
+ const oauth = oauthClients.find(
4585
+ (value) => isRecord(value) && value.client_type === 3
4586
+ );
4587
+ result[platform] = {
4588
+ apiKey: isRecord(apiKey) ? apiKey.current_key : void 0,
4589
+ appId: client.client_info.mobilesdk_app_id,
4590
+ projectId: project.project_id,
4591
+ databaseURL: project.firebase_url,
4592
+ storageBucket: project.storage_bucket,
4593
+ messagingSenderId: project.project_number,
4594
+ clientId: isRecord(oauth) ? oauth.client_id : void 0
4595
+ };
4596
+ }
4597
+ } catch (error) {
4598
+ result[platform] = { error: error instanceof Error ? error.message : String(error) };
4599
+ }
4600
+ }
4601
+ return result;
4602
+ }
4603
+
4604
+ // src/metro-plugin.ts
4485
4605
  var prefix = "/__soot";
4486
4606
  function readCallableMetroExport(moduleValue, label) {
4487
4607
  const callable = typeof moduleValue === "function" ? moduleValue : typeof moduleValue === "object" && moduleValue !== null ? Reflect.get(moduleValue, "default") : void 0;
@@ -4602,6 +4722,22 @@ function withRNX(config) {
4602
4722
  });
4603
4723
  return;
4604
4724
  }
4725
+ if ((req.url || "").split("?")[0] === "/__peach-native-firebase-config") {
4726
+ try {
4727
+ res.setHeader("Content-Type", "application/json");
4728
+ res.setHeader("Cache-Control", "no-store");
4729
+ res.setHeader("Access-Control-Allow-Origin", "*");
4730
+ res.end(JSON.stringify(readNativeFirebaseConfig(cfgProjectRoot)));
4731
+ } catch (error) {
4732
+ res.statusCode = 400;
4733
+ res.end(
4734
+ JSON.stringify({
4735
+ error: error instanceof Error ? error.message : String(error)
4736
+ })
4737
+ );
4738
+ }
4739
+ return;
4740
+ }
4605
4741
  const url = req.url || "";
4606
4742
  const pathname = url.split("?")[0];
4607
4743
  const runtimeRoot = resolveRuntimeRootForRequestHost(req.headers?.host);
@@ -4670,13 +4806,13 @@ function resolveMetroPort(server) {
4670
4806
  var metro_plugin_default = withRNX;
4671
4807
 
4672
4808
  // src/jump-to-source-babel.ts
4673
- function normalizeSourcePath(path8) {
4674
- const queryStart = path8.indexOf("?");
4675
- const clean = queryStart >= 0 ? path8.slice(0, queryStart) : path8;
4809
+ function normalizeSourcePath(path9) {
4810
+ const queryStart = path9.indexOf("?");
4811
+ const clean = queryStart >= 0 ? path9.slice(0, queryStart) : path9;
4676
4812
  return clean.replace(/\\/g, "/");
4677
4813
  }
4678
- function matchesPath(path8, matcher) {
4679
- return typeof matcher === "string" ? path8.includes(matcher) : matcher.test(path8);
4814
+ function matchesPath(path9, matcher) {
4815
+ return typeof matcher === "string" ? path9.includes(matcher) : matcher.test(path9);
4680
4816
  }
4681
4817
  function shouldStampFile(filename, options) {
4682
4818
  const normalized = normalizeSourcePath(filename);
@@ -4709,11 +4845,11 @@ function sootsimJumpToSourceBabelPlugin(api, options = {}) {
4709
4845
  return {
4710
4846
  name: "sootsim-jump-to-source",
4711
4847
  visitor: {
4712
- JSXOpeningElement(path8, state) {
4848
+ JSXOpeningElement(path9, state) {
4713
4849
  if (options.enabled === false) return;
4714
4850
  const filename = state.file?.opts?.filename;
4715
4851
  if (!filename || !shouldStampFile(filename, options)) return;
4716
- const node = path8.node;
4852
+ const node = path9.node;
4717
4853
  const tagName = getJSXName(node.name);
4718
4854
  if (!tagName || tagName === "Fragment" || tagName.endsWith(".Fragment")) return;
4719
4855
  if (hasAttribute(node, attrName)) return;
@@ -1,4 +1,4 @@
1
- /*! rnx v0.1.629 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
1
+ /*! rnx v0.1.631 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
2
2
  let __sootsim_import_meta_url = ''; try { __sootsim_import_meta_url = require('url').pathToFileURL(__filename).href; } catch {}
3
3
  "use strict";
4
4
  var __defProp = Object.defineProperty;
@@ -1,4 +1,4 @@
1
- /*! rnx v0.1.629 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
1
+ /*! rnx v0.1.631 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
2
2
  let __sootsim_import_meta_url = ''; try { __sootsim_import_meta_url = require('url').pathToFileURL(__filename).href; } catch {}
3
3
  "use strict";
4
4
  var __create = Object.create;