UNPKG

@stacksjs/rpx

Version:

A modern and smart reverse proxy.

3 lines (2 loc) 33.1 kB
import{$ as wR,$a as yS,A as L_,Aa as YS,B as y,Ba as $R,C as e,Ca as GR,D as IR,Da as $S,E as O_,Ea as GS,F as U,Fa as JS,G as o,Ga as QS,H as c_,Ha as VS,I as h_,Ia as ZS,J as u_,Ja as zS,K as RR,Ka as DS,L as _R,La as FS,M as v_,Ma as IS,N as SR,Na as HS,O as m_,Oa as WS,P as HR,Pa as ES,Q as b_,Qa as P,R as WR,Ra as UR,S as l_,Sa as L,T as a_,Ta as MS,U as d_,Ua as jS,V as p_,Va as wS,W as g_,Wa as TS,X as ER,Xa as kS,Y as MR,Ya as fS,Z as i_,Za as qS,_ as jR,_a as CS,a as X,aa as QR,ab as US,b as s,ba as n_,bb as PS,c as G_,ca as TR,cb as LS,d as J_,da as t_,db as OS,e as Q_,ea as kR,f as V_,fa as s_,fb as cS,g as Z_,ga as fR,gb as hS,h as z_,ha as dR,hb as uS,i as D_,ia as r_,ib as vS,j as F_,ja as qR,jb as mS,k as I_,ka as e_,kb as bS,l as H_,la as o_,lb as lS,m as W_,ma as CR,mb as aS,n as E_,na as RS,nb as dS,o as M_,oa as _S,ob as pS,p as j_,pa as SS,pb as gS,q as w_,qa as AS,qb as iS,r as T_,ra as xS,rb as nS,s as k_,sa as NS,sb as tS,t as f_,ta as BS,tb as sS,u as q_,ua as C,ub as rS,v as C_,va as KS,vb as eS,w as y_,wa as yR,wb as JR,x as U_,xa as XS,xb as oS,y as P_,ya as XR,yb as RA,z as r,za as YR}from"./chunk-y6ft7tr3.js";import{Ab as lR,Bb as Wx,Cb as Ex,Db as Mx,Eb as A,Fb as aR,Gb as jx,Hb as q,Ib as wx,Jb as Tx,Kb as kx,Lb as fx,Mb as qx,Nb as Cx,Ob as yx,Pb as Ux,Qb as Px,zb as FR}from"./chunk-35444zrq.js";import{execSync as A_}from"node:child_process";import*as AR from"node:http";import*as pR from"node:net";import*as gR from"node:os";import*as iR from"node:path";import*as H from"node:process";var b=(_,R)=>(S)=>`\x1B[${_}m${S}\x1B[${R}m`,M={bold:b(1,22),dim:b(2,22),green:b(32,39),cyan:b(36,39)};import*as uR from"node:fs";import*as vR from"node:path";import*as T from"node:process";function mR(_,R){let x=(R&&R!=="/"?`${_}${R}`:_).replace(/[^a-zA-Z0-9._-]+/g,"-").replace(/^-+|-+$/g,"").slice(0,128);return x.length>0?x:"rpx"}async function l(_){if(_.proxies.length===0)throw Error("runViaDaemon: no proxies provided");let R=_.verbose??!1,S=_.registryDir,x=new Set,Y=_.proxies.map(($)=>{let D=$.id??mR($.to,$.path);if(!YR(D))throw Error(`invalid registry id "${D}" derived from to="${$.to}"`);if(x.has(D))throw Error(`duplicate registry id "${D}" — set an explicit \`id\` on one of the proxies`);return x.add(D),{...$,id:D}}),K=new Date().toISOString();for(let $ of Y)await $R({id:$.id,from:$.from,to:$.to,path:$.path,pid:_.persistent?void 0:T.pid,cwd:T.cwd(),createdAt:K,cleanUrls:$.cleanUrls,changeOrigin:$.changeOrigin,pathRewrites:$.pathRewrites,static:$.static,loadBalancer:$.loadBalancer},S,R);let N=await JR({rpxDir:_.rpxDir,verbose:R,spawnCommand:_.spawnCommand,startupTimeoutMs:_.startupTimeoutMs,spawnEnv:_.spawnEnv});for(let $ of Y){let D=$.static?`static ${typeof $.static==="string"?$.static:$.static.dir}`:$.from;X.success(`https://${$.to}${D}`)}if(X.info(`(via rpx daemon pid=${N.pid}; \`rpx daemon:status\` to inspect)`),_.detached)return;let Q=!1,G=S??XR(),J=Y.map(($)=>$.id),V=async()=>{if(Q)return;Q=!0;for(let $ of J)await GR($,S,R).catch((D)=>{A("runner",`removeEntry(${$}) failed: ${D}`,R)})},Z=($)=>{A("runner",`received ${$}, unregistering ${J.length} entries`,R),V().finally(()=>T.exit(0))};T.once("SIGINT",Z),T.once("SIGTERM",Z),T.once("exit",()=>{if(Q)return;for(let $ of J)try{uR.unlinkSync(vR.join(G,`${$}.json`))}catch{}}),await new Promise(()=>{})}import{spawn as eR}from"node:child_process";import*as a from"node:process";class d{processes=new Map;isShuttingDown=!1;async startProcess(_,R,S){if(this.processes.has(_)){A("start",`Process ${_} is already running`,S);return}let[x,...Y]=R.command.split(" "),K=R.cwd||a.cwd();A("start",`Starting process ${_}:`,S),A("start",` Command: ${x} ${Y.join(" ")}`,S),A("start",` Working directory: ${K}`,S),A("start",` Environment variables: ${q(R.env)}`,S);let N=eR(x,Y,{cwd:K,env:{...a.env,...R.env},shell:!0,stdio:"inherit"});return this.processes.set(_,{command:R.command,cwd:K,process:N,env:R.env}),new Promise((Q,G)=>{if(N.on("error",(J)=>{if(!this.isShuttingDown)A("start",`Process ${_} failed to start: ${J}`,S),this.processes.delete(_),G(J)}),N.on("exit",(J)=>{if(!this.isShuttingDown&&J!==null&&J!==0)A("start",`Process ${_} exited with code ${J}; leaving the proxy running`,S),this.processes.delete(_),G(Error(`Process ${_} exited with code ${J}`))}),S)N.stdout?.on("data",(J)=>{A("process",`[${_}] ${J.toString().trim()}`,!0)}),N.stderr?.on("data",(J)=>{A("process",`[${_}] ERR: ${J.toString().trim()}`,!0)});setTimeout(()=>{if(!this.isShuttingDown&&N.killed)this.processes.delete(_),G(Error(`Process ${_} was killed during startup`));else A("start",`Process ${_} started successfully`,S),Q()},1000)})}async stopProcess(_,R){let S=this.processes.get(_);if(!S?.process){A("start",`No process found for ${_}`,R);return}return A("start",`Stopping process ${_}`,R),new Promise((x)=>{if(!S.process){x();return}S.process.once("exit",()=>{this.processes.delete(_),A("start",`Process ${_} stopped`,R),x()});try{S.process.kill("SIGTERM"),setTimeout(()=>{if(S.process){A("start",`Force killing process ${_}`,R);try{S.process.kill("SIGKILL")}catch(Y){}}},3000)}catch(Y){A("start",`Error stopping process ${_}: ${Y}`,R),this.processes.delete(_),x()}})}async stopAll(_){if(this.isShuttingDown){A("start","Already shutting down, skipping duplicate stopAll call",_);return}this.isShuttingDown=!0,A("start","Stopping all processes",_);let R=Array.from(this.processes.keys()).map((S)=>this.stopProcess(S,_).catch((x)=>{X.error(`Failed to stop process ${S}:`,x)}));await Promise.allSettled(R),this.processes.clear(),this.isShuttingDown=!1}isRunning(_){let R=this.processes.get(_);return!!R?.process&&!R.process.killed}}var JA=new d;import{timingSafeEqual as oR}from"node:crypto";function R_(_,R){if(_==null)return!1;let S=Buffer.from(_),x=Buffer.from(R);if(S.length!==x.length)return!1;return oR(S,x)}function p(_){return _.toLowerCase().replace(/\.$/,"")}var __=["/.well-known/acme-challenge/"];function S_(_){let R=_.headers.get("host");if(R)return p(R.split(":")[0]);try{return p(new URL(_.url).hostname)}catch{return""}}function VR(_){let R=_.header.toLowerCase(),S=new Set,x=[];for(let G of _.hosts){let J=p(G);if(J.startsWith("*."))x.push(J);else S.add(J)}let Y=_.exemptPaths??__,K=_.forbiddenMessage??`Forbidden: direct origin access is not allowed; requests must arrive via the CDN. `,N=(G)=>{let J=p(G);return S.has(J)||x.some((V)=>QR(J,V))},Q=(G)=>{let J=S_(G);if(!N(J))return;let V="/";try{V=new URL(G.url).pathname}catch{}if(Y.some((Z)=>V.startsWith(Z)))return;if(R_(G.headers.get(R),_.value))return;return new Response(K,{status:403,headers:{"content-type":"text/plain"}})};return Q.protects=N,Q}var i=new d,x_="0.12.0",N_=new yR("0.0.0.0"),h=new Set,t=new Set,ZR=!1,g=null,zR=null;async function v(_){if(ZR)return A("cleanup","Cleanup already in progress, skipping",_?.verbose),zR||Promise.resolve();ZR=!0,A("cleanup","Starting cleanup process",_?.verbose),zR=new Promise((R)=>{g=R});try{await i.stopAll(_?.verbose),X.info("Shutting down proxy servers...");let R=[],S=Array.from(h).map((x)=>new Promise((Y)=>{let K=x;try{if(typeof K.stop==="function")K.stop(!0),A("cleanup","Bun server stopped",_?.verbose),Y();else if(typeof K.close==="function")K.close(()=>{A("cleanup","Server closed successfully",_?.verbose),Y()});else Y()}catch(N){A("cleanup",`Error stopping server: ${N}`,_?.verbose),Y()}}));R.push(...S),h.clear();for(let x of t)_R(x);if(t.clear(),_?.hosts&&_.domains?.length){A("cleanup","Cleaning up hosts file entries",_?.verbose),A("cleanup",`Original domains for cleanup: ${JSON.stringify(_.domains)}`,_?.verbose);let x=_.domains.filter((Y)=>{if(Y==="test.local")return!0;return Y!=="localhost"&&!Y.startsWith("localhost.")&&Y!=="127.0.0.1"});if(A("cleanup",`Filtered domains for cleanup: ${JSON.stringify(x)}`,_?.verbose),x.length>0)X.info("Cleaning up hosts file entries..."),R.push(UR(x,_?.verbose).then(()=>{A("cleanup",`Removed hosts entries for ${x.join(", ")}`,_?.verbose)}).catch((Y)=>{A("cleanup",`Failed to remove hosts entries: ${Y}`,_?.verbose),X.warn(`Failed to clean up hosts file entries for ${x.join(", ")}:`,Y)}))}if(_?.certs&&_.domains?.length){A("cleanup","Cleaning up SSL certificates",_?.verbose),X.info("Cleaning up SSL certificates...");let x=_.domains.map(async(Y)=>{try{await IR(Y,_?.verbose),A("cleanup",`Removed certificates for ${Y}`,_?.verbose)}catch(K){A("cleanup",`Failed to remove certificates for ${Y}: ${K}`,_?.verbose),X.warn(`Failed to clean up certificates for ${Y}:`,K)}});R.push(...x)}await Promise.allSettled(R),A("cleanup","All cleanup tasks completed successfully",_?.verbose),X.success("All cleanup tasks completed successfully")}catch(R){A("cleanup",`Error during cleanup: ${R}`,_?.verbose),X.error("Error during cleanup:",R)}finally{if(g)g();g=null,ZR=!1;let R=_&&"vitePluginUsage"in _&&_.vitePluginUsage===!0;if(H.env.NODE_ENV!=="test"&&H.env.BUN_ENV!=="test"&&!R)H.exit(0)}return zR}var DR=!1;function nR(_){if(DR){A("signal",`Received second ${_} signal, forcing exit`,!0),H.exit(1);return}DR=!0,A("signal",`Received ${_} signal, initiating cleanup`,!0),v().catch((R)=>{A("signal",`Cleanup failed after ${_}: ${R}`,!0),H.exit(1)}).finally(()=>{DR=!1})}H.once("SIGINT",()=>nR("SIGINT"));H.once("SIGTERM",()=>nR("SIGTERM"));H.on("uncaughtException",(_)=>{X.error("Uncaught exception (continuing):",_)});H.on("unhandledRejection",(_)=>{X.error("Unhandled rejection (continuing):",_)});async function u(_,R,S,x=5){A("connection",`Testing connection to ${_}:${R} (retries left: ${x})`,S);let Y=15000,K=Date.now();if(H.env.RPX_BYPASS_CONNECTION_TEST==="true"){A("connection",`Bypassing connection test for ${_}:${R} due to RPX_BYPASS_CONNECTION_TEST flag`,S);return}let N=()=>new Promise((Q,G)=>{let J=pR.connect({host:_,port:R,timeout:3000});J.once("connect",()=>{A("connection",`Successfully connected to ${_}:${R}`,S),J.end(),Q()}),J.once("timeout",()=>{A("connection",`Connection to ${_}:${R} timed out`,S),J.destroy(),G(Error("Connection timed out"))}),J.once("error",(V)=>{A("connection",`Failed to connect to ${_}:${R}: ${V}`,S),J.destroy(),G(V)})});try{await N()}catch(Q){let G=Q;if(Date.now()-K>Y){A("connection",`Connection test timed out after ${Y}ms, but continuing anyway`,S),X.warn(`Connection test to ${_}:${R} timed out, but RPX will try to proceed anyway.`);return}if(G.code==="ECONNREFUSED"&&x>0)return A("connection",`Connection refused, server might be starting up. Retrying in 2 seconds... (${x} retries left)`,S),await new Promise((V)=>setTimeout(V,2000)),u(_,R,S,x-1);if(x>0)try{A("connection",`Trying HTTP request to ${_}:${R}`,S),await new Promise((V,Z)=>{let $=AR.request({hostname:_,port:R,path:"/",method:"HEAD",timeout:5000},(D)=>{A("connection",`Received HTTP response with status: ${D.statusCode}`,S),V()});$.on("error",(D)=>Z(D)),$.on("timeout",()=>{$.destroy(),Z(Error("HTTP request timed out"))}),$.end()}),A("connection",`HTTP request to ${_}:${R} succeeded`,S);return}catch(V){return A("connection",`HTTP request to ${_}:${R} failed: ${V}`,S),A("connection",`Retrying socket connection in 2 seconds... (${x} retries left)`,S),await new Promise((Z)=>setTimeout(Z,2000)),u(_,R,S,x-1)}let J=`Failed to connect to ${_}:${R} after ${5-x} attempts: ${G.message}`;A("connection",`${J}. To bypass this check set RPX_BYPASS_CONNECTION_TEST=true`,S),X.warn(J),X.warn("RPX will try to continue anyway. If you're sure this is correct, you can set RPX_BYPASS_CONNECTION_TEST=true to skip this check.")}}async function PR(_){A("server",`Starting server with options: ${q(_)}`,_.verbose);let R=U(_.from),S=new URL(R.startsWith("http")?R:`http://${R}`),x=new URL((_.to?.startsWith("http")?_.to:`http://${_.to}`)||"rpx.localhost"),Y=Number.parseInt(S.port)||(S.protocol.includes("https:")?443:80),K=[x.hostname];if(LR(_)&&!x.hostname.includes("localhost")&&!x.hostname.includes("127.0.0.1")){A("hosts",`Checking if hosts file entry exists for: ${x.hostname}`,_?.verbose);try{if(!(await L(K,_.verbose))[0]){X.info(`Adding ${x.hostname} to hosts file...`),X.info("This may require sudo/administrator privileges");try{await P(K,_.verbose)}catch(G){if(X.error("Failed to add hosts entry:",G.message),X.warn("You can manually add this entry to your hosts file:"),X.warn(`127.0.0.1 ${x.hostname}`),X.warn(`::1 ${x.hostname}`),H.platform==="win32")X.warn("On Windows:"),X.warn("1. Run notepad as administrator"),X.warn("2. Open C:\\Windows\\System32\\drivers\\etc\\hosts");else X.warn("On Unix systems:"),X.warn("sudo nano /etc/hosts")}}else A("hosts",`Host entry already exists for ${x.hostname}`,_.verbose)}catch(Q){X.error("Failed to check hosts file:",Q.message)}}try{await u(S.hostname,Y,_.verbose)}catch(Q){A("server",`Connection test failed: ${Q}`,_.verbose),X.error(Q.message),X.warn("Continuing with proxy setup despite connection test failure..."),X.info("If you need to bypass connection testing, set environment variable RPX_BYPASS_CONNECTION_TEST=true")}let N=_._cachedSSLConfig||null;if(_.https)try{if(_.https===!0)_.https=e({..._,to:x.hostname});if(N=await y({..._,to:x.hostname,https:_.https}),!N){if(A("ssl",`Generating new certificates for ${x.hostname}`,_.verbose),await r({..._,from:S.toString(),to:x.hostname,https:_.https}),N=await y({..._,to:x.hostname,https:_.https}),!N)throw Error(`Failed to load SSL configuration after generating certificates for ${x.hostname}`)}}catch(Q){throw A("server",`SSL setup failed: ${Q}`,_.verbose),Q}A("server",`Setting up reverse proxy with SSL config for ${x.hostname}`,_.verbose),await K_({..._,from:R,originalFrom:_.from||R,to:x.hostname,fromPort:Y,sourceUrl:{hostname:S.hostname,host:S.host},ssl:N})}async function B_(_,R,S,x,Y,K,N,Q,G,J,V,Z){A("proxy",`Creating proxy server ${_} -> ${R} with cleanUrls: ${Q}`,N);let $=o(V??x.host,Z);RR($);let D=[{host:R,route:{sourceHost:x.host,upstreamPool:$,cleanUrls:Q||!1,changeOrigin:G||!1,basePath:"/",auth:J}}];if(!n({routeEntries:D,listenPort:S,sslConfig:Y,originGuard:null,verbose:N??!1}))throw _R($),Error(`Failed to start proxy server for ${R} on port ${S}`);t.add($),$_({from:_,to:R,vitePluginUsage:K,listenPort:S,ssl:!!Y,cleanUrls:Q,verbose:N})}async function K_(_){A("setup",`Setting up reverse proxy: ${q(_)}`,_.verbose);let{from:R,originalFrom:S,to:x,sourceUrl:Y,ssl:K,verbose:N,cleanup:Q,vitePluginUsage:G,changeOrigin:J,cleanUrls:V}=_,Z=80,$=443,D="0.0.0.0",k=_.portManager||N_,O=LR(_);try{if(O&&x&&!x.includes("localhost")&&!x.includes("127.0.0.1")){if(!(await L([x],N))[0]){X.warn(`The hostname ${x} isn't in your hosts file. Adding it now...`);try{await P([x],N),X.success(`Added ${x} to your hosts file.`)}catch(c){X.error(`Failed to add ${x} to your hosts file: ${c}`),X.info(`You may need to manually add '127.0.0.1 ${x}' to your /etc/hosts file.`)}}}else if(O&&H.platform!=="darwin"&&x&&x.includes("localhost")&&!x.match(/^(localhost|127\.0\.0\.1)$/)){if(!(await L([x],N))[0]){A("hosts",`${x} not found in hosts file, adding...`,N);try{await P([x],N)}catch(c){A("hosts",`Failed to add ${x} to hosts file: ${c}`,N)}}}if(K&&!k.usedPorts.has(Z)){if(!await C(Z,D,N))A("setup","Starting HTTP redirect server",N),tR(N),k.usedPorts.add(Z);else if(A("setup","Port 80 is in use, skipping HTTP redirect",N),N)X.warn("Port 80 is in use, HTTP to HTTPS redirect will not be available")}let F=K?$:Z,j=await C(F,D,N),E;if(j){if(A("setup",`Port ${F} is already in use`,N),N)X.warn(`Port ${F} is already in use. This may be another instance of rpx or another service.`);if(F===443){if(E=await k.getNextAvailablePort(3443,!0),A("setup",`Using port ${E} instead of ${F}`,N),N)X.info(`Using port ${E} instead. Access your site at https://${x}:${E}`)}else if(E=await k.getNextAvailablePort(F+1000,!0),A("setup",`Using port ${E} instead of ${F}`,N),N)X.info(`Using port ${E} instead. Access your site at http://${x}:${E}`)}else E=F,k.usedPorts.add(E),A("setup",`Using standard ${F===443?"HTTPS":"HTTP"} port ${F} for ${x}`,N);await B_(R,x,E,Y,K,G,N,V,J,SR(_.auth),S,_.loadBalancer)}catch(F){A("setup",`Setup failed: ${F}`,N),X.error(`Failed to setup reverse proxy: ${F.message}`),v({domains:[x],hosts:typeof Q==="boolean"?Q:Q?.hosts,certs:typeof Q==="boolean"?Q:Q?.certs,verbose:N,vitePluginUsage:G})}}function tR(_,R=80,S=443,x,Y){A("redirect",`Starting HTTP redirect server on port ${R}`,_);let K=AR.createServer((N,Q)=>{let G=N.url?N.url.split("?",1)[0]:"";if(G.startsWith("/.well-known/acme-challenge/")){if(Y){let $=Y.challengeStore.handlePath(G);if($!==void 0){A("redirect",`Serving on-demand ACME challenge ${G}`,_),Q.writeHead(200,{"content-type":"text/plain"}),Q.end($);return}}if(x){let $=jR(x,G);if($!=null){A("redirect",`Serving ACME challenge ${G}`,_),Q.writeHead(200,{"content-type":"text/plain"}),Q.end($);return}}if(Y){Q.writeHead(404,{"content-type":"text/plain"}),Q.end("challenge not found");return}}let J=N.headers.host||"",V=J.includes(":")?J.slice(0,J.indexOf(":")):J;if(Y&&V&&!Y.hasCert(V))Y.ensureCert(V).catch(()=>{});let Z=S===443?V:`${V}:${S}`;A("redirect",`Redirecting request from ${J}${N.url} to https://${Z}`,_),Q.writeHead(301,{Location:`https://${Z}${N.url}`}),Q.end()}).listen(R);h.add(K),A("redirect","HTTP redirect server started",_)}function X_(_){let R={...s,..._};if(A("proxy",`Starting proxy with options: ${q(R)}`,R?.verbose),R.viaDaemon){if(!R.from||!R.to){X.error("viaDaemon mode requires both `from` and `to`");return}l({proxies:[{id:R.id,from:R.from,to:R.to,path:R.path,cleanUrls:R.cleanUrls,changeOrigin:R.changeOrigin,pathRewrites:R.pathRewrites}],verbose:R.verbose}).catch((G)=>{X.error(`Failed to register with rpx daemon: ${G.message}`),H.exit(1)});return}let S=R.to||"",x=S.split(".").pop()?.toLowerCase()||"",Y=H.platform==="darwin"&&S&&!S.includes("localhost")&&!S.includes("127.0.0.1"),K=["dev","app","page","new","day","foo"],N=["test","localhost","local","example","invalid"];if(Y&&K.includes(x)&&R?.verbose)X.warn(`The .${x} TLD may not work reliably for local development`),X.info(` Google owns .${x} with HSTS preloading, which can bypass local DNS`),X.info(" Consider using a reserved TLD: .test, .localhost, or .local");if(Y)import("./chunk-ar58xtsq.js").then(({setupDevelopmentDns:G})=>{G({domains:[S],verbose:R.verbose}).then((J)=>{if(J)Promise.resolve().then(()=>{if(R.verbose)if(N.includes(x))X.success(`DNS server started for .${x} domains`);else X.success(`DNS server started for .${x} domains (hosts file entry also added)`)});else A("dns",`Could not start DNS server - ${S} may not resolve in browser`,R.verbose)})}).catch((G)=>{A("dns",`Failed to start DNS server: ${G}`,R.verbose)});let Q={from:R.from,to:R.to,cleanUrls:R.cleanUrls,https:e(R),cleanup:R.cleanup,vitePluginUsage:R.vitePluginUsage,changeOrigin:R.changeOrigin,verbose:R.verbose,regenerateUntrustedCerts:R.regenerateUntrustedCerts};A("proxy",`Server options: ${q(Q)}`,R.verbose),PR(Q).catch((G)=>{A("proxy",`Failed to start proxy: ${G}`,R.verbose),X.error(`Failed to start proxy: ${G.message}`),v({domains:[R.to],hosts:typeof R.cleanup==="boolean"?R.cleanup:R.cleanup?.hosts,certs:typeof R.cleanup==="boolean"?R.cleanup:R.cleanup?.certs,verbose:R.verbose})})}function Y_(_){return _?.verbose||!1}function LR(_){if(_?.hostsManagement===!1)return!1;let R=_?.cleanup;if(R===!1)return!1;if(R&&typeof R==="object"&&R.hosts===!1)return!1;return!0}async function OR(_){let R={from:"localhost:5173",to:"rpx.localhost",https:!1,cleanup:{hosts:!0,certs:!1},vitePluginUsage:!1,verbose:!1,cleanUrls:!1,changeOrigin:!1,regenerateUntrustedCerts:!0};if(_)R={...R,..._};let S=Y_(R),x=LR(R);if(A("config",`Starting with config: ${q(R,2)}`,S),A("config",`Is multi-proxy? ${"proxies"in R}`,S),A("config",`Hosts management enabled? ${x}`,S),R.viaDaemon){let z="proxies"in R&&Array.isArray(R.proxies)?R.proxies.map((I)=>({id:I.id,from:I.from,to:I.to,path:I.path,cleanUrls:I.cleanUrls??R.cleanUrls,changeOrigin:I.changeOrigin??R.changeOrigin,pathRewrites:I.pathRewrites})):[{id:R.id,from:R.from,to:R.to??"rpx.localhost",path:R.path,cleanUrls:R.cleanUrls,changeOrigin:R.changeOrigin,pathRewrites:R.pathRewrites}];await l({proxies:z,verbose:S});return}if("proxies"in R&&Array.isArray(R.proxies)){A("servers",`Found ${R.proxies.length} proxies in config`,S);for(let B of R.proxies)if(B.start){let z=`${B.from}-${B.to}`;try{A("watch",`Starting command for ${z} with command: ${B.start.command}`,S),X.info(`Starting command for ${z}...`),await i.startProcess(z,B.start,S);let I=U(B.from),W=new URL(I.startsWith("http")?I:`http://${I}`),w=W.hostname||"localhost",f=Number(W.port)||80;try{await u(w,f,S),A("watch",`Dev server is ready at ${w}:${f}`,S)}catch(xR){A("watch",`Connection check failed, but continuing with proxy setup: ${xR}`,S),X.warn("Dev server connection check failed. RPX will try to proceed anyway...")}}catch(I){throw A("watch",`Failed to start command for ${z}: ${I}`,S),Error(`Failed to start command for ${z}: ${I}`)}}else A("watch",`No start command for proxy ${B.from} -> ${B.to}`,S)}else if("start"in R&&R.start){A("watch","Found start command in single proxy config",S);let B=`${R.from}-${R.to}`;try{if(R.start)A("watch",`Starting command: ${R.start.command}`,S),await i.startProcess(B,R.start,S);let z=U(R.from),I=new URL(z.startsWith("http")?z:`http://${z}`),W=I.hostname||"localhost",w=Number(I.port)||80;try{await u(W,w,S),A("watch",`Dev server is ready at ${W}:${w}`,S)}catch(f){A("watch",`Connection check failed, but continuing with proxy setup: ${f}`,S),X.warn("Dev server connection check failed. RPX will try to proceed anyway...")}}catch(z){throw A("watch",`Failed to run start command: ${z}`,S),Error(`Failed to run start command: ${z}`)}}else A("watch","No start command found in config",S);let Y="proxies"in R&&Array.isArray(R.proxies)?R.proxies[0]?.to:("to"in R)?R.to:"rpx.localhost";if(H.platform!=="win32"&&(R.https||x)){if(!lR())try{A("sudo","Pre-acquiring sudo credentials for privileged operations",S),A_("sudo -v",{stdio:"inherit"})}catch{A("sudo","Could not pre-acquire sudo credentials",S)}}let K=[];if(R.productionCerts){if(K=await qR(R.productionCerts,S),K.length>0)A("ssl",`Using ${K.length} production SNI cert(s): ${K.map((B)=>B.serverName).join(", ")}`,S)}if(R.https){let B=K.length>0?null:await y(R);if(!B&&K.length===0){if(A("ssl",`No valid or trusted certificates found for ${Y}, generating new ones`,R.verbose),await r(R),B=await y(R),!B)throw Error(`Failed to load SSL certificates after generation for ${Y}`)}else A("ssl",`Using existing and trusted certificates for ${Y}`,R.verbose);R._cachedSSLConfig=B}let N="proxies"in R&&Array.isArray(R.proxies)?R.proxies.map((B)=>({...B,https:R.https,cleanup:R.cleanup,cleanUrls:B.cleanUrls??("cleanUrls"in R?R.cleanUrls:!1),vitePluginUsage:R.vitePluginUsage,changeOrigin:B.changeOrigin??R.changeOrigin,verbose:S,_cachedSSLConfig:R._cachedSSLConfig})):[{from:"from"in R?R.from:"localhost:5173",to:"to"in R?R.to:"rpx.localhost",cleanUrls:"cleanUrls"in R?R.cleanUrls:!1,https:R.https,cleanup:R.cleanup,vitePluginUsage:R.vitePluginUsage,start:"start"in R?R.start:void 0,changeOrigin:R.changeOrigin,auth:"auth"in R?R.auth:void 0,verbose:S,_cachedSSLConfig:R._cachedSSLConfig}],Q=N.map((B)=>B.to||"rpx.localhost"),G=K.length>0?K:R._cachedSSLConfig??null,J=Q.filter((B)=>B&&!B.includes("localhost")&&!B.includes("127.0.0.1")),V=["dev","app","page","new","day","foo"],Z=["test","localhost","local","example","invalid"],$=[...new Set(J.map((B)=>B.split(".").pop()?.toLowerCase()))],D=$.filter((B)=>!!B&&V.includes(B));if(D.length>0&&S)X.warn(`The following TLDs may not work reliably for local development: ${D.map((B)=>`.${B}`).join(", ")}`),X.info(" These TLDs have HSTS preloading which can bypass local DNS"),X.info(" Consider using reserved TLDs: .test, .localhost, or .local");if(x&&H.platform==="darwin"&&J.length>0){let{setupDevelopmentDns:B}=await import("./chunk-ar58xtsq.js");if(await B({domains:J,verbose:S})){if(S)if($.every((W)=>!!W&&Z.includes(W)))X.success(`DNS server started for ${$.map((W)=>`.${W}`).join(", ")} domains`);else X.success(`DNS server started for ${$.map((W)=>`.${W}`).join(", ")} domains (hosts file entries also added)`)}else A("dns","Could not start DNS server - custom domains may not resolve",S)}let k=async()=>{A("cleanup","Starting cleanup handler",R.verbose);try{let{tearDownDevelopmentDns:B}=await import("./chunk-ar58xtsq.js");await B({verbose:R.verbose})}catch(B){A("cleanup",`Error stopping DNS server: ${B}`,R.verbose)}try{await i.stopAll(R.verbose)}catch(B){A("cleanup",`Error stopping processes: ${B}`,R.verbose)}await v({domains:Q,hosts:typeof R.cleanup==="boolean"?R.cleanup:R.cleanup?.hosts,certs:typeof R.cleanup==="boolean"?R.cleanup:R.cleanup?.certs,verbose:R.verbose||!1})};H.on("SIGINT",k),H.on("SIGTERM",k);let O=R.singlePortMode===!0,F=R.httpsPort??443,j=R.httpPort??80,E=R.originGuard?VR(R.originGuard):null,m=!!G&&(N.length>1||O||K.length>0),c=!G&&O&&N.length>0;if(m&&G){A("proxies",`Creating shared HTTPS server for ${N.length} domains on port ${F}`,S);let B=await bR(N,x,S),z=null,I=R.onDemandTls,W=I?.enabled?new CR({config:I,certsDir:I.certsDir??R.productionCerts?.certsDir??iR.join(gR.homedir(),".stacks","rpx","on-demand-certs"),initial:K,verbose:S,onCertAdded:(NR)=>{xR(NR)}}):null,w=null,f=!1;async function xR(NR){if(w=NR,f)return;f=!0;try{while(w){let cR=w;if(w=null,A("proxies",`rebuilding :${F} with ${cR.length} SNI cert(s)`,S),z)h.delete(z),z.stop(!1);let BR=!1;for(let KR=0;!BR&&KR<60;KR++){let hR=n({routeEntries:B,listenPort:F,sslConfig:cR,originGuard:E,verbose:S});if(hR){z=hR,BR=!0;break}await new Promise((rR)=>setTimeout(rR,Math.min(25*2**Math.min(KR,4),500)))}if(!BR)X.error(`rpx: CRITICAL — could not rebind :${F} after cert issuance; HTTPS unbound until the next cert event or a gateway restart`)}}finally{f=!1}}if(!await C(j,"0.0.0.0",S))tR(S,j,F,R.acmeChallengeWebroot,W);if(await C(F,"0.0.0.0",S)){if(A("proxies",`Port ${F} is already in use, cannot start shared proxy`,S),S)X.warn(`Port ${F} is in use. Shared HTTPS proxy cannot start.`);return}let sR=W&&W.sniEntries().length>0?W.sniEntries():G;if(z=n({routeEntries:B,listenPort:F,sslConfig:sR,originGuard:E,verbose:S}),!z){X.error(`Shared HTTPS proxy failed to bind :${F}; not exiting`);return}}else if(c){A("proxies",`Creating shared HTTP server for ${N.length} domains on port ${j}`,S);let B=await bR(N,x,S);if(await C(j,"0.0.0.0",S)){if(A("proxies",`Port ${j} is already in use, cannot start shared proxy`,S),S)X.warn(`Port ${j} is in use. Shared HTTP proxy cannot start.`);return}if(!n({routeEntries:B,listenPort:j,sslConfig:null,originGuard:E,verbose:S})){X.error(`Shared HTTP proxy failed to bind :${j}; not exiting`);return}}else for(let B of N)try{let z=B.to||"rpx.localhost";A("proxy",`Starting proxy for ${z} with SSL config: ${!!G}`,B.verbose),await PR({from:B.from||"localhost:5173",to:z,cleanUrls:B.cleanUrls||!1,https:B.https||!1,cleanup:B.cleanup||!1,vitePluginUsage:B.vitePluginUsage||!1,verbose:B.verbose||!1,_cachedSSLConfig:R._cachedSSLConfig,changeOrigin:B.changeOrigin||!1,loadBalancer:B.loadBalancer,auth:B.auth,path:B.path,pathRewrites:B.pathRewrites})}catch(z){A("proxies",`Failed to start proxy for ${B.to}: ${z}`,B.verbose),X.error(`Failed to start proxy for ${B.to}:`,z)}}async function bR(_,R,S){let x=[],Y=new Set;for(let K of _){let N=K.to||"rpx.localhost",Q=K.cleanUrls||!1,G=K.path,J=TR(G),V=SR(K.auth);if(K.redirect){let Z=HR(K.redirect);x.push({host:N,path:G,route:{redirect:Z,basePath:J,auth:V}}),A("proxies",`Route: ${N}${G??""} → redirect ${Z.status} ${Z.to}${V?" (auth)":""}`,S)}else if(K.static)x.push({host:N,path:G,route:{static:WR(K.static,Q),cleanUrls:Q,basePath:J,auth:V}}),A("proxies",`Route: ${N}${G??""} → static ${typeof K.static==="string"?K.static:K.static.dir}${V?" (auth)":""}`,S);else{let Z=U(K.from),$=new URL(Z.startsWith("http")?Z:`http://${Z}`),D=o(K.from??$.host,K.loadBalancer);RR(D),t.add(D),x.push({host:N,path:G,route:{sourceHost:$.host,upstreamPool:D,cleanUrls:Q,changeOrigin:K.changeOrigin||!1,pathRewrites:K.pathRewrites,basePath:J,auth:V}}),A("proxies",`Route: ${N}${G??""}${$.host}${V?" (auth)":""}`,S)}if(Y.has(N))continue;if(Y.add(N),R&&!wR(N)&&!N.includes("localhost")&&!N.includes("127.0.0.1"))try{if(!(await L([N],S))[0])await P([N],S)}catch{A("hosts",`Could not add hosts entry for ${N}`,S)}}return x}function n(_){let{routeEntries:R,listenPort:S,sslConfig:x,originGuard:Y,verbose:K}=_,N=kR(R),Q=ER((V,Z)=>fR(N,V,Z),K),G=Y?(V,Z)=>Y(V)??Q(V,Z):Q,J=MR(K);try{let V=Bun.serve({port:S,hostname:"0.0.0.0",reusePort:aR(),...x?{tls:dR(Array.isArray(x)?x.map((Z)=>({serverName:Z.serverName,key:Z.key,cert:Z.cert})):{key:x.key,cert:x.cert,ca:x.ca,requestCert:!1,rejectUnauthorized:!1})}:{},fetch(Z,$){return G(Z,$)},websocket:J,error(Z){return A("server",`Shared proxy server error: ${Z}`,K),new Response(`Server Error: ${Z.message}`,{status:500})}});return h.add(V),A("proxies",`Shared ${x?"HTTPS":"HTTP"} proxy listening on port ${S} for ${N.size} domains`,K),V}catch(V){return A("proxies",`Failed to start shared proxy: ${V}`,K),console.error("Failed to start shared proxy:",V),null}}function $_(_){if(_?.vitePluginUsage||!_?.verbose)return;if(console.log(""),console.log(` ${M.green(M.bold("rpx"))} ${M.green(`v${x_}`)}`),console.log(` ${M.green("➜")} ${M.dim(_?.from??"")} ${M.dim("➜")} ${M.cyan(_?.ssl?`https://${_?.to}`:`http://${_?.to}`)}`),_?.listenPort!==(_?.ssl?443:80))console.log(` ${M.green("➜")} Listening on port ${_?.listenPort}`);if(_?.cleanUrls)console.log(` ${M.green("➜")} Clean URLs enabled`)}var Hx=OR;export{$R as writeEntry,QS as watchRegistry,Z_ as verifyHttpsChain,T_ as trustRootCaForBrowsers,lS as tearDownDevelopmentDns,bS as syncDevelopmentDnsFromRegistry,g_ as stripBasePath,_R as stopHealthChecks,LS as stopDnsServer,oS as stopDaemon,PR as startServer,X_ as startProxy,OR as startProxies,RR as startHealthChecks,PS as startDnsServer,_S as siteIdForHost,aR as shouldReusePort,uS as setupResolver,mS as setupDevelopmentDns,r_ as serverNameFromCertFilename,p_ as serveStaticFile,c_ as selectUpstream,q as safeStringify,a_ as safeRelativePath,Px as safeDeleteFile,l as runViaDaemon,rS as runDaemon,cS as resolverFilePath,qS as resolverBasenamesForDomains,fS as resolverBasenameForDomain,WR as resolveStaticRoute,d_ as resolveStaticFile,HR as resolveRedirect,Ux as resolvePathRewrite,SR as resolveAuth,zS as renderStartingPage,DS as renderFailedPage,jS as removeStaleRpxHosts,aS as removeResolver,vS as removeLegacyTldResolvers,UR as removeHosts,GR as removeEntry,sS as releaseDaemonLock,jx as redactSensitive,dS as reconcileStaleDevelopmentDns,RA as reconcileDevelopmentDnsOnIdle,AS as readSiteManifest,$S as readEntry,iS as readDaemonPid,J_ as readCertSha256Fingerprint,Q_ as readCertCommonName,GS as readAll,jR as readAcmeChallenge,M_ as pruneStaleRootCas,SS as projectNameFromHost,U as primaryUpstreamUrl,XS as portManager,t_ as pathPrefixMatches,z_ as parseSha256HashesFromSecurityListing,v_ as parseHtpasswd,IS as parseHostsLine,G_ as normalizeSha256Fingerprint,TR as normalizePathPrefix,kS as normalizeDevDomain,QR as matchesWildcard,e_ as matchesAllowedSuffix,fR as matchHostRoute,s_ as matchHostList,n_ as matchHost,h_ as markSuccess,u_ as markFailure,U_ as loadSSLConfig,BS as listDiscoverableSites,E_ as listCertSha256HashesByCommonName,wR as isWildcardPattern,Tx as isValidRootCA,YR as isValidId,Cx as isSingleProxyOptions,yx as isSingleProxyConfig,j_ as isRootCaTrustedForSsl,w_ as isRootCaFingerprintInKeychains,Wx as isProcessElevated,C as isPortInUse,YS as isPidAlive,qx as isMultiProxyOptions,fx as isMultiProxyConfig,o_ as isLikelyHostname,OS as isDnsServerRunning,nS as isDaemonRunning,O_ as isCertTrusted,e as httpsConfig,HS as hostsLineMapsHost,lR as getSudoPassword,y_ as getSharedDaemonCertPaths,C_ as getRootCAPaths,XR as getRegistryDir,kx as getPrimaryDomain,W_ as getMacosTrustKeychains,H_ as getMacosLoginKeychainPath,pS as getDaemonRpxDir,gS as getDaemonPidPath,r as generateCertificate,JS as gcStaleEntries,P_ as forceTrustCertificate,MS as findStaleRpxHosts,KS as findAvailablePort,WS as filterRpxHostsEntries,wx as extractHostname,RS as expandHome,Mx as execSudoSync,ZS as escapeHtml,JR as ensureDaemonRunning,m_ as enforceBasicAuth,ES as dropStaleRpxHostsLines,f_ as devSslToSniEntries,CS as devDomainsFromHosts,xS as detectProjectPreset,mR as deriveIdFromTarget,eS as defaultDaemonSpawnCommand,s as defaultConfig,Hx as default,A as debugLog,o as createUpstreamPool,NS as createSiteResolver,MR as createProxyWebSocketHandler,ER as createProxyFetchHandler,VR as createOriginGuard,l_ as contentTypeFor,hS as contentLooksLikeRpxResolver,s as config,M as colors,L_ as clearSslConfigCache,IR as cleanupCertificates,v as cleanup,L as checkHosts,y as checkExistingCertificates,V_ as certIncludesSanHostnames,qR as buildSniTlsConfig,q_ as buildRegistryTlsProxyOptions,b_ as buildRedirectLocation,kR as buildHostRoutes,Ex as authorizeSystemAccess,P as addHosts,tS as acquireDaemonLock,VS as SiteSupervisor,k_ as SHARED_DEV_HOST_CERT_PATH,I_ as RPX_ROOT_CA_COMMON_NAME,US as RPX_RESOLVER_MARKER,FS as RPX_HOSTS_MARKER,CR as OnDemandCertManager,F_ as MACOS_SYSTEM_KEYCHAIN,D_ as MACOS_CA_TRUST_FLAGS,TS as LEGACY_TLD_RESOLVER_LABELS,yR as DefaultPortManager,wS as DNS_STATE_VERSION,yS as DNS_PORT,i_ as ACME_CHALLENGE_PREFIX};