vite-plugin-rpx 0.11.20 → 0.11.22
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/dist/index.js +883 -228
- package/package.json +2 -2
package/dist/index.js
CHANGED
|
@@ -22443,6 +22443,158 @@ var init_https = __esm(() => {
|
|
|
22443
22443
|
SHARED_DEV_HOST_CERT_PATH = join12(homedir8(), ".stacks", "ssl", "rpx.localhost.crt");
|
|
22444
22444
|
});
|
|
22445
22445
|
|
|
22446
|
+
// ../rpx/src/auth.ts
|
|
22447
|
+
import { createHash, timingSafeEqual } from "node:crypto";
|
|
22448
|
+
import { readFileSync as readFileSync2 } from "node:fs";
|
|
22449
|
+
function safeEqual(a2, b2) {
|
|
22450
|
+
const ab = Buffer.from(a2, "utf8");
|
|
22451
|
+
const bb = Buffer.from(b2, "utf8");
|
|
22452
|
+
if (ab.length !== bb.length) {
|
|
22453
|
+
timingSafeEqual(ab, ab);
|
|
22454
|
+
return false;
|
|
22455
|
+
}
|
|
22456
|
+
return timingSafeEqual(ab, bb);
|
|
22457
|
+
}
|
|
22458
|
+
function apr1Crypt(password, salt) {
|
|
22459
|
+
const md5 = (buf) => createHash("md5").update(buf).digest();
|
|
22460
|
+
const pw = Buffer.from(password, "utf8");
|
|
22461
|
+
const saltBuf = Buffer.from(salt, "utf8");
|
|
22462
|
+
const result = md5(Buffer.concat([pw, saltBuf, pw]));
|
|
22463
|
+
const ctx = [pw, Buffer.from("$apr1$", "utf8"), saltBuf];
|
|
22464
|
+
for (let i2 = pw.length;i2 > 0; i2 -= 16)
|
|
22465
|
+
ctx.push(result.subarray(0, Math.min(i2, 16)));
|
|
22466
|
+
for (let i2 = pw.length;i2 > 0; i2 >>= 1)
|
|
22467
|
+
ctx.push(i2 & 1 ? Buffer.from([0]) : pw.subarray(0, 1));
|
|
22468
|
+
let final = md5(Buffer.concat(ctx));
|
|
22469
|
+
for (let i2 = 0;i2 < 1000; i2++) {
|
|
22470
|
+
const round = [];
|
|
22471
|
+
round.push(i2 & 1 ? pw : final.subarray(0, 16));
|
|
22472
|
+
if (i2 % 3)
|
|
22473
|
+
round.push(saltBuf);
|
|
22474
|
+
if (i2 % 7)
|
|
22475
|
+
round.push(pw);
|
|
22476
|
+
round.push(i2 & 1 ? final.subarray(0, 16) : pw);
|
|
22477
|
+
final = md5(Buffer.concat(round));
|
|
22478
|
+
}
|
|
22479
|
+
const itoa64 = "./0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
|
|
22480
|
+
const to64 = (value, count) => {
|
|
22481
|
+
let out = "";
|
|
22482
|
+
let v2 = value;
|
|
22483
|
+
for (let i2 = 0;i2 < count; i2++) {
|
|
22484
|
+
out += itoa64[v2 & 63];
|
|
22485
|
+
v2 >>= 6;
|
|
22486
|
+
}
|
|
22487
|
+
return out;
|
|
22488
|
+
};
|
|
22489
|
+
const p2 = final;
|
|
22490
|
+
let encoded = "";
|
|
22491
|
+
encoded += to64(p2[0] << 16 | p2[6] << 8 | p2[12], 4);
|
|
22492
|
+
encoded += to64(p2[1] << 16 | p2[7] << 8 | p2[13], 4);
|
|
22493
|
+
encoded += to64(p2[2] << 16 | p2[8] << 8 | p2[14], 4);
|
|
22494
|
+
encoded += to64(p2[3] << 16 | p2[9] << 8 | p2[15], 4);
|
|
22495
|
+
encoded += to64(p2[4] << 16 | p2[10] << 8 | p2[5], 4);
|
|
22496
|
+
encoded += to64(p2[11], 2);
|
|
22497
|
+
return `$apr1$${salt}$${encoded}`;
|
|
22498
|
+
}
|
|
22499
|
+
function verifyHtpasswdHash(password, hash) {
|
|
22500
|
+
if (/^\$2[aby]?\$/.test(hash)) {
|
|
22501
|
+
try {
|
|
22502
|
+
return Bun.password.verifySync(password, hash);
|
|
22503
|
+
} catch {
|
|
22504
|
+
return false;
|
|
22505
|
+
}
|
|
22506
|
+
}
|
|
22507
|
+
if (hash.startsWith("$apr1$")) {
|
|
22508
|
+
const parts = hash.split("$");
|
|
22509
|
+
const salt = parts[2] ?? "";
|
|
22510
|
+
return safeEqual(apr1Crypt(password, salt), hash);
|
|
22511
|
+
}
|
|
22512
|
+
if (hash.startsWith("{SHA}")) {
|
|
22513
|
+
const digest = createHash("sha1").update(password, "utf8").digest("base64");
|
|
22514
|
+
return safeEqual(`{SHA}${digest}`, hash);
|
|
22515
|
+
}
|
|
22516
|
+
return safeEqual(password, hash);
|
|
22517
|
+
}
|
|
22518
|
+
function parseHtpasswd(body) {
|
|
22519
|
+
const entries = new Map;
|
|
22520
|
+
for (const rawLine of body.split(`
|
|
22521
|
+
`)) {
|
|
22522
|
+
const line = rawLine.trim();
|
|
22523
|
+
if (!line || line.startsWith("#"))
|
|
22524
|
+
continue;
|
|
22525
|
+
const idx = line.indexOf(":");
|
|
22526
|
+
if (idx <= 0)
|
|
22527
|
+
continue;
|
|
22528
|
+
entries.set(line.slice(0, idx), line.slice(idx + 1));
|
|
22529
|
+
}
|
|
22530
|
+
return entries;
|
|
22531
|
+
}
|
|
22532
|
+
function resolveAuth(cfg) {
|
|
22533
|
+
if (!cfg)
|
|
22534
|
+
return;
|
|
22535
|
+
const realm = (cfg.realm ?? "").trim() || "Restricted";
|
|
22536
|
+
const inline = [];
|
|
22537
|
+
if (cfg.username)
|
|
22538
|
+
inline.push({ username: cfg.username, password: cfg.password ?? "" });
|
|
22539
|
+
for (const user of cfg.users ?? []) {
|
|
22540
|
+
if (user?.username)
|
|
22541
|
+
inline.push({ username: user.username, password: user.password ?? "" });
|
|
22542
|
+
}
|
|
22543
|
+
let htpasswd;
|
|
22544
|
+
if (cfg.htpasswdFile) {
|
|
22545
|
+
try {
|
|
22546
|
+
htpasswd = parseHtpasswd(readFileSync2(cfg.htpasswdFile, "utf8"));
|
|
22547
|
+
} catch {
|
|
22548
|
+
htpasswd = undefined;
|
|
22549
|
+
}
|
|
22550
|
+
}
|
|
22551
|
+
if (inline.length === 0 && (!htpasswd || htpasswd.size === 0))
|
|
22552
|
+
return;
|
|
22553
|
+
const verify = (username, password) => {
|
|
22554
|
+
let ok = false;
|
|
22555
|
+
for (const cred of inline) {
|
|
22556
|
+
if (safeEqual(username, cred.username) && safeEqual(password, cred.password))
|
|
22557
|
+
ok = true;
|
|
22558
|
+
}
|
|
22559
|
+
if (htpasswd) {
|
|
22560
|
+
const hash = htpasswd.get(username);
|
|
22561
|
+
if (hash && verifyHtpasswdHash(password, hash))
|
|
22562
|
+
ok = true;
|
|
22563
|
+
}
|
|
22564
|
+
return ok;
|
|
22565
|
+
};
|
|
22566
|
+
return { realm, verify };
|
|
22567
|
+
}
|
|
22568
|
+
function enforceBasicAuth(req, pathname, auth) {
|
|
22569
|
+
if (pathname.startsWith(ACME_CHALLENGE_PREFIX))
|
|
22570
|
+
return;
|
|
22571
|
+
const header = req.headers.get("authorization") ?? "";
|
|
22572
|
+
const sep = header.indexOf(" ");
|
|
22573
|
+
const scheme = sep === -1 ? header : header.slice(0, sep);
|
|
22574
|
+
const encoded = sep === -1 ? "" : header.slice(sep + 1).trim();
|
|
22575
|
+
if (scheme.toLowerCase() === "basic" && encoded) {
|
|
22576
|
+
let decoded = "";
|
|
22577
|
+
try {
|
|
22578
|
+
decoded = Buffer.from(encoded, "base64").toString("utf8");
|
|
22579
|
+
} catch {
|
|
22580
|
+
decoded = "";
|
|
22581
|
+
}
|
|
22582
|
+
const idx = decoded.indexOf(":");
|
|
22583
|
+
if (idx >= 0 && auth.verify(decoded.slice(0, idx), decoded.slice(idx + 1)))
|
|
22584
|
+
return;
|
|
22585
|
+
}
|
|
22586
|
+
const realm = auth.realm.replace(/["\\]/g, "");
|
|
22587
|
+
return new Response("401 Unauthorized", {
|
|
22588
|
+
status: 401,
|
|
22589
|
+
headers: {
|
|
22590
|
+
"www-authenticate": `Basic realm="${realm}", charset="UTF-8"`,
|
|
22591
|
+
"content-type": "text/plain; charset=utf-8"
|
|
22592
|
+
}
|
|
22593
|
+
});
|
|
22594
|
+
}
|
|
22595
|
+
var ACME_CHALLENGE_PREFIX = "/.well-known/acme-challenge/";
|
|
22596
|
+
var init_auth = () => {};
|
|
22597
|
+
|
|
22446
22598
|
// ../rpx/src/proxy-pool.ts
|
|
22447
22599
|
function isIdempotent(method) {
|
|
22448
22600
|
return IDEMPOTENT_METHODS.has(method.toUpperCase());
|
|
@@ -22613,6 +22765,7 @@ class UpstreamPool {
|
|
|
22613
22765
|
host;
|
|
22614
22766
|
port;
|
|
22615
22767
|
maxTotal;
|
|
22768
|
+
key;
|
|
22616
22769
|
idle = [];
|
|
22617
22770
|
waiters = [];
|
|
22618
22771
|
open = 0;
|
|
@@ -22621,10 +22774,11 @@ class UpstreamPool {
|
|
|
22621
22774
|
maxWaiters;
|
|
22622
22775
|
checkoutIdleMs = checkoutIdleMs();
|
|
22623
22776
|
inUse = new Set;
|
|
22624
|
-
constructor(host, port, maxTotal) {
|
|
22777
|
+
constructor(host, port, maxTotal, key = "") {
|
|
22625
22778
|
this.host = host;
|
|
22626
22779
|
this.port = port;
|
|
22627
22780
|
this.maxTotal = maxTotal;
|
|
22781
|
+
this.key = key;
|
|
22628
22782
|
this.maxWaiters = maxQueued(maxTotal);
|
|
22629
22783
|
}
|
|
22630
22784
|
trackCheckout(conn) {
|
|
@@ -22783,6 +22937,8 @@ class UpstreamPool {
|
|
|
22783
22937
|
if (this.idle.length === 0 && this.inUse.size === 0 && this.sweeper) {
|
|
22784
22938
|
clearInterval(this.sweeper);
|
|
22785
22939
|
this.sweeper = null;
|
|
22940
|
+
if (this.key && pools.get(this.key) === this)
|
|
22941
|
+
pools.delete(this.key);
|
|
22786
22942
|
}
|
|
22787
22943
|
}
|
|
22788
22944
|
}
|
|
@@ -22792,7 +22948,7 @@ function poolFor(hostPort, maxPerHost) {
|
|
|
22792
22948
|
const idx = hostPort.lastIndexOf(":");
|
|
22793
22949
|
const host = idx === -1 ? hostPort : hostPort.slice(0, idx);
|
|
22794
22950
|
const port = idx === -1 ? 80 : Number(hostPort.slice(idx + 1));
|
|
22795
|
-
pool = new UpstreamPool(host, port, maxPerHost);
|
|
22951
|
+
pool = new UpstreamPool(host, port, maxPerHost, hostPort);
|
|
22796
22952
|
pools.set(hostPort, pool);
|
|
22797
22953
|
}
|
|
22798
22954
|
return pool;
|
|
@@ -23231,6 +23387,27 @@ var init_proxy_pool = __esm(() => {
|
|
|
23231
23387
|
pools = new Map;
|
|
23232
23388
|
});
|
|
23233
23389
|
|
|
23390
|
+
// ../rpx/src/redirect.ts
|
|
23391
|
+
function resolveRedirect(input) {
|
|
23392
|
+
const cfg = typeof input === "string" ? { to: input } : input;
|
|
23393
|
+
let to = (cfg.to ?? "").trim();
|
|
23394
|
+
if (to && !/^[a-z][\w+.-]*:\/\//i.test(to))
|
|
23395
|
+
to = `https://${to}`;
|
|
23396
|
+
to = to.replace(/\/+$/, "");
|
|
23397
|
+
return {
|
|
23398
|
+
to,
|
|
23399
|
+
status: cfg.status ?? DEFAULT_REDIRECT_STATUS,
|
|
23400
|
+
preservePath: cfg.preservePath ?? true
|
|
23401
|
+
};
|
|
23402
|
+
}
|
|
23403
|
+
function buildRedirectLocation(redirect, pathname, search) {
|
|
23404
|
+
if (!redirect.preservePath)
|
|
23405
|
+
return redirect.to || "/";
|
|
23406
|
+
const tail = `${pathname}${search}`;
|
|
23407
|
+
return tail === "/" ? `${redirect.to}/` : `${redirect.to}${tail}`;
|
|
23408
|
+
}
|
|
23409
|
+
var DEFAULT_REDIRECT_STATUS = 301;
|
|
23410
|
+
|
|
23234
23411
|
// ../rpx/src/static-files.ts
|
|
23235
23412
|
import * as path2 from "node:path";
|
|
23236
23413
|
function resolveStaticRoute(cfg, cleanUrls) {
|
|
@@ -23346,6 +23523,9 @@ var init_static_files = __esm(() => {
|
|
|
23346
23523
|
});
|
|
23347
23524
|
|
|
23348
23525
|
// ../rpx/src/proxy-handler.ts
|
|
23526
|
+
function wsPendingCapBytes() {
|
|
23527
|
+
return Math.max(0, Number(process.env.RPX_MAX_WS_PENDING_BYTES)) || 8 * 1024 * 1024;
|
|
23528
|
+
}
|
|
23349
23529
|
function extractHostname2(req) {
|
|
23350
23530
|
const hostHeader = req.headers.get("host") || "";
|
|
23351
23531
|
const colon = hostHeader.indexOf(":");
|
|
@@ -23384,15 +23564,34 @@ function splitPathQuery(rawUrl) {
|
|
|
23384
23564
|
return { pathname: rawUrl.slice(pathStart), search: "" };
|
|
23385
23565
|
return { pathname: rawUrl.slice(pathStart, q), search: rawUrl.slice(q) };
|
|
23386
23566
|
}
|
|
23387
|
-
function createProxyFetchHandler(getRoute, verbose) {
|
|
23567
|
+
function createProxyFetchHandler(getRoute, verbose, onNoRoute) {
|
|
23388
23568
|
const inner = async (req, server) => {
|
|
23389
23569
|
const { pathname, search } = splitPathQuery(req.url);
|
|
23390
23570
|
const hostname = extractHostname2(req);
|
|
23391
|
-
|
|
23571
|
+
let route = getRoute(hostname, pathname);
|
|
23572
|
+
if (!route && onNoRoute) {
|
|
23573
|
+
const outcome = await onNoRoute(hostname, pathname, req);
|
|
23574
|
+
if (outcome instanceof Response)
|
|
23575
|
+
return outcome;
|
|
23576
|
+
if (outcome && outcome.retry)
|
|
23577
|
+
route = getRoute(hostname, pathname);
|
|
23578
|
+
}
|
|
23392
23579
|
if (!route) {
|
|
23393
23580
|
debugLog("request", `No route found for host: ${hostname}`, verbose);
|
|
23394
23581
|
return new Response(`No proxy configured for ${hostname}`, { status: 404 });
|
|
23395
23582
|
}
|
|
23583
|
+
if (route.auth) {
|
|
23584
|
+
const challenge = enforceBasicAuth(req, pathname, route.auth);
|
|
23585
|
+
if (challenge) {
|
|
23586
|
+
debugLog("request", `401 challenge for ${hostname}${pathname}`, verbose);
|
|
23587
|
+
return challenge;
|
|
23588
|
+
}
|
|
23589
|
+
}
|
|
23590
|
+
if (route.redirect) {
|
|
23591
|
+
const location = buildRedirectLocation(route.redirect, pathname, search);
|
|
23592
|
+
debugLog("request", `${route.redirect.status} redirect ${hostname}${pathname} → ${location}`, verbose);
|
|
23593
|
+
return new Response(null, { status: route.redirect.status, headers: { location } });
|
|
23594
|
+
}
|
|
23396
23595
|
if (route.static) {
|
|
23397
23596
|
const strip = route.stripBasePathPrefix ?? true;
|
|
23398
23597
|
const staticPath = strip ? stripBasePath(pathname, route.basePath) : pathname;
|
|
@@ -23486,6 +23685,7 @@ function createProxyFetchHandler(getRoute, verbose) {
|
|
|
23486
23685
|
}
|
|
23487
23686
|
function createProxyWebSocketHandler(verbose) {
|
|
23488
23687
|
const state = new WeakMap;
|
|
23688
|
+
const maxPendingBytes = wsPendingCapBytes();
|
|
23489
23689
|
return {
|
|
23490
23690
|
open(ws) {
|
|
23491
23691
|
const { targetUrl, forwardHeaders } = ws.data;
|
|
@@ -23498,13 +23698,14 @@ function createProxyWebSocketHandler(verbose) {
|
|
|
23498
23698
|
return;
|
|
23499
23699
|
}
|
|
23500
23700
|
upstream.binaryType = "arraybuffer";
|
|
23501
|
-
const st = { upstream, upstreamOpen: false, pending: [] };
|
|
23701
|
+
const st = { upstream, upstreamOpen: false, pending: [], pendingBytes: 0 };
|
|
23502
23702
|
state.set(ws, st);
|
|
23503
23703
|
upstream.addEventListener("open", () => {
|
|
23504
23704
|
st.upstreamOpen = true;
|
|
23505
23705
|
for (const frame of st.pending)
|
|
23506
23706
|
upstream.send(frame);
|
|
23507
23707
|
st.pending = [];
|
|
23708
|
+
st.pendingBytes = 0;
|
|
23508
23709
|
});
|
|
23509
23710
|
upstream.addEventListener("message", (ev) => {
|
|
23510
23711
|
ws.send(ev.data);
|
|
@@ -23526,10 +23727,24 @@ function createProxyWebSocketHandler(verbose) {
|
|
|
23526
23727
|
if (!st)
|
|
23527
23728
|
return;
|
|
23528
23729
|
const frame = typeof message === "string" ? message : new Uint8Array(message);
|
|
23529
|
-
if (st.upstreamOpen)
|
|
23730
|
+
if (st.upstreamOpen) {
|
|
23530
23731
|
st.upstream.send(frame);
|
|
23531
|
-
|
|
23532
|
-
|
|
23732
|
+
return;
|
|
23733
|
+
}
|
|
23734
|
+
const size = typeof frame === "string" ? Buffer.byteLength(frame) : frame.byteLength;
|
|
23735
|
+
if (st.pendingBytes + size > maxPendingBytes) {
|
|
23736
|
+
debugLog("ws", `pending buffer would exceed ${maxPendingBytes}B before upstream open; closing socket`, verbose);
|
|
23737
|
+
try {
|
|
23738
|
+
ws.close(1009, "buffer limit");
|
|
23739
|
+
} catch {}
|
|
23740
|
+
try {
|
|
23741
|
+
st.upstream.close(1011, "client buffer overflow");
|
|
23742
|
+
} catch {}
|
|
23743
|
+
state.delete(ws);
|
|
23744
|
+
return;
|
|
23745
|
+
}
|
|
23746
|
+
st.pending.push(frame);
|
|
23747
|
+
st.pendingBytes += size;
|
|
23533
23748
|
},
|
|
23534
23749
|
close(ws, code, reason) {
|
|
23535
23750
|
const st = state.get(ws);
|
|
@@ -23544,6 +23759,7 @@ function createProxyWebSocketHandler(verbose) {
|
|
|
23544
23759
|
}
|
|
23545
23760
|
var HOP_BY_HOP;
|
|
23546
23761
|
var init_proxy_handler = __esm(() => {
|
|
23762
|
+
init_auth();
|
|
23547
23763
|
init_proxy_pool();
|
|
23548
23764
|
init_static_files();
|
|
23549
23765
|
init_utils();
|
|
@@ -23875,6 +24091,144 @@ var init_on_demand = __esm(() => {
|
|
|
23875
24091
|
init_utils();
|
|
23876
24092
|
});
|
|
23877
24093
|
|
|
24094
|
+
// ../rpx/src/site-resolver.ts
|
|
24095
|
+
var DEFAULT_IDLE_TIMEOUT_MS;
|
|
24096
|
+
var init_site_resolver = __esm(() => {
|
|
24097
|
+
DEFAULT_IDLE_TIMEOUT_MS = 30 * 60000;
|
|
24098
|
+
});
|
|
24099
|
+
|
|
24100
|
+
// ../rpx/src/port-manager.ts
|
|
24101
|
+
import * as net from "node:net";
|
|
24102
|
+
function isPortInUse(port, hostname, verbose) {
|
|
24103
|
+
debugLog("port", `Checking if port ${port} is in use on ${hostname}`, verbose);
|
|
24104
|
+
return new Promise((resolve14) => {
|
|
24105
|
+
const server = net.createServer();
|
|
24106
|
+
const timeout = setTimeout(() => {
|
|
24107
|
+
debugLog("port", `Checking port ${port} timed out, assuming it's in use`, verbose);
|
|
24108
|
+
server.close();
|
|
24109
|
+
resolve14(true);
|
|
24110
|
+
}, 3000);
|
|
24111
|
+
server.once("error", (err) => {
|
|
24112
|
+
clearTimeout(timeout);
|
|
24113
|
+
if (err.code === "EADDRINUSE") {
|
|
24114
|
+
debugLog("port", `Port ${port} is in use`, verbose);
|
|
24115
|
+
resolve14(true);
|
|
24116
|
+
} else {
|
|
24117
|
+
debugLog("port", `Error checking port ${port}: ${err.message}`, verbose);
|
|
24118
|
+
resolve14(true);
|
|
24119
|
+
}
|
|
24120
|
+
});
|
|
24121
|
+
server.once("listening", () => {
|
|
24122
|
+
clearTimeout(timeout);
|
|
24123
|
+
debugLog("port", `Port ${port} is available`, verbose);
|
|
24124
|
+
server.close();
|
|
24125
|
+
resolve14(false);
|
|
24126
|
+
});
|
|
24127
|
+
try {
|
|
24128
|
+
server.listen(port, hostname);
|
|
24129
|
+
} catch (err) {
|
|
24130
|
+
clearTimeout(timeout);
|
|
24131
|
+
debugLog("port", `Exception checking port ${port}: ${err}`, verbose);
|
|
24132
|
+
resolve14(true);
|
|
24133
|
+
}
|
|
24134
|
+
});
|
|
24135
|
+
}
|
|
24136
|
+
async function findAvailablePort(startPort, hostname, verbose, maxAttempts = 50) {
|
|
24137
|
+
debugLog("port", `Finding available port starting from ${startPort} (max attempts: ${maxAttempts})`, verbose);
|
|
24138
|
+
let port = startPort;
|
|
24139
|
+
let attempts = 0;
|
|
24140
|
+
while (attempts < maxAttempts) {
|
|
24141
|
+
attempts++;
|
|
24142
|
+
const isInUse = await isPortInUse(port, hostname, verbose);
|
|
24143
|
+
if (!isInUse) {
|
|
24144
|
+
debugLog("port", `Found available port: ${port} after ${attempts} attempts`, verbose);
|
|
24145
|
+
return port;
|
|
24146
|
+
}
|
|
24147
|
+
debugLog("port", `Port ${port} is in use, trying ${port + 1} (attempt ${attempts}/${maxAttempts})`, verbose);
|
|
24148
|
+
port++;
|
|
24149
|
+
}
|
|
24150
|
+
throw new Error(`Unable to find available port after ${maxAttempts} attempts starting from ${startPort}`);
|
|
24151
|
+
}
|
|
24152
|
+
function testPortConnectivity(port, hostname, timeout = 5000, verbose) {
|
|
24153
|
+
debugLog("port", `Testing connection to ${hostname}:${port}`, verbose);
|
|
24154
|
+
return new Promise((resolve14) => {
|
|
24155
|
+
const socket = net.connect({
|
|
24156
|
+
host: hostname,
|
|
24157
|
+
port,
|
|
24158
|
+
timeout
|
|
24159
|
+
});
|
|
24160
|
+
socket.once("connect", () => {
|
|
24161
|
+
debugLog("port", `Successfully connected to ${hostname}:${port}`, verbose);
|
|
24162
|
+
socket.end();
|
|
24163
|
+
resolve14(true);
|
|
24164
|
+
});
|
|
24165
|
+
socket.once("timeout", () => {
|
|
24166
|
+
debugLog("port", `Connection to ${hostname}:${port} timed out`, verbose);
|
|
24167
|
+
socket.destroy();
|
|
24168
|
+
resolve14(false);
|
|
24169
|
+
});
|
|
24170
|
+
socket.once("error", (err) => {
|
|
24171
|
+
debugLog("port", `Failed to connect to ${hostname}:${port}: ${err.message}`, verbose);
|
|
24172
|
+
socket.destroy();
|
|
24173
|
+
resolve14(false);
|
|
24174
|
+
});
|
|
24175
|
+
});
|
|
24176
|
+
}
|
|
24177
|
+
|
|
24178
|
+
class DefaultPortManager {
|
|
24179
|
+
usedPorts = new Set;
|
|
24180
|
+
hostname;
|
|
24181
|
+
verbose;
|
|
24182
|
+
maxRetries;
|
|
24183
|
+
constructor(hostname = "0.0.0.0", verbose, maxRetries = 50) {
|
|
24184
|
+
this.hostname = hostname;
|
|
24185
|
+
this.verbose = verbose;
|
|
24186
|
+
this.maxRetries = maxRetries;
|
|
24187
|
+
}
|
|
24188
|
+
async getNextAvailablePort(startPort, testConnectivity = false) {
|
|
24189
|
+
if (this.usedPorts.has(startPort)) {
|
|
24190
|
+
return this.findNextAvailablePort(startPort + 1, testConnectivity);
|
|
24191
|
+
}
|
|
24192
|
+
const isInUse = await isPortInUse(startPort, this.hostname, this.verbose);
|
|
24193
|
+
if (isInUse) {
|
|
24194
|
+
return this.findNextAvailablePort(startPort + 1, testConnectivity);
|
|
24195
|
+
}
|
|
24196
|
+
if (testConnectivity) {
|
|
24197
|
+
const isConnectable = await testPortConnectivity(startPort, this.hostname, 3000, this.verbose);
|
|
24198
|
+
if (!isConnectable) {
|
|
24199
|
+
debugLog("port", `Port ${startPort} is available but not connectable, trying next port`, this.verbose);
|
|
24200
|
+
return this.findNextAvailablePort(startPort + 1, testConnectivity);
|
|
24201
|
+
}
|
|
24202
|
+
}
|
|
24203
|
+
this.usedPorts.add(startPort);
|
|
24204
|
+
return startPort;
|
|
24205
|
+
}
|
|
24206
|
+
async findNextAvailablePort(startPort, testConnectivity = false) {
|
|
24207
|
+
const port = await findAvailablePort(startPort, this.hostname, this.verbose, this.maxRetries);
|
|
24208
|
+
if (testConnectivity) {
|
|
24209
|
+
const isConnectable = await testPortConnectivity(port, this.hostname, 3000, this.verbose);
|
|
24210
|
+
if (!isConnectable) {
|
|
24211
|
+
if (port < startPort + this.maxRetries) {
|
|
24212
|
+
return this.findNextAvailablePort(port + 1, testConnectivity);
|
|
24213
|
+
} else {
|
|
24214
|
+
throw new Error(`Unable to find a connectable port after ${this.maxRetries} attempts`);
|
|
24215
|
+
}
|
|
24216
|
+
}
|
|
24217
|
+
}
|
|
24218
|
+
this.usedPorts.add(port);
|
|
24219
|
+
return port;
|
|
24220
|
+
}
|
|
24221
|
+
releasePort(port) {
|
|
24222
|
+
debugLog("port", `Releasing port ${port}`, this.verbose);
|
|
24223
|
+
this.usedPorts.delete(port);
|
|
24224
|
+
}
|
|
24225
|
+
}
|
|
24226
|
+
var portManager;
|
|
24227
|
+
var init_port_manager = __esm(() => {
|
|
24228
|
+
init_utils();
|
|
24229
|
+
portManager = new DefaultPortManager;
|
|
24230
|
+
});
|
|
24231
|
+
|
|
23878
24232
|
// ../rpx/src/registry.ts
|
|
23879
24233
|
import * as fsp3 from "node:fs/promises";
|
|
23880
24234
|
import { homedir as homedir9 } from "node:os";
|
|
@@ -23946,19 +24300,399 @@ var init_registry = __esm(() => {
|
|
|
23946
24300
|
ID_PATTERN = /^[a-zA-Z0-9._-]+$/;
|
|
23947
24301
|
});
|
|
23948
24302
|
|
|
23949
|
-
// ../rpx/src/
|
|
24303
|
+
// ../rpx/src/site-supervisor.ts
|
|
24304
|
+
import { closeSync as closeSync6, openSync as openSync6, readFileSync as readFileSync3 } from "node:fs";
|
|
23950
24305
|
import * as fsp4 from "node:fs/promises";
|
|
24306
|
+
import { spawn } from "node:child_process";
|
|
23951
24307
|
import { homedir as homedir10 } from "node:os";
|
|
23952
24308
|
import * as path6 from "node:path";
|
|
24309
|
+
import * as process25 from "node:process";
|
|
24310
|
+
|
|
24311
|
+
class SiteSupervisor {
|
|
24312
|
+
resolver;
|
|
24313
|
+
registryDir;
|
|
24314
|
+
rpxDir;
|
|
24315
|
+
verbose;
|
|
24316
|
+
startupTimeoutMs;
|
|
24317
|
+
pollIntervalMs;
|
|
24318
|
+
restartDelayMs;
|
|
24319
|
+
killGraceMs;
|
|
24320
|
+
launch;
|
|
24321
|
+
probePort;
|
|
24322
|
+
pickPort;
|
|
24323
|
+
isHostRoutable;
|
|
24324
|
+
onSiteActivating;
|
|
24325
|
+
now;
|
|
24326
|
+
writeEntry;
|
|
24327
|
+
removeEntry;
|
|
24328
|
+
sites = new Map;
|
|
24329
|
+
reaper;
|
|
24330
|
+
stopped = false;
|
|
24331
|
+
constructor(opts) {
|
|
24332
|
+
this.resolver = opts.resolver;
|
|
24333
|
+
this.registryDir = opts.registryDir ?? getRegistryDir();
|
|
24334
|
+
this.rpxDir = opts.rpxDir ?? path6.join(homedir10(), ".stacks", "rpx");
|
|
24335
|
+
this.verbose = opts.verbose ?? false;
|
|
24336
|
+
this.startupTimeoutMs = opts.startupTimeoutMs ?? DEFAULT_STARTUP_TIMEOUT_MS;
|
|
24337
|
+
this.pollIntervalMs = opts.pollIntervalMs ?? DEFAULT_POLL_INTERVAL_MS;
|
|
24338
|
+
this.restartDelayMs = opts.restartDelayMs ?? DEFAULT_RESTART_DELAY_MS;
|
|
24339
|
+
this.killGraceMs = opts.killGraceMs ?? KILL_GRACE_MS;
|
|
24340
|
+
this.launch = opts.launcher ?? makeDefaultLauncher(this.verbose);
|
|
24341
|
+
this.probePort = opts.probePort ?? defaultReadinessProbe;
|
|
24342
|
+
this.pickPort = opts.pickPort ?? ((preferred) => findAvailablePort(preferred, "127.0.0.1"));
|
|
24343
|
+
this.isHostRoutable = opts.isHostRoutable ?? (() => false);
|
|
24344
|
+
this.onSiteActivating = opts.onSiteActivating;
|
|
24345
|
+
this.now = opts.now ?? Date.now;
|
|
24346
|
+
this.writeEntry = opts.writeEntry ?? writeEntry;
|
|
24347
|
+
this.removeEntry = opts.removeEntry ?? removeEntry;
|
|
24348
|
+
const reapInterval = opts.reapIntervalMs ?? DEFAULT_REAP_INTERVAL_MS;
|
|
24349
|
+
this.reaper = setInterval(() => {
|
|
24350
|
+
this.reapIdle();
|
|
24351
|
+
}, reapInterval);
|
|
24352
|
+
if (typeof this.reaper.unref === "function")
|
|
24353
|
+
this.reaper.unref();
|
|
24354
|
+
}
|
|
24355
|
+
async onRequest(host) {
|
|
24356
|
+
if (this.stopped)
|
|
24357
|
+
return { kind: "unknown" };
|
|
24358
|
+
let state = this.sites.get(host);
|
|
24359
|
+
if (state && state.status === "failed" && this.now() - state.failedAt >= this.restartDelayMs) {
|
|
24360
|
+
this.sites.delete(host);
|
|
24361
|
+
state = undefined;
|
|
24362
|
+
}
|
|
24363
|
+
if (!state) {
|
|
24364
|
+
const site = this.resolver.resolve(host);
|
|
24365
|
+
if (!site)
|
|
24366
|
+
return { kind: "unknown" };
|
|
24367
|
+
state = await this.start(site);
|
|
24368
|
+
}
|
|
24369
|
+
state.lastAccess = this.now();
|
|
24370
|
+
if (state.status === "ready")
|
|
24371
|
+
return { kind: "ready", host };
|
|
24372
|
+
if (state.status === "failed")
|
|
24373
|
+
return { kind: "failed", host, error: state.error ?? "failed to start", logTail: this.readLogTail(state) };
|
|
24374
|
+
return { kind: "starting", host, sinceMs: this.now() - state.startedAt, source: state.site.source, logTail: this.readLogTail(state, 16) };
|
|
24375
|
+
}
|
|
24376
|
+
async start(site) {
|
|
24377
|
+
try {
|
|
24378
|
+
this.onSiteActivating?.(site.host);
|
|
24379
|
+
} catch {}
|
|
24380
|
+
const ports = new Map;
|
|
24381
|
+
if (!site.selfRegisters) {
|
|
24382
|
+
for (const route of site.routes) {
|
|
24383
|
+
if (ports.has(route.portEnv))
|
|
24384
|
+
continue;
|
|
24385
|
+
const port = await this.pickPort(route.defaultPort ?? 3000);
|
|
24386
|
+
ports.set(route.portEnv, port);
|
|
24387
|
+
}
|
|
24388
|
+
}
|
|
24389
|
+
const logPath = path6.join(this.rpxDir, "sites", `${site.id}.log`);
|
|
24390
|
+
await fsp4.mkdir(path6.dirname(logPath), { recursive: true }).catch(() => {});
|
|
24391
|
+
const env2 = this.buildEnv(site, ports);
|
|
24392
|
+
let handle = null;
|
|
24393
|
+
let startError;
|
|
24394
|
+
try {
|
|
24395
|
+
handle = this.launch({ command: site.command, cwd: site.dir, env: env2, logPath });
|
|
24396
|
+
} catch (err) {
|
|
24397
|
+
startError = `failed to spawn: ${err.message}`;
|
|
24398
|
+
}
|
|
24399
|
+
const state = {
|
|
24400
|
+
site,
|
|
24401
|
+
status: startError ? "failed" : "starting",
|
|
24402
|
+
handle,
|
|
24403
|
+
ports,
|
|
24404
|
+
routeIds: [],
|
|
24405
|
+
startedAt: this.now(),
|
|
24406
|
+
lastAccess: this.now(),
|
|
24407
|
+
failedAt: startError ? this.now() : 0,
|
|
24408
|
+
error: startError,
|
|
24409
|
+
logPath,
|
|
24410
|
+
exited: false,
|
|
24411
|
+
ready: Promise.resolve()
|
|
24412
|
+
};
|
|
24413
|
+
this.sites.set(site.host, state);
|
|
24414
|
+
if (startError) {
|
|
24415
|
+
log.warn(`rpx: site ${site.host} ${startError}`);
|
|
24416
|
+
return state;
|
|
24417
|
+
}
|
|
24418
|
+
log.info(`rpx: booting ${site.host} → ${site.command} (${site.dir})`);
|
|
24419
|
+
handle.exited.then((code) => this.onProcessExit(state, code)).catch(() => {});
|
|
24420
|
+
state.ready = this.driveReadiness(state).catch((err) => {
|
|
24421
|
+
debugLog("sites", `readiness loop for ${site.host} threw: ${err}`, this.verbose);
|
|
24422
|
+
});
|
|
24423
|
+
return state;
|
|
24424
|
+
}
|
|
24425
|
+
async onProcessExit(state, code) {
|
|
24426
|
+
state.exited = true;
|
|
24427
|
+
if (this.stopped || this.sites.get(state.site.host) !== state)
|
|
24428
|
+
return;
|
|
24429
|
+
if (state.status === "ready") {
|
|
24430
|
+
log.warn(`rpx: ${state.site.host} exited${code !== null ? ` (code ${code})` : ""} — will reboot on next request`);
|
|
24431
|
+
this.sites.delete(state.site.host);
|
|
24432
|
+
for (const id of state.routeIds)
|
|
24433
|
+
await this.removeEntry(id, this.registryDir, this.verbose).catch(() => {});
|
|
24434
|
+
} else if (state.status === "starting") {
|
|
24435
|
+
this.fail(state, `process exited${code !== null ? ` with code ${code}` : ""} before becoming ready`);
|
|
24436
|
+
}
|
|
24437
|
+
}
|
|
24438
|
+
async driveReadiness(state) {
|
|
24439
|
+
const { site } = state;
|
|
24440
|
+
const deadline = this.now() + this.startupTimeoutMs;
|
|
24441
|
+
const ready = site.selfRegisters ? () => this.isHostRoutable(site.host) : await this.makeGateProbe(site, state.ports);
|
|
24442
|
+
while (this.now() < deadline && !this.stopped) {
|
|
24443
|
+
if (state.status !== "starting" || state.exited)
|
|
24444
|
+
return;
|
|
24445
|
+
if (await ready()) {
|
|
24446
|
+
await this.publishRoutes(state);
|
|
24447
|
+
if (state.status === "starting") {
|
|
24448
|
+
state.status = "ready";
|
|
24449
|
+
log.success(`rpx: ${site.host} ready`);
|
|
24450
|
+
}
|
|
24451
|
+
return;
|
|
24452
|
+
}
|
|
24453
|
+
await delay(this.pollIntervalMs);
|
|
24454
|
+
}
|
|
24455
|
+
if (state.status === "starting")
|
|
24456
|
+
this.fail(state, `did not become ready within ${Math.round(this.startupTimeoutMs / 1000)}s`);
|
|
24457
|
+
}
|
|
24458
|
+
async makeGateProbe(site, ports) {
|
|
24459
|
+
const gatePorts = site.routes.filter((r2) => r2.readyGate ?? normalizePathPrefix(r2.path) === "/").map((r2) => ports.get(r2.portEnv)).filter((p2) => typeof p2 === "number");
|
|
24460
|
+
const probePorts = gatePorts.length > 0 ? gatePorts : [...ports.values()];
|
|
24461
|
+
return async () => {
|
|
24462
|
+
for (const port of probePorts) {
|
|
24463
|
+
if (!await this.probePort(port))
|
|
24464
|
+
return false;
|
|
24465
|
+
}
|
|
24466
|
+
return probePorts.length > 0;
|
|
24467
|
+
};
|
|
24468
|
+
}
|
|
24469
|
+
async publishRoutes(state) {
|
|
24470
|
+
const { site, ports } = state;
|
|
24471
|
+
if (site.selfRegisters || site.routes.length === 0)
|
|
24472
|
+
return;
|
|
24473
|
+
const defaultRoute = site.routes.find((r2) => normalizePathPrefix(r2.path) === "/") ?? site.routes[0];
|
|
24474
|
+
const fromPort = ports.get(defaultRoute.portEnv);
|
|
24475
|
+
if (fromPort === undefined)
|
|
24476
|
+
return;
|
|
24477
|
+
const pathRewrites = [];
|
|
24478
|
+
for (const route of site.routes) {
|
|
24479
|
+
if (route === defaultRoute)
|
|
24480
|
+
continue;
|
|
24481
|
+
const port = ports.get(route.portEnv);
|
|
24482
|
+
if (port === undefined)
|
|
24483
|
+
continue;
|
|
24484
|
+
pathRewrites.push({
|
|
24485
|
+
from: normalizePathPrefix(route.path),
|
|
24486
|
+
to: `localhost:${port}`,
|
|
24487
|
+
stripPrefix: route.stripPrefix ?? false
|
|
24488
|
+
});
|
|
24489
|
+
}
|
|
24490
|
+
const entry = {
|
|
24491
|
+
id: site.id,
|
|
24492
|
+
from: `localhost:${fromPort}`,
|
|
24493
|
+
to: site.host,
|
|
24494
|
+
cwd: site.dir,
|
|
24495
|
+
createdAt: new Date(this.now()).toISOString(),
|
|
24496
|
+
pathRewrites: pathRewrites.length > 0 ? pathRewrites : undefined,
|
|
24497
|
+
pid: state.handle?.pid
|
|
24498
|
+
};
|
|
24499
|
+
await this.writeEntry(entry, this.registryDir, this.verbose);
|
|
24500
|
+
state.routeIds = [site.id];
|
|
24501
|
+
const tableDeadline = this.now() + 2000;
|
|
24502
|
+
while (this.now() < tableDeadline && !this.isHostRoutable(site.host)) {
|
|
24503
|
+
await delay(50);
|
|
24504
|
+
}
|
|
24505
|
+
}
|
|
24506
|
+
fail(state, error) {
|
|
24507
|
+
state.status = "failed";
|
|
24508
|
+
state.error = error;
|
|
24509
|
+
state.failedAt = this.now();
|
|
24510
|
+
log.warn(`rpx: site ${state.site.host} failed — ${error}`);
|
|
24511
|
+
this.killProcess(state);
|
|
24512
|
+
}
|
|
24513
|
+
buildEnv(site, ports) {
|
|
24514
|
+
const env2 = {};
|
|
24515
|
+
for (const [k2, v2] of Object.entries(process25.env)) {
|
|
24516
|
+
if (typeof v2 === "string")
|
|
24517
|
+
env2[k2] = v2;
|
|
24518
|
+
}
|
|
24519
|
+
Object.assign(env2, site.env);
|
|
24520
|
+
for (const [name, port] of ports)
|
|
24521
|
+
env2[name] = String(port);
|
|
24522
|
+
env2.RPX_SITE_HOST = site.host;
|
|
24523
|
+
env2.RPX_SITE_URL = `https://${site.host}`;
|
|
24524
|
+
return env2;
|
|
24525
|
+
}
|
|
24526
|
+
readLogTail(state, lines = 40) {
|
|
24527
|
+
try {
|
|
24528
|
+
const text = readFileSync3(state.logPath, "utf8");
|
|
24529
|
+
return text.split(`
|
|
24530
|
+
`).slice(-lines).join(`
|
|
24531
|
+
`).trim();
|
|
24532
|
+
} catch {
|
|
24533
|
+
return "";
|
|
24534
|
+
}
|
|
24535
|
+
}
|
|
24536
|
+
async stop(host) {
|
|
24537
|
+
const state = this.sites.get(host);
|
|
24538
|
+
if (!state)
|
|
24539
|
+
return;
|
|
24540
|
+
this.sites.delete(host);
|
|
24541
|
+
for (const id of state.routeIds)
|
|
24542
|
+
await this.removeEntry(id, this.registryDir, this.verbose).catch(() => {});
|
|
24543
|
+
await this.killProcess(state);
|
|
24544
|
+
log.info(`rpx: stopped ${host}`);
|
|
24545
|
+
}
|
|
24546
|
+
async killProcess(state) {
|
|
24547
|
+
const handle = state.handle;
|
|
24548
|
+
if (!handle)
|
|
24549
|
+
return;
|
|
24550
|
+
state.handle = null;
|
|
24551
|
+
try {
|
|
24552
|
+
handle.stop("SIGTERM");
|
|
24553
|
+
} catch {}
|
|
24554
|
+
const timer = setTimeout(() => {
|
|
24555
|
+
try {
|
|
24556
|
+
handle.stop("SIGKILL");
|
|
24557
|
+
} catch {}
|
|
24558
|
+
}, this.killGraceMs);
|
|
24559
|
+
if (typeof timer.unref === "function")
|
|
24560
|
+
timer.unref();
|
|
24561
|
+
await Promise.race([handle.exited.catch(() => {}), delay(this.killGraceMs + 500)]);
|
|
24562
|
+
clearTimeout(timer);
|
|
24563
|
+
}
|
|
24564
|
+
async reapIdle() {
|
|
24565
|
+
if (this.stopped)
|
|
24566
|
+
return;
|
|
24567
|
+
const now = this.now();
|
|
24568
|
+
for (const [host, state] of this.sites) {
|
|
24569
|
+
const idle = state.site.idleTimeoutMs;
|
|
24570
|
+
if (idle <= 0)
|
|
24571
|
+
continue;
|
|
24572
|
+
const idleFor = now - state.lastAccess;
|
|
24573
|
+
const settled = state.status === "ready" || now - state.startedAt > this.startupTimeoutMs;
|
|
24574
|
+
if (settled && idleFor > idle) {
|
|
24575
|
+
debugLog("sites", `reaping ${host} (idle ${Math.round(idleFor / 1000)}s)`, this.verbose);
|
|
24576
|
+
await this.stop(host);
|
|
24577
|
+
}
|
|
24578
|
+
}
|
|
24579
|
+
}
|
|
24580
|
+
list() {
|
|
24581
|
+
const now = this.now();
|
|
24582
|
+
return [...this.sites.values()].map((s) => ({
|
|
24583
|
+
host: s.site.host,
|
|
24584
|
+
dir: s.site.dir,
|
|
24585
|
+
status: s.status,
|
|
24586
|
+
pid: s.handle?.pid ?? null,
|
|
24587
|
+
ports: Object.fromEntries(s.ports),
|
|
24588
|
+
uptimeMs: now - s.startedAt,
|
|
24589
|
+
idleMs: now - s.lastAccess,
|
|
24590
|
+
error: s.error
|
|
24591
|
+
}));
|
|
24592
|
+
}
|
|
24593
|
+
async stopAll() {
|
|
24594
|
+
this.stopped = true;
|
|
24595
|
+
clearInterval(this.reaper);
|
|
24596
|
+
await Promise.allSettled([...this.sites.keys()].map((host) => this.stop(host)));
|
|
24597
|
+
}
|
|
24598
|
+
}
|
|
24599
|
+
function delay(ms) {
|
|
24600
|
+
return new Promise((resolve14) => setTimeout(resolve14, ms));
|
|
24601
|
+
}
|
|
24602
|
+
async function defaultReadinessProbe(port) {
|
|
24603
|
+
const controller = new AbortController;
|
|
24604
|
+
const timer = setTimeout(() => controller.abort(), 2000);
|
|
24605
|
+
try {
|
|
24606
|
+
await fetch(`http://127.0.0.1:${port}/`, { signal: controller.signal, redirect: "manual" });
|
|
24607
|
+
return true;
|
|
24608
|
+
} catch {
|
|
24609
|
+
return false;
|
|
24610
|
+
} finally {
|
|
24611
|
+
clearTimeout(timer);
|
|
24612
|
+
}
|
|
24613
|
+
}
|
|
24614
|
+
function makeDefaultLauncher(verbose) {
|
|
24615
|
+
const drop = privilegeDrop();
|
|
24616
|
+
return ({ command, cwd, env: env2, logPath }) => {
|
|
24617
|
+
const fd = openSync6(logPath, "a");
|
|
24618
|
+
try {
|
|
24619
|
+
const child = spawn("sh", ["-c", command], {
|
|
24620
|
+
cwd,
|
|
24621
|
+
env: env2,
|
|
24622
|
+
detached: true,
|
|
24623
|
+
stdio: ["ignore", fd, fd],
|
|
24624
|
+
...drop ? { uid: drop.uid, gid: drop.gid } : {}
|
|
24625
|
+
});
|
|
24626
|
+
const pid2 = child.pid;
|
|
24627
|
+
if (pid2 === undefined)
|
|
24628
|
+
throw new Error("spawn returned no pid");
|
|
24629
|
+
const exited = new Promise((resolve14) => {
|
|
24630
|
+
child.once("exit", (code) => {
|
|
24631
|
+
resolve14(code);
|
|
24632
|
+
});
|
|
24633
|
+
child.once("error", () => {
|
|
24634
|
+
resolve14(null);
|
|
24635
|
+
});
|
|
24636
|
+
}).finally(() => {
|
|
24637
|
+
try {
|
|
24638
|
+
closeSync6(fd);
|
|
24639
|
+
} catch {}
|
|
24640
|
+
});
|
|
24641
|
+
debugLog("sites", `spawned pid ${pid2}: sh -c ${command} (cwd ${cwd})`, verbose);
|
|
24642
|
+
return {
|
|
24643
|
+
pid: pid2,
|
|
24644
|
+
exited,
|
|
24645
|
+
stop: (signal = "SIGTERM") => {
|
|
24646
|
+
try {
|
|
24647
|
+
process25.kill(-pid2, signal);
|
|
24648
|
+
} catch {
|
|
24649
|
+
try {
|
|
24650
|
+
child.kill(signal);
|
|
24651
|
+
} catch {}
|
|
24652
|
+
}
|
|
24653
|
+
}
|
|
24654
|
+
};
|
|
24655
|
+
} catch (err) {
|
|
24656
|
+
try {
|
|
24657
|
+
closeSync6(fd);
|
|
24658
|
+
} catch {}
|
|
24659
|
+
throw err;
|
|
24660
|
+
}
|
|
24661
|
+
};
|
|
24662
|
+
}
|
|
24663
|
+
function privilegeDrop() {
|
|
24664
|
+
if (process25.platform === "win32")
|
|
24665
|
+
return null;
|
|
24666
|
+
const isRoot = typeof process25.getuid === "function" && process25.getuid() === 0;
|
|
24667
|
+
if (!isRoot)
|
|
24668
|
+
return null;
|
|
24669
|
+
const uid = Number.parseInt(process25.env.SUDO_UID ?? "", 10);
|
|
24670
|
+
const gid = Number.parseInt(process25.env.SUDO_GID ?? "", 10);
|
|
24671
|
+
if (!Number.isInteger(uid) || uid <= 0 || !Number.isInteger(gid) || gid <= 0)
|
|
24672
|
+
return null;
|
|
24673
|
+
return { uid, gid };
|
|
24674
|
+
}
|
|
24675
|
+
var DEFAULT_STARTUP_TIMEOUT_MS = 120000, DEFAULT_POLL_INTERVAL_MS = 200, DEFAULT_REAP_INTERVAL_MS = 30000, DEFAULT_RESTART_DELAY_MS = 3000, KILL_GRACE_MS = 4000;
|
|
24676
|
+
var init_site_supervisor = __esm(() => {
|
|
24677
|
+
init_host_routes();
|
|
24678
|
+
init_port_manager();
|
|
24679
|
+
init_registry();
|
|
24680
|
+
init_utils();
|
|
24681
|
+
init_logger();
|
|
24682
|
+
});
|
|
24683
|
+
// ../rpx/src/dns-state.ts
|
|
24684
|
+
import * as fsp5 from "node:fs/promises";
|
|
24685
|
+
import { homedir as homedir11 } from "node:os";
|
|
24686
|
+
import * as path7 from "node:path";
|
|
23953
24687
|
function defaultRpxDir() {
|
|
23954
|
-
return
|
|
24688
|
+
return path7.join(homedir11(), ".stacks", "rpx");
|
|
23955
24689
|
}
|
|
23956
24690
|
function getDnsStatePath(rpxDir = defaultRpxDir()) {
|
|
23957
|
-
return
|
|
24691
|
+
return path7.join(rpxDir, RPX_DNS_STATE_FILE);
|
|
23958
24692
|
}
|
|
23959
24693
|
async function loadDnsState(rpxDir = defaultRpxDir()) {
|
|
23960
24694
|
try {
|
|
23961
|
-
const raw = await
|
|
24695
|
+
const raw = await fsp5.readFile(getDnsStatePath(rpxDir), "utf8");
|
|
23962
24696
|
const parsed = JSON.parse(raw);
|
|
23963
24697
|
if (parsed.version !== DNS_STATE_VERSION || !Array.isArray(parsed.resolvers))
|
|
23964
24698
|
return null;
|
|
@@ -23976,12 +24710,12 @@ async function loadDnsState(rpxDir = defaultRpxDir()) {
|
|
|
23976
24710
|
}
|
|
23977
24711
|
}
|
|
23978
24712
|
async function saveDnsState(rpxDir, state) {
|
|
23979
|
-
await
|
|
23980
|
-
await
|
|
24713
|
+
await fsp5.mkdir(rpxDir, { recursive: true });
|
|
24714
|
+
await fsp5.writeFile(getDnsStatePath(rpxDir), `${JSON.stringify(state, null, 2)}
|
|
23981
24715
|
`, "utf8");
|
|
23982
24716
|
}
|
|
23983
24717
|
async function clearDnsState(rpxDir) {
|
|
23984
|
-
await
|
|
24718
|
+
await fsp5.rm(getDnsStatePath(rpxDir), { force: true });
|
|
23985
24719
|
}
|
|
23986
24720
|
function normalizeDevDomain(raw) {
|
|
23987
24721
|
const domain = raw.trim().toLowerCase().replace(/\.$/, "");
|
|
@@ -24057,9 +24791,9 @@ __export(exports_dns, {
|
|
|
24057
24791
|
DNS_PORT: () => DNS_PORT
|
|
24058
24792
|
});
|
|
24059
24793
|
import dgram from "node:dgram";
|
|
24060
|
-
import * as
|
|
24061
|
-
import * as
|
|
24062
|
-
import * as
|
|
24794
|
+
import * as fsp6 from "node:fs/promises";
|
|
24795
|
+
import * as path8 from "node:path";
|
|
24796
|
+
import * as process26 from "node:process";
|
|
24063
24797
|
function parseHeader(buffer) {
|
|
24064
24798
|
return {
|
|
24065
24799
|
id: buffer.readUInt16BE(0),
|
|
@@ -24173,7 +24907,7 @@ function buildNxdomainResponse(queryId, question) {
|
|
|
24173
24907
|
return Buffer.concat(parts);
|
|
24174
24908
|
}
|
|
24175
24909
|
async function startDnsServer(domains, verbose) {
|
|
24176
|
-
if (
|
|
24910
|
+
if (process26.platform !== "darwin")
|
|
24177
24911
|
return false;
|
|
24178
24912
|
const devDomains = devDomainsFromHosts(domains);
|
|
24179
24913
|
if (devDomains.length === 0)
|
|
@@ -24293,14 +25027,14 @@ port ${DNS_PORT}
|
|
|
24293
25027
|
`;
|
|
24294
25028
|
}
|
|
24295
25029
|
function resolverFilePath(basename) {
|
|
24296
|
-
return
|
|
25030
|
+
return path8.join(MACOS_RESOLVER_DIR, basename);
|
|
24297
25031
|
}
|
|
24298
25032
|
function contentLooksLikeRpxResolver(content) {
|
|
24299
25033
|
return content.includes("127.0.0.1") && content.includes(String(DNS_PORT));
|
|
24300
25034
|
}
|
|
24301
25035
|
async function readResolverFile(basename) {
|
|
24302
25036
|
try {
|
|
24303
|
-
return await
|
|
25037
|
+
return await fsp6.readFile(resolverFilePath(basename), "utf8");
|
|
24304
25038
|
} catch (err) {
|
|
24305
25039
|
if (err.code === "ENOENT")
|
|
24306
25040
|
return null;
|
|
@@ -24308,7 +25042,7 @@ async function readResolverFile(basename) {
|
|
|
24308
25042
|
}
|
|
24309
25043
|
}
|
|
24310
25044
|
async function flushDnsCache(verbose) {
|
|
24311
|
-
if (
|
|
25045
|
+
if (process26.platform !== "darwin")
|
|
24312
25046
|
return;
|
|
24313
25047
|
const { execSudoSync: execSudoSync2, getSudoPassword: getSudoPassword2, isProcessElevated: isProcessElevated2 } = await Promise.resolve().then(() => (init_utils(), exports_utils));
|
|
24314
25048
|
if (!isProcessElevated2() && !getSudoPassword2()) {
|
|
@@ -24345,7 +25079,7 @@ async function setupResolver(verbose, domains) {
|
|
|
24345
25079
|
return setupDevelopmentDns({ domains: domains ?? [], verbose });
|
|
24346
25080
|
}
|
|
24347
25081
|
async function installResolvers(basenames, verbose) {
|
|
24348
|
-
if (
|
|
25082
|
+
if (process26.platform !== "darwin")
|
|
24349
25083
|
return true;
|
|
24350
25084
|
const { getSudoPassword: getSudoPassword2, isProcessElevated: isProcessElevated2 } = await Promise.resolve().then(() => (init_utils(), exports_utils));
|
|
24351
25085
|
if (!isProcessElevated2() && !getSudoPassword2()) {
|
|
@@ -24363,7 +25097,7 @@ async function installResolvers(basenames, verbose) {
|
|
|
24363
25097
|
}
|
|
24364
25098
|
}
|
|
24365
25099
|
async function uninstallResolvers(basenames, verbose) {
|
|
24366
|
-
if (
|
|
25100
|
+
if (process26.platform !== "darwin")
|
|
24367
25101
|
return;
|
|
24368
25102
|
const { getSudoPassword: getSudoPassword2, isProcessElevated: isProcessElevated2 } = await Promise.resolve().then(() => (init_utils(), exports_utils));
|
|
24369
25103
|
if (!isProcessElevated2() && !getSudoPassword2())
|
|
@@ -24377,7 +25111,7 @@ async function uninstallResolvers(basenames, verbose) {
|
|
|
24377
25111
|
}
|
|
24378
25112
|
}
|
|
24379
25113
|
async function removeLegacyTldResolvers(verbose) {
|
|
24380
|
-
if (
|
|
25114
|
+
if (process26.platform !== "darwin")
|
|
24381
25115
|
return [];
|
|
24382
25116
|
const removed = [];
|
|
24383
25117
|
for (const label of LEGACY_TLD_RESOLVER_LABELS) {
|
|
@@ -24412,7 +25146,7 @@ async function setupDevelopmentDns(opts) {
|
|
|
24412
25146
|
version: DNS_STATE_VERSION,
|
|
24413
25147
|
resolvers: basenames,
|
|
24414
25148
|
domains,
|
|
24415
|
-
ownerPid: opts.ownerPid ??
|
|
25149
|
+
ownerPid: opts.ownerPid ?? process26.pid,
|
|
24416
25150
|
updatedAt: new Date().toISOString()
|
|
24417
25151
|
};
|
|
24418
25152
|
await saveDnsState(rpxDir, state);
|
|
@@ -24436,7 +25170,7 @@ async function syncDevelopmentDnsFromRegistry(entries, opts = {}) {
|
|
|
24436
25170
|
domains,
|
|
24437
25171
|
rpxDir,
|
|
24438
25172
|
verbose: opts.verbose,
|
|
24439
|
-
ownerPid: opts.ownerPid ??
|
|
25173
|
+
ownerPid: opts.ownerPid ?? process26.pid
|
|
24440
25174
|
});
|
|
24441
25175
|
}
|
|
24442
25176
|
async function tearDownDevelopmentDns(opts = {}) {
|
|
@@ -24476,19 +25210,19 @@ var init_dns = __esm(() => {
|
|
|
24476
25210
|
|
|
24477
25211
|
// ../rpx/src/daemon.ts
|
|
24478
25212
|
import { spawn as nodeSpawn } from "node:child_process";
|
|
24479
|
-
import * as
|
|
24480
|
-
import { homedir as
|
|
24481
|
-
import * as
|
|
24482
|
-
import * as
|
|
25213
|
+
import * as fsp7 from "node:fs/promises";
|
|
25214
|
+
import { homedir as homedir12 } from "node:os";
|
|
25215
|
+
import * as path9 from "node:path";
|
|
25216
|
+
import * as process27 from "node:process";
|
|
24483
25217
|
function getDaemonRpxDir() {
|
|
24484
|
-
return
|
|
25218
|
+
return path9.join(homedir12(), ".stacks", "rpx");
|
|
24485
25219
|
}
|
|
24486
25220
|
function getDaemonPidPath(rpxDir = getDaemonRpxDir()) {
|
|
24487
|
-
return
|
|
25221
|
+
return path9.join(rpxDir, "daemon.pid");
|
|
24488
25222
|
}
|
|
24489
25223
|
async function readDaemonPid(rpxDir = getDaemonRpxDir()) {
|
|
24490
25224
|
try {
|
|
24491
|
-
const raw = await
|
|
25225
|
+
const raw = await fsp7.readFile(getDaemonPidPath(rpxDir), "utf8");
|
|
24492
25226
|
const n2 = Number.parseInt(raw.trim(), 10);
|
|
24493
25227
|
if (!Number.isFinite(n2) || n2 <= 0)
|
|
24494
25228
|
return null;
|
|
@@ -24500,14 +25234,14 @@ async function readDaemonPid(rpxDir = getDaemonRpxDir()) {
|
|
|
24500
25234
|
}
|
|
24501
25235
|
}
|
|
24502
25236
|
async function releaseDaemonLock(rpxDir = getDaemonRpxDir()) {
|
|
24503
|
-
await
|
|
25237
|
+
await fsp7.unlink(getDaemonPidPath(rpxDir)).catch(() => {});
|
|
24504
25238
|
}
|
|
24505
25239
|
function defaultDaemonSpawnCommand() {
|
|
24506
|
-
const exec =
|
|
24507
|
-
const interpName =
|
|
25240
|
+
const exec = process27.execPath;
|
|
25241
|
+
const interpName = path9.basename(exec).toLowerCase();
|
|
24508
25242
|
const isInterpreter = interpName === "bun" || interpName === "node" || interpName.startsWith("bun-");
|
|
24509
|
-
if (isInterpreter &&
|
|
24510
|
-
return [exec,
|
|
25243
|
+
if (isInterpreter && process27.argv[1])
|
|
25244
|
+
return [exec, process27.argv[1], "daemon:start"];
|
|
24511
25245
|
return [exec, "daemon:start"];
|
|
24512
25246
|
}
|
|
24513
25247
|
async function ensureDaemonRunning(opts = {}) {
|
|
@@ -24525,7 +25259,7 @@ async function ensureDaemonRunning(opts = {}) {
|
|
|
24525
25259
|
debugLog("daemon", `ensureDaemonRunning: clearing stale pid=${existingPid}`, verbose);
|
|
24526
25260
|
await releaseDaemonLock(rpxDir);
|
|
24527
25261
|
}
|
|
24528
|
-
await
|
|
25262
|
+
await fsp7.mkdir(rpxDir, { recursive: true });
|
|
24529
25263
|
const command = opts.spawnCommand ?? defaultDaemonSpawnCommand();
|
|
24530
25264
|
if (command.length === 0)
|
|
24531
25265
|
throw new Error("ensureDaemonRunning: spawnCommand is empty");
|
|
@@ -24533,8 +25267,8 @@ async function ensureDaemonRunning(opts = {}) {
|
|
|
24533
25267
|
const child = nodeSpawn(command[0], command.slice(1), {
|
|
24534
25268
|
detached: true,
|
|
24535
25269
|
stdio: "ignore",
|
|
24536
|
-
cwd: opts.spawnCwd ??
|
|
24537
|
-
env: opts.spawnEnv ? { ...
|
|
25270
|
+
cwd: opts.spawnCwd ?? process27.cwd(),
|
|
25271
|
+
env: opts.spawnEnv ? { ...process27.env, ...opts.spawnEnv } : process27.env
|
|
24538
25272
|
});
|
|
24539
25273
|
child.unref();
|
|
24540
25274
|
let spawnError = null;
|
|
@@ -24565,15 +25299,18 @@ var init_daemon = __esm(() => {
|
|
|
24565
25299
|
init_host_routes();
|
|
24566
25300
|
init_sni();
|
|
24567
25301
|
init_on_demand();
|
|
25302
|
+
init_site_resolver();
|
|
25303
|
+
init_site_supervisor();
|
|
24568
25304
|
init_static_files();
|
|
25305
|
+
init_auth();
|
|
24569
25306
|
init_registry();
|
|
24570
25307
|
init_dns();
|
|
24571
25308
|
init_utils();
|
|
24572
25309
|
});
|
|
24573
25310
|
|
|
24574
25311
|
// src/index.ts
|
|
24575
|
-
import { exec as exec3, spawn as
|
|
24576
|
-
import * as
|
|
25312
|
+
import { exec as exec3, spawn as spawn3 } from "node:child_process";
|
|
25313
|
+
import * as process34 from "node:process";
|
|
24577
25314
|
import { promisify as promisify2 } from "node:util";
|
|
24578
25315
|
|
|
24579
25316
|
// ../rpx/src/start.ts
|
|
@@ -24581,7 +25318,7 @@ init_logger();
|
|
|
24581
25318
|
import { execSync as execSync4 } from "node:child_process";
|
|
24582
25319
|
import * as http from "node:http";
|
|
24583
25320
|
import * as net2 from "node:net";
|
|
24584
|
-
import * as
|
|
25321
|
+
import * as process31 from "node:process";
|
|
24585
25322
|
|
|
24586
25323
|
// ../rpx/src/colors.ts
|
|
24587
25324
|
var c = (open, close) => (str) => `\x1B[${open}m${str}\x1B[${close}m`;
|
|
@@ -24601,8 +25338,8 @@ init_logger();
|
|
|
24601
25338
|
init_registry();
|
|
24602
25339
|
init_utils();
|
|
24603
25340
|
import * as fs3 from "node:fs";
|
|
24604
|
-
import * as
|
|
24605
|
-
import * as
|
|
25341
|
+
import * as path10 from "node:path";
|
|
25342
|
+
import * as process28 from "node:process";
|
|
24606
25343
|
function deriveIdFromTarget(to, routePath) {
|
|
24607
25344
|
const base = routePath && routePath !== "/" ? `${to}${routePath}` : to;
|
|
24608
25345
|
const cleaned = base.replace(/[^a-zA-Z0-9._-]+/g, "-").replace(/^-+|-+$/g, "").slice(0, 128);
|
|
@@ -24630,8 +25367,8 @@ async function runViaDaemon(opts) {
|
|
|
24630
25367
|
from: p2.from,
|
|
24631
25368
|
to: p2.to,
|
|
24632
25369
|
path: p2.path,
|
|
24633
|
-
pid: opts.persistent ? undefined :
|
|
24634
|
-
cwd:
|
|
25370
|
+
pid: opts.persistent ? undefined : process28.pid,
|
|
25371
|
+
cwd: process28.cwd(),
|
|
24635
25372
|
createdAt,
|
|
24636
25373
|
cleanUrls: p2.cleanUrls,
|
|
24637
25374
|
changeOrigin: p2.changeOrigin,
|
|
@@ -24668,16 +25405,16 @@ async function runViaDaemon(opts) {
|
|
|
24668
25405
|
};
|
|
24669
25406
|
const onSignal = (sig) => {
|
|
24670
25407
|
debugLog("runner", `received ${sig}, unregistering ${idsForCleanup.length} entries`, verbose);
|
|
24671
|
-
cleanup().finally(() =>
|
|
25408
|
+
cleanup().finally(() => process28.exit(0));
|
|
24672
25409
|
};
|
|
24673
|
-
|
|
24674
|
-
|
|
24675
|
-
|
|
25410
|
+
process28.once("SIGINT", onSignal);
|
|
25411
|
+
process28.once("SIGTERM", onSignal);
|
|
25412
|
+
process28.once("exit", () => {
|
|
24676
25413
|
if (cleaned)
|
|
24677
25414
|
return;
|
|
24678
25415
|
for (const id of idsForCleanup) {
|
|
24679
25416
|
try {
|
|
24680
|
-
fs3.unlinkSync(
|
|
25417
|
+
fs3.unlinkSync(path10.join(dirForCleanup, `${id}.json`));
|
|
24681
25418
|
} catch {}
|
|
24682
25419
|
}
|
|
24683
25420
|
});
|
|
@@ -24689,18 +25426,18 @@ init_utils();
|
|
|
24689
25426
|
import { exec } from "node:child_process";
|
|
24690
25427
|
import fs4 from "node:fs";
|
|
24691
25428
|
import os2 from "node:os";
|
|
24692
|
-
import
|
|
24693
|
-
import * as
|
|
25429
|
+
import path11 from "node:path";
|
|
25430
|
+
import * as process29 from "node:process";
|
|
24694
25431
|
import { promisify } from "node:util";
|
|
24695
25432
|
var execAsync = promisify(exec);
|
|
24696
25433
|
function isLoopbackDevelopmentHost(host) {
|
|
24697
25434
|
const normalized = host.trim().toLowerCase();
|
|
24698
25435
|
return normalized === "localhost" || normalized.endsWith(".localhost") || normalized.endsWith(".localhost.");
|
|
24699
25436
|
}
|
|
24700
|
-
var hostsFilePath =
|
|
25437
|
+
var hostsFilePath = process29.platform === "win32" ? path11.join(process29.env.windir || "C:\\Windows", "System32", "drivers", "etc", "hosts") : "/etc/hosts";
|
|
24701
25438
|
var sudoPrivilegesAcquired = false;
|
|
24702
25439
|
async function execSudo(command) {
|
|
24703
|
-
if (
|
|
25440
|
+
if (process29.platform === "win32")
|
|
24704
25441
|
throw new Error("Administrator privileges required on Windows");
|
|
24705
25442
|
if (isProcessElevated()) {
|
|
24706
25443
|
const { stdout } = await execAsync(command);
|
|
@@ -24771,7 +25508,7 @@ async function addHosts(hosts, verbose) {
|
|
|
24771
25508
|
127.0.0.1 ${host}
|
|
24772
25509
|
::1 ${host}`).join(`
|
|
24773
25510
|
`);
|
|
24774
|
-
const tmpFile =
|
|
25511
|
+
const tmpFile = path11.join(os2.tmpdir(), `rpx-hosts-${Date.now()}.tmp`);
|
|
24775
25512
|
try {
|
|
24776
25513
|
await fs4.promises.writeFile(tmpFile, existingContent + hostEntries, "utf8");
|
|
24777
25514
|
await execSudo(`cat "${tmpFile}" | tee "${hostsFilePath}" > /dev/null`);
|
|
@@ -24833,7 +25570,7 @@ async function removeHosts(hosts, verbose) {
|
|
|
24833
25570
|
const newContent = `${filteredLines.join(`
|
|
24834
25571
|
`)}
|
|
24835
25572
|
`;
|
|
24836
|
-
const tmpFile =
|
|
25573
|
+
const tmpFile = path11.join(os2.tmpdir(), `rpx-hosts-${Date.now()}.tmp`);
|
|
24837
25574
|
try {
|
|
24838
25575
|
await fs4.promises.writeFile(tmpFile, newContent, "utf8");
|
|
24839
25576
|
await execSudo(`cat "${tmpFile}" | tee "${hostsFilePath}" > /dev/null`);
|
|
@@ -24882,141 +25619,13 @@ async function checkHosts(hosts, verbose) {
|
|
|
24882
25619
|
|
|
24883
25620
|
// ../rpx/src/start.ts
|
|
24884
25621
|
init_https();
|
|
24885
|
-
|
|
24886
|
-
// ../rpx/src/port-manager.ts
|
|
24887
|
-
init_utils();
|
|
24888
|
-
import * as net from "node:net";
|
|
24889
|
-
function isPortInUse(port, hostname, verbose) {
|
|
24890
|
-
debugLog("port", `Checking if port ${port} is in use on ${hostname}`, verbose);
|
|
24891
|
-
return new Promise((resolve14) => {
|
|
24892
|
-
const server = net.createServer();
|
|
24893
|
-
const timeout = setTimeout(() => {
|
|
24894
|
-
debugLog("port", `Checking port ${port} timed out, assuming it's in use`, verbose);
|
|
24895
|
-
server.close();
|
|
24896
|
-
resolve14(true);
|
|
24897
|
-
}, 3000);
|
|
24898
|
-
server.once("error", (err) => {
|
|
24899
|
-
clearTimeout(timeout);
|
|
24900
|
-
if (err.code === "EADDRINUSE") {
|
|
24901
|
-
debugLog("port", `Port ${port} is in use`, verbose);
|
|
24902
|
-
resolve14(true);
|
|
24903
|
-
} else {
|
|
24904
|
-
debugLog("port", `Error checking port ${port}: ${err.message}`, verbose);
|
|
24905
|
-
resolve14(true);
|
|
24906
|
-
}
|
|
24907
|
-
});
|
|
24908
|
-
server.once("listening", () => {
|
|
24909
|
-
clearTimeout(timeout);
|
|
24910
|
-
debugLog("port", `Port ${port} is available`, verbose);
|
|
24911
|
-
server.close();
|
|
24912
|
-
resolve14(false);
|
|
24913
|
-
});
|
|
24914
|
-
try {
|
|
24915
|
-
server.listen(port, hostname);
|
|
24916
|
-
} catch (err) {
|
|
24917
|
-
clearTimeout(timeout);
|
|
24918
|
-
debugLog("port", `Exception checking port ${port}: ${err}`, verbose);
|
|
24919
|
-
resolve14(true);
|
|
24920
|
-
}
|
|
24921
|
-
});
|
|
24922
|
-
}
|
|
24923
|
-
async function findAvailablePort(startPort, hostname, verbose, maxAttempts = 50) {
|
|
24924
|
-
debugLog("port", `Finding available port starting from ${startPort} (max attempts: ${maxAttempts})`, verbose);
|
|
24925
|
-
let port = startPort;
|
|
24926
|
-
let attempts = 0;
|
|
24927
|
-
while (attempts < maxAttempts) {
|
|
24928
|
-
attempts++;
|
|
24929
|
-
const isInUse = await isPortInUse(port, hostname, verbose);
|
|
24930
|
-
if (!isInUse) {
|
|
24931
|
-
debugLog("port", `Found available port: ${port} after ${attempts} attempts`, verbose);
|
|
24932
|
-
return port;
|
|
24933
|
-
}
|
|
24934
|
-
debugLog("port", `Port ${port} is in use, trying ${port + 1} (attempt ${attempts}/${maxAttempts})`, verbose);
|
|
24935
|
-
port++;
|
|
24936
|
-
}
|
|
24937
|
-
throw new Error(`Unable to find available port after ${maxAttempts} attempts starting from ${startPort}`);
|
|
24938
|
-
}
|
|
24939
|
-
function testPortConnectivity(port, hostname, timeout = 5000, verbose) {
|
|
24940
|
-
debugLog("port", `Testing connection to ${hostname}:${port}`, verbose);
|
|
24941
|
-
return new Promise((resolve14) => {
|
|
24942
|
-
const socket = net.connect({
|
|
24943
|
-
host: hostname,
|
|
24944
|
-
port,
|
|
24945
|
-
timeout
|
|
24946
|
-
});
|
|
24947
|
-
socket.once("connect", () => {
|
|
24948
|
-
debugLog("port", `Successfully connected to ${hostname}:${port}`, verbose);
|
|
24949
|
-
socket.end();
|
|
24950
|
-
resolve14(true);
|
|
24951
|
-
});
|
|
24952
|
-
socket.once("timeout", () => {
|
|
24953
|
-
debugLog("port", `Connection to ${hostname}:${port} timed out`, verbose);
|
|
24954
|
-
socket.destroy();
|
|
24955
|
-
resolve14(false);
|
|
24956
|
-
});
|
|
24957
|
-
socket.once("error", (err) => {
|
|
24958
|
-
debugLog("port", `Failed to connect to ${hostname}:${port}: ${err.message}`, verbose);
|
|
24959
|
-
socket.destroy();
|
|
24960
|
-
resolve14(false);
|
|
24961
|
-
});
|
|
24962
|
-
});
|
|
24963
|
-
}
|
|
24964
|
-
|
|
24965
|
-
class DefaultPortManager {
|
|
24966
|
-
usedPorts = new Set;
|
|
24967
|
-
hostname;
|
|
24968
|
-
verbose;
|
|
24969
|
-
maxRetries;
|
|
24970
|
-
constructor(hostname = "0.0.0.0", verbose, maxRetries = 50) {
|
|
24971
|
-
this.hostname = hostname;
|
|
24972
|
-
this.verbose = verbose;
|
|
24973
|
-
this.maxRetries = maxRetries;
|
|
24974
|
-
}
|
|
24975
|
-
async getNextAvailablePort(startPort, testConnectivity = false) {
|
|
24976
|
-
if (this.usedPorts.has(startPort)) {
|
|
24977
|
-
return this.findNextAvailablePort(startPort + 1, testConnectivity);
|
|
24978
|
-
}
|
|
24979
|
-
const isInUse = await isPortInUse(startPort, this.hostname, this.verbose);
|
|
24980
|
-
if (isInUse) {
|
|
24981
|
-
return this.findNextAvailablePort(startPort + 1, testConnectivity);
|
|
24982
|
-
}
|
|
24983
|
-
if (testConnectivity) {
|
|
24984
|
-
const isConnectable = await testPortConnectivity(startPort, this.hostname, 3000, this.verbose);
|
|
24985
|
-
if (!isConnectable) {
|
|
24986
|
-
debugLog("port", `Port ${startPort} is available but not connectable, trying next port`, this.verbose);
|
|
24987
|
-
return this.findNextAvailablePort(startPort + 1, testConnectivity);
|
|
24988
|
-
}
|
|
24989
|
-
}
|
|
24990
|
-
this.usedPorts.add(startPort);
|
|
24991
|
-
return startPort;
|
|
24992
|
-
}
|
|
24993
|
-
async findNextAvailablePort(startPort, testConnectivity = false) {
|
|
24994
|
-
const port = await findAvailablePort(startPort, this.hostname, this.verbose, this.maxRetries);
|
|
24995
|
-
if (testConnectivity) {
|
|
24996
|
-
const isConnectable = await testPortConnectivity(port, this.hostname, 3000, this.verbose);
|
|
24997
|
-
if (!isConnectable) {
|
|
24998
|
-
if (port < startPort + this.maxRetries) {
|
|
24999
|
-
return this.findNextAvailablePort(port + 1, testConnectivity);
|
|
25000
|
-
} else {
|
|
25001
|
-
throw new Error(`Unable to find a connectable port after ${this.maxRetries} attempts`);
|
|
25002
|
-
}
|
|
25003
|
-
}
|
|
25004
|
-
}
|
|
25005
|
-
this.usedPorts.add(port);
|
|
25006
|
-
return port;
|
|
25007
|
-
}
|
|
25008
|
-
releasePort(port) {
|
|
25009
|
-
debugLog("port", `Releasing port ${port}`, this.verbose);
|
|
25010
|
-
this.usedPorts.delete(port);
|
|
25011
|
-
}
|
|
25012
|
-
}
|
|
25013
|
-
var portManager = new DefaultPortManager;
|
|
25622
|
+
init_port_manager();
|
|
25014
25623
|
|
|
25015
25624
|
// ../rpx/src/process-manager.ts
|
|
25016
25625
|
init_logger();
|
|
25017
25626
|
init_utils();
|
|
25018
|
-
import { spawn } from "node:child_process";
|
|
25019
|
-
import * as
|
|
25627
|
+
import { spawn as spawn2 } from "node:child_process";
|
|
25628
|
+
import * as process30 from "node:process";
|
|
25020
25629
|
|
|
25021
25630
|
class ProcessManager {
|
|
25022
25631
|
processes = new Map;
|
|
@@ -25027,14 +25636,14 @@ class ProcessManager {
|
|
|
25027
25636
|
return;
|
|
25028
25637
|
}
|
|
25029
25638
|
const [cmd, ...args] = options.command.split(" ");
|
|
25030
|
-
const cwd4 = options.cwd ||
|
|
25639
|
+
const cwd4 = options.cwd || process30.cwd();
|
|
25031
25640
|
debugLog("start", `Starting process ${id}:`, verbose);
|
|
25032
25641
|
debugLog("start", ` Command: ${cmd} ${args.join(" ")}`, verbose);
|
|
25033
25642
|
debugLog("start", ` Working directory: ${cwd4}`, verbose);
|
|
25034
25643
|
debugLog("start", ` Environment variables: ${safeStringify(options.env)}`, verbose);
|
|
25035
|
-
const childProcess =
|
|
25644
|
+
const childProcess = spawn2(cmd, args, {
|
|
25036
25645
|
cwd: cwd4,
|
|
25037
|
-
env: { ...
|
|
25646
|
+
env: { ...process30.env, ...options.env },
|
|
25038
25647
|
shell: true,
|
|
25039
25648
|
stdio: "inherit"
|
|
25040
25649
|
});
|
|
@@ -25134,7 +25743,7 @@ class ProcessManager {
|
|
|
25134
25743
|
var processManager = new ProcessManager;
|
|
25135
25744
|
|
|
25136
25745
|
// ../rpx/src/origin-guard.ts
|
|
25137
|
-
import { timingSafeEqual } from "node:crypto";
|
|
25746
|
+
import { timingSafeEqual as timingSafeEqual2 } from "node:crypto";
|
|
25138
25747
|
function secretMatches(provided, expected) {
|
|
25139
25748
|
if (provided == null)
|
|
25140
25749
|
return false;
|
|
@@ -25142,7 +25751,7 @@ function secretMatches(provided, expected) {
|
|
|
25142
25751
|
const b2 = Buffer.from(expected);
|
|
25143
25752
|
if (a2.length !== b2.length)
|
|
25144
25753
|
return false;
|
|
25145
|
-
return
|
|
25754
|
+
return timingSafeEqual2(a2, b2);
|
|
25146
25755
|
}
|
|
25147
25756
|
function normalizeHost(host) {
|
|
25148
25757
|
return host.toLowerCase().replace(/\.$/, "");
|
|
@@ -25196,6 +25805,26 @@ function createOriginGuard(options) {
|
|
|
25196
25805
|
|
|
25197
25806
|
// ../rpx/src/start.ts
|
|
25198
25807
|
init_proxy_handler();
|
|
25808
|
+
|
|
25809
|
+
// ../rpx/src/acme-challenge.ts
|
|
25810
|
+
import * as fs5 from "node:fs";
|
|
25811
|
+
import * as path12 from "node:path";
|
|
25812
|
+
var ACME_CHALLENGE_PREFIX2 = "/.well-known/acme-challenge/";
|
|
25813
|
+
function readAcmeChallenge(webroot, pathname) {
|
|
25814
|
+
if (!webroot || !pathname.startsWith(ACME_CHALLENGE_PREFIX2))
|
|
25815
|
+
return null;
|
|
25816
|
+
const token = pathname.slice(ACME_CHALLENGE_PREFIX2.length);
|
|
25817
|
+
if (!token || !/^[A-Za-z0-9_-]+$/.test(token))
|
|
25818
|
+
return null;
|
|
25819
|
+
try {
|
|
25820
|
+
return fs5.readFileSync(path12.join(webroot, token), "utf8");
|
|
25821
|
+
} catch {
|
|
25822
|
+
return null;
|
|
25823
|
+
}
|
|
25824
|
+
}
|
|
25825
|
+
|
|
25826
|
+
// ../rpx/src/start.ts
|
|
25827
|
+
init_auth();
|
|
25199
25828
|
init_host_routes();
|
|
25200
25829
|
init_sni();
|
|
25201
25830
|
init_static_files();
|
|
@@ -25288,8 +25917,8 @@ async function cleanup(options) {
|
|
|
25288
25917
|
cleanupPromiseResolve = null;
|
|
25289
25918
|
isCleaningUp = false;
|
|
25290
25919
|
const isVitePluginCall = options && "vitePluginUsage" in options && options.vitePluginUsage === true;
|
|
25291
|
-
if (
|
|
25292
|
-
|
|
25920
|
+
if (process31.env.NODE_ENV !== "test" && process31.env.BUN_ENV !== "test" && !isVitePluginCall) {
|
|
25921
|
+
process31.exit(0);
|
|
25293
25922
|
}
|
|
25294
25923
|
}
|
|
25295
25924
|
return cleanupPromise;
|
|
@@ -25298,31 +25927,31 @@ var isHandlingSignal = false;
|
|
|
25298
25927
|
function signalHandler(signal) {
|
|
25299
25928
|
if (isHandlingSignal) {
|
|
25300
25929
|
debugLog("signal", `Received second ${signal} signal, forcing exit`, true);
|
|
25301
|
-
|
|
25930
|
+
process31.exit(1);
|
|
25302
25931
|
return;
|
|
25303
25932
|
}
|
|
25304
25933
|
isHandlingSignal = true;
|
|
25305
25934
|
debugLog("signal", `Received ${signal} signal, initiating cleanup`, true);
|
|
25306
25935
|
cleanup().catch((err) => {
|
|
25307
25936
|
debugLog("signal", `Cleanup failed after ${signal}: ${err}`, true);
|
|
25308
|
-
|
|
25937
|
+
process31.exit(1);
|
|
25309
25938
|
}).finally(() => {
|
|
25310
25939
|
isHandlingSignal = false;
|
|
25311
25940
|
});
|
|
25312
25941
|
}
|
|
25313
|
-
|
|
25314
|
-
|
|
25315
|
-
|
|
25942
|
+
process31.once("SIGINT", () => signalHandler("SIGINT"));
|
|
25943
|
+
process31.once("SIGTERM", () => signalHandler("SIGTERM"));
|
|
25944
|
+
process31.on("uncaughtException", (err) => {
|
|
25316
25945
|
log.error("Uncaught exception (continuing):", err);
|
|
25317
25946
|
});
|
|
25318
|
-
|
|
25947
|
+
process31.on("unhandledRejection", (reason) => {
|
|
25319
25948
|
log.error("Unhandled rejection (continuing):", reason);
|
|
25320
25949
|
});
|
|
25321
25950
|
async function testConnection(hostname, port, verbose, retries = 5) {
|
|
25322
25951
|
debugLog("connection", `Testing connection to ${hostname}:${port} (retries left: ${retries})`, verbose);
|
|
25323
25952
|
const maxTestDuration = 15000;
|
|
25324
25953
|
const startTime = Date.now();
|
|
25325
|
-
if (
|
|
25954
|
+
if (process31.env.RPX_BYPASS_CONNECTION_TEST === "true") {
|
|
25326
25955
|
debugLog("connection", `Bypassing connection test for ${hostname}:${port} due to RPX_BYPASS_CONNECTION_TEST flag`, verbose);
|
|
25327
25956
|
return;
|
|
25328
25957
|
}
|
|
@@ -25418,7 +26047,7 @@ async function startServer(options) {
|
|
|
25418
26047
|
log.warn("You can manually add this entry to your hosts file:");
|
|
25419
26048
|
log.warn(`127.0.0.1 ${toUrl.hostname}`);
|
|
25420
26049
|
log.warn(`::1 ${toUrl.hostname}`);
|
|
25421
|
-
if (
|
|
26050
|
+
if (process31.platform === "win32") {
|
|
25422
26051
|
log.warn("On Windows:");
|
|
25423
26052
|
log.warn("1. Run notepad as administrator");
|
|
25424
26053
|
log.warn("2. Open C:\\Windows\\System32\\drivers\\etc\\hosts");
|
|
@@ -25491,7 +26120,7 @@ async function startServer(options) {
|
|
|
25491
26120
|
ssl: sslConfig
|
|
25492
26121
|
});
|
|
25493
26122
|
}
|
|
25494
|
-
async function createProxyServer(from, to, listenPort, sourceUrl, ssl, vitePluginUsage, verbose, cleanUrls, changeOrigin) {
|
|
26123
|
+
async function createProxyServer(from, to, listenPort, sourceUrl, ssl, vitePluginUsage, verbose, cleanUrls, changeOrigin, auth) {
|
|
25495
26124
|
debugLog("proxy", `Creating proxy server ${from} -> ${to} with cleanUrls: ${cleanUrls}`, verbose);
|
|
25496
26125
|
const routeEntries = [{
|
|
25497
26126
|
host: to,
|
|
@@ -25499,7 +26128,8 @@ async function createProxyServer(from, to, listenPort, sourceUrl, ssl, vitePlugi
|
|
|
25499
26128
|
sourceHost: sourceUrl.host,
|
|
25500
26129
|
cleanUrls: cleanUrls || false,
|
|
25501
26130
|
changeOrigin: changeOrigin || false,
|
|
25502
|
-
basePath: "/"
|
|
26131
|
+
basePath: "/",
|
|
26132
|
+
auth
|
|
25503
26133
|
}
|
|
25504
26134
|
}];
|
|
25505
26135
|
const server = createSharedProxyServer({ routeEntries, listenPort, sslConfig: ssl, originGuard: null, verbose: verbose ?? false });
|
|
@@ -25537,7 +26167,7 @@ async function setupProxy(options) {
|
|
|
25537
26167
|
}
|
|
25538
26168
|
}
|
|
25539
26169
|
} else {
|
|
25540
|
-
if (hostsEnabled &&
|
|
26170
|
+
if (hostsEnabled && process31.platform !== "darwin" && to && to.includes("localhost") && !to.match(/^(localhost|127\.0\.0\.1)$/)) {
|
|
25541
26171
|
const hostsExist = await checkHosts([to], verbose);
|
|
25542
26172
|
if (!hostsExist[0]) {
|
|
25543
26173
|
debugLog("hosts", `${to} not found in hosts file, adding...`, verbose);
|
|
@@ -25584,7 +26214,7 @@ async function setupProxy(options) {
|
|
|
25584
26214
|
portManager2.usedPorts.add(finalPort);
|
|
25585
26215
|
debugLog("setup", `Using standard ${targetPort === 443 ? "HTTPS" : "HTTP"} port ${targetPort} for ${to}`, verbose);
|
|
25586
26216
|
}
|
|
25587
|
-
await createProxyServer(from, to, finalPort, sourceUrl, ssl, vitePluginUsage, verbose, cleanUrls, changeOrigin);
|
|
26217
|
+
await createProxyServer(from, to, finalPort, sourceUrl, ssl, vitePluginUsage, verbose, cleanUrls, changeOrigin, resolveAuth(options.auth));
|
|
25588
26218
|
} catch (err) {
|
|
25589
26219
|
debugLog("setup", `Setup failed: ${err}`, verbose);
|
|
25590
26220
|
log.error(`Failed to setup reverse proxy: ${err.message}`);
|
|
@@ -25597,9 +26227,19 @@ async function setupProxy(options) {
|
|
|
25597
26227
|
});
|
|
25598
26228
|
}
|
|
25599
26229
|
}
|
|
25600
|
-
function startHttpRedirectServer(verbose, httpPort = 80, httpsPort = 443) {
|
|
26230
|
+
function startHttpRedirectServer(verbose, httpPort = 80, httpsPort = 443, acmeChallengeWebroot) {
|
|
25601
26231
|
debugLog("redirect", `Starting HTTP redirect server on port ${httpPort}`, verbose);
|
|
25602
26232
|
const server = http.createServer((req, res) => {
|
|
26233
|
+
if (acmeChallengeWebroot && req.url) {
|
|
26234
|
+
const pathname = req.url.split("?", 1)[0];
|
|
26235
|
+
const keyAuth = readAcmeChallenge(acmeChallengeWebroot, pathname);
|
|
26236
|
+
if (keyAuth != null) {
|
|
26237
|
+
debugLog("redirect", `Serving ACME challenge ${pathname}`, verbose);
|
|
26238
|
+
res.writeHead(200, { "content-type": "text/plain" });
|
|
26239
|
+
res.end(keyAuth);
|
|
26240
|
+
return;
|
|
26241
|
+
}
|
|
26242
|
+
}
|
|
25603
26243
|
const rawHost = req.headers.host || "";
|
|
25604
26244
|
const hostname = rawHost.includes(":") ? rawHost.slice(0, rawHost.indexOf(":")) : rawHost;
|
|
25605
26245
|
const target = httpsPort === 443 ? hostname : `${hostname}:${httpsPort}`;
|
|
@@ -25726,7 +26366,7 @@ async function startProxies(options) {
|
|
|
25726
26366
|
debugLog("watch", "No start command found in config", verbose);
|
|
25727
26367
|
}
|
|
25728
26368
|
const primaryDomain = "proxies" in mergedOptions && Array.isArray(mergedOptions.proxies) ? mergedOptions.proxies[0]?.to : ("to" in mergedOptions) ? mergedOptions.to : "rpx.localhost";
|
|
25729
|
-
if (
|
|
26369
|
+
if (process31.platform !== "win32" && (mergedOptions.https || hostsEnabled)) {
|
|
25730
26370
|
const sudoPassword = getSudoPassword();
|
|
25731
26371
|
if (!sudoPassword) {
|
|
25732
26372
|
try {
|
|
@@ -25776,6 +26416,7 @@ async function startProxies(options) {
|
|
|
25776
26416
|
vitePluginUsage: mergedOptions.vitePluginUsage,
|
|
25777
26417
|
start: "start" in mergedOptions ? mergedOptions.start : undefined,
|
|
25778
26418
|
changeOrigin: mergedOptions.changeOrigin,
|
|
26419
|
+
auth: "auth" in mergedOptions ? mergedOptions.auth : undefined,
|
|
25779
26420
|
verbose,
|
|
25780
26421
|
_cachedSSLConfig: mergedOptions._cachedSSLConfig
|
|
25781
26422
|
}];
|
|
@@ -25791,7 +26432,7 @@ async function startProxies(options) {
|
|
|
25791
26432
|
log.info(` These TLDs have HSTS preloading which can bypass local DNS`);
|
|
25792
26433
|
log.info(` Consider using reserved TLDs: .test, .localhost, or .local`);
|
|
25793
26434
|
}
|
|
25794
|
-
if (hostsEnabled &&
|
|
26435
|
+
if (hostsEnabled && process31.platform === "darwin" && customDomains.length > 0) {
|
|
25795
26436
|
const { setupDevelopmentDns: setupDevelopmentDns2 } = await Promise.resolve().then(() => (init_dns(), exports_dns));
|
|
25796
26437
|
const dnsStarted = await setupDevelopmentDns2({ domains: customDomains, verbose });
|
|
25797
26438
|
if (dnsStarted) {
|
|
@@ -25827,8 +26468,8 @@ async function startProxies(options) {
|
|
|
25827
26468
|
verbose: mergedOptions.verbose || false
|
|
25828
26469
|
});
|
|
25829
26470
|
};
|
|
25830
|
-
|
|
25831
|
-
|
|
26471
|
+
process31.on("SIGINT", cleanupHandler);
|
|
26472
|
+
process31.on("SIGTERM", cleanupHandler);
|
|
25832
26473
|
const singlePortMode = mergedOptions.singlePortMode === true;
|
|
25833
26474
|
const httpsPort = mergedOptions.httpsPort ?? 443;
|
|
25834
26475
|
const httpPort = mergedOptions.httpPort ?? 80;
|
|
@@ -25840,7 +26481,7 @@ async function startProxies(options) {
|
|
|
25840
26481
|
const routeEntries = await collectRouteEntries(proxyOptions, hostsEnabled, verbose);
|
|
25841
26482
|
const isHttpPortBusy = await isPortInUse(httpPort, "0.0.0.0", verbose);
|
|
25842
26483
|
if (!isHttpPortBusy) {
|
|
25843
|
-
startHttpRedirectServer(verbose, httpPort, httpsPort);
|
|
26484
|
+
startHttpRedirectServer(verbose, httpPort, httpsPort, mergedOptions.acmeChallengeWebroot);
|
|
25844
26485
|
}
|
|
25845
26486
|
const isPortBusy = await isPortInUse(httpsPort, "0.0.0.0", verbose);
|
|
25846
26487
|
if (isPortBusy) {
|
|
@@ -25900,13 +26541,22 @@ async function collectRouteEntries(proxyOptions, hostsEnabled, verbose) {
|
|
|
25900
26541
|
const cleanUrls = option.cleanUrls || false;
|
|
25901
26542
|
const routePath = option.path;
|
|
25902
26543
|
const basePath = normalizePathPrefix(routePath);
|
|
25903
|
-
|
|
26544
|
+
const auth = resolveAuth(option.auth);
|
|
26545
|
+
if (option.redirect) {
|
|
26546
|
+
const redirect = resolveRedirect(option.redirect);
|
|
26547
|
+
routeEntries.push({
|
|
26548
|
+
host: domain,
|
|
26549
|
+
path: routePath,
|
|
26550
|
+
route: { redirect, basePath, auth }
|
|
26551
|
+
});
|
|
26552
|
+
debugLog("proxies", `Route: ${domain}${routePath ?? ""} → redirect ${redirect.status} ${redirect.to}${auth ? " (auth)" : ""}`, verbose);
|
|
26553
|
+
} else if (option.static) {
|
|
25904
26554
|
routeEntries.push({
|
|
25905
26555
|
host: domain,
|
|
25906
26556
|
path: routePath,
|
|
25907
|
-
route: { static: resolveStaticRoute(option.static, cleanUrls), cleanUrls, basePath }
|
|
26557
|
+
route: { static: resolveStaticRoute(option.static, cleanUrls), cleanUrls, basePath, auth }
|
|
25908
26558
|
});
|
|
25909
|
-
debugLog("proxies", `Route: ${domain}${routePath ?? ""} → static ${typeof option.static === "string" ? option.static : option.static.dir}`, verbose);
|
|
26559
|
+
debugLog("proxies", `Route: ${domain}${routePath ?? ""} → static ${typeof option.static === "string" ? option.static : option.static.dir}${auth ? " (auth)" : ""}`, verbose);
|
|
25910
26560
|
} else {
|
|
25911
26561
|
const fromUrl = new URL(option.from?.startsWith("http") ? option.from : `http://${option.from}`);
|
|
25912
26562
|
routeEntries.push({
|
|
@@ -25917,10 +26567,11 @@ async function collectRouteEntries(proxyOptions, hostsEnabled, verbose) {
|
|
|
25917
26567
|
cleanUrls,
|
|
25918
26568
|
changeOrigin: option.changeOrigin || false,
|
|
25919
26569
|
pathRewrites: option.pathRewrites,
|
|
25920
|
-
basePath
|
|
26570
|
+
basePath,
|
|
26571
|
+
auth
|
|
25921
26572
|
}
|
|
25922
26573
|
});
|
|
25923
|
-
debugLog("proxies", `Route: ${domain}${routePath ?? ""} → ${fromUrl.host}`, verbose);
|
|
26574
|
+
debugLog("proxies", `Route: ${domain}${routePath ?? ""} → ${fromUrl.host}${auth ? " (auth)" : ""}`, verbose);
|
|
25924
26575
|
}
|
|
25925
26576
|
if (seenDomains.has(domain)) {
|
|
25926
26577
|
continue;
|
|
@@ -25998,15 +26649,19 @@ init_config();
|
|
|
25998
26649
|
init_https();
|
|
25999
26650
|
init_macos_trust();
|
|
26000
26651
|
init_cert_inspect();
|
|
26652
|
+
init_port_manager();
|
|
26001
26653
|
init_registry();
|
|
26002
26654
|
init_dns();
|
|
26003
26655
|
init_dns_state();
|
|
26004
26656
|
init_daemon();
|
|
26005
26657
|
init_proxy_handler();
|
|
26658
|
+
init_auth();
|
|
26006
26659
|
init_host_routes();
|
|
26007
26660
|
init_static_files();
|
|
26008
26661
|
init_sni();
|
|
26009
26662
|
init_on_demand();
|
|
26663
|
+
init_site_resolver();
|
|
26664
|
+
init_site_supervisor();
|
|
26010
26665
|
init_utils();
|
|
26011
26666
|
|
|
26012
26667
|
// src/simplified-plugin.ts
|
|
@@ -26167,11 +26822,11 @@ function VitePluginRpx(options) {
|
|
|
26167
26822
|
if (!hasSudoAccess) {
|
|
26168
26823
|
const origLog = console.log;
|
|
26169
26824
|
console.log = () => {};
|
|
26170
|
-
|
|
26825
|
+
process34.stdout.write(`
|
|
26171
26826
|
Sudo access required for proxy setup.
|
|
26172
26827
|
`);
|
|
26173
26828
|
const gotSudoAccess = await new Promise((resolve14) => {
|
|
26174
|
-
const sudo =
|
|
26829
|
+
const sudo = spawn3("sudo", ["true"], {
|
|
26175
26830
|
stdio: "inherit"
|
|
26176
26831
|
});
|
|
26177
26832
|
sudo.on("exit", (code) => {
|
|
@@ -26181,7 +26836,7 @@ Sudo access required for proxy setup.
|
|
|
26181
26836
|
console.log = origLog;
|
|
26182
26837
|
if (!gotSudoAccess) {
|
|
26183
26838
|
console.error("Failed to get sudo access. Please try again.");
|
|
26184
|
-
|
|
26839
|
+
process34.exit(1);
|
|
26185
26840
|
}
|
|
26186
26841
|
}
|
|
26187
26842
|
}
|
|
@@ -26191,10 +26846,10 @@ Sudo access required for proxy setup.
|
|
|
26191
26846
|
handleSignal("CLOSE").catch(console.error);
|
|
26192
26847
|
});
|
|
26193
26848
|
}
|
|
26194
|
-
const registeredEvents =
|
|
26849
|
+
const registeredEvents = process34.listeners("SIGINT").length > 0 && process34.listeners("SIGTERM").length > 0;
|
|
26195
26850
|
if (!registeredEvents) {
|
|
26196
|
-
|
|
26197
|
-
|
|
26851
|
+
process34.once("SIGINT", () => handleSignal("SIGINT").catch(console.error));
|
|
26852
|
+
process34.once("SIGTERM", () => handleSignal("SIGTERM").catch(console.error));
|
|
26198
26853
|
}
|
|
26199
26854
|
const colorUrl = (url) => colors.cyan(url.replace(/:(\d+)\//, (_, port) => `:${colors.bold(port)}/`));
|
|
26200
26855
|
const originalPrintUrls = serverInstance.printUrls;
|
|
@@ -26232,7 +26887,7 @@ Sudo access required for proxy setup.
|
|
|
26232
26887
|
debug("Proxy setup complete");
|
|
26233
26888
|
} catch (error) {
|
|
26234
26889
|
console.error("Failed to start reverse proxy:", error);
|
|
26235
|
-
|
|
26890
|
+
process34.exit(1);
|
|
26236
26891
|
}
|
|
26237
26892
|
};
|
|
26238
26893
|
if (serverInstance.httpServer) {
|