rnxsim 0.1.344 → 0.1.346

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 (211) hide show
  1. package/README.md +59 -31
  2. package/cli/app-fonts.ts +8 -28
  3. package/cli/app-project.ts +12 -184
  4. package/cli/app-splash.ts +3 -17
  5. package/cli/bin.ts +15 -28
  6. package/cli/commands/control.ts +20 -30
  7. package/cli/commands/daemon.ts +19 -44
  8. package/cli/commands/flow.ts +0 -27
  9. package/cli/commands/install-desktop.ts +2 -16
  10. package/cli/commands/login.ts +3 -43
  11. package/cli/commands/logout.ts +0 -7
  12. package/cli/commands/serve.ts +2 -32
  13. package/cli/commands/setup.ts +20 -194
  14. package/cli/commands/telemetry.ts +7 -5
  15. package/cli/commands/upload.ts +1 -48
  16. package/cli/commands/welcome.ts +64 -0
  17. package/cli/outbound-endpoints.ts +586 -0
  18. package/cli/privacy.ts +53 -0
  19. package/cli/runtime-notes.ts +2 -2
  20. package/cli/telemetry.ts +46 -149
  21. package/dist-cli/bin.js +6 -6
  22. package/dist-cli/chunks/{agent-HTSE5FA4.js → agent-U6HZUHAQ.js} +2 -2
  23. package/dist-cli/chunks/{agent-wrapper-L6X2AEZL.js → agent-wrapper-IM2VL6XP.js} +2 -2
  24. package/dist-cli/chunks/{app-fonts-FGF3ETYH.js → app-fonts-LHVTPWCN.js} +2 -2
  25. package/dist-cli/chunks/{assert-HA2ZYVDN.js → assert-UMGCZXWN.js} +2 -2
  26. package/dist-cli/chunks/{auth-HONDAV75.js → auth-PQY57FPY.js} +2 -2
  27. package/dist-cli/chunks/{beta-U7EE4GMR.js → beta-3MMNRHL7.js} +2 -2
  28. package/dist-cli/chunks/{camera-RTKNUTIX.js → camera-UKISEHMH.js} +2 -2
  29. package/dist-cli/chunks/{chunk-CNLPLCO2.js → chunk-36P343FE.js} +2 -2
  30. package/dist-cli/chunks/{chunk-IWR4HFC4.js → chunk-3KPJVR63.js} +2 -2
  31. package/dist-cli/chunks/chunk-54Y5IXWQ.js +15 -0
  32. package/dist-cli/chunks/{chunk-EPEZZ6YS.js → chunk-5KMV7NAJ.js} +2 -2
  33. package/dist-cli/chunks/{chunk-NDGAF5OF.js → chunk-5WIYZI5H.js} +2 -2
  34. package/dist-cli/chunks/{chunk-KBSVSAAE.js → chunk-6GXUQ3DM.js} +2 -2
  35. package/dist-cli/chunks/{chunk-KT6WMCUS.js → chunk-6MUEKKJ4.js} +1 -1
  36. package/dist-cli/chunks/{chunk-TOHQI2II.js → chunk-6Q6S53R7.js} +1 -1
  37. package/dist-cli/chunks/{chunk-63NZGJ4A.js → chunk-6SQCDLTJ.js} +1 -1
  38. package/dist-cli/chunks/{chunk-Q2B6EQPB.js → chunk-6UW6O575.js} +2 -2
  39. package/dist-cli/chunks/chunk-6VH3UUYQ.js +4 -0
  40. package/dist-cli/chunks/{chunk-BGLWR43R.js → chunk-6XJGGFMD.js} +1 -1
  41. package/dist-cli/chunks/{chunk-TZNRKIS3.js → chunk-75LMZXF5.js} +2 -2
  42. package/dist-cli/chunks/{chunk-FZ5DRZJE.js → chunk-7CJNUNQA.js} +3 -3
  43. package/dist-cli/chunks/{chunk-UCVQOQCY.js → chunk-BQDGFR4B.js} +2 -2
  44. package/dist-cli/chunks/{chunk-H4J7EWZC.js → chunk-BSIVS2SQ.js} +1 -1
  45. package/dist-cli/chunks/{chunk-6E6SBYIO.js → chunk-CD2LITQX.js} +1 -1
  46. package/dist-cli/chunks/{chunk-PNMCALX5.js → chunk-DGH3D6WS.js} +2 -2
  47. package/dist-cli/chunks/chunk-EP6XBKPM.js +11 -0
  48. package/dist-cli/chunks/{chunk-OCUXAA6O.js → chunk-EVX6Y7ZM.js} +2 -2
  49. package/dist-cli/chunks/chunk-EYRKIWHE.js +4 -0
  50. package/dist-cli/chunks/{chunk-N5PFDTFL.js → chunk-FSYB2S6Q.js} +17 -17
  51. package/dist-cli/chunks/{chunk-6PEZSVIG.js → chunk-HPCTBEAP.js} +1 -1
  52. package/dist-cli/chunks/chunk-HS2BME6V.js +6 -0
  53. package/dist-cli/chunks/{chunk-G6IPT2PS.js → chunk-I3GUDHG2.js} +2 -2
  54. package/dist-cli/chunks/{chunk-MWUXQIA3.js → chunk-I4HVFKLM.js} +2 -2
  55. package/dist-cli/chunks/{chunk-ZPTFL5FQ.js → chunk-IBNK7SWL.js} +2 -2
  56. package/dist-cli/chunks/chunk-IUG4GWQC.js +25 -0
  57. package/dist-cli/chunks/{chunk-XQFDEY3F.js → chunk-IUIKD2YI.js} +1 -1
  58. package/dist-cli/chunks/{chunk-JFUVWIJT.js → chunk-JPVXLY7X.js} +1 -1
  59. package/dist-cli/chunks/{chunk-JLUEQPEF.js → chunk-K4PZLT2K.js} +1 -1
  60. package/dist-cli/chunks/{chunk-CRWYKW67.js → chunk-KNIKUJPD.js} +1 -1
  61. package/dist-cli/chunks/{chunk-EZLLZDYX.js → chunk-L6GWDWGZ.js} +2 -2
  62. package/dist-cli/chunks/chunk-LAVDDUQR.js +4 -0
  63. package/dist-cli/chunks/{chunk-22WJPMTQ.js → chunk-LPXKPZ4B.js} +1 -1
  64. package/dist-cli/chunks/{chunk-IPC45HTT.js → chunk-LYLK5VRP.js} +2 -2
  65. package/dist-cli/chunks/{chunk-S6LBAUWH.js → chunk-ME46FC4L.js} +1 -1
  66. package/dist-cli/chunks/chunk-NNXSLD76.js +99 -0
  67. package/dist-cli/chunks/{chunk-7V4ULF3U.js → chunk-PNH3YBZ4.js} +2 -2
  68. package/dist-cli/chunks/{chunk-GRX6NSBG.js → chunk-PPEJMNRO.js} +2 -2
  69. package/dist-cli/chunks/{chunk-K3VNCCEJ.js → chunk-SEWGXJG6.js} +3 -3
  70. package/dist-cli/chunks/{chunk-R3UYHE6G.js → chunk-T4JPZI32.js} +1 -1
  71. package/dist-cli/chunks/{chunk-NRTHFEPM.js → chunk-TAQYETRH.js} +2 -2
  72. package/dist-cli/chunks/{chunk-7JEEO33Y.js → chunk-TBVBWJJL.js} +2 -2
  73. package/dist-cli/chunks/{chunk-HRBMD2FS.js → chunk-UA54YLWR.js} +26 -26
  74. package/dist-cli/chunks/{chunk-G3UAFZE2.js → chunk-UV3TXNMG.js} +1 -1
  75. package/dist-cli/chunks/{chunk-EH35QQG4.js → chunk-VZ6YFPIN.js} +45 -45
  76. package/dist-cli/chunks/chunk-W54SRZ5K.js +14 -0
  77. package/dist-cli/chunks/chunk-WAC75WN6.js +4 -0
  78. package/dist-cli/chunks/{chunk-S7CSBQ6G.js → chunk-X2WIEZ7Q.js} +1 -1
  79. package/dist-cli/chunks/{chunk-EN32LN2B.js → chunk-XWTYFWZ3.js} +2 -2
  80. package/dist-cli/chunks/chunk-Y2C3CZVD.js +4 -0
  81. package/dist-cli/chunks/chunk-YM4L2CFB.js +4 -0
  82. package/dist-cli/chunks/{chunk-ZZSBCGHC.js → chunk-YXDR4CAQ.js} +1 -1
  83. package/dist-cli/chunks/{chunk-6NLWKL7Y.js → chunk-Z6P427WC.js} +2 -2
  84. package/dist-cli/chunks/{cleanup-EMYD5WZG.js → cleanup-KXPS5VSK.js} +2 -2
  85. package/dist-cli/chunks/cli-version-LJ6NFLBP.js +4 -0
  86. package/dist-cli/chunks/{compat-W2N5QTVG.js → compat-JWF7N3VO.js} +2 -2
  87. package/dist-cli/chunks/config-M42VJXNK.js +13 -0
  88. package/dist-cli/chunks/{control-4SFBROUE.js → control-HMZDIS6P.js} +2 -2
  89. package/dist-cli/chunks/daemon-MQ4UYKNQ.js +4 -0
  90. package/dist-cli/chunks/{debug-HCR2KFUA.js → debug-F44GWYMF.js} +3 -3
  91. package/dist-cli/chunks/{desktop-T2KS5PBJ.js → desktop-YJMWW6MW.js} +3 -3
  92. package/dist-cli/chunks/{detox-5S7EHINH.js → detox-5B4ASTJO.js} +2 -2
  93. package/dist-cli/chunks/{device-LVM3MZRJ.js → device-2544YZNY.js} +2 -2
  94. package/dist-cli/chunks/{diagnose-WK37XKLI.js → diagnose-LS4245JR.js} +2 -2
  95. package/dist-cli/chunks/{disk-cleanup-X7VJOXMS.js → disk-cleanup-XYL2HQQY.js} +2 -2
  96. package/dist-cli/chunks/drivers-P5CRSKH6.js +4 -0
  97. package/dist-cli/chunks/{film-7KLTI3ER.js → film-LS3TIZRM.js} +3 -3
  98. package/dist-cli/chunks/flow-7V77GXAB.js +4 -0
  99. package/dist-cli/chunks/help-HRESIFZX.js +4 -0
  100. package/dist-cli/chunks/{hidden-runtime-alias-4RVGUVFO.js → hidden-runtime-alias-HGN2EEQR.js} +2 -2
  101. package/dist-cli/chunks/{home-paths-5DHSAEGZ.js → home-paths-T3NTXE4U.js} +2 -2
  102. package/dist-cli/chunks/inspect-UC44UH4M.js +4 -0
  103. package/dist-cli/chunks/install-desktop-RDV66JHG.js +4 -0
  104. package/dist-cli/chunks/login-WVP5H5NQ.js +26 -0
  105. package/dist-cli/chunks/logout-GXX5OY6X.js +4 -0
  106. package/dist-cli/chunks/{maestro-HTRLQDJE.js → maestro-MQMGBNOB.js} +3 -3
  107. package/dist-cli/chunks/{maestro-generate-GX2QNA3F.js → maestro-generate-CO355QV2.js} +3 -3
  108. package/dist-cli/chunks/{mode-GM6KZHQP.js → mode-QFB3M2AA.js} +2 -2
  109. package/dist-cli/chunks/{optional-demo-registry-B4TD7JQP.js → optional-demo-registry-UIPVSBHV.js} +2 -2
  110. package/dist-cli/chunks/{perf-3PRXJQIC.js → perf-TDOSW3LD.js} +2 -2
  111. package/dist-cli/chunks/{permissions-TJZIRF2S.js → permissions-TEVQLCGJ.js} +2 -2
  112. package/dist-cli/chunks/privacy-DVHAMHR6.js +4 -0
  113. package/dist-cli/chunks/{record-LI55DTPE.js → record-OFEQLSWA.js} +3 -3
  114. package/dist-cli/chunks/{report-issue-TBGJVQIQ.js → report-issue-B2UPOERJ.js} +2 -2
  115. package/dist-cli/chunks/reset-XQQYM55B.js +4 -0
  116. package/dist-cli/chunks/runtime-GN6HITVB.js +4 -0
  117. package/dist-cli/chunks/{screenshot-command-BEN7YYIF.js → screenshot-command-NTLAY7QN.js} +3 -3
  118. package/dist-cli/chunks/{screenshot-layers-3IU4KYNO.js → screenshot-layers-BBLKNRQU.js} +2 -2
  119. package/dist-cli/chunks/{screenshots-capture-ZRC54ST6.js → screenshots-capture-6GHIRCTG.js} +2 -2
  120. package/dist-cli/chunks/serve-H6GJ4OHW.js +118 -0
  121. package/dist-cli/chunks/setup-6YLKSMWS.js +30 -0
  122. package/dist-cli/chunks/{skills-GKBR2HQB.js → skills-FUL7D6C7.js} +2 -2
  123. package/dist-cli/chunks/{state-LIHZJSL7.js → state-O2HE7XSR.js} +2 -2
  124. package/dist-cli/chunks/{storage-4TPN3NFB.js → storage-WIZTF7RN.js} +4 -4
  125. package/dist-cli/chunks/store-NJ2MBZC2.js +4 -0
  126. package/dist-cli/chunks/telemetry-BEC6W5WP.js +4 -0
  127. package/dist-cli/chunks/{timeline-EX63UNWU.js → timeline-MA4JT7UJ.js} +2 -2
  128. package/dist-cli/chunks/{upgrade-O6Y6EINE.js → upgrade-H5LSDXBH.js} +2 -2
  129. package/dist-cli/chunks/upload-Q4DWKIA4.js +4 -0
  130. package/dist-cli/chunks/{version-HLNL3NFB.js → version-4UWEEDVP.js} +2 -2
  131. package/dist-cli/chunks/{web-4EDPYPW7.js → web-F3LXRN3K.js} +2 -2
  132. package/dist-cli/chunks/welcome-AZBOSIG2.js +5 -0
  133. package/dist-cli/chunks/{what-happened-77VNUEPK.js → what-happened-6OSNLOAY.js} +2 -2
  134. package/dist-lib/agent-daemon-client.cjs +1 -1
  135. package/dist-lib/agent-events.cjs +1 -1
  136. package/dist-lib/agent-identity.cjs +1 -1
  137. package/dist-lib/agent-sessions.cjs +1 -1
  138. package/dist-lib/attached-projects.cjs +1 -1
  139. package/dist-lib/auth/shared-session.cjs +1 -1
  140. package/dist-lib/backend-origin.cjs +13 -1
  141. package/dist-lib/beta.cjs +1 -1
  142. package/dist-lib/beta.mjs +1 -1
  143. package/dist-lib/bridge-constants.cjs +1 -1
  144. package/dist-lib/bridge-contract.cjs +1 -1
  145. package/dist-lib/cli-constants.cjs +1 -1
  146. package/dist-lib/config.cjs +1 -1
  147. package/dist-lib/detox/index.cjs +1 -1
  148. package/dist-lib/dev-bundle-resolution.cjs +43 -13
  149. package/dist-lib/home-paths.cjs +145 -11
  150. package/dist-lib/host/bridge-host.cjs +326 -78
  151. package/dist-lib/host/fetch-proxy-handler.cjs +1 -1
  152. package/dist-lib/host/fetch-proxy-overrides.cjs +1 -1
  153. package/dist-lib/host/fetch-proxy-overrides.mjs +1 -1
  154. package/dist-lib/host/replacement-module-handler.cjs +71 -4
  155. package/dist-lib/host/websocket-proxy.cjs +1 -1
  156. package/dist-lib/index.cjs +337 -37
  157. package/dist-lib/jump-to-source-babel.cjs +1 -1
  158. package/dist-lib/menu.cjs +1 -1
  159. package/dist-lib/menu.mjs +1 -1
  160. package/dist-lib/metro-production-bundle.cjs +1 -1
  161. package/dist-lib/metro-production-bundle.mjs +1 -1
  162. package/dist-lib/metro.cjs +484 -14
  163. package/dist-lib/profiles.cjs +1 -1
  164. package/dist-lib/public-brand.cjs +1 -1
  165. package/dist-lib/react-native-host-modules.cjs +1 -1
  166. package/dist-lib/react-native-host-modules.mjs +1 -1
  167. package/dist-lib/render-mode.cjs +1 -1
  168. package/dist-lib/scripts/dev-server-scanner.cjs +120 -50
  169. package/dist-lib/sdk.cjs +1 -1
  170. package/dist-lib/sdk.mjs +1 -1
  171. package/dist-lib/skills.cjs +493 -439
  172. package/dist-lib/vite.cjs +193 -37
  173. package/package.json +2 -1
  174. package/scripts/dev-server-scanner.ts +112 -51
  175. package/skills/rnx-test/SKILL.md +4 -0
  176. package/src/backend-origin.ts +15 -1
  177. package/src/dev-bundle-resolution.ts +42 -18
  178. package/src/dev-server-open.ts +197 -0
  179. package/src/home-paths.ts +168 -15
  180. package/src/host/bridge-host.ts +2 -2
  181. package/src/host/replacement-module-handler.ts +68 -2
  182. package/src/metro-plugin.ts +83 -5
  183. package/src/native-dev-bundle-url.ts +22 -0
  184. package/src/vite-plugin-one.ts +42 -28
  185. package/dist-cli/chunks/chunk-3JC7H4PR.js +0 -25
  186. package/dist-cli/chunks/chunk-6DLIC7BZ.js +0 -4
  187. package/dist-cli/chunks/chunk-7TNPO2OJ.js +0 -9
  188. package/dist-cli/chunks/chunk-AMF5KTOI.js +0 -4
  189. package/dist-cli/chunks/chunk-BX5VXTOQ.js +0 -101
  190. package/dist-cli/chunks/chunk-D73H7ACZ.js +0 -4
  191. package/dist-cli/chunks/chunk-DY4O7KYI.js +0 -4
  192. package/dist-cli/chunks/chunk-GU2V4JDG.js +0 -15
  193. package/dist-cli/chunks/chunk-POOCKX5B.js +0 -14
  194. package/dist-cli/chunks/chunk-Z2HMSVSN.js +0 -4
  195. package/dist-cli/chunks/cli-version-CN3NTKLH.js +0 -4
  196. package/dist-cli/chunks/config-SELKGT4F.js +0 -13
  197. package/dist-cli/chunks/daemon-V2SFCU2E.js +0 -4
  198. package/dist-cli/chunks/drivers-3YFTJ5WA.js +0 -4
  199. package/dist-cli/chunks/flow-36ANTMJU.js +0 -4
  200. package/dist-cli/chunks/help-ZGL26SPK.js +0 -4
  201. package/dist-cli/chunks/inspect-R3B55RTS.js +0 -4
  202. package/dist-cli/chunks/install-desktop-TZ3H3VWK.js +0 -4
  203. package/dist-cli/chunks/login-4DFULI7S.js +0 -28
  204. package/dist-cli/chunks/logout-QMCVYMBB.js +0 -4
  205. package/dist-cli/chunks/reset-O6MW6X7X.js +0 -4
  206. package/dist-cli/chunks/runtime-JESXBHRQ.js +0 -4
  207. package/dist-cli/chunks/serve-QGKTWPNF.js +0 -118
  208. package/dist-cli/chunks/setup-3WPWATCX.js +0 -32
  209. package/dist-cli/chunks/store-VBX3L46S.js +0 -4
  210. package/dist-cli/chunks/telemetry-ACNNIQIG.js +0 -4
  211. package/dist-cli/chunks/upload-OD2UQHMM.js +0 -4
