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.
Files changed (2) hide show
  1. package/dist/index.js +854 -222
  2. 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
- const route = getRoute(hostname, pathname);
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/dns-state.ts
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 path6.join(homedir10(), ".stacks", "rpx");
24665
+ return path7.join(homedir11(), ".stacks", "rpx");
23955
24666
  }
23956
24667
  function getDnsStatePath(rpxDir = defaultRpxDir()) {
23957
- return path6.join(rpxDir, RPX_DNS_STATE_FILE);
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 fsp4.readFile(getDnsStatePath(rpxDir), "utf8");
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 fsp4.mkdir(rpxDir, { recursive: true });
23980
- await fsp4.writeFile(getDnsStatePath(rpxDir), `${JSON.stringify(state, null, 2)}
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 fsp4.rm(getDnsStatePath(rpxDir), { force: true });
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 fsp5 from "node:fs/promises";
24061
- import * as path7 from "node:path";
24062
- import * as process25 from "node:process";
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 (process25.platform !== "darwin")
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 path7.join(MACOS_RESOLVER_DIR, basename);
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 fsp5.readFile(resolverFilePath(basename), "utf8");
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 (process25.platform !== "darwin")
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 (process25.platform !== "darwin")
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 (process25.platform !== "darwin")
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 (process25.platform !== "darwin")
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 ?? process25.pid,
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 ?? process25.pid
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 fsp6 from "node:fs/promises";
24480
- import { homedir as homedir11 } from "node:os";
24481
- import * as path8 from "node:path";
24482
- import * as process26 from "node:process";
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 path8.join(homedir11(), ".stacks", "rpx");
25195
+ return path9.join(homedir12(), ".stacks", "rpx");
24485
25196
  }
24486
25197
  function getDaemonPidPath(rpxDir = getDaemonRpxDir()) {
24487
- return path8.join(rpxDir, "daemon.pid");
25198
+ return path9.join(rpxDir, "daemon.pid");
24488
25199
  }
24489
25200
  async function readDaemonPid(rpxDir = getDaemonRpxDir()) {
24490
25201
  try {
24491
- const raw = await fsp6.readFile(getDaemonPidPath(rpxDir), "utf8");
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 fsp6.unlink(getDaemonPidPath(rpxDir)).catch(() => {});
25214
+ await fsp7.unlink(getDaemonPidPath(rpxDir)).catch(() => {});
24504
25215
  }
24505
25216
  function defaultDaemonSpawnCommand() {
24506
- const exec = process26.execPath;
24507
- const interpName = path8.basename(exec).toLowerCase();
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 && process26.argv[1])
24510
- return [exec, process26.argv[1], "daemon:start"];
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 fsp6.mkdir(rpxDir, { recursive: true });
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 ?? process26.cwd(),
24537
- env: opts.spawnEnv ? { ...process26.env, ...opts.spawnEnv } : process26.env
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 spawn2 } from "node:child_process";
24576
- import * as process31 from "node:process";
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 process30 from "node:process";
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 path9 from "node:path";
24605
- import * as process27 from "node:process";
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 : process27.pid,
24634
- cwd: process27.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(() => process27.exit(0));
25385
+ cleanup().finally(() => process28.exit(0));
24672
25386
  };
24673
- process27.once("SIGINT", onSignal);
24674
- process27.once("SIGTERM", onSignal);
24675
- process27.once("exit", () => {
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(path9.join(dirForCleanup, `${id}.json`));
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 path10 from "node:path";
24693
- import * as process28 from "node:process";
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 = process28.platform === "win32" ? path10.join(process28.env.windir || "C:\\Windows", "System32", "drivers", "etc", "hosts") : "/etc/hosts";
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 (process28.platform === "win32")
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 = path10.join(os2.tmpdir(), `rpx-hosts-${Date.now()}.tmp`);
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 = path10.join(os2.tmpdir(), `rpx-hosts-${Date.now()}.tmp`);
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 process29 from "node:process";
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 || process29.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 = spawn(cmd, args, {
25621
+ const childProcess = spawn2(cmd, args, {
25036
25622
  cwd: cwd4,
25037
- env: { ...process29.env, ...options.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 timingSafeEqual(a2, b2);
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 (process30.env.NODE_ENV !== "test" && process30.env.BUN_ENV !== "test" && !isVitePluginCall) {
25292
- process30.exit(0);
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
- process30.exit(1);
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
- process30.exit(1);
25914
+ process31.exit(1);
25309
25915
  }).finally(() => {
25310
25916
  isHandlingSignal = false;
25311
25917
  });
25312
25918
  }
25313
- process30.once("SIGINT", () => signalHandler("SIGINT"));
25314
- process30.once("SIGTERM", () => signalHandler("SIGTERM"));
25315
- process30.on("uncaughtException", (err) => {
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
- process30.on("unhandledRejection", (reason) => {
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 (process30.env.RPX_BYPASS_CONNECTION_TEST === "true") {
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 (process30.platform === "win32") {
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 && process30.platform !== "darwin" && to && to.includes("localhost") && !to.match(/^(localhost|127\.0\.0\.1)$/)) {
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 (process30.platform !== "win32" && (mergedOptions.https || hostsEnabled)) {
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 && process30.platform === "darwin" && customDomains.length > 0) {
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
- process30.on("SIGINT", cleanupHandler);
25831
- process30.on("SIGTERM", cleanupHandler);
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
- if (option.static) {
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
- process31.stdout.write(`
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 = spawn2("sudo", ["true"], {
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
- process31.exit(1);
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 = process31.listeners("SIGINT").length > 0 && process31.listeners("SIGTERM").length > 0;
26826
+ const registeredEvents = process34.listeners("SIGINT").length > 0 && process34.listeners("SIGTERM").length > 0;
26195
26827
  if (!registeredEvents) {
26196
- process31.once("SIGINT", () => handleSignal("SIGINT").catch(console.error));
26197
- process31.once("SIGTERM", () => handleSignal("SIGTERM").catch(console.error));
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
- process31.exit(1);
26867
+ process34.exit(1);
26236
26868
  }
26237
26869
  };
26238
26870
  if (serverInstance.httpServer) {