@stacksjs/rpx 0.11.27 → 0.11.29

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.
@@ -1 +1 @@
1
- import{$a as l,Qa as a,Ra as b,Sa as c,Ta as d,Ua as e,Va as f,Wa as g,Xa as h,Ya as i,Za as j,_a as k,ab as m,bb as n,cb as o}from"./chunk-6m0f5m3q.js";import"./chunk-2yk7jygq.js";export{m as tearDownDevelopmentDns,l as syncDevelopmentDnsFromRegistry,d as stopDnsServer,c as startDnsServer,i as setupResolver,k as setupDevelopmentDns,g as resolverFilePath,n as removeResolver,j as removeLegacyTldResolvers,o as reconcileStaleDevelopmentDns,f as isRpxDevelopmentDnsAnswering,e as isDnsServerRunning,h as contentLooksLikeRpxResolver,b as RPX_RESOLVER_MARKER,a as DNS_PORT};
1
+ import{$a as b,_a as a,ab as c,bb as d,cb as e,db as f,eb as g,fb as h,gb as i,hb as j,ib as k,jb as l,kb as m,lb as n,mb as o}from"./chunk-9dh9jhc6.js";import"./chunk-z86swbd8.js";export{m as tearDownDevelopmentDns,l as syncDevelopmentDnsFromRegistry,d as stopDnsServer,c as startDnsServer,i as setupResolver,k as setupDevelopmentDns,g as resolverFilePath,n as removeResolver,j as removeLegacyTldResolvers,o as reconcileStaleDevelopmentDns,f as isRpxDevelopmentDnsAnswering,e as isDnsServerRunning,h as contentLooksLikeRpxResolver,b as RPX_RESOLVER_MARKER,a as DNS_PORT};
@@ -46,4 +46,4 @@ export type ConfigOf = Config
46
46
  `.replace(this.ANSI_PATTERN,"");if(this.shouldWriteToFile())await this.writeToFile(G)}async prompt(Q){if(O())return Promise.resolve(!0);return new Promise((U)=>{console.error(`${K.cyan("?")} ${Q} (y/n) `);let X=(Z)=>{let $=Z.toString().trim().toLowerCase();L.stdin.removeListener("data",X);try{if(typeof L.stdin.setRawMode==="function")L.stdin.setRawMode(!1)}catch{}L.stdin.pause(),console.error(""),U($==="y"||$==="yes")};try{if(typeof L.stdin.setRawMode==="function")L.stdin.setRawMode(!0)}catch{}L.stdin.resume(),L.stdin.once("data",X)})}setFancy(Q){this.fancy=Q}isFancy(){return this.fancy}pause(){this.enabled=!1}resume(){this.enabled=!0}async start(Q,...U){if(!this.enabled)return;let X=Q;if(U&&U.length>0){let H=/%([sdijfo%])/g,q=0;if(X=Q.replace(H,(_,A)=>{if(A==="%")return"%";if(q>=U.length)return _;let z=U[q++];switch(A){case"s":return String(z);case"d":case"i":return Number(z).toString();case"j":case"o":return JSON.stringify(z,null,2);default:return _}}),q<U.length)X+=` ${U.slice(q).map((_)=>typeof _==="object"?JSON.stringify(_,null,2):String(_)).join(" ")}`}let{consoleText:Z,fileText:$}=this.buildOutputTexts(X);if(this.shouldStyleConsole()){let H=this.options.showTags!==!1&&this.name?K.gray(this.formatTag(this.name)):"",q=this.options.showIcons===!1?"":`${K.blue("◐")} `;console.error(`${q}${H} ${K.cyan(Z)}`)}let J=`[${new Date().toISOString()}] ${this.environment}.INFO: [START] ${$}
47
47
  `.replace(this.ANSI_PATTERN,"");if(this.shouldWriteToFile())await this.writeToFile(J)}renderProgressBar(Q,U=!1){if(!this.enabled||!this.shouldStyleConsole()||!L.stdout.isTTY)return;let X=Math.min(100,Math.max(0,Math.round(Q.current/Q.total*100))),Z=Math.round(Q.barLength*X/100),$=Q.barLength-Z,Y=K.green("━".repeat(Z)),G=K.gray("━".repeat($)),J=`[${Y}${G}]`,H=`${X}%`.padStart(4),q=Q.message?` ${Q.message}`:"",_=this.options.showIcons===!1?"":U||X===100?K.green("✓"):K.blue("▶"),A=this.options.showTags!==!1&&this.name?` ${K.gray(this.formatTag(this.name))}`:"",z=`\r${_}${A} ${J} ${H}${q}`,W=L.stdout.columns||80,V=" ".repeat(Math.max(0,W-z.replace(this.ANSI_PATTERN,"").length));if(Q.lastRenderedLine=`${z}${V}`,L.stdout.write(Q.lastRenderedLine),U)L.stdout.write(`
48
48
  `)}finishProgressBar(Q,U){if(!this.enabled||!this.fancy||O()||!L.stdout.isTTY){this.activeProgressBar=null;return}if(Q.current<Q.total)Q.current=Q.total;if(U)Q.message=U;this.renderProgressBar(Q,!0),this.activeProgressBar=null}async clear(Q={}){if(O()){console.warn("Log clearing is not supported in browser environments.");return}try{console.warn("Clearing logs...",this.config.logDirectory);let U=await DQ(this.config.logDirectory),X=[];for(let Z of U){if(!(Q.name?new RegExp(Q.name.replace("*",".*")).test(Z):Z.startsWith(this.name))||!Z.endsWith(".log"))continue;let Y=GQ(this.config.logDirectory,Z);if(Q.before)try{if((await YQ(Y)).mtime>=Q.before)continue}catch(G){console.error(`Failed to get stats for file ${Y}:`,G);continue}X.push(Y)}if(X.length===0){console.warn("No log files matched the criteria for clearing.");return}console.warn(`Preparing to delete ${X.length} log file(s)...`);for(let Z of X)try{await NQ(Z),console.warn(`Deleted log file: ${Z}`)}catch($){console.error(`Failed to delete log file ${Z}:`,$)}console.warn("Log clearing process finished.")}catch(U){console.error("Error during log clearing process:",U)}}}var W8=new BQ("stacks");var h0=new BQ("rpx",{showTags:!1});function d0(){return process.env.SUDO_PASSWORD}function f0(){if(process.platform==="win32")return!1;try{return typeof process.getuid==="function"&&process.getuid()===0}catch{return!1}}function L8(Q){if(f0())return EU(Q,{encoding:"utf-8",stdio:["pipe","pipe","pipe"]});let U=d0(),X=Q.replace(/'/g,"'\\''");if(U)return EU(`echo '${U}' | sudo -S sh -c '${X}' 2>/dev/null`,{encoding:"utf-8",stdio:["pipe","pipe","pipe"]});try{return EU(`sudo -n sh -c '${X}'`,{encoding:"utf-8",stdio:["pipe","pipe","pipe"]})}catch{throw Error("sudo required but no cached credentials (set SUDO_PASSWORD in .env or run sudo -v)")}}function bX(Q,U,X){if(X)h0.debug(`[rpx:${Q}] ${U}`)}function w8(){let Q=process.env.RPX_REUSE_PORT;return Q==="1"||Q==="true"}var yX="[redacted]",p0=new Set(["certificate","privatekey","key","cert","ca","rootca","password","sudo_password"]),u0=/-----BEGIN [A-Z ]+-----[\s\S]*?-----END [A-Z ]+-----/;function m0(Q){let U=Q.toLowerCase();return p0.has(U)||U.endsWith("password")||U.includes("secret")||U.includes("token")}function OU(Q){if(Array.isArray(Q))return Q.map((X)=>OU(X));if(typeof Q==="string")return u0.test(Q)?yX:Q;if(!Q||typeof Q!=="object")return Q;let U={};for(let[X,Z]of Object.entries(Q)){if(m0(X)){U[X]=yX;continue}U[X]=OU(Z)}return U}function K8(Q,U){return JSON.stringify(OU(Q),null,U)}function j8(Q){if(hX(Q))return Q.proxies.map((U)=>{let X=U.to||"stacks.localhost";return X.startsWith("http")?new URL(X).hostname:X});if(dX(Q)){let U=Q.to||"stacks.localhost";return[U.startsWith("http")?new URL(U).hostname:U]}return["stacks.localhost"]}function F8(Q){return typeof Q==="object"&&Q!==null&&"certificate"in Q&&"privateKey"in Q&&typeof Q.certificate==="string"&&typeof Q.privateKey==="string"}function I8(Q){if(!Q)return"stacks.localhost";if(hX(Q)&&Q.proxies.length>0)return Q.proxies[0].to||"stacks.localhost";if(dX(Q))return Q.to||"stacks.localhost";return"stacks.localhost"}function M8(Q){return!!(Q&&("proxies"in Q)&&Array.isArray(Q.proxies))}function hX(Q){return"proxies"in Q&&Array.isArray(Q.proxies)}function dX(Q){return"to"in Q&&typeof Q.to==="string"}function V8(Q){return!!(Q&&("to"in Q)&&!("proxies"in Q))}function D8(Q,U){if(!U||U.length===0)return null;for(let X of U)if(Q===X.from||Q.startsWith(`${X.from}/`)){let Z=X.to.startsWith("http")?new URL(X.to).host:X.to,$=X.stripPrefix===!0?Q.slice(X.from.length)||"/":Q;return{targetHost:Z,targetPath:$}}return null}async function N8(Q,U){try{await vX.unlink(Q),bX("certificates",`Successfully deleted: ${Q}`,U)}catch(X){if(X.code!=="ENOENT")bX("certificates",`Warning: Could not delete ${Q}: ${X}`,U)}}
49
- export{r as ob,d0 as pb,f0 as qb,L8 as rb,bX as sb,w8 as tb,OU as ub,K8 as vb,j8 as wb,F8 as xb,I8 as yb,M8 as zb,hX as Ab,dX as Bb,V8 as Cb,D8 as Db,N8 as Eb};
49
+ export{r as yb,d0 as zb,f0 as Ab,L8 as Bb,bX as Cb,w8 as Db,OU as Eb,K8 as Fb,j8 as Gb,F8 as Hb,I8 as Ib,M8 as Jb,hX as Kb,dX as Lb,V8 as Mb,D8 as Nb,N8 as Ob};
package/dist/daemon.d.ts CHANGED
@@ -1,5 +1,7 @@
1
1
  import { OnDemandCertManager } from './on-demand';
2
2
  import type { OnDemandSitesConfig, OnDemandTlsConfig, ProductionTlsConfig, TlsOption } from './types';
3
+ import type { ProxyRoute } from './proxy-handler';
4
+ import type { RegistryEntry } from './registry';
3
5
  import type { SiteSnapshot } from './site-supervisor';
4
6
  export declare function getDaemonRpxDir(): string;
5
7
  export declare function getDaemonPidPath(rpxDir?: string): string;
@@ -21,6 +23,15 @@ export declare function isDaemonRunning(rpxDir?: string): Promise<boolean>;
21
23
  */
22
24
  export declare function acquireDaemonLock(rpxDir?: string): Promise<string>;
23
25
  export declare function releaseDaemonLock(rpxDir?: string): Promise<void>;
26
+ /**
27
+ * Translate a registry entry into the routing shape consumed by the proxy
28
+ * fetch handler. The entry's `from` is normalized to `host:port`, or (for a
29
+ * multi-upstream `from`) load-balanced via a per-entry-id cached pool.
30
+ *
31
+ * Exported for tests, which exercise the entry-id-keyed pool cache/reconcile
32
+ * logic directly rather than through a full daemon + live HTTP round trip.
33
+ */
34
+ export declare function entryToRoute(entry: RegistryEntry): ProxyRoute;
24
35
  /**
25
36
  * The shared `:80` handler: serve ACME http-01 challenges, kick off on-demand
26
37
  * issuance for an approved-but-uncovered host, then 301 to HTTPS. The request
package/dist/hosts.d.ts CHANGED
@@ -1,6 +1,76 @@
1
1
  /** `.localhost` names resolve to loopback per RFC 6761 — no /etc/hosts entry needed. */
2
2
  export declare function isLoopbackDevelopmentHost(host: string): boolean;
3
+ /**
4
+ * Parse one /etc/hosts line into address, hostnames, and rpx ownership.
5
+ * Returns null for blank lines and pure comments. Host matching anywhere in
6
+ * this module is exact token comparison — never substring — so removing
7
+ * `example.test` can never touch `api.example.test`.
8
+ */
9
+ export declare function parseHostsLine(line: string): HostsLine | null;
10
+ /** True when the line maps `host` exactly (one of its names) to loopback. */
11
+ export declare function hostsLineMapsHost(line: string, host: string): boolean;
12
+ /**
13
+ * Remove rpx-owned loopback lines for `hostsToRemove` from hosts-file content.
14
+ *
15
+ * Ownership rules:
16
+ * - Inline-marked lines (`# rpx:pid=N` / `# rpx`) are dropped when any of
17
+ * their exact hostnames is in the removal set.
18
+ * - Legacy `# Added by rpx` blocks own the address lines directly beneath
19
+ * them; matched lines are dropped and the comment is dropped only when
20
+ * its whole block went away (previously every legacy comment in the file
21
+ * was stripped even when its hosts stayed — orphaning unmarked lines).
22
+ * - Unmarked lines are never touched: a hand-written entry belongs to the
23
+ * user, and a missing marker must not become a deletion license.
24
+ */
25
+ export declare function filterRpxHostsEntries(content: string, hostsToRemove: string[]): HostsFilterResult;
26
+ /**
27
+ * Drop inline-marked lines whose owning PID is dead. Lines without a PID
28
+ * stamp (legacy blocks, hand-written entries) are kept: without an owner to
29
+ * probe there is no safe automatic removal signal.
30
+ */
31
+ export declare function dropStaleRpxHostsLines(content: string, isAlive: (pid: number) => boolean): StaleRpxHostsResult;
3
32
  export declare function addHosts(hosts: string[], verbose?: boolean): Promise<void>;
