rnxsim 0.1.343 → 0.1.345

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 (209) 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-RFD32ACU.js → agent-52G55XTO.js} +2 -2
  23. package/dist-cli/chunks/{agent-wrapper-2YLW2HQK.js → agent-wrapper-BOZM43ON.js} +2 -2
  24. package/dist-cli/chunks/{app-fonts-6OATDF3Y.js → app-fonts-A2L6V5EZ.js} +2 -2
  25. package/dist-cli/chunks/{assert-PSQICFOY.js → assert-CJKIP4LN.js} +2 -2
  26. package/dist-cli/chunks/{auth-XWLFKZT7.js → auth-VHHN3TOB.js} +2 -2
  27. package/dist-cli/chunks/{beta-I6WTVM6C.js → beta-EBDIGV3E.js} +2 -2
  28. package/dist-cli/chunks/{camera-45HKTLWG.js → camera-5F4KKCQ3.js} +2 -2
  29. package/dist-cli/chunks/chunk-3DBTS4MC.js +15 -0
  30. package/dist-cli/chunks/chunk-3N7M5HMJ.js +6 -0
  31. package/dist-cli/chunks/{chunk-RWYA2USN.js → chunk-4BIPA7MD.js} +1 -1
  32. package/dist-cli/chunks/chunk-4OKV7T5D.js +4 -0
  33. package/dist-cli/chunks/{chunk-T4T6DDKM.js → chunk-4ZILDTZX.js} +2 -2
  34. package/dist-cli/chunks/{chunk-R7X74I3I.js → chunk-5GXRRUOA.js} +26 -26
  35. package/dist-cli/chunks/chunk-63ASUHOV.js +4 -0
  36. package/dist-cli/chunks/{chunk-DASP2EKF.js → chunk-7GO2DSYQ.js} +1 -1
  37. package/dist-cli/chunks/{chunk-HJXIBZ35.js → chunk-7LGINKM6.js} +2 -2
  38. package/dist-cli/chunks/{chunk-2IPMH24A.js → chunk-7UIIAHVU.js} +2 -2
  39. package/dist-cli/chunks/{chunk-ES5BHPTS.js → chunk-7V4KPZ7O.js} +2 -2
  40. package/dist-cli/chunks/{chunk-FCHRGEHA.js → chunk-7YMQRA6L.js} +2 -2
  41. package/dist-cli/chunks/{chunk-DC7TS573.js → chunk-A2Q76LJV.js} +2 -2
  42. package/dist-cli/chunks/{chunk-SAUCA7SN.js → chunk-BCQV63BW.js} +45 -45
  43. package/dist-cli/chunks/{chunk-3FYJ7U3C.js → chunk-BFTDL2GX.js} +17 -17
  44. package/dist-cli/chunks/chunk-BHKSCNDC.js +4 -0
  45. package/dist-cli/chunks/{chunk-QPTJNLPT.js → chunk-DGTV3KOW.js} +1 -1
  46. package/dist-cli/chunks/chunk-DHM3JDCK.js +4 -0
  47. package/dist-cli/chunks/{chunk-TQRVLK64.js → chunk-DPP2M4AJ.js} +1 -1
  48. package/dist-cli/chunks/chunk-E2RGSCHT.js +4 -0
  49. package/dist-cli/chunks/{chunk-TQ5OPG33.js → chunk-EPLVIDPC.js} +1 -1
  50. package/dist-cli/chunks/{chunk-P3IDK7FR.js → chunk-GKGXULJK.js} +2 -2
  51. package/dist-cli/chunks/{chunk-34KDMQ6U.js → chunk-HK7YKH6N.js} +2 -2
  52. package/dist-cli/chunks/{chunk-BJUNXCPO.js → chunk-HXE344V3.js} +1 -1
  53. package/dist-cli/chunks/{chunk-HJHFC5ZZ.js → chunk-JC33TOAF.js} +1 -1
  54. package/dist-cli/chunks/{chunk-G3B2RZ47.js → chunk-KA4PJNTE.js} +2 -2
  55. package/dist-cli/chunks/{chunk-VC4TDPJQ.js → chunk-KH777UAO.js} +3 -3
  56. package/dist-cli/chunks/{chunk-4PFMDBLB.js → chunk-KRZPFXVN.js} +1 -1
  57. package/dist-cli/chunks/{chunk-6SLM6URN.js → chunk-KZFJNDH3.js} +1 -1
  58. package/dist-cli/chunks/{chunk-KLMWLQAO.js → chunk-LAQGSSTT.js} +1 -1
  59. package/dist-cli/chunks/chunk-LRM2XCEQ.js +11 -0
  60. package/dist-cli/chunks/{chunk-YKKVDKLE.js → chunk-NUPRHZQW.js} +2 -2
  61. package/dist-cli/chunks/{chunk-AKV2DGKM.js → chunk-OPHT4DUO.js} +1 -1
  62. package/dist-cli/chunks/{chunk-KON6XG4Y.js → chunk-P2CGGMU2.js} +2 -2
  63. package/dist-cli/chunks/{chunk-5FB4EA33.js → chunk-PLB6UVVX.js} +2 -2
  64. package/dist-cli/chunks/{chunk-FRRHCLG2.js → chunk-PLUWN2V5.js} +1 -1
  65. package/dist-cli/chunks/{chunk-WDA4QR6U.js → chunk-QNBBKRCA.js} +2 -2
  66. package/dist-cli/chunks/{chunk-G7S6T5OX.js → chunk-QNRWPAOA.js} +2 -2
  67. package/dist-cli/chunks/{chunk-2EVBZ37G.js → chunk-R7ZNRGQN.js} +1 -1
  68. package/dist-cli/chunks/{chunk-TMJGPUWJ.js → chunk-RB4VFUNM.js} +3 -3
  69. package/dist-cli/chunks/chunk-RT3DXTDP.js +25 -0
  70. package/dist-cli/chunks/{chunk-OYLIWHDB.js → chunk-RTDHX6CW.js} +2 -2
  71. package/dist-cli/chunks/{chunk-YLFDA4WW.js → chunk-T7SBFG5I.js} +2 -2
  72. package/dist-cli/chunks/{chunk-W7EXE22C.js → chunk-TEU5NGUA.js} +2 -2
  73. package/dist-cli/chunks/chunk-TMRD5MHL.js +99 -0
  74. package/dist-cli/chunks/{chunk-SU7RE3PV.js → chunk-TOF5TA5X.js} +11 -11
  75. package/dist-cli/chunks/{chunk-6IVEGYWV.js → chunk-U5YHXQHH.js} +2 -2
  76. package/dist-cli/chunks/{chunk-BA3HRFJ5.js → chunk-UVOEHIGP.js} +1 -1
  77. package/dist-cli/chunks/{chunk-SW4DV2TY.js → chunk-VLBN3KRC.js} +1 -1
  78. package/dist-cli/chunks/{chunk-7WKV6EKX.js → chunk-WPPGU5IK.js} +2 -2
  79. package/dist-cli/chunks/{chunk-JOE3J46E.js → chunk-WU72KL6Z.js} +2 -2
  80. package/dist-cli/chunks/chunk-XJBFJGNT.js +4 -0
  81. package/dist-cli/chunks/{chunk-3CMKNWRL.js → chunk-XXZLK4BY.js} +1 -1
  82. package/dist-cli/chunks/{chunk-BKMPV6N4.js → chunk-YVSUFVVB.js} +1 -1
  83. package/dist-cli/chunks/{chunk-RO3WVQPB.js → chunk-ZMTZ5G5H.js} +2 -2
  84. package/dist-cli/chunks/{cleanup-OU4VKFC3.js → cleanup-MAVZJXVW.js} +2 -2
  85. package/dist-cli/chunks/cli-version-2B4KRNML.js +4 -0
  86. package/dist-cli/chunks/{compat-GAEKP67T.js → compat-7AURLGCS.js} +2 -2
  87. package/dist-cli/chunks/config-TN3Y4J5N.js +13 -0
  88. package/dist-cli/chunks/{control-VQJURMFD.js → control-EV2ELU5R.js} +2 -2
  89. package/dist-cli/chunks/daemon-KKDG5MYR.js +4 -0
  90. package/dist-cli/chunks/{debug-F5N2LO4T.js → debug-JICITUB6.js} +3 -3
  91. package/dist-cli/chunks/{desktop-I4ILT4BS.js → desktop-4F5HGLLX.js} +3 -3
  92. package/dist-cli/chunks/{detox-2HMPYSP4.js → detox-ZC7NHLJI.js} +2 -2
  93. package/dist-cli/chunks/{device-5LWLOB5R.js → device-V57GHRXX.js} +2 -2
  94. package/dist-cli/chunks/{diagnose-5S4IQPAP.js → diagnose-FF4IWYXC.js} +2 -2
  95. package/dist-cli/chunks/{disk-cleanup-CJSZVJQX.js → disk-cleanup-T277L6ZO.js} +2 -2
  96. package/dist-cli/chunks/drivers-LCX7S76X.js +4 -0
  97. package/dist-cli/chunks/{film-HCPU2TDB.js → film-G7ZAPWB7.js} +3 -3
  98. package/dist-cli/chunks/flow-ONZGGFGD.js +4 -0
  99. package/dist-cli/chunks/help-DROVDXJM.js +4 -0
  100. package/dist-cli/chunks/{hidden-runtime-alias-YYUEZQPR.js → hidden-runtime-alias-VSJNNSNI.js} +2 -2
  101. package/dist-cli/chunks/{home-paths-3FXISM5I.js → home-paths-75HP3HKA.js} +2 -2
  102. package/dist-cli/chunks/inspect-MDA7ESGQ.js +4 -0
  103. package/dist-cli/chunks/install-desktop-EIVZHIFA.js +4 -0
  104. package/dist-cli/chunks/login-ZSIHGHUK.js +26 -0
  105. package/dist-cli/chunks/logout-Q4DXMI2C.js +4 -0
  106. package/dist-cli/chunks/{maestro-F5KK4RC4.js → maestro-LE5LCRBL.js} +3 -3
  107. package/dist-cli/chunks/{maestro-generate-6SUU3Y5I.js → maestro-generate-D3ZIJCRL.js} +3 -3
  108. package/dist-cli/chunks/{mode-CSWGOJLI.js → mode-BWX6MDPJ.js} +2 -2
  109. package/dist-cli/chunks/{optional-demo-registry-Y6VJ4SVV.js → optional-demo-registry-6CVASL3S.js} +2 -2
  110. package/dist-cli/chunks/{perf-NRWWAJL4.js → perf-NXQZISN6.js} +2 -2
  111. package/dist-cli/chunks/{permissions-OD62A3DJ.js → permissions-5PD67RRO.js} +2 -2
  112. package/dist-cli/chunks/privacy-H5KNRNIZ.js +4 -0
  113. package/dist-cli/chunks/{record-JZT2GM3G.js → record-YJF4KXMR.js} +3 -3
  114. package/dist-cli/chunks/{report-issue-ZWSNHOPB.js → report-issue-XOL5FQBG.js} +2 -2
  115. package/dist-cli/chunks/reset-5LZN5P5B.js +4 -0
  116. package/dist-cli/chunks/runtime-U36VWBH3.js +4 -0
  117. package/dist-cli/chunks/{screenshot-command-ZGJR7ICY.js → screenshot-command-VRPJXWFS.js} +3 -3
  118. package/dist-cli/chunks/{screenshot-layers-5AQKOJZK.js → screenshot-layers-QP4WA2F5.js} +2 -2
  119. package/dist-cli/chunks/{screenshots-capture-CPTWK5Y3.js → screenshots-capture-O2IBAVSP.js} +2 -2
  120. package/dist-cli/chunks/serve-WC6RZNJL.js +118 -0
  121. package/dist-cli/chunks/setup-A6BIG3KL.js +30 -0
  122. package/dist-cli/chunks/{skills-NVWRBMEO.js → skills-A7YC6RL5.js} +2 -2
  123. package/dist-cli/chunks/{state-QGLTKN6K.js → state-IJI5YROJ.js} +2 -2
  124. package/dist-cli/chunks/{storage-OZMEZ4AV.js → storage-Z2DBO2PQ.js} +4 -4
  125. package/dist-cli/chunks/store-BYTCKNIC.js +4 -0
  126. package/dist-cli/chunks/telemetry-JLHFMD7N.js +4 -0
  127. package/dist-cli/chunks/{timeline-EXLDREY3.js → timeline-N7V2M6QA.js} +2 -2
  128. package/dist-cli/chunks/{upgrade-4VWXQO6I.js → upgrade-P2NH3BOR.js} +2 -2
  129. package/dist-cli/chunks/upload-Z2WX5DCD.js +4 -0
  130. package/dist-cli/chunks/{version-WZM6XCD2.js → version-OUHCSCUS.js} +2 -2
  131. package/dist-cli/chunks/{web-ZYPEUVWL.js → web-6NRTF3UV.js} +2 -2
  132. package/dist-cli/chunks/welcome-6RS7PEJW.js +5 -0
  133. package/dist-cli/chunks/{what-happened-KF2HLWKW.js → what-happened-FUEL3BEF.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 +1 -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 +18 -4
  149. package/dist-lib/home-paths.cjs +145 -11
  150. package/dist-lib/host/bridge-host.cjs +236 -31
  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 +25 -2
  169. package/dist-lib/sdk.cjs +1 -1
  170. package/dist-lib/sdk.mjs +1 -1
  171. package/dist-lib/skills.cjs +384 -385
  172. package/dist-lib/vite.cjs +193 -37
  173. package/package.json +2 -1
  174. package/scripts/dev-server-scanner.ts +12 -5
  175. package/skills/rnx-test/SKILL.md +4 -0
  176. package/src/dev-bundle-resolution.ts +13 -6
  177. package/src/dev-server-open.ts +197 -0
  178. package/src/home-paths.ts +168 -15
  179. package/src/host/bridge-host.ts +2 -2
  180. package/src/host/replacement-module-handler.ts +68 -2
  181. package/src/metro-plugin.ts +83 -5
  182. package/src/native-dev-bundle-url.ts +22 -0
  183. package/src/vite-plugin-one.ts +42 -28
  184. package/dist-cli/chunks/chunk-44LRQEU3.js +0 -4
  185. package/dist-cli/chunks/chunk-4RKTGL63.js +0 -9
  186. package/dist-cli/chunks/chunk-5SCIDXJD.js +0 -25
  187. package/dist-cli/chunks/chunk-CUVY4A2A.js +0 -4
  188. package/dist-cli/chunks/chunk-DPJGB7MP.js +0 -101
  189. package/dist-cli/chunks/chunk-HD6P3W5R.js +0 -4
  190. package/dist-cli/chunks/chunk-HFNESSHG.js +0 -15
  191. package/dist-cli/chunks/chunk-I7HHBFSD.js +0 -4
  192. package/dist-cli/chunks/chunk-LV2G34IX.js +0 -4
  193. package/dist-cli/chunks/cli-version-REQOM22G.js +0 -4
  194. package/dist-cli/chunks/config-QTFXJKD6.js +0 -13
  195. package/dist-cli/chunks/daemon-ETA375WE.js +0 -4
  196. package/dist-cli/chunks/drivers-R4YB2XAF.js +0 -4
  197. package/dist-cli/chunks/flow-3VQL6XZT.js +0 -4
  198. package/dist-cli/chunks/help-2RYCKZ2G.js +0 -4
  199. package/dist-cli/chunks/inspect-XDSX5D6D.js +0 -4
  200. package/dist-cli/chunks/install-desktop-OKVRLL35.js +0 -4
  201. package/dist-cli/chunks/login-4VCBPAY2.js +0 -28
  202. package/dist-cli/chunks/logout-WSL5JS5R.js +0 -4
  203. package/dist-cli/chunks/reset-3C52YQCV.js +0 -4
  204. package/dist-cli/chunks/runtime-4V2RBUOR.js +0 -4
  205. package/dist-cli/chunks/serve-ERAZQGCJ.js +0 -118
  206. package/dist-cli/chunks/setup-UG6PLYIL.js +0 -32
  207. package/dist-cli/chunks/store-VPBQKKWA.js +0 -4
  208. package/dist-cli/chunks/telemetry-FRW7OCYZ.js +0 -4
  209. package/dist-cli/chunks/upload-C6676NST.js +0 -4
@@ -1,4 +1,4 @@
1
- /*! rnx v0.1.343 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
1
+ /*! rnx v0.1.345 | (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;
@@ -41,6 +41,17 @@ module.exports = __toCommonJS(replacement_module_handler_exports);
41
41
  var import_promises = __toESM(require("node:fs/promises"), 1);
42
42
  var import_node_path = __toESM(require("node:path"), 1);
43
43
  var import_esbuild = require("esbuild");
44
+
45
+ // src/backend-origin.ts
46
+ function normalizeHostname(hostname) {
47
+ return hostname.replace(/^\[|\]$/g, "").toLowerCase();
48
+ }
49
+ function isLoopbackHost(hostname) {
50
+ const normalized = normalizeHostname(hostname);
51
+ return normalized === "localhost" || normalized.endsWith(".localhost") || normalized === "0.0.0.0" || normalized === "::1" || /^127(?:\.\d{1,3}){3}$/.test(normalized);
52
+ }
53
+
54
+ // src/host/replacement-module-handler.ts
44
55
  var REPLACEMENT_MODULE_ROUTE = "/__sootsim-replacement-module";
45
56
  var REPLACEMENT_RUNTIME_PREFIX = "/__sootsim-replacement-runtime";
46
57
  function getReplacementLoader(filePath) {
@@ -141,9 +152,18 @@ export const Appearance = rn.Appearance
141
152
  `;
142
153
  }
143
154
  function sendJavaScript(req, res, source) {
155
+ const origin = req.headers.origin;
156
+ let allowedOrigin;
157
+ if (origin) {
158
+ try {
159
+ if (isLoopbackHost(new URL(origin).hostname)) allowedOrigin = origin;
160
+ } catch {
161
+ }
162
+ }
144
163
  res.writeHead(200, {
145
164
  "Content-Type": "application/javascript; charset=utf-8",
146
- "Cache-Control": "no-cache"
165
+ "Cache-Control": "no-cache",
166
+ ...allowedOrigin ? { "Access-Control-Allow-Origin": allowedOrigin, Vary: "Origin" } : {}
147
167
  });
148
168
  res.end(req.method === "HEAD" ? void 0 : source);
149
169
  }
@@ -164,6 +184,23 @@ async function handleReplacementModuleRequest(req, res, options = {}) {
164
184
  } catch {
165
185
  return false;
166
186
  }
187
+ if (options.requireLoopbackOrigin) {
188
+ const origin = req.headers.origin;
189
+ const remoteAddress = req.socket.remoteAddress?.replace(/^::ffff:/, "");
190
+ let allowed = Boolean(remoteAddress && isLoopbackHost(remoteAddress));
191
+ if (allowed && origin) {
192
+ try {
193
+ allowed = isLoopbackHost(new URL(origin).hostname);
194
+ } catch {
195
+ allowed = false;
196
+ }
197
+ }
198
+ if (!allowed) {
199
+ res.writeHead(403, { "Content-Type": "text/plain; charset=utf-8" });
200
+ res.end("replacement modules are available only to loopback origins");
201
+ return true;
202
+ }
203
+ }
167
204
  if (url.pathname === `${REPLACEMENT_RUNTIME_PREFIX}/react`) {
168
205
  sendJavaScript(req, res, reactRuntimeSource(options.reactBridgeUrl));
169
206
  return true;
@@ -173,6 +210,11 @@ async function handleReplacementModuleRequest(req, res, options = {}) {
173
210
  return true;
174
211
  }
175
212
  if (url.pathname !== REPLACEMENT_MODULE_ROUTE) return false;
213
+ if (options.requestToken && url.searchParams.get("token") !== options.requestToken) {
214
+ res.writeHead(403, { "Content-Type": "text/plain; charset=utf-8" });
215
+ res.end("replacement module request token is invalid");
216
+ return true;
217
+ }
176
218
  const targetPath = url.searchParams.get("path");
177
219
  if (!targetPath) {
178
220
  res.writeHead(400, { "Content-Type": "text/plain; charset=utf-8" });
@@ -185,17 +227,42 @@ async function handleReplacementModuleRequest(req, res, options = {}) {
185
227
  res.end("replacement files must be .js, .jsx, .ts, or .tsx");
186
228
  return true;
187
229
  }
230
+ if (!import_node_path.default.isAbsolute(targetPath)) {
231
+ res.writeHead(400, { "Content-Type": "text/plain; charset=utf-8" });
232
+ res.end("replacement file path must be absolute");
233
+ return true;
234
+ }
235
+ let resolvedProjectRoot;
236
+ if (options.projectRoot) {
237
+ const lexicalProjectRoot = import_node_path.default.resolve(options.projectRoot);
238
+ resolvedProjectRoot = await import_promises.default.realpath(options.projectRoot);
239
+ const lexicalTarget = import_node_path.default.relative(lexicalProjectRoot, targetPath);
240
+ if (lexicalTarget.startsWith("..") || import_node_path.default.isAbsolute(lexicalTarget)) {
241
+ res.writeHead(403, { "Content-Type": "text/plain; charset=utf-8" });
242
+ res.end("replacement file must be inside the project root");
243
+ return true;
244
+ }
245
+ }
246
+ let resolvedTargetPath;
188
247
  try {
189
- await import_promises.default.access(targetPath);
248
+ resolvedTargetPath = await import_promises.default.realpath(targetPath);
190
249
  } catch (error) {
191
250
  const message = error instanceof Error ? error.message : String(error);
192
251
  res.writeHead(404, { "Content-Type": "text/plain; charset=utf-8" });
193
252
  res.end(`replacement file not found: ${message}`);
194
253
  return true;
195
254
  }
255
+ if (resolvedProjectRoot) {
256
+ const relativeTarget = import_node_path.default.relative(resolvedProjectRoot, resolvedTargetPath);
257
+ if (relativeTarget.startsWith("..") || import_node_path.default.isAbsolute(relativeTarget)) {
258
+ res.writeHead(403, { "Content-Type": "text/plain; charset=utf-8" });
259
+ res.end("replacement file must be inside the project root");
260
+ return true;
261
+ }
262
+ }
196
263
  try {
197
264
  const result = await (0, import_esbuild.build)({
198
- entryPoints: [targetPath],
265
+ entryPoints: [resolvedTargetPath],
199
266
  bundle: true,
200
267
  write: false,
201
268
  format: "esm",
@@ -1,4 +1,4 @@
1
- /*! rnx v0.1.343 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
1
+ /*! rnx v0.1.345 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
2
2
  let __sootsim_import_meta_url = ''; try { __sootsim_import_meta_url = require('url').pathToFileURL(__filename).href; } catch {}
3
3
  "use strict";
4
4
  var __defProp = Object.defineProperty;
@@ -1,4 +1,4 @@
1
- /*! rnx v0.1.343 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
1
+ /*! rnx v0.1.345 | (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;
@@ -2879,19 +2879,183 @@ async function connect(options = {}) {
2879
2879
  }
2880
2880
 
2881
2881
  // src/vite-plugin-one.ts
2882
- var import_child_process2 = require("child_process");
2883
2882
  var import_fs4 = __toESM(require("fs"), 1);
2883
+ var import_node_crypto2 = require("node:crypto");
2884
2884
  var import_path3 = __toESM(require("path"), 1);
2885
- var import_url2 = require("url");
2885
+
2886
+ // src/dev-server-open.ts
2887
+ var import_node_child_process = require("node:child_process");
2888
+ var import_node_crypto = require("node:crypto");
2889
+ var import_node_fs3 = __toESM(require("node:fs"), 1);
2890
+ var import_node_http = __toESM(require("node:http"), 1);
2891
+ var import_node_path3 = __toESM(require("node:path"), 1);
2892
+ var import_node_url = require("node:url");
2893
+ var scheduledMetroOpens = /* @__PURE__ */ new Set();
2894
+ function resolveRnxDesktopAppName(open, projectRoot) {
2895
+ if (!open) return null;
2896
+ if (typeof open === "object") {
2897
+ const appName = open.appName.trim();
2898
+ if (!appName) throw new Error("rnx: open.appName must not be empty");
2899
+ return appName;
2900
+ }
2901
+ const packageJsonPath = import_node_path3.default.join(projectRoot, "package.json");
2902
+ const packageJson = JSON.parse(import_node_fs3.default.readFileSync(packageJsonPath, "utf8"));
2903
+ const packageName = packageJson && typeof packageJson === "object" ? Reflect.get(packageJson, "name") : void 0;
2904
+ if (typeof packageName !== "string" || !packageName.trim()) {
2905
+ throw new Error(`rnx: ${packageJsonPath} must declare a package name`);
2906
+ }
2907
+ return packageName.trim();
2908
+ }
2909
+ function tagRnxBundleForPlugin(bundleUrl, replacementOrigin, replacementToken, projectRoot, appName) {
2910
+ const absolute = /^[a-z][a-z\d+.-]*:\/\//i.test(bundleUrl);
2911
+ const url = new URL(bundleUrl, absolute ? void 0 : "http://rnx.local");
2912
+ if (appName) {
2913
+ url.searchParams.set("rnxAppName", appName);
2914
+ url.searchParams.set(
2915
+ "rnxProjectId",
2916
+ (0, import_node_crypto.createHash)("sha256").update(import_node_fs3.default.realpathSync(projectRoot)).digest("hex").slice(0, 32)
2917
+ );
2918
+ }
2919
+ url.searchParams.set("rnxReplacementOrigin", replacementOrigin);
2920
+ url.searchParams.set("rnxReplacementToken", replacementToken);
2921
+ return absolute ? url.toString() : `${url.pathname}${url.search}${url.hash}`;
2922
+ }
2923
+ function resolveRnxDesktopLaunchTarget(port, bundleUrl) {
2924
+ return new URL(bundleUrl, `http://localhost:${port}`).toString();
2925
+ }
2926
+ function buildRnxDesktopOpenArgs(bundleUrl) {
2927
+ return ["open", bundleUrl, "--new", "--driver", "electron", "--no-describe"];
2928
+ }
2929
+ function observeRnxDesktopLaunch(child, appName) {
2930
+ let stderr = "";
2931
+ let spawnFailed = false;
2932
+ child.stderr?.setEncoding("utf8");
2933
+ child.stderr?.on("data", (chunk) => {
2934
+ stderr = `${stderr}${chunk}`.slice(-16384);
2935
+ });
2936
+ child.once("error", (error) => {
2937
+ spawnFailed = true;
2938
+ console.error(`[rnx] could not open ${appName}: ${error.message}`);
2939
+ });
2940
+ child.once("close", (code, signal) => {
2941
+ if (spawnFailed) return;
2942
+ if (code === 0) {
2943
+ console.log(`[rnx] opened ${appName} in the desktop simulator`);
2944
+ return;
2945
+ }
2946
+ const detail = stderr.trim() || (signal ? `signal ${signal}` : `exit ${code}`);
2947
+ console.error(`[rnx] could not open ${appName}: ${detail}`);
2948
+ });
2949
+ }
2950
+ function launchRnxDesktop(bundleUrl, appName) {
2951
+ const packageRoot = import_node_path3.default.resolve(import_node_path3.default.dirname((0, import_node_url.fileURLToPath)(__sootsim_import_meta_url)), "..");
2952
+ const binPath = import_node_path3.default.join(packageRoot, "dist-cli", "bin.js");
2953
+ console.log(`[rnx] opening ${appName} in the desktop simulator`);
2954
+ const child = (0, import_node_child_process.spawn)(
2955
+ process.execPath,
2956
+ [binPath, ...buildRnxDesktopOpenArgs(bundleUrl)],
2957
+ {
2958
+ stdio: ["ignore", "ignore", "pipe"],
2959
+ windowsHide: true
2960
+ }
2961
+ );
2962
+ observeRnxDesktopLaunch(child, appName);
2963
+ }
2964
+ function openRnxDesktopWhenMetroIsReady({
2965
+ port,
2966
+ bundleUrl,
2967
+ appName,
2968
+ projectRoot,
2969
+ serverId,
2970
+ intervalMs = 100,
2971
+ timeoutMs = 3e4,
2972
+ launch = launchRnxDesktop
2973
+ }) {
2974
+ const key = `${projectRoot}\0${port}`;
2975
+ if (scheduledMetroOpens.has(key)) return Promise.resolve(false);
2976
+ scheduledMetroOpens.add(key);
2977
+ const deadline = Date.now() + timeoutMs;
2978
+ return new Promise((resolve2) => {
2979
+ let finished = false;
2980
+ const finish = (opened) => {
2981
+ if (finished) return;
2982
+ finished = true;
2983
+ scheduledMetroOpens.delete(key);
2984
+ resolve2(opened);
2985
+ };
2986
+ const retry = () => {
2987
+ if (finished) return;
2988
+ if (Date.now() >= deadline) {
2989
+ console.error(
2990
+ `[rnx] could not open ${appName}: Metro did not become ready on port ${port}`
2991
+ );
2992
+ finish(false);
2993
+ return;
2994
+ }
2995
+ const timer = setTimeout(check, intervalMs);
2996
+ timer.unref();
2997
+ };
2998
+ const check = () => {
2999
+ const request = import_node_http.default.get(
3000
+ `http://127.0.0.1:${port}/__server-scan`,
3001
+ { headers: { Accept: "application/json" } },
3002
+ (response) => {
3003
+ let body = "";
3004
+ response.setEncoding("utf8");
3005
+ response.on("data", (chunk) => {
3006
+ body = `${body}${chunk}`.slice(0, 65536);
3007
+ });
3008
+ response.once("end", () => {
3009
+ try {
3010
+ const scan = JSON.parse(body);
3011
+ const matches = response.statusCode === 200 && Array.isArray(scan) && scan.some((entry) => {
3012
+ if (!entry || typeof entry !== "object") return false;
3013
+ const scannedPort = Reflect.get(entry, "port");
3014
+ const projectName = Reflect.get(entry, "projectName");
3015
+ const scannedServerId = Reflect.get(entry, "rnxServerId");
3016
+ const scannedBundle = Reflect.get(entry, "bundleUrl");
3017
+ if (scannedPort !== port || projectName !== appName || scannedServerId !== serverId || typeof scannedBundle !== "string") {
3018
+ return false;
3019
+ }
3020
+ return new URL(scannedBundle).searchParams.get("rnxAppName") === appName;
3021
+ });
3022
+ if (matches) {
3023
+ launch(bundleUrl, appName);
3024
+ finish(true);
3025
+ return;
3026
+ }
3027
+ } catch {
3028
+ }
3029
+ retry();
3030
+ });
3031
+ }
3032
+ );
3033
+ request.setTimeout(Math.min(1e3, timeoutMs), () => request.destroy());
3034
+ request.once("error", retry);
3035
+ };
3036
+ check();
3037
+ });
3038
+ }
2886
3039
 
2887
3040
  // src/host/replacement-module-handler.ts
2888
3041
  var import_promises = __toESM(require("node:fs/promises"), 1);
2889
- var import_node_path3 = __toESM(require("node:path"), 1);
3042
+ var import_node_path4 = __toESM(require("node:path"), 1);
2890
3043
  var import_esbuild = require("esbuild");
3044
+
3045
+ // src/backend-origin.ts
3046
+ function normalizeHostname(hostname) {
3047
+ return hostname.replace(/^\[|\]$/g, "").toLowerCase();
3048
+ }
3049
+ function isLoopbackHost(hostname) {
3050
+ const normalized = normalizeHostname(hostname);
3051
+ return normalized === "localhost" || normalized.endsWith(".localhost") || normalized === "0.0.0.0" || normalized === "::1" || /^127(?:\.\d{1,3}){3}$/.test(normalized);
3052
+ }
3053
+
3054
+ // src/host/replacement-module-handler.ts
2891
3055
  var REPLACEMENT_MODULE_ROUTE = "/__sootsim-replacement-module";
2892
3056
  var REPLACEMENT_RUNTIME_PREFIX = "/__sootsim-replacement-runtime";
2893
3057
  function getReplacementLoader(filePath) {
2894
- const ext = import_node_path3.default.extname(filePath).toLowerCase();
3058
+ const ext = import_node_path4.default.extname(filePath).toLowerCase();
2895
3059
  switch (ext) {
2896
3060
  case ".js":
2897
3061
  return "js";
@@ -2988,12 +3152,30 @@ export const Appearance = rn.Appearance
2988
3152
  `;
2989
3153
  }
2990
3154
  function sendJavaScript(req, res, source) {
3155
+ const origin = req.headers.origin;
3156
+ let allowedOrigin;
3157
+ if (origin) {
3158
+ try {
3159
+ if (isLoopbackHost(new URL(origin).hostname)) allowedOrigin = origin;
3160
+ } catch {
3161
+ }
3162
+ }
2991
3163
  res.writeHead(200, {
2992
3164
  "Content-Type": "application/javascript; charset=utf-8",
2993
- "Cache-Control": "no-cache"
3165
+ "Cache-Control": "no-cache",
3166
+ ...allowedOrigin ? { "Access-Control-Allow-Origin": allowedOrigin, Vary: "Origin" } : {}
2994
3167
  });
2995
3168
  res.end(req.method === "HEAD" ? void 0 : source);
2996
3169
  }
3170
+ function isReplacementModuleRequestUrl(rawUrl) {
3171
+ if (!rawUrl) return false;
3172
+ try {
3173
+ const pathname = new URL(rawUrl, "http://rnx.local").pathname;
3174
+ return pathname === REPLACEMENT_MODULE_ROUTE || pathname.startsWith(`${REPLACEMENT_RUNTIME_PREFIX}/`);
3175
+ } catch {
3176
+ return false;
3177
+ }
3178
+ }
2997
3179
  async function handleReplacementModuleRequest(req, res, options = {}) {
2998
3180
  const rawUrl = req.url || "";
2999
3181
  let url;
@@ -3002,6 +3184,23 @@ async function handleReplacementModuleRequest(req, res, options = {}) {
3002
3184
  } catch {
3003
3185
  return false;
3004
3186
  }
3187
+ if (options.requireLoopbackOrigin) {
3188
+ const origin = req.headers.origin;
3189
+ const remoteAddress = req.socket.remoteAddress?.replace(/^::ffff:/, "");
3190
+ let allowed = Boolean(remoteAddress && isLoopbackHost(remoteAddress));
3191
+ if (allowed && origin) {
3192
+ try {
3193
+ allowed = isLoopbackHost(new URL(origin).hostname);
3194
+ } catch {
3195
+ allowed = false;
3196
+ }
3197
+ }
3198
+ if (!allowed) {
3199
+ res.writeHead(403, { "Content-Type": "text/plain; charset=utf-8" });
3200
+ res.end("replacement modules are available only to loopback origins");
3201
+ return true;
3202
+ }
3203
+ }
3005
3204
  if (url.pathname === `${REPLACEMENT_RUNTIME_PREFIX}/react`) {
3006
3205
  sendJavaScript(req, res, reactRuntimeSource(options.reactBridgeUrl));
3007
3206
  return true;
@@ -3011,6 +3210,11 @@ async function handleReplacementModuleRequest(req, res, options = {}) {
3011
3210
  return true;
3012
3211
  }
3013
3212
  if (url.pathname !== REPLACEMENT_MODULE_ROUTE) return false;
3213
+ if (options.requestToken && url.searchParams.get("token") !== options.requestToken) {
3214
+ res.writeHead(403, { "Content-Type": "text/plain; charset=utf-8" });
3215
+ res.end("replacement module request token is invalid");
3216
+ return true;
3217
+ }
3014
3218
  const targetPath = url.searchParams.get("path");
3015
3219
  if (!targetPath) {
3016
3220
  res.writeHead(400, { "Content-Type": "text/plain; charset=utf-8" });
@@ -3023,17 +3227,42 @@ async function handleReplacementModuleRequest(req, res, options = {}) {
3023
3227
  res.end("replacement files must be .js, .jsx, .ts, or .tsx");
3024
3228
  return true;
3025
3229
  }
3230
+ if (!import_node_path4.default.isAbsolute(targetPath)) {
3231
+ res.writeHead(400, { "Content-Type": "text/plain; charset=utf-8" });
3232
+ res.end("replacement file path must be absolute");
3233
+ return true;
3234
+ }
3235
+ let resolvedProjectRoot;
3236
+ if (options.projectRoot) {
3237
+ const lexicalProjectRoot = import_node_path4.default.resolve(options.projectRoot);
3238
+ resolvedProjectRoot = await import_promises.default.realpath(options.projectRoot);
3239
+ const lexicalTarget = import_node_path4.default.relative(lexicalProjectRoot, targetPath);
3240
+ if (lexicalTarget.startsWith("..") || import_node_path4.default.isAbsolute(lexicalTarget)) {
3241
+ res.writeHead(403, { "Content-Type": "text/plain; charset=utf-8" });
3242
+ res.end("replacement file must be inside the project root");
3243
+ return true;
3244
+ }
3245
+ }
3246
+ let resolvedTargetPath;
3026
3247
  try {
3027
- await import_promises.default.access(targetPath);
3248
+ resolvedTargetPath = await import_promises.default.realpath(targetPath);
3028
3249
  } catch (error) {
3029
3250
  const message = error instanceof Error ? error.message : String(error);
3030
3251
  res.writeHead(404, { "Content-Type": "text/plain; charset=utf-8" });
3031
3252
  res.end(`replacement file not found: ${message}`);
3032
3253
  return true;
3033
3254
  }
3255
+ if (resolvedProjectRoot) {
3256
+ const relativeTarget = import_node_path4.default.relative(resolvedProjectRoot, resolvedTargetPath);
3257
+ if (relativeTarget.startsWith("..") || import_node_path4.default.isAbsolute(relativeTarget)) {
3258
+ res.writeHead(403, { "Content-Type": "text/plain; charset=utf-8" });
3259
+ res.end("replacement file must be inside the project root");
3260
+ return true;
3261
+ }
3262
+ }
3034
3263
  try {
3035
3264
  const result = await (0, import_esbuild.build)({
3036
- entryPoints: [targetPath],
3265
+ entryPoints: [resolvedTargetPath],
3037
3266
  bundle: true,
3038
3267
  write: false,
3039
3268
  format: "esm",
@@ -3220,11 +3449,25 @@ function rnxPlugin(options = {}) {
3220
3449
  configureServer(server) {
3221
3450
  if (options.enabled === false) return;
3222
3451
  const bundleUrl = options.bundleUrl || "/node_modules/one/metro-entry.bundle?platform=ios&dev=true&minify=false";
3452
+ const appName = resolveRnxDesktopAppName(options.open, server.config.root);
3453
+ const replacementToken = (0, import_node_crypto2.randomUUID)();
3454
+ const configuredPort = server.config.server.port || 8081;
3455
+ const listeningPort = () => {
3456
+ const address = server.httpServer?.address();
3457
+ return address && typeof address === "object" ? address.port : configuredPort;
3458
+ };
3459
+ const bundleForPort = (port2) => tagRnxBundleForPlugin(
3460
+ bundleUrl,
3461
+ `http://localhost:${port2}`,
3462
+ replacementToken,
3463
+ server.config.root,
3464
+ appName
3465
+ );
3223
3466
  const buildShellHtml = (runtimeRoot) => {
3224
3467
  const html = import_fs4.default.readFileSync(import_path3.default.join(runtimeRoot, "index.html"), "utf8");
3225
3468
  const lock = readDaemonLockfile();
3226
3469
  const bridgePort = lock && isDaemonLockfileFresh(lock) && lock.bridgePort > 0 ? lock.bridgePort : DEFAULT_SOOTSIM_BRIDGE_PORT;
3227
- const inject = `<script>(function(){var u=new URL(location.href);if(!u.searchParams.has('bundle'))u.searchParams.set('bundle',${JSON.stringify(bundleUrl)});window.__sootsimBridgePort=${bridgePort};history.replaceState(null,'','${prefix2}/'+u.search)})()</script>`;
3470
+ const inject = `<script>(function(){var u=new URL(location.href);if(!u.searchParams.has('bundle'))u.searchParams.set('bundle',${JSON.stringify(bundleForPort(listeningPort()))});window.__sootsimBridgePort=${bridgePort};history.replaceState(null,'','${prefix2}/'+u.search)})()</script>`;
3228
3471
  return html.replace("</head>", inject + "</head>");
3229
3472
  };
3230
3473
  server.middlewares.use((req, res, next) => {
@@ -3245,8 +3488,9 @@ function rnxPlugin(options = {}) {
3245
3488
  });
3246
3489
  server.middlewares.use((req, res, next) => {
3247
3490
  if ((req.url || "").split("?")[0] !== "/__server-scan") return next();
3248
- const scanPort = server.config.server.port || 8081;
3249
- const absoluteBundleUrl = bundleUrl.startsWith("http") ? bundleUrl : `http://localhost:${scanPort}${bundleUrl}`;
3491
+ const scanPort = listeningPort();
3492
+ const pluginBundleUrl = bundleForPort(scanPort);
3493
+ const absoluteBundleUrl = pluginBundleUrl.startsWith("http") ? pluginBundleUrl : `http://localhost:${scanPort}${pluginBundleUrl}`;
3250
3494
  res.setHeader("content-type", "application/json");
3251
3495
  res.end(
3252
3496
  JSON.stringify([
@@ -3254,13 +3498,18 @@ function rnxPlugin(options = {}) {
3254
3498
  port: scanPort,
3255
3499
  framework: "one",
3256
3500
  bundleUrl: absoluteBundleUrl,
3501
+ ...appName ? { projectName: appName } : {},
3257
3502
  lastSeen: Date.now()
3258
3503
  }
3259
3504
  ])
3260
3505
  );
3261
3506
  });
3262
3507
  server.middlewares.use((req, res, next) => {
3263
- void handleReplacementModuleRequest(req, res).then((handled) => {
3508
+ void handleReplacementModuleRequest(req, res, {
3509
+ projectRoot: server.config.root,
3510
+ requestToken: replacementToken,
3511
+ requireLoopbackOrigin: true
3512
+ }).then((handled) => {
3264
3513
  if (!handled) next();
3265
3514
  });
3266
3515
  });
@@ -3307,23 +3556,16 @@ function rnxPlugin(options = {}) {
3307
3556
  }
3308
3557
  next();
3309
3558
  });
3310
- const port = server.config.server.port || 8081;
3559
+ const port = configuredPort;
3311
3560
  const sootsimUrl = `http://localhost:${port}${prefix2}/`;
3312
3561
  console.log(`[rnx] serving at ${sootsimUrl}`);
3313
- if (options.open && !process.env.CI && !process.env.SOOTSIM_NO_OPEN) {
3562
+ if (appName && !process.env.CI && !process.env.SOOTSIM_NO_OPEN) {
3314
3563
  server.httpServer?.once("listening", () => {
3315
- const packageRoot = import_path3.default.resolve(
3316
- import_path3.default.dirname((0, import_url2.fileURLToPath)(__sootsim_import_meta_url)),
3317
- ".."
3564
+ const openPort = listeningPort();
3565
+ launchRnxDesktop(
3566
+ resolveRnxDesktopLaunchTarget(openPort, bundleForPort(openPort)),
3567
+ appName
3318
3568
  );
3319
- const binPath = import_path3.default.join(packageRoot, "dist-cli", "bin.js");
3320
- const openPort = server.config.server.port || port;
3321
- console.log(`[rnx] opening simulator for :${openPort}`);
3322
- (0, import_child_process2.spawn)(process.execPath, [binPath, "open", String(openPort)], {
3323
- detached: true,
3324
- stdio: "ignore",
3325
- windowsHide: true
3326
- }).unref();
3327
3569
  });
3328
3570
  }
3329
3571
  }
@@ -3333,6 +3575,7 @@ function rnxPlugin(options = {}) {
3333
3575
  // src/metro-plugin.ts
3334
3576
  var import_fs5 = __toESM(require("fs"), 1);
3335
3577
  var import_module2 = require("module");
3578
+ var import_node_crypto3 = require("node:crypto");
3336
3579
  var import_path4 = __toESM(require("path"), 1);
3337
3580
 
3338
3581
  // src/metro-production-bundle.ts
@@ -3423,12 +3666,22 @@ function prependRNXModulePaths(result, graph, options, projectRoot) {
3423
3666
  return typeof result === "string" ? result + footer : { ...result, code: result.code + footer };
3424
3667
  }
3425
3668
  function withRNX(config, options = {}) {
3426
- if (!options.devServer && !options.productionBundles) return config;
3427
- const bundleUrl = options.bundleUrl || "/index.bundle?platform=ios&dev=true&hot=true&minify=false";
3669
+ if (!options.devServer && !options.open && !options.productionBundles) return config;
3670
+ const cfgProjectRoot = config.projectRoot || process.cwd();
3671
+ const appName = resolveRnxDesktopAppName(options.open, cfgProjectRoot);
3672
+ const configuredBundleUrl = options.bundleUrl || "/index.bundle?platform=ios&dev=true&hot=true&minify=false";
3428
3673
  const cfgServer = config.server;
3674
+ const port = resolveMetroPort(cfgServer);
3675
+ const metroServerId = (0, import_node_crypto3.randomUUID)();
3676
+ const bundleUrl = tagRnxBundleForPlugin(
3677
+ configuredBundleUrl,
3678
+ `http://localhost:${port}`,
3679
+ metroServerId,
3680
+ cfgProjectRoot,
3681
+ appName
3682
+ );
3429
3683
  const cfgResolver = config.resolver;
3430
3684
  const cfgSerializer = config.serializer;
3431
- const cfgProjectRoot = config.projectRoot || process.cwd();
3432
3685
  const existingEnhance = cfgServer?.enhanceMiddleware;
3433
3686
  const existingResolveRequest = cfgResolver?.resolveRequest;
3434
3687
  const existingSerializer = cfgSerializer?.customSerializer;
@@ -3485,8 +3738,35 @@ function withRNX(config, options = {}) {
3485
3738
  }
3486
3739
  const server = connect2();
3487
3740
  server.use((req, res, next) => {
3741
+ if (isReplacementModuleRequestUrl(req.url)) {
3742
+ void handleReplacementModuleRequest(req, res, {
3743
+ projectRoot: cfgProjectRoot,
3744
+ requestToken: metroServerId,
3745
+ requireLoopbackOrigin: true
3746
+ }).then((handled) => {
3747
+ if (!handled) next();
3748
+ });
3749
+ return;
3750
+ }
3488
3751
  const url = req.url || "";
3489
3752
  const pathname = url.split("?")[0];
3753
+ if (pathname === "/__server-scan") {
3754
+ const absoluteBundleUrl = bundleUrl.startsWith("http") ? bundleUrl : `http://localhost:${port}${bundleUrl}`;
3755
+ res.setHeader("content-type", "application/json");
3756
+ res.end(
3757
+ JSON.stringify([
3758
+ {
3759
+ port,
3760
+ framework: "metro",
3761
+ rnxServerId: metroServerId,
3762
+ bundleUrl: absoluteBundleUrl,
3763
+ ...appName ? { projectName: appName } : {},
3764
+ lastSeen: Date.now()
3765
+ }
3766
+ ])
3767
+ );
3768
+ return;
3769
+ }
3490
3770
  const runtimeRoot = resolveRuntimeRootForRequestHost(req.headers?.host);
3491
3771
  if (!runtimeRoot) {
3492
3772
  if (pathname === prefix || pathname === prefix + "/") {
@@ -3521,7 +3801,7 @@ function withRNX(config, options = {}) {
3521
3801
  let html = import_fs5.default.readFileSync(htmlPath, "utf8");
3522
3802
  html = html.replace(
3523
3803
  "</head>",
3524
- `<script>history.replaceState(null,'','${prefix}/?bundle=${encodeURIComponent(bundleUrl)}')</script></head>`
3804
+ `<script>(function(){var u=new URL(location.href);if(!u.searchParams.has('bundle'))u.searchParams.set('bundle',${JSON.stringify(bundleUrl)});history.replaceState(null,'','${prefix}/'+u.search)})()</script></head>`
3525
3805
  );
3526
3806
  res.setHeader("content-type", "text/html");
3527
3807
  res.end(html);
@@ -3535,22 +3815,42 @@ function withRNX(config, options = {}) {
3535
3815
  return server;
3536
3816
  }
3537
3817
  };
3818
+ if (appName && !process.env.CI && !process.env.SOOTSIM_NO_OPEN) {
3819
+ void openRnxDesktopWhenMetroIsReady({
3820
+ port,
3821
+ bundleUrl: resolveRnxDesktopLaunchTarget(port, bundleUrl),
3822
+ appName,
3823
+ projectRoot: cfgProjectRoot,
3824
+ serverId: metroServerId
3825
+ });
3826
+ }
3538
3827
  return {
3539
3828
  ...config,
3540
3829
  ...productionConfig,
3541
- ...options.devServer ? { server: serverConfig } : {}
3830
+ ...options.devServer || options.open ? { server: serverConfig } : {}
3542
3831
  };
3543
3832
  }
3833
+ function resolveMetroPort(server) {
3834
+ const inlinePort = process.argv.find((arg) => arg.startsWith("--port="))?.slice(7);
3835
+ const portIndex = process.argv.indexOf("--port");
3836
+ const splitPort = portIndex >= 0 ? process.argv[portIndex + 1] : void 0;
3837
+ const rawPort = inlinePort || splitPort || process.env.RCT_METRO_PORT || server?.port || 8081;
3838
+ const port = Number(rawPort);
3839
+ if (!Number.isInteger(port) || port <= 0) {
3840
+ throw new Error(`rnx: Metro port must be a positive integer, got ${rawPort}`);
3841
+ }
3842
+ return port;
3843
+ }
3544
3844
  var metro_plugin_default = withRNX;
3545
3845
 
3546
3846
  // src/jump-to-source-babel.ts
3547
- function normalizeSourcePath(path7) {
3548
- const queryStart = path7.indexOf("?");
3549
- const clean = queryStart >= 0 ? path7.slice(0, queryStart) : path7;
3847
+ function normalizeSourcePath(path8) {
3848
+ const queryStart = path8.indexOf("?");
3849
+ const clean = queryStart >= 0 ? path8.slice(0, queryStart) : path8;
3550
3850
  return clean.replace(/\\/g, "/");
3551
3851
  }
3552
- function matchesPath(path7, matcher) {
3553
- return typeof matcher === "string" ? path7.includes(matcher) : matcher.test(path7);
3852
+ function matchesPath(path8, matcher) {
3853
+ return typeof matcher === "string" ? path8.includes(matcher) : matcher.test(path8);
3554
3854
  }
3555
3855
  function shouldStampFile(filename, options) {
3556
3856
  const normalized = normalizeSourcePath(filename);
@@ -3583,11 +3883,11 @@ function sootsimJumpToSourceBabelPlugin(api, options = {}) {
3583
3883
  return {
3584
3884
  name: "sootsim-jump-to-source",
3585
3885
  visitor: {
3586
- JSXOpeningElement(path7, state) {
3886
+ JSXOpeningElement(path8, state) {
3587
3887
  if (options.enabled === false) return;
3588
3888
  const filename = state.file?.opts?.filename;
3589
3889
  if (!filename || !shouldStampFile(filename, options)) return;
3590
- const node = path7.node;
3890
+ const node = path8.node;
3591
3891
  const tagName = getJSXName(node.name);
3592
3892
  if (!tagName || tagName === "Fragment" || tagName.endsWith(".Fragment")) return;
3593
3893
  if (hasAttribute(node, attrName)) return;
@@ -1,4 +1,4 @@
1
- /*! rnx v0.1.343 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
1
+ /*! rnx v0.1.345 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
2
2
  let __sootsim_import_meta_url = ''; try { __sootsim_import_meta_url = require('url').pathToFileURL(__filename).href; } catch {}
3
3
  "use strict";
4
4
  var __defProp = Object.defineProperty;
package/dist-lib/menu.cjs CHANGED
@@ -1,4 +1,4 @@
1
- /*! rnx v0.1.343 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
1
+ /*! rnx v0.1.345 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
2
2
  let __sootsim_import_meta_url = ''; try { __sootsim_import_meta_url = require('url').pathToFileURL(__filename).href; } catch {}
3
3
  "use strict";
4
4
  var __defProp = Object.defineProperty;