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
package/dist-lib/vite.cjs CHANGED
@@ -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;
@@ -37,43 +37,119 @@ __export(vite_plugin_one_exports, {
37
37
  sootsimPlugin: () => sootsimPlugin
38
38
  });
39
39
  module.exports = __toCommonJS(vite_plugin_one_exports);
40
- var import_child_process = require("child_process");
41
40
  var import_fs2 = __toESM(require("fs"), 1);
41
+ var import_node_crypto2 = require("node:crypto");
42
42
  var import_path2 = __toESM(require("path"), 1);
43
- var import_url2 = require("url");
44
43
 
45
44
  // src/bridge-constants.ts
46
45
  var DEFAULT_SOOTSIM_BRIDGE_PORT = 7668;
47
46
  var DEFAULT_SOOTSIM_BRIDGE_URL = `http://localhost:${DEFAULT_SOOTSIM_BRIDGE_PORT}/`;
48
47
 
49
- // src/home-paths.ts
48
+ // src/dev-server-open.ts
49
+ var import_node_child_process = require("node:child_process");
50
+ var import_node_crypto = require("node:crypto");
50
51
  var import_node_fs = __toESM(require("node:fs"), 1);
51
- var import_node_os = require("node:os");
52
52
  var import_node_path = __toESM(require("node:path"), 1);
53
+ var import_node_url = require("node:url");
54
+ function resolveRnxDesktopAppName(open, projectRoot) {
55
+ if (!open) return null;
56
+ if (typeof open === "object") {
57
+ const appName = open.appName.trim();
58
+ if (!appName) throw new Error("rnx: open.appName must not be empty");
59
+ return appName;
60
+ }
61
+ const packageJsonPath = import_node_path.default.join(projectRoot, "package.json");
62
+ const packageJson = JSON.parse(import_node_fs.default.readFileSync(packageJsonPath, "utf8"));
63
+ const packageName = packageJson && typeof packageJson === "object" ? Reflect.get(packageJson, "name") : void 0;
64
+ if (typeof packageName !== "string" || !packageName.trim()) {
65
+ throw new Error(`rnx: ${packageJsonPath} must declare a package name`);
66
+ }
67
+ return packageName.trim();
68
+ }
69
+ function tagRnxBundleForPlugin(bundleUrl, replacementOrigin, replacementToken, projectRoot, appName) {
70
+ const absolute = /^[a-z][a-z\d+.-]*:\/\//i.test(bundleUrl);
71
+ const url = new URL(bundleUrl, absolute ? void 0 : "http://rnx.local");
72
+ if (appName) {
73
+ url.searchParams.set("rnxAppName", appName);
74
+ url.searchParams.set(
75
+ "rnxProjectId",
76
+ (0, import_node_crypto.createHash)("sha256").update(import_node_fs.default.realpathSync(projectRoot)).digest("hex").slice(0, 32)
77
+ );
78
+ }
79
+ url.searchParams.set("rnxReplacementOrigin", replacementOrigin);
80
+ url.searchParams.set("rnxReplacementToken", replacementToken);
81
+ return absolute ? url.toString() : `${url.pathname}${url.search}${url.hash}`;
82
+ }
83
+ function resolveRnxDesktopLaunchTarget(port, bundleUrl) {
84
+ return new URL(bundleUrl, `http://localhost:${port}`).toString();
85
+ }
86
+ function buildRnxDesktopOpenArgs(bundleUrl) {
87
+ return ["open", bundleUrl, "--new", "--driver", "electron", "--no-describe"];
88
+ }
89
+ function observeRnxDesktopLaunch(child, appName) {
90
+ let stderr = "";
91
+ let spawnFailed = false;
92
+ child.stderr?.setEncoding("utf8");
93
+ child.stderr?.on("data", (chunk) => {
94
+ stderr = `${stderr}${chunk}`.slice(-16384);
95
+ });
96
+ child.once("error", (error) => {
97
+ spawnFailed = true;
98
+ console.error(`[rnx] could not open ${appName}: ${error.message}`);
99
+ });
100
+ child.once("close", (code, signal) => {
101
+ if (spawnFailed) return;
102
+ if (code === 0) {
103
+ console.log(`[rnx] opened ${appName} in the desktop simulator`);
104
+ return;
105
+ }
106
+ const detail = stderr.trim() || (signal ? `signal ${signal}` : `exit ${code}`);
107
+ console.error(`[rnx] could not open ${appName}: ${detail}`);
108
+ });
109
+ }
110
+ function launchRnxDesktop(bundleUrl, appName) {
111
+ const packageRoot = import_node_path.default.resolve(import_node_path.default.dirname((0, import_node_url.fileURLToPath)(__sootsim_import_meta_url)), "..");
112
+ const binPath = import_node_path.default.join(packageRoot, "dist-cli", "bin.js");
113
+ console.log(`[rnx] opening ${appName} in the desktop simulator`);
114
+ const child = (0, import_node_child_process.spawn)(
115
+ process.execPath,
116
+ [binPath, ...buildRnxDesktopOpenArgs(bundleUrl)],
117
+ {
118
+ stdio: ["ignore", "ignore", "pipe"],
119
+ windowsHide: true
120
+ }
121
+ );
122
+ observeRnxDesktopLaunch(child, appName);
123
+ }
124
+
125
+ // src/home-paths.ts
126
+ var import_node_fs2 = __toESM(require("node:fs"), 1);
127
+ var import_node_os = require("node:os");
128
+ var import_node_path2 = __toESM(require("node:path"), 1);
53
129
  var SOOTSIM_HOME_ENV = "SOOTSIM_HOME";