4
33
  export declare function removeHosts(hosts: string[], verbose?: boolean): Promise<void>;
5
34
  export declare function checkHosts(hosts: string[], verbose?: boolean): Promise<boolean[]>;
35
+ /**
36
+ * Hostnames on inline-marked rpx lines whose owning PID is dead. Read-only:
37
+ * safe for unprivileged diagnostics (e.g. `buddy doctor`) since /etc/hosts is
38
+ * world-readable. Returns [] when the file cannot be read.
39
+ */
40
+ export declare function findStaleRpxHosts(isAlive?: (pid: number) => boolean): Promise<string[]>;
41
+ /**
42
+ * Remove inline-marked rpx hosts lines whose owning PID is dead. Runs inside
43
+ * the (root) daemon's periodic GC so entries orphaned by a killed dev session
44
+ * disappear within seconds instead of hijacking the domain forever. Returns
45
+ * the removed hostnames; [] when nothing was stale or the write failed.
46
+ */
47
+ export declare function removeStaleRpxHosts(opts?: { verbose?: boolean, isAlive?: (pid: number) => boolean }): Promise<string[]>;
6
48
  export declare const hostsFilePath: string;
49
+ /**
50
+ * Inline marker identifying an /etc/hosts line rpx owns. New entries are
51
+ * stamped with the owning process id so a garbage-collection pass (the
52
+ * daemon's periodic sweep, or `removeStaleRpxHosts`) can reap lines whose
53
+ * owner died without cleaning up:
54
+ *
55
+ * 127.0.0.1 example.test # rpx:pid=12345
56
+ *
57
+ * Older entries used a `# Added by rpx` comment above each address pair;
58
+ * removal still understands that legacy layout.
59
+ */
60
+ export declare const RPX_HOSTS_MARKER: '# rpx';
61
+ export declare interface HostsLine {
62
+ address: string
63
+ names: string[]
64
+ comment: string
65
+ rpxPid: number | null
66
+ rpxManaged: boolean
67
+ }
68
+ export declare interface HostsFilterResult {
69
+ content: string
70
+ removed: string[]
71
+ }
72
+ export declare interface StaleRpxHostsResult {
73
+ content: string
74
+ removed: string[]
75
+ stalePids: number[]
76
+ }
package/dist/index.d.ts CHANGED
@@ -1,4 +1,5 @@
1
1
  import { startProxies as startProxiesFunc } from './start';
2
+ export type { HostsFilterResult, HostsLine, StaleRpxHostsResult } from './hosts';
2
3
  export type { RegistryEntry, WatchHandle, WatchOptions } from './registry';
3
4
  export type { DevelopmentDnsOptions } from './dns';