@@ -1,4 +1,4 @@
1
- /*! rnx v0.1.344 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
1
+ /*! rnx v0.1.346 | (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;
@@ -9476,6 +9476,27 @@ var init_bridge_constants = __esm({
9476
9476
  }
9477
9477
  });
9478
9478
 
9479
+ // src/backend-origin.ts
9480
+ function normalizeHostname(hostname) {
9481
+ return hostname.replace(/^\[|\]$/g, "").toLowerCase();
9482
+ }
9483
+ function buildLoopbackProbeBaseUrl(protocol, hostname, port) {
9484
+ const normalizedHostname = normalizeHostname(hostname);
9485
+ const urlHost = normalizedHostname.includes(":") ? `[${normalizedHostname}]` : normalizedHostname;
9486
+ return `${protocol}//${urlHost}:${port}`;
9487
+ }
9488
+ function isLoopbackHost(hostname) {
9489
+ const normalized = normalizeHostname(hostname);
9490
+ return normalized === "localhost" || normalized.endsWith(".localhost") || normalized === "0.0.0.0" || normalized === "::1" || /^127(?:\.\d{1,3}){3}$/.test(normalized);
9491
+ }
9492
+ var LOOPBACK_PROBE_HOSTNAMES;
9493
+ var init_backend_origin = __esm({
9494
+ "src/backend-origin.ts"() {
9495
+ "use strict";
9496
+ LOOPBACK_PROBE_HOSTNAMES = ["::1", "127.0.0.1"];
9497
+ }
9498
+ });
9499
+
9479
9500
  // src/config.ts
9480
9501
  function hasOwnKeys(value) {
9481
9502
  return !!value && Object.keys(value).length > 0;
@@ -9531,6 +9552,19 @@ function isAbsoluteHttpUrl(url) {
9531
9552
  function isNativeDevBundlePath(pathname) {
9532
9553
  return pathname.endsWith(".bundle");
9533
9554
  }
9555
+ function alignLoopbackBundleUrlWithProbe(bundleUrl, probeBaseUrl) {
9556
+ try {
9557
+ const bundle = new URL(bundleUrl);
9558
+ const probe3 = new URL(probeBaseUrl);
9559
+ if (!isLoopbackHost(bundle.hostname) || !isLoopbackHost(probe3.hostname)) {
9560
+ return bundleUrl;
9561
+ }
9562
+ bundle.hostname = probe3.hostname;
9563
+ return bundle.toString();
9564
+ } catch {
9565
+ return bundleUrl;
9566
+ }
9567
+ }
9534
9568
  function applyPreviewProdBundle(parsed) {
9535
9569
  if (!process.env.SOOTSIM_PREVIEW_PROD_BUNDLE) return;
9536
9570
  parsed.searchParams.set("dev", "false");
@@ -9570,6 +9604,7 @@ var METRO_BUNDLE_PATH;
9570
9604
  var init_native_dev_bundle_url = __esm({
9571
9605
  "src/native-dev-bundle-url.ts"() {
9572
9606
  "use strict";
9607
+ init_backend_origin();
9573
9608
  METRO_BUNDLE_PATH = "/index.bundle?platform=ios&dev=true&hot=true&minify=false";
9574
9609
  }
9575
9610
  });
@@ -9678,9 +9713,9 @@ function safeChildPlistName(value) {
9678
9713
  name: file.toLowerCase().endsWith(".plist") ? file : `${file}.plist`
9679
9714
  };
9680
9715
  }
9681
- async function readableSettingsBundle(path19) {
9716
+ async function readableSettingsBundle(path18) {
9682
9717
  try {
9683
- const root = await (0, import_promises.stat)((0, import_node_path.join)(path19, "Root.plist"));
9718
+ const root = await (0, import_promises.stat)((0, import_node_path.join)(path18, "Root.plist"));
9684
9719
  return root.isFile() && root.size <= MAX_PLIST_BYTES;
9685
9720
  } catch {
9686
9721
  return false;
@@ -9725,11 +9760,11 @@ async function settingsBundleFingerprint(bundlePath) {
9725
9760
  return null;
9726
9761
  }
9727
9762
  }
9728
- async function parsePlistRecord(path19) {
9763
+ async function parsePlistRecord(path18) {
9729
9764
  try {
9730
- const metadata = await (0, import_promises.stat)(path19);
9765
+ const metadata = await (0, import_promises.stat)(path18);
9731
9766
  if (!metadata.isFile() || metadata.size > MAX_PLIST_BYTES) return null;
9732
- const source = await (0, import_promises.readFile)(path19, "utf8");
9767
+ const source = await (0, import_promises.readFile)(path18, "utf8");
9733
9768
  const parsed = parsePlistSource(source);
9734
9769
  return isRecord2(parsed) ? parsed : null;
9735
9770
  } catch {
@@ -9991,27 +10026,36 @@ var init_optional_demo_registry = __esm({
9991
10026
  });
9992
10027
 
9993
10028
  // scripts/dev-server-scanner.ts
9994
- function tcpPing(port, timeout = TCP_GATE_MS) {
9995
- return new Promise((resolve7) => {
9996
- const sock = new import_net.default.Socket();
9997
- let settled = false;
9998
- const done = (ok) => {
9999
- if (settled) return;
10000
- settled = true;
10001
- sock.destroy();
10002
- resolve7(ok);
10003
- };
10004
- sock.setTimeout(timeout);
10005
- sock.once("connect", () => done(true));
10006
- sock.once("timeout", () => done(false));
10007
- sock.once("error", () => done(false));
10008
- sock.connect(port, "localhost");
10009
- });
10029
+ async function tcpPing(port, timeout = TCP_GATE_MS) {
10030
+ try {
10031
+ return await Promise.any(
10032
+ LOOPBACK_PROBE_HOSTNAMES.map(
10033
+ (hostname) => new Promise((resolve7, reject) => {
10034
+ const sock = new import_net.default.Socket();
10035
+ let settled = false;
10036
+ const done = (reachable) => {
10037
+ if (settled) return;
10038
+ settled = true;
10039
+ sock.destroy();
10040
+ if (reachable) resolve7(hostname);
10041
+ else reject(new Error(`unreachable loopback host ${hostname}`));
10042
+ };
10043
+ sock.setTimeout(timeout);
10044
+ sock.once("connect", () => done(true));
10045
+ sock.once("timeout", () => done(false));
10046
+ sock.once("error", () => done(false));
10047
+ sock.connect(port, hostname);
10048
+ })
10049
+ )
10050
+ );
10051
+ } catch {
10052
+ return null;
10053
+ }
10010
10054
  }
10011
- function httpGet(port, path19, method = "GET", timeout = TIMEOUT_MS, headers = {}) {
10055
+ function httpGet(hostname, port, path18, method = "GET", timeout = TIMEOUT_MS, headers = {}) {
10012
10056
  return new Promise((resolve7) => {
10013
10057
  const req = import_http.default.request(
10014
- { hostname: "localhost", port, path: path19, method, timeout, headers },
10058
+ { hostname, port, path: path18, method, timeout, headers },
10015
10059
  (res) => {
10016
10060
  let body = "";
10017
10061
  res.on("data", (c) => body += c.toString());
@@ -10023,7 +10067,12 @@ function httpGet(port, path19, method = "GET", timeout = TIMEOUT_MS, headers = {
10023
10067
  })();
10024
10068
  res.on(
10025
10069
  "end",
10026
- () => resolve7({ statusCode: res.statusCode || 0, body, contentType })
10070
+ () => resolve7({
10071
+ statusCode: res.statusCode || 0,
10072
+ body,
10073
+ contentType,
10074
+ hostname
10075
+ })
10027
10076
  );
10028
10077
  }
10029
10078
  );
@@ -10163,16 +10212,16 @@ async function resolveProcessCwd(pid) {
10163
10212
  }
10164
10213
  return null;
10165
10214
  }
10166
- function makeResult(port, framework, owner) {
10215
+ function makeResult(hostname, port, framework, owner) {
10167
10216
  return {
10168
10217
  port,
10169
10218
  framework,
10170
10219
  bundleUrl: withRuntimeConfig(
10171
10220
  port,
10172
- `http://localhost:${port}/index.bundle?platform=ios&dev=true&hot=true&minify=false`,
10221
+ `${buildLoopbackProbeBaseUrl("http:", hostname, port)}/index.bundle?platform=ios&dev=true&hot=true&minify=false`,
10173
10222
  owner.cwd
10174
10223
  ),
10175
- hmrUrl: `ws://localhost:${port}/hot`,
10224
+ hmrUrl: `${buildLoopbackProbeBaseUrl("ws:", hostname, port)}/hot`,
10176
10225
  lastSeen: Date.now(),
10177
10226
  ...owner
10178
10227
  };
@@ -10203,7 +10252,11 @@ function applyManifest(result, manifestRes, buildIconProxyUrl) {
10203
10252
  if (client.name) result.projectName = client.name;
10204
10253
  if (client.ios?.bundleIdentifier) result.bundleId = client.ios.bundleIdentifier;
10205
10254
  if (result.framework === "metro" && client.sdkVersion) result.framework = "expo";
10206
- const launchUrl2 = manifest?.launchAsset?.url;
10255
+ const rawLaunchUrl = manifest?.launchAsset?.url;
10256
+ const launchUrl2 = typeof rawLaunchUrl === "string" ? alignLoopbackBundleUrlWithProbe(
10257
+ rawLaunchUrl,
10258
+ buildLoopbackProbeBaseUrl("http:", manifestRes.hostname, result.port)
10259
+ ) : rawLaunchUrl;
10207
10260
  if (launchUrl2 && !result.patched && !isDirectOneBundleUrl(result.bundleUrl)) {
10208
10261
  result.bundleUrl = withRuntimeConfig(result.port, launchUrl2, result.cwd);
10209
10262
  }
@@ -10216,11 +10269,11 @@ function applyManifest(result, manifestRes, buildIconProxyUrl) {
10216
10269
  } else {
10217
10270
  const cleanPath = rawIconUrl.replace(/^\.\//, "");
10218
10271
  result.iconUrl = buildIconProxyUrl(
10219
- `http://localhost:${result.port}/assets/${cleanPath}`
10272
+ `${buildLoopbackProbeBaseUrl("http:", manifestRes.hostname, result.port)}/assets/${cleanPath}`
10220
10273
  );
10221
10274
  }
10222
10275
  } else {
10223
- result.iconUrl = rawIconUrl.startsWith("http") ? rawIconUrl : `http://localhost:${result.port}/assets/${rawIconUrl.replace(/^\.\//, "")}`;
10276
+ result.iconUrl = rawIconUrl.startsWith("http") ? rawIconUrl : `${buildLoopbackProbeBaseUrl("http:", manifestRes.hostname, result.port)}/assets/${rawIconUrl.replace(/^\.\//, "")}`;
10224
10277
  }
10225
10278
  }
10226
10279
  } catch {
@@ -10241,35 +10294,48 @@ function enrichWithManifest(result, buildIconProxyUrl) {
10241
10294
  }
10242
10295
  if (!enrichmentInFlight.has(result.port)) {
10243
10296
  enrichmentInFlight.add(result.port);
10244
- const port = result.port;
10245
- void fetchManifestEnrichment(port, buildIconProxyUrl).finally(
10246
- () => enrichmentInFlight.delete(port)
10297
+ void fetchManifestEnrichment(result, buildIconProxyUrl).finally(
10298
+ () => enrichmentInFlight.delete(result.port)
10247
10299
  );
10248
10300
  }
10249
10301
  return result;
10250
10302
  }
10251
- async function fetchManifestEnrichment(port, buildIconProxyUrl) {
10252
- const manifestRes = await httpGet(port, "/", "GET", ENRICH_TIMEOUT_MS, {
10253
- "expo-platform": "ios"
10254
- });
10303
+ async function fetchManifestEnrichment(result, buildIconProxyUrl) {
10304
+ const hostname = normalizeHostname(new URL(result.bundleUrl).hostname);
10305
+ const manifestRes = await httpGet(
10306
+ hostname,
10307
+ result.port,
10308
+ "/",
10309
+ "GET",
10310
+ ENRICH_TIMEOUT_MS,
10311
+ {
10312
+ "expo-platform": "ios"
10313
+ }
10314
+ );
10255
10315
  if (!manifestRes || manifestRes.statusCode !== 200) return;
10256
10316
  const parsed = applyManifest(
10257
- { port, framework: "one", bundleUrl: "", lastSeen: 0 },
10317
+ {
10318
+ port: result.port,
10319
+ framework: "one",
10320
+ bundleUrl: `${buildLoopbackProbeBaseUrl("http:", hostname, result.port)}/index.bundle`,
10321
+ lastSeen: 0
10322
+ },
10258
10323
  manifestRes,
10259
10324
  buildIconProxyUrl
10260
10325
  );
10261
10326
  if (!parsed.projectName && !parsed.iconUrl) return;
10262
- enrichmentCache.set(port, {
10327
+ enrichmentCache.set(result.port, {
10263
10328
  projectName: parsed.projectName,
10264
10329
  iconUrl: parsed.iconUrl,
10265
10330
  iconPath: parsed.iconPath,
10266
10331
  bundleId: parsed.bundleId
10267
10332
  });
10268
- portCache.delete(port);
10333
+ portCache.delete(result.port);
10269
10334
  }
10270
10335
  async function probePort(port, buildIconProxyUrl, listeningProcess) {
10271
10336
  await loadOptionalDemoApps();
10272
- if (!await tcpPing(port)) return null;
10337
+ const probeHostname = await tcpPing(port);
10338
+ if (!probeHostname) return null;
10273
10339
  const suppliedPid = listeningProcess?.pid;
10274
10340
  const ownerPid = suppliedPid && suppliedPid > 0 ? suppliedPid : await resolveListeningPid(port);
10275
10341
  const ownerCwd = ownerPid ? await resolveProcessCwd(ownerPid) : null;
@@ -10279,10 +10345,12 @@ async function probePort(port, buildIconProxyUrl, listeningProcess) {
10279
10345
  };
10280
10346
  const onePath = `/node_modules/one/metro-entry.bundle?platform=ios&dev=true`;
10281
10347
  const [sootsimRes, statusRes, manifestRes, expoRes] = await Promise.all([
10282
- knownNonPatched.has(port) ? Promise.resolve(null) : httpGet(port, "/__soot/"),
10283
- httpGet(port, "/status"),
10284
- knownOne.has(port) ? Promise.resolve(null) : httpGet(port, "/", "GET", MANIFEST_TIMEOUT_MS, { "expo-platform": "ios" }),
10285
- knownNonExpo.has(port) ? Promise.resolve(null) : httpGet(port, "/_expo/status")
10348
+ knownNonPatched.has(port) ? Promise.resolve(null) : httpGet(probeHostname, port, "/__soot/"),
10349
+ httpGet(probeHostname, port, "/status"),
10350
+ knownOne.has(port) ? Promise.resolve(null) : httpGet(probeHostname, port, "/", "GET", MANIFEST_TIMEOUT_MS, {
10351
+ "expo-platform": "ios"
10352
+ }),
10353
+ knownNonExpo.has(port) ? Promise.resolve(null) : httpGet(probeHostname, port, "/_expo/status")
10286
10354
  ]);
10287
10355
  if (expoRes && expoRes.statusCode === 200) {
10288
10356
  knownNonExpo.delete(port);
@@ -10294,7 +10362,7 @@ async function probePort(port, buildIconProxyUrl, listeningProcess) {
10294
10362
  const manifestClient = manifestParsed?.extra?.expoClient || manifestParsed?.extra || {};
10295
10363
  if (manifestParsed && (manifestLaunchUrl || typeof manifestClient.name === "string")) {
10296
10364
  knownNonPatched.add(port);
10297
- const launchUrl2 = manifestLaunchUrl || `http://localhost:${port}/index.bundle?platform=ios&dev=true&hot=true&minify=false`;
10365
+ const launchUrl2 = manifestLaunchUrl || `${buildLoopbackProbeBaseUrl("http:", probeHostname, port)}/index.bundle?platform=ios&dev=true&hot=true&minify=false`;
10298
10366
  const framework = launchUrl2.includes(
10299
10367
  "/one/metro-entry.bundle"
10300
10368
  ) ? "one" : "expo";
@@ -10303,7 +10371,7 @@ async function probePort(port, buildIconProxyUrl, listeningProcess) {
10303
10371
  port,
10304
10372
  framework,
10305
10373
  bundleUrl: withRuntimeConfig(port, launchUrl2, owner.cwd),
10306
- hmrUrl: `ws://localhost:${port}/hot`,
10374
+ hmrUrl: `${buildLoopbackProbeBaseUrl("ws:", probeHostname, port)}/hot`,
10307
10375
  lastSeen: Date.now(),
10308
10376
  ...owner
10309
10377
  },
@@ -10315,7 +10383,12 @@ async function probePort(port, buildIconProxyUrl, listeningProcess) {
10315
10383
  knownNonPatched.add(port);
10316
10384
  return enrichWithManifest(
10317
10385
  applyManifest(
10318
- makeResult(port, expoRes && expoRes.statusCode === 200 ? "expo" : "metro", owner),
10386
+ makeResult(
10387
+ probeHostname,
10388
+ port,
10389
+ expoRes && expoRes.statusCode === 200 ? "expo" : "metro",
10390
+ owner
10391
+ ),
10319
10392
  manifestRes,
10320
10393
  buildIconProxyUrl
10321
10394
  ),
@@ -10331,10 +10404,10 @@ async function probePort(port, buildIconProxyUrl, listeningProcess) {
10331
10404
  framework: "one",
10332
10405
  bundleUrl: withRuntimeConfig(
10333
10406
  port,
10334
- `http://localhost:${port}/__soot/bundle.js`,
10407
+ `${buildLoopbackProbeBaseUrl("http:", probeHostname, port)}/__soot/bundle.js`,
10335
10408
  owner.cwd
10336
10409
  ),
10337
- hmrUrl: `ws://localhost:${port}/hot`,
10410
+ hmrUrl: `${buildLoopbackProbeBaseUrl("ws:", probeHostname, port)}/hot`,
10338
10411
  lastSeen: Date.now(),
10339
10412
  patched: true,
10340
10413
  ...owner
@@ -10349,7 +10422,7 @@ async function probePort(port, buildIconProxyUrl, listeningProcess) {
10349
10422
  if (looksLikeMetroPackager) {
10350
10423
  return null;
10351
10424
  }
10352
- const oneRes = await httpGet(port, onePath, "HEAD");
10425
+ const oneRes = await httpGet(probeHostname, port, onePath, "HEAD");
10353
10426
  if (oneRes && oneRes.statusCode > 0 && oneRes.statusCode < 400 && /application\/javascript/i.test(oneRes.contentType || "")) {
10354
10427
  knownNonPatched.add(port);
10355
10428
  knownOne.add(port);
@@ -10360,10 +10433,10 @@ async function probePort(port, buildIconProxyUrl, listeningProcess) {
10360
10433
  framework: "one",
10361
10434
  bundleUrl: withRuntimeConfig(
10362
10435
  port,
10363
- `http://localhost:${port}${onePath}&minify=false`,
10436
+ `${buildLoopbackProbeBaseUrl("http:", probeHostname, port)}${onePath}&minify=false`,
10364
10437
  owner.cwd
10365
10438
  ),
10366
- hmrUrl: `ws://localhost:${port}/hot`,
10439
+ hmrUrl: `${buildLoopbackProbeBaseUrl("ws:", probeHostname, port)}/hot`,
10367
10440
  lastSeen: Date.now(),
10368
10441
  ...owner
10369
10442
  },
@@ -10485,6 +10558,7 @@ var init_dev_server_scanner = __esm({
10485
10558
  import_net = __toESM(require("net"), 1);
10486
10559
  import_node_path3 = require("node:path");
10487
10560
  import_util = require("util");
10561
+ init_backend_origin();
10488
10562
  init_config();
10489
10563
  init_native_dev_bundle_url();
10490
10564
  init_settings_bundle();
@@ -10724,8 +10798,8 @@ function readPrivacyPreferences() {
10724
10798
  const productAnalytics = settings?.productAnalytics;
10725
10799
  const crashReports = settings?.crashReports;
10726
10800
  return {
10727
- productAnalytics: typeof productAnalytics === "boolean" ? productAnalytics : true,
10728
- crashReports: typeof crashReports === "boolean" ? crashReports : true,
10801
+ productAnalytics: typeof productAnalytics === "boolean" ? productAnalytics : false,
10802
+ crashReports: typeof crashReports === "boolean" ? crashReports : false,
10729
10803
  configured: typeof productAnalytics === "boolean" && typeof crashReports === "boolean"
10730
10804
  };
10731
10805
  }
@@ -10894,10 +10968,30 @@ function readLiveRuntimeVersions() {
10894
10968
  }
10895
10969
  function writeDaemonLockfile(data) {
10896
10970
  ensureSootsimHome();
10897
- const tmp = `${daemonLockfilePath()}.tmp`;
10971
+ const lockfilePath = daemonLockfilePath();
10972
+ const existing = readDaemonLockfile();
10973
+ if (!existing) {
10974
+ if (createDaemonLockfileExclusive(data)) return;
10975
+ throw new Error("another rnx daemon claimed the lockfile before this write");
10976
+ }
10977
+ if (!sameDaemonOwner(existing, data)) {
10978
+ throw new Error("refusing to overwrite another rnx daemon lockfile");
10979
+ }
10980
+ const tmp = `${lockfilePath}.${data.pid}.${(0, import_node_crypto.randomUUID)()}.tmp`;
10898
10981
  import_node_fs3.default.writeFileSync(tmp, `${JSON.stringify(data, null, 2)}
10899
10982
  `, "utf8");
10900
- import_node_fs3.default.renameSync(tmp, daemonLockfilePath());
10983
+ try {
10984
+ const current = readDaemonLockfile();
10985
+ if (!current || !sameDaemonOwner(current, data)) {
10986
+ throw new Error("rnx daemon lock ownership changed during heartbeat");
10987
+ }
10988
+ import_node_fs3.default.renameSync(tmp, lockfilePath);
10989
+ } finally {
10990
+ try {
10991
+ import_node_fs3.default.unlinkSync(tmp);
10992
+ } catch {
10993
+ }
10994
+ }
10901
10995
  }
10902
10996
  function writeDevBridgeLockfile(data) {
10903
10997
  import_node_fs3.default.mkdirSync(devBridgesDir(), { recursive: true });
@@ -10907,17 +11001,130 @@ function writeDevBridgeLockfile(data) {
10907
11001
  `, "utf8");
10908
11002
  import_node_fs3.default.renameSync(tmp, lockfilePath);
10909
11003
  }
11004
+ function sameDaemonOwner(left, right) {
11005
+ return left.pid === right.pid && left.startedAt === right.startedAt;
11006
+ }
11007
+ function isAlreadyExists(error) {
11008
+ return error instanceof Error && "code" in error && Reflect.get(error, "code") === "EEXIST";
11009
+ }
11010
+ function createDaemonLockfileExclusive(data) {
11011
+ const lockfilePath = daemonLockfilePath();
11012
+ const tmp = `${lockfilePath}.${data.pid}.${(0, import_node_crypto.randomUUID)()}.claim`;
11013
+ import_node_fs3.default.writeFileSync(tmp, `${JSON.stringify(data, null, 2)}
11014
+ `, "utf8");
11015
+ try {
11016
+ import_node_fs3.default.linkSync(tmp, lockfilePath);
11017
+ return true;
11018
+ } catch (error) {
11019
+ if (isAlreadyExists(error)) return false;
11020
+ throw error;
11021
+ } finally {
11022
+ try {
11023
+ import_node_fs3.default.unlinkSync(tmp);
11024
+ } catch {
11025
+ }
11026
+ }
11027
+ }
11028
+ function daemonClaimPath() {
11029
+ return `${daemonLockfilePath()}.claim`;
11030
+ }
11031
+ function parseDaemonClaim(raw) {
11032
+ try {
11033
+ const value = JSON.parse(raw);
11034
+ if (value !== null && typeof value === "object" && typeof Reflect.get(value, "pid") === "number" && typeof Reflect.get(value, "startedAt") === "number" && typeof Reflect.get(value, "createdAt") === "number") {
11035
+ return {
11036
+ pid: Reflect.get(value, "pid"),
11037
+ startedAt: Reflect.get(value, "startedAt"),
11038
+ createdAt: Reflect.get(value, "createdAt")
11039
+ };
11040
+ }
11041
+ } catch {
11042
+ }
11043
+ return null;
11044
+ }
11045
+ function readDaemonClaim() {
11046
+ try {
11047
+ return parseDaemonClaim(import_node_fs3.default.readFileSync(daemonClaimPath(), "utf8"));
11048
+ } catch {
11049
+ return null;
11050
+ }
11051
+ }
11052
+ function isDaemonClaimFresh(claim, now = Date.now()) {
11053
+ if (now - claim.createdAt > DAEMON_CLAIM_STALE_MS) return false;
11054
+ try {
11055
+ process.kill(claim.pid, 0);
11056
+ return true;
11057
+ } catch {
11058
+ return false;
11059
+ }
11060
+ }
11061
+ function acquireDaemonClaim(data) {
11062
+ const claim = {
11063
+ pid: data.pid,
11064
+ startedAt: data.startedAt,
11065
+ createdAt: Date.now()
11066
+ };
11067
+ const serialized = `${JSON.stringify(claim)}
11068
+ `;
11069
+ for (let attempt = 0; attempt < 8; attempt++) {
11070
+ try {
11071
+ import_node_fs3.default.writeFileSync(daemonClaimPath(), serialized, { encoding: "utf8", flag: "wx" });
11072
+ return true;
11073
+ } catch (error) {
11074
+ if (!isAlreadyExists(error)) throw error;
11075
+ }
11076
+ const existing = readDaemonClaim();
11077
+ if (existing && sameDaemonOwner(existing, claim)) return true;
11078
+ if (existing && isDaemonClaimFresh(existing)) return false;
11079
+ let before;
11080
+ try {
11081
+ before = import_node_fs3.default.readFileSync(daemonClaimPath(), "utf8");
11082
+ const latest = parseDaemonClaim(before);
11083
+ if (latest && isDaemonClaimFresh(latest)) return false;
11084
+ if (import_node_fs3.default.readFileSync(daemonClaimPath(), "utf8") !== before) continue;
11085
+ import_node_fs3.default.unlinkSync(daemonClaimPath());
11086
+ } catch {
11087
+ }
11088
+ }
11089
+ return false;
11090
+ }
11091
+ function releaseDaemonClaim(owner) {
11092
+ try {
11093
+ const claim = readDaemonClaim();
11094
+ if (!claim || !sameDaemonOwner(claim, owner)) return;
11095
+ import_node_fs3.default.unlinkSync(daemonClaimPath());
11096
+ } catch {
11097
+ }
11098
+ }
10910
11099
  function claimDaemonLockfile(data) {
10911
11100
  ensureSootsimHome();
10912
- const existing = readDaemonLockfile();
10913
- if (existing && isDaemonLockfileFresh(existing) && existing.pid !== data.pid) {
10914
- return false;
11101
+ if (!acquireDaemonClaim(data)) return false;
11102
+ try {
11103
+ const existing = readDaemonLockfile();
11104
+ if (existing && sameDaemonOwner(existing, data)) {
11105
+ writeDaemonLockfile(data);
11106
+ return true;
11107
+ }
11108
+ if (existing && isDaemonLockfileFresh(existing)) return false;
11109
+ if (existing) {
11110
+ const latest = readDaemonLockfile();
11111
+ if (!latest || !sameDaemonOwner(latest, existing)) return false;
11112
+ if (isDaemonLockfileFresh(latest)) return false;
11113
+ try {
11114
+ import_node_fs3.default.unlinkSync(daemonLockfilePath());
11115
+ } catch {
11116
+ return false;
11117
+ }
11118
+ }
11119
+ return createDaemonLockfileExclusive(data);
11120
+ } finally {
11121
+ releaseDaemonClaim(data);
10915
11122
  }
10916
- writeDaemonLockfile(data);
10917
- return true;
10918
11123
  }
10919
- function removeDaemonLockfile() {
11124
+ function removeDaemonLockfile(owner) {
10920
11125
  try {
11126
+ const lockfile = readDaemonLockfile();
11127
+ if (!lockfile || !sameDaemonOwner(lockfile, owner)) return;
10921
11128
  import_node_fs3.default.unlinkSync(daemonLockfilePath());
10922
11129
  } catch {
10923
11130
  }
@@ -10931,7 +11138,7 @@ function removeDevBridgeLockfile(owner) {
10931
11138
  } catch {
10932
11139
  }
10933
11140
  }
10934
- var import_node_crypto, import_node_fs3, import_node_os, import_node_path4, SOOTSIM_HOME_ENV, ACTIVE_RUNTIME_FILE, DAEMON_LOCKFILE, DEV_BRIDGES_DIR, CONFIG_FILE, DAEMON_HEARTBEAT_STALE_MS, DAEMON_LOCKFILE_MAX_BYTES;
11141
+ var import_node_crypto, import_node_fs3, import_node_os, import_node_path4, SOOTSIM_HOME_ENV, ACTIVE_RUNTIME_FILE, DAEMON_LOCKFILE, DEV_BRIDGES_DIR, CONFIG_FILE, DAEMON_HEARTBEAT_STALE_MS, DAEMON_LOCKFILE_MAX_BYTES, DAEMON_CLAIM_STALE_MS;
10935
11142
  var init_home_paths = __esm({
10936
11143
  "src/home-paths.ts"() {
10937
11144
  "use strict";
@@ -10946,6 +11153,7 @@ var init_home_paths = __esm({
10946
11153
  CONFIG_FILE = "config.json";
10947
11154
  DAEMON_HEARTBEAT_STALE_MS = 3e4;
10948
11155
  DAEMON_LOCKFILE_MAX_BYTES = 16 * 1024;
11156
+ DAEMON_CLAIM_STALE_MS = 3e4;
10949
11157
  }
10950
11158
  });
10951
11159
 
@@ -12870,20 +13078,6 @@ var init_agent_host = __esm({
12870
13078
  }
12871
13079
  });
12872
13080
 
12873
- // src/backend-origin.ts
12874
- function normalizeHostname(hostname) {
12875
- return hostname.replace(/^\[|\]$/g, "").toLowerCase();
12876
- }
12877
- function isLoopbackHost(hostname) {
12878
- const normalized = normalizeHostname(hostname);
12879
- return normalized === "localhost" || normalized.endsWith(".localhost") || normalized === "0.0.0.0" || normalized === "::1" || /^127(?:\.\d{1,3}){3}$/.test(normalized);
12880
- }
12881
- var init_backend_origin = __esm({
12882
- "src/backend-origin.ts"() {
12883
- "use strict";
12884
- }
12885
- });
12886
-
12887
13081
  // src/host/fetch-proxy-overrides.ts
12888
13082
  function headerOverridesFor(hostname) {
12889
13083
  const host = hostname.toLowerCase();
@@ -13367,9 +13561,18 @@ export const Appearance = rn.Appearance
13367
13561
  `;
13368
13562
  }
13369
13563
  function sendJavaScript(req, res, source) {
13564
+ const origin = req.headers.origin;
13565
+ let allowedOrigin;
13566
+ if (origin) {
13567
+ try {
13568
+ if (isLoopbackHost(new URL(origin).hostname)) allowedOrigin = origin;
13569
+ } catch {
13570
+ }
13571
+ }
13370
13572
  res.writeHead(200, {
13371
13573
  "Content-Type": "application/javascript; charset=utf-8",
13372
- "Cache-Control": "no-cache"
13574
+ "Cache-Control": "no-cache",
13575
+ ...allowedOrigin ? { "Access-Control-Allow-Origin": allowedOrigin, Vary: "Origin" } : {}
13373
13576
  });
13374
13577
  res.end(req.method === "HEAD" ? void 0 : source);
13375
13578
  }
@@ -13390,6 +13593,23 @@ async function handleReplacementModuleRequest(req, res, options = {}) {
13390
13593
  } catch {
13391
13594
  return false;
13392
13595
  }
13596
+ if (options.requireLoopbackOrigin) {
13597
+ const origin = req.headers.origin;
13598
+ const remoteAddress = req.socket.remoteAddress?.replace(/^::ffff:/, "");
13599
+ let allowed = Boolean(remoteAddress && isLoopbackHost(remoteAddress));
13600
+ if (allowed && origin) {
13601
+ try {
13602
+ allowed = isLoopbackHost(new URL(origin).hostname);
13603
+ } catch {
13604
+ allowed = false;
13605
+ }
13606
+ }
13607
+ if (!allowed) {
13608
+ res.writeHead(403, { "Content-Type": "text/plain; charset=utf-8" });
13609
+ res.end("replacement modules are available only to loopback origins");
13610
+ return true;
13611
+ }
13612
+ }
13393
13613
  if (url.pathname === `${REPLACEMENT_RUNTIME_PREFIX}/react`) {
13394
13614
  sendJavaScript(req, res, reactRuntimeSource(options.reactBridgeUrl));
13395
13615
  return true;
@@ -13399,6 +13619,11 @@ async function handleReplacementModuleRequest(req, res, options = {}) {
13399
13619
  return true;
13400
13620
  }
13401
13621
  if (url.pathname !== REPLACEMENT_MODULE_ROUTE) return false;
13622
+ if (options.requestToken && url.searchParams.get("token") !== options.requestToken) {
13623
+ res.writeHead(403, { "Content-Type": "text/plain; charset=utf-8" });
13624
+ res.end("replacement module request token is invalid");
13625
+ return true;
13626
+ }
13402
13627
  const targetPath = url.searchParams.get("path");
13403
13628
  if (!targetPath) {
13404
13629
  res.writeHead(400, { "Content-Type": "text/plain; charset=utf-8" });
@@ -13411,17 +13636,42 @@ async function handleReplacementModuleRequest(req, res, options = {}) {
13411
13636
  res.end("replacement files must be .js, .jsx, .ts, or .tsx");
13412
13637
  return true;
13413
13638
  }
13639
+ if (!import_node_path10.default.isAbsolute(targetPath)) {
13640
+ res.writeHead(400, { "Content-Type": "text/plain; charset=utf-8" });
13641
+ res.end("replacement file path must be absolute");
13642
+ return true;
13643
+ }
13644
+ let resolvedProjectRoot;
13645
+ if (options.projectRoot) {
13646
+ const lexicalProjectRoot = import_node_path10.default.resolve(options.projectRoot);
13647
+ resolvedProjectRoot = await import_promises5.default.realpath(options.projectRoot);
13648
+ const lexicalTarget = import_node_path10.default.relative(lexicalProjectRoot, targetPath);
13649
+ if (lexicalTarget.startsWith("..") || import_node_path10.default.isAbsolute(lexicalTarget)) {
13650
+ res.writeHead(403, { "Content-Type": "text/plain; charset=utf-8" });
13651
+ res.end("replacement file must be inside the project root");
13652
+ return true;
13653
+ }
13654
+ }
13655
+ let resolvedTargetPath;
13414
13656
  try {
13415
- await import_promises5.default.access(targetPath);
13657
+ resolvedTargetPath = await import_promises5.default.realpath(targetPath);
13416
13658
  } catch (error) {
13417
13659
  const message = error instanceof Error ? error.message : String(error);
13418
13660
  res.writeHead(404, { "Content-Type": "text/plain; charset=utf-8" });
13419
13661
  res.end(`replacement file not found: ${message}`);
13420
13662
  return true;
13421
13663
  }
13664
+ if (resolvedProjectRoot) {
13665
+ const relativeTarget = import_node_path10.default.relative(resolvedProjectRoot, resolvedTargetPath);
13666
+ if (relativeTarget.startsWith("..") || import_node_path10.default.isAbsolute(relativeTarget)) {
13667
+ res.writeHead(403, { "Content-Type": "text/plain; charset=utf-8" });
13668
+ res.end("replacement file must be inside the project root");
13669
+ return true;
13670
+ }
13671
+ }
13422
13672
  try {
13423
13673
  const result = await (0, import_esbuild.build)({
13424
- entryPoints: [targetPath],
13674
+ entryPoints: [resolvedTargetPath],
13425
13675
  bundle: true,
13426
13676
  write: false,
13427
13677
  format: "esm",
@@ -13460,6 +13710,7 @@ var init_replacement_module_handler = __esm({
13460
13710
  import_promises5 = __toESM(require("node:fs/promises"), 1);
13461
13711
  import_node_path10 = __toESM(require("node:path"), 1);
13462
13712
  import_esbuild = require("esbuild");
13713
+ init_backend_origin();
13463
13714
  REPLACEMENT_MODULE_ROUTE = "/__sootsim-replacement-module";
13464
13715
  REPLACEMENT_RUNTIME_PREFIX = "/__sootsim-replacement-runtime";
13465
13716
  }
@@ -14726,7 +14977,7 @@ var init_bridge_host = __esm({
14726
14977
  removeLockfile() {
14727
14978
  if (!this.shouldWriteLockfile) return;
14728
14979
  try {
14729
- removeDaemonLockfile();
14980
+ removeDaemonLockfile({ pid: process.pid, startedAt: this.startedAt });
14730
14981
  } catch {
14731
14982
  }
14732
14983
  }
@@ -15285,7 +15536,7 @@ var init_bridge_host = __esm({
15285
15536
  }
15286
15537
  if (this.shouldWriteLockfile) {
15287
15538
  try {
15288
- removeDaemonLockfile();
15539
+ removeDaemonLockfile({ pid: process.pid, startedAt: this.startedAt });
15289
15540
  } catch {
15290
15541
  }
15291
15542
  }
@@ -16487,11 +16738,11 @@ async function evalInBridge(bridge, code, opts = {}) {
16487
16738
  if (opts.acquireLock) payload.acquireLock = true;
16488
16739
  return bridge.send(payload);
16489
16740
  }
16490
- async function callInBridge(bridge, path19, ...args) {
16491
- return bridge.send({ type: "call", path: path19, args });
16741
+ async function callInBridge(bridge, path18, ...args) {
16742
+ return bridge.send({ type: "call", path: path18, args });
16492
16743
  }
16493
- async function callInBridgeWrite(bridge, path19, ...args) {
16494
- return bridge.send({ type: "call", path: path19, args, acquireLock: true });
16744
+ async function callInBridgeWrite(bridge, path18, ...args) {
16745
+ return bridge.send({ type: "call", path: path18, args, acquireLock: true });
16495
16746
  }
16496
16747
  var import_node_fs9, import_node_path11, import_ws3, resolvedWorld, BridgeSimLockedError;
16497
16748
  var init_ws_bridge = __esm({
@@ -16553,8 +16804,8 @@ function getDefaultPortForProtocol(protocol) {
16553
16804
  return protocol === "https:" ? 443 : 80;
16554
16805
  }
16555
16806
  function isBaseServerUrl(url) {
16556
- const path19 = url.pathname || "/";
16557
- return (path19 === "/" || path19 === "") && !url.search && !url.hash;
16807
+ const path18 = url.pathname || "/";
16808
+ return (path18 === "/" || path18 === "") && !url.search && !url.hash;
16558
16809
  }
16559
16810
  async function resolvePortViaServerScan(port) {
16560
16811
  try {
@@ -16570,8 +16821,8 @@ async function resolvePortViaServerScan(port) {
16570
16821
  if (!match) return null;
16571
16822
  return {
16572
16823
  // normalize exactly like the manifest-probe branch below — the scan
16573
- // returns metro's raw launchAsset url (lazy=true for code-split one/vxrn
16574
- // apps), but sootsim loads ONE self-contained bundle into the tenant
16824
+ // returns metro's manifest launchAsset url (lazy=true for code-split
16825
+ // one/vxrn apps), but sootsim loads ONE self-contained bundle into the tenant
16575
16826
  // worker, so a deferred React.lazy chunk throws "Requiring unknown module
16576
16827
  // <id>". normalizeNativeDevBundleUrl forces lazy=false + strips bytecode.
16577
16828
  bundleUrl: normalizeNativeDevBundleUrl(match.bundleUrl),
@@ -16583,15 +16834,8 @@ async function resolvePortViaServerScan(port) {
16583
16834
  return null;
16584
16835
  }
16585
16836
  }
16586
- async function probeBaseUrlBundle(baseUrl, port) {
16837
+ async function probeBaseUrlBundleDirect(baseUrl, port) {
16587
16838
  const normalizedBaseUrl = baseUrl.replace(/\/+$/, "");
16588
- try {
16589
- if (isLoopbackHost(new URL(normalizedBaseUrl).hostname)) {
16590
- const viaScan = await resolvePortViaServerScan(port);
16591
- if (viaScan) return viaScan;
16592
- }
16593
- } catch {
16594
- }
16595
16839
  try {
16596
16840
  const manifestRes = await fetchDevProbe(`${normalizedBaseUrl}/`, {
16597
16841
  headers: { "expo-platform": "ios" }
@@ -16599,7 +16843,8 @@ async function probeBaseUrlBundle(baseUrl, port) {
16599
16843
  if (manifestRes.ok) {
16600
16844
  const manifest = await manifestRes.json();
16601
16845
  const client = manifest?.extra?.expoClient || manifest?.extra || {};
16602
- const launchUrl2 = typeof manifest?.launchAsset?.url === "string" ? manifest.launchAsset.url : void 0;
16846
+ const rawLaunchUrl = typeof manifest?.launchAsset?.url === "string" ? manifest.launchAsset.url : void 0;
16847
+ const launchUrl2 = rawLaunchUrl ? alignLoopbackBundleUrlWithProbe(rawLaunchUrl, normalizedBaseUrl) : void 0;
16603
16848
  if (launchUrl2 || client.name) {
16604
16849
  return {
16605
16850
  bundleUrl: normalizeNativeDevBundleUrl(
@@ -16629,8 +16874,25 @@ async function probeBaseUrlBundle(baseUrl, port) {
16629
16874
  }
16630
16875
  return null;
16631
16876
  }
16877
+ async function probeBaseUrlBundle(baseUrl, port) {
16878
+ try {
16879
+ if (isLoopbackHost(new URL(baseUrl).hostname)) {
16880
+ const viaScan = await resolvePortViaServerScan(port);
16881
+ if (viaScan) return viaScan;
16882
+ }
16883
+ } catch {
16884
+ }
16885
+ return probeBaseUrlBundleDirect(baseUrl, port);
16886
+ }
16632
16887
  async function probePortBundle(port) {
16633
- return probeBaseUrlBundle(buildBaseUrl("http:", "localhost", port), port);
16888
+ const viaScan = await resolvePortViaServerScan(port);
16889
+ if (viaScan) return viaScan;
16890
+ const candidates = await Promise.all(
16891
+ LOOPBACK_PROBE_HOSTNAMES.map(
16892
+ (hostname) => probeBaseUrlBundleDirect(buildLoopbackProbeBaseUrl("http:", hostname, port), port)
16893
+ )
16894
+ );
16895
+ return candidates.find((candidate) => candidate !== null) ?? null;
16634
16896
  }
16635
16897
  async function resolveConnectionInput(input) {
16636
16898
  const trimmed = input.trim();
@@ -16788,150 +17050,20 @@ function findExpoAppConfig(dir) {
16788
17050
  function findSootSimAppConfig(dir) {
16789
17051
  return firstNamedFile(dir, SOOTSIM_APP_CONFIG_NAMES);
16790
17052
  }
16791
- function readWorkspacePatterns(rootDir) {
17053
+ function resolveAppProject(projectRoot) {
17054
+ const appDir = import_node_path12.default.resolve(projectRoot);
16792
17055
  try {
16793
- const value = JSON.parse(import_node_fs10.default.readFileSync(import_node_path12.default.join(rootDir, "package.json"), "utf8"));
16794
- if (!value || typeof value !== "object") return [];
16795
- const workspaces = Reflect.get(value, "workspaces");
16796
- if (Array.isArray(workspaces)) {
16797
- return workspaces.filter((entry) => typeof entry === "string");
16798
- }
16799
- if (!workspaces || typeof workspaces !== "object") return [];
16800
- const packages = Reflect.get(workspaces, "packages");
16801
- return Array.isArray(packages) ? packages.filter((entry) => typeof entry === "string") : [];
17056
+ if (!import_node_fs10.default.statSync(appDir).isDirectory()) return null;
16802
17057
  } catch {
16803
- return [];
16804
- }
16805
- }
16806
- function workspaceDirectories(rootDir) {
16807
- const directories = /* @__PURE__ */ new Set();
16808
- let expanded = 0;
16809
- let complete = true;
16810
- for (const pattern of readWorkspacePatterns(rootDir)) {
16811
- if (expanded >= MAX_SCANNED_DIRECTORIES) {
16812
- complete = false;
16813
- break;
16814
- }
16815
- if (!pattern || import_node_path12.default.isAbsolute(pattern)) continue;
16816
- const segments = pattern.split(/[\\/]+/).filter(Boolean);
16817
- if (segments.length === 0 || segments.length > MAX_SCAN_DEPTH) continue;
16818
- if (segments.some((segment) => segment === ".." || segment === "**")) continue;
16819
- let current = [rootDir];
16820
- for (const segment of segments) {
16821
- const next = [];
16822
- for (const parent of current) {
16823
- if (expanded >= MAX_SCANNED_DIRECTORIES) {
16824
- complete = false;
16825
- break;
16826
- }
16827
- expanded++;
16828
- if (segment === "*") {
16829
- let entries;
16830
- try {
16831
- entries = import_node_fs10.default.readdirSync(parent, { withFileTypes: true });
16832
- } catch {
16833
- continue;
16834
- }
16835
- for (const entry of entries) {
16836
- if (!entry.isDirectory() || entry.isSymbolicLink()) continue;
16837
- if (entry.name.startsWith(".") || SKIP_DIR_NAMES.has(entry.name)) continue;
16838
- next.push(import_node_path12.default.join(parent, entry.name));
16839
- }
16840
- } else if (!segment.includes("*")) {
16841
- const candidate = import_node_path12.default.join(parent, segment);
16842
- try {
16843
- const stat2 = import_node_fs10.default.lstatSync(candidate);
16844
- if (stat2.isDirectory() && !stat2.isSymbolicLink()) next.push(candidate);
16845
- } catch {
16846
- }
16847
- }
16848
- }
16849
- current = next;
16850
- if (current.length > MAX_SCANNED_DIRECTORIES) {
16851
- current.length = MAX_SCANNED_DIRECTORIES;
16852
- complete = false;
16853
- }
16854
- }
16855
- for (const directory of current) directories.add(directory);
16856
- }
16857
- return { directories: [...directories], complete };
16858
- }
16859
- function boundedConfigDirectories(rootDir) {
16860
- const queue = [{ dir: rootDir, depth: 0 }];
16861
- const matches = [];
16862
- let visited = 0;
16863
- let nextIndex = 0;
16864
- let complete = true;
16865
- while (nextIndex < queue.length && visited < MAX_SCANNED_DIRECTORIES) {
16866
- const current = queue[nextIndex++];
16867
- if (!current) break;
16868
- visited++;
16869
- if (current.depth > 0 && findExpoAppConfig(current.dir)) {
16870
- matches.push(current.dir);
16871
- if (matches.length > 1) return { directories: matches, complete: true };
16872
- }
16873
- if (current.depth >= MAX_SCAN_DEPTH) continue;
16874
- let entries;
16875
- try {
16876
- entries = import_node_fs10.default.readdirSync(current.dir, { withFileTypes: true });
16877
- } catch {
16878
- continue;
16879
- }
16880
- for (const entry of entries) {
16881
- if (!entry.isDirectory() || entry.isSymbolicLink()) continue;
16882
- if (entry.name.startsWith(".") || SKIP_DIR_NAMES.has(entry.name)) continue;
16883
- if (queue.length >= MAX_SCANNED_DIRECTORIES) {
16884
- complete = false;
16885
- break;
16886
- }
16887
- queue.push({ dir: import_node_path12.default.join(current.dir, entry.name), depth: current.depth + 1 });
16888
- }
16889
- }
16890
- return { directories: matches, complete: complete && nextIndex === queue.length };
16891
- }
16892
- function resolveAppProject(startDir, opts = {}) {
16893
- const rootDir = import_node_path12.default.resolve(startDir);
16894
- const rootConfigPath = findSootSimAppConfig(rootDir);
16895
- if (findExpoAppConfig(rootDir) || opts.trustStart && import_node_fs10.default.existsSync(rootDir)) {
16896
- let configPath = rootConfigPath;
16897
- if (!configPath && opts.trustStart) {
16898
- let parent = import_node_path12.default.dirname(rootDir);
16899
- for (let depth = 0; depth < MAX_SCAN_DEPTH && parent !== import_node_path12.default.dirname(parent); depth++) {
16900
- configPath = findSootSimAppConfig(parent);
16901
- if (configPath) break;
16902
- parent = import_node_path12.default.dirname(parent);
16903
- }
16904
- }
16905
- return { appDir: rootDir, configPath };
16906
- }
16907
- const workspaceScan = workspaceDirectories(rootDir);
16908
- const workspaceApps = workspaceScan.directories.filter(
16909
- (directory) => findExpoAppConfig(directory)
16910
- );
16911
- if (workspaceApps.length > 1) return null;
16912
- if (!workspaceScan.complete) return null;
16913
- if (workspaceApps.length === 1) {
16914
- const workspaceAppDir = workspaceApps[0];
16915
- return {
16916
- appDir: workspaceAppDir,
16917
- configPath: rootConfigPath ?? findSootSimAppConfig(workspaceAppDir)
16918
- };
17058
+ return null;
16919
17059
  }
16920
- const fallbackScan = boundedConfigDirectories(rootDir);
16921
- const scannedApps = fallbackScan.directories;
16922
- if (scannedApps.length > 1) return null;
16923
- if (!fallbackScan.complete) return null;
16924
- if (scannedApps.length === 1) {
16925
- const scannedAppDir = scannedApps[0];
16926
- return {
16927
- appDir: scannedAppDir,
16928
- configPath: rootConfigPath ?? findSootSimAppConfig(scannedAppDir)
16929
- };
17060
+ const configPath = findSootSimAppConfig(appDir);
17061
+ if (!configPath && !findExpoAppConfig(appDir) && !import_node_fs10.default.existsSync(import_node_path12.default.join(appDir, "package.json"))) {
17062
+ return null;
16930
17063
  }
16931
- if (rootConfigPath) return { appDir: rootDir, configPath: rootConfigPath };
16932
- return null;
17064
+ return { appDir, configPath };
16933
17065
  }
16934
- var import_node_fs10, import_node_path12, EXPO_APP_CONFIG_NAMES, SOOTSIM_APP_CONFIG_NAMES, SKIP_DIR_NAMES, MAX_SCANNED_DIRECTORIES, MAX_SCAN_DEPTH;
17066
+ var import_node_fs10, import_node_path12, EXPO_APP_CONFIG_NAMES, SOOTSIM_APP_CONFIG_NAMES;
16935
17067
  var init_app_project = __esm({
16936
17068
  "cli/app-project.ts"() {
16937
17069
  "use strict";
@@ -16950,28 +17082,6 @@ var init_app_project = __esm({
16950
17082
  "sootsim.config.mjs",
16951
17083
  "sootsim.config.cjs"
16952
17084
  ];
16953
- SKIP_DIR_NAMES = /* @__PURE__ */ new Set([
16954
- "node_modules",
16955
- "artifacts",
16956
- "dist",
16957
- "build",
16958
- "out",
16959
- "coverage",
16960
- "tmp",
16961
- "public",
16962
- "ios",
16963
- "android",
16964
- "macos",
16965
- "windows",
16966
- "__fixtures__",
16967
- "__tests__",
16968
- "fixtures",
16969
- "fixture",
16970
- "examples",
16971
- "example"
16972
- ]);
16973
- MAX_SCANNED_DIRECTORIES = 256;
16974
- MAX_SCAN_DEPTH = 4;
16975
17085
  }
16976
17086
  });
16977
17087
 
@@ -17049,28 +17159,18 @@ function localOriginOf(value) {
17049
17159
  }
17050
17160
  return null;
17051
17161
  }
17052
- function hasPackageJson(dir) {
17053
- try {
17054
- return import_node_fs11.default.statSync(import_node_path14.default.join(dir, "package.json")).isFile();
17055
- } catch {
17056
- return false;
17057
- }
17058
- }
17059
17162
  function discoverNativeLinkedAppFonts(opts) {
17060
17163
  const metroOrigin = localOriginOf(opts.bundleUrl);
17061
17164
  if (!metroOrigin) return [];
17062
- const explicit = opts.repoDir || process.env.CONTRAST_REPO_DIR;
17063
- const startDir = explicit ? import_node_path14.default.resolve(explicit) : process.cwd();
17064
- if (!explicit && !hasPackageJson(startDir)) return [];
17065
- const project = resolveAppProject(startDir, { trustStart: opts.exactProjectDir });
17165
+ const project = resolveAppProject(opts.projectDir);
17066
17166
  const selectedAppDir = project?.appDir ?? null;
17067
17167
  if (!selectedAppDir) return [];
17068
17168
  const platform = (opts.platform || process.env.CONTRAST_PLATFORM || "ios").toLowerCase();
17069
17169
  let getConfig = null;
17070
- if (explicit || project) {
17170
+ if (project) {
17071
17171
  try {
17072
17172
  const modulePath = require3.resolve("@expo/config", {
17073
- paths: [selectedAppDir, startDir]
17173
+ paths: [selectedAppDir]
17074
17174
  });
17075
17175
  const mod = require3(modulePath);
17076
17176
  if (typeof mod.getConfig === "function") {
@@ -17289,27 +17389,17 @@ function localOriginOf2(value) {
17289
17389
  }
17290
17390
  return null;
17291
17391
  }
17292
- function hasPackageJson2(dir) {
17293
- try {
17294
- return import_node_fs12.default.statSync(import_node_path15.default.join(dir, "package.json")).isFile();
17295
- } catch {
17296
- return false;
17297
- }
17298
- }
17299
17392
  function discoverConfigPluginAppSplash(opts) {
17300
17393
  const metroOrigin = localOriginOf2(opts.bundleUrl);
17301
17394
  if (!metroOrigin) return null;
17302
- const explicit = opts.repoDir || process.env.CONTRAST_REPO_DIR;
17303
- const startDir = explicit ? import_node_path15.default.resolve(explicit) : process.cwd();
17304
- if (!explicit && !hasPackageJson2(startDir)) return null;
17305
- const project = resolveAppProject(startDir, { trustStart: opts.exactProjectDir });
17395
+ const project = resolveAppProject(opts.projectDir);
17306
17396
  const selectedAppDir = project?.appDir ?? null;
17307
17397
  if (!selectedAppDir) return null;
17308
17398
  const platform = (opts.platform || process.env.CONTRAST_PLATFORM || "ios").toLowerCase();
17309
17399
  let getConfig = null;
17310
17400
  try {
17311
17401
  const modulePath = require4.resolve("@expo/config", {
17312
- paths: [selectedAppDir, startDir]
17402
+ paths: [selectedAppDir]
17313
17403
  });
17314
17404
  const mod = require4(modulePath);
17315
17405
  if (typeof mod.getConfig === "function") {
@@ -17334,13 +17424,11 @@ function discoverConfigPluginAppSplash(opts) {
17334
17424
  function encodeAppSplashWire(spec) {
17335
17425
  return encodeURIComponent(JSON.stringify(spec));
17336
17426
  }
17337
- var import_node_fs12, import_node_module3, import_node_path15, import_config_plugin_splash, require4;
17427
+ var import_node_module3, import_config_plugin_splash, require4;
17338
17428
  var init_app_splash = __esm({
17339
17429
  "cli/app-splash.ts"() {
17340
17430
  "use strict";
17341
- import_node_fs12 = __toESM(require("node:fs"), 1);
17342
17431
  import_node_module3 = require("node:module");
17343
- import_node_path15 = __toESM(require("node:path"), 1);
17344
17432
  import_config_plugin_splash = require("sootsim-engine/engine/config-plugin-splash");
17345
17433
  init_app_project();
17346
17434
  require4 = (0, import_node_module3.createRequire)(__sootsim_import_meta_url);
@@ -18358,7 +18446,7 @@ function resolvePlaywright() {
18358
18446
  try {
18359
18447
  const modulePath = requireFromRoot.resolve(spec);
18360
18448
  const packageJsonPath = requireFromRoot.resolve(`${spec}/package.json`);
18361
- const parsed = JSON.parse((0, import_node_fs13.readFileSync)(packageJsonPath, "utf8"));
18449
+ const parsed = JSON.parse((0, import_node_fs12.readFileSync)(packageJsonPath, "utf8"));
18362
18450
  if (!isRecord3(parsed) || typeof parsed.version !== "string") continue;
18363
18451
  const bin = parsed.bin;
18364
18452
  let binPath = null;
@@ -18370,8 +18458,8 @@ function resolvePlaywright() {
18370
18458
  binPath = typeof named === "string" ? named : typeof first === "string" ? first : null;
18371
18459
  }
18372
18460
  if (!binPath) continue;
18373
- const cliPath = (0, import_node_path16.join)((0, import_node_path16.dirname)(packageJsonPath), binPath);
18374
- if (!(0, import_node_fs13.existsSync)(cliPath)) continue;
18461
+ const cliPath = (0, import_node_path15.join)((0, import_node_path15.dirname)(packageJsonPath), binPath);
18462
+ if (!(0, import_node_fs12.existsSync)(cliPath)) continue;
18375
18463
  return {
18376
18464
  spec,
18377
18465
  modulePath,
@@ -18410,7 +18498,7 @@ function probeBrowser(resolved, env) {
18410
18498
  return {
18411
18499
  ok: true,
18412
18500
  executablePath: parsed.executablePath,
18413
- exists: parsed.exists && (0, import_node_fs13.existsSync)(parsed.executablePath)
18501
+ exists: parsed.exists && (0, import_node_fs12.existsSync)(parsed.executablePath)
18414
18502
  };
18415
18503
  } catch (error) {
18416
18504
  return {
@@ -18449,14 +18537,14 @@ ${detail}` : ""}`
18449
18537
  }
18450
18538
  return { ok: true, executablePath: after.executablePath, installed: true };
18451
18539
  }
18452
- var import_node_child_process5, import_node_fs13, import_node_module4, import_node_path16, PLAYWRIGHT_BROWSER_PROBE;
18540
+ var import_node_child_process5, import_node_fs12, import_node_module4, import_node_path15, PLAYWRIGHT_BROWSER_PROBE;
18453
18541
  var init_playwright_provisioning = __esm({
18454
18542
  "cli/drivers/playwright-provisioning.ts"() {
18455
18543
  "use strict";
18456
18544
  import_node_child_process5 = require("node:child_process");
18457
- import_node_fs13 = require("node:fs");
18545
+ import_node_fs12 = require("node:fs");
18458
18546
  import_node_module4 = require("node:module");
18459
- import_node_path16 = require("node:path");
18547
+ import_node_path15 = require("node:path");
18460
18548
  PLAYWRIGHT_BROWSER_PROBE = `
18461
18549
  const { existsSync } = require('node:fs');
18462
18550
  const modulePath = process.env.SOOTSIM_PW_MODULE;
@@ -19142,11 +19230,11 @@ function isChangelogEntry(value) {
19142
19230
  )
19143
19231
  );
19144
19232
  }
19145
- async function fetchRuntimeReleaseNotes(version, request = fetch) {
19233
+ async function fetchRuntimeReleaseNotes(version, fetchImpl = fetch) {
19146
19234
  try {
19147
19235
  const url = new URL(SOOTSIM_CHANGELOG_API_URL);
19148
19236
  url.searchParams.set("version", version);
19149
- const response = await request(url, {
19237
+ const response = await fetchImpl(url, {
19150
19238
  headers: { accept: "application/json" },
19151
19239
  signal: AbortSignal.timeout(5e3)
19152
19240
  });
@@ -19551,9 +19639,12 @@ function shouldOpenAsDeepLink(target) {
19551
19639
  return !/^https?:\/\//i.test(trimmed);
19552
19640
  }
19553
19641
  async function ensureBridgeForOpen(preferredPort, explicitPort = false) {
19554
- if (await canConnectToBridge(preferredPort, 250)) return preferredPort;
19555
19642
  const lock = readDaemonLockfile();
19556
19643
  assertExplicitBridgeLock(preferredPort, explicitPort, lock);
19644
+ const preferredWorld = worldForBridgePort(preferredPort);
19645
+ if ((explicitPort || preferredWorld !== null) && await canConnectToBridge(preferredPort, 250)) {
19646
+ return preferredPort;
19647
+ }
19557
19648
  if (lock && isDaemonLockfileFresh(lock) && await canConnectToBridge(lock.bridgePort, 500)) {
19558
19649
  return lock.bridgePort;
19559
19650
  }
@@ -19579,12 +19670,6 @@ async function ensureBridgeForOpen(preferredPort, explicitPort = false) {
19579
19670
  );
19580
19671
  return nextLock.bridgePort;
19581
19672
  }
19582
- if (await canConnectToBridge(preferredPort, 250)) {
19583
- console.error(
19584
- ` started local bridge on port ${preferredPort}; run \`sootsim setup\` to install the background daemon`
19585
- );
19586
- return preferredPort;
19587
- }
19588
19673
  if (childExit) {
19589
19674
  throw new Error(`rnx bridge exited before becoming ready (exit ${childExit})`);
19590
19675
  }
@@ -19604,14 +19689,14 @@ function assertExplicitBridgeLock(preferredPort, explicitPort, lock) {
19604
19689
  }
19605
19690
  async function ensureRuntimeForOpen() {
19606
19691
  const active = readActiveRuntime();
19607
- if (active && import_node_fs14.default.existsSync(runtimeDir(active))) return;
19692
+ if (active && import_node_fs13.default.existsSync(runtimeDir(active))) return;
19608
19693
  console.error(" installing rnx engine runtime...");
19609
19694
  const { runRuntime: runRuntime2 } = await Promise.resolve().then(() => (init_runtime(), runtime_exports));
19610
19695
  await runRuntime2(["install"], {});
19611
19696
  }
19612
19697
  function spawnSootsim(args) {
19613
19698
  const entry = process.argv[1];
19614
- if (entry && import_node_fs14.default.existsSync(entry)) {
19699
+ if (entry && import_node_fs13.default.existsSync(entry)) {
19615
19700
  return (0, import_node_child_process6.spawn)(process.execPath, [entry, ...args], {
19616
19701
  detached: true,
19617
19702
  stdio: "ignore",
@@ -19758,10 +19843,10 @@ async function buildResolvedBundleOpenUrl(target, baseUrl, opts = {}) {
19758
19843
  }
19759
19844
  function normalizeReplacementPath(filePath) {
19760
19845
  if (filePath.startsWith("~/")) {
19761
- return import_node_path17.default.join(import_node_os3.default.homedir(), filePath.slice(2));
19846
+ return import_node_path16.default.join(import_node_os3.default.homedir(), filePath.slice(2));
19762
19847
  }
19763
- if (import_node_path17.default.isAbsolute(filePath)) return filePath;
19764
- return import_node_path17.default.resolve(process.cwd(), filePath);
19848
+ if (import_node_path16.default.isAbsolute(filePath)) return filePath;
19849
+ return import_node_path16.default.resolve(process.cwd(), filePath);
19765
19850
  }
19766
19851
  function parseOpenRuntimeConfig(args) {
19767
19852
  const modules = {};
@@ -19780,7 +19865,7 @@ function parseOpenRuntimeConfig(args) {
19780
19865
  }
19781
19866
  const moduleName = raw.slice(0, eqIndex).trim();
19782
19867
  const filePath = normalizeReplacementPath(raw.slice(eqIndex + 1).trim());
19783
- if (!import_node_fs14.default.existsSync(filePath)) {
19868
+ if (!import_node_fs13.default.existsSync(filePath)) {
19784
19869
  console.error(` rnx open: replacement file not found: ${filePath}`);
19785
19870
  process.exit(1);
19786
19871
  }
@@ -19806,16 +19891,12 @@ function parseOpenRuntimeConfig(args) {
19806
19891
  return config.modules || config.network ? config : void 0;
19807
19892
  }
19808
19893
  async function resolveOpenAppProject(resolvedBundle) {
19809
- if (resolvedBundle) {
19810
- const advertisedRoot = await resolveMetroProjectRoot(resolvedBundle.bundleUrl);
19811
- if (advertisedRoot) {
19812
- return resolveAppProject(advertisedRoot, { trustStart: true });
19813
- }
19814
- }
19815
- return resolveAppProject(process.env.CONTRAST_REPO_DIR || process.cwd());
19894
+ if (!resolvedBundle) return null;
19895
+ const advertisedRoot = await resolveMetroProjectRoot(resolvedBundle.bundleUrl);
19896
+ return advertisedRoot ? resolveAppProject(advertisedRoot) : null;
19816
19897
  }
19817
19898
  async function ensureConfiguredRuntime(version) {
19818
- if (import_node_fs14.default.existsSync(import_node_path17.default.join(runtimeDir(version), "index.html"))) return;
19899
+ if (import_node_fs13.default.existsSync(import_node_path16.default.join(runtimeDir(version), "index.html"))) return;
19819
19900
  console.error(` installing configured engine runtime ${version}...`);
19820
19901
  const result = await sootsimRuntime.install({
19821
19902
  version,
@@ -19847,12 +19928,10 @@ async function stampAppFonts(urlStr, project) {
19847
19928
  try {
19848
19929
  const url = new URL(urlStr);
19849
19930
  const bundleUrl = url.searchParams.get("bundle") || "";
19850
- if (!bundleUrl) return urlStr;
19851
- const repoDir = project?.appDir ?? await resolveMetroProjectRoot(bundleUrl) ?? void 0;
19931
+ if (!bundleUrl || !project) return urlStr;
19852
19932
  const specs = discoverNativeLinkedAppFonts({
19853
19933
  bundleUrl,
19854
- repoDir,
19855
- exactProjectDir: !!project || !!repoDir
19934
+ projectDir: project.appDir
19856
19935
  });
19857
19936
  if (specs.length === 0) return urlStr;
19858
19937
  const reachable = await filterReachableAppFonts(specs);
@@ -19914,12 +19993,10 @@ async function stampAppSplash(urlStr, project) {
19914
19993
  try {
19915
19994
  const url = new URL(urlStr);
19916
19995
  const bundleUrl = url.searchParams.get("bundle") || "";
19917
- if (!bundleUrl) return urlStr;
19918
- const repoDir = project?.appDir ?? await resolveMetroProjectRoot(bundleUrl) ?? void 0;
19996
+ if (!bundleUrl || !project) return urlStr;
19919
19997
  const spec = discoverConfigPluginAppSplash({
19920
19998
  bundleUrl,
19921
- repoDir,
19922
- exactProjectDir: !!project || !!repoDir
19999
+ projectDir: project.appDir
19923
20000
  });
19924
20001
  if (!spec) return urlStr;
19925
20002
  if (spec.imageUrl) {
@@ -20179,7 +20256,7 @@ function playwrightHostPidForSim(sim) {
20179
20256
  function signalDriverLaunchConnected(result) {
20180
20257
  if (!result.connectAckFile) return false;
20181
20258
  try {
20182
- import_node_fs14.default.writeFileSync(
20259
+ import_node_fs13.default.writeFileSync(
20183
20260
  result.connectAckFile,
20184
20261
  `${JSON.stringify({ connectedAt: Date.now(), pid: result.pid ?? null })}
20185
20262
  `,
@@ -20193,19 +20270,19 @@ function signalDriverLaunchConnected(result) {
20193
20270
  function printDriverDiagnosticLogTail(result) {
20194
20271
  if (!result.diagnosticLogPath) return;
20195
20272
  try {
20196
- const detail = import_node_fs14.default.readFileSync(result.diagnosticLogPath, "utf8").trim();
20273
+ const detail = import_node_fs13.default.readFileSync(result.diagnosticLogPath, "utf8").trim();
20197
20274
  if (!detail) {
20198
- console.error(` ${import_node_path17.default.basename(result.diagnosticLogPath)} was empty`);
20275
+ console.error(` ${import_node_path16.default.basename(result.diagnosticLogPath)} was empty`);
20199
20276
  return;
20200
20277
  }
20201
20278
  const shown = detail.length > 4e3 ? `\u2026
20202
20279
  ${detail.slice(-4e3)}` : detail;
20203
- console.error(` ${import_node_path17.default.basename(result.diagnosticLogPath)}:`);
20280
+ console.error(` ${import_node_path16.default.basename(result.diagnosticLogPath)}:`);
20204
20281
  console.error(shown);
20205
20282
  } catch (err) {
20206
20283
  const message = err instanceof Error ? err.message : String(err);
20207
20284
  console.error(
20208
- ` failed to read ${import_node_path17.default.basename(result.diagnosticLogPath)}: ${message}`
20285
+ ` failed to read ${import_node_path16.default.basename(result.diagnosticLogPath)}: ${message}`
20209
20286
  );
20210
20287
  }
20211
20288
  }
@@ -20687,7 +20764,7 @@ async function runOpenCommand(args, opts = {}) {
20687
20764
  );
20688
20765
  printOpenedSim(shellUrl, { ...targetSim, url: shellUrl }, "current sim");
20689
20766
  await maybeDescribeAfterOpen(parsed.wsPort, targetSim.id, args);
20690
- return;
20767
+ return parsed.wsPort;
20691
20768
  }
20692
20769
  } finally {
20693
20770
  bridge.close();
@@ -20760,6 +20837,7 @@ async function runOpenCommand(args, opts = {}) {
20760
20837
  signalDriverLaunchConnected(result);
20761
20838
  printOpenedSim(openUrl2, match, `${driver.name} driver`);
20762
20839
  await maybeDescribeAfterOpen(parsed.wsPort, match.id, args);
20840
+ return parsed.wsPort;
20763
20841
  }
20764
20842
  async function runUseLikeCommand(args, opts = {}) {
20765
20843
  const parsed = parseBridgeCliArgs(args, {
@@ -21022,14 +21100,14 @@ async function runCloseCommand(args, opts = {}) {
21022
21100
  bridge.close();
21023
21101
  }
21024
21102
  }
21025
- var import_node_child_process6, import_node_fs14, import_node_os3, import_node_path17, import_settings, import_ws5, DEFAULT_DRIVER_CONNECT_TIMEOUT_MS, DRIVER_CONNECT_INTERVAL_MS, DRIVER_CONNECT_HOST_GRACE_MS, OPEN_BRIDGE_SPAWN_TIMEOUT_MS, SHELL_APPS, DEEP_LINK_SCHEME_RE;
21103
+ var import_node_child_process6, import_node_fs13, import_node_os3, import_node_path16, import_settings, import_ws5, DEFAULT_DRIVER_CONNECT_TIMEOUT_MS, DRIVER_CONNECT_INTERVAL_MS, DRIVER_CONNECT_HOST_GRACE_MS, OPEN_BRIDGE_SPAWN_TIMEOUT_MS, SHELL_APPS, DEEP_LINK_SCHEME_RE;
21026
21104
  var init_control = __esm({
21027
21105
  "cli/commands/control.ts"() {
21028
21106
  "use strict";
21029
21107
  import_node_child_process6 = require("node:child_process");
21030
- import_node_fs14 = __toESM(require("node:fs"), 1);
21108
+ import_node_fs13 = __toESM(require("node:fs"), 1);
21031
21109
  import_node_os3 = __toESM(require("node:os"), 1);
21032
- import_node_path17 = __toESM(require("node:path"), 1);
21110
+ import_node_path16 = __toESM(require("node:path"), 1);
21033
21111
  import_settings = require("sootsim-engine/settings");
21034
21112
  import_ws5 = require("ws");
21035
21113
  init_dev_server_scanner();
@@ -21811,14 +21889,14 @@ process.stdin.on('end', async () => {
21811
21889
  function getBaseDir() {
21812
21890
  const explicit = process.env[SESSION_FILE_ENV];
21813
21891
  if (explicit?.trim()) {
21814
- return (0, import_node_path18.dirname)((0, import_node_path18.resolve)(explicit));
21892
+ return (0, import_node_path17.dirname)((0, import_node_path17.resolve)(explicit));
21815
21893
  }
21816
21894
  return electronUserDataDir();
21817
21895
  }
21818
21896
  function getSharedDesktopAuthFilePath() {
21819
21897
  const explicit = process.env[SESSION_FILE_ENV];
21820
- if (explicit?.trim()) return (0, import_node_path18.resolve)(explicit);
21821
- return (0, import_node_path18.join)(getBaseDir(), "desktop-auth.json");
21898
+ if (explicit?.trim()) return (0, import_node_path17.resolve)(explicit);
21899
+ return (0, import_node_path17.join)(getBaseDir(), "desktop-auth.json");
21822
21900
  }
21823
21901
  function normalizeUser(input) {
21824
21902
  if (!input || typeof input !== "object") return null;
@@ -21865,26 +21943,26 @@ function normalizeSession(input) {
21865
21943
  }
21866
21944
  function readSharedDesktopAuthSession() {
21867
21945
  const filepath = getSharedDesktopAuthFilePath();
21868
- if (!(0, import_node_fs15.existsSync)(filepath)) return null;
21946
+ if (!(0, import_node_fs14.existsSync)(filepath)) return null;
21869
21947
  try {
21870
- const parsed = JSON.parse((0, import_node_fs15.readFileSync)(filepath, "utf8"));
21948
+ const parsed = JSON.parse((0, import_node_fs14.readFileSync)(filepath, "utf8"));
21871
21949
  const normalized = normalizeSession(parsed);
21872
21950
  if (!normalized) {
21873
- (0, import_node_fs15.rmSync)(filepath, { force: true });
21951
+ (0, import_node_fs14.rmSync)(filepath, { force: true });
21874
21952
  return null;
21875
21953
  }
21876
21954
  return normalized;
21877
21955
  } catch {
21878
- (0, import_node_fs15.rmSync)(filepath, { force: true });
21956
+ (0, import_node_fs14.rmSync)(filepath, { force: true });
21879
21957
  return null;
21880
21958
  }
21881
21959
  }
21882
- var import_node_fs15, import_node_path18, SESSION_VERSION, SESSION_FILE_ENV, DEFAULT_ORIGIN;
21960
+ var import_node_fs14, import_node_path17, SESSION_VERSION, SESSION_FILE_ENV, DEFAULT_ORIGIN;
21883
21961
  var init_shared_session = __esm({
21884
21962
  "src/auth/shared-session.ts"() {
21885
21963
  "use strict";
21886
- import_node_fs15 = require("node:fs");
21887
- import_node_path18 = require("node:path");
21964
+ import_node_fs14 = require("node:fs");
21965
+ import_node_path17 = require("node:path");
21888
21966
  init_home_paths();
21889
21967
  SESSION_VERSION = 1;
21890
21968
  SESSION_FILE_ENV = "SOOTSIM_SHARED_AUTH_FILE";
@@ -21967,33 +22045,13 @@ var init_run_registry = __esm({
21967
22045
  }
21968
22046
  });
21969
22047
 
21970
- // cli/telemetry.ts
21971
- var init_telemetry = __esm({
21972
- "cli/telemetry.ts"() {
21973
- "use strict";
21974
- init_agent_identity();
21975
- init_shared_session();
21976
- init_home_paths();
21977
- }
21978
- });
21979
-
21980
22048
  // cli/commands/upload.ts
21981
22049
  async function resolveDefaultUploadOrigin(explicitOrigin) {
21982
22050
  if (explicitOrigin) return explicitOrigin;
21983
22051
  if (process.env.SOOTSIM_UPLOAD_ORIGIN) return process.env.SOOTSIM_UPLOAD_ORIGIN;
21984
- try {
21985
- const res = await fetch(`${LOCAL_DEFAULT_ORIGIN}/api/preview/upload/init`, {
21986
- method: "OPTIONS",
21987
- signal: AbortSignal.timeout(LOCAL_UPLOAD_PROBE_TIMEOUT_MS)
21988
- });
21989
- if (res.ok || res.status === 204 || res.status === 405) {
21990
- return LOCAL_DEFAULT_ORIGIN;
21991
- }
21992
- } catch {
21993
- }
21994
22052
  return REMOTE_DEFAULT_ORIGIN;
21995
22053
  }
21996
- var import_compress, import_compress_node, import_presigned_upload, import_trace, REMOTE_DEFAULT_ORIGIN, REMOTE_DEFAULT_PUBLIC_PREVIEW_ORIGIN, LOCAL_DEFAULT_ORIGIN, LOCAL_UPLOAD_PROBE_TIMEOUT_MS;
22054
+ var import_compress, import_compress_node, import_presigned_upload, import_trace, REMOTE_DEFAULT_ORIGIN, REMOTE_DEFAULT_PUBLIC_PREVIEW_ORIGIN;
21997
22055
  var init_upload = __esm({
21998
22056
  "cli/commands/upload.ts"() {
21999
22057
  "use strict";
@@ -22001,19 +22059,15 @@ var init_upload = __esm({
22001
22059
  import_compress_node = require("sootsim-engine/preview/compress-node");
22002
22060
  import_presigned_upload = require("sootsim-engine/preview/presigned-upload");
22003
22061
  import_trace = require("sootsim-engine/preview/trace");
22004
- init_shared_session();
22005
22062
  init_public_brand();
22006
22063
  init_app_fonts();
22007
22064
  init_app_splash();
22008
22065
  init_auth();
22009
22066
  init_open_url2();
22010
22067
  init_run_registry();
22011
- init_telemetry();
22012
22068
  init_ws_bridge();
22013
22069
  REMOTE_DEFAULT_ORIGIN = process.env.SOOTSIM_UPLOAD_ORIGIN || "https://contrast.dev";
22014
22070
  REMOTE_DEFAULT_PUBLIC_PREVIEW_ORIGIN = process.env.SOOTSIM_PREVIEW_ORIGIN || rnxPublicBrand.origin;
22015
- LOCAL_DEFAULT_ORIGIN = "https://contrast.localhost:3000";
22016
- LOCAL_UPLOAD_PROBE_TIMEOUT_MS = 2e3;
22017
22071
  }
22018
22072
  });
22019
22073
 
@@ -22147,16 +22201,16 @@ function resolveFlowScreenshotPath(screenshotDir, name2) {
22147
22201
  }
22148
22202
  function resolveProjectRelativeFlowBase(flowDir) {
22149
22203
  if (!flowDir) return process.cwd();
22150
- return path17.basename(flowDir) === ".maestro" ? path17.dirname(flowDir) : flowDir;
22204
+ return path16.basename(flowDir) === ".maestro" ? path16.dirname(flowDir) : flowDir;
22151
22205
  }
22152
22206
  function resolveFlowScreenshotPathWithMode(screenshotDir, name2, opts) {
22153
22207
  const normalizedName = name2.endsWith(".png") ? name2 : `${name2}.png`;
22154
- if (path17.isAbsolute(normalizedName)) return normalizedName;
22208
+ if (path16.isAbsolute(normalizedName)) return normalizedName;
22155
22209
  if (opts.mode === "flow" && /[\\/]/.test(normalizedName)) {
22156
22210
  const baseDir = normalizedName.startsWith("./") || normalizedName.startsWith("../") ? opts.flowDir ?? process.cwd() : resolveProjectRelativeFlowBase(opts.flowDir);
22157
- return path17.resolve(baseDir, normalizedName);
22211
+ return path16.resolve(baseDir, normalizedName);
22158
22212
  }
22159
- return path17.join(screenshotDir, normalizedName);
22213
+ return path16.join(screenshotDir, normalizedName);
22160
22214
  }
22161
22215
  function normalizeFlowScreenshotSpec(step) {
22162
22216
  if (typeof step === "string") {
@@ -22213,12 +22267,12 @@ function parseFlowSteps(content) {
22213
22267
  const parsed = import_yaml.default.parse(candidate);
22214
22268
  return Array.isArray(parsed) ? parsed : [];
22215
22269
  }
22216
- var fs17, path17, import_settings3, import_yaml, DEFAULT_TIMEOUT, FLOW_TIMEOUT_SCALE, scaledTimeout, SCREEN_W, SCREEN_H, SootSimBridgeFlowRunner;
22270
+ var fs16, path16, import_settings3, import_yaml, DEFAULT_TIMEOUT, FLOW_TIMEOUT_SCALE, scaledTimeout, SCREEN_W, SCREEN_H, SootSimBridgeFlowRunner;
22217
22271
  var init_bridge_flow_runner = __esm({
22218
22272
  "cli/bridge-flow-runner.ts"() {
22219
22273
  "use strict";
22220
- fs17 = __toESM(require("fs"), 1);
22221
- path17 = __toESM(require("path"), 1);
22274
+ fs16 = __toESM(require("fs"), 1);
22275
+ path16 = __toESM(require("path"), 1);
22222
22276
  import_settings3 = require("sootsim-engine/settings");
22223
22277
  import_yaml = __toESM(require("yaml"), 1);
22224
22278
  init_frame_compose();
@@ -22515,14 +22569,14 @@ var init_bridge_flow_runner = __esm({
22515
22569
  if (chunks.length === 0) {
22516
22570
  throw new Error("recording requested but no video buffer was produced");
22517
22571
  }
22518
- fs17.mkdirSync(this.opts.recordingOutputDir, { recursive: true });
22572
+ fs16.mkdirSync(this.opts.recordingOutputDir, { recursive: true });
22519
22573
  const videoMime = stopped.mime || this.opts.recordingFormat || "video/webm";
22520
22574
  const extension = videoMime.includes("mp4") ? "mp4" : "webm";
22521
- const outputPath = path17.join(
22575
+ const outputPath = path16.join(
22522
22576
  this.opts.recordingOutputDir,
22523
22577
  `sootsim-${Date.now()}.${extension}`
22524
22578
  );
22525
- fs17.writeFileSync(outputPath, Buffer.concat(chunks));
22579
+ fs16.writeFileSync(outputPath, Buffer.concat(chunks));
22526
22580
  return outputPath;
22527
22581
  }
22528
22582
  async waitForRecordingTail(opts) {
@@ -23025,7 +23079,7 @@ var init_bridge_flow_runner = __esm({
23025
23079
  flowDir: this.opts.flowDir
23026
23080
  }
23027
23081
  );
23028
- fs17.mkdirSync(path17.dirname(outputPath), { recursive: true });
23082
+ fs16.mkdirSync(path16.dirname(outputPath), { recursive: true });
23029
23083
  const layers = spec.layers ?? this.opts.screenshotLayers;
23030
23084
  const screenshotRequest = {
23031
23085
  type: "screenshot",
@@ -23041,11 +23095,11 @@ var init_bridge_flow_runner = __esm({
23041
23095
  throw new Error("could not read current device model for framed screenshot");
23042
23096
  }
23043
23097
  const framed = await composeFramedScreenshot(rawBuffer, model);
23044
- fs17.writeFileSync(outputPath, framed);
23098
+ fs16.writeFileSync(outputPath, framed);
23045
23099
  console.log(`[flow] screenshot: ${outputPath} (frame: ${model})`);
23046
23100
  return outputPath;
23047
23101
  }
23048
- fs17.writeFileSync(outputPath, rawBuffer);
23102
+ fs16.writeFileSync(outputPath, rawBuffer);
23049
23103
  console.log(`[flow] screenshot: ${outputPath}`);
23050
23104
  return outputPath;
23051
23105
  }
@@ -23061,22 +23115,22 @@ var init_bridge_flow_runner = __esm({
23061
23115
  return model;
23062
23116
  }
23063
23117
  async captureScreenshot(outputPath) {
23064
- fs17.mkdirSync(path17.dirname(outputPath), { recursive: true });
23118
+ fs16.mkdirSync(path16.dirname(outputPath), { recursive: true });
23065
23119
  const dataUrl = await this.bridge.send({
23066
23120
  type: "screenshot",
23067
23121
  simId: this.opts.simId
23068
23122
  });
23069
23123
  const base64 = dataUrl.replace(/^data:image\/png;base64,/, "");
23070
- fs17.writeFileSync(outputPath, Buffer.from(base64, "base64"));
23124
+ fs16.writeFileSync(outputPath, Buffer.from(base64, "base64"));
23071
23125
  }
23072
23126
  // writes a per-failure debug bundle: screenshot + structured describe
23073
23127
  // + a11y tree + console errors/warnings + failed network requests +
23074
23128
  // shell state, all under one directory so post-mortem is one grep away.
23075
23129
  async captureFailureBundle(outputDir, context) {
23076
- fs17.mkdirSync(outputDir, { recursive: true });
23130
+ fs16.mkdirSync(outputDir, { recursive: true });
23077
23131
  const write = (name2, content) => {
23078
23132
  try {
23079
- fs17.writeFileSync(path17.join(outputDir, name2), content);
23133
+ fs16.writeFileSync(path16.join(outputDir, name2), content);
23080
23134
  } catch {
23081
23135
  }
23082
23136
  };
@@ -23595,8 +23649,8 @@ var init_bridge_flow_runner = __esm({
23595
23649
  runScript(target) {
23596
23650
  const spec = typeof target === "string" ? { file: target } : target;
23597
23651
  if (!spec.file) throw new Error("runScript requires a file");
23598
- const scriptPath = path17.resolve(this.opts.flowDir, spec.file);
23599
- const source = fs17.readFileSync(scriptPath, "utf8");
23652
+ const scriptPath = path16.resolve(this.opts.flowDir, spec.file);
23653
+ const source = fs16.readFileSync(scriptPath, "utf8");
23600
23654
  this.js.evaluate(source, { env: spec.env });
23601
23655
  console.log(`[flow] runScript: ${spec.file} done`);
23602
23656
  }
@@ -23890,10 +23944,10 @@ var init_bridge_flow_runner = __esm({
23890
23944
  for (const [k, v] of Object.entries(env)) this.js.putEnv(k, v);
23891
23945
  }
23892
23946
  if (file) {
23893
- const flowPath = path17.resolve(this.opts.flowDir, file);
23894
- const flowContent = fs17.readFileSync(flowPath, "utf8");
23947
+ const flowPath = path16.resolve(this.opts.flowDir, file);
23948
+ const flowContent = fs16.readFileSync(flowPath, "utf8");
23895
23949
  const previousDir = this.opts.flowDir;
23896
- this.opts.flowDir = path17.dirname(flowPath);
23950
+ this.opts.flowDir = path16.dirname(flowPath);
23897
23951
  try {
23898
23952
  await this.runFlow(parseFlowSteps(flowContent));
23899
23953
  } finally {
@@ -24048,8 +24102,8 @@ __export(registry_exports, {
24048
24102
  skillRegistry: () => skillRegistry
24049
24103
  });
24050
24104
  module.exports = __toCommonJS(registry_exports);
24051
- var fs18 = __toESM(require("fs"), 1);
24052
- var path18 = __toESM(require("path"), 1);
24105
+ var fs17 = __toESM(require("fs"), 1);
24106
+ var path17 = __toESM(require("path"), 1);
24053
24107
 
24054
24108
  // src/skills/builtin/a11y-review.ts
24055
24109
  init_reap_browser();
@@ -24068,8 +24122,8 @@ var skill = {
24068
24122
  },
24069
24123
  async execute(params, context) {
24070
24124
  const { chromium } = await import("playwright");
24071
- const path19 = await import("path");
24072
- const fs19 = await import("fs");
24125
+ const path18 = await import("path");
24126
+ const fs18 = await import("fs");
24073
24127
  const browser = await launchReapedChrome((options) => chromium.launch(options), {
24074
24128
  headless: true
24075
24129
  });
@@ -24124,9 +24178,9 @@ var skill = {
24124
24178
  warnings: issues.filter((i) => i.severity === "warning").length,
24125
24179
  tree: typeof tree === "string" ? tree : JSON.stringify(tree)
24126
24180
  };
24127
- const reportPath = path19.join(context.outputDir, "a11y-report.json");
24128
- fs19.mkdirSync(path19.dirname(reportPath), { recursive: true });
24129
- fs19.writeFileSync(reportPath, JSON.stringify(report, null, 2));
24181
+ const reportPath = path18.join(context.outputDir, "a11y-report.json");
24182
+ fs18.mkdirSync(path18.dirname(reportPath), { recursive: true });
24183
+ fs18.writeFileSync(reportPath, JSON.stringify(report, null, 2));
24130
24184
  return {
24131
24185
  success: issues.filter((i) => i.severity === "error").length === 0,
24132
24186
  message: `${report.errors} errors, ${report.warnings} warnings across ${report.nodeCount} nodes`,
@@ -24159,14 +24213,14 @@ var skill2 = {
24159
24213
  }
24160
24214
  },
24161
24215
  async execute(params, context) {
24162
- const fs19 = await import("node:fs");
24163
- const path19 = await import("node:path");
24216
+ const fs18 = await import("node:fs");
24217
+ const path18 = await import("node:path");
24164
24218
  const { COMPAT_RESULT_CAVEAT: COMPAT_RESULT_CAVEAT2, formatPackageStatus: formatPackageStatus2, formatScanCounts: formatScanCounts2, scanDeps: scanDeps2 } = await Promise.resolve().then(() => (init_web(), web_exports));
24165
- const packagePath = path19.join(context.projectDir, "package.json");
24166
- if (!fs19.existsSync(packagePath)) {
24219
+ const packagePath = path18.join(context.projectDir, "package.json");
24220
+ if (!fs18.existsSync(packagePath)) {
24167
24221
  return { success: false, message: "No package.json found" };
24168
24222
  }
24169
- const parsed = JSON.parse(fs19.readFileSync(packagePath, "utf8"));
24223
+ const parsed = JSON.parse(fs18.readFileSync(packagePath, "utf8"));
24170
24224
  if (!parsed || typeof parsed !== "object") {
24171
24225
  return { success: false, message: "package.json is not an object" };
24172
24226
  }
@@ -24178,9 +24232,9 @@ var skill2 = {
24178
24232
  }) : scanDeps2(parsed);
24179
24233
  const packageResult = params.packageName ? scan.packages[0] : void 0;
24180
24234
  const message = packageResult ? `${params.packageName}: ${formatPackageStatus2(packageResult)}. ${COMPAT_RESULT_CAVEAT2}` : `${formatScanCounts2(scan)}. ${COMPAT_RESULT_CAVEAT2}`;
24181
- const reportPath = path19.join(context.outputDir, "compat-report.json");
24182
- fs19.mkdirSync(path19.dirname(reportPath), { recursive: true });
24183
- fs19.writeFileSync(reportPath, `${JSON.stringify(scan, null, 2)}
24235
+ const reportPath = path18.join(context.outputDir, "compat-report.json");
24236
+ fs18.mkdirSync(path18.dirname(reportPath), { recursive: true });
24237
+ fs18.writeFileSync(reportPath, `${JSON.stringify(scan, null, 2)}
24184
24238
  `);
24185
24239
  return {
24186
24240
  success: true,
@@ -24224,8 +24278,8 @@ var skill3 = {
24224
24278
  record: { type: "boolean", description: "Record video of the flow" }
24225
24279
  },
24226
24280
  async execute(params, context) {
24227
- const fs19 = await import("fs");
24228
- const path19 = await import("path");
24281
+ const fs18 = await import("fs");
24282
+ const path18 = await import("path");
24229
24283
  const yaml2 = await import("yaml");
24230
24284
  const { DEFAULT_SOOTSIM_BRIDGE_PORT: DEFAULT_SOOTSIM_BRIDGE_PORT2 } = await Promise.resolve().then(() => (init_bridge_constants(), bridge_constants_exports));
24231
24285
  const { SootSimBridgeHost: SootSimBridgeHost2 } = await Promise.resolve().then(() => (init_bridge_host(), bridge_host_exports));
@@ -24235,7 +24289,7 @@ var skill3 = {
24235
24289
  const host = new SootSimBridgeHost2({ port: DEFAULT_SOOTSIM_BRIDGE_PORT2 });
24236
24290
  host.start({ silent: true });
24237
24291
  try {
24238
- const flowContent = fs19.readFileSync(params.flowPath, "utf8");
24292
+ const flowContent = fs18.readFileSync(params.flowPath, "utf8");
24239
24293
  const fmMatch = flowContent.match(/^---\n([\s\S]*?)\n---\n?/);
24240
24294
  const stepsBody = fmMatch ? flowContent.slice(fmMatch[0].length) : flowContent;
24241
24295
  const steps = yaml2.parse(stepsBody);
@@ -24262,7 +24316,7 @@ var skill3 = {
24262
24316
  });
24263
24317
  const driver = new SootSimBridgeFlowRunner2(bridge, {
24264
24318
  screenshotDir: context.outputDir,
24265
- flowDir: path19.dirname(path19.resolve(params.flowPath)),
24319
+ flowDir: path18.dirname(path18.resolve(params.flowPath)),
24266
24320
  simId,
24267
24321
  recordingOutputDir: params.record ? context.outputDir : void 0
24268
24322
  });
@@ -24284,7 +24338,7 @@ var skill3 = {
24284
24338
  simId,
24285
24339
  videoPath: videoPath || void 0
24286
24340
  },
24287
- artifacts: videoPath ? [{ type: "video", name: path19.basename(videoPath), path: videoPath }] : void 0
24341
+ artifacts: videoPath ? [{ type: "video", name: path18.basename(videoPath), path: videoPath }] : void 0
24288
24342
  };
24289
24343
  } finally {
24290
24344
  try {
@@ -24331,8 +24385,8 @@ var skill4 = {
24331
24385
  },
24332
24386
  async execute(params, context) {
24333
24387
  const { chromium } = await import("playwright");
24334
- const path19 = await import("path");
24335
- const fs19 = await import("fs");
24388
+ const path18 = await import("path");
24389
+ const fs18 = await import("fs");
24336
24390
  const browser = await launchReapedChrome((options) => chromium.launch(options), {
24337
24391
  headless: true
24338
24392
  });
@@ -24358,9 +24412,9 @@ var skill4 = {
24358
24412
  ...profile,
24359
24413
  fps: syncCount > 0 ? (syncCount / (duration / 1e3)).toFixed(1) : "0"
24360
24414
  };
24361
- const reportPath = path19.join(context.outputDir, "perf-report.json");
24362
- fs19.mkdirSync(path19.dirname(reportPath), { recursive: true });
24363
- fs19.writeFileSync(reportPath, JSON.stringify(report, null, 2));
24415
+ const reportPath = path18.join(context.outputDir, "perf-report.json");
24416
+ fs18.mkdirSync(path18.dirname(reportPath), { recursive: true });
24417
+ fs18.writeFileSync(reportPath, JSON.stringify(report, null, 2));
24364
24418
  return {
24365
24419
  success: true,
24366
24420
  message: `${report.fps} fps, ${profile.avgSyncMs?.toFixed(1)}ms avg sync, ${nodeCount} nodes`,
@@ -24393,8 +24447,8 @@ var skill5 = {
24393
24447
  },
24394
24448
  async execute(params, context) {
24395
24449
  const { chromium } = await import("playwright");
24396
- const path19 = await import("path");
24397
- const fs19 = await import("fs");
24450
+ const path18 = await import("path");
24451
+ const fs18 = await import("fs");
24398
24452
  const browser = await launchReapedChrome((options) => chromium.launch(options), {
24399
24453
  headless: true
24400
24454
  });
@@ -24402,8 +24456,8 @@ var skill5 = {
24402
24456
  try {
24403
24457
  await page.goto(context.url, { waitUntil: "networkidle" });
24404
24458
  await page.waitForTimeout(2e3);
24405
- const outputPath = path19.join(context.outputDir, `${params.name}.png`);
24406
- fs19.mkdirSync(path19.dirname(outputPath), { recursive: true });
24459
+ const outputPath = path18.join(context.outputDir, `${params.name}.png`);
24460
+ fs18.mkdirSync(path18.dirname(outputPath), { recursive: true });
24407
24461
  await page.screenshot({ path: outputPath });
24408
24462
  return {
24409
24463
  success: true,
@@ -24444,8 +24498,8 @@ var skill6 = {
24444
24498
  },
24445
24499
  async execute(params, context) {
24446
24500
  const { chromium } = await import("playwright");
24447
- const path19 = await import("path");
24448
- const fs19 = await import("fs");
24501
+ const path18 = await import("path");
24502
+ const fs18 = await import("fs");
24449
24503
  const browser = await launchReapedChrome((options) => chromium.launch(options), {
24450
24504
  headless: true
24451
24505
  });
@@ -24453,14 +24507,14 @@ var skill6 = {
24453
24507
  try {
24454
24508
  await page.goto(context.url, { waitUntil: "networkidle" });
24455
24509
  await page.waitForTimeout(2e3);
24456
- const currentPath = path19.join(context.outputDir, `${params.name}-current.png`);
24457
- fs19.mkdirSync(path19.dirname(currentPath), { recursive: true });
24510
+ const currentPath = path18.join(context.outputDir, `${params.name}-current.png`);
24511
+ fs18.mkdirSync(path18.dirname(currentPath), { recursive: true });
24458
24512
  await page.screenshot({ path: currentPath });
24459
24513
  try {
24460
24514
  const { PNG } = await import("pngjs");
24461
24515
  const pixelmatch = (await import("pixelmatch")).default;
24462
- const baseline = PNG.sync.read(fs19.readFileSync(params.baseline));
24463
- const current = PNG.sync.read(fs19.readFileSync(currentPath));
24516
+ const baseline = PNG.sync.read(fs18.readFileSync(params.baseline));
24517
+ const current = PNG.sync.read(fs18.readFileSync(currentPath));
24464
24518
  const { width, height } = baseline;
24465
24519
  const diff = new PNG({ width, height });
24466
24520
  const numDiffPixels = pixelmatch(
@@ -24471,8 +24525,8 @@ var skill6 = {
24471
24525
  height,
24472
24526
  { threshold: params.threshold || 0.1 }
24473
24527
  );
24474
- const diffPath = path19.join(context.outputDir, `${params.name}-diff.png`);
24475
- fs19.writeFileSync(diffPath, PNG.sync.write(diff));
24528
+ const diffPath = path18.join(context.outputDir, `${params.name}-diff.png`);
24529
+ fs18.writeFileSync(diffPath, PNG.sync.write(diff));
24476
24530
  const totalPixels = width * height;
24477
24531
  const diffPercent = (numDiffPixels / totalPixels * 100).toFixed(2);
24478
24532
  return {
@@ -24536,10 +24590,10 @@ var skillRegistry = {
24536
24590
  },
24537
24591
  // load skills from a project manifest (sootsim-skills.json)
24538
24592
  async loadFromManifest(projectDir) {
24539
- const manifestPath = path18.join(projectDir, "sootsim-skills.json");
24540
- if (!fs18.existsSync(manifestPath)) return;
24593
+ const manifestPath = path17.join(projectDir, "sootsim-skills.json");
24594
+ if (!fs17.existsSync(manifestPath)) return;
24541
24595
  try {
24542
- const manifest = JSON.parse(fs18.readFileSync(manifestPath, "utf8"));
24596
+ const manifest = JSON.parse(fs17.readFileSync(manifestPath, "utf8"));
24543
24597
  if (Array.isArray(manifest.skills)) {
24544
24598
  for (const entry of manifest.skills) {
24545
24599
  if (typeof entry === "string") {
@@ -24550,7 +24604,7 @@ var skillRegistry = {
24550
24604
  }
24551
24605
  } else if (entry.file) {
24552
24606
  try {
24553
- const mod = await import(path18.resolve(projectDir, entry.file));
24607
+ const mod = await import(path17.resolve(projectDir, entry.file));
24554
24608
  if (mod.skill) this.register(mod.skill);
24555
24609
  } catch {
24556
24610
  }
@@ -24563,10 +24617,10 @@ var skillRegistry = {
24563
24617
  // auto-discover skills based on project dependencies
24564
24618
  suggestSkills(projectDir) {
24565
24619
  const suggestions = [];
24566
- const pkgPath = path18.join(projectDir, "package.json");
24567
- if (!fs18.existsSync(pkgPath)) return suggestions;
24620
+ const pkgPath = path17.join(projectDir, "package.json");
24621
+ if (!fs17.existsSync(pkgPath)) return suggestions;
24568
24622
  try {
24569
- const pkg = JSON.parse(fs18.readFileSync(pkgPath, "utf8"));
24623
+ const pkg = JSON.parse(fs17.readFileSync(pkgPath, "utf8"));
24570
24624
  const deps = { ...pkg.dependencies, ...pkg.devDependencies };
24571
24625
  if (deps["detox"] || deps["maestro"]) {
24572
24626
  suggestions.push("maestro-test");