54
130
  var ACTIVE_RUNTIME_FILE = "active";
55
131
  var DAEMON_LOCKFILE = "daemon.json";
56
132
  var DAEMON_HEARTBEAT_STALE_MS = 3e4;
57
133
  function sootsimHomeDir() {
58
134
  const override = process.env[SOOTSIM_HOME_ENV];
59
- if (override && override.length > 0) return import_node_path.default.resolve(override);
60
- return import_node_path.default.join((0, import_node_os.homedir)(), ".sootsim");
135
+ if (override && override.length > 0) return import_node_path2.default.resolve(override);
136
+ return import_node_path2.default.join((0, import_node_os.homedir)(), ".sootsim");
61
137
  }
62
138
  function runtimesDir() {
63
- return import_node_path.default.join(sootsimHomeDir(), "runtimes");
139
+ return import_node_path2.default.join(sootsimHomeDir(), "runtimes");
64
140
  }
65
141
  function runtimeDir(version) {
66
- return import_node_path.default.join(runtimesDir(), version);
142
+ return import_node_path2.default.join(runtimesDir(), version);
67
143
  }
68
144
  function activeRuntimeFile() {
69
- return import_node_path.default.join(runtimesDir(), ACTIVE_RUNTIME_FILE);
145
+ return import_node_path2.default.join(runtimesDir(), ACTIVE_RUNTIME_FILE);
70
146
  }
71
147
  function daemonLockfilePath() {
72
- return import_node_path.default.join(sootsimHomeDir(), DAEMON_LOCKFILE);
148
+ return import_node_path2.default.join(sootsimHomeDir(), DAEMON_LOCKFILE);
73
149
  }