4
5
  export type {
@@ -40,7 +41,14 @@ export { config, config as defaultConfig } from './config';
40
41
  export {
41
42
  addHosts,
42
43
  checkHosts,
44
+ dropStaleRpxHostsLines,
45
+ filterRpxHostsEntries,
46
+ findStaleRpxHosts,
47
+ hostsLineMapsHost,
48
+ parseHostsLine,
43
49
  removeHosts,
50
+ removeStaleRpxHosts,
51
+ RPX_HOSTS_MARKER,
44
52
  } from './hosts';
45
53
  export {
46
54
  SHARED_DEV_HOST_CERT_PATH,
package/dist/index.js CHANGED
@@ -1,9 +1,2 @@
1
- import{$ as fR,$a as c_,A as OS,Aa as XR,B as P,Ba as JR,C as DR,Ca as J_,D as kR,Da as Q_,E as cS,Ea as V_,F as L,Fa as Z_,G as NR,Ga as I_,H as hS,Ha as W_,I as vS,Ia as z_,J as uS,Ja as M_,K as AR,Ka as x_,L as BR,La as j_,M as mS,Ma as T_,N as KR,Na as H_,O as bS,Oa as k_,P as wR,Pa as w_,Q as aS,Qa as E_,R as ER,Ra as F_,S as lS,Sa as q_,T as dS,Ta as C_,U as pS,Ua as f_,V as gS,W as nS,Wa as y_,X as FR,Xa as U_,Y as qR,Ya as P_,Z as iS,Za as L_,_ as CR,_a as O_,a as G,aa as WR,ab as h_,b as SR,ba as tS,bb as v_,c as JS,ca as yR,cb as u_,d as QS,da as rS,db as m_,e as VS,ea as UR,eb as b_,f as ZS,fa as sS,fb as a_,g as IS,ga as PR,gb as l_,h as WS,ha as eS,hb as d_,i as zS,ia as LR,ib as p_,j as MS,ja as oS,jb as g_,k as xS,ka as R_,kb as n_,l as jS,la as S_,lb as QR,m as TS,ma as __,mb as i_,n as HS,na as D_,nb as t_,o as kS,oa as N_,p as wS,pa as A_,q as ES,qa as B_,r as FS,ra as K_,s as qS,sa as Y_,t as CS,ta as f,u as fS,ua as $_,v as yS,va as OR,w as US,wa as G_,x as PS,xa as $R,y as LS,ya as GR,z as _R,za as X_}from"./chunk-6m0f5m3q.js";import{Ab as EN,Bb as FN,Cb as qN,Db as CN,Eb as fN,ob as TR,pb as u,qb as lR,rb as xN,sb as D,tb as gR,ub as jN,vb as q,wb as TN,xb as HN,yb as kN,zb as wN}from"./chunk-2yk7jygq.js";import{execSync as NS}from"node:child_process";import*as YR from"node:http";import*as nR from"node:net";import*as z from"node:process";var p=(S,R)=>(_)=>`\x1B[${S}m${_}\x1B[${R}m`,k={bold:p(1,22),dim:p(2,22),green:p(32,39),cyan:p(36,39)};import*as uR from"node:fs";import*as mR from"node:path";import*as E from"node:process";function bR(S,R){let N=(R&&R!=="/"?`${S}${R}`:S).replace(/[^a-zA-Z0-9._-]+/g,"-").replace(/^-+|-+$/g,"").slice(0,128);return N.length>0?N:"rpx"}async function g(S){if(S.proxies.length===0)throw Error("runViaDaemon: no proxies provided");let R=S.verbose??!1,_=S.registryDir,N=new Set,K=S.proxies.map((Q)=>{let I=Q.id??bR(Q.to,Q.path);if(!GR(I))throw Error(`invalid registry id "${I}" derived from to="${Q.to}"`);if(N.has(I))throw Error(`duplicate registry id "${I}" — set an explicit \`id\` on one of the proxies`);return N.add(I),{...Q,id:I}}),B=new Date().toISOString();for(let Q of K)await XR({id:Q.id,from:Q.from,to:Q.to,path:Q.path,pid:S.persistent?void 0:E.pid,cwd:E.cwd(),createdAt:B,cleanUrls:Q.cleanUrls,changeOrigin:Q.changeOrigin,pathRewrites:Q.pathRewrites,static:Q.static,loadBalancer:Q.loadBalancer},_,R);let A=await QR({rpxDir:S.rpxDir,verbose:R,spawnCommand:S.spawnCommand,startupTimeoutMs:S.startupTimeoutMs,spawnEnv:S.spawnEnv});for(let Q of K){let I=Q.static?`static ${typeof Q.static==="string"?Q.static:Q.static.dir}`:Q.from;G.success(`https://${Q.to} → ${I}`)}if(G.info(`(via rpx daemon pid=${A.pid}; \`rpx daemon:status\` to inspect)`),S.detached)return;let X=!1,Y=_??$R(),J=K.map((Q)=>Q.id),V=async()=>{if(X)return;X=!0;for(let Q of J)await JR(Q,_,R).catch((I)=>{D("runner",`removeEntry(${Q}) failed: ${I}`,R)})},Z=(Q)=>{D("runner",`received ${Q}, unregistering ${J.length} entries`,R),V().finally(()=>E.exit(0))};E.once("SIGINT",Z),E.once("SIGTERM",Z),E.once("exit",()=>{if(X)return;for(let Q of J)try{uR.unlinkSync(mR.join(Y,`${Q}.json`))}catch{}}),await new Promise(()=>{})}import{exec as sR}from"node:child_process";import C from"node:fs";import dR from"node:os";import ZR from"node:path";import*as m from"node:process";import{promisify as eR}from"node:util";var v=eR(sR);function aR(S){let R=S.trim().toLowerCase();return R==="localhost"||R.endsWith(".localhost")||R.endsWith(".localhost.")}var H=m.platform==="win32"?ZR.join(m.env.windir||"C:\\Windows","System32","drivers","etc","hosts"):"/etc/hosts",VR=!1;async function n(S){if(m.platform==="win32")throw Error("Administrator privileges required on Windows");if(lR()){let{stdout:N}=await v(S);return N}let R=u(),_=S.replace(/'/g,"'\\''");try{if(R){let{stdout:N}=await v(`echo '${R}' | sudo -S sh -c '${_}' 2>/dev/null`);return VR=!0,N}if(VR)try{let{stdout:N}=await v(`sudo -n sh -c '${_}'`);return N}catch(N){D("hosts","Cached sudo privileges expired, requesting again",!0)}try{let{stdout:N}=await v(`sudo -n sh -c '${_}'`);return VR=!0,N}catch{throw Error("sudo required but no cached credentials (set SUDO_PASSWORD in .env or run sudo -v)")}}catch(N){throw Error(`Failed to execute sudo command: ${N.message}`)}}async function y(S,R){let _=S.filter((K)=>!aR(K)),N=S.filter((K)=>aR(K));if(N.length>0)D("hosts",`Skipping /etc/hosts for loopback dev names: ${N.join(", ")}`,R);if(_.length===0)return;D("hosts",`Adding hosts: ${_.join(", ")}`,R),D("hosts",`Using hosts file at: ${H}`,R);try{let K;try{K=await C.promises.readFile(H,"utf-8")}catch{D("hosts","Reading hosts file requires elevated permissions, using sudo",R);try{K=await n(`cat "${H}"`)}catch(Y){throw console.log(" Could not read hosts file skipping hosts setup"),D("hosts",`sudo read also failed: ${Y}`,R),Error(`Cannot read hosts file: ${Y}`)}}let B=_.filter((Y)=>{let J=`127.0.0.1 ${Y}`,V=`::1 ${Y}`;return!K.includes(J)&&!K.includes(V)});if(B.length===0){D("hosts","All hosts already exist in hosts file",R);return}let A=B.map((Y)=>`
2
- # Added by rpx
3
- 127.0.0.1 ${Y}
4
- ::1 ${Y}`).join(`
5
- `),X=ZR.join(dR.tmpdir(),`rpx-hosts-${Date.now()}.tmp`);try{await C.promises.writeFile(X,K+A,"utf8"),await n(`cat "${X}" | tee "${H}" > /dev/null`),console.log(` Hosts updated: ${B.join(", ")}`)}catch(Y){console.log(" Could not update hosts file automatically"),console.log(" Add these entries to /etc/hosts:"),B.forEach((J)=>{console.log(` 127.0.0.1 ${J}`),console.log(` ::1 ${J}`)}),console.log(` Or run: sudo nano ${H}`)}finally{try{await C.promises.unlink(X)}catch{}}}catch(K){D("hosts",`Failed to manage hosts file: ${K.message}`,R)}}async function IR(S,R){D("hosts",`Removing hosts: ${S.join(", ")}`,R);try{let _;try{_=await C.promises.readFile(H,"utf-8")}catch{D("hosts","Reading hosts file requires elevated permissions, using sudo",R);try{_=await n(`cat "${H}"`)}catch(Y){throw D("hosts",`sudo read also failed: ${Y}`,R),Error(`Cannot read hosts file: ${Y}`)}}let N=_.split(`
6
- `),K=!1,B=N.filter((Y)=>{if(S.some((V)=>Y.includes(` ${V}`)&&(Y.includes("127.0.0.1")||Y.includes("::1"))))return K=!0,!1;if(Y.trim()==="# Added by rpx")return K=!0,!1;return!0});if(!K){D("hosts","No matching hosts found to remove",R);return}while(B[B.length-1]?.trim()==="")B.pop();let A=`${B.join(`
7
- `)}
8
- `,X=ZR.join(dR.tmpdir(),`rpx-hosts-${Date.now()}.tmp`);try{await C.promises.writeFile(X,A,"utf8"),await n(`cat "${X}" | tee "${H}" > /dev/null`),D("hosts","Hosts removed successfully",R)}catch(Y){D("hosts","Could not clean up hosts file automatically",R)}finally{try{await C.promises.unlink(X)}catch(Y){D("hosts",`Failed to remove temporary file: ${Y}`,R)}}}catch(_){D("hosts",`Failed to clean up hosts file: ${_.message}`,R)}}async function U(S,R){D("hosts",`Checking hosts: ${S}`,R);let _;try{_=await C.promises.readFile(H,"utf-8")}catch(N){D("hosts",`Error reading hosts file: ${N}`,R);try{let K=u(),B;if(K)B=`echo '${K}' | sudo -S cat "${H}" 2>/dev/null`;else B=`sudo -n cat "${H}" 2>/dev/null || cat "${H}" 2>/dev/null || echo ""`;let{stdout:A}=await v(B);_=A}catch(K){return D("hosts",`Cannot read hosts file, assuming entries don't exist: ${K}`,R),S.map(()=>!1)}}return S.map((N)=>{let K=`127.0.0.1 ${N}`,B=`::1 ${N}`;return _.includes(K)||_.includes(B)})}import{spawn as oR}from"node:child_process";import*as i from"node:process";class t{processes=new Map;isShuttingDown=!1;async startProcess(S,R,_){if(this.processes.has(S)){D("start",`Process ${S} is already running`,_);return}let[N,...K]=R.command.split(" "),B=R.cwd||i.cwd();D("start",`Starting process ${S}:`,_),D("start",` Command: ${N} ${K.join(" ")}`,_),D("start",` Working directory: ${B}`,_),D("start",` Environment variables: ${q(R.env)}`,_);let A=oR(N,K,{cwd:B,env:{...i.env,...R.env},shell:!0,stdio:"inherit"});return this.processes.set(S,{command:R.command,cwd:B,process:A,env:R.env}),new Promise((X,Y)=>{if(A.on("error",(J)=>{if(!this.isShuttingDown)D("start",`Process ${S} failed to start: ${J}`,_),this.processes.delete(S),Y(J)}),A.on("exit",(J)=>{if(!this.isShuttingDown&&J!==null&&J!==0)D("start",`Process ${S} exited with code ${J}; leaving the proxy running`,_),this.processes.delete(S),Y(Error(`Process ${S} exited with code ${J}`))}),_)A.stdout?.on("data",(J)=>{D("process",`[${S}] ${J.toString().trim()}`,!0)}),A.stderr?.on("data",(J)=>{D("process",`[${S}] ERR: ${J.toString().trim()}`,!0)});setTimeout(()=>{if(!this.isShuttingDown&&A.killed)this.processes.delete(S),Y(Error(`Process ${S} was killed during startup`));else D("start",`Process ${S} started successfully`,_),X()},1000)})}async stopProcess(S,R){let _=this.processes.get(S);if(!_?.process){D("start",`No process found for ${S}`,R);return}return D("start",`Stopping process ${S}`,R),new Promise((N)=>{if(!_.process){N();return}_.process.once("exit",()=>{this.processes.delete(S),D("start",`Process ${S} stopped`,R),N()});try{_.process.kill("SIGTERM"),setTimeout(()=>{if(_.process){D("start",`Force killing process ${S}`,R);try{_.process.kill("SIGKILL")}catch(K){}}},3000)}catch(K){D("start",`Error stopping process ${S}: ${K}`,R),this.processes.delete(S),N()}})}async stopAll(S){if(this.isShuttingDown){D("start","Already shutting down, skipping duplicate stopAll call",S);return}this.isShuttingDown=!0,D("start","Stopping all processes",S);let R=Array.from(this.processes.keys()).map((_)=>this.stopProcess(_,S).catch((N)=>{G.error(`Failed to stop process ${_}:`,N)}));await Promise.allSettled(R),this.processes.clear(),this.isShuttingDown=!1}isRunning(S){let R=this.processes.get(S);return!!R?.process&&!R.process.killed}}var JD=new t;import{timingSafeEqual as RS}from"node:crypto";function SS(S,R){if(S==null)return!1;let _=Buffer.from(S),N=Buffer.from(R);if(_.length!==N.length)return!1;return RS(_,N)}function r(S){return S.toLowerCase().replace(/\.$/,"")}var _S=["/.well-known/acme-challenge/"];function DS(S){let R=S.headers.get("host");if(R)return r(R.split(":")[0]);try{return r(new URL(S.url).hostname)}catch{return""}}function zR(S){let R=S.header.toLowerCase(),_=new Set,N=[];for(let Y of S.hosts){let J=r(Y);if(J.startsWith("*."))N.push(J);else _.add(J)}let K=S.exemptPaths??_S,B=S.forbiddenMessage??`Forbidden: direct origin access is not allowed; requests must arrive via the CDN.
9
- `,A=(Y)=>{let J=r(Y);return _.has(J)||N.some((V)=>WR(J,V))},X=(Y)=>{let J=DS(Y);if(!A(J))return;let V="/";try{V=new URL(Y.url).pathname}catch{}if(K.some((Z)=>V.startsWith(Z)))return;if(SS(Y.headers.get(R),S.value))return;return new Response(B,{status:403,headers:{"content-type":"text/plain"}})};return X.protects=A,X}var e=new t,AS="0.12.0",BS=new OR("0.0.0.0"),o=new Set,RR=new Set,MR=!1,s=null,xR=null;async function a(S){if(MR)return D("cleanup","Cleanup already in progress, skipping",S?.verbose),xR||Promise.resolve();MR=!0,D("cleanup","Starting cleanup process",S?.verbose),xR=new Promise((R)=>{s=R});try{await e.stopAll(S?.verbose),G.info("Shutting down proxy servers...");let R=[],_=Array.from(o).map((N)=>new Promise((K)=>{let B=N;try{if(typeof B.stop==="function")B.stop(!0),D("cleanup","Bun server stopped",S?.verbose),K();else if(typeof B.close==="function")B.close(()=>{D("cleanup","Server closed successfully",S?.verbose),K()});else K()}catch(A){D("cleanup",`Error stopping server: ${A}`,S?.verbose),K()}}));R.push(..._),o.clear();for(let N of RR)BR(N);if(RR.clear(),S?.hosts&&S.domains?.length){D("cleanup","Cleaning up hosts file entries",S?.verbose),D("cleanup",`Original domains for cleanup: ${JSON.stringify(S.domains)}`,S?.verbose);let N=S.domains.filter((K)=>{if(K==="test.local")return!0;return K!=="localhost"&&!K.startsWith("localhost.")&&K!=="127.0.0.1"});if(D("cleanup",`Filtered domains for cleanup: ${JSON.stringify(N)}`,S?.verbose),N.length>0)G.info("Cleaning up hosts file entries..."),R.push(IR(N,S?.verbose).then(()=>{D("cleanup",`Removed hosts entries for ${N.join(", ")}`,S?.verbose)}).catch((K)=>{D("cleanup",`Failed to remove hosts entries: ${K}`,S?.verbose),G.warn(`Failed to clean up hosts file entries for ${N.join(", ")}:`,K)}))}if(S?.certs&&S.domains?.length){D("cleanup","Cleaning up SSL certificates",S?.verbose),G.info("Cleaning up SSL certificates...");let N=S.domains.map(async(K)=>{try{await kR(K,S?.verbose),D("cleanup",`Removed certificates for ${K}`,S?.verbose)}catch(B){D("cleanup",`Failed to remove certificates for ${K}: ${B}`,S?.verbose),G.warn(`Failed to clean up certificates for ${K}:`,B)}});R.push(...N)}await Promise.allSettled(R),D("cleanup","All cleanup tasks completed successfully",S?.verbose),G.success("All cleanup tasks completed successfully")}catch(R){D("cleanup",`Error during cleanup: ${R}`,S?.verbose),G.error("Error during cleanup:",R)}finally{if(s)s();s=null,MR=!1;let R=S&&"vitePluginUsage"in S&&S.vitePluginUsage===!0;if(z.env.NODE_ENV!=="test"&&z.env.BUN_ENV!=="test"&&!R)z.exit(0)}return xR}var jR=!1;function iR(S){if(jR){D("signal",`Received second ${S} signal, forcing exit`,!0),z.exit(1);return}jR=!0,D("signal",`Received ${S} signal, initiating cleanup`,!0),a().catch((R)=>{D("signal",`Cleanup failed after ${S}: ${R}`,!0),z.exit(1)}).finally(()=>{jR=!1})}z.once("SIGINT",()=>iR("SIGINT"));z.once("SIGTERM",()=>iR("SIGTERM"));z.on("uncaughtException",(S)=>{G.error("Uncaught exception (continuing):",S)});z.on("unhandledRejection",(S)=>{G.error("Unhandled rejection (continuing):",S)});async function b(S,R,_,N=5){D("connection",`Testing connection to ${S}:${R} (retries left: ${N})`,_);let K=15000,B=Date.now();if(z.env.RPX_BYPASS_CONNECTION_TEST==="true"){D("connection",`Bypassing connection test for ${S}:${R} due to RPX_BYPASS_CONNECTION_TEST flag`,_);return}let A=()=>new Promise((X,Y)=>{let J=nR.connect({host:S,port:R,timeout:3000});J.once("connect",()=>{D("connection",`Successfully connected to ${S}:${R}`,_),J.end(),X()}),J.once("timeout",()=>{D("connection",`Connection to ${S}:${R} timed out`,_),J.destroy(),Y(Error("Connection timed out"))}),J.once("error",(V)=>{D("connection",`Failed to connect to ${S}:${R}: ${V}`,_),J.destroy(),Y(V)})});try{await A()}catch(X){let Y=X;if(Date.now()-B>K){D("connection",`Connection test timed out after ${K}ms, but continuing anyway`,_),G.warn(`Connection test to ${S}:${R} timed out, but RPX will try to proceed anyway.`);return}if(Y.code==="ECONNREFUSED"&&N>0)return D("connection",`Connection refused, server might be starting up. Retrying in 2 seconds... (${N} retries left)`,_),await new Promise((V)=>setTimeout(V,2000)),b(S,R,_,N-1);if(N>0)try{D("connection",`Trying HTTP request to ${S}:${R}`,_),await new Promise((V,Z)=>{let Q=YR.request({hostname:S,port:R,path:"/",method:"HEAD",timeout:5000},(I)=>{D("connection",`Received HTTP response with status: ${I.statusCode}`,_),V()});Q.on("error",(I)=>Z(I)),Q.on("timeout",()=>{Q.destroy(),Z(Error("HTTP request timed out"))}),Q.end()}),D("connection",`HTTP request to ${S}:${R} succeeded`,_);return}catch(V){return D("connection",`HTTP request to ${S}:${R} failed: ${V}`,_),D("connection",`Retrying socket connection in 2 seconds... (${N} retries left)`,_),await new Promise((Z)=>setTimeout(Z,2000)),b(S,R,_,N-1)}let J=`Failed to connect to ${S}:${R} after ${5-N} attempts: ${Y.message}`;D("connection",`${J}. To bypass this check set RPX_BYPASS_CONNECTION_TEST=true`,_),G.warn(J),G.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 cR(S){D("server",`Starting server with options: ${q(S)}`,S.verbose);let R=L(S.from),_=new URL(R.startsWith("http")?R:`http://${R}`),N=new URL((S.to?.startsWith("http")?S.to:`http://${S.to}`)||"rpx.localhost"),K=Number.parseInt(_.port)||(_.protocol.includes("https:")?443:80),B=[N.hostname];if(hR(S)&&!N.hostname.includes("localhost")&&!N.hostname.includes("127.0.0.1")){D("hosts",`Checking if hosts file entry exists for: ${N.hostname}`,S?.verbose);try{if(!(await U(B,S.verbose))[0]){G.info(`Adding ${N.hostname} to hosts file...`),G.info("This may require sudo/administrator privileges");try{await y(B,S.verbose)}catch(Y){if(G.error("Failed to add hosts entry:",Y.message),G.warn("You can manually add this entry to your hosts file:"),G.warn(`127.0.0.1 ${N.hostname}`),G.warn(`::1 ${N.hostname}`),z.platform==="win32")G.warn("On Windows:"),G.warn("1. Run notepad as administrator"),G.warn("2. Open C:\\Windows\\System32\\drivers\\etc\\hosts");else G.warn("On Unix systems:"),G.warn("sudo nano /etc/hosts")}}else D("hosts",`Host entry already exists for ${N.hostname}`,S.verbose)}catch(X){G.error("Failed to check hosts file:",X.message)}}try{await b(_.hostname,K,S.verbose)}catch(X){D("server",`Connection test failed: ${X}`,S.verbose),G.error(X.message),G.warn("Continuing with proxy setup despite connection test failure..."),G.info("If you need to bypass connection testing, set environment variable RPX_BYPASS_CONNECTION_TEST=true")}let A=S._cachedSSLConfig||null;if(S.https)try{if(S.https===!0)S.https=DR({...S,to:N.hostname});if(A=await P({...S,to:N.hostname,https:S.https}),!A){if(D("ssl",`Generating new certificates for ${N.hostname}`,S.verbose),await _R({...S,from:_.toString(),to:N.hostname,https:S.https}),A=await P({...S,to:N.hostname,https:S.https}),!A)throw Error(`Failed to load SSL configuration after generating certificates for ${N.hostname}`)}}catch(X){throw D("server",`SSL setup failed: ${X}`,S.verbose),X}D("server",`Setting up reverse proxy with SSL config for ${N.hostname}`,S.verbose),await YS({...S,from:R,originalFrom:S.from||R,to:N.hostname,fromPort:K,sourceUrl:{hostname:_.hostname,host:_.host},ssl:A})}async function KS(S,R,_,N,K,B,A,X,Y,J,V,Z){D("proxy",`Creating proxy server ${S} -> ${R} with cleanUrls: ${X}`,A);let Q=NR(V??N.host,Z);AR(Q);let I=[{host:R,route:{sourceHost:N.host,upstreamPool:Q,cleanUrls:X||!1,changeOrigin:Y||!1,basePath:"/",auth:J}}];if(!HR({routeEntries:I,listenPort:_,sslConfig:K,originGuard:null,verbose:A??!1}))throw BR(Q),Error(`Failed to start proxy server for ${R} on port ${_}`);RR.add(Q),XS({from:S,to:R,vitePluginUsage:B,listenPort:_,ssl:!!K,cleanUrls:X,verbose:A})}async function YS(S){D("setup",`Setting up reverse proxy: ${q(S)}`,S.verbose);let{from:R,originalFrom:_,to:N,sourceUrl:K,ssl:B,verbose:A,cleanup:X,vitePluginUsage:Y,changeOrigin:J,cleanUrls:V}=S,Z=80,Q=443,I="0.0.0.0",F=S.portManager||BS,O=hR(S);try{if(O&&N&&!N.includes("localhost")&&!N.includes("127.0.0.1")){if(!(await U([N],A))[0]){G.warn(`The hostname ${N} isn't in your hosts file. Adding it now...`);try{await y([N],A),G.success(`Added ${N} to your hosts file.`)}catch(c){G.error(`Failed to add ${N} to your hosts file: ${c}`),G.info(`You may need to manually add '127.0.0.1 ${N}' to your /etc/hosts file.`)}}}else if(O&&z.platform!=="darwin"&&N&&N.includes("localhost")&&!N.match(/^(localhost|127\.0\.0\.1)$/)){if(!(await U([N],A))[0]){D("hosts",`${N} not found in hosts file, adding...`,A);try{await y([N],A)}catch(c){D("hosts",`Failed to add ${N} to hosts file: ${c}`,A)}}}if(B&&!F.usedPorts.has(Z)){if(!await f(Z,I,A))D("setup","Starting HTTP redirect server",A),tR(A),F.usedPorts.add(Z);else if(D("setup","Port 80 is in use, skipping HTTP redirect",A),A)G.warn("Port 80 is in use, HTTP to HTTPS redirect will not be available")}let M=B?Q:Z,w=await f(M,I,A),j;if(w){if(D("setup",`Port ${M} is already in use`,A),A)G.warn(`Port ${M} is already in use. This may be another instance of rpx or another service.`);if(M===443){if(j=await F.getNextAvailablePort(3443,!0),D("setup",`Using port ${j} instead of ${M}`,A),A)G.info(`Using port ${j} instead. Access your site at https://${N}:${j}`)}else if(j=await F.getNextAvailablePort(M+1000,!0),D("setup",`Using port ${j} instead of ${M}`,A),A)G.info(`Using port ${j} instead. Access your site at http://${N}:${j}`)}else j=M,F.usedPorts.add(j),D("setup",`Using standard ${M===443?"HTTPS":"HTTP"} port ${M} for ${N}`,A);await KS(R,N,j,K,B,Y,A,V,J,KR(S.auth),_,S.loadBalancer)}catch(M){D("setup",`Setup failed: ${M}`,A),G.error(`Failed to setup reverse proxy: ${M.message}`),a({domains:[N],hosts:typeof X==="boolean"?X:X?.hosts,certs:typeof X==="boolean"?X:X?.certs,verbose:A,vitePluginUsage:Y})}}function tR(S,R=80,_=443,N){D("redirect",`Starting HTTP redirect server on port ${R}`,S);let K=YR.createServer((B,A)=>{if(N&&B.url){let V=B.url.split("?",1)[0],Z=CR(N,V);if(Z!=null){D("redirect",`Serving ACME challenge ${V}`,S),A.writeHead(200,{"content-type":"text/plain"}),A.end(Z);return}}let X=B.headers.host||"",Y=X.includes(":")?X.slice(0,X.indexOf(":")):X,J=_===443?Y:`${Y}:${_}`;D("redirect",`Redirecting request from ${X}${B.url} to https://${J}`,S),A.writeHead(301,{Location:`https://${J}${B.url}`}),A.end()}).listen(R);o.add(K),D("redirect","HTTP redirect server started",S)}function $S(S){let R={...SR,...S};if(D("proxy",`Starting proxy with options: ${q(R)}`,R?.verbose),R.viaDaemon){if(!R.from||!R.to){G.error("viaDaemon mode requires both `from` and `to`");return}g({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((Y)=>{G.error(`Failed to register with rpx daemon: ${Y.message}`),z.exit(1)});return}let _=R.to||"",N=_.split(".").pop()?.toLowerCase()||"",K=z.platform==="darwin"&&_&&!_.includes("localhost")&&!_.includes("127.0.0.1"),B=["dev","app","page","new","day","foo"],A=["test","localhost","local","example","invalid"];if(K&&B.includes(N)&&R?.verbose)G.warn(`The .${N} TLD may not work reliably for local development`),G.info(` Google owns .${N} with HSTS preloading, which can bypass local DNS`),G.info(" Consider using a reserved TLD: .test, .localhost, or .local");if(K)import("./chunk-r1k4wgag.js").then(({setupDevelopmentDns:Y})=>{Y({domains:[_],verbose:R.verbose}).then((J)=>{if(J)Promise.resolve().then(()=>{if(R.verbose)if(A.includes(N))G.success(`DNS server started for .${N} domains`);else G.success(`DNS server started for .${N} domains (hosts file entry also added)`)});else D("dns",`Could not start DNS server - ${_} may not resolve in browser`,R.verbose)})}).catch((Y)=>{D("dns",`Failed to start DNS server: ${Y}`,R.verbose)});let X={from:R.from,to:R.to,cleanUrls:R.cleanUrls,https:DR(R),cleanup:R.cleanup,vitePluginUsage:R.vitePluginUsage,changeOrigin:R.changeOrigin,verbose:R.verbose,regenerateUntrustedCerts:R.regenerateUntrustedCerts};D("proxy",`Server options: ${q(X)}`,R.verbose),cR(X).catch((Y)=>{D("proxy",`Failed to start proxy: ${Y}`,R.verbose),G.error(`Failed to start proxy: ${Y.message}`),a({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 GS(S){return S?.verbose||!1}function hR(S){if(S?.hostsManagement===!1)return!1;let R=S?.cleanup;if(R===!1)return!1;if(R&&typeof R==="object"&&R.hosts===!1)return!1;return!0}async function vR(S){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(S)R={...R,...S};let _=GS(R),N=hR(R);if(D("config",`Starting with config: ${q(R,2)}`,_),D("config",`Is multi-proxy? ${"proxies"in R}`,_),D("config",`Hosts management enabled? ${N}`,_),R.viaDaemon){let W="proxies"in R&&Array.isArray(R.proxies)?R.proxies.map((x)=>({id:x.id,from:x.from,to:x.to,path:x.path,cleanUrls:x.cleanUrls??R.cleanUrls,changeOrigin:x.changeOrigin??R.changeOrigin,pathRewrites:x.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 g({proxies:W,verbose:_});return}if("proxies"in R&&Array.isArray(R.proxies)){D("servers",`Found ${R.proxies.length} proxies in config`,_);for(let $ of R.proxies)if($.start){let W=`${$.from}-${$.to}`;try{D("watch",`Starting command for ${W} with command: ${$.start.command}`,_),G.info(`Starting command for ${W}...`),await e.startProcess(W,$.start,_);let x=L($.from),T=new URL(x.startsWith("http")?x:`http://${x}`),h=T.hostname||"localhost",d=Number(T.port)||80;try{await b(h,d,_),D("watch",`Dev server is ready at ${h}:${d}`,_)}catch(rR){D("watch",`Connection check failed, but continuing with proxy setup: ${rR}`,_),G.warn("Dev server connection check failed. RPX will try to proceed anyway...")}}catch(x){throw D("watch",`Failed to start command for ${W}: ${x}`,_),Error(`Failed to start command for ${W}: ${x}`)}}else D("watch",`No start command for proxy ${$.from} -> ${$.to}`,_)}else if("start"in R&&R.start){D("watch","Found start command in single proxy config",_);let $=`${R.from}-${R.to}`;try{if(R.start)D("watch",`Starting command: ${R.start.command}`,_),await e.startProcess($,R.start,_);let W=L(R.from),x=new URL(W.startsWith("http")?W:`http://${W}`),T=x.hostname||"localhost",h=Number(x.port)||80;try{await b(T,h,_),D("watch",`Dev server is ready at ${T}:${h}`,_)}catch(d){D("watch",`Connection check failed, but continuing with proxy setup: ${d}`,_),G.warn("Dev server connection check failed. RPX will try to proceed anyway...")}}catch(W){throw D("watch",`Failed to run start command: ${W}`,_),Error(`Failed to run start command: ${W}`)}}else D("watch","No start command found in config",_);let K="proxies"in R&&Array.isArray(R.proxies)?R.proxies[0]?.to:("to"in R)?R.to:"rpx.localhost";if(z.platform!=="win32"&&(R.https||N)){if(!u())try{D("sudo","Pre-acquiring sudo credentials for privileged operations",_),NS("sudo -v",{stdio:"inherit"})}catch{D("sudo","Could not pre-acquire sudo credentials",_)}}let B=[];if(R.productionCerts){if(B=await LR(R.productionCerts,_),B.length>0)D("ssl",`Using ${B.length} production SNI cert(s): ${B.map(($)=>$.serverName).join(", ")}`,_)}if(R.https){let $=B.length>0?null:await P(R);if(!$&&B.length===0){if(D("ssl",`No valid or trusted certificates found for ${K}, generating new ones`,R.verbose),await _R(R),$=await P(R),!$)throw Error(`Failed to load SSL certificates after generation for ${K}`)}else D("ssl",`Using existing and trusted certificates for ${K}`,R.verbose);R._cachedSSLConfig=$}let A="proxies"in R&&Array.isArray(R.proxies)?R.proxies.map(($)=>({...$,https:R.https,cleanup:R.cleanup,cleanUrls:$.cleanUrls??("cleanUrls"in R?R.cleanUrls:!1),vitePluginUsage:R.vitePluginUsage,changeOrigin:$.changeOrigin??R.changeOrigin,verbose:_,_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:_,_cachedSSLConfig:R._cachedSSLConfig}],X=A.map(($)=>$.to||"rpx.localhost"),Y=B.length>0?B:R._cachedSSLConfig??null,J=X.filter(($)=>$&&!$.includes("localhost")&&!$.includes("127.0.0.1")),V=["dev","app","page","new","day","foo"],Z=["test","localhost","local","example","invalid"],Q=[...new Set(J.map(($)=>$.split(".").pop()?.toLowerCase()))],I=Q.filter(($)=>!!$&&V.includes($));if(I.length>0&&_)G.warn(`The following TLDs may not work reliably for local development: ${I.map(($)=>`.${$}`).join(", ")}`),G.info(" These TLDs have HSTS preloading which can bypass local DNS"),G.info(" Consider using reserved TLDs: .test, .localhost, or .local");if(N&&z.platform==="darwin"&&J.length>0){let{setupDevelopmentDns:$}=await import("./chunk-r1k4wgag.js");if(await $({domains:J,verbose:_})){if(_)if(Q.every((T)=>!!T&&Z.includes(T)))G.success(`DNS server started for ${Q.map((T)=>`.${T}`).join(", ")} domains`);else G.success(`DNS server started for ${Q.map((T)=>`.${T}`).join(", ")} domains (hosts file entries also added)`)}else D("dns","Could not start DNS server - custom domains may not resolve",_)}let F=async()=>{D("cleanup","Starting cleanup handler",R.verbose);try{let{tearDownDevelopmentDns:$}=await import("./chunk-r1k4wgag.js");await $({verbose:R.verbose})}catch($){D("cleanup",`Error stopping DNS server: ${$}`,R.verbose)}try{await e.stopAll(R.verbose)}catch($){D("cleanup",`Error stopping processes: ${$}`,R.verbose)}await a({domains:X,hosts:typeof R.cleanup==="boolean"?R.cleanup:R.cleanup?.hosts,certs:typeof R.cleanup==="boolean"?R.cleanup:R.cleanup?.certs,verbose:R.verbose||!1})};z.on("SIGINT",F),z.on("SIGTERM",F);let O=R.singlePortMode===!0,M=R.httpsPort??443,w=R.httpPort??80,j=R.originGuard?zR(R.originGuard):null,l=!!Y&&(A.length>1||O||B.length>0),c=!Y&&O&&A.length>0;if(l&&Y){D("proxies",`Creating shared HTTPS server for ${A.length} domains on port ${M}`,_);let $=await pR(A,N,_);if(!await f(w,"0.0.0.0",_))tR(_,w,M,R.acmeChallengeWebroot);if(await f(M,"0.0.0.0",_)){if(D("proxies",`Port ${M} is already in use, cannot start shared proxy`,_),_)G.warn(`Port ${M} is in use. Shared HTTPS proxy cannot start.`);return}if(!HR({routeEntries:$,listenPort:M,sslConfig:Y,originGuard:j,verbose:_})){G.error(`Shared HTTPS proxy failed to bind :${M}; not exiting`);return}}else if(c){D("proxies",`Creating shared HTTP server for ${A.length} domains on port ${w}`,_);let $=await pR(A,N,_);if(await f(w,"0.0.0.0",_)){if(D("proxies",`Port ${w} is already in use, cannot start shared proxy`,_),_)G.warn(`Port ${w} is in use. Shared HTTP proxy cannot start.`);return}if(!HR({routeEntries:$,listenPort:w,sslConfig:null,originGuard:j,verbose:_})){G.error(`Shared HTTP proxy failed to bind :${w}; not exiting`);return}}else for(let $ of A)try{let W=$.to||"rpx.localhost";D("proxy",`Starting proxy for ${W} with SSL config: ${!!Y}`,$.verbose),await cR({from:$.from||"localhost:5173",to:W,cleanUrls:$.cleanUrls||!1,https:$.https||!1,cleanup:$.cleanup||!1,vitePluginUsage:$.vitePluginUsage||!1,verbose:$.verbose||!1,_cachedSSLConfig:R._cachedSSLConfig,changeOrigin:$.changeOrigin||!1,loadBalancer:$.loadBalancer,auth:$.auth,path:$.path,pathRewrites:$.pathRewrites})}catch(W){D("proxies",`Failed to start proxy for ${$.to}: ${W}`,$.verbose),G.error(`Failed to start proxy for ${$.to}:`,W)}}async function pR(S,R,_){let N=[],K=new Set;for(let B of S){let A=B.to||"rpx.localhost",X=B.cleanUrls||!1,Y=B.path,J=yR(Y),V=KR(B.auth);if(B.redirect){let Z=wR(B.redirect);N.push({host:A,path:Y,route:{redirect:Z,basePath:J,auth:V}}),D("proxies",`Route: ${A}${Y??""} → redirect ${Z.status} ${Z.to}${V?" (auth)":""}`,_)}else if(B.static)N.push({host:A,path:Y,route:{static:ER(B.static,X),cleanUrls:X,basePath:J,auth:V}}),D("proxies",`Route: ${A}${Y??""} → static ${typeof B.static==="string"?B.static:B.static.dir}${V?" (auth)":""}`,_);else{let Z=L(B.from),Q=new URL(Z.startsWith("http")?Z:`http://${Z}`),I=NR(B.from??Q.host,B.loadBalancer);AR(I),RR.add(I),N.push({host:A,path:Y,route:{sourceHost:Q.host,upstreamPool:I,cleanUrls:X,changeOrigin:B.changeOrigin||!1,pathRewrites:B.pathRewrites,basePath:J,auth:V}}),D("proxies",`Route: ${A}${Y??""} → ${Q.host}${V?" (auth)":""}`,_)}if(K.has(A))continue;if(K.add(A),R&&!fR(A)&&!A.includes("localhost")&&!A.includes("127.0.0.1"))try{if(!(await U([A],_))[0])await y([A],_)}catch{D("hosts",`Could not add hosts entry for ${A}`,_)}}return N}function HR(S){let{routeEntries:R,listenPort:_,sslConfig:N,originGuard:K,verbose:B}=S,A=UR(R),X=FR((V,Z)=>PR(A,V,Z),B),Y=K?(V,Z)=>K(V)??X(V,Z):X,J=qR(B);try{let V=Bun.serve({port:_,hostname:"0.0.0.0",reusePort:gR(),...N?{tls:Array.isArray(N)?N.map((Z)=>({serverName:Z.serverName,key:Z.key,cert:Z.cert})):{key:N.key,cert:N.cert,ca:N.ca,requestCert:!1,rejectUnauthorized:!1}}:{},fetch(Z,Q){return Y(Z,Q)},websocket:J,error(Z){return D("server",`Shared proxy server error: ${Z}`,B),new Response(`Server Error: ${Z.message}`,{status:500})}});return o.add(V),D("proxies",`Shared ${N?"HTTPS":"HTTP"} proxy listening on port ${_} for ${A.size} domains`,B),V}catch(V){return D("proxies",`Failed to start shared proxy: ${V}`,B),console.error("Failed to start shared proxy:",V),null}}function XS(S){if(S?.vitePluginUsage||!S?.verbose)return;if(console.log(""),console.log(` ${k.green(k.bold("rpx"))} ${k.green(`v${AS}`)}`),console.log(` ${k.green("➜")} ${k.dim(S?.from??"")} ${k.dim("➜")} ${k.cyan(S?.ssl?`https://${S?.to}`:`http://${S?.to}`)}`),S?.listenPort!==(S?.ssl?443:80))console.log(` ${k.green("➜")} Listening on port ${S?.listenPort}`);if(S?.cleanUrls)console.log(` ${k.green("➜")} Clean URLs enabled`)}var MN=vR;export{XR as writeEntry,Z_ as watchRegistry,IS as verifyHttpsChain,FS as trustRootCaForBrowsers,h_ as tearDownDevelopmentDns,c_ as syncDevelopmentDnsFromRegistry,nS as stripBasePath,BR as stopHealthChecks,C_ as stopDnsServer,i_ as stopDaemon,cR as startServer,$S as startProxy,vR as startProxies,AR as startHealthChecks,q_ as startDnsServer,D_ as siteIdForHost,gR as shouldReusePort,P_ as setupResolver,O_ as setupDevelopmentDns,eS as serverNameFromCertFilename,gS as serveStaticFile,hS as selectUpstream,q as safeStringify,dS as safeRelativePath,fN as safeDeleteFile,g as runViaDaemon,g_ as runDaemon,y_ as resolverFilePath,k_ as resolverBasenamesForDomains,H_ as resolverBasenameForDomain,ER as resolveStaticRoute,pS as resolveStaticFile,wR as resolveRedirect,CN as resolvePathRewrite,KR as resolveAuth,z_ as renderStartingPage,M_ as renderFailedPage,v_ as removeResolver,L_ as removeLegacyTldResolvers,IR as removeHosts,JR as removeEntry,p_ as releaseDaemonLock,jN as redactSensitive,u_ as reconcileStaleDevelopmentDns,t_ as reconcileDevelopmentDnsOnIdle,A_ as readSiteManifest,J_ as readEntry,a_ as readDaemonPid,QS as readCertSha256Fingerprint,VS as readCertCommonName,Q_ as readAll,CR as readAcmeChallenge,kS as pruneStaleRootCas,N_ as projectNameFromHost,L as primaryUpstreamUrl,G_ as portManager,rS as pathPrefixMatches,WS as parseSha256HashesFromSecurityListing,mS as parseHtpasswd,JS as normalizeSha256Fingerprint,yR as normalizePathPrefix,T_ as normalizeDevDomain,WR as matchesWildcard,oS as matchesAllowedSuffix,PR as matchHostRoute,sS as matchHostList,tS as matchHost,vS as markSuccess,uS as markFailure,PS as loadSSLConfig,Y_ as listDiscoverableSites,HS as listCertSha256HashesByCommonName,fR as isWildcardPattern,HN as isValidRootCA,GR as isValidId,FN as isSingleProxyOptions,qN as isSingleProxyConfig,wS as isRootCaTrustedForSsl,ES as isRootCaFingerprintInKeychains,lR as isProcessElevated,f as isPortInUse,X_ as isPidAlive,EN as isMultiProxyOptions,wN as isMultiProxyConfig,R_ as isLikelyHostname,f_ as isDnsServerRunning,l_ as isDaemonRunning,cS as isCertTrusted,DR as httpsConfig,u as getSudoPassword,US as getSharedDaemonCertPaths,yS as getRootCAPaths,$R as getRegistryDir,kN as getPrimaryDomain,TS as getMacosTrustKeychains,jS as getMacosLoginKeychainPath,m_ as getDaemonRpxDir,b_ as getDaemonPidPath,_R as generateCertificate,V_ as gcStaleEntries,LS as forceTrustCertificate,$_ as findAvailablePort,TN as extractHostname,__ as expandHome,xN as execSudoSync,W_ as escapeHtml,QR as ensureDaemonRunning,bS as enforceBasicAuth,CS as devSslToSniEntries,w_ as devDomainsFromHosts,B_ as detectProjectPreset,bR as deriveIdFromTarget,n_ as defaultDaemonSpawnCommand,SR as defaultConfig,MN as default,D as debugLog,NR as createUpstreamPool,K_ as createSiteResolver,qR as createProxyWebSocketHandler,FR as createProxyFetchHandler,zR as createOriginGuard,lS as contentTypeFor,U_ as contentLooksLikeRpxResolver,SR as config,k as colors,OS as clearSslConfigCache,kR as cleanupCertificates,a as cleanup,U as checkHosts,P as checkExistingCertificates,ZS as certIncludesSanHostnames,LR as buildSniTlsConfig,fS as buildRegistryTlsProxyOptions,aS as buildRedirectLocation,UR as buildHostRoutes,y as addHosts,d_ as acquireDaemonLock,I_ as SiteSupervisor,qS as SHARED_DEV_HOST_CERT_PATH,xS as RPX_ROOT_CA_COMMON_NAME,F_ as RPX_RESOLVER_MARKER,S_ as OnDemandCertManager,MS as MACOS_SYSTEM_KEYCHAIN,zS as MACOS_CA_TRUST_FLAGS,j_ as LEGACY_TLD_RESOLVER_LABELS,OR as DefaultPortManager,x_ as DNS_STATE_VERSION,E_ as DNS_PORT,iS as ACME_CHALLENGE_PREFIX};
1
+ import{$ as HR,$a as M_,A as WS,Aa as xR,B as q,Ba as NR,C as r,Ca as R_,D as VR,Da as S_,E as jS,Ea as __,F as C,Fa as A_,G as e,Ga as D_,H as wS,Ha as x_,I as TS,Ia as N_,J as kS,Ja as B_,K as o,Ka as K_,L as RR,La as X_,M as fS,Ma as Y_,N as SR,Na as $_,O as qS,Oa as G_,P as ZR,Pa as y,Q as CS,Qa as kR,R as zR,Ra as U,S as yS,Sa as J_,T as US,Ta as Q_,U as LS,Ua as V_,V as PS,Va as Z_,W as OS,Wa as z_,X as FR,Xa as F_,Y as IR,Ya as I_,Z as hS,Za as E_,_ as ER,_a as H_,a as K,aa as KR,ab as W_,b as t,ba as cS,bb as j_,c as RS,ca as MR,cb as w_,d as SS,da as uS,e as _S,ea as WR,eb as T_,f as AS,fa as vS,fb as k_,g as DS,ga as jR,gb as f_,h as xS,ha as mS,hb as q_,i as NS,ia as wR,ib as C_,j as BS,ja as bS,jb as y_,k as KS,ka as lS,kb as U_,l as XS,la as aS,lb as L_,m as YS,ma as dS,mb as P_,n as $S,na as pS,nb as O_,o as GS,oa as gS,ob as h_,p as JS,pa as iS,pb as c_,q as QS,qa as nS,qb as u_,r as VS,ra as tS,rb as v_,s as ZS,sa as sS,sb as m_,t as zS,ta as f,tb as b_,u as FS,ua as rS,ub as l_,v as IS,va as TR,vb as BR,w as ES,wa as eS,wb as a_,x as HS,xa as AR,xb as d_,y as MS,ya as DR,z as s,za as oS}from"./chunk-9dh9jhc6.js";import{Ab as KD,Bb as XD,Cb as A,Db as hR,Eb as YD,Fb as k,Gb as $D,Hb as GD,Ib as JD,Jb as QD,Kb as VD,Lb as ZD,Mb as zD,Nb as FD,Ob as ID,yb as JR,zb as OR}from"./chunk-z86swbd8.js";import{execSync as gR}from"node:child_process";import*as _R from"node:http";import*as cR from"node:net";import*as F from"node:process";var m=(S,R)=>(_)=>`\x1B[${S}m${_}\x1B[${R}m`,W={bold:m(1,22),dim:m(2,22),green:m(32,39),cyan:m(36,39)};import*as yR from"node:fs";import*as UR from"node:path";import*as w from"node:process";function LR(S,R){let D=(R&&R!=="/"?`${S}${R}`:S).replace(/[^a-zA-Z0-9._-]+/g,"-").replace(/^-+|-+$/g,"").slice(0,128);return D.length>0?D:"rpx"}async function b(S){if(S.proxies.length===0)throw Error("runViaDaemon: no proxies provided");let R=S.verbose??!1,_=S.registryDir,D=new Set,X=S.proxies.map(($)=>{let Z=$.id??LR($.to,$.path);if(!DR(Z))throw Error(`invalid registry id "${Z}" derived from to="${$.to}"`);if(D.has(Z))throw Error(`duplicate registry id "${Z}" — set an explicit \`id\` on one of the proxies`);return D.add(Z),{...$,id:Z}}),N=new Date().toISOString();for(let $ of X)await xR({id:$.id,from:$.from,to:$.to,path:$.path,pid:S.persistent?void 0:w.pid,cwd:w.cwd(),createdAt:N,cleanUrls:$.cleanUrls,changeOrigin:$.changeOrigin,pathRewrites:$.pathRewrites,static:$.static,loadBalancer:$.loadBalancer},_,R);let x=await BR({rpxDir:S.rpxDir,verbose:R,spawnCommand:S.spawnCommand,startupTimeoutMs:S.startupTimeoutMs,spawnEnv:S.spawnEnv});for(let $ of X){let Z=$.static?`static ${typeof $.static==="string"?$.static:$.static.dir}`:$.from;K.success(`https://${$.to} → ${Z}`)}if(K.info(`(via rpx daemon pid=${x.pid}; \`rpx daemon:status\` to inspect)`),S.detached)return;let J=!1,Y=_??AR(),G=X.map(($)=>$.id),V=async()=>{if(J)return;J=!0;for(let $ of G)await NR($,_,R).catch((Z)=>{A("runner",`removeEntry(${$}) failed: ${Z}`,R)})},Q=($)=>{A("runner",`received ${$}, unregistering ${G.length} entries`,R),V().finally(()=>w.exit(0))};w.once("SIGINT",Q),w.once("SIGTERM",Q),w.once("exit",()=>{if(J)return;for(let $ of G)try{yR.unlinkSync(UR.join(Y,`${$}.json`))}catch{}}),await new Promise(()=>{})}import{spawn as bR}from"node:child_process";import*as l from"node:process";class a{processes=new Map;isShuttingDown=!1;async startProcess(S,R,_){if(this.processes.has(S)){A("start",`Process ${S} is already running`,_);return}let[D,...X]=R.command.split(" "),N=R.cwd||l.cwd();A("start",`Starting process ${S}:`,_),A("start",` Command: ${D} ${X.join(" ")}`,_),A("start",` Working directory: ${N}`,_),A("start",` Environment variables: ${k(R.env)}`,_);let x=bR(D,X,{cwd:N,env:{...l.env,...R.env},shell:!0,stdio:"inherit"});return this.processes.set(S,{command:R.command,cwd:N,process:x,env:R.env}),new Promise((J,Y)=>{if(x.on("error",(G)=>{if(!this.isShuttingDown)A("start",`Process ${S} failed to start: ${G}`,_),this.processes.delete(S),Y(G)}),x.on("exit",(G)=>{if(!this.isShuttingDown&&G!==null&&G!==0)A("start",`Process ${S} exited with code ${G}; leaving the proxy running`,_),this.processes.delete(S),Y(Error(`Process ${S} exited with code ${G}`))}),_)x.stdout?.on("data",(G)=>{A("process",`[${S}] ${G.toString().trim()}`,!0)}),x.stderr?.on("data",(G)=>{A("process",`[${S}] ERR: ${G.toString().trim()}`,!0)});setTimeout(()=>{if(!this.isShuttingDown&&x.killed)this.processes.delete(S),Y(Error(`Process ${S} was killed during startup`));else A("start",`Process ${S} started successfully`,_),J()},1000)})}async stopProcess(S,R){let _=this.processes.get(S);if(!_?.process){A("start",`No process found for ${S}`,R);return}return A("start",`Stopping process ${S}`,R),new Promise((D)=>{if(!_.process){D();return}_.process.once("exit",()=>{this.processes.delete(S),A("start",`Process ${S} stopped`,R),D()});try{_.process.kill("SIGTERM"),setTimeout(()=>{if(_.process){A("start",`Force killing process ${S}`,R);try{_.process.kill("SIGKILL")}catch(X){}}},3000)}catch(X){A("start",`Error stopping process ${S}: ${X}`,R),this.processes.delete(S),D()}})}async stopAll(S){if(this.isShuttingDown){A("start","Already shutting down, skipping duplicate stopAll call",S);return}this.isShuttingDown=!0,A("start","Stopping all processes",S);let R=Array.from(this.processes.keys()).map((_)=>this.stopProcess(_,S).catch((D)=>{K.error(`Failed to stop process ${_}:`,D)}));await Promise.allSettled(R),this.processes.clear(),this.isShuttingDown=!1}isRunning(S){let R=this.processes.get(S);return!!R?.process&&!R.process.killed}}var RA=new a;import{timingSafeEqual as lR}from"node:crypto";function aR(S,R){if(S==null)return!1;let _=Buffer.from(S),D=Buffer.from(R);if(_.length!==D.length)return!1;return lR(_,D)}function d(S){return S.toLowerCase().replace(/\.$/,"")}var dR=["/.well-known/acme-challenge/"];function pR(S){let R=S.headers.get("host");if(R)return d(R.split(":")[0]);try{return d(new URL(S.url).hostname)}catch{return""}}function XR(S){let R=S.header.toLowerCase(),_=new Set,D=[];for(let Y of S.hosts){let G=d(Y);if(G.startsWith("*."))D.push(G);else _.add(G)}let X=S.exemptPaths??dR,N=S.forbiddenMessage??`Forbidden: direct origin access is not allowed; requests must arrive via the CDN.
2
+ `,x=(Y)=>{let G=d(Y);return _.has(G)||D.some((V)=>KR(G,V))},J=(Y)=>{let G=pR(Y);if(!x(G))return;let V="/";try{V=new URL(Y.url).pathname}catch{}if(X.some((Q)=>V.startsWith(Q)))return;if(aR(Y.headers.get(R),S.value))return;return new Response(N,{status:403,headers:{"content-type":"text/plain"}})};return J.protects=x,J}var g=new a,iR="0.12.0",nR=new TR("0.0.0.0"),i=new Set,n=new Set,YR=!1,p=null,$R=null;async function c(S){if(YR)return A("cleanup","Cleanup already in progress, skipping",S?.verbose),$R||Promise.resolve();YR=!0,A("cleanup","Starting cleanup process",S?.verbose),$R=new Promise((R)=>{p=R});try{await g.stopAll(S?.verbose),K.info("Shutting down proxy servers...");let R=[],_=Array.from(i).map((D)=>new Promise((X)=>{let N=D;try{if(typeof N.stop==="function")N.stop(!0),A("cleanup","Bun server stopped",S?.verbose),X();else if(typeof N.close==="function")N.close(()=>{A("cleanup","Server closed successfully",S?.verbose),X()});else X()}catch(x){A("cleanup",`Error stopping server: ${x}`,S?.verbose),X()}}));R.push(..._),i.clear();for(let D of n)RR(D);if(n.clear(),S?.hosts&&S.domains?.length){A("cleanup","Cleaning up hosts file entries",S?.verbose),A("cleanup",`Original domains for cleanup: ${JSON.stringify(S.domains)}`,S?.verbose);let D=S.domains.filter((X)=>{if(X==="test.local")return!0;return X!=="localhost"&&!X.startsWith("localhost.")&&X!=="127.0.0.1"});if(A("cleanup",`Filtered domains for cleanup: ${JSON.stringify(D)}`,S?.verbose),D.length>0)K.info("Cleaning up hosts file entries..."),R.push(kR(D,S?.verbose).then(()=>{A("cleanup",`Removed hosts entries for ${D.join(", ")}`,S?.verbose)}).catch((X)=>{A("cleanup",`Failed to remove hosts entries: ${X}`,S?.verbose),K.warn(`Failed to clean up hosts file entries for ${D.join(", ")}:`,X)}))}if(S?.certs&&S.domains?.length){A("cleanup","Cleaning up SSL certificates",S?.verbose),K.info("Cleaning up SSL certificates...");let D=S.domains.map(async(X)=>{try{await VR(X,S?.verbose),A("cleanup",`Removed certificates for ${X}`,S?.verbose)}catch(N){A("cleanup",`Failed to remove certificates for ${X}: ${N}`,S?.verbose),K.warn(`Failed to clean up certificates for ${X}:`,N)}});R.push(...D)}await Promise.allSettled(R),A("cleanup","All cleanup tasks completed successfully",S?.verbose),K.success("All cleanup tasks completed successfully")}catch(R){A("cleanup",`Error during cleanup: ${R}`,S?.verbose),K.error("Error during cleanup:",R)}finally{if(p)p();p=null,YR=!1;let R=S&&"vitePluginUsage"in S&&S.vitePluginUsage===!0;if(F.env.NODE_ENV!=="test"&&F.env.BUN_ENV!=="test"&&!R)F.exit(0)}return $R}var GR=!1;function uR(S){if(GR){A("signal",`Received second ${S} signal, forcing exit`,!0),F.exit(1);return}GR=!0,A("signal",`Received ${S} signal, initiating cleanup`,!0),c().catch((R)=>{A("signal",`Cleanup failed after ${S}: ${R}`,!0),F.exit(1)}).finally(()=>{GR=!1})}F.once("SIGINT",()=>uR("SIGINT"));F.once("SIGTERM",()=>uR("SIGTERM"));F.on("uncaughtException",(S)=>{K.error("Uncaught exception (continuing):",S)});F.on("unhandledRejection",(S)=>{K.error("Unhandled rejection (continuing):",S)});async function h(S,R,_,D=5){A("connection",`Testing connection to ${S}:${R} (retries left: ${D})`,_);let X=15000,N=Date.now();if(F.env.RPX_BYPASS_CONNECTION_TEST==="true"){A("connection",`Bypassing connection test for ${S}:${R} due to RPX_BYPASS_CONNECTION_TEST flag`,_);return}let x=()=>new Promise((J,Y)=>{let G=cR.connect({host:S,port:R,timeout:3000});G.once("connect",()=>{A("connection",`Successfully connected to ${S}:${R}`,_),G.end(),J()}),G.once("timeout",()=>{A("connection",`Connection to ${S}:${R} timed out`,_),G.destroy(),Y(Error("Connection timed out"))}),G.once("error",(V)=>{A("connection",`Failed to connect to ${S}:${R}: ${V}`,_),G.destroy(),Y(V)})});try{await x()}catch(J){let Y=J;if(Date.now()-N>X){A("connection",`Connection test timed out after ${X}ms, but continuing anyway`,_),K.warn(`Connection test to ${S}:${R} timed out, but RPX will try to proceed anyway.`);return}if(Y.code==="ECONNREFUSED"&&D>0)return A("connection",`Connection refused, server might be starting up. Retrying in 2 seconds... (${D} retries left)`,_),await new Promise((V)=>setTimeout(V,2000)),h(S,R,_,D-1);if(D>0)try{A("connection",`Trying HTTP request to ${S}:${R}`,_),await new Promise((V,Q)=>{let $=_R.request({hostname:S,port:R,path:"/",method:"HEAD",timeout:5000},(Z)=>{A("connection",`Received HTTP response with status: ${Z.statusCode}`,_),V()});$.on("error",(Z)=>Q(Z)),$.on("timeout",()=>{$.destroy(),Q(Error("HTTP request timed out"))}),$.end()}),A("connection",`HTTP request to ${S}:${R} succeeded`,_);return}catch(V){return A("connection",`HTTP request to ${S}:${R} failed: ${V}`,_),A("connection",`Retrying socket connection in 2 seconds... (${D} retries left)`,_),await new Promise((Q)=>setTimeout(Q,2000)),h(S,R,_,D-1)}let G=`Failed to connect to ${S}:${R} after ${5-D} attempts: ${Y.message}`;A("connection",`${G}. To bypass this check set RPX_BYPASS_CONNECTION_TEST=true`,_),K.warn(G),K.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 fR(S){A("server",`Starting server with options: ${k(S)}`,S.verbose);let R=C(S.from),_=new URL(R.startsWith("http")?R:`http://${R}`),D=new URL((S.to?.startsWith("http")?S.to:`http://${S.to}`)||"rpx.localhost"),X=Number.parseInt(_.port)||(_.protocol.includes("https:")?443:80),N=[D.hostname];if(qR(S)&&!D.hostname.includes("localhost")&&!D.hostname.includes("127.0.0.1")){A("hosts",`Checking if hosts file entry exists for: ${D.hostname}`,S?.verbose);try{if(!(await U(N,S.verbose))[0]){K.info(`Adding ${D.hostname} to hosts file...`),K.info("This may require sudo/administrator privileges");try{await y(N,S.verbose)}catch(Y){if(K.error("Failed to add hosts entry:",Y.message),K.warn("You can manually add this entry to your hosts file:"),K.warn(`127.0.0.1 ${D.hostname}`),K.warn(`::1 ${D.hostname}`),F.platform==="win32")K.warn("On Windows:"),K.warn("1. Run notepad as administrator"),K.warn("2. Open C:\\Windows\\System32\\drivers\\etc\\hosts");else K.warn("On Unix systems:"),K.warn("sudo nano /etc/hosts")}}else A("hosts",`Host entry already exists for ${D.hostname}`,S.verbose)}catch(J){K.error("Failed to check hosts file:",J.message)}}try{await h(_.hostname,X,S.verbose)}catch(J){A("server",`Connection test failed: ${J}`,S.verbose),K.error(J.message),K.warn("Continuing with proxy setup despite connection test failure..."),K.info("If you need to bypass connection testing, set environment variable RPX_BYPASS_CONNECTION_TEST=true")}let x=S._cachedSSLConfig||null;if(S.https)try{if(S.https===!0)S.https=r({...S,to:D.hostname});if(x=await q({...S,to:D.hostname,https:S.https}),!x){if(A("ssl",`Generating new certificates for ${D.hostname}`,S.verbose),await s({...S,from:_.toString(),to:D.hostname,https:S.https}),x=await q({...S,to:D.hostname,https:S.https}),!x)throw Error(`Failed to load SSL configuration after generating certificates for ${D.hostname}`)}}catch(J){throw A("server",`SSL setup failed: ${J}`,S.verbose),J}A("server",`Setting up reverse proxy with SSL config for ${D.hostname}`,S.verbose),await sR({...S,from:R,originalFrom:S.from||R,to:D.hostname,fromPort:X,sourceUrl:{hostname:_.hostname,host:_.host},ssl:x})}async function tR(S,R,_,D,X,N,x,J,Y,G,V,Q){A("proxy",`Creating proxy server ${S} -> ${R} with cleanUrls: ${J}`,x);let $=e(V??D.host,Q);o($);let Z=[{host:R,route:{sourceHost:D.host,upstreamPool:$,cleanUrls:J||!1,changeOrigin:Y||!1,basePath:"/",auth:G}}];if(!QR({routeEntries:Z,listenPort:_,sslConfig:X,originGuard:null,verbose:x??!1}))throw RR($),Error(`Failed to start proxy server for ${R} on port ${_}`);n.add($),oR({from:S,to:R,vitePluginUsage:N,listenPort:_,ssl:!!X,cleanUrls:J,verbose:x})}async function sR(S){A("setup",`Setting up reverse proxy: ${k(S)}`,S.verbose);let{from:R,originalFrom:_,to:D,sourceUrl:X,ssl:N,verbose:x,cleanup:J,vitePluginUsage:Y,changeOrigin:G,cleanUrls:V}=S,Q=80,$=443,Z="0.0.0.0",T=S.portManager||nR,L=qR(S);try{if(L&&D&&!D.includes("localhost")&&!D.includes("127.0.0.1")){if(!(await U([D],x))[0]){K.warn(`The hostname ${D} isn't in your hosts file. Adding it now...`);try{await y([D],x),K.success(`Added ${D} to your hosts file.`)}catch(P){K.error(`Failed to add ${D} to your hosts file: ${P}`),K.info(`You may need to manually add '127.0.0.1 ${D}' to your /etc/hosts file.`)}}}else if(L&&F.platform!=="darwin"&&D&&D.includes("localhost")&&!D.match(/^(localhost|127\.0\.0\.1)$/)){if(!(await U([D],x))[0]){A("hosts",`${D} not found in hosts file, adding...`,x);try{await y([D],x)}catch(P){A("hosts",`Failed to add ${D} to hosts file: ${P}`,x)}}}if(N&&!T.usedPorts.has(Q)){if(!await f(Q,Z,x))A("setup","Starting HTTP redirect server",x),vR(x),T.usedPorts.add(Q);else if(A("setup","Port 80 is in use, skipping HTTP redirect",x),x)K.warn("Port 80 is in use, HTTP to HTTPS redirect will not be available")}let I=N?$:Q,j=await f(I,Z,x),H;if(j){if(A("setup",`Port ${I} is already in use`,x),x)K.warn(`Port ${I} is already in use. This may be another instance of rpx or another service.`);if(I===443){if(H=await T.getNextAvailablePort(3443,!0),A("setup",`Using port ${H} instead of ${I}`,x),x)K.info(`Using port ${H} instead. Access your site at https://${D}:${H}`)}else if(H=await T.getNextAvailablePort(I+1000,!0),A("setup",`Using port ${H} instead of ${I}`,x),x)K.info(`Using port ${H} instead. Access your site at http://${D}:${H}`)}else H=I,T.usedPorts.add(H),A("setup",`Using standard ${I===443?"HTTPS":"HTTP"} port ${I} for ${D}`,x);await tR(R,D,H,X,N,Y,x,V,G,SR(S.auth),_,S.loadBalancer)}catch(I){A("setup",`Setup failed: ${I}`,x),K.error(`Failed to setup reverse proxy: ${I.message}`),c({domains:[D],hosts:typeof J==="boolean"?J:J?.hosts,certs:typeof J==="boolean"?J:J?.certs,verbose:x,vitePluginUsage:Y})}}function vR(S,R=80,_=443,D){A("redirect",`Starting HTTP redirect server on port ${R}`,S);let X=_R.createServer((N,x)=>{if(D&&N.url){let V=N.url.split("?",1)[0],Q=ER(D,V);if(Q!=null){A("redirect",`Serving ACME challenge ${V}`,S),x.writeHead(200,{"content-type":"text/plain"}),x.end(Q);return}}let J=N.headers.host||"",Y=J.includes(":")?J.slice(0,J.indexOf(":")):J,G=_===443?Y:`${Y}:${_}`;A("redirect",`Redirecting request from ${J}${N.url} to https://${G}`,S),x.writeHead(301,{Location:`https://${G}${N.url}`}),x.end()}).listen(R);i.add(X),A("redirect","HTTP redirect server started",S)}function rR(S){let R={...t,...S};if(A("proxy",`Starting proxy with options: ${k(R)}`,R?.verbose),R.viaDaemon){if(!R.from||!R.to){K.error("viaDaemon mode requires both `from` and `to`");return}b({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((Y)=>{K.error(`Failed to register with rpx daemon: ${Y.message}`),F.exit(1)});return}let _=R.to||"",D=_.split(".").pop()?.toLowerCase()||"",X=F.platform==="darwin"&&_&&!_.includes("localhost")&&!_.includes("127.0.0.1"),N=["dev","app","page","new","day","foo"],x=["test","localhost","local","example","invalid"];if(X&&N.includes(D)&&R?.verbose)K.warn(`The .${D} TLD may not work reliably for local development`),K.info(` Google owns .${D} with HSTS preloading, which can bypass local DNS`),K.info(" Consider using a reserved TLD: .test, .localhost, or .local");if(X)import("./chunk-ws2cmbxh.js").then(({setupDevelopmentDns:Y})=>{Y({domains:[_],verbose:R.verbose}).then((G)=>{if(G)Promise.resolve().then(()=>{if(R.verbose)if(x.includes(D))K.success(`DNS server started for .${D} domains`);else K.success(`DNS server started for .${D} domains (hosts file entry also added)`)});else A("dns",`Could not start DNS server - ${_} may not resolve in browser`,R.verbose)})}).catch((Y)=>{A("dns",`Failed to start DNS server: ${Y}`,R.verbose)});let J={from:R.from,to:R.to,cleanUrls:R.cleanUrls,https:r(R),cleanup:R.cleanup,vitePluginUsage:R.vitePluginUsage,changeOrigin:R.changeOrigin,verbose:R.verbose,regenerateUntrustedCerts:R.regenerateUntrustedCerts};A("proxy",`Server options: ${k(J)}`,R.verbose),fR(J).catch((Y)=>{A("proxy",`Failed to start proxy: ${Y}`,R.verbose),K.error(`Failed to start proxy: ${Y.message}`),c({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 eR(S){return S?.verbose||!1}function qR(S){if(S?.hostsManagement===!1)return!1;let R=S?.cleanup;if(R===!1)return!1;if(R&&typeof R==="object"&&R.hosts===!1)return!1;return!0}async function CR(S){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(S)R={...R,...S};let _=eR(R),D=qR(R);if(A("config",`Starting with config: ${k(R,2)}`,_),A("config",`Is multi-proxy? ${"proxies"in R}`,_),A("config",`Hosts management enabled? ${D}`,_),R.viaDaemon){let z="proxies"in R&&Array.isArray(R.proxies)?R.proxies.map((E)=>({id:E.id,from:E.from,to:E.to,path:E.path,cleanUrls:E.cleanUrls??R.cleanUrls,changeOrigin:E.changeOrigin??R.changeOrigin,pathRewrites:E.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 b({proxies:z,verbose:_});return}if("proxies"in R&&Array.isArray(R.proxies)){A("servers",`Found ${R.proxies.length} proxies in config`,_);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}`,_),K.info(`Starting command for ${z}...`),await g.startProcess(z,B.start,_);let E=C(B.from),M=new URL(E.startsWith("http")?E:`http://${E}`),O=M.hostname||"localhost",v=Number(M.port)||80;try{await h(O,v,_),A("watch",`Dev server is ready at ${O}:${v}`,_)}catch(mR){A("watch",`Connection check failed, but continuing with proxy setup: ${mR}`,_),K.warn("Dev server connection check failed. RPX will try to proceed anyway...")}}catch(E){throw A("watch",`Failed to start command for ${z}: ${E}`,_),Error(`Failed to start command for ${z}: ${E}`)}}else A("watch",`No start command for proxy ${B.from} -> ${B.to}`,_)}else if("start"in R&&R.start){A("watch","Found start command in single proxy config",_);let B=`${R.from}-${R.to}`;try{if(R.start)A("watch",`Starting command: ${R.start.command}`,_),await g.startProcess(B,R.start,_);let z=C(R.from),E=new URL(z.startsWith("http")?z:`http://${z}`),M=E.hostname||"localhost",O=Number(E.port)||80;try{await h(M,O,_),A("watch",`Dev server is ready at ${M}:${O}`,_)}catch(v){A("watch",`Connection check failed, but continuing with proxy setup: ${v}`,_),K.warn("Dev server connection check failed. RPX will try to proceed anyway...")}}catch(z){throw A("watch",`Failed to run start command: ${z}`,_),Error(`Failed to run start command: ${z}`)}}else A("watch","No start command found in config",_);let X="proxies"in R&&Array.isArray(R.proxies)?R.proxies[0]?.to:("to"in R)?R.to:"rpx.localhost";if(F.platform!=="win32"&&(R.https||D)){if(!OR())try{A("sudo","Pre-acquiring sudo credentials for privileged operations",_),gR("sudo -v",{stdio:"inherit"})}catch{A("sudo","Could not pre-acquire sudo credentials",_)}}let N=[];if(R.productionCerts){if(N=await wR(R.productionCerts,_),N.length>0)A("ssl",`Using ${N.length} production SNI cert(s): ${N.map((B)=>B.serverName).join(", ")}`,_)}if(R.https){let B=N.length>0?null:await q(R);if(!B&&N.length===0){if(A("ssl",`No valid or trusted certificates found for ${X}, generating new ones`,R.verbose),await s(R),B=await q(R),!B)throw Error(`Failed to load SSL certificates after generation for ${X}`)}else A("ssl",`Using existing and trusted certificates for ${X}`,R.verbose);R._cachedSSLConfig=B}let x="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:_,_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:_,_cachedSSLConfig:R._cachedSSLConfig}],J=x.map((B)=>B.to||"rpx.localhost"),Y=N.length>0?N:R._cachedSSLConfig??null,G=J.filter((B)=>B&&!B.includes("localhost")&&!B.includes("127.0.0.1")),V=["dev","app","page","new","day","foo"],Q=["test","localhost","local","example","invalid"],$=[...new Set(G.map((B)=>B.split(".").pop()?.toLowerCase()))],Z=$.filter((B)=>!!B&&V.includes(B));if(Z.length>0&&_)K.warn(`The following TLDs may not work reliably for local development: ${Z.map((B)=>`.${B}`).join(", ")}`),K.info(" These TLDs have HSTS preloading which can bypass local DNS"),K.info(" Consider using reserved TLDs: .test, .localhost, or .local");if(D&&F.platform==="darwin"&&G.length>0){let{setupDevelopmentDns:B}=await import("./chunk-ws2cmbxh.js");if(await B({domains:G,verbose:_})){if(_)if($.every((M)=>!!M&&Q.includes(M)))K.success(`DNS server started for ${$.map((M)=>`.${M}`).join(", ")} domains`);else K.success(`DNS server started for ${$.map((M)=>`.${M}`).join(", ")} domains (hosts file entries also added)`)}else A("dns","Could not start DNS server - custom domains may not resolve",_)}let T=async()=>{A("cleanup","Starting cleanup handler",R.verbose);try{let{tearDownDevelopmentDns:B}=await import("./chunk-ws2cmbxh.js");await B({verbose:R.verbose})}catch(B){A("cleanup",`Error stopping DNS server: ${B}`,R.verbose)}try{await g.stopAll(R.verbose)}catch(B){A("cleanup",`Error stopping processes: ${B}`,R.verbose)}await c({domains:J,hosts:typeof R.cleanup==="boolean"?R.cleanup:R.cleanup?.hosts,certs:typeof R.cleanup==="boolean"?R.cleanup:R.cleanup?.certs,verbose:R.verbose||!1})};F.on("SIGINT",T),F.on("SIGTERM",T);let L=R.singlePortMode===!0,I=R.httpsPort??443,j=R.httpPort??80,H=R.originGuard?XR(R.originGuard):null,u=!!Y&&(x.length>1||L||N.length>0),P=!Y&&L&&x.length>0;if(u&&Y){A("proxies",`Creating shared HTTPS server for ${x.length} domains on port ${I}`,_);let B=await PR(x,D,_);if(!await f(j,"0.0.0.0",_))vR(_,j,I,R.acmeChallengeWebroot);if(await f(I,"0.0.0.0",_)){if(A("proxies",`Port ${I} is already in use, cannot start shared proxy`,_),_)K.warn(`Port ${I} is in use. Shared HTTPS proxy cannot start.`);return}if(!QR({routeEntries:B,listenPort:I,sslConfig:Y,originGuard:H,verbose:_})){K.error(`Shared HTTPS proxy failed to bind :${I}; not exiting`);return}}else if(P){A("proxies",`Creating shared HTTP server for ${x.length} domains on port ${j}`,_);let B=await PR(x,D,_);if(await f(j,"0.0.0.0",_)){if(A("proxies",`Port ${j} is already in use, cannot start shared proxy`,_),_)K.warn(`Port ${j} is in use. Shared HTTP proxy cannot start.`);return}if(!QR({routeEntries:B,listenPort:j,sslConfig:null,originGuard:H,verbose:_})){K.error(`Shared HTTP proxy failed to bind :${j}; not exiting`);return}}else for(let B of x)try{let z=B.to||"rpx.localhost";A("proxy",`Starting proxy for ${z} with SSL config: ${!!Y}`,B.verbose),await fR({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),K.error(`Failed to start proxy for ${B.to}:`,z)}}async function PR(S,R,_){let D=[],X=new Set;for(let N of S){let x=N.to||"rpx.localhost",J=N.cleanUrls||!1,Y=N.path,G=MR(Y),V=SR(N.auth);if(N.redirect){let Q=ZR(N.redirect);D.push({host:x,path:Y,route:{redirect:Q,basePath:G,auth:V}}),A("proxies",`Route: ${x}${Y??""} → redirect ${Q.status} ${Q.to}${V?" (auth)":""}`,_)}else if(N.static)D.push({host:x,path:Y,route:{static:zR(N.static,J),cleanUrls:J,basePath:G,auth:V}}),A("proxies",`Route: ${x}${Y??""} → static ${typeof N.static==="string"?N.static:N.static.dir}${V?" (auth)":""}`,_);else{let Q=C(N.from),$=new URL(Q.startsWith("http")?Q:`http://${Q}`),Z=e(N.from??$.host,N.loadBalancer);o(Z),n.add(Z),D.push({host:x,path:Y,route:{sourceHost:$.host,upstreamPool:Z,cleanUrls:J,changeOrigin:N.changeOrigin||!1,pathRewrites:N.pathRewrites,basePath:G,auth:V}}),A("proxies",`Route: ${x}${Y??""} → ${$.host}${V?" (auth)":""}`,_)}if(X.has(x))continue;if(X.add(x),R&&!HR(x)&&!x.includes("localhost")&&!x.includes("127.0.0.1"))try{if(!(await U([x],_))[0])await y([x],_)}catch{A("hosts",`Could not add hosts entry for ${x}`,_)}}return D}function QR(S){let{routeEntries:R,listenPort:_,sslConfig:D,originGuard:X,verbose:N}=S,x=WR(R),J=FR((V,Q)=>jR(x,V,Q),N),Y=X?(V,Q)=>X(V)??J(V,Q):J,G=IR(N);try{let V=Bun.serve({port:_,hostname:"0.0.0.0",reusePort:hR(),...D?{tls:Array.isArray(D)?D.map((Q)=>({serverName:Q.serverName,key:Q.key,cert:Q.cert})):{key:D.key,cert:D.cert,ca:D.ca,requestCert:!1,rejectUnauthorized:!1}}:{},fetch(Q,$){return Y(Q,$)},websocket:G,error(Q){return A("server",`Shared proxy server error: ${Q}`,N),new Response(`Server Error: ${Q.message}`,{status:500})}});return i.add(V),A("proxies",`Shared ${D?"HTTPS":"HTTP"} proxy listening on port ${_} for ${x.size} domains`,N),V}catch(V){return A("proxies",`Failed to start shared proxy: ${V}`,N),console.error("Failed to start shared proxy:",V),null}}function oR(S){if(S?.vitePluginUsage||!S?.verbose)return;if(console.log(""),console.log(` ${W.green(W.bold("rpx"))} ${W.green(`v${iR}`)}`),console.log(` ${W.green("➜")} ${W.dim(S?.from??"")} ${W.dim("➜")} ${W.cyan(S?.ssl?`https://${S?.to}`:`http://${S?.to}`)}`),S?.listenPort!==(S?.ssl?443:80))console.log(` ${W.green("➜")} Listening on port ${S?.listenPort}`);if(S?.cleanUrls)console.log(` ${W.green("➜")} Clean URLs enabled`)}var BD=CR;export{xR as writeEntry,A_ as watchRegistry,DS as verifyHttpsChain,VS as trustRootCaForBrowsers,U_ as tearDownDevelopmentDns,y_ as syncDevelopmentDnsFromRegistry,OS as stripBasePath,RR as stopHealthChecks,j_ as stopDnsServer,a_ as stopDaemon,fR as startServer,rR as startProxy,CR as startProxies,o as startHealthChecks,W_ as startDnsServer,pS as siteIdForHost,hR as shouldReusePort,f_ as setupResolver,C_ as setupDevelopmentDns,mS as serverNameFromCertFilename,PS as serveStaticFile,wS as selectUpstream,k as safeStringify,US as safeRelativePath,ID as safeDeleteFile,b as runViaDaemon,b_ as runDaemon,T_ as resolverFilePath,I_ as resolverBasenamesForDomains,F_ as resolverBasenameForDomain,zR as resolveStaticRoute,LS as resolveStaticFile,ZR as resolveRedirect,FD as resolvePathRewrite,SR as resolveAuth,N_ as renderStartingPage,B_ as renderFailedPage,Q_ as removeStaleRpxHosts,L_ as removeResolver,q_ as removeLegacyTldResolvers,kR as removeHosts,NR as removeEntry,m_ as releaseDaemonLock,YD as redactSensitive,P_ as reconcileStaleDevelopmentDns,d_ as reconcileDevelopmentDnsOnIdle,iS as readSiteManifest,R_ as readEntry,c_ as readDaemonPid,SS as readCertSha256Fingerprint,_S as readCertCommonName,S_ as readAll,ER as readAcmeChallenge,GS as pruneStaleRootCas,gS as projectNameFromHost,C as primaryUpstreamUrl,eS as portManager,uS as pathPrefixMatches,xS as parseSha256HashesFromSecurityListing,fS as parseHtpasswd,X_ as parseHostsLine,RS as normalizeSha256Fingerprint,MR as normalizePathPrefix,z_ as normalizeDevDomain,KR as matchesWildcard,bS as matchesAllowedSuffix,jR as matchHostRoute,vS as matchHostList,cS as matchHost,TS as markSuccess,kS as markFailure,HS as loadSSLConfig,sS as listDiscoverableSites,$S as listCertSha256HashesByCommonName,HR as isWildcardPattern,GD as isValidRootCA,DR as isValidId,ZD as isSingleProxyOptions,zD as isSingleProxyConfig,JS as isRootCaTrustedForSsl,QS as isRootCaFingerprintInKeychains,KD as isProcessElevated,f as isPortInUse,oS as isPidAlive,VD as isMultiProxyOptions,QD as isMultiProxyConfig,lS as isLikelyHostname,w_ as isDnsServerRunning,u_ as isDaemonRunning,jS as isCertTrusted,r as httpsConfig,Y_ as hostsLineMapsHost,OR as getSudoPassword,ES as getSharedDaemonCertPaths,IS as getRootCAPaths,AR as getRegistryDir,JD as getPrimaryDomain,YS as getMacosTrustKeychains,XS as getMacosLoginKeychainPath,O_ as getDaemonRpxDir,h_ as getDaemonPidPath,s as generateCertificate,__ as gcStaleEntries,MS as forceTrustCertificate,J_ as findStaleRpxHosts,rS as findAvailablePort,$_ as filterRpxHostsEntries,$D as extractHostname,dS as expandHome,XD as execSudoSync,x_ as escapeHtml,BR as ensureDaemonRunning,qS as enforceBasicAuth,G_ as dropStaleRpxHostsLines,zS as devSslToSniEntries,E_ as devDomainsFromHosts,nS as detectProjectPreset,LR as deriveIdFromTarget,l_ as defaultDaemonSpawnCommand,t as defaultConfig,BD as default,A as debugLog,e as createUpstreamPool,tS as createSiteResolver,IR as createProxyWebSocketHandler,FR as createProxyFetchHandler,XR as createOriginGuard,yS as contentTypeFor,k_ as contentLooksLikeRpxResolver,t as config,W as colors,WS as clearSslConfigCache,VR as cleanupCertificates,c as cleanup,U as checkHosts,q as checkExistingCertificates,AS as certIncludesSanHostnames,wR as buildSniTlsConfig,FS as buildRegistryTlsProxyOptions,CS as buildRedirectLocation,WR as buildHostRoutes,y as addHosts,v_ as acquireDaemonLock,D_ as SiteSupervisor,ZS as SHARED_DEV_HOST_CERT_PATH,KS as RPX_ROOT_CA_COMMON_NAME,M_ as RPX_RESOLVER_MARKER,K_ as RPX_HOSTS_MARKER,aS as OnDemandCertManager,BS as MACOS_SYSTEM_KEYCHAIN,NS as MACOS_CA_TRUST_FLAGS,Z_ as LEGACY_TLD_RESOLVER_LABELS,TR as DefaultPortManager,V_ as DNS_STATE_VERSION,H_ as DNS_PORT,hS as ACME_CHALLENGE_PREFIX};
@@ -1,4 +1,12 @@
1
1
  import type { LoadBalancerConfig, LoadBalancerStrategy, ProxyFrom } from './types';
2
+ /**
3
+ * The ordered list of upstream `host:port` URLs a {@link ProxyFrom} resolves
4
+ * to, ignoring weight/other per-upstream config. Lets callers cheaply compare
5
+ * "does this pool still match this `from`" without rebuilding a full pool —
6
+ * see `entryToRoute` in daemon.ts, which reconciles a cached pool against a
7
+ * registry entry's current `from` on every call.
8
+ */
9
+ export declare function resolveUpstreamUrls(from: ProxyFrom): string[];
2
10
  /**
3
11
  * The first upstream's `host:port` from a {@link ProxyFrom}, for call sites
4
12
  * that need a single representative address (connection testing before the
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@stacksjs/rpx",
3
3
  "type": "module",
4
- "version": "0.11.27",
4
+ "version": "0.11.29",
5
5
  "description": "A modern and smart reverse proxy.",
6
6
  "author": "Chris Breuer <chris@stacksjs.org>",
7
7
  "license": "MIT",
@@ -77,7 +77,7 @@
77
77
  "devDependencies": {
78
78
  "bunfig": "^0.15.6",
79
79
  "mitata": "^1.0.34",
80
- "typescript": "^5.9.3"
80
+ "typescript": "^7.0.2"
81
81
  },
82
82
  "simple-git-hooks": {
83
83
  "pre-commit": "bunx lint-staged"