vite-plugin-rpx 0.11.20 → 0.11.21
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 +854 -222
- 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());
|
|
@@ -23231,6 +23383,27 @@ var init_proxy_pool = __esm(() => {
|
|
|
23231
23383
|
pools = new Map;
|
|
23232
23384
|
});
|
|
23233
23385
|
|
|
23386
|
+
// ../rpx/src/redirect.ts
|
|
23387
|
+
function resolveRedirect(input) {
|
|
23388
|
+
const cfg = typeof input === "string" ? { to: input } : input;
|
|
23389
|
+
let to = (cfg.to ?? "").trim();
|
|
23390
|
+
if (to && !/^[a-z][\w+.-]*:\/\//i.test(to))
|
|
23391
|
+
to = `https://${to}`;
|
|
23392
|
+
to = to.replace(/\/+$/, "");
|
|
23393
|
+
return {
|
|
23394
|
+
to,
|
|
23395
|
+
status: cfg.status ?? DEFAULT_REDIRECT_STATUS,
|
|
23396
|
+
preservePath: cfg.preservePath ?? true
|
|
23397
|
+
};
|
|
23398
|
+
}
|
|
23399
|
+
function buildRedirectLocation(redirect, pathname, search) {
|
|
23400
|
+
if (!redirect.preservePath)
|
|
23401
|
+
return redirect.to || "/";
|
|
23402
|
+
const tail = `${pathname}${search}`;
|
|
23403
|
+
return tail === "/" ? `${redirect.to}/` : `${redirect.to}${tail}`;
|
|
23404
|
+
}
|
|
23405
|
+
var DEFAULT_REDIRECT_STATUS = 301;
|
|
23406
|
+
|
|
23234
23407
|
// ../rpx/src/static-files.ts
|
|
23235
23408
|
import * as path2 from "node:path";
|
|
23236
23409
|
function resolveStaticRoute(cfg, cleanUrls) {
|
|
@@ -23384,15 +23557,34 @@ function splitPathQuery(rawUrl) {
|
|
|
23384
23557
|
return { pathname: rawUrl.slice(pathStart), search: "" };
|
|
23385
23558
|
return { pathname: rawUrl.slice(pathStart, q), search: rawUrl.slice(q) };
|
|
23386
23559
|
}
|
|
23387
|
-
function createProxyFetchHandler(getRoute, verbose) {
|
|
23560
|
+
function createProxyFetchHandler(getRoute, verbose, onNoRoute) {
|
|
23388
23561
|
const inner = async (req, server) => {
|
|
23389
23562
|
const { pathname, search } = splitPathQuery(req.url);
|
|
23390
23563
|
const hostname = extractHostname2(req);
|
|
23391
|
-
|
|
23564
|
+
let route = getRoute(hostname, pathname);
|
|
23565
|
+
if (!route && onNoRoute) {
|
|
23566
|
+
const outcome = await onNoRoute(hostname, pathname, req);
|
|
23567
|
+
if (outcome instanceof Response)
|
|
23568
|
+
return outcome;
|
|
23569
|
+
if (outcome && outcome.retry)
|
|
23570
|
+
route = getRoute(hostname, pathname);
|
|
23571
|
+
}
|
|
23392
23572
|
if (!route) {
|
|
23393
23573
|
debugLog("request", `No route found for host: ${hostname}`, verbose);
|
|
23394
23574
|
return new Response(`No proxy configured for ${hostname}`, { status: 404 });
|
|
23395
23575
|
}
|
|
23576
|
+
if (route.auth) {
|
|
23577
|
+
const challenge = enforceBasicAuth(req, pathname, route.auth);
|
|
23578
|
+
if (challenge) {
|
|
23579
|
+
debugLog("request", `401 challenge for ${hostname}${pathname}`, verbose);
|
|
23580
|
+
return challenge;
|
|
23581
|
+
}
|
|
23582
|
+
}
|
|
23583
|
+
if (route.redirect) {
|
|
23584
|
+
const location = buildRedirectLocation(route.redirect, pathname, search);
|
|
23585
|
+
debugLog("request", `${route.redirect.status} redirect ${hostname}${pathname} → ${location}`, verbose);
|
|
23586
|
+
return new Response(null, { status: route.redirect.status, headers: { location } });
|
|
23587
|
+
}
|
|
23396
23588
|
if (route.static) {
|
|
23397
23589
|
const strip = route.stripBasePathPrefix ?? true;
|
|
23398
23590
|
const staticPath = strip ? stripBasePath(pathname, route.basePath) : pathname;
|
|
@@ -23544,6 +23736,7 @@ function createProxyWebSocketHandler(verbose) {
|
|
|
23544
23736
|
}
|
|
23545
23737
|
var HOP_BY_HOP;
|
|
23546
23738
|
var init_proxy_handler = __esm(() => {
|
|
23739
|
+
init_auth();
|
|
23547
23740
|
init_proxy_pool();
|
|
23548
23741
|
init_static_files();
|
|
23549
23742
|
init_utils();
|
|
@@ -23875,6 +24068,144 @@ var init_on_demand = __esm(() => {
|
|
|
23875
24068
|
init_utils();
|
|
23876
24069
|
});
|
|
23877
24070
|
|
|
24071
|
+
// ../rpx/src/site-resolver.ts
|
|
24072
|
+
var DEFAULT_IDLE_TIMEOUT_MS;
|
|
24073
|
+
var init_site_resolver = __esm(() => {
|
|
24074
|
+
DEFAULT_IDLE_TIMEOUT_MS = 30 * 60000;
|
|
24075
|
+
});
|
|
24076
|
+
|
|
24077
|
+
// ../rpx/src/port-manager.ts
|
|
24078
|
+
import * as net from "node:net";
|
|
24079
|
+
function isPortInUse(port, hostname, verbose) {
|
|
24080
|
+
debugLog("port", `Checking if port ${port} is in use on ${hostname}`, verbose);
|
|
24081
|
+
return new Promise((resolve14) => {
|
|
24082
|
+
const server = net.createServer();
|
|
24083
|
+
const timeout = setTimeout(() => {
|
|
24084
|
+
debugLog("port", `Checking port ${port} timed out, assuming it's in use`, verbose);
|
|
24085
|
+
server.close();
|
|
24086
|
+
resolve14(true);
|
|
24087
|
+
}, 3000);
|
|
24088
|
+
server.once("error", (err) => {
|
|
24089
|
+
clearTimeout(timeout);
|
|
24090
|
+
if (err.code === "EADDRINUSE") {
|
|
24091
|
+
debugLog("port", `Port ${port} is in use`, verbose);
|
|
24092
|
+
resolve14(true);
|
|
24093
|
+
} else {
|
|
24094
|
+
debugLog("port", `Error checking port ${port}: ${err.message}`, verbose);
|
|
24095
|
+
resolve14(true);
|
|
24096
|
+
}
|
|
24097
|
+
});
|
|
24098
|
+
server.once("listening", () => {
|
|
24099
|
+
clearTimeout(timeout);
|
|
24100
|
+
debugLog("port", `Port ${port} is available`, verbose);
|
|
24101
|
+
server.close();
|
|
24102
|
+
resolve14(false);
|
|
24103
|
+
});
|
|
24104
|
+
try {
|
|
24105
|
+
server.listen(port, hostname);
|
|
24106
|
+
} catch (err) {
|
|
24107
|
+
clearTimeout(timeout);
|
|
24108
|
+
debugLog("port", `Exception checking port ${port}: ${err}`, verbose);
|
|
24109
|
+
resolve14(true);
|
|
24110
|
+
}
|
|
24111
|
+
});
|
|
24112
|
+
}
|
|
24113
|
+
async function findAvailablePort(startPort, hostname, verbose, maxAttempts = 50) {
|
|
24114
|
+
debugLog("port", `Finding available port starting from ${startPort} (max attempts: ${maxAttempts})`, verbose);
|
|
24115
|
+
let port = startPort;
|
|
24116
|
+
let attempts = 0;
|
|
24117
|
+
while (attempts < maxAttempts) {
|
|
24118
|
+
attempts++;
|
|
24119
|
+
const isInUse = await isPortInUse(port, hostname, verbose);
|
|
24120
|
+
if (!isInUse) {
|
|
24121
|
+
debugLog("port", `Found available port: ${port} after ${attempts} attempts`, verbose);
|
|
24122
|
+
return port;
|
|
24123
|
+
}
|
|
24124
|
+
debugLog("port", `Port ${port} is in use, trying ${port + 1} (attempt ${attempts}/${maxAttempts})`, verbose);
|
|
24125
|
+
port++;
|
|
24126
|
+
}
|
|
24127
|
+
throw new Error(`Unable to find available port after ${maxAttempts} attempts starting from ${startPort}`);
|
|
24128
|
+
}
|
|
24129
|
+
function testPortConnectivity(port, hostname, timeout = 5000, verbose) {
|
|
24130
|
+
debugLog("port", `Testing connection to ${hostname}:${port}`, verbose);
|
|
24131
|
+
return new Promise((resolve14) => {
|
|
24132
|
+
const socket = net.connect({
|
|
24133
|
+
host: hostname,
|
|
24134
|
+
port,
|
|
24135
|
+
timeout
|
|
24136
|
+
});
|
|
24137
|
+
socket.once("connect", () => {
|
|
24138
|
+
debugLog("port", `Successfully connected to ${hostname}:${port}`, verbose);
|
|
24139
|
+
socket.end();
|
|
24140
|
+
resolve14(true);
|
|
24141
|
+
});
|
|
24142
|
+
socket.once("timeout", () => {
|
|
24143
|
+
debugLog("port", `Connection to ${hostname}:${port} timed out`, verbose);
|
|
24144
|
+
socket.destroy();
|
|
24145
|
+
resolve14(false);
|
|
24146
|
+
});
|
|
24147
|
+
socket.once("error", (err) => {
|
|
24148
|
+
debugLog("port", `Failed to connect to ${hostname}:${port}: ${err.message}`, verbose);
|
|
24149
|
+
socket.destroy();
|
|
24150
|
+
resolve14(false);
|
|
24151
|
+
});
|
|
24152
|
+
});
|
|
24153
|
+
}
|
|
24154
|
+
|
|
24155
|
+
class DefaultPortManager {
|
|
24156
|
+
usedPorts = new Set;
|
|
24157
|
+
hostname;
|
|
24158
|
+
verbose;
|
|
24159
|
+
maxRetries;
|
|
24160
|
+
constructor(hostname = "0.0.0.0", verbose, maxRetries = 50) {
|
|
24161
|
+
this.hostname = hostname;
|
|
24162
|
+
this.verbose = verbose;
|
|
24163
|
+
this.maxRetries = maxRetries;
|
|
24164
|
+
}
|
|
24165
|
+
async getNextAvailablePort(startPort, testConnectivity = false) {
|
|
24166
|
+
if (this.usedPorts.has(startPort)) {
|
|
24167
|
+
return this.findNextAvailablePort(startPort + 1, testConnectivity);
|
|
24168
|
+
}
|
|
24169
|
+
const isInUse = await isPortInUse(startPort, this.hostname, this.verbose);
|
|
24170
|
+
if (isInUse) {
|
|
24171
|
+
return this.findNextAvailablePort(startPort + 1, testConnectivity);
|
|
24172
|
+
}
|
|
24173
|
+
if (testConnectivity) {
|
|
24174
|
+
const isConnectable = await testPortConnectivity(startPort, this.hostname, 3000, this.verbose);
|
|
24175
|
+
if (!isConnectable) {
|
|
24176
|
+
debugLog("port", `Port ${startPort} is available but not connectable, trying next port`, this.verbose);
|
|
24177
|
+
return this.findNextAvailablePort(startPort + 1, testConnectivity);
|
|
24178
|
+
}
|
|
24179
|
+
}
|
|
24180
|
+
this.usedPorts.add(startPort);
|
|
24181
|
+
return startPort;
|
|
24182
|
+
}
|
|
24183
|
+
async findNextAvailablePort(startPort, testConnectivity = false) {
|
|
24184
|
+
const port = await findAvailablePort(startPort, this.hostname, this.verbose, this.maxRetries);
|
|
24185
|
+
if (testConnectivity) {
|
|
24186
|
+
const isConnectable = await testPortConnectivity(port, this.hostname, 3000, this.verbose);
|
|
24187
|
+
if (!isConnectable) {
|
|
24188
|
+
if (port < startPort + this.maxRetries) {
|
|
24189
|
+
return this.findNextAvailablePort(port + 1, testConnectivity);
|
|
24190
|
+
} else {
|
|
24191
|
+
throw new Error(`Unable to find a connectable port after ${this.maxRetries} attempts`);
|
|
24192
|
+
}
|
|
24193
|
+
}
|
|
24194
|
+
}
|
|
24195
|
+
this.usedPorts.add(port);
|
|
24196
|
+
return port;
|
|
24197
|
+
}
|
|
24198
|
+
releasePort(port) {
|
|
24199
|
+
debugLog("port", `Releasing port ${port}`, this.verbose);
|
|
24200
|
+
this.usedPorts.delete(port);
|
|
24201
|
+
}
|
|
24202
|
+
}
|
|
24203
|
+
var portManager;
|
|
24204
|
+
var init_port_manager = __esm(() => {
|
|
24205
|
+
init_utils();
|
|
24206
|
+
portManager = new DefaultPortManager;
|
|
24207
|
+
});
|
|
24208
|
+
|
|
23878
24209
|
// ../rpx/src/registry.ts
|
|
23879
24210
|
import * as fsp3 from "node:fs/promises";
|
|
23880
24211
|
import { homedir as homedir9 } from "node:os";
|
|
@@ -23946,19 +24277,399 @@ var init_registry = __esm(() => {
|
|
|
23946
24277
|
ID_PATTERN = /^[a-zA-Z0-9._-]+$/;
|
|
23947
24278
|
});
|
|
23948
24279
|
|
|
23949
|
-
// ../rpx/src/
|
|
24280
|
+
// ../rpx/src/site-supervisor.ts
|
|
24281
|
+
import { closeSync as closeSync6, openSync as openSync6, readFileSync as readFileSync3 } from "node:fs";
|
|
23950
24282
|
import * as fsp4 from "node:fs/promises";
|
|
24283
|
+
import { spawn } from "node:child_process";
|
|
23951
24284
|
import { homedir as homedir10 } from "node:os";
|
|
23952
24285
|
import * as path6 from "node:path";
|
|
24286
|
+
import * as process25 from "node:process";
|
|
24287
|
+
|
|
24288
|
+
class SiteSupervisor {
|
|
24289
|
+
resolver;
|
|
24290
|
+
registryDir;
|
|
24291
|
+
rpxDir;
|
|
24292
|
+
verbose;
|
|
24293
|
+
startupTimeoutMs;
|
|
24294
|
+
pollIntervalMs;
|
|
24295
|
+
restartDelayMs;
|
|
24296
|
+
killGraceMs;
|
|
24297
|
+
launch;
|
|
24298
|
+
probePort;
|
|
24299
|
+
pickPort;
|
|
24300
|
+
isHostRoutable;
|
|
24301
|
+
onSiteActivating;
|
|
24302
|
+
now;
|
|
24303
|
+
writeEntry;
|
|
24304
|
+
removeEntry;
|
|
24305
|
+
sites = new Map;
|
|
24306
|
+
reaper;
|
|
24307
|
+
stopped = false;
|
|
24308
|
+
constructor(opts) {
|
|
24309
|
+
this.resolver = opts.resolver;
|
|
24310
|
+
this.registryDir = opts.registryDir ?? getRegistryDir();
|
|
24311
|
+
this.rpxDir = opts.rpxDir ?? path6.join(homedir10(), ".stacks", "rpx");
|
|
24312
|
+
this.verbose = opts.verbose ?? false;
|
|
24313
|
+
this.startupTimeoutMs = opts.startupTimeoutMs ?? DEFAULT_STARTUP_TIMEOUT_MS;
|
|
24314
|
+
this.pollIntervalMs = opts.pollIntervalMs ?? DEFAULT_POLL_INTERVAL_MS;
|
|
24315
|
+
this.restartDelayMs = opts.restartDelayMs ?? DEFAULT_RESTART_DELAY_MS;
|
|
24316
|
+
this.killGraceMs = opts.killGraceMs ?? KILL_GRACE_MS;
|
|
24317
|
+
this.launch = opts.launcher ?? makeDefaultLauncher(this.verbose);
|
|
24318
|
+
this.probePort = opts.probePort ?? defaultReadinessProbe;
|
|
24319
|
+
this.pickPort = opts.pickPort ?? ((preferred) => findAvailablePort(preferred, "127.0.0.1"));
|
|
24320
|
+
this.isHostRoutable = opts.isHostRoutable ?? (() => false);
|
|
24321
|
+
this.onSiteActivating = opts.onSiteActivating;
|
|
24322
|
+
this.now = opts.now ?? Date.now;
|
|
24323
|
+
this.writeEntry = opts.writeEntry ?? writeEntry;
|
|
24324
|
+
this.removeEntry = opts.removeEntry ?? removeEntry;
|
|
24325
|
+
const reapInterval = opts.reapIntervalMs ?? DEFAULT_REAP_INTERVAL_MS;
|
|
24326
|
+
this.reaper = setInterval(() => {
|
|
24327
|
+
this.reapIdle();
|
|
24328
|
+
}, reapInterval);
|
|
24329
|
+
if (typeof this.reaper.unref === "function")
|
|
24330
|
+
this.reaper.unref();
|
|
24331
|
+
}
|
|
24332
|
+
async onRequest(host) {
|
|
24333
|
+
if (this.stopped)
|
|
24334
|
+
return { kind: "unknown" };
|
|
24335
|
+
let state = this.sites.get(host);
|
|
24336
|
+
if (state && state.status === "failed" && this.now() - state.failedAt >= this.restartDelayMs) {
|
|
24337
|
+
this.sites.delete(host);
|
|
24338
|
+
state = undefined;
|
|
24339
|
+
}
|
|
24340
|
+
if (!state) {
|
|
24341
|
+
const site = this.resolver.resolve(host);
|
|
24342
|
+
if (!site)
|
|
24343
|
+
return { kind: "unknown" };
|
|
24344
|
+
state = await this.start(site);
|
|
24345
|
+
}
|
|
24346
|
+
state.lastAccess = this.now();
|
|
24347
|
+
if (state.status === "ready")
|
|
24348
|
+
return { kind: "ready", host };
|
|
24349
|
+
if (state.status === "failed")
|
|
24350
|
+
return { kind: "failed", host, error: state.error ?? "failed to start", logTail: this.readLogTail(state) };
|
|
24351
|
+
return { kind: "starting", host, sinceMs: this.now() - state.startedAt, source: state.site.source, logTail: this.readLogTail(state, 16) };
|
|
24352
|
+
}
|
|
24353
|
+
async start(site) {
|
|
24354
|
+
try {
|
|
24355
|
+
this.onSiteActivating?.(site.host);
|
|
24356
|
+
} catch {}
|
|
24357
|
+
const ports = new Map;
|
|
24358
|
+
if (!site.selfRegisters) {
|
|
24359
|
+
for (const route of site.routes) {
|
|
24360
|
+
if (ports.has(route.portEnv))
|
|
24361
|
+
continue;
|
|
24362
|
+
const port = await this.pickPort(route.defaultPort ?? 3000);
|
|
24363
|
+
ports.set(route.portEnv, port);
|
|
24364
|
+
}
|
|
24365
|
+
}
|
|
24366
|
+
const logPath = path6.join(this.rpxDir, "sites", `${site.id}.log`);
|
|
24367
|
+
await fsp4.mkdir(path6.dirname(logPath), { recursive: true }).catch(() => {});
|
|
24368
|
+
const env2 = this.buildEnv(site, ports);
|
|
24369
|
+
let handle = null;
|
|
24370
|
+
let startError;
|
|
24371
|
+
try {
|
|
24372
|
+
handle = this.launch({ command: site.command, cwd: site.dir, env: env2, logPath });
|
|
24373
|
+
} catch (err) {
|
|
24374
|
+
startError = `failed to spawn: ${err.message}`;
|
|
24375
|
+
}
|
|
24376
|
+
const state = {
|
|
24377
|
+
site,
|
|
24378
|
+
status: startError ? "failed" : "starting",
|
|
24379
|
+
handle,
|
|
24380
|
+
ports,
|
|
24381
|
+
routeIds: [],
|
|
24382
|
+
startedAt: this.now(),
|
|
24383
|
+
lastAccess: this.now(),
|
|
24384
|
+
failedAt: startError ? this.now() : 0,
|
|
24385
|
+
error: startError,
|
|
24386
|
+
logPath,
|
|
24387
|
+
exited: false,
|
|
24388
|
+
ready: Promise.resolve()
|
|
24389
|
+
};
|
|
24390
|
+
this.sites.set(site.host, state);
|
|
24391
|
+
if (startError) {
|
|
24392
|
+
log.warn(`rpx: site ${site.host} ${startError}`);
|
|
24393
|
+
return state;
|
|
24394
|
+
}
|
|
24395
|
+
log.info(`rpx: booting ${site.host} → ${site.command} (${site.dir})`);
|
|
24396
|
+
handle.exited.then((code) => this.onProcessExit(state, code)).catch(() => {});
|
|
24397
|
+
state.ready = this.driveReadiness(state).catch((err) => {
|
|
24398
|
+
debugLog("sites", `readiness loop for ${site.host} threw: ${err}`, this.verbose);
|
|
24399
|
+
});
|
|
24400
|
+
return state;
|
|
24401
|
+
}
|
|
24402
|
+
async onProcessExit(state, code) {
|
|
24403
|
+
state.exited = true;
|
|
24404
|
+
if (this.stopped || this.sites.get(state.site.host) !== state)
|
|
24405
|
+
return;
|
|
24406
|
+
if (state.status === "ready") {
|
|
24407
|
+
log.warn(`rpx: ${state.site.host} exited${code !== null ? ` (code ${code})` : ""} — will reboot on next request`);
|
|
24408
|
+
this.sites.delete(state.site.host);
|
|
24409
|
+
for (const id of state.routeIds)
|
|
24410
|
+
await this.removeEntry(id, this.registryDir, this.verbose).catch(() => {});
|
|
24411
|
+
} else if (state.status === "starting") {
|
|
24412
|
+
this.fail(state, `process exited${code !== null ? ` with code ${code}` : ""} before becoming ready`);
|
|
24413
|
+
}
|
|
24414
|
+
}
|
|
24415
|
+
async driveReadiness(state) {
|
|
24416
|
+
const { site } = state;
|
|
24417
|
+
const deadline = this.now() + this.startupTimeoutMs;
|
|
24418
|
+
const ready = site.selfRegisters ? () => this.isHostRoutable(site.host) : await this.makeGateProbe(site, state.ports);
|
|
24419
|
+
while (this.now() < deadline && !this.stopped) {
|
|
24420
|
+
if (state.status !== "starting" || state.exited)
|
|
24421
|
+
return;
|
|
24422
|
+
if (await ready()) {
|
|
24423
|
+
await this.publishRoutes(state);
|
|
24424
|
+
if (state.status === "starting") {
|
|
24425
|
+
state.status = "ready";
|
|
24426
|
+
log.success(`rpx: ${site.host} ready`);
|
|
24427
|
+
}
|
|
24428
|
+
return;
|
|
24429
|
+
}
|
|
24430
|
+
await delay(this.pollIntervalMs);
|
|
24431
|
+
}
|
|
24432
|
+
if (state.status === "starting")
|
|
24433
|
+
this.fail(state, `did not become ready within ${Math.round(this.startupTimeoutMs / 1000)}s`);
|
|
24434
|
+
}
|
|
24435
|
+
async makeGateProbe(site, ports) {
|
|
24436
|
+
const gatePorts = site.routes.filter((r2) => r2.readyGate ?? normalizePathPrefix(r2.path) === "/").map((r2) => ports.get(r2.portEnv)).filter((p2) => typeof p2 === "number");
|
|
24437
|
+
const probePorts = gatePorts.length > 0 ? gatePorts : [...ports.values()];
|
|
24438
|
+
return async () => {
|
|
24439
|
+
for (const port of probePorts) {
|
|
24440
|
+
if (!await this.probePort(port))
|
|
24441
|
+
return false;
|
|
24442
|
+
}
|
|
24443
|
+
return probePorts.length > 0;
|
|
24444
|
+
};
|
|
24445
|
+
}
|
|
24446
|
+
async publishRoutes(state) {
|
|
24447
|
+
const { site, ports } = state;
|
|
24448
|
+
if (site.selfRegisters || site.routes.length === 0)
|
|
24449
|
+
return;
|
|
24450
|
+
const defaultRoute = site.routes.find((r2) => normalizePathPrefix(r2.path) === "/") ?? site.routes[0];
|
|
24451
|
+
const fromPort = ports.get(defaultRoute.portEnv);
|
|
24452
|
+
if (fromPort === undefined)
|
|
24453
|
+
return;
|
|
24454
|
+
const pathRewrites = [];
|
|
24455
|
+
for (const route of site.routes) {
|
|
24456
|
+
if (route === defaultRoute)
|
|
24457
|
+
continue;
|
|
24458
|
+
const port = ports.get(route.portEnv);
|
|
24459
|
+
if (port === undefined)
|
|
24460
|
+
continue;
|
|
24461
|
+
pathRewrites.push({
|
|
24462
|
+
from: normalizePathPrefix(route.path),
|
|
24463
|
+
to: `localhost:${port}`,
|
|
24464
|
+
stripPrefix: route.stripPrefix ?? false
|
|
24465
|
+
});
|
|
24466
|
+
}
|
|
24467
|
+
const entry = {
|
|
24468
|
+
id: site.id,
|
|
24469
|
+
from: `localhost:${fromPort}`,
|
|
24470
|
+
to: site.host,
|
|
24471
|
+
cwd: site.dir,
|
|
24472
|
+
createdAt: new Date(this.now()).toISOString(),
|
|
24473
|
+
pathRewrites: pathRewrites.length > 0 ? pathRewrites : undefined,
|
|
24474
|
+
pid: state.handle?.pid
|
|
24475
|
+
};
|
|
24476
|
+
await this.writeEntry(entry, this.registryDir, this.verbose);
|
|
24477
|
+
state.routeIds = [site.id];
|
|
24478
|
+
const tableDeadline = this.now() + 2000;
|
|
24479
|
+
while (this.now() < tableDeadline && !this.isHostRoutable(site.host)) {
|
|
24480
|
+
await delay(50);
|
|
24481
|
+
}
|
|
24482
|
+
}
|
|
24483
|
+
fail(state, error) {
|
|
24484
|
+
state.status = "failed";
|
|
24485
|
+
state.error = error;
|
|
24486
|
+
state.failedAt = this.now();
|
|
24487
|
+
log.warn(`rpx: site ${state.site.host} failed — ${error}`);
|
|
24488
|
+
this.killProcess(state);
|
|
24489
|
+
}
|
|
24490
|
+
buildEnv(site, ports) {
|
|
24491
|
+
const env2 = {};
|
|
24492
|
+
for (const [k2, v2] of Object.entries(process25.env)) {
|
|
24493
|
+
if (typeof v2 === "string")
|
|
24494
|
+
env2[k2] = v2;
|
|
24495
|
+
}
|
|
24496
|
+
Object.assign(env2, site.env);
|
|
24497
|
+
for (const [name, port] of ports)
|
|
24498
|
+
env2[name] = String(port);
|
|
24499
|
+
env2.RPX_SITE_HOST = site.host;
|
|
24500
|
+
env2.RPX_SITE_URL = `https://${site.host}`;
|
|
24501
|
+
return env2;
|
|
24502
|
+
}
|
|
24503
|
+
readLogTail(state, lines = 40) {
|
|
24504
|
+
try {
|
|
24505
|
+
const text = readFileSync3(state.logPath, "utf8");
|
|
24506
|
+
return text.split(`
|
|
24507
|
+
`).slice(-lines).join(`
|
|
24508
|
+
`).trim();
|
|
24509
|
+
} catch {
|
|
24510
|
+
return "";
|
|
24511
|
+
}
|
|
24512
|
+
}
|
|
24513
|
+
async stop(host) {
|
|
24514
|
+
const state = this.sites.get(host);
|
|
24515
|
+
if (!state)
|
|
24516
|
+
return;
|
|
24517
|
+
this.sites.delete(host);
|
|
24518
|
+
for (const id of state.routeIds)
|
|
24519
|
+
await this.removeEntry(id, this.registryDir, this.verbose).catch(() => {});
|
|
24520
|
+
await this.killProcess(state);
|
|
24521
|
+
log.info(`rpx: stopped ${host}`);
|
|
24522
|
+
}
|
|
24523
|
+
async killProcess(state) {
|
|
24524
|
+
const handle = state.handle;
|
|
24525
|
+
if (!handle)
|
|
24526
|
+
return;
|
|
24527
|
+
state.handle = null;
|
|
24528
|
+
try {
|
|
24529
|
+
handle.stop("SIGTERM");
|
|
24530
|
+
} catch {}
|
|
24531
|
+
const timer = setTimeout(() => {
|
|
24532
|
+
try {
|
|
24533
|
+
handle.stop("SIGKILL");
|
|
24534
|
+
} catch {}
|
|
24535
|
+
}, this.killGraceMs);
|
|
24536
|
+
if (typeof timer.unref === "function")
|
|
24537
|
+
timer.unref();
|
|
24538
|
+
await Promise.race([handle.exited.catch(() => {}), delay(this.killGraceMs + 500)]);
|
|
24539
|
+
clearTimeout(timer);
|
|
24540
|
+
}
|
|
24541
|
+
async reapIdle() {
|
|
24542
|
+
if (this.stopped)
|
|
24543
|
+
return;
|
|
24544
|
+
const now = this.now();
|
|
24545
|
+
for (const [host, state] of this.sites) {
|
|
24546
|
+
const idle = state.site.idleTimeoutMs;
|
|
24547
|
+
if (idle <= 0)
|
|
24548
|
+
continue;
|
|
24549
|
+
const idleFor = now - state.lastAccess;
|
|
24550
|
+
const settled = state.status === "ready" || now - state.startedAt > this.startupTimeoutMs;
|
|
24551
|
+
if (settled && idleFor > idle) {
|
|
24552
|
+
debugLog("sites", `reaping ${host} (idle ${Math.round(idleFor / 1000)}s)`, this.verbose);
|
|
24553
|
+
await this.stop(host);
|
|
24554
|
+
}
|
|
24555
|
+
}
|
|
24556
|
+
}
|
|
24557
|
+
list() {
|
|
24558
|
+
const now = this.now();
|
|
24559
|
+
return [...this.sites.values()].map((s) => ({
|
|
24560
|
+
host: s.site.host,
|
|
24561
|
+
dir: s.site.dir,
|
|
24562
|
+
status: s.status,
|
|
24563
|
+
pid: s.handle?.pid ?? null,
|
|
24564
|
+
ports: Object.fromEntries(s.ports),
|
|
24565
|
+
uptimeMs: now - s.startedAt,
|
|
24566
|
+
idleMs: now - s.lastAccess,
|
|
24567
|
+
error: s.error
|
|
24568
|
+
}));
|
|
24569
|
+
}
|
|
24570
|
+
async stopAll() {
|
|
24571
|
+
this.stopped = true;
|
|
24572
|
+
clearInterval(this.reaper);
|
|
24573
|
+
await Promise.allSettled([...this.sites.keys()].map((host) => this.stop(host)));
|
|
24574
|
+
}
|
|
24575
|
+
}
|
|
24576
|
+
function delay(ms) {
|
|
24577
|
+
return new Promise((resolve14) => setTimeout(resolve14, ms));
|
|
24578
|
+
}
|
|
24579
|
+
async function defaultReadinessProbe(port) {
|
|
24580
|
+
const controller = new AbortController;
|
|
24581
|
+
const timer = setTimeout(() => controller.abort(), 2000);
|
|
24582
|
+
try {
|
|
24583
|
+
await fetch(`http://127.0.0.1:${port}/`, { signal: controller.signal, redirect: "manual" });
|
|
24584
|
+
return true;
|
|
24585
|
+
} catch {
|
|
24586
|
+
return false;
|
|
24587
|
+
} finally {
|
|
24588
|
+
clearTimeout(timer);
|
|
24589
|
+
}
|
|
24590
|
+
}
|
|
24591
|
+
function makeDefaultLauncher(verbose) {
|
|
24592
|
+
const drop = privilegeDrop();
|
|
24593
|
+
return ({ command, cwd, env: env2, logPath }) => {
|
|
24594
|
+
const fd = openSync6(logPath, "a");
|
|
24595
|
+
try {
|
|
24596
|
+
const child = spawn("sh", ["-c", command], {
|
|
24597
|
+
cwd,
|
|
24598
|
+
env: env2,
|
|
24599
|
+
detached: true,
|
|
24600
|
+
stdio: ["ignore", fd, fd],
|
|
24601
|
+
...drop ? { uid: drop.uid, gid: drop.gid } : {}
|
|
24602
|
+
});
|
|
24603
|
+
const pid2 = child.pid;
|
|
24604
|
+
if (pid2 === undefined)
|
|
24605
|
+
throw new Error("spawn returned no pid");
|
|
24606
|
+
const exited = new Promise((resolve14) => {
|
|
24607
|
+
child.once("exit", (code) => {
|
|
24608
|
+
resolve14(code);
|
|
24609
|
+
});
|
|
24610
|
+
child.once("error", () => {
|
|
24611
|
+
resolve14(null);
|
|
24612
|
+
});
|
|
24613
|
+
}).finally(() => {
|
|
24614
|
+
try {
|
|
24615
|
+
closeSync6(fd);
|
|
24616
|
+
} catch {}
|
|
24617
|
+
});
|
|
24618
|
+
debugLog("sites", `spawned pid ${pid2}: sh -c ${command} (cwd ${cwd})`, verbose);
|
|
24619
|
+
return {
|
|
24620
|
+
pid: pid2,
|
|
24621
|
+
exited,
|
|
24622
|
+
stop: (signal = "SIGTERM") => {
|
|
24623
|
+
try {
|
|
24624
|
+
process25.kill(-pid2, signal);
|
|
24625
|
+
} catch {
|
|
24626
|
+
try {
|
|
24627
|
+
child.kill(signal);
|
|
24628
|
+
} catch {}
|
|
24629
|
+
}
|
|
24630
|
+
}
|
|
24631
|
+
};
|
|
24632
|
+
} catch (err) {
|
|
24633
|
+
try {
|
|
24634
|
+
closeSync6(fd);
|
|
24635
|
+
} catch {}
|
|
24636
|
+
throw err;
|
|
24637
|
+
}
|
|
24638
|
+
};
|
|
24639
|
+
}
|
|
24640
|
+
function privilegeDrop() {
|
|
24641
|
+
if (process25.platform === "win32")
|
|
24642
|
+
return null;
|
|
24643
|
+
const isRoot = typeof process25.getuid === "function" && process25.getuid() === 0;
|
|
24644
|
+
if (!isRoot)
|
|
24645
|
+
return null;
|
|
24646
|
+
const uid = Number.parseInt(process25.env.SUDO_UID ?? "", 10);
|
|
24647
|
+
const gid = Number.parseInt(process25.env.SUDO_GID ?? "", 10);
|
|
24648
|
+
if (!Number.isInteger(uid) || uid <= 0 || !Number.isInteger(gid) || gid <= 0)
|
|
24649
|
+
return null;
|
|
24650
|
+
return { uid, gid };
|
|
24651
|
+
}
|
|
24652
|
+
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;
|
|
24653
|
+
var init_site_supervisor = __esm(() => {
|
|
24654
|
+
init_host_routes();
|
|
24655
|
+
init_port_manager();
|
|
24656
|
+
init_registry();
|
|
24657
|
+
init_utils();
|
|
24658
|
+
init_logger();
|
|
24659
|
+
});
|
|
24660
|
+
// ../rpx/src/dns-state.ts
|
|
24661
|
+
import * as fsp5 from "node:fs/promises";
|
|
24662
|
+
import { homedir as homedir11 } from "node:os";
|
|
24663
|
+
import * as path7 from "node:path";
|
|
23953
24664
|
function defaultRpxDir() {
|
|
23954
|
-
return
|
|
24665
|
+
return path7.join(homedir11(), ".stacks", "rpx");
|
|
23955
24666
|
}
|
|
23956
24667
|
function getDnsStatePath(rpxDir = defaultRpxDir()) {
|
|
23957
|
-
return
|
|
24668
|
+
return path7.join(rpxDir, RPX_DNS_STATE_FILE);
|
|
23958
24669
|
}
|
|
23959
24670
|
async function loadDnsState(rpxDir = defaultRpxDir()) {
|
|
23960
24671
|
try {
|
|
23961
|
-
const raw = await
|
|
24672
|
+
const raw = await fsp5.readFile(getDnsStatePath(rpxDir), "utf8");
|
|
23962
24673
|
const parsed = JSON.parse(raw);
|
|
23963
24674
|
if (parsed.version !== DNS_STATE_VERSION || !Array.isArray(parsed.resolvers))
|
|
23964
24675
|
return null;
|
|
@@ -23976,12 +24687,12 @@ async function loadDnsState(rpxDir = defaultRpxDir()) {
|
|
|
23976
24687
|
}
|
|
23977
24688
|
}
|
|
23978
24689
|
async function saveDnsState(rpxDir, state) {
|
|
23979
|
-
await
|
|
23980
|
-
await
|
|
24690
|
+
await fsp5.mkdir(rpxDir, { recursive: true });
|
|
24691
|
+
await fsp5.writeFile(getDnsStatePath(rpxDir), `${JSON.stringify(state, null, 2)}
|
|
23981
24692
|
`, "utf8");
|
|
23982
24693
|
}
|
|
23983
24694
|
async function clearDnsState(rpxDir) {
|
|
23984
|
-
await
|
|
24695
|
+
await fsp5.rm(getDnsStatePath(rpxDir), { force: true });
|
|
23985
24696
|
}
|
|
23986
24697
|
function normalizeDevDomain(raw) {
|
|
23987
24698
|
const domain = raw.trim().toLowerCase().replace(/\.$/, "");
|
|
@@ -24057,9 +24768,9 @@ __export(exports_dns, {
|
|
|
24057
24768
|
DNS_PORT: () => DNS_PORT
|
|
24058
24769
|
});
|
|
24059
24770
|
import dgram from "node:dgram";
|
|
24060
|
-
import * as
|
|
24061
|
-
import * as
|
|
24062
|
-
import * as
|
|
24771
|
+
import * as fsp6 from "node:fs/promises";
|
|
24772
|
+
import * as path8 from "node:path";
|
|
24773
|
+
import * as process26 from "node:process";
|
|
24063
24774
|
function parseHeader(buffer) {
|
|
24064
24775
|
return {
|
|
24065
24776
|
id: buffer.readUInt16BE(0),
|
|
@@ -24173,7 +24884,7 @@ function buildNxdomainResponse(queryId, question) {
|
|
|
24173
24884
|
return Buffer.concat(parts);
|
|
24174
24885
|
}
|
|
24175
24886
|
async function startDnsServer(domains, verbose) {
|
|
24176
|
-
if (
|
|
24887
|
+
if (process26.platform !== "darwin")
|
|
24177
24888
|
return false;
|
|
24178
24889
|
const devDomains = devDomainsFromHosts(domains);
|
|
24179
24890
|
if (devDomains.length === 0)
|
|
@@ -24293,14 +25004,14 @@ port ${DNS_PORT}
|
|
|
24293
25004
|
`;
|
|
24294
25005
|
}
|
|
24295
25006
|
function resolverFilePath(basename) {
|
|
24296
|
-
return
|
|
25007
|
+
return path8.join(MACOS_RESOLVER_DIR, basename);
|
|
24297
25008
|
}
|
|
24298
25009
|
function contentLooksLikeRpxResolver(content) {
|
|
24299
25010
|
return content.includes("127.0.0.1") && content.includes(String(DNS_PORT));
|
|
24300
25011
|
}
|
|
24301
25012
|
async function readResolverFile(basename) {
|
|
24302
25013
|
try {
|
|
24303
|
-
return await
|
|
25014
|
+
return await fsp6.readFile(resolverFilePath(basename), "utf8");
|
|
24304
25015
|
} catch (err) {
|
|
24305
25016
|
if (err.code === "ENOENT")
|
|
24306
25017
|
return null;
|
|
@@ -24308,7 +25019,7 @@ async function readResolverFile(basename) {
|
|
|
24308
25019
|
}
|
|
24309
25020
|
}
|
|
24310
25021
|
async function flushDnsCache(verbose) {
|
|
24311
|
-
if (
|
|
25022
|
+
if (process26.platform !== "darwin")
|
|
24312
25023
|
return;
|
|
24313
25024
|
const { execSudoSync: execSudoSync2, getSudoPassword: getSudoPassword2, isProcessElevated: isProcessElevated2 } = await Promise.resolve().then(() => (init_utils(), exports_utils));
|
|
24314
25025
|
if (!isProcessElevated2() && !getSudoPassword2()) {
|
|
@@ -24345,7 +25056,7 @@ async function setupResolver(verbose, domains) {
|
|
|
24345
25056
|
return setupDevelopmentDns({ domains: domains ?? [], verbose });
|
|
24346
25057
|
}
|
|
24347
25058
|
async function installResolvers(basenames, verbose) {
|
|
24348
|
-
if (
|
|
25059
|
+
if (process26.platform !== "darwin")
|
|
24349
25060
|
return true;
|
|
24350
25061
|
const { getSudoPassword: getSudoPassword2, isProcessElevated: isProcessElevated2 } = await Promise.resolve().then(() => (init_utils(), exports_utils));
|
|
24351
25062
|
if (!isProcessElevated2() && !getSudoPassword2()) {
|
|
@@ -24363,7 +25074,7 @@ async function installResolvers(basenames, verbose) {
|
|
|
24363
25074
|
}
|
|
24364
25075
|
}
|
|
24365
25076
|
async function uninstallResolvers(basenames, verbose) {
|
|
24366
|
-
if (
|
|
25077
|
+
if (process26.platform !== "darwin")
|
|
24367
25078
|
return;
|
|
24368
25079
|
const { getSudoPassword: getSudoPassword2, isProcessElevated: isProcessElevated2 } = await Promise.resolve().then(() => (init_utils(), exports_utils));
|
|
24369
25080
|
if (!isProcessElevated2() && !getSudoPassword2())
|
|
@@ -24377,7 +25088,7 @@ async function uninstallResolvers(basenames, verbose) {
|
|
|
24377
25088
|
}
|
|
24378
25089
|
}
|
|
24379
25090
|
async function removeLegacyTldResolvers(verbose) {
|
|
24380
|
-
if (
|
|
25091
|
+
if (process26.platform !== "darwin")
|
|
24381
25092
|
return [];
|
|
24382
25093
|
const removed = [];
|
|
24383
25094
|
for (const label of LEGACY_TLD_RESOLVER_LABELS) {
|
|
@@ -24412,7 +25123,7 @@ async function setupDevelopmentDns(opts) {
|
|
|
24412
25123
|
version: DNS_STATE_VERSION,
|
|
24413
25124
|
resolvers: basenames,
|
|
24414
25125
|
domains,
|
|
24415
|
-
ownerPid: opts.ownerPid ??
|
|
25126
|
+
ownerPid: opts.ownerPid ?? process26.pid,
|
|
24416
25127
|
updatedAt: new Date().toISOString()
|
|
24417
25128
|
};
|
|
24418
25129
|
await saveDnsState(rpxDir, state);
|
|
@@ -24436,7 +25147,7 @@ async function syncDevelopmentDnsFromRegistry(entries, opts = {}) {
|
|
|
24436
25147
|
domains,
|
|
24437
25148
|
rpxDir,
|
|
24438
25149
|
verbose: opts.verbose,
|
|
24439
|
-
ownerPid: opts.ownerPid ??
|
|
25150
|
+
ownerPid: opts.ownerPid ?? process26.pid
|
|
24440
25151
|
});
|
|
24441
25152
|
}
|
|
24442
25153
|
async function tearDownDevelopmentDns(opts = {}) {
|
|
@@ -24476,19 +25187,19 @@ var init_dns = __esm(() => {
|
|
|
24476
25187
|
|
|
24477
25188
|
// ../rpx/src/daemon.ts
|
|
24478
25189
|
import { spawn as nodeSpawn } from "node:child_process";
|
|
24479
|
-
import * as
|
|
24480
|
-
import { homedir as
|
|
24481
|
-
import * as
|
|
24482
|
-
import * as
|
|
25190
|
+
import * as fsp7 from "node:fs/promises";
|
|
25191
|
+
import { homedir as homedir12 } from "node:os";
|
|
25192
|
+
import * as path9 from "node:path";
|
|
25193
|
+
import * as process27 from "node:process";
|
|
24483
25194
|
function getDaemonRpxDir() {
|
|
24484
|
-
return
|
|
25195
|
+
return path9.join(homedir12(), ".stacks", "rpx");
|
|
24485
25196
|
}
|
|
24486
25197
|
function getDaemonPidPath(rpxDir = getDaemonRpxDir()) {
|
|
24487
|
-
return
|
|
25198
|
+
return path9.join(rpxDir, "daemon.pid");
|
|
24488
25199
|
}
|
|
24489
25200
|
async function readDaemonPid(rpxDir = getDaemonRpxDir()) {
|
|
24490
25201
|
try {
|
|
24491
|
-
const raw = await
|
|
25202
|
+
const raw = await fsp7.readFile(getDaemonPidPath(rpxDir), "utf8");
|
|
24492
25203
|
const n2 = Number.parseInt(raw.trim(), 10);
|
|
24493
25204
|
if (!Number.isFinite(n2) || n2 <= 0)
|
|
24494
25205
|
return null;
|
|
@@ -24500,14 +25211,14 @@ async function readDaemonPid(rpxDir = getDaemonRpxDir()) {
|
|
|
24500
25211
|
}
|
|
24501
25212
|
}
|
|
24502
25213
|
async function releaseDaemonLock(rpxDir = getDaemonRpxDir()) {
|
|
24503
|
-
await
|
|
25214
|
+
await fsp7.unlink(getDaemonPidPath(rpxDir)).catch(() => {});
|
|
24504
25215
|
}
|
|
24505
25216
|
function defaultDaemonSpawnCommand() {
|
|
24506
|
-
const exec =
|
|
24507
|
-
const interpName =
|
|
25217
|
+
const exec = process27.execPath;
|
|
25218
|
+
const interpName = path9.basename(exec).toLowerCase();
|
|
24508
25219
|
const isInterpreter = interpName === "bun" || interpName === "node" || interpName.startsWith("bun-");
|
|
24509
|
-
if (isInterpreter &&
|
|
24510
|
-
return [exec,
|
|
25220
|
+
if (isInterpreter && process27.argv[1])
|
|
25221
|
+
return [exec, process27.argv[1], "daemon:start"];
|
|
24511
25222
|
return [exec, "daemon:start"];
|
|
24512
25223
|
}
|
|
24513
25224
|
async function ensureDaemonRunning(opts = {}) {
|
|
@@ -24525,7 +25236,7 @@ async function ensureDaemonRunning(opts = {}) {
|
|
|
24525
25236
|
debugLog("daemon", `ensureDaemonRunning: clearing stale pid=${existingPid}`, verbose);
|
|
24526
25237
|
await releaseDaemonLock(rpxDir);
|
|
24527
25238
|
}
|
|
24528
|
-
await
|
|
25239
|
+
await fsp7.mkdir(rpxDir, { recursive: true });
|
|
24529
25240
|
const command = opts.spawnCommand ?? defaultDaemonSpawnCommand();
|
|
24530
25241
|
if (command.length === 0)
|
|
24531
25242
|
throw new Error("ensureDaemonRunning: spawnCommand is empty");
|
|
@@ -24533,8 +25244,8 @@ async function ensureDaemonRunning(opts = {}) {
|
|
|
24533
25244
|
const child = nodeSpawn(command[0], command.slice(1), {
|
|
24534
25245
|
detached: true,
|
|
24535
25246
|
stdio: "ignore",
|
|
24536
|
-
cwd: opts.spawnCwd ??
|
|
24537
|
-
env: opts.spawnEnv ? { ...
|
|
25247
|
+
cwd: opts.spawnCwd ?? process27.cwd(),
|
|
25248
|
+
env: opts.spawnEnv ? { ...process27.env, ...opts.spawnEnv } : process27.env
|
|
24538
25249
|
});
|
|
24539
25250
|
child.unref();
|
|
24540
25251
|
let spawnError = null;
|
|
@@ -24565,15 +25276,18 @@ var init_daemon = __esm(() => {
|
|
|
24565
25276
|
init_host_routes();
|
|
24566
25277
|
init_sni();
|
|
24567
25278
|
init_on_demand();
|
|
25279
|
+
init_site_resolver();
|
|
25280
|
+
init_site_supervisor();
|
|
24568
25281
|
init_static_files();
|
|
25282
|
+
init_auth();
|
|
24569
25283
|
init_registry();
|
|
24570
25284
|
init_dns();
|
|
24571
25285
|
init_utils();
|
|
24572
25286
|
});
|
|
24573
25287
|
|
|
24574
25288
|
// src/index.ts
|
|
24575
|
-
import { exec as exec3, spawn as
|
|
24576
|
-
import * as
|
|
25289
|
+
import { exec as exec3, spawn as spawn3 } from "node:child_process";
|
|
25290
|
+
import * as process34 from "node:process";
|
|
24577
25291
|
import { promisify as promisify2 } from "node:util";
|
|
24578
25292
|
|
|
24579
25293
|
// ../rpx/src/start.ts
|
|
@@ -24581,7 +25295,7 @@ init_logger();
|
|
|
24581
25295
|
import { execSync as execSync4 } from "node:child_process";
|
|
24582
25296
|
import * as http from "node:http";
|
|
24583
25297
|
import * as net2 from "node:net";
|
|
24584
|
-
import * as
|
|
25298
|
+
import * as process31 from "node:process";
|
|
24585
25299
|
|
|
24586
25300
|
// ../rpx/src/colors.ts
|
|
24587
25301
|
var c = (open, close) => (str) => `\x1B[${open}m${str}\x1B[${close}m`;
|
|
@@ -24601,8 +25315,8 @@ init_logger();
|
|
|
24601
25315
|
init_registry();
|
|
24602
25316
|
init_utils();
|
|
24603
25317
|
import * as fs3 from "node:fs";
|
|
24604
|
-
import * as
|
|
24605
|
-
import * as
|
|
25318
|
+
import * as path10 from "node:path";
|
|
25319
|
+
import * as process28 from "node:process";
|
|
24606
25320
|
function deriveIdFromTarget(to, routePath) {
|
|
24607
25321
|
const base = routePath && routePath !== "/" ? `${to}${routePath}` : to;
|
|
24608
25322
|
const cleaned = base.replace(/[^a-zA-Z0-9._-]+/g, "-").replace(/^-+|-+$/g, "").slice(0, 128);
|
|
@@ -24630,8 +25344,8 @@ async function runViaDaemon(opts) {
|
|
|
24630
25344
|
from: p2.from,
|
|
24631
25345
|
to: p2.to,
|
|
24632
25346
|
path: p2.path,
|
|
24633
|
-
pid: opts.persistent ? undefined :
|
|
24634
|
-
cwd:
|
|
25347
|
+
pid: opts.persistent ? undefined : process28.pid,
|
|
25348
|
+
cwd: process28.cwd(),
|
|
24635
25349
|
createdAt,
|
|
24636
25350
|
cleanUrls: p2.cleanUrls,
|
|
24637
25351
|
changeOrigin: p2.changeOrigin,
|
|
@@ -24668,16 +25382,16 @@ async function runViaDaemon(opts) {
|
|
|
24668
25382
|
};
|
|
24669
25383
|
const onSignal = (sig) => {
|
|
24670
25384
|
debugLog("runner", `received ${sig}, unregistering ${idsForCleanup.length} entries`, verbose);
|
|
24671
|
-
cleanup().finally(() =>
|
|
25385
|
+
cleanup().finally(() => process28.exit(0));
|
|
24672
25386
|
};
|
|
24673
|
-
|
|
24674
|
-
|
|
24675
|
-
|
|
25387
|
+
process28.once("SIGINT", onSignal);
|
|
25388
|
+
process28.once("SIGTERM", onSignal);
|
|
25389
|
+
process28.once("exit", () => {
|
|
24676
25390
|
if (cleaned)
|
|
24677
25391
|
return;
|
|
24678
25392
|
for (const id of idsForCleanup) {
|
|
24679
25393
|
try {
|
|
24680
|
-
fs3.unlinkSync(
|
|
25394
|
+
fs3.unlinkSync(path10.join(dirForCleanup, `${id}.json`));
|
|
24681
25395
|
} catch {}
|
|
24682
25396
|
}
|
|
24683
25397
|
});
|
|
@@ -24689,18 +25403,18 @@ init_utils();
|
|
|
24689
25403
|
import { exec } from "node:child_process";
|
|
24690
25404
|
import fs4 from "node:fs";
|
|
24691
25405
|
import os2 from "node:os";
|
|
24692
|
-
import
|
|
24693
|
-
import * as
|
|
25406
|
+
import path11 from "node:path";
|
|
25407
|
+
import * as process29 from "node:process";
|
|
24694
25408
|
import { promisify } from "node:util";
|
|
24695
25409
|
var execAsync = promisify(exec);
|
|
24696
25410
|
function isLoopbackDevelopmentHost(host) {
|
|
24697
25411
|
const normalized = host.trim().toLowerCase();
|
|
24698
25412
|
return normalized === "localhost" || normalized.endsWith(".localhost") || normalized.endsWith(".localhost.");
|
|
24699
25413
|
}
|
|
24700
|
-
var hostsFilePath =
|
|
25414
|
+
var hostsFilePath = process29.platform === "win32" ? path11.join(process29.env.windir || "C:\\Windows", "System32", "drivers", "etc", "hosts") : "/etc/hosts";
|
|
24701
25415
|
var sudoPrivilegesAcquired = false;
|
|
24702
25416
|
async function execSudo(command) {
|
|
24703
|
-
if (
|
|
25417
|
+
if (process29.platform === "win32")
|
|
24704
25418
|
throw new Error("Administrator privileges required on Windows");
|
|
24705
25419
|
if (isProcessElevated()) {
|
|
24706
25420
|
const { stdout } = await execAsync(command);
|
|
@@ -24771,7 +25485,7 @@ async function addHosts(hosts, verbose) {
|
|
|
24771
25485
|
127.0.0.1 ${host}
|
|
24772
25486
|
::1 ${host}`).join(`
|
|
24773
25487
|
`);
|
|
24774
|
-
const tmpFile =
|
|
25488
|
+
const tmpFile = path11.join(os2.tmpdir(), `rpx-hosts-${Date.now()}.tmp`);
|
|
24775
25489
|
try {
|
|
24776
25490
|
await fs4.promises.writeFile(tmpFile, existingContent + hostEntries, "utf8");
|
|
24777
25491
|
await execSudo(`cat "${tmpFile}" | tee "${hostsFilePath}" > /dev/null`);
|
|
@@ -24833,7 +25547,7 @@ async function removeHosts(hosts, verbose) {
|
|
|
24833
25547
|
const newContent = `${filteredLines.join(`
|
|
24834
25548
|
`)}
|
|
24835
25549
|
`;
|
|
24836
|
-
const tmpFile =
|
|
25550
|
+
const tmpFile = path11.join(os2.tmpdir(), `rpx-hosts-${Date.now()}.tmp`);
|
|
24837
25551
|
try {
|
|
24838
25552
|
await fs4.promises.writeFile(tmpFile, newContent, "utf8");
|
|
24839
25553
|
await execSudo(`cat "${tmpFile}" | tee "${hostsFilePath}" > /dev/null`);
|
|
@@ -24882,141 +25596,13 @@ async function checkHosts(hosts, verbose) {
|
|
|
24882
25596
|
|
|
24883
25597
|
// ../rpx/src/start.ts
|
|
24884
25598
|
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;
|
|
25599
|
+
init_port_manager();
|
|
25014
25600
|
|
|
25015
25601
|
// ../rpx/src/process-manager.ts
|
|
25016
25602
|
init_logger();
|
|
25017
25603
|
init_utils();
|
|
25018
|
-
import { spawn } from "node:child_process";
|
|
25019
|
-
import * as
|
|
25604
|
+
import { spawn as spawn2 } from "node:child_process";
|
|
25605
|
+
import * as process30 from "node:process";
|
|
25020
25606
|
|
|
25021
25607
|
class ProcessManager {
|
|
25022
25608
|
processes = new Map;
|
|
@@ -25027,14 +25613,14 @@ class ProcessManager {
|
|
|
25027
25613
|
return;
|
|
25028
25614
|
}
|
|
25029
25615
|
const [cmd, ...args] = options.command.split(" ");
|
|
25030
|
-
const cwd4 = options.cwd ||
|
|
25616
|
+
const cwd4 = options.cwd || process30.cwd();
|
|
25031
25617
|
debugLog("start", `Starting process ${id}:`, verbose);
|
|
25032
25618
|
debugLog("start", ` Command: ${cmd} ${args.join(" ")}`, verbose);
|
|
25033
25619
|
debugLog("start", ` Working directory: ${cwd4}`, verbose);
|
|
25034
25620
|
debugLog("start", ` Environment variables: ${safeStringify(options.env)}`, verbose);
|
|
25035
|
-
const childProcess =
|
|
25621
|
+
const childProcess = spawn2(cmd, args, {
|
|
25036
25622
|
cwd: cwd4,
|
|
25037
|
-
env: { ...
|
|
25623
|
+
env: { ...process30.env, ...options.env },
|
|
25038
25624
|
shell: true,
|
|
25039
25625
|
stdio: "inherit"
|
|
25040
25626
|
});
|
|
@@ -25134,7 +25720,7 @@ class ProcessManager {
|
|
|
25134
25720
|
var processManager = new ProcessManager;
|
|
25135
25721
|
|
|
25136
25722
|
// ../rpx/src/origin-guard.ts
|
|
25137
|
-
import { timingSafeEqual } from "node:crypto";
|
|
25723
|
+
import { timingSafeEqual as timingSafeEqual2 } from "node:crypto";
|
|
25138
25724
|
function secretMatches(provided, expected) {
|
|
25139
25725
|
if (provided == null)
|
|
25140
25726
|
return false;
|
|
@@ -25142,7 +25728,7 @@ function secretMatches(provided, expected) {
|
|
|
25142
25728
|
const b2 = Buffer.from(expected);
|
|
25143
25729
|
if (a2.length !== b2.length)
|
|
25144
25730
|
return false;
|
|
25145
|
-
return
|
|
25731
|
+
return timingSafeEqual2(a2, b2);
|
|
25146
25732
|
}
|
|
25147
25733
|
function normalizeHost(host) {
|
|
25148
25734
|
return host.toLowerCase().replace(/\.$/, "");
|
|
@@ -25196,6 +25782,26 @@ function createOriginGuard(options) {
|
|
|
25196
25782
|
|
|
25197
25783
|
// ../rpx/src/start.ts
|
|
25198
25784
|
init_proxy_handler();
|
|
25785
|
+
|
|
25786
|
+
// ../rpx/src/acme-challenge.ts
|
|
25787
|
+
import * as fs5 from "node:fs";
|
|
25788
|
+
import * as path12 from "node:path";
|
|
25789
|
+
var ACME_CHALLENGE_PREFIX2 = "/.well-known/acme-challenge/";
|
|
25790
|
+
function readAcmeChallenge(webroot, pathname) {
|
|
25791
|
+
if (!webroot || !pathname.startsWith(ACME_CHALLENGE_PREFIX2))
|
|
25792
|
+
return null;
|
|
25793
|
+
const token = pathname.slice(ACME_CHALLENGE_PREFIX2.length);
|
|
25794
|
+
if (!token || !/^[A-Za-z0-9_-]+$/.test(token))
|
|
25795
|
+
return null;
|
|
25796
|
+
try {
|
|
25797
|
+
return fs5.readFileSync(path12.join(webroot, token), "utf8");
|
|
25798
|
+
} catch {
|
|
25799
|
+
return null;
|
|
25800
|
+
}
|
|
25801
|
+
}
|
|
25802
|
+
|
|
25803
|
+
// ../rpx/src/start.ts
|
|
25804
|
+
init_auth();
|
|
25199
25805
|
init_host_routes();
|
|
25200
25806
|
init_sni();
|
|
25201
25807
|
init_static_files();
|
|
@@ -25288,8 +25894,8 @@ async function cleanup(options) {
|
|
|
25288
25894
|
cleanupPromiseResolve = null;
|
|
25289
25895
|
isCleaningUp = false;
|
|
25290
25896
|
const isVitePluginCall = options && "vitePluginUsage" in options && options.vitePluginUsage === true;
|
|
25291
|
-
if (
|
|
25292
|
-
|
|
25897
|
+
if (process31.env.NODE_ENV !== "test" && process31.env.BUN_ENV !== "test" && !isVitePluginCall) {
|
|
25898
|
+
process31.exit(0);
|
|
25293
25899
|
}
|
|
25294
25900
|
}
|
|
25295
25901
|
return cleanupPromise;
|
|
@@ -25298,31 +25904,31 @@ var isHandlingSignal = false;
|
|
|
25298
25904
|
function signalHandler(signal) {
|
|
25299
25905
|
if (isHandlingSignal) {
|
|
25300
25906
|
debugLog("signal", `Received second ${signal} signal, forcing exit`, true);
|
|
25301
|
-
|
|
25907
|
+
process31.exit(1);
|
|
25302
25908
|
return;
|
|
25303
25909
|
}
|
|
25304
25910
|
isHandlingSignal = true;
|
|
25305
25911
|
debugLog("signal", `Received ${signal} signal, initiating cleanup`, true);
|
|
25306
25912
|
cleanup().catch((err) => {
|
|
25307
25913
|
debugLog("signal", `Cleanup failed after ${signal}: ${err}`, true);
|
|
25308
|
-
|
|
25914
|
+
process31.exit(1);
|
|
25309
25915
|
}).finally(() => {
|
|
25310
25916
|
isHandlingSignal = false;
|
|
25311
25917
|
});
|
|
25312
25918
|
}
|
|
25313
|
-
|
|
25314
|
-
|
|
25315
|
-
|
|
25919
|
+
process31.once("SIGINT", () => signalHandler("SIGINT"));
|
|
25920
|
+
process31.once("SIGTERM", () => signalHandler("SIGTERM"));
|
|
25921
|
+
process31.on("uncaughtException", (err) => {
|
|
25316
25922
|
log.error("Uncaught exception (continuing):", err);
|
|
25317
25923
|
});
|
|
25318
|
-
|
|
25924
|
+
process31.on("unhandledRejection", (reason) => {
|
|
25319
25925
|
log.error("Unhandled rejection (continuing):", reason);
|
|
25320
25926
|
});
|
|
25321
25927
|
async function testConnection(hostname, port, verbose, retries = 5) {
|
|
25322
25928
|
debugLog("connection", `Testing connection to ${hostname}:${port} (retries left: ${retries})`, verbose);
|
|
25323
25929
|
const maxTestDuration = 15000;
|
|
25324
25930
|
const startTime = Date.now();
|
|
25325
|
-
if (
|
|
25931
|
+
if (process31.env.RPX_BYPASS_CONNECTION_TEST === "true") {
|
|
25326
25932
|
debugLog("connection", `Bypassing connection test for ${hostname}:${port} due to RPX_BYPASS_CONNECTION_TEST flag`, verbose);
|
|
25327
25933
|
return;
|
|
25328
25934
|
}
|
|
@@ -25418,7 +26024,7 @@ async function startServer(options) {
|
|
|
25418
26024
|
log.warn("You can manually add this entry to your hosts file:");
|
|
25419
26025
|
log.warn(`127.0.0.1 ${toUrl.hostname}`);
|
|
25420
26026
|
log.warn(`::1 ${toUrl.hostname}`);
|
|
25421
|
-
if (
|
|
26027
|
+
if (process31.platform === "win32") {
|
|
25422
26028
|
log.warn("On Windows:");
|
|
25423
26029
|
log.warn("1. Run notepad as administrator");
|
|
25424
26030
|
log.warn("2. Open C:\\Windows\\System32\\drivers\\etc\\hosts");
|
|
@@ -25491,7 +26097,7 @@ async function startServer(options) {
|
|
|
25491
26097
|
ssl: sslConfig
|
|
25492
26098
|
});
|
|
25493
26099
|
}
|
|
25494
|
-
async function createProxyServer(from, to, listenPort, sourceUrl, ssl, vitePluginUsage, verbose, cleanUrls, changeOrigin) {
|
|
26100
|
+
async function createProxyServer(from, to, listenPort, sourceUrl, ssl, vitePluginUsage, verbose, cleanUrls, changeOrigin, auth) {
|
|
25495
26101
|
debugLog("proxy", `Creating proxy server ${from} -> ${to} with cleanUrls: ${cleanUrls}`, verbose);
|
|
25496
26102
|
const routeEntries = [{
|
|
25497
26103
|
host: to,
|
|
@@ -25499,7 +26105,8 @@ async function createProxyServer(from, to, listenPort, sourceUrl, ssl, vitePlugi
|
|
|
25499
26105
|
sourceHost: sourceUrl.host,
|
|
25500
26106
|
cleanUrls: cleanUrls || false,
|
|
25501
26107
|
changeOrigin: changeOrigin || false,
|
|
25502
|
-
basePath: "/"
|
|
26108
|
+
basePath: "/",
|
|
26109
|
+
auth
|
|
25503
26110
|
}
|
|
25504
26111
|
}];
|
|
25505
26112
|
const server = createSharedProxyServer({ routeEntries, listenPort, sslConfig: ssl, originGuard: null, verbose: verbose ?? false });
|
|
@@ -25537,7 +26144,7 @@ async function setupProxy(options) {
|
|
|
25537
26144
|
}
|
|
25538
26145
|
}
|
|
25539
26146
|
} else {
|
|
25540
|
-
if (hostsEnabled &&
|
|
26147
|
+
if (hostsEnabled && process31.platform !== "darwin" && to && to.includes("localhost") && !to.match(/^(localhost|127\.0\.0\.1)$/)) {
|
|
25541
26148
|
const hostsExist = await checkHosts([to], verbose);
|
|
25542
26149
|
if (!hostsExist[0]) {
|
|
25543
26150
|
debugLog("hosts", `${to} not found in hosts file, adding...`, verbose);
|
|
@@ -25584,7 +26191,7 @@ async function setupProxy(options) {
|
|
|
25584
26191
|
portManager2.usedPorts.add(finalPort);
|
|
25585
26192
|
debugLog("setup", `Using standard ${targetPort === 443 ? "HTTPS" : "HTTP"} port ${targetPort} for ${to}`, verbose);
|
|
25586
26193
|
}
|
|
25587
|
-
await createProxyServer(from, to, finalPort, sourceUrl, ssl, vitePluginUsage, verbose, cleanUrls, changeOrigin);
|
|
26194
|
+
await createProxyServer(from, to, finalPort, sourceUrl, ssl, vitePluginUsage, verbose, cleanUrls, changeOrigin, resolveAuth(options.auth));
|
|
25588
26195
|
} catch (err) {
|
|
25589
26196
|
debugLog("setup", `Setup failed: ${err}`, verbose);
|
|
25590
26197
|
log.error(`Failed to setup reverse proxy: ${err.message}`);
|
|
@@ -25597,9 +26204,19 @@ async function setupProxy(options) {
|
|
|
25597
26204
|
});
|
|
25598
26205
|
}
|
|
25599
26206
|
}
|
|
25600
|
-
function startHttpRedirectServer(verbose, httpPort = 80, httpsPort = 443) {
|
|
26207
|
+
function startHttpRedirectServer(verbose, httpPort = 80, httpsPort = 443, acmeChallengeWebroot) {
|
|
25601
26208
|
debugLog("redirect", `Starting HTTP redirect server on port ${httpPort}`, verbose);
|
|
25602
26209
|
const server = http.createServer((req, res) => {
|
|
26210
|
+
if (acmeChallengeWebroot && req.url) {
|
|
26211
|
+
const pathname = req.url.split("?", 1)[0];
|
|
26212
|
+
const keyAuth = readAcmeChallenge(acmeChallengeWebroot, pathname);
|
|
26213
|
+
if (keyAuth != null) {
|
|
26214
|
+
debugLog("redirect", `Serving ACME challenge ${pathname}`, verbose);
|
|
26215
|
+
res.writeHead(200, { "content-type": "text/plain" });
|
|
26216
|
+
res.end(keyAuth);
|
|
26217
|
+
return;
|
|
26218
|
+
}
|
|
26219
|
+
}
|
|
25603
26220
|
const rawHost = req.headers.host || "";
|
|
25604
26221
|
const hostname = rawHost.includes(":") ? rawHost.slice(0, rawHost.indexOf(":")) : rawHost;
|
|
25605
26222
|
const target = httpsPort === 443 ? hostname : `${hostname}:${httpsPort}`;
|
|
@@ -25726,7 +26343,7 @@ async function startProxies(options) {
|
|
|
25726
26343
|
debugLog("watch", "No start command found in config", verbose);
|
|
25727
26344
|
}
|
|
25728
26345
|
const primaryDomain = "proxies" in mergedOptions && Array.isArray(mergedOptions.proxies) ? mergedOptions.proxies[0]?.to : ("to" in mergedOptions) ? mergedOptions.to : "rpx.localhost";
|
|
25729
|
-
if (
|
|
26346
|
+
if (process31.platform !== "win32" && (mergedOptions.https || hostsEnabled)) {
|
|
25730
26347
|
const sudoPassword = getSudoPassword();
|
|
25731
26348
|
if (!sudoPassword) {
|
|
25732
26349
|
try {
|
|
@@ -25776,6 +26393,7 @@ async function startProxies(options) {
|
|
|
25776
26393
|
vitePluginUsage: mergedOptions.vitePluginUsage,
|
|
25777
26394
|
start: "start" in mergedOptions ? mergedOptions.start : undefined,
|
|
25778
26395
|
changeOrigin: mergedOptions.changeOrigin,
|
|
26396
|
+
auth: "auth" in mergedOptions ? mergedOptions.auth : undefined,
|
|
25779
26397
|
verbose,
|
|
25780
26398
|
_cachedSSLConfig: mergedOptions._cachedSSLConfig
|
|
25781
26399
|
}];
|
|
@@ -25791,7 +26409,7 @@ async function startProxies(options) {
|
|
|
25791
26409
|
log.info(` These TLDs have HSTS preloading which can bypass local DNS`);
|
|
25792
26410
|
log.info(` Consider using reserved TLDs: .test, .localhost, or .local`);
|
|
25793
26411
|
}
|
|
25794
|
-
if (hostsEnabled &&
|
|
26412
|
+
if (hostsEnabled && process31.platform === "darwin" && customDomains.length > 0) {
|
|
25795
26413
|
const { setupDevelopmentDns: setupDevelopmentDns2 } = await Promise.resolve().then(() => (init_dns(), exports_dns));
|
|
25796
26414
|
const dnsStarted = await setupDevelopmentDns2({ domains: customDomains, verbose });
|
|
25797
26415
|
if (dnsStarted) {
|
|
@@ -25827,8 +26445,8 @@ async function startProxies(options) {
|
|
|
25827
26445
|
verbose: mergedOptions.verbose || false
|
|
25828
26446
|
});
|
|
25829
26447
|
};
|
|
25830
|
-
|
|
25831
|
-
|
|
26448
|
+
process31.on("SIGINT", cleanupHandler);
|
|
26449
|
+
process31.on("SIGTERM", cleanupHandler);
|
|
25832
26450
|
const singlePortMode = mergedOptions.singlePortMode === true;
|
|
25833
26451
|
const httpsPort = mergedOptions.httpsPort ?? 443;
|
|
25834
26452
|
const httpPort = mergedOptions.httpPort ?? 80;
|
|
@@ -25840,7 +26458,7 @@ async function startProxies(options) {
|
|
|
25840
26458
|
const routeEntries = await collectRouteEntries(proxyOptions, hostsEnabled, verbose);
|
|
25841
26459
|
const isHttpPortBusy = await isPortInUse(httpPort, "0.0.0.0", verbose);
|
|
25842
26460
|
if (!isHttpPortBusy) {
|
|
25843
|
-
startHttpRedirectServer(verbose, httpPort, httpsPort);
|
|
26461
|
+
startHttpRedirectServer(verbose, httpPort, httpsPort, mergedOptions.acmeChallengeWebroot);
|
|
25844
26462
|
}
|
|
25845
26463
|
const isPortBusy = await isPortInUse(httpsPort, "0.0.0.0", verbose);
|
|
25846
26464
|
if (isPortBusy) {
|
|
@@ -25900,13 +26518,22 @@ async function collectRouteEntries(proxyOptions, hostsEnabled, verbose) {
|
|
|
25900
26518
|
const cleanUrls = option.cleanUrls || false;
|
|
25901
26519
|
const routePath = option.path;
|
|
25902
26520
|
const basePath = normalizePathPrefix(routePath);
|
|
25903
|
-
|
|
26521
|
+
const auth = resolveAuth(option.auth);
|
|
26522
|
+
if (option.redirect) {
|
|
26523
|
+
const redirect = resolveRedirect(option.redirect);
|
|
26524
|
+
routeEntries.push({
|
|
26525
|
+
host: domain,
|
|
26526
|
+
path: routePath,
|
|
26527
|
+
route: { redirect, basePath, auth }
|
|
26528
|
+
});
|
|
26529
|
+
debugLog("proxies", `Route: ${domain}${routePath ?? ""} → redirect ${redirect.status} ${redirect.to}${auth ? " (auth)" : ""}`, verbose);
|
|
26530
|
+
} else if (option.static) {
|
|
25904
26531
|
routeEntries.push({
|
|
25905
26532
|
host: domain,
|
|
25906
26533
|
path: routePath,
|
|
25907
|
-
route: { static: resolveStaticRoute(option.static, cleanUrls), cleanUrls, basePath }
|
|
26534
|
+
route: { static: resolveStaticRoute(option.static, cleanUrls), cleanUrls, basePath, auth }
|
|
25908
26535
|
});
|
|
25909
|
-
debugLog("proxies", `Route: ${domain}${routePath ?? ""} → static ${typeof option.static === "string" ? option.static : option.static.dir}`, verbose);
|
|
26536
|
+
debugLog("proxies", `Route: ${domain}${routePath ?? ""} → static ${typeof option.static === "string" ? option.static : option.static.dir}${auth ? " (auth)" : ""}`, verbose);
|
|
25910
26537
|
} else {
|
|
25911
26538
|
const fromUrl = new URL(option.from?.startsWith("http") ? option.from : `http://${option.from}`);
|
|
25912
26539
|
routeEntries.push({
|
|
@@ -25917,10 +26544,11 @@ async function collectRouteEntries(proxyOptions, hostsEnabled, verbose) {
|
|
|
25917
26544
|
cleanUrls,
|
|
25918
26545
|
changeOrigin: option.changeOrigin || false,
|
|
25919
26546
|
pathRewrites: option.pathRewrites,
|
|
25920
|
-
basePath
|
|
26547
|
+
basePath,
|
|
26548
|
+
auth
|
|
25921
26549
|
}
|
|
25922
26550
|
});
|
|
25923
|
-
debugLog("proxies", `Route: ${domain}${routePath ?? ""} → ${fromUrl.host}`, verbose);
|
|
26551
|
+
debugLog("proxies", `Route: ${domain}${routePath ?? ""} → ${fromUrl.host}${auth ? " (auth)" : ""}`, verbose);
|
|
25924
26552
|
}
|
|
25925
26553
|
if (seenDomains.has(domain)) {
|
|
25926
26554
|
continue;
|
|
@@ -25998,15 +26626,19 @@ init_config();
|
|
|
25998
26626
|
init_https();
|
|
25999
26627
|
init_macos_trust();
|
|
26000
26628
|
init_cert_inspect();
|
|
26629
|
+
init_port_manager();
|
|
26001
26630
|
init_registry();
|
|
26002
26631
|
init_dns();
|
|
26003
26632
|
init_dns_state();
|
|
26004
26633
|
init_daemon();
|
|
26005
26634
|
init_proxy_handler();
|
|
26635
|
+
init_auth();
|
|
26006
26636
|
init_host_routes();
|
|
26007
26637
|
init_static_files();
|
|
26008
26638
|
init_sni();
|
|
26009
26639
|
init_on_demand();
|
|
26640
|
+
init_site_resolver();
|
|
26641
|
+
init_site_supervisor();
|
|
26010
26642
|
init_utils();
|
|
26011
26643
|
|
|
26012
26644
|
// src/simplified-plugin.ts
|
|
@@ -26167,11 +26799,11 @@ function VitePluginRpx(options) {
|
|
|
26167
26799
|
if (!hasSudoAccess) {
|
|
26168
26800
|
const origLog = console.log;
|
|
26169
26801
|
console.log = () => {};
|
|
26170
|
-
|
|
26802
|
+
process34.stdout.write(`
|
|
26171
26803
|
Sudo access required for proxy setup.
|
|
26172
26804
|
`);
|
|
26173
26805
|
const gotSudoAccess = await new Promise((resolve14) => {
|
|
26174
|
-
const sudo =
|
|
26806
|
+
const sudo = spawn3("sudo", ["true"], {
|
|
26175
26807
|
stdio: "inherit"
|
|
26176
26808
|
});
|
|
26177
26809
|
sudo.on("exit", (code) => {
|
|
@@ -26181,7 +26813,7 @@ Sudo access required for proxy setup.
|
|
|
26181
26813
|
console.log = origLog;
|
|
26182
26814
|
if (!gotSudoAccess) {
|
|
26183
26815
|
console.error("Failed to get sudo access. Please try again.");
|
|
26184
|
-
|
|
26816
|
+
process34.exit(1);
|
|
26185
26817
|
}
|
|
26186
26818
|
}
|
|
26187
26819
|
}
|
|
@@ -26191,10 +26823,10 @@ Sudo access required for proxy setup.
|
|
|
26191
26823
|
handleSignal("CLOSE").catch(console.error);
|
|
26192
26824
|
});
|
|
26193
26825
|
}
|
|
26194
|
-
const registeredEvents =
|
|
26826
|
+
const registeredEvents = process34.listeners("SIGINT").length > 0 && process34.listeners("SIGTERM").length > 0;
|
|
26195
26827
|
if (!registeredEvents) {
|
|
26196
|
-
|
|
26197
|
-
|
|
26828
|
+
process34.once("SIGINT", () => handleSignal("SIGINT").catch(console.error));
|
|
26829
|
+
process34.once("SIGTERM", () => handleSignal("SIGTERM").catch(console.error));
|
|
26198
26830
|
}
|
|
26199
26831
|
const colorUrl = (url) => colors.cyan(url.replace(/:(\d+)\//, (_, port) => `:${colors.bold(port)}/`));
|
|
26200
26832
|
const originalPrintUrls = serverInstance.printUrls;
|
|
@@ -26232,7 +26864,7 @@ Sudo access required for proxy setup.
|
|
|
26232
26864
|
debug("Proxy setup complete");
|
|
26233
26865
|
} catch (error) {
|
|
26234
26866
|
console.error("Failed to start reverse proxy:", error);
|
|
26235
|
-
|
|
26867
|
+
process34.exit(1);
|
|
26236
26868
|
}
|
|
26237
26869
|
};
|
|
26238
26870
|
if (serverInstance.httpServer) {
|