74
150
  function readActiveRuntime() {
75
151
  try {
76
- const value = import_node_fs.default.readFileSync(activeRuntimeFile(), "utf8").trim();
152
+ const value = import_node_fs2.default.readFileSync(activeRuntimeFile(), "utf8").trim();
77
153
  return value.length > 0 ? value : null;
78
154
  } catch {
79
155
  return null;
@@ -82,10 +158,10 @@ function readActiveRuntime() {
82
158
  var DAEMON_LOCKFILE_MAX_BYTES = 16 * 1024;
83
159
  function readDaemonLockfile() {
84
160
  try {
85
- const fd = import_node_fs.default.openSync(daemonLockfilePath(), "r");
161
+ const fd = import_node_fs2.default.openSync(daemonLockfilePath(), "r");
86
162
  try {
87
163
  const buf = Buffer.alloc(DAEMON_LOCKFILE_MAX_BYTES);
88
- const bytesRead = import_node_fs.default.readSync(fd, buf, 0, DAEMON_LOCKFILE_MAX_BYTES, 0);
164
+ const bytesRead = import_node_fs2.default.readSync(fd, buf, 0, DAEMON_LOCKFILE_MAX_BYTES, 0);
89
165
  const raw = buf.subarray(0, bytesRead).toString("utf8");
90
166
  const parsed = JSON.parse(raw);
91
167
  if (parsed && parsed.schema === 1 && typeof parsed.pid === "number" && typeof parsed.bridgePort === "number" && typeof parsed.runtimePort === "number" && typeof parsed.startedAt === "number" && typeof parsed.heartbeatAt === "number" && (parsed.servedRuntimes === void 0 || Array.isArray(parsed.servedRuntimes) && parsed.servedRuntimes.every((version) => typeof version === "string"))) {
@@ -93,7 +169,7 @@ function readDaemonLockfile() {
93
169
  }
94
170
  return null;
95
171
  } finally {
96
- import_node_fs.default.closeSync(fd);
172
+ import_node_fs2.default.closeSync(fd);
97
173
  }
98
174
  } catch {
99
175
  return null;
@@ -112,12 +188,23 @@ function isDaemonLockfileFresh(lock, now = Date.now()) {
112
188
 
113
189
  // src/host/replacement-module-handler.ts
114
190
  var import_promises = __toESM(require("node:fs/promises"), 1);
115
- var import_node_path2 = __toESM(require("node:path"), 1);
191
+ var import_node_path3 = __toESM(require("node:path"), 1);
116
192
  var import_esbuild = require("esbuild");
193
+
194
+ // src/backend-origin.ts
195
+ function normalizeHostname(hostname) {
196
+ return hostname.replace(/^\[|\]$/g, "").toLowerCase();
197
+ }
198
+ function isLoopbackHost(hostname) {
199
+ const normalized = normalizeHostname(hostname);
200
+ return normalized === "localhost" || normalized.endsWith(".localhost") || normalized === "0.0.0.0" || normalized === "::1" || /^127(?:\.\d{1,3}){3}$/.test(normalized);
201
+ }
202
+
203
+ // src/host/replacement-module-handler.ts
117
204
  var REPLACEMENT_MODULE_ROUTE = "/__sootsim-replacement-module";
118
205
  var REPLACEMENT_RUNTIME_PREFIX = "/__sootsim-replacement-runtime";
119
206
  function getReplacementLoader(filePath) {
120
- const ext = import_node_path2.default.extname(filePath).toLowerCase();
207
+ const ext = import_node_path3.default.extname(filePath).toLowerCase();
121
208
  switch (ext) {
122
209
  case ".js":
123
210
  return "js";
@@ -214,9 +301,18 @@ export const Appearance = rn.Appearance
214
301
  `;
215
302
  }
216
303
  function sendJavaScript(req, res, source) {
304
+ const origin = req.headers.origin;
305
+ let allowedOrigin;
306
+ if (origin) {
307
+ try {
308
+ if (isLoopbackHost(new URL(origin).hostname)) allowedOrigin = origin;
309
+ } catch {
310
+ }
311
+ }
217
312
  res.writeHead(200, {
218
313
  "Content-Type": "application/javascript; charset=utf-8",
219
- "Cache-Control": "no-cache"
314
+ "Cache-Control": "no-cache",
315
+ ...allowedOrigin ? { "Access-Control-Allow-Origin": allowedOrigin, Vary: "Origin" } : {}
220
316
  });
221
317
  res.end(req.method === "HEAD" ? void 0 : source);
222
318
  }
@@ -228,6 +324,23 @@ async function handleReplacementModuleRequest(req, res, options = {}) {
228
324
  } catch {
229
325
  return false;
230
326
  }
327
+ if (options.requireLoopbackOrigin) {
328
+ const origin = req.headers.origin;
329
+ const remoteAddress = req.socket.remoteAddress?.replace(/^::ffff:/, "");
330
+ let allowed = Boolean(remoteAddress && isLoopbackHost(remoteAddress));
331
+ if (allowed && origin) {
332
+ try {
333
+ allowed = isLoopbackHost(new URL(origin).hostname);
334
+ } catch {
335
+ allowed = false;
336
+ }
337
+ }
338
+ if (!allowed) {
339
+ res.writeHead(403, { "Content-Type": "text/plain; charset=utf-8" });
340
+ res.end("replacement modules are available only to loopback origins");
341
+ return true;
342
+ }
343
+ }
231
344
  if (url.pathname === `${REPLACEMENT_RUNTIME_PREFIX}/react`) {
232
345
  sendJavaScript(req, res, reactRuntimeSource(options.reactBridgeUrl));
233
346
  return true;
@@ -237,6 +350,11 @@ async function handleReplacementModuleRequest(req, res, options = {}) {
237
350
  return true;
238
351
  }
239
352
  if (url.pathname !== REPLACEMENT_MODULE_ROUTE) return false;
353
+ if (options.requestToken && url.searchParams.get("token") !== options.requestToken) {
354
+ res.writeHead(403, { "Content-Type": "text/plain; charset=utf-8" });
355
+ res.end("replacement module request token is invalid");
356
+ return true;
357
+ }
240
358
  const targetPath = url.searchParams.get("path");
241
359
  if (!targetPath) {
242
360
  res.writeHead(400, { "Content-Type": "text/plain; charset=utf-8" });
@@ -249,17 +367,42 @@ async function handleReplacementModuleRequest(req, res, options = {}) {
249
367
  res.end("replacement files must be .js, .jsx, .ts, or .tsx");
250
368
  return true;
251
369
  }
370
+ if (!import_node_path3.default.isAbsolute(targetPath)) {
371
+ res.writeHead(400, { "Content-Type": "text/plain; charset=utf-8" });
372
+ res.end("replacement file path must be absolute");
373
+ return true;
374
+ }
375
+ let resolvedProjectRoot;
376
+ if (options.projectRoot) {
377
+ const lexicalProjectRoot = import_node_path3.default.resolve(options.projectRoot);
378
+ resolvedProjectRoot = await import_promises.default.realpath(options.projectRoot);
379
+ const lexicalTarget = import_node_path3.default.relative(lexicalProjectRoot, targetPath);
380
+ if (lexicalTarget.startsWith("..") || import_node_path3.default.isAbsolute(lexicalTarget)) {
381
+ res.writeHead(403, { "Content-Type": "text/plain; charset=utf-8" });
382
+ res.end("replacement file must be inside the project root");
383
+ return true;
384
+ }
385
+ }
386
+ let resolvedTargetPath;
252
387
  try {
253
- await import_promises.default.access(targetPath);
388
+ resolvedTargetPath = await import_promises.default.realpath(targetPath);
254
389
  } catch (error) {
255
390
  const message = error instanceof Error ? error.message : String(error);
256
391
  res.writeHead(404, { "Content-Type": "text/plain; charset=utf-8" });
257
392
  res.end(`replacement file not found: ${message}`);
258
393
  return true;
259
394
  }
395
+ if (resolvedProjectRoot) {
396
+ const relativeTarget = import_node_path3.default.relative(resolvedProjectRoot, resolvedTargetPath);
397
+ if (relativeTarget.startsWith("..") || import_node_path3.default.isAbsolute(relativeTarget)) {
398
+ res.writeHead(403, { "Content-Type": "text/plain; charset=utf-8" });
399
+ res.end("replacement file must be inside the project root");
400
+ return true;
401
+ }
402
+ }
260
403
  try {
261
404
  const result = await (0, import_esbuild.build)({
262
- entryPoints: [targetPath],
405
+ entryPoints: [resolvedTargetPath],
263
406
  bundle: true,
264
407
  write: false,
265
408
  format: "esm",
@@ -446,11 +589,25 @@ function rnxPlugin(options = {}) {
446
589
  configureServer(server) {
447
590
  if (options.enabled === false) return;
448
591
  const bundleUrl = options.bundleUrl || "/node_modules/one/metro-entry.bundle?platform=ios&dev=true&minify=false";
592
+ const appName = resolveRnxDesktopAppName(options.open, server.config.root);
593
+ const replacementToken = (0, import_node_crypto2.randomUUID)();
594
+ const configuredPort = server.config.server.port || 8081;
595
+ const listeningPort = () => {
596
+ const address = server.httpServer?.address();
597
+ return address && typeof address === "object" ? address.port : configuredPort;
598
+ };
599
+ const bundleForPort = (port2) => tagRnxBundleForPlugin(
600
+ bundleUrl,
601
+ `http://localhost:${port2}`,
602
+ replacementToken,
603
+ server.config.root,
604
+ appName
605
+ );
449
606
  const buildShellHtml = (runtimeRoot) => {
450
607
  const html = import_fs2.default.readFileSync(import_path2.default.join(runtimeRoot, "index.html"), "utf8");
451
608
  const lock = readDaemonLockfile();
452
609
  const bridgePort = lock && isDaemonLockfileFresh(lock) && lock.bridgePort > 0 ? lock.bridgePort : DEFAULT_SOOTSIM_BRIDGE_PORT;
453
- 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,'','${prefix}/'+u.search)})()</script>`;
610
+ 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,'','${prefix}/'+u.search)})()</script>`;
454
611
  return html.replace("</head>", inject + "</head>");
455
612
  };
456
613
  server.middlewares.use((req, res, next) => {
@@ -471,8 +628,9 @@ function rnxPlugin(options = {}) {
471
628
  });
472
629
  server.middlewares.use((req, res, next) => {
473
630
  if ((req.url || "").split("?")[0] !== "/__server-scan") return next();
474
- const scanPort = server.config.server.port || 8081;
475
- const absoluteBundleUrl = bundleUrl.startsWith("http") ? bundleUrl : `http://localhost:${scanPort}${bundleUrl}`;
631
+ const scanPort = listeningPort();
632
+ const pluginBundleUrl = bundleForPort(scanPort);
633
+ const absoluteBundleUrl = pluginBundleUrl.startsWith("http") ? pluginBundleUrl : `http://localhost:${scanPort}${pluginBundleUrl}`;
476
634
  res.setHeader("content-type", "application/json");
477
635
  res.end(
478
636
  JSON.stringify([
@@ -480,13 +638,18 @@ function rnxPlugin(options = {}) {
480
638
  port: scanPort,
481
639
  framework: "one",
482
640
  bundleUrl: absoluteBundleUrl,
641
+ ...appName ? { projectName: appName } : {},
483
642
  lastSeen: Date.now()
484
643
  }
485
644
  ])
486
645
  );
487
646
  });
488
647
  server.middlewares.use((req, res, next) => {
489
- void handleReplacementModuleRequest(req, res).then((handled) => {
648
+ void handleReplacementModuleRequest(req, res, {
649
+ projectRoot: server.config.root,
650
+ requestToken: replacementToken,
651
+ requireLoopbackOrigin: true
652
+ }).then((handled) => {
490
653
  if (!handled) next();
491
654
  });
492
655
  });
@@ -533,23 +696,16 @@ function rnxPlugin(options = {}) {
533
696
  }
534
697
  next();
535
698
  });
536
- const port = server.config.server.port || 8081;
699
+ const port = configuredPort;
537
700
  const sootsimUrl = `http://localhost:${port}${prefix}/`;
538
701
  console.log(`[rnx] serving at ${sootsimUrl}`);
539
- if (options.open && !process.env.CI && !process.env.SOOTSIM_NO_OPEN) {
702
+ if (appName && !process.env.CI && !process.env.SOOTSIM_NO_OPEN) {
540
703
  server.httpServer?.once("listening", () => {
541
- const packageRoot = import_path2.default.resolve(
542
- import_path2.default.dirname((0, import_url2.fileURLToPath)(__sootsim_import_meta_url)),
543
- ".."
704
+ const openPort = listeningPort();
705
+ launchRnxDesktop(
706
+ resolveRnxDesktopLaunchTarget(openPort, bundleForPort(openPort)),
707
+ appName
544
708
  );
545
- const binPath = import_path2.default.join(packageRoot, "dist-cli", "bin.js");
546
- const openPort = server.config.server.port || port;
547
- console.log(`[rnx] opening simulator for :${openPort}`);
548
- (0, import_child_process.spawn)(process.execPath, [binPath, "open", String(openPort)], {
549
- detached: true,
550
- stdio: "ignore",
551
- windowsHide: true
552
- }).unref();
553
709
  });
554
710
  }
555
711
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "rnxsim",
3
- "version": "0.1.344",
3
+ "version": "0.1.346",
4
4
  "description": "rnx CLI, Vite and Metro plugins, and agent skills for driving React Native apps in the browser.",
5
5
  "author": "Tamagui LLC",
6
6
  "license": "SEE LICENSE IN LICENSE",
@@ -200,6 +200,7 @@
200
200
  "@expo/plist": "^0.5.4",
201
201
  "esbuild": "^0.25.0",
202
202
  "jiti": "2.7.0",
203
+ "playwright-core": "1.61.0",
203
204
  "ws": "^8.18.0"
204
205
  },
205
206
  "peerDependencies": {