rnxsim 0.1.370 → 0.1.372
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.
- package/cli/bin.ts +2 -2
- package/cli/commands/box/checkout-plane.ts +65 -14
- package/cli/commands/box/cloud-box.ts +294 -0
- package/cli/commands/box/plane-share.ts +208 -0
- package/cli/commands/box/rnx-command.ts +2 -2
- package/cli/commands/box/search-command.ts +157 -0
- package/cli/commands/box.ts +269 -13
- package/cli/commands/control.ts +6 -1
- package/cli/outbound-endpoints.ts +37 -1
- package/dist-cli/bin.js +5 -5
- package/dist-cli/chunks/{agent-3R4424LW.js → agent-RTAKJ5UM.js} +2 -2
- package/dist-cli/chunks/{agent-wrapper-4PPWZWSY.js → agent-wrapper-JWRDI26D.js} +2 -2
- package/dist-cli/chunks/{app-fonts-KYXB6CXQ.js → app-fonts-RKO3N4PB.js} +2 -2
- package/dist-cli/chunks/{assert-7EZOWGYQ.js → assert-Y5LXPWC6.js} +2 -2
- package/dist-cli/chunks/{auth-L7M36H6C.js → auth-4SPTRWTM.js} +2 -2
- package/dist-cli/chunks/{beta-FKAF6YWJ.js → beta-KYS22QPY.js} +2 -2
- package/dist-cli/chunks/{box-5Y5VBMHV.js → box-7CK72ZPF.js} +617 -596
- package/dist-cli/chunks/{camera-XHF2RPFG.js → camera-RUL426MQ.js} +2 -2
- package/dist-cli/chunks/{chunk-EQ3OE2AV.js → chunk-2MA3Q44D.js} +1 -1
- package/dist-cli/chunks/{chunk-DHYTRCIP.js → chunk-2OA2WE7G.js} +2 -2
- package/dist-cli/chunks/{chunk-DMHSD6YJ.js → chunk-3C6LTIDV.js} +2 -2
- package/dist-cli/chunks/{chunk-NR2V2ONN.js → chunk-4UMODU6C.js} +2 -2
- package/dist-cli/chunks/{chunk-Z2VBSNFI.js → chunk-5Q6ZEZFF.js} +1 -1
- package/dist-cli/chunks/{chunk-LUOHRBJG.js → chunk-7FYOZQBM.js} +1 -1
- package/dist-cli/chunks/{chunk-Q4MRJ6PO.js → chunk-BRRWRBW7.js} +2 -2
- package/dist-cli/chunks/chunk-CEQXZ3GA.js +11 -0
- package/dist-cli/chunks/{chunk-DUXHYPBP.js → chunk-CZ2FOZVC.js} +2 -2
- package/dist-cli/chunks/{chunk-CMVRTZ5G.js → chunk-D5AT7UT5.js} +2 -2
- package/dist-cli/chunks/{chunk-D2GZASRV.js → chunk-DQOQQFMP.js} +1 -1
- package/dist-cli/chunks/{chunk-3UXXYJVD.js → chunk-EKSSAC4L.js} +1 -1
- package/dist-cli/chunks/{chunk-5BR33NAZ.js → chunk-EY5OHL2M.js} +1 -1
- package/dist-cli/chunks/{chunk-FK4JSTZ3.js → chunk-FOW2LPM7.js} +2 -2
- package/dist-cli/chunks/{chunk-WFQHT4QD.js → chunk-G5G7PQKO.js} +2 -2
- package/dist-cli/chunks/{chunk-WPV4H5S4.js → chunk-GPYG2L7R.js} +2 -2
- package/dist-cli/chunks/{chunk-3SOPYG7S.js → chunk-H2SKPUSI.js} +2 -2
- package/dist-cli/chunks/{chunk-6A6L3VJR.js → chunk-H567W7JT.js} +2 -2
- package/dist-cli/chunks/{chunk-PAUEZO6F.js → chunk-HDGZAVH3.js} +2 -2
- package/dist-cli/chunks/{chunk-AALKIAUZ.js → chunk-I37LRPZ3.js} +1 -1
- package/dist-cli/chunks/{chunk-JZLDHPB7.js → chunk-I7LRVN5M.js} +2 -2
- package/dist-cli/chunks/{chunk-3BG6UCQY.js → chunk-IX25FFF3.js} +11 -11
- package/dist-cli/chunks/{chunk-72S5FCBY.js → chunk-J2TJRPDB.js} +2 -2
- package/dist-cli/chunks/{chunk-RRX52O37.js → chunk-JPRHJMUJ.js} +1 -1
- package/dist-cli/chunks/{chunk-BMIJZNKD.js → chunk-K4TGUDY4.js} +2 -2
- package/dist-cli/chunks/{chunk-7C7TJYZ3.js → chunk-KSE5XGYU.js} +2 -2
- package/dist-cli/chunks/{chunk-OJZHECGH.js → chunk-KVVNCIJA.js} +1 -1
- package/dist-cli/chunks/{chunk-LXPHL6FO.js → chunk-KWLUOLYQ.js} +2 -2
- package/dist-cli/chunks/{chunk-C7BA5CCM.js → chunk-KXAE64HF.js} +2 -2
- package/dist-cli/chunks/{chunk-EWSWOWXK.js → chunk-L5BEWXQZ.js} +2 -2
- package/dist-cli/chunks/{chunk-D24EOJ4C.js → chunk-LAQKTHRM.js} +2 -2
- package/dist-cli/chunks/{chunk-IDLFVRPN.js → chunk-LWLHVF6Y.js} +1 -1
- package/dist-cli/chunks/{chunk-EOIBLKZO.js → chunk-MKHW3JNL.js} +2 -2
- package/dist-cli/chunks/{chunk-64JAEJ6N.js → chunk-MSM2E74F.js} +1 -1
- package/dist-cli/chunks/{chunk-DXV37CJN.js → chunk-NBEOA72C.js} +1 -1
- package/dist-cli/chunks/{chunk-H5X5FJ63.js → chunk-NLMDMNO6.js} +2 -2
- package/dist-cli/chunks/{chunk-6W4HIWC7.js → chunk-NT43X5LI.js} +2 -2
- package/dist-cli/chunks/{chunk-PNOENVMJ.js → chunk-NWPYM54M.js} +1 -1
- package/dist-cli/chunks/{chunk-O6QLKSAM.js → chunk-OLHHB6LD.js} +2 -2
- package/dist-cli/chunks/{chunk-MDCY532E.js → chunk-P6K6XFWE.js} +2 -2
- package/dist-cli/chunks/{chunk-LIMWG54N.js → chunk-PETP6LVI.js} +1 -1
- package/dist-cli/chunks/{chunk-YIZGNT7A.js → chunk-PKY76S57.js} +1 -1
- package/dist-cli/chunks/chunk-POR7R3TP.js +4 -0
- package/dist-cli/chunks/chunk-PUH6RQBJ.js +7 -0
- package/dist-cli/chunks/{chunk-NJMU5BN6.js → chunk-Q5WPPOFV.js} +1 -1
- package/dist-cli/chunks/{chunk-HQQTS2CB.js → chunk-Q7JSU5DI.js} +3 -3
- package/dist-cli/chunks/{chunk-U63T6LIQ.js → chunk-QA2PTSOC.js} +1 -1
- package/dist-cli/chunks/{chunk-2FOGXVOK.js → chunk-R7HMFLXI.js} +2 -2
- package/dist-cli/chunks/{chunk-FA75F34Z.js → chunk-RXKZJAGY.js} +1 -1
- package/dist-cli/chunks/{chunk-5RKRVS5A.js → chunk-SC7PGULZ.js} +2 -2
- package/dist-cli/chunks/{chunk-UFVAAWGZ.js → chunk-SLTEYNAE.js} +1 -1
- package/dist-cli/chunks/{chunk-6GPS7U4X.js → chunk-SSQCKJQZ.js} +2 -2
- package/dist-cli/chunks/{chunk-ARMXC3C3.js → chunk-SZX6ARJX.js} +3 -3
- package/dist-cli/chunks/{chunk-6S5LJV2O.js → chunk-U3XYGLXG.js} +2 -2
- package/dist-cli/chunks/chunk-UA5WQCBU.js +7 -0
- package/dist-cli/chunks/{chunk-VVQCZ42C.js → chunk-UVKX6FQ4.js} +1 -1
- package/dist-cli/chunks/{chunk-Z3DENGXW.js → chunk-VQQXUXXF.js} +2 -2
- package/dist-cli/chunks/{chunk-I52NT6VG.js → chunk-WR7LRCYW.js} +1 -1
- package/dist-cli/chunks/{chunk-HEKHLQ3B.js → chunk-XA4YU3A4.js} +3 -3
- package/dist-cli/chunks/{chunk-GRO74J2X.js → chunk-YEGWC2OY.js} +1 -1
- package/dist-cli/chunks/{chunk-AVJBT5LF.js → chunk-YWTBN7SD.js} +1 -1
- package/dist-cli/chunks/{chunk-PIZXOXBL.js → chunk-ZRQKEKHX.js} +2 -2
- package/dist-cli/chunks/{chunk-X6WQ4YWE.js → chunk-ZTZTRD5R.js} +1 -1
- package/dist-cli/chunks/{cleanup-MXMMJ7JW.js → cleanup-7ATBNZHC.js} +2 -2
- package/dist-cli/chunks/cli-version-D2IDQO7X.js +4 -0
- package/dist-cli/chunks/{compat-E6KE5KDI.js → compat-4ZC6ZMMA.js} +2 -2
- package/dist-cli/chunks/{config-QSLLVZDS.js → config-EOBODQ32.js} +2 -2
- package/dist-cli/chunks/{connect-QE4VU6GB.js → connect-ZQ72CZ7O.js} +2 -2
- package/dist-cli/chunks/control-FKPKLOE2.js +4 -0
- package/dist-cli/chunks/daemon-WFHWS3JU.js +4 -0
- package/dist-cli/chunks/{debug-5FG3MBJS.js → debug-7Z325GNU.js} +3 -3
- package/dist-cli/chunks/{desktop-5EZX7NX5.js → desktop-CR7ALX7H.js} +3 -3
- package/dist-cli/chunks/{detox-WJGQP5TM.js → detox-RZ37736M.js} +2 -2
- package/dist-cli/chunks/{device-D7LKCAMS.js → device-TA3SQP6K.js} +2 -2
- package/dist-cli/chunks/{diagnose-A3W6ZTGA.js → diagnose-JFKFKR3S.js} +2 -2
- package/dist-cli/chunks/disk-cleanup-HZM7VM4S.js +4 -0
- package/dist-cli/chunks/{do-chain-RUJNRPPF.js → do-chain-MTT65YNX.js} +2 -2
- package/dist-cli/chunks/drivers-BXEXWRVJ.js +4 -0
- package/dist-cli/chunks/{env-U67SQVTV.js → env-SFI24PLS.js} +2 -2
- package/dist-cli/chunks/{film-XC6UAQ3V.js → film-GPHSJLIH.js} +3 -3
- package/dist-cli/chunks/flow-FBCU3TZN.js +4 -0
- package/dist-cli/chunks/help-6MLROWYO.js +4 -0
- package/dist-cli/chunks/{hidden-runtime-alias-7S2TNZXG.js → hidden-runtime-alias-7Z6PYH53.js} +2 -2
- package/dist-cli/chunks/{home-paths-LMO5LB4F.js → home-paths-MEGVA6ET.js} +2 -2
- package/dist-cli/chunks/inspect-EWT5SXBD.js +4 -0
- package/dist-cli/chunks/install-desktop-IFBUH7MH.js +4 -0
- package/dist-cli/chunks/{login-JIU42S4C.js → login-EHUZYRAM.js} +2 -2
- package/dist-cli/chunks/{logout-JTAWQXZO.js → logout-WPGT62N6.js} +2 -2
- package/dist-cli/chunks/{maestro-TYBQYOGB.js → maestro-4HVDLCTI.js} +3 -3
- package/dist-cli/chunks/{maestro-generate-YLAGWVKE.js → maestro-generate-VBS2EZMM.js} +3 -3
- package/dist-cli/chunks/{mode-DV24ZRZ6.js → mode-MGTS4QTF.js} +2 -2
- package/dist-cli/chunks/{optional-demo-registry-JZH66VIM.js → optional-demo-registry-F3FQT5WT.js} +2 -2
- package/dist-cli/chunks/{perf-LXXJ4EAX.js → perf-47R3OEI7.js} +2 -2
- package/dist-cli/chunks/{permissions-KAOVZUPW.js → permissions-ZOLFZWR2.js} +2 -2
- package/dist-cli/chunks/{platform-KW35446W.js → platform-C4E26ZBZ.js} +3 -3
- package/dist-cli/chunks/privacy-WXGGG4ZI.js +4 -0
- package/dist-cli/chunks/{record-EK3ETFHZ.js → record-OUXWCWUF.js} +3 -3
- package/dist-cli/chunks/{report-issue-NW5PCAZF.js → report-issue-LJJXONXQ.js} +2 -2
- package/dist-cli/chunks/reset-ZZYLPETS.js +4 -0
- package/dist-cli/chunks/resolve-target-MVZZR2JW.js +4 -0
- package/dist-cli/chunks/runtime-DEC5AF3M.js +4 -0
- package/dist-cli/chunks/{screenshot-command-TQSPTFT2.js → screenshot-command-CUHDOS5D.js} +3 -3
- package/dist-cli/chunks/{screenshot-layers-IZP3L3AQ.js → screenshot-layers-DVWQVKT6.js} +2 -2
- package/dist-cli/chunks/{screenshots-capture-K7LJOVAE.js → screenshots-capture-GWIUJ3A6.js} +2 -2
- package/dist-cli/chunks/{serve-MMX4WXXG.js → serve-TX5OKGJM.js} +19 -19
- package/dist-cli/chunks/setup-X7LLGW67.js +4 -0
- package/dist-cli/chunks/{sim-client-KLAYIFOC.js → sim-client-W4WZOZ3P.js} +2 -2
- package/dist-cli/chunks/{skills-N4D3POIS.js → skills-TDFQAZ6A.js} +2 -2
- package/dist-cli/chunks/{state-ES6TFQZF.js → state-P2Z25OLQ.js} +2 -2
- package/dist-cli/chunks/{storage-52DR5V2C.js → storage-DWEOFXEB.js} +2 -2
- package/dist-cli/chunks/store-TKBLMYCV.js +4 -0
- package/dist-cli/chunks/telemetry-E3CBKJMD.js +4 -0
- package/dist-cli/chunks/{timeline-NRVNB5JR.js → timeline-55XNA4GX.js} +2 -2
- package/dist-cli/chunks/{upgrade-LNED5YQ2.js → upgrade-3XRCVDVC.js} +2 -2
- package/dist-cli/chunks/upload-QB77GSMQ.js +4 -0
- package/dist-cli/chunks/{version-VEF2DYGG.js → version-WTYGLXBJ.js} +2 -2
- package/dist-cli/chunks/{web-CGDE6S73.js → web-ZWRY3VHM.js} +2 -2
- package/dist-cli/chunks/welcome-7ATM5EGX.js +5 -0
- package/dist-cli/chunks/{what-happened-FSIDWL32.js → what-happened-Q3IGIH4K.js} +2 -2
- package/dist-cli/chunks/{ws-bridge-QXFQLRE7.js → ws-bridge-KCPZSBBX.js} +2 -2
- package/dist-lib/agent-daemon-client.cjs +1 -1
- package/dist-lib/agent-events.cjs +1 -1
- package/dist-lib/agent-identity.cjs +1 -1
- package/dist-lib/agent-sessions.cjs +1 -1
- package/dist-lib/attached-projects.cjs +1 -1
- package/dist-lib/auth/shared-session.cjs +1 -1
- package/dist-lib/backend-origin.cjs +1 -1
- package/dist-lib/beta.cjs +1 -1
- package/dist-lib/beta.mjs +1 -1
- package/dist-lib/bridge-constants.cjs +1 -1
- package/dist-lib/bridge-contract-input.cjs +1 -1
- package/dist-lib/bridge-contract-input.mjs +1 -1
- package/dist-lib/bridge-contract.cjs +1 -1
- package/dist-lib/bridge-contract.mjs +1 -1
- package/dist-lib/cli-constants.cjs +1 -1
- package/dist-lib/config.cjs +1 -1
- package/dist-lib/detox/index.cjs +1 -1
- package/dist-lib/dev-bundle-resolution.cjs +69 -34
- package/dist-lib/home-paths.cjs +1 -1
- package/dist-lib/host/bridge-host.cjs +430 -106
- package/dist-lib/host/fetch-proxy-handler.cjs +1 -1
- package/dist-lib/host/fetch-proxy-overrides.cjs +1 -1
- package/dist-lib/host/fetch-proxy-overrides.mjs +1 -1
- package/dist-lib/host/replacement-module-handler.cjs +1 -1
- package/dist-lib/host/websocket-proxy.cjs +1 -1
- package/dist-lib/index.cjs +1 -1
- package/dist-lib/jump-to-source-babel.cjs +1 -1
- package/dist-lib/menu.cjs +1 -1
- package/dist-lib/menu.mjs +1 -1
- package/dist-lib/metro-production-bundle.cjs +1 -1
- package/dist-lib/metro-production-bundle.mjs +1 -1
- package/dist-lib/metro.cjs +1 -1
- package/dist-lib/profiles.cjs +1 -1
- package/dist-lib/public-brand.cjs +1 -1
- package/dist-lib/react-native-host-modules.cjs +1 -1
- package/dist-lib/react-native-host-modules.mjs +1 -1
- package/dist-lib/render-mode.cjs +1 -1
- package/dist-lib/scripts/dev-server-scanner.cjs +149 -93
- package/dist-lib/sdk.cjs +1 -1
- package/dist-lib/sdk.mjs +1 -1
- package/dist-lib/skills.cjs +679 -307
- package/dist-lib/vite.cjs +1 -1
- package/package.json +1 -1
- package/scripts/dev-server-scanner.ts +275 -92
- package/src/dev-bundle-resolution.ts +123 -40
- package/src/disk-cleanup.ts +44 -0
- package/src/host/bridge-host.ts +11 -0
- package/src/host/plane-host.ts +203 -0
- package/dist-cli/chunks/chunk-GJRLPJQL.js +0 -6
- package/dist-cli/chunks/chunk-NTC46LSA.js +0 -4
- package/dist-cli/chunks/chunk-OTFTMGAL.js +0 -7
- package/dist-cli/chunks/cli-version-MLS55UTY.js +0 -4
- package/dist-cli/chunks/control-XD5F6K5T.js +0 -4
- package/dist-cli/chunks/daemon-UN5SE2JO.js +0 -4
- package/dist-cli/chunks/disk-cleanup-BDRHX5ZG.js +0 -4
- package/dist-cli/chunks/drivers-3NJ5RL6X.js +0 -4
- package/dist-cli/chunks/flow-FHZZD3P6.js +0 -4
- package/dist-cli/chunks/help-BYYDUSXF.js +0 -4
- package/dist-cli/chunks/inspect-RQCTWDCC.js +0 -4
- package/dist-cli/chunks/install-desktop-GOWXCQUE.js +0 -4
- package/dist-cli/chunks/privacy-MIFE72CG.js +0 -4
- package/dist-cli/chunks/reset-RZ4O5AFJ.js +0 -4
- package/dist-cli/chunks/resolve-target-7R5BNYMM.js +0 -4
- package/dist-cli/chunks/runtime-MZHLSRS6.js +0 -4
- package/dist-cli/chunks/setup-YFP3KEQZ.js +0 -4
- package/dist-cli/chunks/store-HOL45WCI.js +0 -4
- package/dist-cli/chunks/telemetry-BV6OTYPF.js +0 -4
- package/dist-cli/chunks/upload-RXKHFIBU.js +0 -4
- package/dist-cli/chunks/welcome-FT2AMUWW.js +0 -5
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
/*! rnx v0.1.
|
|
1
|
+
/*! rnx v0.1.372 | (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;
|
|
@@ -114,7 +114,6 @@ var import_promises2 = require("fs/promises");
|
|
|
114
114
|
var import_http = __toESM(require("http"), 1);
|
|
115
115
|
var import_net = __toESM(require("net"), 1);
|
|
116
116
|
var import_node_path3 = require("node:path");
|
|
117
|
-
var import_util = require("util");
|
|
118
117
|
|
|
119
118
|
// src/backend-origin.ts
|
|
120
119
|
var LOOPBACK_PROBE_HOSTNAMES = ["::1", "127.0.0.1"];
|
|
@@ -301,9 +300,9 @@ function safeChildPlistName(value) {
|
|
|
301
300
|
name: file.toLowerCase().endsWith(".plist") ? file : `${file}.plist`
|
|
302
301
|
};
|
|
303
302
|
}
|
|
304
|
-
async function readableSettingsBundle(
|
|
303
|
+
async function readableSettingsBundle(path12) {
|
|
305
304
|
try {
|
|
306
|
-
const root = await (0, import_promises.stat)((0, import_node_path.join)(
|
|
305
|
+
const root = await (0, import_promises.stat)((0, import_node_path.join)(path12, "Root.plist"));
|
|
307
306
|
return root.isFile() && root.size <= MAX_PLIST_BYTES;
|
|
308
307
|
} catch {
|
|
309
308
|
return false;
|
|
@@ -348,11 +347,11 @@ async function settingsBundleFingerprint(bundlePath) {
|
|
|
348
347
|
return null;
|
|
349
348
|
}
|
|
350
349
|
}
|
|
351
|
-
async function parsePlistRecord(
|
|
350
|
+
async function parsePlistRecord(path12) {
|
|
352
351
|
try {
|
|
353
|
-
const metadata = await (0, import_promises.stat)(
|
|
352
|
+
const metadata = await (0, import_promises.stat)(path12);
|
|
354
353
|
if (!metadata.isFile() || metadata.size > MAX_PLIST_BYTES) return null;
|
|
355
|
-
const source = await (0, import_promises.readFile)(
|
|
354
|
+
const source = await (0, import_promises.readFile)(path12, "utf8");
|
|
356
355
|
const parsed = parsePlistSource(source);
|
|
357
356
|
return isRecord2(parsed) ? parsed : null;
|
|
358
357
|
} catch {
|
|
@@ -538,10 +537,30 @@ async function readAppSettingsBundle(projectRoot) {
|
|
|
538
537
|
|
|
539
538
|
// scripts/dev-server-scanner.ts
|
|
540
539
|
init_optional_demo_registry();
|
|
541
|
-
var
|
|
540
|
+
var EXEC_EXIT_GRACE_MS = 2e3;
|
|
541
|
+
function execGuarded(command, timeout, extra = {}) {
|
|
542
|
+
let child;
|
|
543
|
+
const ran = new Promise((resolve2, reject) => {
|
|
544
|
+
child = (0, import_child_process.exec)(command, { encoding: "utf8", timeout, ...extra }, (err, stdout) => {
|
|
545
|
+
if (err) reject(err);
|
|
546
|
+
else resolve2({ stdout });
|
|
547
|
+
});
|
|
548
|
+
});
|
|
549
|
+
const deadline = new Promise((_resolve, reject) => {
|
|
550
|
+
const timer = setTimeout(() => {
|
|
551
|
+
child?.kill("SIGKILL");
|
|
552
|
+
const err = new Error(`\`${command}\` did not exit within ${timeout}ms`);
|
|
553
|
+
Object.assign(err, { killed: true });
|
|
554
|
+
reject(err);
|
|
555
|
+
}, timeout + EXEC_EXIT_GRACE_MS);
|
|
556
|
+
timer.unref?.();
|
|
557
|
+
});
|
|
558
|
+
return Promise.race([ran, deadline]);
|
|
559
|
+
}
|
|
542
560
|
var TIMEOUT_MS = 250;
|
|
543
561
|
var MANIFEST_TIMEOUT_MS = 1500;
|
|
544
562
|
var TCP_GATE_MS = 120;
|
|
563
|
+
var BODY_TIMEOUT_MS = 3e4;
|
|
545
564
|
async function tcpPing(port, timeout = TCP_GATE_MS) {
|
|
546
565
|
try {
|
|
547
566
|
return await Promise.any(
|
|
@@ -568,36 +587,46 @@ async function tcpPing(port, timeout = TCP_GATE_MS) {
|
|
|
568
587
|
return null;
|
|
569
588
|
}
|
|
570
589
|
}
|
|
571
|
-
function httpGet(hostname, port,
|
|
590
|
+
function httpGet(hostname, port, path12, method = "GET", timeout = TIMEOUT_MS, headers = {}) {
|
|
572
591
|
return new Promise((resolve2) => {
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
(
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
req.destroy();
|
|
598
|
-
resolve2(null);
|
|
592
|
+
let settled = false;
|
|
593
|
+
const settle = (value) => {
|
|
594
|
+
if (settled) return;
|
|
595
|
+
settled = true;
|
|
596
|
+
resolve2(value);
|
|
597
|
+
};
|
|
598
|
+
const req = import_http.default.request({ hostname, port, path: path12, method, headers }, (res) => {
|
|
599
|
+
let body = "";
|
|
600
|
+
res.on("data", (c) => body += c.toString());
|
|
601
|
+
const contentType = (() => {
|
|
602
|
+
const raw = res.headers["content-type"];
|
|
603
|
+
if (typeof raw === "string") return raw;
|
|
604
|
+
if (Array.isArray(raw)) return raw[0];
|
|
605
|
+
return void 0;
|
|
606
|
+
})();
|
|
607
|
+
res.on(
|
|
608
|
+
"end",
|
|
609
|
+
() => settle({
|
|
610
|
+
statusCode: res.statusCode || 0,
|
|
611
|
+
body,
|
|
612
|
+
contentType,
|
|
613
|
+
hostname
|
|
614
|
+
})
|
|
615
|
+
);
|
|
599
616
|
});
|
|
617
|
+
req.on("error", () => settle(null));
|
|
600
618
|
req.end();
|
|
619
|
+
const giveUp = setTimeout(() => {
|
|
620
|
+
settle(null);
|
|
621
|
+
req.socket?.unref();
|
|
622
|
+
}, timeout);
|
|
623
|
+
const abandon = setTimeout(() => req.destroy(), BODY_TIMEOUT_MS);
|
|
624
|
+
abandon.unref?.();
|
|
625
|
+
req.on("close", () => {
|
|
626
|
+
clearTimeout(giveUp);
|
|
627
|
+
clearTimeout(abandon);
|
|
628
|
+
setImmediate(() => settle(null));
|
|
629
|
+
});
|
|
601
630
|
});
|
|
602
631
|
}
|
|
603
632
|
var FALLBACK_PORTS = [
|
|
@@ -611,61 +640,83 @@ var FALLBACK_PORTS = [
|
|
|
611
640
|
3001,
|
|
612
641
|
19e3
|
|
613
642
|
].map((port) => ({ port, pid: 0 }));
|
|
643
|
+
var SELF_PORT_RANGE_START = 5170;
|
|
644
|
+
var SELF_PORT_RANGE_END = 5200;
|
|
645
|
+
function isOwnShellPort(port) {
|
|
646
|
+
const offset = Number(process.env.PORT_OFFSET) || 0;
|
|
647
|
+
if (port >= SELF_PORT_RANGE_START && port <= SELF_PORT_RANGE_END) return true;
|
|
648
|
+
return offset > 0 && port >= SELF_PORT_RANGE_START + offset && port <= SELF_PORT_RANGE_END + offset;
|
|
649
|
+
}
|
|
614
650
|
function acceptPort(port, excluded) {
|
|
615
651
|
if (port <= 0 || port >= 2e4) return false;
|
|
616
652
|
if (excluded.has(port)) return false;
|
|
617
|
-
if (port
|
|
653
|
+
if (isOwnShellPort(port)) return false;
|
|
618
654
|
return true;
|
|
619
655
|
}
|
|
620
|
-
|
|
656
|
+
var DevServerScanUnavailableError = class extends Error {
|
|
657
|
+
constructor() {
|
|
658
|
+
super("could not enumerate listening processes (lsof/ss unavailable or timed out)");
|
|
659
|
+
this.name = "DevServerScanUnavailableError";
|
|
660
|
+
}
|
|
661
|
+
};
|
|
662
|
+
var LISTING_TIMEOUT_MS = 1e4;
|
|
663
|
+
function wasKilledByTimeout(err) {
|
|
664
|
+
return !!(err && typeof err === "object" && err.killed);
|
|
665
|
+
}
|
|
666
|
+
async function scanListeningProcesses(excludePorts = []) {
|
|
621
667
|
const excluded = new Set(excludePorts);
|
|
668
|
+
let timedOut = false;
|
|
622
669
|
try {
|
|
623
|
-
const { stdout } = await
|
|
670
|
+
const { stdout } = await execGuarded(
|
|
624
671
|
`lsof -iTCP -sTCP:LISTEN -P -n 2>/dev/null | grep -E '^(node|bun)'`,
|
|
625
|
-
|
|
672
|
+
LISTING_TIMEOUT_MS
|
|
626
673
|
);
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
if (!seen.has(port)) seen.set(port, pid);
|
|
639
|
-
}
|
|
640
|
-
if (seen.size > 0) {
|
|
641
|
-
return [...seen.entries()].map(([port, pid]) => ({ port, pid }));
|
|
642
|
-
}
|
|
674
|
+
const seen = /* @__PURE__ */ new Map();
|
|
675
|
+
for (const line of stdout.trim() ? stdout.trim().split("\n") : []) {
|
|
676
|
+
const parts = line.trim().split(/\s+/);
|
|
677
|
+
if (parts.length < 9) continue;
|
|
678
|
+
const pid = Number(parts[1]);
|
|
679
|
+
const addr = parts[8];
|
|
680
|
+
const m = addr.match(/:(\d+)$/);
|
|
681
|
+
if (!m) continue;
|
|
682
|
+
const port = Number(m[1]);
|
|
683
|
+
if (!acceptPort(port, excluded)) continue;
|
|
684
|
+
if (!seen.has(port)) seen.set(port, pid);
|
|
643
685
|
}
|
|
644
|
-
|
|
686
|
+
return {
|
|
687
|
+
state: "looked",
|
|
688
|
+
processes: [...seen.entries()].map(([port, pid]) => ({ port, pid }))
|
|
689
|
+
};
|
|
690
|
+
} catch (err) {
|
|
691
|
+
if (wasKilledByTimeout(err)) timedOut = true;
|
|
645
692
|
}
|
|
646
693
|
try {
|
|
647
|
-
const { stdout } = await
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
if (!seen.has(port)) seen.set(port, pid);
|
|
661
|
-
}
|
|
662
|
-
if (seen.size > 0) {
|
|
663
|
-
return [...seen.entries()].map(([port, pid]) => ({ port, pid }));
|
|
664
|
-
}
|
|
694
|
+
const { stdout } = await execGuarded(
|
|
695
|
+
`ss -tlnp 2>/dev/null | grep -E '"(node|bun)"'`,
|
|
696
|
+
LISTING_TIMEOUT_MS
|
|
697
|
+
);
|
|
698
|
+
const seen = /* @__PURE__ */ new Map();
|
|
699
|
+
for (const line of stdout.trim() ? stdout.trim().split("\n") : []) {
|
|
700
|
+
const portMatch = line.match(/:(\d+)\s/);
|
|
701
|
+
const pidMatch = line.match(/pid=(\d+)/);
|
|
702
|
+
if (!portMatch) continue;
|
|
703
|
+
const port = Number(portMatch[1]);
|
|
704
|
+
const pid = pidMatch ? Number(pidMatch[1]) : 0;
|
|
705
|
+
if (!acceptPort(port, excluded)) continue;
|
|
706
|
+
if (!seen.has(port)) seen.set(port, pid);
|
|
665
707
|
}
|
|
666
|
-
|
|
708
|
+
return {
|
|
709
|
+
state: "looked",
|
|
710
|
+
processes: [...seen.entries()].map(([port, pid]) => ({ port, pid }))
|
|
711
|
+
};
|
|
712
|
+
} catch (err) {
|
|
713
|
+
if (wasKilledByTimeout(err)) timedOut = true;
|
|
667
714
|
}
|
|
668
|
-
|
|
715
|
+
if (timedOut) return { state: "could-not-look" };
|
|
716
|
+
return {
|
|
717
|
+
state: "looked",
|
|
718
|
+
processes: FALLBACK_PORTS.filter((p) => acceptPort(p.port, excluded))
|
|
719
|
+
};
|
|
669
720
|
}
|
|
670
721
|
function pidFromNetstat(stdout, port) {
|
|
671
722
|
for (const line of stdout.split("\n")) {
|
|
@@ -685,9 +736,7 @@ function pidFromNetstat(stdout, port) {
|
|
|
685
736
|
async function resolveListeningPid(port) {
|
|
686
737
|
if (!Number.isInteger(port) || port <= 0) return null;
|
|
687
738
|
try {
|
|
688
|
-
const { stdout } = await
|
|
689
|
-
encoding: "utf8",
|
|
690
|
-
timeout: 8e3,
|
|
739
|
+
const { stdout } = await execGuarded("netstat -anv -p tcp 2>/dev/null", 8e3, {
|
|
691
740
|
maxBuffer: 16 * 1024 * 1024
|
|
692
741
|
});
|
|
693
742
|
const pid = pidFromNetstat(stdout, port);
|
|
@@ -695,9 +744,7 @@ async function resolveListeningPid(port) {
|
|
|
695
744
|
} catch {
|
|
696
745
|
}
|
|
697
746
|
try {
|
|
698
|
-
const { stdout } = await
|
|
699
|
-
encoding: "utf8",
|
|
700
|
-
timeout: 8e3,
|
|
747
|
+
const { stdout } = await execGuarded(`ss -tlnp 2>/dev/null`, 8e3, {
|
|
701
748
|
maxBuffer: 16 * 1024 * 1024
|
|
702
749
|
});
|
|
703
750
|
for (const line of stdout.split("\n")) {
|
|
@@ -723,10 +770,10 @@ async function resolveProcessCwd(pid) {
|
|
|
723
770
|
} catch {
|
|
724
771
|
}
|
|
725
772
|
try {
|
|
726
|
-
const { stdout } = await
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
773
|
+
const { stdout } = await execGuarded(
|
|
774
|
+
`lsof -b -w -p ${pid} -a -d cwd -Fn 2>/dev/null`,
|
|
775
|
+
8e3
|
|
776
|
+
);
|
|
730
777
|
for (const line of stdout.split("\n")) {
|
|
731
778
|
if (line.startsWith("n") && line.length > 1) {
|
|
732
779
|
const cwd = line.slice(1).trim();
|
|
@@ -894,7 +941,8 @@ async function probePort(port, buildIconProxyUrl, listeningProcess) {
|
|
|
894
941
|
const manifestParsed = manifestRes ? safeParseManifest(manifestRes.body) : null;
|
|
895
942
|
const manifestLaunchUrl = typeof manifestParsed?.launchAsset?.url === "string" ? manifestParsed.launchAsset.url : null;
|
|
896
943
|
const manifestClient = manifestParsed?.extra?.expoClient || manifestParsed?.extra || {};
|
|
897
|
-
|
|
944
|
+
const manifestState = !manifestRes ? "no-answer" : manifestParsed && (manifestLaunchUrl || typeof manifestClient.name === "string") ? "resolved" : "no-manifest";
|
|
945
|
+
if (manifestState === "resolved") {
|
|
898
946
|
knownNonPatched.add(port);
|
|
899
947
|
const launchUrl2 = manifestLaunchUrl || `${buildLoopbackProbeBaseUrl("http:", probeHostname, port)}/index.bundle?platform=ios&dev=true&hot=true&minify=false`;
|
|
900
948
|
const framework = launchUrl2.includes(
|
|
@@ -915,17 +963,15 @@ async function probePort(port, buildIconProxyUrl, listeningProcess) {
|
|
|
915
963
|
}
|
|
916
964
|
if (statusRes && statusRes.body.includes("packager-status:running")) {
|
|
917
965
|
knownNonPatched.add(port);
|
|
966
|
+
const result = makeResult(
|
|
967
|
+
probeHostname,
|
|
968
|
+
port,
|
|
969
|
+
expoRes && expoRes.statusCode === 200 ? "expo" : "metro",
|
|
970
|
+
owner
|
|
971
|
+
);
|
|
972
|
+
if (manifestState === "no-answer") result.bundleUrlProvisional = true;
|
|
918
973
|
return enrichWithManifest(
|
|
919
|
-
applyManifest(
|
|
920
|
-
makeResult(
|
|
921
|
-
probeHostname,
|
|
922
|
-
port,
|
|
923
|
-
expoRes && expoRes.statusCode === 200 ? "expo" : "metro",
|
|
924
|
-
owner
|
|
925
|
-
),
|
|
926
|
-
manifestRes,
|
|
927
|
-
buildIconProxyUrl
|
|
928
|
-
),
|
|
974
|
+
applyManifest(result, manifestRes, buildIconProxyUrl),
|
|
929
975
|
buildIconProxyUrl
|
|
930
976
|
);
|
|
931
977
|
}
|
|
@@ -1021,7 +1067,9 @@ async function attachAppSettingsBundle(result, projectRoot) {
|
|
|
1021
1067
|
}
|
|
1022
1068
|
async function scanDevServers(opts = {}) {
|
|
1023
1069
|
await loadOptionalDemoApps();
|
|
1024
|
-
const
|
|
1070
|
+
const scan = await scanListeningProcesses(opts.excludePorts);
|
|
1071
|
+
if (scan.state === "could-not-look") throw new DevServerScanUnavailableError();
|
|
1072
|
+
const processes = scan.processes;
|
|
1025
1073
|
const currentPorts = new Set(processes.map((p) => p.port));
|
|
1026
1074
|
for (const p of [...portCache.keys()]) {
|
|
1027
1075
|
if (!currentPorts.has(p)) portCache.delete(p);
|
|
@@ -3708,13 +3756,283 @@ async function openUrl(url, options = {}) {
|
|
|
3708
3756
|
await launchUrl(url, options);
|
|
3709
3757
|
}
|
|
3710
3758
|
|
|
3759
|
+
// src/host/plane-host.ts
|
|
3760
|
+
var import_node_fs10 = __toESM(require("node:fs"), 1);
|
|
3761
|
+
var import_node_path11 = __toESM(require("node:path"), 1);
|
|
3762
|
+
var import_plane = require("@contrast/harness/plane");
|
|
3763
|
+
|
|
3764
|
+
// cli/commands/box/checkout-plane.ts
|
|
3765
|
+
var import_node_child_process4 = require("node:child_process");
|
|
3766
|
+
var import_node_fs9 = __toESM(require("node:fs"), 1);
|
|
3767
|
+
var import_node_path10 = __toESM(require("node:path"), 1);
|
|
3768
|
+
var CheckoutRequiresGitError = class extends Error {
|
|
3769
|
+
};
|
|
3770
|
+
var BINARY_SNIFF_BYTES = 8192;
|
|
3771
|
+
var LIST_FILES_TIMEOUT_MS = 1e4;
|
|
3772
|
+
function looksBinary(filePath, size) {
|
|
3773
|
+
if (size === 0) return false;
|
|
3774
|
+
let fd;
|
|
3775
|
+
try {
|
|
3776
|
+
fd = import_node_fs9.default.openSync(filePath, "r");
|
|
3777
|
+
const buffer = Buffer.alloc(Math.min(BINARY_SNIFF_BYTES, size));
|
|
3778
|
+
const read = import_node_fs9.default.readSync(fd, buffer, 0, buffer.length, 0);
|
|
3779
|
+
return buffer.subarray(0, read).includes(0);
|
|
3780
|
+
} catch {
|
|
3781
|
+
return false;
|
|
3782
|
+
} finally {
|
|
3783
|
+
if (fd !== void 0) import_node_fs9.default.closeSync(fd);
|
|
3784
|
+
}
|
|
3785
|
+
}
|
|
3786
|
+
var CheckoutFilePlane = class {
|
|
3787
|
+
root;
|
|
3788
|
+
entries = /* @__PURE__ */ new Map();
|
|
3789
|
+
sniffed = /* @__PURE__ */ new Map();
|
|
3790
|
+
// the deadline above, as a number a caller can set. what it changes is how
|
|
3791
|
+
// long a wedged git is waited on, never whether it is waited on, so proving
|
|
3792
|
+
// the kill does not have to cost the production deadline.
|
|
3793
|
+
listTimeoutMs;
|
|
3794
|
+
constructor(root, options = {}) {
|
|
3795
|
+
this.root = import_node_path10.default.resolve(root);
|
|
3796
|
+
this.listTimeoutMs = options.listTimeoutMs ?? LIST_FILES_TIMEOUT_MS;
|
|
3797
|
+
}
|
|
3798
|
+
index() {
|
|
3799
|
+
return this.entries;
|
|
3800
|
+
}
|
|
3801
|
+
async refresh() {
|
|
3802
|
+
let stdout;
|
|
3803
|
+
try {
|
|
3804
|
+
stdout = (0, import_node_child_process4.execFileSync)("git", ["ls-files", "-co", "--exclude-standard", "-z"], {
|
|
3805
|
+
cwd: this.root,
|
|
3806
|
+
encoding: "utf8",
|
|
3807
|
+
maxBuffer: 64 * 1024 * 1024,
|
|
3808
|
+
timeout: this.listTimeoutMs,
|
|
3809
|
+
// stating the environment is not redundant here. bun 1.3.14 resolves
|
|
3810
|
+
// the binary for the synchronous form against the PATH it captured at
|
|
3811
|
+
// startup and ignores a later change to `process.env.PATH`, so without
|
|
3812
|
+
// this it runs a different git than every other call in the process.
|
|
3813
|
+
env: process.env
|
|
3814
|
+
});
|
|
3815
|
+
} catch (error) {
|
|
3816
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
3817
|
+
const killed = error instanceof Error && "signal" in error && error.signal === "SIGTERM";
|
|
3818
|
+
throw new CheckoutRequiresGitError(
|
|
3819
|
+
killed ? `git did not list the project's files in ${this.root} within ${this.listTimeoutMs / 1e3}s, so the box has no file set to serve. the checkout may be on a stalled filesystem, or this machine may be too loaded to start a subprocess.` : `could not list the project's files with git in ${this.root}: ${message}`
|
|
3820
|
+
);
|
|
3821
|
+
}
|
|
3822
|
+
if (stdout.length === 0) {
|
|
3823
|
+
throw new CheckoutRequiresGitError(
|
|
3824
|
+
`git listed no files in ${this.root}. either git tracks nothing there and nothing untracked is visible to it, or this machine could not read the child process's output.`
|
|
3825
|
+
);
|
|
3826
|
+
}
|
|
3827
|
+
const next = /* @__PURE__ */ new Map();
|
|
3828
|
+
const nextSniffed = /* @__PURE__ */ new Map();
|
|
3829
|
+
for (const relative of stdout.split("\0")) {
|
|
3830
|
+
if (!relative) continue;
|
|
3831
|
+
let stat2;
|
|
3832
|
+
try {
|
|
3833
|
+
stat2 = import_node_fs9.default.statSync(import_node_path10.default.join(this.root, relative));
|
|
3834
|
+
} catch {
|
|
3835
|
+
continue;
|
|
3836
|
+
}
|
|
3837
|
+
if (!stat2.isFile()) continue;
|
|
3838
|
+
const seen = this.sniffed.get(relative);
|
|
3839
|
+
const binary = seen && seen.size === stat2.size && seen.mtimeMs === stat2.mtimeMs ? seen.binary : looksBinary(import_node_path10.default.join(this.root, relative), stat2.size);
|
|
3840
|
+
nextSniffed.set(relative, { size: stat2.size, mtimeMs: stat2.mtimeMs, binary });
|
|
3841
|
+
next.set(relative, {
|
|
3842
|
+
size: stat2.size,
|
|
3843
|
+
// a checkout carries no write policy of its own; if the user can write
|
|
3844
|
+
// the file, so can the shell
|
|
3845
|
+
readOnly: false,
|
|
3846
|
+
binary
|
|
3847
|
+
});
|
|
3848
|
+
}
|
|
3849
|
+
this.entries = next;
|
|
3850
|
+
this.sniffed = nextSniffed;
|
|
3851
|
+
}
|
|
3852
|
+
absolute(planePath) {
|
|
3853
|
+
const resolved = import_node_path10.default.resolve(this.root, planePath);
|
|
3854
|
+
if (resolved !== this.root && !resolved.startsWith(`${this.root}${import_node_path10.default.sep}`)) {
|
|
3855
|
+
throw new Error(`path escapes the project: ${planePath}`);
|
|
3856
|
+
}
|
|
3857
|
+
return resolved;
|
|
3858
|
+
}
|
|
3859
|
+
async read(planePath) {
|
|
3860
|
+
return import_node_fs9.default.promises.readFile(this.absolute(planePath), "utf8");
|
|
3861
|
+
}
|
|
3862
|
+
async readBytes(planePath) {
|
|
3863
|
+
return import_node_fs9.default.promises.readFile(this.absolute(planePath));
|
|
3864
|
+
}
|
|
3865
|
+
async write(planePath, content) {
|
|
3866
|
+
const absolute = this.absolute(planePath);
|
|
3867
|
+
await import_node_fs9.default.promises.mkdir(import_node_path10.default.dirname(absolute), { recursive: true });
|
|
3868
|
+
await import_node_fs9.default.promises.writeFile(absolute, content);
|
|
3869
|
+
this.entries.set(planePath, {
|
|
3870
|
+
size: content.byteLength,
|
|
3871
|
+
readOnly: false,
|
|
3872
|
+
// the bytes are in hand, so the same NUL test costs nothing here
|
|
3873
|
+
binary: content.subarray(0, BINARY_SNIFF_BYTES).includes(0)
|
|
3874
|
+
});
|
|
3875
|
+
}
|
|
3876
|
+
async delete(planePath) {
|
|
3877
|
+
await import_node_fs9.default.promises.rm(this.absolute(planePath), { force: true });
|
|
3878
|
+
this.entries.delete(planePath);
|
|
3879
|
+
}
|
|
3880
|
+
};
|
|
3881
|
+
|
|
3882
|
+
// src/host/plane-host.ts
|
|
3883
|
+
var WS_OPEN2 = 1;
|
|
3884
|
+
var PlaneNotRegisteredError = class extends Error {
|
|
3885
|
+
};
|
|
3886
|
+
var PlaneHost = class {
|
|
3887
|
+
// roots a box authorized, each tied to the socket that authorized it
|
|
3888
|
+
registered = /* @__PURE__ */ new Map();
|
|
3889
|
+
open = /* @__PURE__ */ new Map();
|
|
3890
|
+
byRoot = /* @__PURE__ */ new Map();
|
|
3891
|
+
nextPlaneId = 1;
|
|
3892
|
+
/** handle a plane:* message. returns true iff it was one. */
|
|
3893
|
+
async handleMessage(ws, msg) {
|
|
3894
|
+
if (typeof msg !== "object" || msg === null) return false;
|
|
3895
|
+
const record = { ...msg };
|
|
3896
|
+
const type = record.type;
|
|
3897
|
+
if (typeof type !== "string" || !type.startsWith("plane:")) return false;
|
|
3898
|
+
const id = record.id;
|
|
3899
|
+
try {
|
|
3900
|
+
this.respond(ws, id, await this.dispatch(ws, type, record));
|
|
3901
|
+
} catch (err) {
|
|
3902
|
+
const code = err instanceof PlaneNotRegisteredError ? "plane-not-registered" : void 0;
|
|
3903
|
+
this.respondError(ws, id, err instanceof Error ? err.message : String(err), code);
|
|
3904
|
+
}
|
|
3905
|
+
return true;
|
|
3906
|
+
}
|
|
3907
|
+
unregisterSocket(ws) {
|
|
3908
|
+
for (const [root, sockets] of [...this.registered]) {
|
|
3909
|
+
if (!sockets.delete(ws)) continue;
|
|
3910
|
+
if (sockets.size > 0) continue;
|
|
3911
|
+
this.registered.delete(root);
|
|
3912
|
+
const planeId = this.byRoot.get(root);
|
|
3913
|
+
if (planeId) {
|
|
3914
|
+
this.open.delete(planeId);
|
|
3915
|
+
this.byRoot.delete(root);
|
|
3916
|
+
}
|
|
3917
|
+
}
|
|
3918
|
+
for (const [planeId, entry] of [...this.open]) {
|
|
3919
|
+
entry.sockets.delete(ws);
|
|
3920
|
+
if (entry.sockets.size === 0 && !this.registered.has(entry.root)) {
|
|
3921
|
+
this.open.delete(planeId);
|
|
3922
|
+
this.byRoot.delete(entry.root);
|
|
3923
|
+
}
|
|
3924
|
+
}
|
|
3925
|
+
}
|
|
3926
|
+
/** roots currently reachable, for the daemon's status output. */
|
|
3927
|
+
registeredRoots() {
|
|
3928
|
+
return [...this.registered.keys()];
|
|
3929
|
+
}
|
|
3930
|
+
async dispatch(ws, type, msg) {
|
|
3931
|
+
switch (type) {
|
|
3932
|
+
case "plane:register": {
|
|
3933
|
+
const root = this.realRoot(String(msg.root ?? ""));
|
|
3934
|
+
const sockets = this.registered.get(root) ?? /* @__PURE__ */ new Set();
|
|
3935
|
+
sockets.add(ws);
|
|
3936
|
+
this.registered.set(root, sockets);
|
|
3937
|
+
return { root, holders: sockets.size };
|
|
3938
|
+
}
|
|
3939
|
+
// READ ONLY, and that is the whole point of it existing beside the
|
|
3940
|
+
// `holders` field on plane:register.
|
|
3941
|
+
//
|
|
3942
|
+
// registration is the AUTHORIZATION step: it is what permits any page on
|
|
3943
|
+
// this machine to open that root over a loopback socket that CORS does
|
|
3944
|
+
// not protect. so a caller asking "is anyone else in this checkout"
|
|
3945
|
+
// cannot be made to authorize the root in order to find out, even
|
|
3946
|
+
// briefly. it also means the count is the honest one: no box reads 0 and
|
|
3947
|
+
// a single box reads 1, where asking through register would have made
|
|
3948
|
+
// every answer one higher and forced a threshold that counts the asker.
|
|
3949
|
+
case "plane:status": {
|
|
3950
|
+
const root = this.realRoot(String(msg.root ?? ""));
|
|
3951
|
+
const sockets = this.registered.get(root);
|
|
3952
|
+
return { root, holders: sockets?.size ?? 0, registered: Boolean(sockets) };
|
|
3953
|
+
}
|
|
3954
|
+
case "plane:open": {
|
|
3955
|
+
const root = this.realRoot(String(msg.root ?? ""));
|
|
3956
|
+
if (!this.registered.has(root)) {
|
|
3957
|
+
throw new PlaneNotRegisteredError(
|
|
3958
|
+
`no box has registered ${root}; start one with \`rnx box\` there`
|
|
3959
|
+
);
|
|
3960
|
+
}
|
|
3961
|
+
const existingId = this.byRoot.get(root);
|
|
3962
|
+
const existing = existingId ? this.open.get(existingId) : void 0;
|
|
3963
|
+
if (existingId && existing) {
|
|
3964
|
+
existing.sockets.add(ws);
|
|
3965
|
+
return { planeId: existingId, version: existing.version };
|
|
3966
|
+
}
|
|
3967
|
+
const plane = new CheckoutFilePlane(root);
|
|
3968
|
+
await plane.refresh();
|
|
3969
|
+
const planeId = `plane-${this.nextPlaneId++}`;
|
|
3970
|
+
const entry = {
|
|
3971
|
+
root,
|
|
3972
|
+
plane,
|
|
3973
|
+
version: (0, import_plane.indexVersion)(plane.index()),
|
|
3974
|
+
sockets: /* @__PURE__ */ new Set([ws])
|
|
3975
|
+
};
|
|
3976
|
+
this.open.set(planeId, entry);
|
|
3977
|
+
this.byRoot.set(root, planeId);
|
|
3978
|
+
return { planeId, version: entry.version };
|
|
3979
|
+
}
|
|
3980
|
+
case "plane:close": {
|
|
3981
|
+
const entry = this.open.get(String(msg.planeId ?? ""));
|
|
3982
|
+
if (entry) entry.sockets.delete(ws);
|
|
3983
|
+
return { ok: true };
|
|
3984
|
+
}
|
|
3985
|
+
default: {
|
|
3986
|
+
const entry = this.entry(msg);
|
|
3987
|
+
const handled = await (0, import_plane.dispatchPlaneCommand)(entry.plane, type, msg);
|
|
3988
|
+
if (!handled) throw new Error(`unknown plane command: ${type}`);
|
|
3989
|
+
if (handled.version) entry.version = handled.version;
|
|
3990
|
+
return handled.result;
|
|
3991
|
+
}
|
|
3992
|
+
}
|
|
3993
|
+
}
|
|
3994
|
+
entry(msg) {
|
|
3995
|
+
const entry = this.open.get(String(msg.planeId ?? ""));
|
|
3996
|
+
if (!entry) throw new Error(`unknown planeId: ${String(msg.planeId)}`);
|
|
3997
|
+
return entry;
|
|
3998
|
+
}
|
|
3999
|
+
// resolve symlinks and relative segments before comparing, so a registered
|
|
4000
|
+
// root and an opened one cannot disagree about the same directory.
|
|
4001
|
+
realRoot(root) {
|
|
4002
|
+
if (!root) throw new Error("root is required");
|
|
4003
|
+
const resolved = import_node_path11.default.resolve(root);
|
|
4004
|
+
let real;
|
|
4005
|
+
try {
|
|
4006
|
+
real = import_node_fs10.default.realpathSync(resolved);
|
|
4007
|
+
} catch {
|
|
4008
|
+
throw new Error(`no such directory: ${resolved}`);
|
|
4009
|
+
}
|
|
4010
|
+
if (!import_node_fs10.default.statSync(real).isDirectory()) throw new Error(`not a directory: ${real}`);
|
|
4011
|
+
return real;
|
|
4012
|
+
}
|
|
4013
|
+
respond(ws, id, result) {
|
|
4014
|
+
if (ws.readyState !== WS_OPEN2) return;
|
|
4015
|
+
try {
|
|
4016
|
+
ws.send(JSON.stringify({ id, result }));
|
|
4017
|
+
} catch {
|
|
4018
|
+
}
|
|
4019
|
+
}
|
|
4020
|
+
respondError(ws, id, error, code) {
|
|
4021
|
+
if (ws.readyState !== WS_OPEN2) return;
|
|
4022
|
+
try {
|
|
4023
|
+
ws.send(JSON.stringify({ id, error, ...code ? { code } : {} }));
|
|
4024
|
+
} catch {
|
|
4025
|
+
}
|
|
4026
|
+
}
|
|
4027
|
+
};
|
|
4028
|
+
|
|
3711
4029
|
// src/host/replacement-module-handler.ts
|
|
3712
4030
|
var import_promises5 = __toESM(require("node:fs/promises"), 1);
|
|
3713
|
-
var
|
|
4031
|
+
var import_node_path12 = __toESM(require("node:path"), 1);
|
|
3714
4032
|
var import_esbuild = require("esbuild");
|
|
3715
4033
|
var REPLACEMENT_MODULE_ROUTE = "/__sootsim-replacement-module";
|
|
3716
4034
|
function getReplacementLoader(filePath) {
|
|
3717
|
-
const ext =
|
|
4035
|
+
const ext = import_node_path12.default.extname(filePath).toLowerCase();
|
|
3718
4036
|
switch (ext) {
|
|
3719
4037
|
case ".js":
|
|
3720
4038
|
return "js";
|
|
@@ -3796,7 +4114,7 @@ async function handleReplacementModuleRequest(req, res, options = {}) {
|
|
|
3796
4114
|
res.end("replacement files must be .js, .jsx, .ts, or .tsx");
|
|
3797
4115
|
return true;
|
|
3798
4116
|
}
|
|
3799
|
-
if (!
|
|
4117
|
+
if (!import_node_path12.default.isAbsolute(targetPath)) {
|
|
3800
4118
|
res.writeHead(400, { "Content-Type": "text/plain; charset=utf-8" });
|
|
3801
4119
|
res.end("replacement file path must be absolute");
|
|
3802
4120
|
return true;
|
|
@@ -3804,10 +4122,10 @@ async function handleReplacementModuleRequest(req, res, options = {}) {
|
|
|
3804
4122
|
const runtimePackages = new Set(url.searchParams.getAll("runtimePackage"));
|
|
3805
4123
|
let resolvedProjectRoot;
|
|
3806
4124
|
if (options.projectRoot) {
|
|
3807
|
-
const lexicalProjectRoot =
|
|
4125
|
+
const lexicalProjectRoot = import_node_path12.default.resolve(options.projectRoot);
|
|
3808
4126
|
resolvedProjectRoot = await import_promises5.default.realpath(options.projectRoot);
|
|
3809
|
-
const lexicalTarget =
|
|
3810
|
-
if (lexicalTarget.startsWith("..") ||
|
|
4127
|
+
const lexicalTarget = import_node_path12.default.relative(lexicalProjectRoot, targetPath);
|
|
4128
|
+
if (lexicalTarget.startsWith("..") || import_node_path12.default.isAbsolute(lexicalTarget)) {
|
|
3811
4129
|
res.writeHead(403, { "Content-Type": "text/plain; charset=utf-8" });
|
|
3812
4130
|
res.end("replacement file must be inside the project root");
|
|
3813
4131
|
return true;
|
|
@@ -3823,8 +4141,8 @@ async function handleReplacementModuleRequest(req, res, options = {}) {
|
|
|
3823
4141
|
return true;
|
|
3824
4142
|
}
|
|
3825
4143
|
if (resolvedProjectRoot) {
|
|
3826
|
-
const relativeTarget =
|
|
3827
|
-
if (relativeTarget.startsWith("..") ||
|
|
4144
|
+
const relativeTarget = import_node_path12.default.relative(resolvedProjectRoot, resolvedTargetPath);
|
|
4145
|
+
if (relativeTarget.startsWith("..") || import_node_path12.default.isAbsolute(relativeTarget)) {
|
|
3828
4146
|
res.writeHead(403, { "Content-Type": "text/plain; charset=utf-8" });
|
|
3829
4147
|
res.end("replacement file must be inside the project root");
|
|
3830
4148
|
return true;
|
|
@@ -4183,6 +4501,7 @@ var SootSimBridgeHost = class _SootSimBridgeHost {
|
|
|
4183
4501
|
nextCliFallbackId = 1;
|
|
4184
4502
|
cliIdleTimer = null;
|
|
4185
4503
|
agentHost;
|
|
4504
|
+
planeHost = new PlaneHost();
|
|
4186
4505
|
static CLI_IDLE_TIMEOUT_MS = 6e4;
|
|
4187
4506
|
static CLI_LEASE_TTL_MS = 6e5;
|
|
4188
4507
|
static USER_ACTIVE_LEASE_TTL_MS = 8e3;
|
|
@@ -4414,6 +4733,10 @@ var SootSimBridgeHost = class _SootSimBridgeHost {
|
|
|
4414
4733
|
void this.agentHost.handleMessage(ws, msg);
|
|
4415
4734
|
return;
|
|
4416
4735
|
}
|
|
4736
|
+
if (typeof msg.type === "string" && msg.type.startsWith("plane:")) {
|
|
4737
|
+
void this.planeHost.handleMessage(ws, msg);
|
|
4738
|
+
return;
|
|
4739
|
+
}
|
|
4417
4740
|
if (msg.type === "runtime:list") {
|
|
4418
4741
|
const versions = sootsimRuntime.listInstalled();
|
|
4419
4742
|
const active = this.getActiveRuntime();
|
|
@@ -4736,6 +5059,7 @@ var SootSimBridgeHost = class _SootSimBridgeHost {
|
|
|
4736
5059
|
});
|
|
4737
5060
|
ws.on("close", () => {
|
|
4738
5061
|
this.agentHost.unregisterSocket(ws);
|
|
5062
|
+
this.planeHost.unregisterSocket(ws);
|
|
4739
5063
|
if (role === "sim" && sim) {
|
|
4740
5064
|
this.rememberDisconnectedSim(sim);
|
|
4741
5065
|
if (this.primarySimId === sim.id) {
|