@stacksjs/rpx 0.11.48 → 0.11.50
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/bin/cli.js +1 -1
- package/dist/index.js +1 -1
- package/package.json +1 -1
package/dist/bin/cli.js
CHANGED
|
@@ -383,6 +383,6 @@ Received ${r}, cleaning up...`),e)try{await e()}catch(n){console.error("Error du
|
|
|
383
383
|
`)),this.enableDidYouMean){let t=[];for(let n of this.commands){if(n.displayName)t.push(n.displayName);if(n.aliasNames)t.push(...n.aliasNames)}let r=Ld(e,t);if(r.length>0)console.log(gi.yellow("Did you mean one of these?")),r.forEach((n)=>console.log(` ${gi.dim("•")} ${this.name} ${n}`)),console.log("")}console.log(gi.dim("Run"),`${this.name} --help`,gi.dim("to see all available commands")),pr.exit(1)}async parse(e=Od,t={}){let{run:r=!0,exitOnError:n=!1}=t;if(n)try{return await this.parse(e,{run:r})}catch(a){throw this.handleUsageError(a),a}if(this.rawArgs=e,!this.name)this.name=e[1]?Bb(e[1]):"cli";let i=!0,s=e.slice(2),o=s[0];if(o&&!o.startsWith("-")){for(let a of this.commands)if(a.isMatched(o)){let l=this.mri(s,a);i=!1;let u={...l,args:l.args.slice(1)};this.setParsedInfo(u,a,o),this.emit(`command:${o}`,a);break}}if(i){for(let a of this.commands)if(a.name===""){i=!1;let l=this.mri(s,a);this.setParsedInfo(l,a),this.emit("command:!",a);break}}if(i){let a=this.mri(e.slice(2));this.setParsedInfo(a)}if(this.options.verbose)this.isVerbose=!0;if(this.options.quiet)this.isQuiet=!0;if(this.options.debug)this.isDebug=!0;if(this.options.noInteraction)this.isNoInteraction=!0;if(this.options.env)this.environment=String(this.options.env);if(this.options.dryRun)this.isDryRun=!0;if(this.options.force)this.isForce=!0;if(this.options.noEmoji!==void 0)this.useEmoji=!this.options.noEmoji;if(this.options.theme)this.theme=String(this.options.theme);if(this.options.noCache!==void 0)this.isNoCache=Boolean(this.options.noCache);if(this.options.help&&this.showHelpOnExit)this.outputHelp(),r=!1,this.unsetMatchedCommand();if(this.options.version&&this.showVersionOnExit&&this.matchedCommandName==null)this.outputVersion(),r=!1,this.unsetMatchedCommand();let c={args:this.args,options:this.options};if(r)await this.runMatchedCommand();if(!this.matchedCommand&&this.args[0]){if(this.emit("command:*"),!(this.listenerCount("command:*")>0))this.showCommandNotFound(this.args[0])}return c}mri(e,t){let r=[...this.globalCommand.options,...t?t.options:[]],n=Nb(r),i=[],s=e.indexOf("--");if(s>-1)i=e.slice(s+1),e=e.slice(0,s);let o=kb(e,n),c={_:o._};for(let h of Object.keys(o))if(h!=="_")c[Fd(h)]=o[h];let a=c._,l={"--":i},u=t&&t.config.ignoreOptionDefaultValue?t.config.ignoreOptionDefaultValue:this.globalCommand.config.ignoreOptionDefaultValue,f=Object.create(null);for(let h of r){if(!u&&h.config.default!==void 0)for(let d of h.names)l[d]=h.config.default;if(Array.isArray(h.config.type)){if(f[h.name]===void 0)f[h.name]={shouldTransform:!0,transformFunction:h.config.type[0]}}}for(let h of Object.keys(c))if(h!=="_"){let d=h.split(".");Fb(l,d,c[h]),Lb(l,f)}return{args:a,options:l}}async run(e=Od){return this.parse(e,{run:!0,exitOnError:!0})}handleUsageError(e){if(!(!!e&&typeof e==="object"&&e.name==="ClappError"&&e.isUsageError!==!1))return;let n=e,i=n.message??"command-line error",s=this.name?`${this.name}: `:"",o=/--help/.test(i)?"":`
|
|
384
384
|
Run \`${this.name??"cli"} --help\` for usage.`;pr.stderr.write(`${s}${i}${o}
|
|
385
385
|
`),pr.exit(n.exitCode??2)}async runMatchedCommand(){let{args:e,options:t,matchedCommand:r}=this;if(!r||!r.commandAction)return;r.checkUnknownOptions(),r.checkOptionValue(),r.checkRequiredArgs();let n=[];r.args.forEach((c,a)=>{if(c.variadic)n.push(e.slice(a));else n.push(e[a])}),n.push(t);let i={command:r,args:n,options:t};for(let c of r.beforeHooks)await c(i);let s,o=async()=>{let c=r.commandAction.apply(this,n);if(c instanceof Promise)s=await c;else s=c;return s};if(r.middleware.length>0){let c=0,a=async()=>{if(c<r.middleware.length){let l=r.middleware[c++];await l({...i,next:a})}else await o()};await a()}else await o();for(let c of r.afterHooks)await c(i);return s}removeSignalHandlers(){if(!this.signalHandlersSet)return this;return pr.removeAllListeners("SIGINT"),pr.removeAllListeners("SIGTERM"),this.signalHandlersSet=!1,this}destroy(){this.removeSignalHandlers(),this.commands=[],this.rawArgs=[],this.args=[],this.options={},this.matchedCommand=void 0,this.matchedCommandName=void 0,this.removeAllListeners()}}class Id{cache;enabled;cleanupInterval=null;hits=0;misses=0;constructor(){this.cache=new Map,this.enabled=!0,this.startCleanupInterval()}startCleanupInterval(){if(this.cleanupInterval)return;this.cleanupInterval=setInterval(()=>{this.cleanup()},30000),this.cleanupInterval.unref()}stopCleanup(){if(this.cleanupInterval)clearInterval(this.cleanupInterval),this.cleanupInterval=null}isEnabled(){return this.enabled}get(e){if(!this.enabled){this.misses++;return}let t=this.cache.get(e);if(!t){this.misses++;return}if(Date.now()-t.timestamp>t.ttl){this.cache.delete(e),this.misses++;return}return this.hits++,t.value}set(e,t,r=5000){if(!this.enabled)return;this.cache.set(e,{value:t,timestamp:Date.now(),ttl:r})}has(e){if(!this.enabled)return!1;let t=this.cache.get(e);if(!t)return!1;if(Date.now()-t.timestamp>t.ttl)return this.cache.delete(e),!1;return!0}delete(e){this.cache.delete(e)}clear(){this.cache.clear()}disable(){this.enabled=!1,this.clear()}enable(){this.enabled=!0}stats(){return{size:this.cache.size,enabled:this.enabled,hits:this.hits,misses:this.misses}}resetStats(){this.hits=0,this.misses=0}keys(){return Array.from(this.cache.keys())}cleanup(){let e=Date.now();for(let[t,r]of this.cache.entries())if(e-r.timestamp>r.ttl)this.cache.delete(t)}destroy(){this.stopCleanup(),this.clear(),this.resetStats()}}var n1=new Id;function Vb(){if("FORCE_COLOR"in pi.env)return pi.env.FORCE_COLOR!=="0";if("NO_COLOR"in pi.env||pi.env.TERM==="dumb")return!1;if(pi.platform==="win32")return!0;return Nd.isatty(1)&&Nd.isatty(2)}var Md=Vb();function ue(e,t){if(!Md)return(r)=>r;return(r)=>e+r+t}var Hb=ue("\x1B[0m","\x1B[0m"),zb=ue("\x1B[31m","\x1B[39m"),Gb=ue("\x1B[32m","\x1B[39m"),qb=ue("\x1B[33m","\x1B[39m"),Yb=ue("\x1B[34m","\x1B[39m"),Jb=ue("\x1B[35m","\x1B[39m"),Xb=ue("\x1B[36m","\x1B[39m"),Zb=ue("\x1B[37m","\x1B[39m"),Qb=ue("\x1B[90m","\x1B[39m"),e$=ue("\x1B[1m","\x1B[22m"),t$=ue("\x1B[3m","\x1B[23m"),r$=ue("\x1B[4m","\x1B[24m"),n$=ue("\x1B[2m","\x1B[22m"),i$=ue("\x1B[7m","\x1B[27m"),s$=ue("\x1B[8m","\x1B[28m"),o$=ue("\x1B[9m","\x1B[29m"),a$=ue("\x1B[41m","\x1B[49m"),c$=ue("\x1B[42m","\x1B[49m"),l$=ue("\x1B[43m","\x1B[49m"),u$=ue("\x1B[44m","\x1B[49m"),f$=ue("\x1B[45m","\x1B[49m"),h$=ue("\x1B[46m","\x1B[49m"),d$=ue("\x1B[47m","\x1B[49m"),m$=Md,g$={reset:Hb,red:zb,green:Gb,yellow:qb,blue:Yb,magenta:Jb,cyan:Xb,white:Zb,gray:Qb,bold:e$,italic:t$,underline:r$,dim:n$,inverse:i$,hidden:s$,strikethrough:o$,bgRed:a$,bgGreen:c$,bgYellow:l$,bgBlue:u$,bgMagenta:f$,bgCyan:h$,bgWhite:d$,isColorSupported:m$},p$=g$,w$=["up","down","left","right","space","enter","cancel"],l1={actions:new Set(w$),aliases:new Map([["k","up"],["j","down"],["h","left"],["l","right"],["\x03","cancel"],["escape","cancel"]]),messages:{cancel:"Canceled",error:"Something went wrong"}};var u1=y$.platform.startsWith("win"),f1=Symbol("clapp:cancel");var v$=Ib();var oe=(e,t)=>v$?e:t,h1=oe("◆","*"),d1=oe("■","x"),m1=oe("▲","x"),g1=oe("◇","o"),p1=oe("┌","T"),b$=oe("│","|"),y1=oe("└","—"),w1=oe("●",">"),v1=oe("○"," "),b1=oe("◻","[•]"),$1=oe("◼","[+]"),C1=oe("◻","[ ]"),x1=oe("▪","•"),S1=oe("─","-"),E1=oe("╮","+"),T1=oe("├","+"),P1=oe("╯","+"),A1=oe("●","•"),R1=oe("◆","*"),k1=oe("▲","!"),O1=oe("■","x");var N1={light:oe("─","-"),heavy:oe("━","="),block:oe("█","#")};function $$(){return`${p$.gray(b$)} `}var D1=$$();Co();ou();Bc();qt();fe();import{spawn as vk}from"node:child_process";import*as Kc from"node:process";class Wc{processes=new Map;isShuttingDown=!1;async startProcess(e,t,r){if(this.processes.has(e)){m("start",`Process ${e} is already running`,r);return}let[n,...i]=t.command.split(" "),s=t.cwd||Kc.cwd();m("start",`Starting process ${e}:`,r),m("start",` Command: ${n} ${i.join(" ")}`,r),m("start",` Working directory: ${s}`,r),m("start",` Environment variables: ${ur(t.env)}`,r);let o=vk(n,i,{cwd:s,env:{...Kc.env,...t.env},shell:!0,stdio:"inherit"});return this.processes.set(e,{command:t.command,cwd:s,process:o,env:t.env}),new Promise((c,a)=>{if(o.on("error",(l)=>{if(!this.isShuttingDown)m("start",`Process ${e} failed to start: ${l}`,r),this.processes.delete(e),a(l)}),o.on("exit",(l)=>{if(!this.isShuttingDown&&l!==null&&l!==0)m("start",`Process ${e} exited with code ${l}; leaving the proxy running`,r),this.processes.delete(e),a(Error(`Process ${e} exited with code ${l}`))}),r)o.stdout?.on("data",(l)=>{m("process",`[${e}] ${l.toString().trim()}`,!0)}),o.stderr?.on("data",(l)=>{m("process",`[${e}] ERR: ${l.toString().trim()}`,!0)});setTimeout(()=>{if(!this.isShuttingDown&&o.killed)this.processes.delete(e),a(Error(`Process ${e} was killed during startup`));else m("start",`Process ${e} started successfully`,r),c()},1000)})}async stopProcess(e,t){let r=this.processes.get(e);if(!r?.process){m("start",`No process found for ${e}`,t);return}return m("start",`Stopping process ${e}`,t),new Promise((n)=>{if(!r.process){n();return}r.process.once("exit",()=>{this.processes.delete(e),m("start",`Process ${e} stopped`,t),n()});try{r.process.kill("SIGTERM"),setTimeout(()=>{if(r.process){m("start",`Force killing process ${e}`,t);try{r.process.kill("SIGKILL")}catch(i){}}},3000)}catch(i){m("start",`Error stopping process ${e}: ${i}`,t),this.processes.delete(e),n()}})}async stopAll(e){if(this.isShuttingDown){m("start","Already shutting down, skipping duplicate stopAll call",e);return}this.isShuttingDown=!0,m("start","Stopping all processes",e);let t=Array.from(this.processes.keys()).map((r)=>this.stopProcess(r,e).catch((n)=>{b.error(`Failed to stop process ${r}:`,n)}));await Promise.allSettled(t),this.processes.clear(),this.isShuttingDown=!1}isRunning(e){let t=this.processes.get(e);return!!t?.process&&!t.process.killed}}var Vc=new Wc;Xr();import{readdir as Nk,readFile as Dk}from"node:fs/promises";import*as gb from"node:path";import*as pb from"node:process";qt();import{execSync as Tk}from"node:child_process";import*as Xc from"node:http";import*as cb from"node:net";import*as Zc from"node:os";import*as lb from"node:path";import*as q from"node:process";var Hc=(e,t)=>(r)=>`\x1B[${e}m${r}\x1B[${t}m`,Mt={bold:Hc(1,22),dim:Hc(2,22),green:Hc(32,39),cyan:Hc(36,39)};Co();Bc();qt();Xr();fe();import*as nb from"node:fs";import*as ib from"node:path";import*as _t from"node:process";function bk(e,t){let n=(t&&t!=="/"?`${e}${t}`:e).replace(/[^a-zA-Z0-9._-]+/g,"-").replace(/^-+|-+$/g,"").slice(0,128);return n.length>0?n:"rpx"}async function yd(e){if(e.proxies.length===0)throw Error("runViaDaemon: no proxies provided");let t=e.verbose??!1,r=e.registryDir,n=new Set,i=e.proxies.map((h)=>{let d=h.id??bk(h.to,h.path);if(!Dr(d))throw Error(`invalid registry id "${d}" derived from to="${h.to}"`);if(n.has(d))throw Error(`duplicate registry id "${d}" — set an explicit \`id\` on one of the proxies`);return n.add(d),{...h,id:d}}),s=new Date().toISOString();for(let h of i)await ai({id:h.id,from:h.from,to:h.to,path:h.path,pid:e.persistent?void 0:_t.pid,cwd:_t.cwd(),createdAt:s,cleanUrls:h.cleanUrls,changeOrigin:h.changeOrigin,pathRewrites:h.pathRewrites,static:h.static,loadBalancer:h.loadBalancer},r,t);let o=await Uc({rpxDir:e.rpxDir,verbose:t,spawnCommand:e.spawnCommand,startupTimeoutMs:e.startupTimeoutMs,spawnEnv:e.spawnEnv});for(let h of i){let d=h.static?`static ${typeof h.static==="string"?h.static:h.static.dir}`:h.from;b.success(`https://${h.to} → ${d}`)}if(b.info(`(via rpx daemon pid=${o.pid}; \`rpx daemon:status\` to inspect)`),e.detached)return;let c=!1,a=r??dt(),l=i.map((h)=>h.id),u=async()=>{if(c)return;c=!0;for(let h of l)await Jr(h,r,t).catch((d)=>{m("runner",`removeEntry(${h}) failed: ${d}`,t)})},f=(h)=>{m("runner",`received ${h}, unregistering ${l.length} entries`,t),u().finally(()=>_t.exit(0))};_t.once("SIGINT",f),_t.once("SIGTERM",f),_t.once("exit",()=>{if(c)return;for(let h of l)try{nb.unlinkSync(ib.join(a,`${h}.json`))}catch{}}),await new Promise(()=>{})}id();hc();Yh();import{timingSafeEqual as $k}from"node:crypto";function Ck(e,t){if(e==null)return!1;let r=Buffer.from(e),n=Buffer.from(t);if(r.length!==n.length)return!1;return $k(r,n)}function zc(e){return e.toLowerCase().replace(/\.$/,"")}var xk=["/.well-known/acme-challenge/"];function Sk(e){let t=e.headers.get("host");if(t)return zc(t.split(":")[0]);try{return zc(new URL(e.url).hostname)}catch{return""}}function sb(e){let t=e.header.toLowerCase(),r=new Set,n=[];for(let a of e.hosts){let l=zc(a);if(l.startsWith("*."))n.push(l);else r.add(l)}let i=e.exemptPaths??xk,s=e.forbiddenMessage??`Forbidden: direct origin access is not allowed; requests must arrive via the CDN.
|
|
386
|
-
`,o=(a)=>{let l=zc(a);return r.has(l)||n.some((u)=>Ur(l,u))},c=(a)=>{let l=Sk(a);if(!o(l))return;let u="/";try{u=new URL(a.url).pathname}catch{}if(i.some((f)=>u.startsWith(f)))return;if(Ck(a.headers.get(t),e.value))return;return new Response(s,{status:403,headers:{"content-type":"text/plain"}})};return c.protects=o,c}_h();jh();mc();bc();Wh();Gh();xc();wc();fe();var ob={name:"@stacksjs/rpx",type:"module",version:"0.11.48",description:"A modern and smart reverse proxy.",author:"Chris Breuer <chris@stacksjs.org>",license:"MIT",homepage:"https://github.com/stacksjs/rpx",repository:{type:"git",url:"git+https://github.com/stacksjs/rpx.git"},bugs:{url:"https://github.com/stacksjs/rpx/issues"},keywords:["reverse proxy","ssl","development","environment","proxy","bun","stacks","typescript","javascript"],exports:{".":{types:"./dist/index.d.ts",bun:"./dist/index.js",import:"./dist/index.js"}},module:"./dist/index.js",types:"./dist/index.d.ts",bin:{rpx:"./dist/bin/cli.js","reverse-proxy":"./dist/bin/cli.js"},files:["README.md","dist"],scripts:{build:"bun build.ts && bun build --production ./bin/cli.ts --compile --outfile bin/rpx",compile:"bun build --production ./bin/cli.ts --compile --outfile bin/rpx","compile:all":"bun run compile:linux-x64 && bun run compile:linux-arm64 && bun run compile:windows-x64 && bun run compile:darwin-x64 && bun run compile:darwin-arm64","compile:linux-x64":"bun build --production ./bin/cli.ts --compile --target=bun-linux-x64 --outfile bin/rpx-linux-x64","compile:linux-arm64":"bun build --production ./bin/cli.ts --compile --target=bun-linux-arm64 --outfile bin/rpx-linux-arm64","compile:windows-x64":"bun build --production ./bin/cli.ts --compile --target=bun-windows-x64 --outfile bin/rpx-windows-x64.exe","compile:darwin-x64":"bun build --production ./bin/cli.ts --compile --target=bun-darwin-x64 --outfile bin/rpx-darwin-x64","compile:darwin-arm64":"bun build --production ./bin/cli.ts --compile --target=bun-darwin-arm64 --outfile bin/rpx-darwin-arm64",bench:"bun run bench/run.ts","bench:html":"bun run bench/run.ts --html","bench:latency":"bun run bench/run.ts --latency","bench:throughput":"bun run bench/run.ts --throughput",lint:"bunx --bun pickier .","lint:fix":"bunx --bun pickier . --fix",fresh:"bunx rimraf node_modules/ bun.lock && bun i",changelog:"changelogen --output CHANGELOG.md",prepublishOnly:"bun build.ts","release:binaries":"bun run compile:all && bun run zip",test:"bun test",typecheck:"bunx tsc --noEmit",zip:"bun run zip:all","zip:all":"bun run zip:linux-x64 && bun run zip:linux-arm64 && bun run zip:windows-x64 && bun run zip:darwin-x64 && bun run zip:darwin-arm64","zip:linux-x64":"zip -j bin/rpx-linux-x64.zip bin/rpx-linux-x64","zip:linux-arm64":"zip -j bin/rpx-linux-arm64.zip bin/rpx-linux-arm64","zip:windows-x64":"zip -j bin/rpx-windows-x64.zip bin/rpx-windows-x64.exe","zip:darwin-x64":"zip -j bin/rpx-darwin-x64.zip bin/rpx-darwin-x64","zip:darwin-arm64":"zip -j bin/rpx-darwin-arm64.zip bin/rpx-darwin-arm64"},dependencies:{"@stacksjs/clapp":"^0.2.10","@stacksjs/tlsx":"^0.13.19"},devDependencies:{bunfig:"^0.15.6",mitata:"^1.0.34",typescript:"^7.0.2"},"simple-git-hooks":{"pre-commit":"bunx lint-staged"},"lint-staged":{"*.{js,ts}":"bunx --bun pickier . --fix"}};var Rs=ob.version;var qc=new Wc,Pk=new Ec("0.0.0.0"),Os=new Set,Jc=new Set,wd=!1,Gc=null,vd=null;async function Qc(e){if(wd)return m("cleanup","Cleanup already in progress, skipping",e?.verbose),vd||Promise.resolve();wd=!0,m("cleanup","Starting cleanup process",e?.verbose),vd=new Promise((t)=>{Gc=t});try{await qc.stopAll(e?.verbose),b.info("Shutting down proxy servers...");let t=[],r=Array.from(Os).map((n)=>new Promise((i)=>{let s=n;try{if(typeof s.stop==="function")s.stop(!0),m("cleanup","Bun server stopped",e?.verbose),i();else if(typeof s.close==="function")s.close(()=>{m("cleanup","Server closed successfully",e?.verbose),i()});else i()}catch(o){m("cleanup",`Error stopping server: ${o}`,e?.verbose),i()}}));t.push(...r),Os.clear();for(let n of Jc)ii(n);if(Jc.clear(),e?.hosts&&e.domains?.length){m("cleanup","Cleaning up hosts file entries",e?.verbose),m("cleanup",`Original domains for cleanup: ${JSON.stringify(e.domains)}`,e?.verbose);let n=e.domains.filter((i)=>{if(i==="test.local")return!0;return i!=="localhost"&&!i.startsWith("localhost.")&&i!=="127.0.0.1"});if(m("cleanup",`Filtered domains for cleanup: ${JSON.stringify(n)}`,e?.verbose),n.length>0)b.info("Cleaning up hosts file entries..."),t.push(Rv(n,e?.verbose).then(()=>{m("cleanup",`Removed hosts entries for ${n.join(", ")}`,e?.verbose)}).catch((i)=>{m("cleanup",`Failed to remove hosts entries: ${i}`,e?.verbose),b.warn(`Failed to clean up hosts file entries for ${n.join(", ")}:`,i)}))}if(e?.certs&&e.domains?.length){m("cleanup","Cleaning up SSL certificates",e?.verbose),b.info("Cleaning up SSL certificates...");let n=e.domains.map(async(i)=>{try{await Uw(i,e?.verbose),m("cleanup",`Removed certificates for ${i}`,e?.verbose)}catch(s){m("cleanup",`Failed to remove certificates for ${i}: ${s}`,e?.verbose),b.warn(`Failed to clean up certificates for ${i}:`,s)}});t.push(...n)}await Promise.allSettled(t),m("cleanup","All cleanup tasks completed successfully",e?.verbose),b.success("All cleanup tasks completed successfully")}catch(t){m("cleanup",`Error during cleanup: ${t}`,e?.verbose),b.error("Error during cleanup:",t)}finally{if(Gc)Gc();Gc=null,wd=!1;let t=e&&"vitePluginUsage"in e&&e.vitePluginUsage===!0;if(q.env.NODE_ENV!=="test"&&q.env.BUN_ENV!=="test"&&!t)q.exit(0)}return vd}var bd=!1;function ub(e){if(bd){m("signal",`Received second ${e} signal, forcing exit`,!0),q.exit(1);return}bd=!0,m("signal",`Received ${e} signal, initiating cleanup`,!0),Qc().catch((t)=>{m("signal",`Cleanup failed after ${e}: ${t}`,!0),q.exit(1)}).finally(()=>{bd=!1})}q.once("SIGINT",()=>ub("SIGINT"));q.once("SIGTERM",()=>ub("SIGTERM"));q.on("uncaughtException",(e)=>{b.error("Uncaught exception (continuing):",e)});q.on("unhandledRejection",(e)=>{b.error("Unhandled rejection (continuing):",e)});async function Ns(e,t,r,n=5){m("connection",`Testing connection to ${e}:${t} (retries left: ${n})`,r);let i=15000,s=Date.now();if(q.env.RPX_BYPASS_CONNECTION_TEST==="true"){m("connection",`Bypassing connection test for ${e}:${t} due to RPX_BYPASS_CONNECTION_TEST flag`,r);return}let o=()=>new Promise((c,a)=>{let l=cb.connect({host:e,port:t,timeout:3000});l.once("connect",()=>{m("connection",`Successfully connected to ${e}:${t}`,r),l.end(),c()}),l.once("timeout",()=>{m("connection",`Connection to ${e}:${t} timed out`,r),l.destroy(),a(Error("Connection timed out"))}),l.once("error",(u)=>{m("connection",`Failed to connect to ${e}:${t}: ${u}`,r),l.destroy(),a(u)})});try{await o()}catch(c){let a=c;if(Date.now()-s>i){m("connection",`Connection test timed out after ${i}ms, but continuing anyway`,r),b.warn(`Connection test to ${e}:${t} timed out, but RPX will try to proceed anyway.`);return}if(a.code==="ECONNREFUSED"&&n>0)return m("connection",`Connection refused, server might be starting up. Retrying in 2 seconds... (${n} retries left)`,r),await new Promise((u)=>setTimeout(u,2000)),Ns(e,t,r,n-1);if(n>0)try{m("connection",`Trying HTTP request to ${e}:${t}`,r),await new Promise((u,f)=>{let h=Xc.request({hostname:e,port:t,path:"/",method:"HEAD",timeout:5000},(d)=>{m("connection",`Received HTTP response with status: ${d.statusCode}`,r),u()});h.on("error",(d)=>f(d)),h.on("timeout",()=>{h.destroy(),f(Error("HTTP request timed out"))}),h.end()}),m("connection",`HTTP request to ${e}:${t} succeeded`,r);return}catch(u){return m("connection",`HTTP request to ${e}:${t} failed: ${u}`,r),m("connection",`Retrying socket connection in 2 seconds... (${n} retries left)`,r),await new Promise((f)=>setTimeout(f,2000)),Ns(e,t,r,n-1)}let l=`Failed to connect to ${e}:${t} after ${5-n} attempts: ${a.message}`;m("connection",`${l}. To bypass this check set RPX_BYPASS_CONNECTION_TEST=true`,r),b.warn(l),b.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 fb(e){m("server",`Starting server with options: ${ur(e)}`,e.verbose);let t=qr(e.from),r=new URL(t.startsWith("http")?t:`http://${t}`),n=new URL((e.to?.startsWith("http")?e.to:`http://${e.to}`)||"rpx.localhost"),i=Number.parseInt(r.port)||(r.protocol.includes("https:")?443:80),s=[n.hostname];if($d(e)&&!n.hostname.includes("localhost")&&!n.hostname.includes("127.0.0.1")){m("hosts",`Checking if hosts file entry exists for: ${n.hostname}`,e?.verbose);try{if(!(await Ss(s,e.verbose))[0]){b.info(`Adding ${n.hostname} to hosts file...`),b.info("This may require sudo/administrator privileges");try{await xs(s,e.verbose)}catch(a){if(b.error("Failed to add hosts entry:",a.message),b.warn("You can manually add this entry to your hosts file:"),b.warn(`127.0.0.1 ${n.hostname}`),b.warn(`::1 ${n.hostname}`),q.platform==="win32")b.warn("On Windows:"),b.warn("1. Run notepad as administrator"),b.warn("2. Open C:\\Windows\\System32\\drivers\\etc\\hosts");else b.warn("On Unix systems:"),b.warn("sudo nano /etc/hosts")}}else m("hosts",`Host entry already exists for ${n.hostname}`,e.verbose)}catch(c){b.error("Failed to check hosts file:",c.message)}}try{await Ns(r.hostname,i,e.verbose)}catch(c){m("server",`Connection test failed: ${c}`,e.verbose),b.error(c.message),b.warn("Continuing with proxy setup despite connection test failure..."),b.info("If you need to bypass connection testing, set environment variable RPX_BYPASS_CONNECTION_TEST=true")}let o=e._cachedSSLConfig||null;if(e.https)try{if(e.https===!0)e.https=ts({...e,to:n.hostname});if(o=await Or({...e,to:n.hostname,https:e.https}),!o){if(m("ssl",`Generating new certificates for ${n.hostname}`,e.verbose),await es({...e,from:r.toString(),to:n.hostname,https:e.https}),o=await Or({...e,to:n.hostname,https:e.https}),!o)throw Error(`Failed to load SSL configuration after generating certificates for ${n.hostname}`)}}catch(c){throw m("server",`SSL setup failed: ${c}`,e.verbose),c}m("server",`Setting up reverse proxy with SSL config for ${n.hostname}`,e.verbose),await Rk({...e,from:t,originalFrom:e.from||t,to:n.hostname,fromPort:i,sourceUrl:{hostname:r.hostname,host:r.host},ssl:o})}async function Ak(e,t,r,n,i,s,o,c,a,l,u,f){m("proxy",`Creating proxy server ${e} -> ${t} with cleanUrls: ${c}`,o);let h=rs(u??n.host,f);is(h);let d=[{host:t,route:{sourceHost:n.host,upstreamPool:h,cleanUrls:c||!1,changeOrigin:a||!1,basePath:"/",auth:l}}];if(!Yc({routeEntries:d,listenPort:r,sslConfig:i,originGuard:null,verbose:o??!1}))throw ii(h),Error(`Failed to start proxy server for ${t} on port ${r}`);Jc.add(h),Ok({from:e,to:t,vitePluginUsage:s,listenPort:r,ssl:!!i,cleanUrls:c,verbose:o})}async function Rk(e){m("setup",`Setting up reverse proxy: ${ur(e)}`,e.verbose);let{from:t,originalFrom:r,to:n,sourceUrl:i,ssl:s,verbose:o,cleanup:c,vitePluginUsage:a,changeOrigin:l,cleanUrls:u}=e,f=e.httpPort??80,h=e.httpsPort??443,d=mb(),y=e.portManager||Pk,g=$d(e);try{if(g&&n&&!n.includes("localhost")&&!n.includes("127.0.0.1")){if(!(await Ss([n],o))[0]){b.warn(`The hostname ${n} isn't in your hosts file. Adding it now...`);try{await xs([n],o),b.success(`Added ${n} to your hosts file.`)}catch(A){b.error(`Failed to add ${n} to your hosts file: ${A}`),b.info(`You may need to manually add '127.0.0.1 ${n}' to your /etc/hosts file.`)}}}else if(g&&q.platform!=="darwin"&&n&&n.includes("localhost")&&!n.match(/^(localhost|127\.0\.0\.1)$/)){if(!(await Ss([n],o))[0]){m("hosts",`${n} not found in hosts file, adding...`,o);try{await xs([n],o)}catch(A){m("hosts",`Failed to add ${n} to hosts file: ${A}`,o)}}}if(s&&!y.usedPorts.has(f)){if(!await Nr(f,d,o))m("setup","Starting HTTP redirect server",o),hb(o,f,h),y.usedPorts.add(f);else if(m("setup","Port 80 is in use, skipping HTTP redirect",o),o)b.warn("Port 80 is in use, HTTP to HTTPS redirect will not be available")}let p=s?h:f,w=await Nr(p,d,o),v;if(w){if(m("setup",`Port ${p} is already in use`,o),o)b.warn(`Port ${p} is already in use. This may be another instance of rpx or another service.`);if(p===443){if(v=await y.getNextAvailablePort(3443,!0),m("setup",`Using port ${v} instead of ${p}`,o),o)b.info(`Using port ${v} instead. Access your site at https://${n}:${v}`)}else if(v=await y.getNextAvailablePort(p+1000,!0),m("setup",`Using port ${v} instead of ${p}`,o),o)b.info(`Using port ${v} instead. Access your site at http://${n}:${v}`)}else v=p,y.usedPorts.add(v),m("setup",`Using standard ${p===443?"HTTPS":"HTTP"} port ${p} for ${n}`,o);await Ak(t,n,v,i,s,a,o,u,l,os(e.auth),r,e.loadBalancer)}catch(p){m("setup",`Setup failed: ${p}`,o),b.error(`Failed to setup reverse proxy: ${p.message}`),Qc({domains:[n],hosts:typeof c==="boolean"?c:c?.hosts,certs:typeof c==="boolean"?c:c?.certs,verbose:o,vitePluginUsage:a})}}function hb(e,t=80,r=443,n,i,s){m("redirect",`Starting HTTP redirect server on port ${t}`,e);let o=Xc.createServer((c,a)=>{let l=c.url?c.url.split("?",1)[0]:"";if(l.startsWith("/.well-known/acme-challenge/")){if(i){let d=i.challengeStore.handlePath(l);if(d!==void 0){m("redirect",`Serving on-demand ACME challenge ${l}`,e),a.writeHead(200,{"content-type":"text/plain"}),a.end(d);return}}if(n){let d=vc(n,l);if(d!=null){m("redirect",`Serving ACME challenge ${l}`,e),a.writeHead(200,{"content-type":"text/plain"}),a.end(d);return}}if(i){a.writeHead(404,{"content-type":"text/plain"}),a.end("challenge not found");return}}let u=c.headers.host||"",f=u.includes(":")?u.slice(0,u.indexOf(":")):u;if(i&&f&&!i.hasCert(f)&&(!s||s(f)))i.ensureCert(f).catch(()=>{});let h=r===443?f:`${f}:${r}`;m("redirect",`Redirecting request from ${u}${c.url} to https://${h}`,e),a.writeHead(301,{Location:`https://${h}${c.url}`}),a.end()}).listen(t);Os.add(o),m("redirect","HTTP redirect server started",e)}function db(e){let t={...Gt,...e};if(m("proxy",`Starting proxy with options: ${ur(t)}`,t?.verbose),t.viaDaemon){if(!t.from||!t.to){b.error("viaDaemon mode requires both `from` and `to`");return}yd({proxies:[{id:t.id,from:t.from,to:t.to,path:t.path,cleanUrls:t.cleanUrls,changeOrigin:t.changeOrigin,pathRewrites:t.pathRewrites}],verbose:t.verbose}).catch((a)=>{b.error(`Failed to register with rpx daemon: ${a.message}`),q.exit(1)});return}let r=t.to||"",n=r.split(".").pop()?.toLowerCase()||"",i=q.platform==="darwin"&&r&&!r.includes("localhost")&&!r.includes("127.0.0.1"),s=["dev","app","page","new","day","foo"],o=["test","localhost","local","example","invalid"];if(i&&s.includes(n)&&t?.verbose)b.warn(`The .${n} TLD may not work reliably for local development`),b.info(` Google owns .${n} with HSTS preloading, which can bypass local DNS`),b.info(" Consider using a reserved TLD: .test, .localhost, or .local");if(i)Promise.resolve().then(() => (As(),Lc)).then(({setupDevelopmentDns:a})=>{a({domains:[r],verbose:t.verbose}).then((l)=>{if(l)Promise.resolve().then(()=>{if(t.verbose)if(o.includes(n))b.success(`DNS server started for .${n} domains`);else b.success(`DNS server started for .${n} domains (hosts file entry also added)`)});else m("dns",`Could not start DNS server - ${r} may not resolve in browser`,t.verbose)})}).catch((a)=>{m("dns",`Failed to start DNS server: ${a}`,t.verbose)});let c={from:t.from,to:t.to,cleanUrls:t.cleanUrls,https:ts(t),cleanup:t.cleanup,vitePluginUsage:t.vitePluginUsage,changeOrigin:t.changeOrigin,verbose:t.verbose,regenerateUntrustedCerts:t.regenerateUntrustedCerts};m("proxy",`Server options: ${ur(c)}`,t.verbose),fb(c).catch((a)=>{m("proxy",`Failed to start proxy: ${a}`,t.verbose),b.error(`Failed to start proxy: ${a.message}`),Qc({domains:[t.to],hosts:typeof t.cleanup==="boolean"?t.cleanup:t.cleanup?.hosts,certs:typeof t.cleanup==="boolean"?t.cleanup:t.cleanup?.certs,verbose:t.verbose})})}function kk(e){return e?.verbose||!1}function $d(e){if(e?.hostsManagement===!1)return!1;let t=e?.cleanup;if(t===!1)return!1;if(t&&typeof t==="object"&&t.hosts===!1)return!1;return!0}async function el(e){let t={from:"localhost:5173",to:"rpx.localhost",https:!1,cleanup:{hosts:!0,certs:!1},vitePluginUsage:!1,verbose:!1,cleanUrls:!1,changeOrigin:!1,regenerateUntrustedCerts:!0};if(e)t={...t,...e};let r=kk(t),n=t.https===!1;if(t.localCa)ws(t.localCa,t.onDemandTls);let i=$d(t);if(m("config",`Starting with config: ${ur(t,2)}`,r),m("config",`Is multi-proxy? ${"proxies"in t}`,r),m("config",`Hosts management enabled? ${i}`,r),t.viaDaemon){let k="proxies"in t&&Array.isArray(t.proxies)?t.proxies.map((L)=>({id:L.id,from:L.from,to:L.to,path:L.path,cleanUrls:L.cleanUrls??t.cleanUrls,changeOrigin:L.changeOrigin??t.changeOrigin,pathRewrites:L.pathRewrites})):[{id:t.id,from:t.from,to:t.to??"rpx.localhost",path:t.path,cleanUrls:t.cleanUrls,changeOrigin:t.changeOrigin,pathRewrites:t.pathRewrites}];await yd({proxies:k,verbose:r});return}if("proxies"in t&&Array.isArray(t.proxies)){m("servers",`Found ${t.proxies.length} proxies in config`,r);for(let x of t.proxies)if(x.start){let k=`${x.from}-${x.to}`;try{m("watch",`Starting command for ${k} with command: ${x.start.command}`,r),b.info(`Starting command for ${k}...`),await qc.startProcess(k,x.start,r);let L=qr(x.from),ee=new URL(L.startsWith("http")?L:`http://${L}`),Re=ee.hostname||"localhost",Ge=Number(ee.port)||80;try{await Ns(Re,Ge,r),m("watch",`Dev server is ready at ${Re}:${Ge}`,r)}catch(jt){m("watch",`Connection check failed, but continuing with proxy setup: ${jt}`,r),b.warn("Dev server connection check failed. RPX will try to proceed anyway...")}}catch(L){throw m("watch",`Failed to start command for ${k}: ${L}`,r),Error(`Failed to start command for ${k}: ${L}`)}}else m("watch",`No start command for proxy ${x.from} -> ${x.to}`,r)}else if("start"in t&&t.start){m("watch","Found start command in single proxy config",r);let x=`${t.from}-${t.to}`;try{if(t.start)m("watch",`Starting command: ${t.start.command}`,r),await qc.startProcess(x,t.start,r);let k=qr(t.from),L=new URL(k.startsWith("http")?k:`http://${k}`),ee=L.hostname||"localhost",Re=Number(L.port)||80;try{await Ns(ee,Re,r),m("watch",`Dev server is ready at ${ee}:${Re}`,r)}catch(Ge){m("watch",`Connection check failed, but continuing with proxy setup: ${Ge}`,r),b.warn("Dev server connection check failed. RPX will try to proceed anyway...")}}catch(k){throw m("watch",`Failed to run start command: ${k}`,r),Error(`Failed to run start command: ${k}`)}}else m("watch","No start command found in config",r);let s="proxies"in t&&Array.isArray(t.proxies)?t.proxies[0]?.to:("to"in t)?t.to:"rpx.localhost";if(q.platform!=="win32"&&(t.https||i)){if(!Er())try{m("sudo","Pre-acquiring sudo credentials for privileged operations",r),Tk("sudo -v",{stdio:"inherit"})}catch{m("sudo","Could not pre-acquire sudo credentials",r)}}let o=[],c=null;if(t.productionCerts&&!n){if(o=await ms(t.productionCerts,r),o.length>0)m("ssl",`Using ${o.length} production SNI cert(s): ${o.map((x)=>x.serverName).join(", ")}`,r)}if(t.localCa&&!n){let x=await vs(t.localCa,{verbose:r,onDemandTls:t.onDemandTls}),k=new Set(x.entries.map((L)=>L.serverName));o=[...x.entries,...o.filter((L)=>!k.has(L.serverName))],c=x.defaultTls,m("ssl",`Local CA: ${x.leafMinted?`minted leaf (${x.renewalReason})`:"reusing leaf"} for ${[...k].join(", ")}; valid until ${x.notAfter.toISOString()}`,r)}if(t.https){let x=o.length>0?null:await Or(t);if(!x&&o.length===0){if(m("ssl",`No valid or trusted certificates found for ${s}, generating new ones`,t.verbose),await es(t),x=await Or(t),!x)throw Error(`Failed to load SSL certificates after generation for ${s}`)}else m("ssl",`Using existing and trusted certificates for ${s}`,t.verbose);t._cachedSSLConfig=x}let a="proxies"in t&&Array.isArray(t.proxies)?t.proxies.map((x)=>({...x,https:t.https,cleanup:t.cleanup,cleanUrls:x.cleanUrls??("cleanUrls"in t?t.cleanUrls:!1),vitePluginUsage:t.vitePluginUsage,changeOrigin:x.changeOrigin??t.changeOrigin,verbose:r,_cachedSSLConfig:t._cachedSSLConfig})):[{from:"from"in t?t.from:"localhost:5173",to:"to"in t?t.to:"rpx.localhost",cleanUrls:"cleanUrls"in t?t.cleanUrls:!1,https:t.https,cleanup:t.cleanup,vitePluginUsage:t.vitePluginUsage,start:"start"in t?t.start:void 0,changeOrigin:t.changeOrigin,auth:"auth"in t?t.auth:void 0,verbose:r,_cachedSSLConfig:t._cachedSSLConfig}],l=a.map((x)=>x.to||"rpx.localhost"),u=n?null:o.length>0?o:t._cachedSSLConfig??null,f=l.filter((x)=>x&&!x.includes("localhost")&&!x.includes("127.0.0.1")),h=["dev","app","page","new","day","foo"],d=["test","localhost","local","example","invalid"],y=[...new Set(f.map((x)=>x.split(".").pop()?.toLowerCase()))],g=y.filter((x)=>!!x&&h.includes(x));if(g.length>0&&r)b.warn(`The following TLDs may not work reliably for local development: ${g.map((x)=>`.${x}`).join(", ")}`),b.info(" These TLDs have HSTS preloading which can bypass local DNS"),b.info(" Consider using reserved TLDs: .test, .localhost, or .local");if(i&&q.platform==="darwin"&&f.length>0){let{setupDevelopmentDns:x}=await Promise.resolve().then(() => (As(),Lc));if(await x({domains:f,verbose:r})){if(r)if(y.every((ee)=>!!ee&&d.includes(ee)))b.success(`DNS server started for ${y.map((ee)=>`.${ee}`).join(", ")} domains`);else b.success(`DNS server started for ${y.map((ee)=>`.${ee}`).join(", ")} domains (hosts file entries also added)`)}else m("dns","Could not start DNS server - custom domains may not resolve",r)}let p=async()=>{m("cleanup","Starting cleanup handler",t.verbose);try{let{tearDownDevelopmentDns:x}=await Promise.resolve().then(() => (As(),Lc));await x({verbose:t.verbose})}catch(x){m("cleanup",`Error stopping DNS server: ${x}`,t.verbose)}try{await qc.stopAll(t.verbose)}catch(x){m("cleanup",`Error stopping processes: ${x}`,t.verbose)}await Qc({domains:l,hosts:typeof t.cleanup==="boolean"?t.cleanup:t.cleanup?.hosts,certs:typeof t.cleanup==="boolean"?t.cleanup:t.cleanup?.certs,verbose:t.verbose||!1})};q.on("SIGINT",p),q.on("SIGTERM",p);let w=t.singlePortMode===!0,v=t.httpsPort??443,P=t.httpPort??80,A=t.originGuard?sb(t.originGuard):null,R=!!u&&(a.length>1||w||o.length>0),D=!u&&a.length>0&&(w||a.length>1),F=t.maxTlsContexts;if(R&&u){m("proxies",`Creating shared HTTPS server for ${a.length} domains on port ${v}`,r);let x=await ab(a,i,r),k=null,L=t.onDemandTls,ee=L?.enabled?new oi({config:L,certsDir:L.certsDir??t.productionCerts?.certsDir??lb.join(Zc.homedir(),".stacks","rpx","on-demand-certs"),initial:o,verbose:r,onCertAdded:(rt)=>{if(gs()==="restart")m("on-demand","certificate installed; restarting supervised gateway to reload TLS",r),setTimeout(()=>q.kill(q.pid,"SIGTERM"),10).unref();else jt(rt)}}):null,Re=null,Ge=!1;async function jt(rt){if(Re=rt,Ge)return;Ge=!0;try{while(Re){let O=Re;if(Re=null,m("proxies",`rebuilding :${v} with ${O.length} SNI cert(s)`,r),k)Os.delete(k),k.stop(!1);let pe=!1;for(let ye=0;!pe&&ye<60;ye++){let Ir=Yc({routeEntries:x,listenPort:v,sslConfig:O,defaultTls:c,maxTlsContexts:F,originGuard:A,verbose:r});if(Ir){k=Ir,pe=!0;break}await new Promise((tn)=>setTimeout(tn,Math.min(25*2**Math.min(ye,4),500)))}if(!pe)b.error(`rpx: CRITICAL — could not rebind :${v} after cert issuance; HTTPS unbound until the next cert event or a gateway restart`)}}finally{Ge=!1}}if(!await Nr(P,"0.0.0.0",r)){let rt=new Set(x.map((pe)=>pe.host)),O=(pe)=>rt.has(pe)||[...rt].some((ye)=>Ur(pe,ye));hb(r,P,v,t.acmeChallengeWebroot,ee,O)}if(await Nr(v,"0.0.0.0",r)){if(m("proxies",`Port ${v} is already in use, cannot start shared proxy`,r),r)b.warn(`Port ${v} is in use. Shared HTTPS proxy cannot start.`);return}let St=ee&&ee.sniEntries().length>0?ee.sniEntries():u;if(k=Yc({routeEntries:x,listenPort:v,sslConfig:St,defaultTls:c,maxTlsContexts:F,originGuard:A,verbose:r}),!k){b.error(`Shared HTTPS proxy failed to bind :${v}; not exiting`);return}}else if(D){m("proxies",`Creating shared HTTP server for ${a.length} domains on port ${P}`,r);let x=await ab(a,i,r);if(await Nr(P,"0.0.0.0",r)){if(m("proxies",`Port ${P} is already in use, cannot start shared proxy`,r),r)b.warn(`Port ${P} is in use. Shared HTTP proxy cannot start.`);return}if(!Yc({routeEntries:x,listenPort:P,sslConfig:null,originGuard:A,verbose:r})){b.error(`Shared HTTP proxy failed to bind :${P}; not exiting`);return}}else for(let x of a)try{let k=x.to||"rpx.localhost";m("proxy",`Starting proxy for ${k} with SSL config: ${!!u}`,x.verbose),await fb({from:x.from||"localhost:5173",to:k,cleanUrls:x.cleanUrls||!1,https:x.https||!1,cleanup:x.cleanup||!1,vitePluginUsage:x.vitePluginUsage||!1,verbose:x.verbose||!1,_cachedSSLConfig:t._cachedSSLConfig,changeOrigin:x.changeOrigin||!1,loadBalancer:x.loadBalancer,auth:x.auth,path:x.path,pathRewrites:x.pathRewrites,httpPort:t.httpPort,httpsPort:t.httpsPort})}catch(k){m("proxies",`Failed to start proxy for ${x.to}: ${k}`,x.verbose),b.error(`Failed to start proxy for ${x.to}:`,k)}}async function ab(e,t,r){let n=[],i=new Set;for(let s of e){let o=s.to||"rpx.localhost",c=s.cleanUrls||!1,a=s.path,l=fr(a),u=os(s.auth);if(s.redirect){let f=rv(s.redirect);n.push({host:o,path:a,route:{redirect:f,basePath:l,auth:u}}),m("proxies",`Route: ${o}${a??""} → redirect ${f.status} ${f.to}${u?" (auth)":""}`,r)}else if(s.static)n.push({host:o,path:a,route:{static:yc(s.static,c),cleanUrls:c,basePath:l,auth:u}}),m("proxies",`Route: ${o}${a??""} → static ${typeof s.static==="string"?s.static:s.static.dir}${u?" (auth)":""}`,r);else{let f=qr(s.from),h=new URL(f.startsWith("http")?f:`http://${f}`),d=rs(s.from??h.host,s.loadBalancer);is(d),Jc.add(d),n.push({host:o,path:a,route:{sourceHost:h.host,upstreamPool:d,cleanUrls:c,changeOrigin:s.changeOrigin||!1,pathRewrites:s.pathRewrites,basePath:l,auth:u}}),m("proxies",`Route: ${o}${a??""} → ${h.host}${u?" (auth)":""}`,r)}if(i.has(o))continue;if(i.add(o),t&&!yn(o)&&!o.includes("localhost")&&!o.includes("127.0.0.1"))try{if(!(await Ss([o],r))[0])await xs([o],r)}catch{m("hosts",`Could not add hosts entry for ${o}`,r)}}return n}var ks=null;function mb(){if(q.env.RPX_BIND_HOSTNAME)return q.env.RPX_BIND_HOSTNAME;if(ks)return ks;try{ks=Object.values(Zc.networkInterfaces()).flat().some((r)=>r&&r.family==="IPv6")?"::":"0.0.0.0"}catch{ks="0.0.0.0"}return ks}function Yc(e){let{routeEntries:t,listenPort:r,sslConfig:n,defaultTls:i,maxTlsContexts:s,originGuard:o,verbose:c}=e,a=us(t),l=cs((h,d)=>fs(a,h,d),c),u=o?(h,d)=>o(h)??l(h,d):l,f=ls(c);try{let h=Bun.serve({port:r,hostname:mb(),reusePort:ti(),...n?{tls:Array.isArray(n)?ds({sni:n,defaultTls:i,maxTlsContexts:s,verbose:c}):si({key:n.key,cert:n.cert,ca:n.ca,requestCert:!1,rejectUnauthorized:!1})}:{},fetch(d,y){return u(d,y)},websocket:f,error(d){return m("server",`Shared proxy server error: ${d}`,c),new Response(`Server Error: ${d.message}`,{status:500})}});return Os.add(h),m("proxies",`Shared ${n?"HTTPS":"HTTP"} proxy listening on port ${r} for ${a.size} domains`,c),h}catch(h){return m("proxies",`Failed to start shared proxy: ${h}`,c),console.error("Failed to start shared proxy:",h),null}}function Ok(e){if(e?.vitePluginUsage||!e?.verbose)return;if(console.log(""),console.log(` ${Mt.green(Mt.bold("rpx"))} ${Mt.green(`v${Rs}`)}`),console.log(` ${Mt.green("➜")} ${Mt.dim(e?.from??"")} ${Mt.dim("➜")} ${Mt.cyan(e?.ssl?`https://${e?.to}`:`http://${e?.to}`)}`),e?.listenPort!==(e?.ssl?443:80))console.log(` ${Mt.green("➜")} Listening on port ${e?.listenPort}`);if(e?.cleanUrls)console.log(` ${Mt.green("➜")} Clean URLs enabled`)}fe();var Cd="/etc/rpx/certs",Ds="/etc/rpx/sites.d";function Fk(e,t){console.error(`[rpx gateway] SKIPPING malformed fragment ${e}; its host(s) will 404 until fixed: ${t.message}`)}async function Lk(e,t=Fk){let r=[];try{r=(await Nk(e)).filter((i)=>i.endsWith(".json")).sort()}catch{return[]}let n=[];for(let i of r)try{let s=JSON.parse(await Dk(gb.join(e,i),"utf8"));if(!s||typeof s!=="object"||Array.isArray(s))throw TypeError("fragment is not a JSON object");n.push({file:i,fragment:s})}catch(s){t(i,s instanceof Error?s:Error(String(s)))}return n}function Bk(e,t={}){let r=t.onWarning??((y)=>console.warn(`[rpx gateway] ${y}`)),n=[],i=new Set,s=new Map,o=new Set,c=new Set,a,l=t.certsDir??Cd,u,f,h=!1;for(let{file:y,fragment:g}of e){for(let p of Array.isArray(g.proxies)?g.proxies:[]){let w=p.id||`${p.to}${p.path??""}`;if(i.has(w)){r(`duplicate route ${w} in ${y} ignored; first declared by ${s.get(w)}`);continue}i.add(w),s.set(w,y),n.push(p)}for(let p of g.onDemandTls?.allowedSuffixes??[])o.add(p);if(a??=g.onDemandTls?.email,g.onDemandTls&&g.onDemandTls.staging!==!0)h=!0;if(g.productionCerts?.certsDir)l=g.productionCerts.certsDir;if(u??=g.acmeChallengeWebroot,g.originGuard&&g.originGuard.header&&g.originGuard.value)if(f??={header:g.originGuard.header,value:g.originGuard.value},g.originGuard.header===f.header&&g.originGuard.value===f.value)for(let p of g.originGuard.hosts??[])c.add(p);else r(`origin-guard secret in ${y} differs from the one already in force; its hosts stay unguarded rather than rejecting all traffic`)}let d={proxies:n,productionCerts:{certsDir:l,certsDirServerNames:[...new Set(n.map((y)=>y.to).filter(Boolean))]},https:!0,hostsManagement:!1,cleanup:{hosts:!1,certs:!1}};if(o.size>0)d.onDemandTls={enabled:!0,allowedSuffixes:[...o],email:a,certsDir:l,staging:!h};if(u)d.acmeChallengeWebroot=u;if(f)d.originGuard={header:f.header,value:f.value,hosts:[...c]};return d}async function Ik(e={}){let t=e.sitesDir??Ds,r=e.verbose??pb.env.RPX_VERBOSE!=="false",n=await Lk(t,e.onFragmentError);m("gateway",`merged ${n.length} fragment(s) from ${t}: ${n.map((o)=>o.file).join(", ")||"<none>"}`,r);let s={...Bk(n,{certsDir:e.certsDir}),verbose:r,singlePortMode:!0};if(e.https===!1)s.https=!1;if(e.httpPort!==void 0)s.httpPort=e.httpPort;if(e.httpsPort!==void 0)s.httpsPort=e.httpsPort;if(e.localCa)s.localCa=e.localCa;if(e.maxTlsContexts!==void 0)s.maxTlsContexts=e.maxTlsContexts;return s}async function yb(e={}){let t=await Ik(e);if(("proxies"in t&&Array.isArray(t.proxies)?t.proxies:[]).length===0&&!e.localCa)console.warn(`[rpx gateway] no routes found under ${e.sitesDir??Ds}; every request will answer 404 until a fragment is deployed`);await el(t)}fe();Fe.chdir(_k(Mk(jk(import.meta.url)),".."));var He=new fl("rpx");He.command("start","Start the Reverse Proxy Server").option("--from <from>","The URL to proxy from").option("--to <to>","The URL to proxy to").option("--key-path <path>","Absolute path to the SSL key").option("--cert-path <path>","Absolute path to the SSL certificate").option("--ca-cert-path <path>","Absolute path to the SSL CA certificate").option("--hosts-cleanup","Cleanup /etc/hosts on exit").option("--certs-cleanup","Cleanup SSL certificates on exit").option("--start-command <command>","Command to start the dev server").option("--start-cwd <path>","Current working directory for the dev server").option("--start-env <env>","Environment variables for the dev server").option("--change-origin","Change the origin of the host header to the target URL").option("--single-port-mode","Route all proxies through one shared listener (by Host header) instead of a port per proxy").option("--http-port <port>","Port for the shared HTTP listener / redirect (default: 80)").option("--https-port <port>","Port for the shared HTTPS listener (default: 443)").option("--via-daemon","Route through the shared rpx daemon instead of binding :443 directly").option("--id <id>","Stable id used when registering with the daemon (auto-derived from --to)").option("--verbose","Enable verbose logging").example("rpx start --from localhost:5173 --to my-project.localhost").example("rpx start --from localhost:3000 --to my-project.localhost/api").example("rpx start --from localhost:3000 --to localhost:3001").example("rpx start --from localhost:5173 --to my-project.test --key-path /absolute/path/to/key --cert-path /absolute/path/to/cert").example("rpx start --from localhost:5173 --to my-project.localhost --change-origin").example("rpx start --single-port-mode --https-port 8443").action(async(e)=>{if(!e?.from||!e.to)return el(Gt);let t={from:e.from,to:e.to,https:{keyPath:e.keyPath,certPath:e.certPath,caCertPath:e.caCertPath},cleanup:{certs:e.certsCleanup||!1,hosts:e.hostsCleanup||!1},verbose:e.verbose||!1,changeOrigin:e.changeOrigin||!1,singlePortMode:e.singlePortMode||!1,httpPort:e.httpPort!==void 0?Number(e.httpPort):void 0,httpsPort:e.httpsPort!==void 0?Number(e.httpsPort):void 0,viaDaemon:e.viaDaemon||!1,id:e.id};if(e.startCommand){let r={command:e.startCommand};if(e.startCwd)r.cwd=e.startCwd;if(e.startEnv)try{r.env=JSON.parse(e.startEnv)}catch(n){console.error("Failed to parse start-env JSON:",n),Fe.exit(1)}t.start=r}return db(t)});He.command("watch:start <proxy>","Start the dev server for a specific proxy").option("--verbose","Enable verbose logging").action(async(e,t)=>{let r=oc(Gt)?Gt.proxies.find((n)=>n.to===e||`${n.from}-${n.to}`===e):Gt.to===e?Gt:null;if(!r?.start)console.error(`No watch configuration found for proxy: ${e}`),Fe.exit(1);try{await Vc.startProcess(e,r.start,t.verbose),console.log(`Started dev server for ${e}`)}catch(n){console.error(`Failed to start dev server for ${e}:`,n),Fe.exit(1)}});He.command("watch:stop <proxy>","Stop the dev server for a specific proxy").option("--verbose","Enable verbose logging").action(async(e,t)=>{try{await Vc.stopProcess(e,t.verbose),console.log(`Stopped dev server for ${e}`)}catch(r){console.error(`Failed to stop dev server for ${e}:`,r),Fe.exit(1)}});He.command("watch:stopall","Stop all running dev servers").option("--verbose","Enable verbose logging").action(async(e)=>{try{await Vc.stopAll(e.verbose),console.log("Stopped all dev servers")}catch(t){console.error("Failed to stop all dev servers:",t),Fe.exit(1)}});function wb(e){if(e===void 0)return;let t=typeof e==="string"?Number.parseInt(e,10):e;if(!Number.isInteger(t)||t<0||t>65535)throw Error(`invalid port: ${JSON.stringify(e)}`);return t}function vb(e){return(e??"").split(",").map((t)=>t.trim()).filter(Boolean)}He.command("gateway","Serve every app fragment under a sites.d directory on :80/:443 (production gateway)").option("--sites-dir <path>",`Directory of per-app <slug>.json fragments (default ${Ds})`).option("--certs-dir <path>",`Fallback directory of real PEM certs when no fragment names one (default ${Cd})`).option("--no-https","Serve plain HTTP on the http port only; bind nothing on the https port").option("--http-port <port>","Shared HTTP port: redirect + ACME http-01, or the only listener with --no-https (default 80)").option("--https-port <port>","Shared HTTPS port (default 443)").option("--local-ca-dir <path>","LAN production mode: directory for a local Root CA and its leaf (enables localCa)").option("--local-ca-hosts <hosts>","Comma-separated LAN hostnames the local leaf must cover (e.g. pi-stacks.local)").option("--local-ca-ips <ips>","Comma-separated IP addresses the local leaf must cover (e.g. 192.168.1.20)").option("--install-trust","Install the local Root CA into the system trust store on start (needs root)").option("--max-tls-contexts <n>","Memory guard: most SNI certificates kept live (default 256)").option("--verbose","Enable verbose logging (default on; RPX_VERBOSE=false turns it off)").example("rpx gateway --sites-dir /etc/rpx/sites.d").example("rpx gateway --sites-dir /etc/rpx/sites.d --no-https --http-port 8080").example("rpx gateway --local-ca-dir /etc/rpx/local-ca --local-ca-hosts pi-stacks.local --local-ca-ips 192.168.1.20 --install-trust").action(async(e)=>{try{let t=vb(e.localCaHosts),r=vb(e.localCaIps);if((t.length>0||r.length>0||e.installTrust)&&!e.localCaDir)throw Error("--local-ca-hosts / --local-ca-ips / --install-trust need --local-ca-dir");if(e.localCaDir&&t.length===0)throw Error("--local-ca-dir needs at least one --local-ca-hosts entry");await yb({sitesDir:e.sitesDir??Ds,certsDir:e.certsDir,https:e.https!==!1,httpPort:wb(e.httpPort),httpsPort:wb(e.httpsPort),maxTlsContexts:e.maxTlsContexts===void 0?void 0:Number.parseInt(String(e.maxTlsContexts),10),localCa:e.localCaDir?{dir:e.localCaDir,hosts:t,ips:r,installTrust:e.installTrust===!0}:void 0,verbose:e.verbose===void 0?void 0:e.verbose})}catch(t){console.error(`Failed to start rpx gateway: ${t.message}`),Fe.exit(1)}});async function bb(e){let r={...(await ug().catch(()=>null))?.onDemand??{},enabled:!0};if(e){let n=e.split(",").map((i)=>i.trim()).filter(Boolean);if(n.length>0)r.roots=n}return r}He.command("daemon:start","Start the rpx daemon in the foreground (binds :443 + :80)").option("--rpx-dir <path>","Override the rpx state dir (default ~/.stacks/rpx)").option("--registry-dir <path>","Override the registry dir (default <rpx-dir>/registry.d)").option("--https-port <port>","HTTPS port (default 443)",{default:443}).option("--http-port <port>","HTTP redirect port; 0 to disable (default 80)",{default:80}).option("--hostname <host>","Bind address (default 0.0.0.0)",{default:"0.0.0.0"}).option("--certs-dir <path>","Directory of real PEM certs for per-domain SNI (<domain>.crt/.key, _wildcard.<apex>.crt/.key)").option("--workers <n>","Run as a multi-core cluster of N worker processes (default 1; also RPX_WORKERS)").option("--on-demand","Lazily boot a project's dev server the first time its host is visited (reads config `onDemand`)").option("--roots <dirs>","Comma-separated roots scanned for on-demand sites (overrides config; default ~/Code)").option("--verbose","Enable verbose logging").example("rpx daemon:start --on-demand").example("rpx daemon:start --on-demand --roots ~/Code,~/work").action(async(e)=>{try{let t=e.onDemand?await bb(e.roots):void 0;await(await eb({rpxDir:e.rpxDir,registryDir:e.registryDir,httpsPort:typeof e.httpsPort==="string"?Number.parseInt(e.httpsPort,10):e.httpsPort,httpPort:typeof e.httpPort==="string"?Number.parseInt(e.httpPort,10):e.httpPort,hostname:e.hostname,productionCerts:e.certsDir?{certsDir:e.certsDir}:void 0,workers:e.workers===void 0?void 0:typeof e.workers==="string"?Number.parseInt(e.workers,10):e.workers,onDemandSites:t,verbose:e.verbose??!0})).done,Fe.exit(0)}catch(t){console.error(`Failed to start rpx daemon: ${t.message}`),Fe.exit(1)}});He.command("daemon:stop","Stop the running rpx daemon (SIGTERM, escalates to SIGKILL)").option("--rpx-dir <path>","Override the rpx state dir (default ~/.stacks/rpx)").option("--timeout <ms>","Max ms to wait for graceful shutdown",{default:5000}).option("--no-force","Do not escalate to SIGKILL after timeout").option("--verbose","Enable verbose logging").action(async(e)=>{let t=typeof e.timeout==="string"?Number.parseInt(e.timeout,10):e.timeout,r=await tb({rpxDir:e.rpxDir,timeoutMs:t,forceAfterTimeout:e.force!==!1,verbose:e.verbose});if(!r.stopped&&r.pid===null){console.log("rpx daemon is not running");return}if(!r.stopped){console.log(`rpx daemon pid=${r.pid} was already gone (cleaned stale lock)`);return}console.log(`rpx daemon pid=${r.pid} stopped${r.forced?" (SIGKILL)":""}`)});He.command("daemon:status","Print daemon state and currently registered hosts").option("--rpx-dir <path>","Override the rpx state dir (default ~/.stacks/rpx)").option("--registry-dir <path>","Override the registry dir (default <rpx-dir>/registry.d)").option("--json","Emit machine-readable JSON instead of a human summary").action(async(e)=>{let t=e.rpxDir??De(),r=await Lr(t),n=await jc(t),i=e.registryDir,s=await xt(i).catch(()=>[]);if(e.json){console.log(JSON.stringify({running:n,pid:r,pidFile:Br(t),rpxDir:t,registryDir:i??dt(),entries:s},null,2));return}if(!n){if(console.log("rpx daemon: not running"),r!==null)console.log(`(stale pid file at ${Br(t)} → pid ${r})`)}else console.log(`rpx daemon: running (pid=${r})`),console.log(`pid file: ${Br(t)}`);if(console.log(`registry: ${i??dt()}`),s.length===0){console.log("no registered hosts");return}console.log(`registered hosts (${s.length}):`);for(let o of s){let c=o.pid!==void 0?`, pid=${o.pid}`:"";console.log(` https://${o.to} → ${o.from} (id=${o.id}${c})`)}});He.command("register","Register an upstream app with the rpx daemon").option("--id <id>","Unique id for this entry (a-z, 0-9, dot, dash, underscore)").option("--from <host:port>","Upstream host:port (e.g. localhost:5173)").option("--to <host>","Public hostname (e.g. pet-store.localhost)").option("--cwd <path>","Working directory of the upstream (informational)").option("--clean-urls","Strip .html and 301 to the clean URL").option("--change-origin","Rewrite Origin to the upstream").option("--rpx-dir <path>","Override the rpx state dir (default ~/.stacks/rpx)").option("--registry-dir <path>","Override the registry dir (default <rpx-dir>/registry.d)").option("--skip-spawn","Do not lazy-spawn the daemon if it is not already running").option("--verbose","Enable verbose logging").example("rpx register --id pet-store --from localhost:5173 --to pet-store.localhost").action(async(e)=>{if(!e.id||!e.from||!e.to)console.error("rpx register requires --id, --from, and --to"),Fe.exit(1);if(!Dr(e.id))console.error(`invalid id: ${JSON.stringify(e.id)} (must match /^[a-zA-Z0-9._-]+$/, ≤128 chars)`),Fe.exit(1);try{await ai({id:e.id,from:e.from,to:e.to,cwd:e.cwd,createdAt:new Date().toISOString(),cleanUrls:e.cleanUrls,changeOrigin:e.changeOrigin},e.registryDir,e.verbose)}catch(t){console.error(`failed to write registry entry: ${t.message}`),Fe.exit(1)}if(e.skipSpawn){console.log(`registered ${e.to} → ${e.from} (daemon spawn skipped)`);return}try{let t=await Uc({rpxDir:e.rpxDir,verbose:e.verbose}),r=t.spawned?"spawned":"attached to";console.log(`registered https://${e.to} → ${e.from} (${r} daemon pid=${t.pid})`)}catch(t){console.error(`registered entry but daemon spawn failed: ${t.message}`),console.error("the entry remains in the registry; start the daemon manually with `rpx daemon:start`"),Fe.exit(1)}});He.command("unregister <id>","Remove a previously registered app from the rpx daemon").option("--registry-dir <path>","Override the registry dir (default ~/.stacks/rpx/registry.d)").option("--verbose","Enable verbose logging").action(async(e,t)=>{if(!Dr(e))console.error(`invalid id: ${JSON.stringify(e)}`),Fe.exit(1);let r=await Zh(e,t.registryDir,t.verbose);if(!r){console.log(`no registry entry for id=${e}`);return}await Jr(e,t.registryDir,t.verbose),console.log(`unregistered ${r.to} (id=${e})`)});He.command("dns:reconcile","Remove stale macOS DNS overrides left by crashed dev sessions").option("--rpx-dir <path>","Override the rpx state dir (default ~/.stacks/rpx)").option("--verbose","Enable verbose logging").action(async(e)=>{let t=e.rpxDir??De();if(await jc(t)){console.log("rpx daemon is running — DNS overrides are managed by the daemon");return}await rb({rpxDir:t,verbose:e.verbose}),console.log("DNS reconcile complete")});He.command("sites","List the on-demand sites rpx can boot (and which are currently live)").option("--roots <dirs>","Comma-separated roots to scan (overrides config; default ~/Code)").option("--registry-dir <path>","Override the registry dir (default ~/.stacks/rpx/registry.d)").option("--json","Emit machine-readable JSON").action(async(e)=>{let t=await bb(e.roots),r=pg(t),n=await xt(e.registryDir).catch(()=>[]),i=new Set(n.map((c)=>c.to)),s=r.map((c)=>({host:c.host,dir:c.dir,command:c.command,source:c.source,live:i.has(c.host)}));if(e.json){console.log(JSON.stringify(s,null,2));return}let o=t.roots??["~/Code"];if(s.length===0){console.log("no on-demand sites found"),console.log(`scanned roots: ${o.join(", ")}`),console.log("start the daemon with `rpx daemon:start --on-demand` to serve them");return}console.log(`on-demand sites (${s.length}) — scanned ${o.join(", ")}:`);for(let c of s)console.log(` ${c.live?"●":"○"} https://${c.host} ${c.live?"(live)":"(idle)"}`),console.log(` ${c.command} ${c.dir}`)});He.command("logs <host>","Show an on-demand site's boot log (e.g. rpx logs myapp.localhost)").option("--rpx-dir <path>","Override the rpx state dir (default ~/.stacks/rpx)").option("--lines <n>","Show only the last N lines (default: all)").action(async(e,t)=>{let{readFile:r}=await import("node:fs/promises"),{join:n}=await import("node:path"),i=t.rpxDir??De(),s=n(i,"sites",`${xo(e.split(":")[0])}.log`),o;try{o=await r(s,"utf8")}catch{console.log(`no log for ${e} (it may not have been booted yet) — looked in ${s}`);return}let c=t.lines===void 0?void 0:typeof t.lines==="string"?Number.parseInt(t.lines,10):t.lines,a=o.split(`
|
|
386
|
+
`,o=(a)=>{let l=zc(a);return r.has(l)||n.some((u)=>Ur(l,u))},c=(a)=>{let l=Sk(a);if(!o(l))return;let u="/";try{u=new URL(a.url).pathname}catch{}if(i.some((f)=>u.startsWith(f)))return;if(Ck(a.headers.get(t),e.value))return;return new Response(s,{status:403,headers:{"content-type":"text/plain"}})};return c.protects=o,c}_h();jh();mc();bc();Wh();Gh();xc();wc();fe();var ob={name:"@stacksjs/rpx",type:"module",version:"0.11.50",description:"A modern and smart reverse proxy.",author:"Chris Breuer <chris@stacksjs.org>",license:"MIT",homepage:"https://github.com/stacksjs/rpx",repository:{type:"git",url:"git+https://github.com/stacksjs/rpx.git"},bugs:{url:"https://github.com/stacksjs/rpx/issues"},keywords:["reverse proxy","ssl","development","environment","proxy","bun","stacks","typescript","javascript"],exports:{".":{types:"./dist/index.d.ts",bun:"./dist/index.js",import:"./dist/index.js"}},module:"./dist/index.js",types:"./dist/index.d.ts",bin:{rpx:"./dist/bin/cli.js","reverse-proxy":"./dist/bin/cli.js"},files:["README.md","dist"],scripts:{build:"bun build.ts && bun build --production ./bin/cli.ts --compile --outfile bin/rpx",compile:"bun build --production ./bin/cli.ts --compile --outfile bin/rpx","compile:all":"bun run compile:linux-x64 && bun run compile:linux-arm64 && bun run compile:windows-x64 && bun run compile:darwin-x64 && bun run compile:darwin-arm64","compile:linux-x64":"bun build --production ./bin/cli.ts --compile --target=bun-linux-x64 --outfile bin/rpx-linux-x64","compile:linux-arm64":"bun build --production ./bin/cli.ts --compile --target=bun-linux-arm64 --outfile bin/rpx-linux-arm64","compile:windows-x64":"bun build --production ./bin/cli.ts --compile --target=bun-windows-x64 --outfile bin/rpx-windows-x64.exe","compile:darwin-x64":"bun build --production ./bin/cli.ts --compile --target=bun-darwin-x64 --outfile bin/rpx-darwin-x64","compile:darwin-arm64":"bun build --production ./bin/cli.ts --compile --target=bun-darwin-arm64 --outfile bin/rpx-darwin-arm64",bench:"bun run bench/run.ts","bench:html":"bun run bench/run.ts --html","bench:latency":"bun run bench/run.ts --latency","bench:throughput":"bun run bench/run.ts --throughput",lint:"bunx --bun pickier .","lint:fix":"bunx --bun pickier . --fix",fresh:"bunx rimraf node_modules/ bun.lock && bun i",changelog:"changelogen --output CHANGELOG.md",prepublishOnly:"bun build.ts","release:binaries":"bun run compile:all && bun run zip",test:"bun test",typecheck:"bunx tsc --noEmit",zip:"bun run zip:all","zip:all":"bun run zip:linux-x64 && bun run zip:linux-arm64 && bun run zip:windows-x64 && bun run zip:darwin-x64 && bun run zip:darwin-arm64","zip:linux-x64":"zip -j bin/rpx-linux-x64.zip bin/rpx-linux-x64","zip:linux-arm64":"zip -j bin/rpx-linux-arm64.zip bin/rpx-linux-arm64","zip:windows-x64":"zip -j bin/rpx-windows-x64.zip bin/rpx-windows-x64.exe","zip:darwin-x64":"zip -j bin/rpx-darwin-x64.zip bin/rpx-darwin-x64","zip:darwin-arm64":"zip -j bin/rpx-darwin-arm64.zip bin/rpx-darwin-arm64"},dependencies:{"@stacksjs/clapp":"^0.2.10","@stacksjs/tlsx":"^0.13.19"},devDependencies:{bunfig:"^0.15.6",mitata:"^1.0.34",typescript:"^7.0.2"},"simple-git-hooks":{"pre-commit":"bunx lint-staged"},"lint-staged":{"*.{js,ts}":"bunx --bun pickier . --fix"}};var Rs=ob.version;var qc=new Wc,Pk=new Ec("0.0.0.0"),Os=new Set,Jc=new Set,wd=!1,Gc=null,vd=null;async function Qc(e){if(wd)return m("cleanup","Cleanup already in progress, skipping",e?.verbose),vd||Promise.resolve();wd=!0,m("cleanup","Starting cleanup process",e?.verbose),vd=new Promise((t)=>{Gc=t});try{await qc.stopAll(e?.verbose),b.info("Shutting down proxy servers...");let t=[],r=Array.from(Os).map((n)=>new Promise((i)=>{let s=n;try{if(typeof s.stop==="function")s.stop(!0),m("cleanup","Bun server stopped",e?.verbose),i();else if(typeof s.close==="function")s.close(()=>{m("cleanup","Server closed successfully",e?.verbose),i()});else i()}catch(o){m("cleanup",`Error stopping server: ${o}`,e?.verbose),i()}}));t.push(...r),Os.clear();for(let n of Jc)ii(n);if(Jc.clear(),e?.hosts&&e.domains?.length){m("cleanup","Cleaning up hosts file entries",e?.verbose),m("cleanup",`Original domains for cleanup: ${JSON.stringify(e.domains)}`,e?.verbose);let n=e.domains.filter((i)=>{if(i==="test.local")return!0;return i!=="localhost"&&!i.startsWith("localhost.")&&i!=="127.0.0.1"});if(m("cleanup",`Filtered domains for cleanup: ${JSON.stringify(n)}`,e?.verbose),n.length>0)b.info("Cleaning up hosts file entries..."),t.push(Rv(n,e?.verbose).then(()=>{m("cleanup",`Removed hosts entries for ${n.join(", ")}`,e?.verbose)}).catch((i)=>{m("cleanup",`Failed to remove hosts entries: ${i}`,e?.verbose),b.warn(`Failed to clean up hosts file entries for ${n.join(", ")}:`,i)}))}if(e?.certs&&e.domains?.length){m("cleanup","Cleaning up SSL certificates",e?.verbose),b.info("Cleaning up SSL certificates...");let n=e.domains.map(async(i)=>{try{await Uw(i,e?.verbose),m("cleanup",`Removed certificates for ${i}`,e?.verbose)}catch(s){m("cleanup",`Failed to remove certificates for ${i}: ${s}`,e?.verbose),b.warn(`Failed to clean up certificates for ${i}:`,s)}});t.push(...n)}await Promise.allSettled(t),m("cleanup","All cleanup tasks completed successfully",e?.verbose),b.success("All cleanup tasks completed successfully")}catch(t){m("cleanup",`Error during cleanup: ${t}`,e?.verbose),b.error("Error during cleanup:",t)}finally{if(Gc)Gc();Gc=null,wd=!1;let t=e&&"vitePluginUsage"in e&&e.vitePluginUsage===!0;if(q.env.NODE_ENV!=="test"&&q.env.BUN_ENV!=="test"&&!t)q.exit(0)}return vd}var bd=!1;function ub(e){if(bd){m("signal",`Received second ${e} signal, forcing exit`,!0),q.exit(1);return}bd=!0,m("signal",`Received ${e} signal, initiating cleanup`,!0),Qc().catch((t)=>{m("signal",`Cleanup failed after ${e}: ${t}`,!0),q.exit(1)}).finally(()=>{bd=!1})}q.once("SIGINT",()=>ub("SIGINT"));q.once("SIGTERM",()=>ub("SIGTERM"));q.on("uncaughtException",(e)=>{b.error("Uncaught exception (continuing):",e)});q.on("unhandledRejection",(e)=>{b.error("Unhandled rejection (continuing):",e)});async function Ns(e,t,r,n=5){m("connection",`Testing connection to ${e}:${t} (retries left: ${n})`,r);let i=15000,s=Date.now();if(q.env.RPX_BYPASS_CONNECTION_TEST==="true"){m("connection",`Bypassing connection test for ${e}:${t} due to RPX_BYPASS_CONNECTION_TEST flag`,r);return}let o=()=>new Promise((c,a)=>{let l=cb.connect({host:e,port:t,timeout:3000});l.once("connect",()=>{m("connection",`Successfully connected to ${e}:${t}`,r),l.end(),c()}),l.once("timeout",()=>{m("connection",`Connection to ${e}:${t} timed out`,r),l.destroy(),a(Error("Connection timed out"))}),l.once("error",(u)=>{m("connection",`Failed to connect to ${e}:${t}: ${u}`,r),l.destroy(),a(u)})});try{await o()}catch(c){let a=c;if(Date.now()-s>i){m("connection",`Connection test timed out after ${i}ms, but continuing anyway`,r),b.warn(`Connection test to ${e}:${t} timed out, but RPX will try to proceed anyway.`);return}if(a.code==="ECONNREFUSED"&&n>0)return m("connection",`Connection refused, server might be starting up. Retrying in 2 seconds... (${n} retries left)`,r),await new Promise((u)=>setTimeout(u,2000)),Ns(e,t,r,n-1);if(n>0)try{m("connection",`Trying HTTP request to ${e}:${t}`,r),await new Promise((u,f)=>{let h=Xc.request({hostname:e,port:t,path:"/",method:"HEAD",timeout:5000},(d)=>{m("connection",`Received HTTP response with status: ${d.statusCode}`,r),u()});h.on("error",(d)=>f(d)),h.on("timeout",()=>{h.destroy(),f(Error("HTTP request timed out"))}),h.end()}),m("connection",`HTTP request to ${e}:${t} succeeded`,r);return}catch(u){return m("connection",`HTTP request to ${e}:${t} failed: ${u}`,r),m("connection",`Retrying socket connection in 2 seconds... (${n} retries left)`,r),await new Promise((f)=>setTimeout(f,2000)),Ns(e,t,r,n-1)}let l=`Failed to connect to ${e}:${t} after ${5-n} attempts: ${a.message}`;m("connection",`${l}. To bypass this check set RPX_BYPASS_CONNECTION_TEST=true`,r),b.warn(l),b.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 fb(e){m("server",`Starting server with options: ${ur(e)}`,e.verbose);let t=qr(e.from),r=new URL(t.startsWith("http")?t:`http://${t}`),n=new URL((e.to?.startsWith("http")?e.to:`http://${e.to}`)||"rpx.localhost"),i=Number.parseInt(r.port)||(r.protocol.includes("https:")?443:80),s=[n.hostname];if($d(e)&&!n.hostname.includes("localhost")&&!n.hostname.includes("127.0.0.1")){m("hosts",`Checking if hosts file entry exists for: ${n.hostname}`,e?.verbose);try{if(!(await Ss(s,e.verbose))[0]){b.info(`Adding ${n.hostname} to hosts file...`),b.info("This may require sudo/administrator privileges");try{await xs(s,e.verbose)}catch(a){if(b.error("Failed to add hosts entry:",a.message),b.warn("You can manually add this entry to your hosts file:"),b.warn(`127.0.0.1 ${n.hostname}`),b.warn(`::1 ${n.hostname}`),q.platform==="win32")b.warn("On Windows:"),b.warn("1. Run notepad as administrator"),b.warn("2. Open C:\\Windows\\System32\\drivers\\etc\\hosts");else b.warn("On Unix systems:"),b.warn("sudo nano /etc/hosts")}}else m("hosts",`Host entry already exists for ${n.hostname}`,e.verbose)}catch(c){b.error("Failed to check hosts file:",c.message)}}try{await Ns(r.hostname,i,e.verbose)}catch(c){m("server",`Connection test failed: ${c}`,e.verbose),b.error(c.message),b.warn("Continuing with proxy setup despite connection test failure..."),b.info("If you need to bypass connection testing, set environment variable RPX_BYPASS_CONNECTION_TEST=true")}let o=e._cachedSSLConfig||null;if(e.https)try{if(e.https===!0)e.https=ts({...e,to:n.hostname});if(o=await Or({...e,to:n.hostname,https:e.https}),!o){if(m("ssl",`Generating new certificates for ${n.hostname}`,e.verbose),await es({...e,from:r.toString(),to:n.hostname,https:e.https}),o=await Or({...e,to:n.hostname,https:e.https}),!o)throw Error(`Failed to load SSL configuration after generating certificates for ${n.hostname}`)}}catch(c){throw m("server",`SSL setup failed: ${c}`,e.verbose),c}m("server",`Setting up reverse proxy with SSL config for ${n.hostname}`,e.verbose),await Rk({...e,from:t,originalFrom:e.from||t,to:n.hostname,fromPort:i,sourceUrl:{hostname:r.hostname,host:r.host},ssl:o})}async function Ak(e,t,r,n,i,s,o,c,a,l,u,f){m("proxy",`Creating proxy server ${e} -> ${t} with cleanUrls: ${c}`,o);let h=rs(u??n.host,f);is(h);let d=[{host:t,route:{sourceHost:n.host,upstreamPool:h,cleanUrls:c||!1,changeOrigin:a||!1,basePath:"/",auth:l}}];if(!Yc({routeEntries:d,listenPort:r,sslConfig:i,originGuard:null,verbose:o??!1}))throw ii(h),Error(`Failed to start proxy server for ${t} on port ${r}`);Jc.add(h),Ok({from:e,to:t,vitePluginUsage:s,listenPort:r,ssl:!!i,cleanUrls:c,verbose:o})}async function Rk(e){m("setup",`Setting up reverse proxy: ${ur(e)}`,e.verbose);let{from:t,originalFrom:r,to:n,sourceUrl:i,ssl:s,verbose:o,cleanup:c,vitePluginUsage:a,changeOrigin:l,cleanUrls:u}=e,f=e.httpPort??80,h=e.httpsPort??443,d=mb(),y=e.portManager||Pk,g=$d(e);try{if(g&&n&&!n.includes("localhost")&&!n.includes("127.0.0.1")){if(!(await Ss([n],o))[0]){b.warn(`The hostname ${n} isn't in your hosts file. Adding it now...`);try{await xs([n],o),b.success(`Added ${n} to your hosts file.`)}catch(A){b.error(`Failed to add ${n} to your hosts file: ${A}`),b.info(`You may need to manually add '127.0.0.1 ${n}' to your /etc/hosts file.`)}}}else if(g&&q.platform!=="darwin"&&n&&n.includes("localhost")&&!n.match(/^(localhost|127\.0\.0\.1)$/)){if(!(await Ss([n],o))[0]){m("hosts",`${n} not found in hosts file, adding...`,o);try{await xs([n],o)}catch(A){m("hosts",`Failed to add ${n} to hosts file: ${A}`,o)}}}if(s&&!y.usedPorts.has(f)){if(!await Nr(f,d,o))m("setup","Starting HTTP redirect server",o),hb(o,f,h),y.usedPorts.add(f);else if(m("setup","Port 80 is in use, skipping HTTP redirect",o),o)b.warn("Port 80 is in use, HTTP to HTTPS redirect will not be available")}let p=s?h:f,w=await Nr(p,d,o),v;if(w){if(m("setup",`Port ${p} is already in use`,o),o)b.warn(`Port ${p} is already in use. This may be another instance of rpx or another service.`);if(p===443){if(v=await y.getNextAvailablePort(3443,!0),m("setup",`Using port ${v} instead of ${p}`,o),o)b.info(`Using port ${v} instead. Access your site at https://${n}:${v}`)}else if(v=await y.getNextAvailablePort(p+1000,!0),m("setup",`Using port ${v} instead of ${p}`,o),o)b.info(`Using port ${v} instead. Access your site at http://${n}:${v}`)}else v=p,y.usedPorts.add(v),m("setup",`Using standard ${p===443?"HTTPS":"HTTP"} port ${p} for ${n}`,o);await Ak(t,n,v,i,s,a,o,u,l,os(e.auth),r,e.loadBalancer)}catch(p){m("setup",`Setup failed: ${p}`,o),b.error(`Failed to setup reverse proxy: ${p.message}`),Qc({domains:[n],hosts:typeof c==="boolean"?c:c?.hosts,certs:typeof c==="boolean"?c:c?.certs,verbose:o,vitePluginUsage:a})}}function hb(e,t=80,r=443,n,i,s){m("redirect",`Starting HTTP redirect server on port ${t}`,e);let o=Xc.createServer((c,a)=>{let l=c.url?c.url.split("?",1)[0]:"";if(l.startsWith("/.well-known/acme-challenge/")){if(i){let d=i.challengeStore.handlePath(l);if(d!==void 0){m("redirect",`Serving on-demand ACME challenge ${l}`,e),a.writeHead(200,{"content-type":"text/plain"}),a.end(d);return}}if(n){let d=vc(n,l);if(d!=null){m("redirect",`Serving ACME challenge ${l}`,e),a.writeHead(200,{"content-type":"text/plain"}),a.end(d);return}}if(i){a.writeHead(404,{"content-type":"text/plain"}),a.end("challenge not found");return}}let u=c.headers.host||"",f=u.includes(":")?u.slice(0,u.indexOf(":")):u;if(i&&f&&!i.hasCert(f)&&(!s||s(f)))i.ensureCert(f).catch(()=>{});let h=r===443?f:`${f}:${r}`;m("redirect",`Redirecting request from ${u}${c.url} to https://${h}`,e),a.writeHead(301,{Location:`https://${h}${c.url}`}),a.end()}).listen(t);Os.add(o),m("redirect","HTTP redirect server started",e)}function db(e){let t={...Gt,...e};if(m("proxy",`Starting proxy with options: ${ur(t)}`,t?.verbose),t.viaDaemon){if(!t.from||!t.to){b.error("viaDaemon mode requires both `from` and `to`");return}yd({proxies:[{id:t.id,from:t.from,to:t.to,path:t.path,cleanUrls:t.cleanUrls,changeOrigin:t.changeOrigin,pathRewrites:t.pathRewrites}],verbose:t.verbose}).catch((a)=>{b.error(`Failed to register with rpx daemon: ${a.message}`),q.exit(1)});return}let r=t.to||"",n=r.split(".").pop()?.toLowerCase()||"",i=q.platform==="darwin"&&r&&!r.includes("localhost")&&!r.includes("127.0.0.1"),s=["dev","app","page","new","day","foo"],o=["test","localhost","local","example","invalid"];if(i&&s.includes(n)&&t?.verbose)b.warn(`The .${n} TLD may not work reliably for local development`),b.info(` Google owns .${n} with HSTS preloading, which can bypass local DNS`),b.info(" Consider using a reserved TLD: .test, .localhost, or .local");if(i)Promise.resolve().then(() => (As(),Lc)).then(({setupDevelopmentDns:a})=>{a({domains:[r],verbose:t.verbose}).then((l)=>{if(l)Promise.resolve().then(()=>{if(t.verbose)if(o.includes(n))b.success(`DNS server started for .${n} domains`);else b.success(`DNS server started for .${n} domains (hosts file entry also added)`)});else m("dns",`Could not start DNS server - ${r} may not resolve in browser`,t.verbose)})}).catch((a)=>{m("dns",`Failed to start DNS server: ${a}`,t.verbose)});let c={from:t.from,to:t.to,cleanUrls:t.cleanUrls,https:ts(t),cleanup:t.cleanup,vitePluginUsage:t.vitePluginUsage,changeOrigin:t.changeOrigin,verbose:t.verbose,regenerateUntrustedCerts:t.regenerateUntrustedCerts};m("proxy",`Server options: ${ur(c)}`,t.verbose),fb(c).catch((a)=>{m("proxy",`Failed to start proxy: ${a}`,t.verbose),b.error(`Failed to start proxy: ${a.message}`),Qc({domains:[t.to],hosts:typeof t.cleanup==="boolean"?t.cleanup:t.cleanup?.hosts,certs:typeof t.cleanup==="boolean"?t.cleanup:t.cleanup?.certs,verbose:t.verbose})})}function kk(e){return e?.verbose||!1}function $d(e){if(e?.hostsManagement===!1)return!1;let t=e?.cleanup;if(t===!1)return!1;if(t&&typeof t==="object"&&t.hosts===!1)return!1;return!0}async function el(e){let t={from:"localhost:5173",to:"rpx.localhost",https:!1,cleanup:{hosts:!0,certs:!1},vitePluginUsage:!1,verbose:!1,cleanUrls:!1,changeOrigin:!1,regenerateUntrustedCerts:!0};if(e)t={...t,...e};let r=kk(t),n=t.https===!1;if(t.localCa)ws(t.localCa,t.onDemandTls);let i=$d(t);if(m("config",`Starting with config: ${ur(t,2)}`,r),m("config",`Is multi-proxy? ${"proxies"in t}`,r),m("config",`Hosts management enabled? ${i}`,r),t.viaDaemon){let k="proxies"in t&&Array.isArray(t.proxies)?t.proxies.map((L)=>({id:L.id,from:L.from,to:L.to,path:L.path,cleanUrls:L.cleanUrls??t.cleanUrls,changeOrigin:L.changeOrigin??t.changeOrigin,pathRewrites:L.pathRewrites})):[{id:t.id,from:t.from,to:t.to??"rpx.localhost",path:t.path,cleanUrls:t.cleanUrls,changeOrigin:t.changeOrigin,pathRewrites:t.pathRewrites}];await yd({proxies:k,verbose:r});return}if("proxies"in t&&Array.isArray(t.proxies)){m("servers",`Found ${t.proxies.length} proxies in config`,r);for(let x of t.proxies)if(x.start){let k=`${x.from}-${x.to}`;try{m("watch",`Starting command for ${k} with command: ${x.start.command}`,r),b.info(`Starting command for ${k}...`),await qc.startProcess(k,x.start,r);let L=qr(x.from),ee=new URL(L.startsWith("http")?L:`http://${L}`),Re=ee.hostname||"localhost",Ge=Number(ee.port)||80;try{await Ns(Re,Ge,r),m("watch",`Dev server is ready at ${Re}:${Ge}`,r)}catch(jt){m("watch",`Connection check failed, but continuing with proxy setup: ${jt}`,r),b.warn("Dev server connection check failed. RPX will try to proceed anyway...")}}catch(L){throw m("watch",`Failed to start command for ${k}: ${L}`,r),Error(`Failed to start command for ${k}: ${L}`)}}else m("watch",`No start command for proxy ${x.from} -> ${x.to}`,r)}else if("start"in t&&t.start){m("watch","Found start command in single proxy config",r);let x=`${t.from}-${t.to}`;try{if(t.start)m("watch",`Starting command: ${t.start.command}`,r),await qc.startProcess(x,t.start,r);let k=qr(t.from),L=new URL(k.startsWith("http")?k:`http://${k}`),ee=L.hostname||"localhost",Re=Number(L.port)||80;try{await Ns(ee,Re,r),m("watch",`Dev server is ready at ${ee}:${Re}`,r)}catch(Ge){m("watch",`Connection check failed, but continuing with proxy setup: ${Ge}`,r),b.warn("Dev server connection check failed. RPX will try to proceed anyway...")}}catch(k){throw m("watch",`Failed to run start command: ${k}`,r),Error(`Failed to run start command: ${k}`)}}else m("watch","No start command found in config",r);let s="proxies"in t&&Array.isArray(t.proxies)?t.proxies[0]?.to:("to"in t)?t.to:"rpx.localhost";if(q.platform!=="win32"&&(t.https||i)){if(!Er())try{m("sudo","Pre-acquiring sudo credentials for privileged operations",r),Tk("sudo -v",{stdio:"inherit"})}catch{m("sudo","Could not pre-acquire sudo credentials",r)}}let o=[],c=null;if(t.productionCerts&&!n){if(o=await ms(t.productionCerts,r),o.length>0)m("ssl",`Using ${o.length} production SNI cert(s): ${o.map((x)=>x.serverName).join(", ")}`,r)}if(t.localCa&&!n){let x=await vs(t.localCa,{verbose:r,onDemandTls:t.onDemandTls}),k=new Set(x.entries.map((L)=>L.serverName));o=[...x.entries,...o.filter((L)=>!k.has(L.serverName))],c=x.defaultTls,m("ssl",`Local CA: ${x.leafMinted?`minted leaf (${x.renewalReason})`:"reusing leaf"} for ${[...k].join(", ")}; valid until ${x.notAfter.toISOString()}`,r)}if(t.https){let x=o.length>0?null:await Or(t);if(!x&&o.length===0){if(m("ssl",`No valid or trusted certificates found for ${s}, generating new ones`,t.verbose),await es(t),x=await Or(t),!x)throw Error(`Failed to load SSL certificates after generation for ${s}`)}else m("ssl",`Using existing and trusted certificates for ${s}`,t.verbose);t._cachedSSLConfig=x}let a="proxies"in t&&Array.isArray(t.proxies)?t.proxies.map((x)=>({...x,https:t.https,cleanup:t.cleanup,cleanUrls:x.cleanUrls??("cleanUrls"in t?t.cleanUrls:!1),vitePluginUsage:t.vitePluginUsage,changeOrigin:x.changeOrigin??t.changeOrigin,verbose:r,_cachedSSLConfig:t._cachedSSLConfig})):[{from:"from"in t?t.from:"localhost:5173",to:"to"in t?t.to:"rpx.localhost",cleanUrls:"cleanUrls"in t?t.cleanUrls:!1,https:t.https,cleanup:t.cleanup,vitePluginUsage:t.vitePluginUsage,start:"start"in t?t.start:void 0,changeOrigin:t.changeOrigin,auth:"auth"in t?t.auth:void 0,verbose:r,_cachedSSLConfig:t._cachedSSLConfig}],l=a.map((x)=>x.to||"rpx.localhost"),u=n?null:o.length>0?o:t._cachedSSLConfig??null,f=l.filter((x)=>x&&!x.includes("localhost")&&!x.includes("127.0.0.1")),h=["dev","app","page","new","day","foo"],d=["test","localhost","local","example","invalid"],y=[...new Set(f.map((x)=>x.split(".").pop()?.toLowerCase()))],g=y.filter((x)=>!!x&&h.includes(x));if(g.length>0&&r)b.warn(`The following TLDs may not work reliably for local development: ${g.map((x)=>`.${x}`).join(", ")}`),b.info(" These TLDs have HSTS preloading which can bypass local DNS"),b.info(" Consider using reserved TLDs: .test, .localhost, or .local");if(i&&q.platform==="darwin"&&f.length>0){let{setupDevelopmentDns:x}=await Promise.resolve().then(() => (As(),Lc));if(await x({domains:f,verbose:r})){if(r)if(y.every((ee)=>!!ee&&d.includes(ee)))b.success(`DNS server started for ${y.map((ee)=>`.${ee}`).join(", ")} domains`);else b.success(`DNS server started for ${y.map((ee)=>`.${ee}`).join(", ")} domains (hosts file entries also added)`)}else m("dns","Could not start DNS server - custom domains may not resolve",r)}let p=async()=>{m("cleanup","Starting cleanup handler",t.verbose);try{let{tearDownDevelopmentDns:x}=await Promise.resolve().then(() => (As(),Lc));await x({verbose:t.verbose})}catch(x){m("cleanup",`Error stopping DNS server: ${x}`,t.verbose)}try{await qc.stopAll(t.verbose)}catch(x){m("cleanup",`Error stopping processes: ${x}`,t.verbose)}await Qc({domains:l,hosts:typeof t.cleanup==="boolean"?t.cleanup:t.cleanup?.hosts,certs:typeof t.cleanup==="boolean"?t.cleanup:t.cleanup?.certs,verbose:t.verbose||!1})};q.on("SIGINT",p),q.on("SIGTERM",p);let w=t.singlePortMode===!0,v=t.httpsPort??443,P=t.httpPort??80,A=t.originGuard?sb(t.originGuard):null,R=!!u&&(a.length>1||w||o.length>0),D=!u&&a.length>0&&(w||a.length>1),F=t.maxTlsContexts;if(R&&u){m("proxies",`Creating shared HTTPS server for ${a.length} domains on port ${v}`,r);let x=await ab(a,i,r),k=null,L=t.onDemandTls,ee=L?.enabled?new oi({config:L,certsDir:L.certsDir??t.productionCerts?.certsDir??lb.join(Zc.homedir(),".stacks","rpx","on-demand-certs"),initial:o,verbose:r,onCertAdded:(rt)=>{if(gs()==="restart")m("on-demand","certificate installed; restarting supervised gateway to reload TLS",r),setTimeout(()=>q.kill(q.pid,"SIGTERM"),10).unref();else jt(rt)}}):null,Re=null,Ge=!1;async function jt(rt){if(Re=rt,Ge)return;Ge=!0;try{while(Re){let O=Re;if(Re=null,m("proxies",`rebuilding :${v} with ${O.length} SNI cert(s)`,r),k)Os.delete(k),k.stop(!1);let pe=!1;for(let ye=0;!pe&&ye<60;ye++){let Ir=Yc({routeEntries:x,listenPort:v,sslConfig:O,defaultTls:c,maxTlsContexts:F,originGuard:A,verbose:r});if(Ir){k=Ir,pe=!0;break}await new Promise((tn)=>setTimeout(tn,Math.min(25*2**Math.min(ye,4),500)))}if(!pe)b.error(`rpx: CRITICAL — could not rebind :${v} after cert issuance; HTTPS unbound until the next cert event or a gateway restart`)}}finally{Ge=!1}}if(!await Nr(P,"0.0.0.0",r)){let rt=new Set(x.map((pe)=>pe.host)),O=(pe)=>rt.has(pe)||[...rt].some((ye)=>Ur(pe,ye));hb(r,P,v,t.acmeChallengeWebroot,ee,O)}if(await Nr(v,"0.0.0.0",r)){if(m("proxies",`Port ${v} is already in use, cannot start shared proxy`,r),r)b.warn(`Port ${v} is in use. Shared HTTPS proxy cannot start.`);return}let St=ee&&ee.sniEntries().length>0?ee.sniEntries():u;if(k=Yc({routeEntries:x,listenPort:v,sslConfig:St,defaultTls:c,maxTlsContexts:F,originGuard:A,verbose:r}),!k){b.error(`Shared HTTPS proxy failed to bind :${v}; not exiting`);return}}else if(D){m("proxies",`Creating shared HTTP server for ${a.length} domains on port ${P}`,r);let x=await ab(a,i,r);if(await Nr(P,"0.0.0.0",r)){if(m("proxies",`Port ${P} is already in use, cannot start shared proxy`,r),r)b.warn(`Port ${P} is in use. Shared HTTP proxy cannot start.`);return}if(!Yc({routeEntries:x,listenPort:P,sslConfig:null,originGuard:A,verbose:r})){b.error(`Shared HTTP proxy failed to bind :${P}; not exiting`);return}}else for(let x of a)try{let k=x.to||"rpx.localhost";m("proxy",`Starting proxy for ${k} with SSL config: ${!!u}`,x.verbose),await fb({from:x.from||"localhost:5173",to:k,cleanUrls:x.cleanUrls||!1,https:x.https||!1,cleanup:x.cleanup||!1,vitePluginUsage:x.vitePluginUsage||!1,verbose:x.verbose||!1,_cachedSSLConfig:t._cachedSSLConfig,changeOrigin:x.changeOrigin||!1,loadBalancer:x.loadBalancer,auth:x.auth,path:x.path,pathRewrites:x.pathRewrites,httpPort:t.httpPort,httpsPort:t.httpsPort})}catch(k){m("proxies",`Failed to start proxy for ${x.to}: ${k}`,x.verbose),b.error(`Failed to start proxy for ${x.to}:`,k)}}async function ab(e,t,r){let n=[],i=new Set;for(let s of e){let o=s.to||"rpx.localhost",c=s.cleanUrls||!1,a=s.path,l=fr(a),u=os(s.auth);if(s.redirect){let f=rv(s.redirect);n.push({host:o,path:a,route:{redirect:f,basePath:l,auth:u}}),m("proxies",`Route: ${o}${a??""} → redirect ${f.status} ${f.to}${u?" (auth)":""}`,r)}else if(s.static)n.push({host:o,path:a,route:{static:yc(s.static,c),cleanUrls:c,basePath:l,auth:u}}),m("proxies",`Route: ${o}${a??""} → static ${typeof s.static==="string"?s.static:s.static.dir}${u?" (auth)":""}`,r);else{let f=qr(s.from),h=new URL(f.startsWith("http")?f:`http://${f}`),d=rs(s.from??h.host,s.loadBalancer);is(d),Jc.add(d),n.push({host:o,path:a,route:{sourceHost:h.host,upstreamPool:d,cleanUrls:c,changeOrigin:s.changeOrigin||!1,pathRewrites:s.pathRewrites,basePath:l,auth:u}}),m("proxies",`Route: ${o}${a??""} → ${h.host}${u?" (auth)":""}`,r)}if(i.has(o))continue;if(i.add(o),t&&!yn(o)&&!o.includes("localhost")&&!o.includes("127.0.0.1"))try{if(!(await Ss([o],r))[0])await xs([o],r)}catch{m("hosts",`Could not add hosts entry for ${o}`,r)}}return n}var ks=null;function mb(){if(q.env.RPX_BIND_HOSTNAME)return q.env.RPX_BIND_HOSTNAME;if(ks)return ks;try{ks=Object.values(Zc.networkInterfaces()).flat().some((r)=>r&&r.family==="IPv6")?"::":"0.0.0.0"}catch{ks="0.0.0.0"}return ks}function Yc(e){let{routeEntries:t,listenPort:r,sslConfig:n,defaultTls:i,maxTlsContexts:s,originGuard:o,verbose:c}=e,a=us(t),l=cs((h,d)=>fs(a,h,d),c),u=o?(h,d)=>o(h)??l(h,d):l,f=ls(c);try{let h=Bun.serve({port:r,hostname:mb(),reusePort:ti(),...n?{tls:Array.isArray(n)?ds({sni:n,defaultTls:i,maxTlsContexts:s,verbose:c}):si({key:n.key,cert:n.cert,ca:n.ca,requestCert:!1,rejectUnauthorized:!1})}:{},fetch(d,y){return u(d,y)},websocket:f,error(d){return m("server",`Shared proxy server error: ${d}`,c),new Response(`Server Error: ${d.message}`,{status:500})}});return Os.add(h),m("proxies",`Shared ${n?"HTTPS":"HTTP"} proxy listening on port ${r} for ${a.size} domains`,c),h}catch(h){return m("proxies",`Failed to start shared proxy: ${h}`,c),console.error("Failed to start shared proxy:",h),null}}function Ok(e){if(e?.vitePluginUsage||!e?.verbose)return;if(console.log(""),console.log(` ${Mt.green(Mt.bold("rpx"))} ${Mt.green(`v${Rs}`)}`),console.log(` ${Mt.green("➜")} ${Mt.dim(e?.from??"")} ${Mt.dim("➜")} ${Mt.cyan(e?.ssl?`https://${e?.to}`:`http://${e?.to}`)}`),e?.listenPort!==(e?.ssl?443:80))console.log(` ${Mt.green("➜")} Listening on port ${e?.listenPort}`);if(e?.cleanUrls)console.log(` ${Mt.green("➜")} Clean URLs enabled`)}fe();var Cd="/etc/rpx/certs",Ds="/etc/rpx/sites.d";function Fk(e,t){console.error(`[rpx gateway] SKIPPING malformed fragment ${e}; its host(s) will 404 until fixed: ${t.message}`)}async function Lk(e,t=Fk){let r=[];try{r=(await Nk(e)).filter((i)=>i.endsWith(".json")).sort()}catch{return[]}let n=[];for(let i of r)try{let s=JSON.parse(await Dk(gb.join(e,i),"utf8"));if(!s||typeof s!=="object"||Array.isArray(s))throw TypeError("fragment is not a JSON object");n.push({file:i,fragment:s})}catch(s){t(i,s instanceof Error?s:Error(String(s)))}return n}function Bk(e,t={}){let r=t.onWarning??((y)=>console.warn(`[rpx gateway] ${y}`)),n=[],i=new Set,s=new Map,o=new Set,c=new Set,a,l=t.certsDir??Cd,u,f,h=!1;for(let{file:y,fragment:g}of e){for(let p of Array.isArray(g.proxies)?g.proxies:[]){let w=p.id||`${p.to}${p.path??""}`;if(i.has(w)){r(`duplicate route ${w} in ${y} ignored; first declared by ${s.get(w)}`);continue}i.add(w),s.set(w,y),n.push(p)}for(let p of g.onDemandTls?.allowedSuffixes??[])o.add(p);if(a??=g.onDemandTls?.email,g.onDemandTls&&g.onDemandTls.staging!==!0)h=!0;if(g.productionCerts?.certsDir)l=g.productionCerts.certsDir;if(u??=g.acmeChallengeWebroot,g.originGuard&&g.originGuard.header&&g.originGuard.value)if(f??={header:g.originGuard.header,value:g.originGuard.value},g.originGuard.header===f.header&&g.originGuard.value===f.value)for(let p of g.originGuard.hosts??[])c.add(p);else r(`origin-guard secret in ${y} differs from the one already in force; its hosts stay unguarded rather than rejecting all traffic`)}let d={proxies:n,productionCerts:{certsDir:l,certsDirServerNames:[...new Set(n.map((y)=>y.to).filter(Boolean))]},https:!0,hostsManagement:!1,cleanup:{hosts:!1,certs:!1}};if(o.size>0)d.onDemandTls={enabled:!0,allowedSuffixes:[...o],email:a,certsDir:l,staging:!h};if(u)d.acmeChallengeWebroot=u;if(f)d.originGuard={header:f.header,value:f.value,hosts:[...c]};return d}async function Ik(e={}){let t=e.sitesDir??Ds,r=e.verbose??pb.env.RPX_VERBOSE!=="false",n=await Lk(t,e.onFragmentError);m("gateway",`merged ${n.length} fragment(s) from ${t}: ${n.map((o)=>o.file).join(", ")||"<none>"}`,r);let s={...Bk(n,{certsDir:e.certsDir}),verbose:r,singlePortMode:!0};if(e.https===!1)s.https=!1;if(e.httpPort!==void 0)s.httpPort=e.httpPort;if(e.httpsPort!==void 0)s.httpsPort=e.httpsPort;if(e.localCa)s.localCa=e.localCa;if(e.maxTlsContexts!==void 0)s.maxTlsContexts=e.maxTlsContexts;return s}async function yb(e={}){let t=await Ik(e);if(("proxies"in t&&Array.isArray(t.proxies)?t.proxies:[]).length===0&&!e.localCa)console.warn(`[rpx gateway] no routes found under ${e.sitesDir??Ds}; every request will answer 404 until a fragment is deployed`);await el(t)}fe();Fe.chdir(_k(Mk(jk(import.meta.url)),".."));var He=new fl("rpx");He.command("start","Start the Reverse Proxy Server").option("--from <from>","The URL to proxy from").option("--to <to>","The URL to proxy to").option("--key-path <path>","Absolute path to the SSL key").option("--cert-path <path>","Absolute path to the SSL certificate").option("--ca-cert-path <path>","Absolute path to the SSL CA certificate").option("--hosts-cleanup","Cleanup /etc/hosts on exit").option("--certs-cleanup","Cleanup SSL certificates on exit").option("--start-command <command>","Command to start the dev server").option("--start-cwd <path>","Current working directory for the dev server").option("--start-env <env>","Environment variables for the dev server").option("--change-origin","Change the origin of the host header to the target URL").option("--single-port-mode","Route all proxies through one shared listener (by Host header) instead of a port per proxy").option("--http-port <port>","Port for the shared HTTP listener / redirect (default: 80)").option("--https-port <port>","Port for the shared HTTPS listener (default: 443)").option("--via-daemon","Route through the shared rpx daemon instead of binding :443 directly").option("--id <id>","Stable id used when registering with the daemon (auto-derived from --to)").option("--verbose","Enable verbose logging").example("rpx start --from localhost:5173 --to my-project.localhost").example("rpx start --from localhost:3000 --to my-project.localhost/api").example("rpx start --from localhost:3000 --to localhost:3001").example("rpx start --from localhost:5173 --to my-project.test --key-path /absolute/path/to/key --cert-path /absolute/path/to/cert").example("rpx start --from localhost:5173 --to my-project.localhost --change-origin").example("rpx start --single-port-mode --https-port 8443").action(async(e)=>{if(!e?.from||!e.to)return el(Gt);let t={from:e.from,to:e.to,https:{keyPath:e.keyPath,certPath:e.certPath,caCertPath:e.caCertPath},cleanup:{certs:e.certsCleanup||!1,hosts:e.hostsCleanup||!1},verbose:e.verbose||!1,changeOrigin:e.changeOrigin||!1,singlePortMode:e.singlePortMode||!1,httpPort:e.httpPort!==void 0?Number(e.httpPort):void 0,httpsPort:e.httpsPort!==void 0?Number(e.httpsPort):void 0,viaDaemon:e.viaDaemon||!1,id:e.id};if(e.startCommand){let r={command:e.startCommand};if(e.startCwd)r.cwd=e.startCwd;if(e.startEnv)try{r.env=JSON.parse(e.startEnv)}catch(n){console.error("Failed to parse start-env JSON:",n),Fe.exit(1)}t.start=r}return db(t)});He.command("watch:start <proxy>","Start the dev server for a specific proxy").option("--verbose","Enable verbose logging").action(async(e,t)=>{let r=oc(Gt)?Gt.proxies.find((n)=>n.to===e||`${n.from}-${n.to}`===e):Gt.to===e?Gt:null;if(!r?.start)console.error(`No watch configuration found for proxy: ${e}`),Fe.exit(1);try{await Vc.startProcess(e,r.start,t.verbose),console.log(`Started dev server for ${e}`)}catch(n){console.error(`Failed to start dev server for ${e}:`,n),Fe.exit(1)}});He.command("watch:stop <proxy>","Stop the dev server for a specific proxy").option("--verbose","Enable verbose logging").action(async(e,t)=>{try{await Vc.stopProcess(e,t.verbose),console.log(`Stopped dev server for ${e}`)}catch(r){console.error(`Failed to stop dev server for ${e}:`,r),Fe.exit(1)}});He.command("watch:stopall","Stop all running dev servers").option("--verbose","Enable verbose logging").action(async(e)=>{try{await Vc.stopAll(e.verbose),console.log("Stopped all dev servers")}catch(t){console.error("Failed to stop all dev servers:",t),Fe.exit(1)}});function wb(e){if(e===void 0)return;let t=typeof e==="string"?Number.parseInt(e,10):e;if(!Number.isInteger(t)||t<0||t>65535)throw Error(`invalid port: ${JSON.stringify(e)}`);return t}function vb(e){return(e??"").split(",").map((t)=>t.trim()).filter(Boolean)}He.command("gateway","Serve every app fragment under a sites.d directory on :80/:443 (production gateway)").option("--sites-dir <path>",`Directory of per-app <slug>.json fragments (default ${Ds})`).option("--certs-dir <path>",`Fallback directory of real PEM certs when no fragment names one (default ${Cd})`).option("--no-https","Serve plain HTTP on the http port only; bind nothing on the https port").option("--http-port <port>","Shared HTTP port: redirect + ACME http-01, or the only listener with --no-https (default 80)").option("--https-port <port>","Shared HTTPS port (default 443)").option("--local-ca-dir <path>","LAN production mode: directory for a local Root CA and its leaf (enables localCa)").option("--local-ca-hosts <hosts>","Comma-separated LAN hostnames the local leaf must cover (e.g. pi-stacks.local)").option("--local-ca-ips <ips>","Comma-separated IP addresses the local leaf must cover (e.g. 192.168.1.20)").option("--install-trust","Install the local Root CA into the system trust store on start (needs root)").option("--max-tls-contexts <n>","Memory guard: most SNI certificates kept live (default 256)").option("--verbose","Enable verbose logging (default on; RPX_VERBOSE=false turns it off)").example("rpx gateway --sites-dir /etc/rpx/sites.d").example("rpx gateway --sites-dir /etc/rpx/sites.d --no-https --http-port 8080").example("rpx gateway --local-ca-dir /etc/rpx/local-ca --local-ca-hosts pi-stacks.local --local-ca-ips 192.168.1.20 --install-trust").action(async(e)=>{try{let t=vb(e.localCaHosts),r=vb(e.localCaIps);if((t.length>0||r.length>0||e.installTrust)&&!e.localCaDir)throw Error("--local-ca-hosts / --local-ca-ips / --install-trust need --local-ca-dir");if(e.localCaDir&&t.length===0)throw Error("--local-ca-dir needs at least one --local-ca-hosts entry");await yb({sitesDir:e.sitesDir??Ds,certsDir:e.certsDir,https:e.https!==!1,httpPort:wb(e.httpPort),httpsPort:wb(e.httpsPort),maxTlsContexts:e.maxTlsContexts===void 0?void 0:Number.parseInt(String(e.maxTlsContexts),10),localCa:e.localCaDir?{dir:e.localCaDir,hosts:t,ips:r,installTrust:e.installTrust===!0}:void 0,verbose:e.verbose===void 0?void 0:e.verbose})}catch(t){console.error(`Failed to start rpx gateway: ${t.message}`),Fe.exit(1)}});async function bb(e){let r={...(await ug().catch(()=>null))?.onDemand??{},enabled:!0};if(e){let n=e.split(",").map((i)=>i.trim()).filter(Boolean);if(n.length>0)r.roots=n}return r}He.command("daemon:start","Start the rpx daemon in the foreground (binds :443 + :80)").option("--rpx-dir <path>","Override the rpx state dir (default ~/.stacks/rpx)").option("--registry-dir <path>","Override the registry dir (default <rpx-dir>/registry.d)").option("--https-port <port>","HTTPS port (default 443)",{default:443}).option("--http-port <port>","HTTP redirect port; 0 to disable (default 80)",{default:80}).option("--hostname <host>","Bind address (default 0.0.0.0)",{default:"0.0.0.0"}).option("--certs-dir <path>","Directory of real PEM certs for per-domain SNI (<domain>.crt/.key, _wildcard.<apex>.crt/.key)").option("--workers <n>","Run as a multi-core cluster of N worker processes (default 1; also RPX_WORKERS)").option("--on-demand","Lazily boot a project's dev server the first time its host is visited (reads config `onDemand`)").option("--roots <dirs>","Comma-separated roots scanned for on-demand sites (overrides config; default ~/Code)").option("--verbose","Enable verbose logging").example("rpx daemon:start --on-demand").example("rpx daemon:start --on-demand --roots ~/Code,~/work").action(async(e)=>{try{let t=e.onDemand?await bb(e.roots):void 0;await(await eb({rpxDir:e.rpxDir,registryDir:e.registryDir,httpsPort:typeof e.httpsPort==="string"?Number.parseInt(e.httpsPort,10):e.httpsPort,httpPort:typeof e.httpPort==="string"?Number.parseInt(e.httpPort,10):e.httpPort,hostname:e.hostname,productionCerts:e.certsDir?{certsDir:e.certsDir}:void 0,workers:e.workers===void 0?void 0:typeof e.workers==="string"?Number.parseInt(e.workers,10):e.workers,onDemandSites:t,verbose:e.verbose??!0})).done,Fe.exit(0)}catch(t){console.error(`Failed to start rpx daemon: ${t.message}`),Fe.exit(1)}});He.command("daemon:stop","Stop the running rpx daemon (SIGTERM, escalates to SIGKILL)").option("--rpx-dir <path>","Override the rpx state dir (default ~/.stacks/rpx)").option("--timeout <ms>","Max ms to wait for graceful shutdown",{default:5000}).option("--no-force","Do not escalate to SIGKILL after timeout").option("--verbose","Enable verbose logging").action(async(e)=>{let t=typeof e.timeout==="string"?Number.parseInt(e.timeout,10):e.timeout,r=await tb({rpxDir:e.rpxDir,timeoutMs:t,forceAfterTimeout:e.force!==!1,verbose:e.verbose});if(!r.stopped&&r.pid===null){console.log("rpx daemon is not running");return}if(!r.stopped){console.log(`rpx daemon pid=${r.pid} was already gone (cleaned stale lock)`);return}console.log(`rpx daemon pid=${r.pid} stopped${r.forced?" (SIGKILL)":""}`)});He.command("daemon:status","Print daemon state and currently registered hosts").option("--rpx-dir <path>","Override the rpx state dir (default ~/.stacks/rpx)").option("--registry-dir <path>","Override the registry dir (default <rpx-dir>/registry.d)").option("--json","Emit machine-readable JSON instead of a human summary").action(async(e)=>{let t=e.rpxDir??De(),r=await Lr(t),n=await jc(t),i=e.registryDir,s=await xt(i).catch(()=>[]);if(e.json){console.log(JSON.stringify({running:n,pid:r,pidFile:Br(t),rpxDir:t,registryDir:i??dt(),entries:s},null,2));return}if(!n){if(console.log("rpx daemon: not running"),r!==null)console.log(`(stale pid file at ${Br(t)} → pid ${r})`)}else console.log(`rpx daemon: running (pid=${r})`),console.log(`pid file: ${Br(t)}`);if(console.log(`registry: ${i??dt()}`),s.length===0){console.log("no registered hosts");return}console.log(`registered hosts (${s.length}):`);for(let o of s){let c=o.pid!==void 0?`, pid=${o.pid}`:"";console.log(` https://${o.to} → ${o.from} (id=${o.id}${c})`)}});He.command("register","Register an upstream app with the rpx daemon").option("--id <id>","Unique id for this entry (a-z, 0-9, dot, dash, underscore)").option("--from <host:port>","Upstream host:port (e.g. localhost:5173)").option("--to <host>","Public hostname (e.g. pet-store.localhost)").option("--cwd <path>","Working directory of the upstream (informational)").option("--clean-urls","Strip .html and 301 to the clean URL").option("--change-origin","Rewrite Origin to the upstream").option("--rpx-dir <path>","Override the rpx state dir (default ~/.stacks/rpx)").option("--registry-dir <path>","Override the registry dir (default <rpx-dir>/registry.d)").option("--skip-spawn","Do not lazy-spawn the daemon if it is not already running").option("--verbose","Enable verbose logging").example("rpx register --id pet-store --from localhost:5173 --to pet-store.localhost").action(async(e)=>{if(!e.id||!e.from||!e.to)console.error("rpx register requires --id, --from, and --to"),Fe.exit(1);if(!Dr(e.id))console.error(`invalid id: ${JSON.stringify(e.id)} (must match /^[a-zA-Z0-9._-]+$/, ≤128 chars)`),Fe.exit(1);try{await ai({id:e.id,from:e.from,to:e.to,cwd:e.cwd,createdAt:new Date().toISOString(),cleanUrls:e.cleanUrls,changeOrigin:e.changeOrigin},e.registryDir,e.verbose)}catch(t){console.error(`failed to write registry entry: ${t.message}`),Fe.exit(1)}if(e.skipSpawn){console.log(`registered ${e.to} → ${e.from} (daemon spawn skipped)`);return}try{let t=await Uc({rpxDir:e.rpxDir,verbose:e.verbose}),r=t.spawned?"spawned":"attached to";console.log(`registered https://${e.to} → ${e.from} (${r} daemon pid=${t.pid})`)}catch(t){console.error(`registered entry but daemon spawn failed: ${t.message}`),console.error("the entry remains in the registry; start the daemon manually with `rpx daemon:start`"),Fe.exit(1)}});He.command("unregister <id>","Remove a previously registered app from the rpx daemon").option("--registry-dir <path>","Override the registry dir (default ~/.stacks/rpx/registry.d)").option("--verbose","Enable verbose logging").action(async(e,t)=>{if(!Dr(e))console.error(`invalid id: ${JSON.stringify(e)}`),Fe.exit(1);let r=await Zh(e,t.registryDir,t.verbose);if(!r){console.log(`no registry entry for id=${e}`);return}await Jr(e,t.registryDir,t.verbose),console.log(`unregistered ${r.to} (id=${e})`)});He.command("dns:reconcile","Remove stale macOS DNS overrides left by crashed dev sessions").option("--rpx-dir <path>","Override the rpx state dir (default ~/.stacks/rpx)").option("--verbose","Enable verbose logging").action(async(e)=>{let t=e.rpxDir??De();if(await jc(t)){console.log("rpx daemon is running — DNS overrides are managed by the daemon");return}await rb({rpxDir:t,verbose:e.verbose}),console.log("DNS reconcile complete")});He.command("sites","List the on-demand sites rpx can boot (and which are currently live)").option("--roots <dirs>","Comma-separated roots to scan (overrides config; default ~/Code)").option("--registry-dir <path>","Override the registry dir (default ~/.stacks/rpx/registry.d)").option("--json","Emit machine-readable JSON").action(async(e)=>{let t=await bb(e.roots),r=pg(t),n=await xt(e.registryDir).catch(()=>[]),i=new Set(n.map((c)=>c.to)),s=r.map((c)=>({host:c.host,dir:c.dir,command:c.command,source:c.source,live:i.has(c.host)}));if(e.json){console.log(JSON.stringify(s,null,2));return}let o=t.roots??["~/Code"];if(s.length===0){console.log("no on-demand sites found"),console.log(`scanned roots: ${o.join(", ")}`),console.log("start the daemon with `rpx daemon:start --on-demand` to serve them");return}console.log(`on-demand sites (${s.length}) — scanned ${o.join(", ")}:`);for(let c of s)console.log(` ${c.live?"●":"○"} https://${c.host} ${c.live?"(live)":"(idle)"}`),console.log(` ${c.command} ${c.dir}`)});He.command("logs <host>","Show an on-demand site's boot log (e.g. rpx logs myapp.localhost)").option("--rpx-dir <path>","Override the rpx state dir (default ~/.stacks/rpx)").option("--lines <n>","Show only the last N lines (default: all)").action(async(e,t)=>{let{readFile:r}=await import("node:fs/promises"),{join:n}=await import("node:path"),i=t.rpxDir??De(),s=n(i,"sites",`${xo(e.split(":")[0])}.log`),o;try{o=await r(s,"utf8")}catch{console.log(`no log for ${e} (it may not have been booted yet) — looked in ${s}`);return}let c=t.lines===void 0?void 0:typeof t.lines==="string"?Number.parseInt(t.lines,10):t.lines,a=o.split(`
|
|
387
387
|
`);Fe.stdout.write((c&&c>0?a.slice(-c):a).join(`
|
|
388
388
|
`))});He.command("version","Show the version of the Reverse Proxy CLI").action(()=>{console.log(Rs)});He.version(Rs);He.help();He.run();
|
package/dist/index.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
import{$ as De,$a as Qr,A as se,Aa as Fr,Ab as vo,B as nr,Ba as Hr,Bb as Co,C as k,Ca as Ne,Cb as $o,D as ie,Da as Ur,Db as To,E as Ce,Ea as _r,Eb as Ro,F as sr,Fa as kr,Fb as Oo,G as M,Ga as Mr,Gb as Do,H as ae,Ha as Nr,Hb as Eo,I as ir,Ia as Gr,Ib as Lo,J as ar,Ja as Br,Jb as Ao,K as lr,Ka as U,Kb as Fo,L as le,La as Ir,Lb as Ho,M as ce,Ma as Ge,Mb as ye,N as cr,Na as jr,Nb as Uo,O as pe,Oa as me,Ob as _o,P as pr,Pa as ge,Q as $e,Qa as Wr,R as ur,Ra as he,S as Te,Sa as xe,T as dr,Ta as zr,U as fr,Ua as Xr,V as mr,Va as Yr,W as gr,Wa as qr,X as hr,Xa as Vr,Y as Re,Ya as Kr,Z as Oe,Za as Jr,_ as xr,_a as Zr,a as u,aa as Ee,ab as eo,b as ne,ba as I,bb as to,c as Ht,ca as yr,cb as ro,d as Ut,da as Le,db as oo,e as _t,ea as wr,eb as N,f as kt,fa as Ae,fb as Be,g as Mt,ga as Pr,gb as G,h as Nt,ha as Fe,hb as no,i as Gt,ia as Sr,ib as so,j as Bt,ja as br,jb as io,k as It,ka as He,kb as ao,l as jt,la as Ue,lb as lo,m as Wt,ma as vr,mb as co,n as zt,na as _e,nb as po,o as Xt,oa as tt,ob as uo,p as Yt,pa as Cr,pb as fo,q as qt,qa as $r,qb as mo,r as Vt,ra as ke,rb as go,s as Kt,sa as Tr,sb as ho,t as Jt,ta as Rr,tb as xo,u as Zt,ua as Or,v as Qt,va as Dr,vb as yo,w as er,wa as Er,wb as wo,x as tr,xa as Lr,xb as Po,y as rr,ya as Me,yb as So,z as or,za as Ar,zb as bo}from"./chunk-3tg7caak.js";import{$b as Ss,Qb as ve,Rb as Qe,Sb as gs,Tb as hs,Ub as xs,Vb as o,Wb as et,Xb as ys,Yb as F,Zb as ws,_b as Ps,ac as bs,bc as vs,cc as Cs,dc as $s,ec as Ts,fc as Rs}from"./chunk-pcyc92vj.js";import{execSync as vt}from"node:child_process";import*as ue from"node:http";import*as rt from"node:net";import*as de from"node:os";import*as ot from"node:path";import*as y from"node:process";var V=(t,e)=>(r)=>`\x1B[${t}m${r}\x1B[${e}m`,R={bold:V(1,22),dim:V(2,22),green:V(32,39),cyan:V(36,39)};import*as Ye from"node:fs";import*as qe from"node:path";import*as E from"node:process";function Ve(t,e){let n=(e&&e!=="/"?`${t}${e}`:t).replace(/[^a-zA-Z0-9._-]+/g,"-").replace(/^-+|-+$/g,"").slice(0,128);return n.length>0?n:"rpx"}async function K(t){if(t.proxies.length===0)throw Error("runViaDaemon: no proxies provided");let e=t.verbose??!1,r=t.registryDir,n=new Set,l=t.proxies.map((p)=>{let g=p.id??Ve(p.to,p.path);if(!ge(g))throw Error(`invalid registry id "${g}" derived from to="${p.to}"`);if(n.has(g))throw Error(`duplicate registry id "${g}" — set an explicit \`id\` on one of the proxies`);return n.add(g),{...p,id:g}}),a=new Date().toISOString();for(let p of l)await he({id:p.id,from:p.from,to:p.to,path:p.path,pid:t.persistent?void 0:E.pid,cwd:E.cwd(),createdAt:a,cleanUrls:p.cleanUrls,changeOrigin:p.changeOrigin,pathRewrites:p.pathRewrites,static:p.static,loadBalancer:p.loadBalancer},r,e);let s=await ye({rpxDir:t.rpxDir,verbose:e,spawnCommand:t.spawnCommand,startupTimeoutMs:t.startupTimeoutMs,spawnEnv:t.spawnEnv});for(let p of l){let g=p.static?`static ${typeof p.static==="string"?p.static:p.static.dir}`:p.from;u.success(`https://${p.to} → ${g}`)}if(u.info(`(via rpx daemon pid=${s.pid}; \`rpx daemon:status\` to inspect)`),t.detached)return;let f=!1,c=r??me(),d=l.map((p)=>p.id),h=async()=>{if(f)return;f=!0;for(let p of d)await xe(p,r,e).catch((g)=>{o("runner",`removeEntry(${p}) failed: ${g}`,e)})},m=(p)=>{o("runner",`received ${p}, unregistering ${d.length} entries`,e),h().finally(()=>E.exit(0))};E.once("SIGINT",m),E.once("SIGTERM",m),E.once("exit",()=>{if(f)return;for(let p of d)try{Ye.unlinkSync(qe.join(c,`${p}.json`))}catch{}}),await new Promise(()=>{})}import{spawn as xt}from"node:child_process";import*as J from"node:process";class Z{processes=new Map;isShuttingDown=!1;async startProcess(t,e,r){if(this.processes.has(t)){o("start",`Process ${t} is already running`,r);return}let[n,...l]=e.command.split(" "),a=e.cwd||J.cwd();o("start",`Starting process ${t}:`,r),o("start",` Command: ${n} ${l.join(" ")}`,r),o("start",` Working directory: ${a}`,r),o("start",` Environment variables: ${F(e.env)}`,r);let s=xt(n,l,{cwd:a,env:{...J.env,...e.env},shell:!0,stdio:"inherit"});return this.processes.set(t,{command:e.command,cwd:a,process:s,env:e.env}),new Promise((f,c)=>{if(s.on("error",(d)=>{if(!this.isShuttingDown)o("start",`Process ${t} failed to start: ${d}`,r),this.processes.delete(t),c(d)}),s.on("exit",(d)=>{if(!this.isShuttingDown&&d!==null&&d!==0)o("start",`Process ${t} exited with code ${d}; leaving the proxy running`,r),this.processes.delete(t),c(Error(`Process ${t} exited with code ${d}`))}),r)s.stdout?.on("data",(d)=>{o("process",`[${t}] ${d.toString().trim()}`,!0)}),s.stderr?.on("data",(d)=>{o("process",`[${t}] ERR: ${d.toString().trim()}`,!0)});setTimeout(()=>{if(!this.isShuttingDown&&s.killed)this.processes.delete(t),c(Error(`Process ${t} was killed during startup`));else o("start",`Process ${t} started successfully`,r),f()},1000)})}async stopProcess(t,e){let r=this.processes.get(t);if(!r?.process){o("start",`No process found for ${t}`,e);return}return o("start",`Stopping process ${t}`,e),new Promise((n)=>{if(!r.process){n();return}r.process.once("exit",()=>{this.processes.delete(t),o("start",`Process ${t} stopped`,e),n()});try{r.process.kill("SIGTERM"),setTimeout(()=>{if(r.process){o("start",`Force killing process ${t}`,e);try{r.process.kill("SIGKILL")}catch(l){}}},3000)}catch(l){o("start",`Error stopping process ${t}: ${l}`,e),this.processes.delete(t),n()}})}async stopAll(t){if(this.isShuttingDown){o("start","Already shutting down, skipping duplicate stopAll call",t);return}this.isShuttingDown=!0,o("start","Stopping all processes",t);let e=Array.from(this.processes.keys()).map((r)=>this.stopProcess(r,t).catch((n)=>{u.error(`Failed to stop process ${r}:`,n)}));await Promise.allSettled(e),this.processes.clear(),this.isShuttingDown=!1}isRunning(t){let e=this.processes.get(t);return!!e?.process&&!e.process.killed}}var qo=new Z;import{timingSafeEqual as yt}from"node:crypto";function wt(t,e){if(t==null)return!1;let r=Buffer.from(t),n=Buffer.from(e);if(r.length!==n.length)return!1;return yt(r,n)}function Q(t){return t.toLowerCase().replace(/\.$/,"")}var Pt=["/.well-known/acme-challenge/"];function St(t){let e=t.headers.get("host");if(e)return Q(e.split(":")[0]);try{return Q(new URL(t.url).hostname)}catch{return""}}function we(t){let e=t.header.toLowerCase(),r=new Set,n=[];for(let c of t.hosts){let d=Q(c);if(d.startsWith("*."))n.push(d);else r.add(d)}let l=t.exemptPaths??Pt,a=t.forbiddenMessage??`Forbidden: direct origin access is not allowed; requests must arrive via the CDN.
|
|
2
|
-
`,s=(c)=>{let d=Q(c);return r.has(d)||n.some((h)=>I(d,h))},f=(c)=>{let d=St(c);if(!s(d))return;let h="/";try{h=new URL(c.url).pathname}catch{}if(l.some((m)=>h.startsWith(m)))return;if(wt(c.headers.get(e),t.value))return;return new Response(a,{status:403,headers:{"content-type":"text/plain"}})};return f.protects=s,f}var Ke={name:"@stacksjs/rpx",type:"module",version:"0.11.48",description:"A modern and smart reverse proxy.",author:"Chris Breuer <chris@stacksjs.org>",license:"MIT",homepage:"https://github.com/stacksjs/rpx",repository:{type:"git",url:"git+https://github.com/stacksjs/rpx.git"},bugs:{url:"https://github.com/stacksjs/rpx/issues"},keywords:["reverse proxy","ssl","development","environment","proxy","bun","stacks","typescript","javascript"],exports:{".":{types:"./dist/index.d.ts",bun:"./dist/index.js",import:"./dist/index.js"}},module:"./dist/index.js",types:"./dist/index.d.ts",bin:{rpx:"./dist/bin/cli.js","reverse-proxy":"./dist/bin/cli.js"},files:["README.md","dist"],scripts:{build:"bun build.ts && bun build --production ./bin/cli.ts --compile --outfile bin/rpx",compile:"bun build --production ./bin/cli.ts --compile --outfile bin/rpx","compile:all":"bun run compile:linux-x64 && bun run compile:linux-arm64 && bun run compile:windows-x64 && bun run compile:darwin-x64 && bun run compile:darwin-arm64","compile:linux-x64":"bun build --production ./bin/cli.ts --compile --target=bun-linux-x64 --outfile bin/rpx-linux-x64","compile:linux-arm64":"bun build --production ./bin/cli.ts --compile --target=bun-linux-arm64 --outfile bin/rpx-linux-arm64","compile:windows-x64":"bun build --production ./bin/cli.ts --compile --target=bun-windows-x64 --outfile bin/rpx-windows-x64.exe","compile:darwin-x64":"bun build --production ./bin/cli.ts --compile --target=bun-darwin-x64 --outfile bin/rpx-darwin-x64","compile:darwin-arm64":"bun build --production ./bin/cli.ts --compile --target=bun-darwin-arm64 --outfile bin/rpx-darwin-arm64",bench:"bun run bench/run.ts","bench:html":"bun run bench/run.ts --html","bench:latency":"bun run bench/run.ts --latency","bench:throughput":"bun run bench/run.ts --throughput",lint:"bunx --bun pickier .","lint:fix":"bunx --bun pickier . --fix",fresh:"bunx rimraf node_modules/ bun.lock && bun i",changelog:"changelogen --output CHANGELOG.md",prepublishOnly:"bun build.ts","release:binaries":"bun run compile:all && bun run zip",test:"bun test",typecheck:"bunx tsc --noEmit",zip:"bun run zip:all","zip:all":"bun run zip:linux-x64 && bun run zip:linux-arm64 && bun run zip:windows-x64 && bun run zip:darwin-x64 && bun run zip:darwin-arm64","zip:linux-x64":"zip -j bin/rpx-linux-x64.zip bin/rpx-linux-x64","zip:linux-arm64":"zip -j bin/rpx-linux-arm64.zip bin/rpx-linux-arm64","zip:windows-x64":"zip -j bin/rpx-windows-x64.zip bin/rpx-windows-x64.exe","zip:darwin-x64":"zip -j bin/rpx-darwin-x64.zip bin/rpx-darwin-x64","zip:darwin-arm64":"zip -j bin/rpx-darwin-arm64.zip bin/rpx-darwin-arm64"},dependencies:{"@stacksjs/clapp":"^0.2.10","@stacksjs/tlsx":"^0.13.19"},devDependencies:{bunfig:"^0.15.6",mitata:"^1.0.34",typescript:"^7.0.2"},"simple-git-hooks":{"pre-commit":"bunx lint-staged"},"lint-staged":{"*.{js,ts}":"bunx --bun pickier . --fix"}};var Je=Ke.version;var te=new Z,Ct=new Ge("0.0.0.0"),W=new Set,oe=new Set,Pe=!1,ee=null,Se=null;async function X(t){if(Pe)return o("cleanup","Cleanup already in progress, skipping",t?.verbose),Se||Promise.resolve();Pe=!0,o("cleanup","Starting cleanup process",t?.verbose),Se=new Promise((e)=>{ee=e});try{await te.stopAll(t?.verbose),u.info("Shutting down proxy servers...");let e=[],r=Array.from(W).map((n)=>new Promise((l)=>{let a=n;try{if(typeof a.stop==="function")a.stop(!0),o("cleanup","Bun server stopped",t?.verbose),l();else if(typeof a.close==="function")a.close(()=>{o("cleanup","Server closed successfully",t?.verbose),l()});else l()}catch(s){o("cleanup",`Error stopping server: ${s}`,t?.verbose),l()}}));e.push(...r),W.clear();for(let n of oe)ce(n);if(oe.clear(),t?.hosts&&t.domains?.length){o("cleanup","Cleaning up hosts file entries",t?.verbose),o("cleanup",`Original domains for cleanup: ${JSON.stringify(t.domains)}`,t?.verbose);let n=t.domains.filter((l)=>{if(l==="test.local")return!0;return l!=="localhost"&&!l.startsWith("localhost.")&&l!=="127.0.0.1"});if(o("cleanup",`Filtered domains for cleanup: ${JSON.stringify(n)}`,t?.verbose),n.length>0)u.info("Cleaning up hosts file entries..."),e.push(Be(n,t?.verbose).then(()=>{o("cleanup",`Removed hosts entries for ${n.join(", ")}`,t?.verbose)}).catch((l)=>{o("cleanup",`Failed to remove hosts entries: ${l}`,t?.verbose),u.warn(`Failed to clean up hosts file entries for ${n.join(", ")}:`,l)}))}if(t?.certs&&t.domains?.length){o("cleanup","Cleaning up SSL certificates",t?.verbose),u.info("Cleaning up SSL certificates...");let n=t.domains.map(async(l)=>{try{await Ce(l,t?.verbose),o("cleanup",`Removed certificates for ${l}`,t?.verbose)}catch(a){o("cleanup",`Failed to remove certificates for ${l}: ${a}`,t?.verbose),u.warn(`Failed to clean up certificates for ${l}:`,a)}});e.push(...n)}await Promise.allSettled(e),o("cleanup","All cleanup tasks completed successfully",t?.verbose),u.success("All cleanup tasks completed successfully")}catch(e){o("cleanup",`Error during cleanup: ${e}`,t?.verbose),u.error("Error during cleanup:",e)}finally{if(ee)ee();ee=null,Pe=!1;let e=t&&"vitePluginUsage"in t&&t.vitePluginUsage===!0;if(y.env.NODE_ENV!=="test"&&y.env.BUN_ENV!=="test"&&!e)y.exit(0)}return Se}var be=!1;function nt(t){if(be){o("signal",`Received second ${t} signal, forcing exit`,!0),y.exit(1);return}be=!0,o("signal",`Received ${t} signal, initiating cleanup`,!0),X().catch((e)=>{o("signal",`Cleanup failed after ${t}: ${e}`,!0),y.exit(1)}).finally(()=>{be=!1})}y.once("SIGINT",()=>nt("SIGINT"));y.once("SIGTERM",()=>nt("SIGTERM"));y.on("uncaughtException",(t)=>{u.error("Uncaught exception (continuing):",t)});y.on("unhandledRejection",(t)=>{u.error("Unhandled rejection (continuing):",t)});async function z(t,e,r,n=5){o("connection",`Testing connection to ${t}:${e} (retries left: ${n})`,r);let l=15000,a=Date.now();if(y.env.RPX_BYPASS_CONNECTION_TEST==="true"){o("connection",`Bypassing connection test for ${t}:${e} due to RPX_BYPASS_CONNECTION_TEST flag`,r);return}let s=()=>new Promise((f,c)=>{let d=rt.connect({host:t,port:e,timeout:3000});d.once("connect",()=>{o("connection",`Successfully connected to ${t}:${e}`,r),d.end(),f()}),d.once("timeout",()=>{o("connection",`Connection to ${t}:${e} timed out`,r),d.destroy(),c(Error("Connection timed out"))}),d.once("error",(h)=>{o("connection",`Failed to connect to ${t}:${e}: ${h}`,r),d.destroy(),c(h)})});try{await s()}catch(f){let c=f;if(Date.now()-a>l){o("connection",`Connection test timed out after ${l}ms, but continuing anyway`,r),u.warn(`Connection test to ${t}:${e} timed out, but RPX will try to proceed anyway.`);return}if(c.code==="ECONNREFUSED"&&n>0)return o("connection",`Connection refused, server might be starting up. Retrying in 2 seconds... (${n} retries left)`,r),await new Promise((h)=>setTimeout(h,2000)),z(t,e,r,n-1);if(n>0)try{o("connection",`Trying HTTP request to ${t}:${e}`,r),await new Promise((h,m)=>{let p=ue.request({hostname:t,port:e,path:"/",method:"HEAD",timeout:5000},(g)=>{o("connection",`Received HTTP response with status: ${g.statusCode}`,r),h()});p.on("error",(g)=>m(g)),p.on("timeout",()=>{p.destroy(),m(Error("HTTP request timed out"))}),p.end()}),o("connection",`HTTP request to ${t}:${e} succeeded`,r);return}catch(h){return o("connection",`HTTP request to ${t}:${e} failed: ${h}`,r),o("connection",`Retrying socket connection in 2 seconds... (${n} retries left)`,r),await new Promise((m)=>setTimeout(m,2000)),z(t,e,r,n-1)}let d=`Failed to connect to ${t}:${e} after ${5-n} attempts: ${c.message}`;o("connection",`${d}. To bypass this check set RPX_BYPASS_CONNECTION_TEST=true`,r),u.warn(d),u.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 Ie(t){o("server",`Starting server with options: ${F(t)}`,t.verbose);let e=M(t.from),r=new URL(e.startsWith("http")?e:`http://${e}`),n=new URL((t.to?.startsWith("http")?t.to:`http://${t.to}`)||"rpx.localhost"),l=Number.parseInt(r.port)||(r.protocol.includes("https:")?443:80),a=[n.hostname];if(je(t)&&!n.hostname.includes("localhost")&&!n.hostname.includes("127.0.0.1")){o("hosts",`Checking if hosts file entry exists for: ${n.hostname}`,t?.verbose);try{if(!(await G(a,t.verbose))[0]){u.info(`Adding ${n.hostname} to hosts file...`),u.info("This may require sudo/administrator privileges");try{await N(a,t.verbose)}catch(c){if(u.error("Failed to add hosts entry:",c.message),u.warn("You can manually add this entry to your hosts file:"),u.warn(`127.0.0.1 ${n.hostname}`),u.warn(`::1 ${n.hostname}`),y.platform==="win32")u.warn("On Windows:"),u.warn("1. Run notepad as administrator"),u.warn("2. Open C:\\Windows\\System32\\drivers\\etc\\hosts");else u.warn("On Unix systems:"),u.warn("sudo nano /etc/hosts")}}else o("hosts",`Host entry already exists for ${n.hostname}`,t.verbose)}catch(f){u.error("Failed to check hosts file:",f.message)}}try{await z(r.hostname,l,t.verbose)}catch(f){o("server",`Connection test failed: ${f}`,t.verbose),u.error(f.message),u.warn("Continuing with proxy setup despite connection test failure..."),u.info("If you need to bypass connection testing, set environment variable RPX_BYPASS_CONNECTION_TEST=true")}let s=t._cachedSSLConfig||null;if(t.https)try{if(t.https===!0)t.https=ie({...t,to:n.hostname});if(s=await k({...t,to:n.hostname,https:t.https}),!s){if(o("ssl",`Generating new certificates for ${n.hostname}`,t.verbose),await se({...t,from:r.toString(),to:n.hostname,https:t.https}),s=await k({...t,to:n.hostname,https:t.https}),!s)throw Error(`Failed to load SSL configuration after generating certificates for ${n.hostname}`)}}catch(f){throw o("server",`SSL setup failed: ${f}`,t.verbose),f}o("server",`Setting up reverse proxy with SSL config for ${n.hostname}`,t.verbose),await Tt({...t,from:e,originalFrom:t.from||e,to:n.hostname,fromPort:l,sourceUrl:{hostname:r.hostname,host:r.host},ssl:s})}async function $t(t,e,r,n,l,a,s,f,c,d,h,m){o("proxy",`Creating proxy server ${t} -> ${e} with cleanUrls: ${f}`,s);let p=ae(h??n.host,m);le(p);let g=[{host:e,route:{sourceHost:n.host,upstreamPool:p,cleanUrls:f||!1,changeOrigin:c||!1,basePath:"/",auth:d}}];if(!re({routeEntries:g,listenPort:r,sslConfig:l,originGuard:null,verbose:s??!1}))throw ce(p),Error(`Failed to start proxy server for ${e} on port ${r}`);oe.add(p),Dt({from:t,to:e,vitePluginUsage:a,listenPort:r,ssl:!!l,cleanUrls:f,verbose:s})}async function Tt(t){o("setup",`Setting up reverse proxy: ${F(t)}`,t.verbose);let{from:e,originalFrom:r,to:n,sourceUrl:l,ssl:a,verbose:s,cleanup:f,vitePluginUsage:c,changeOrigin:d,cleanUrls:h}=t,m=t.httpPort??80,p=t.httpsPort??443,g=it(),v=t.portManager||Ct,S=je(t);try{if(S&&n&&!n.includes("localhost")&&!n.includes("127.0.0.1")){if(!(await G([n],s))[0]){u.warn(`The hostname ${n} isn't in your hosts file. Adding it now...`);try{await N([n],s),u.success(`Added ${n} to your hosts file.`)}catch(H){u.error(`Failed to add ${n} to your hosts file: ${H}`),u.info(`You may need to manually add '127.0.0.1 ${n}' to your /etc/hosts file.`)}}}else if(S&&y.platform!=="darwin"&&n&&n.includes("localhost")&&!n.match(/^(localhost|127\.0\.0\.1)$/)){if(!(await G([n],s))[0]){o("hosts",`${n} not found in hosts file, adding...`,s);try{await N([n],s)}catch(H){o("hosts",`Failed to add ${n} to hosts file: ${H}`,s)}}}if(a&&!v.usedPorts.has(m)){if(!await U(m,g,s))o("setup","Starting HTTP redirect server",s),st(s,m,p),v.usedPorts.add(m);else if(o("setup","Port 80 is in use, skipping HTTP redirect",s),s)u.warn("Port 80 is in use, HTTP to HTTPS redirect will not be available")}let P=a?p:m,O=await U(P,g,s),b;if(O){if(o("setup",`Port ${P} is already in use`,s),s)u.warn(`Port ${P} is already in use. This may be another instance of rpx or another service.`);if(P===443){if(b=await v.getNextAvailablePort(3443,!0),o("setup",`Using port ${b} instead of ${P}`,s),s)u.info(`Using port ${b} instead. Access your site at https://${n}:${b}`)}else if(b=await v.getNextAvailablePort(P+1000,!0),o("setup",`Using port ${b} instead of ${P}`,s),s)u.info(`Using port ${b} instead. Access your site at http://${n}:${b}`)}else b=P,v.usedPorts.add(b),o("setup",`Using standard ${P===443?"HTTPS":"HTTP"} port ${P} for ${n}`,s);await $t(e,n,b,l,a,c,s,h,d,pe(t.auth),r,t.loadBalancer)}catch(P){o("setup",`Setup failed: ${P}`,s),u.error(`Failed to setup reverse proxy: ${P.message}`),X({domains:[n],hosts:typeof f==="boolean"?f:f?.hosts,certs:typeof f==="boolean"?f:f?.certs,verbose:s,vitePluginUsage:c})}}function st(t,e=80,r=443,n,l,a){o("redirect",`Starting HTTP redirect server on port ${e}`,t);let s=ue.createServer((f,c)=>{let d=f.url?f.url.split("?",1)[0]:"";if(d.startsWith("/.well-known/acme-challenge/")){if(l){let g=l.challengeStore.handlePath(d);if(g!==void 0){o("redirect",`Serving on-demand ACME challenge ${d}`,t),c.writeHead(200,{"content-type":"text/plain"}),c.end(g);return}}if(n){let g=De(n,d);if(g!=null){o("redirect",`Serving ACME challenge ${d}`,t),c.writeHead(200,{"content-type":"text/plain"}),c.end(g);return}}if(l){c.writeHead(404,{"content-type":"text/plain"}),c.end("challenge not found");return}}let h=f.headers.host||"",m=h.includes(":")?h.slice(0,h.indexOf(":")):h;if(l&&m&&!l.hasCert(m)&&(!a||a(m)))l.ensureCert(m).catch(()=>{});let p=r===443?m:`${m}:${r}`;o("redirect",`Redirecting request from ${h}${f.url} to https://${p}`,t),c.writeHead(301,{Location:`https://${p}${f.url}`}),c.end()}).listen(e);W.add(s),o("redirect","HTTP redirect server started",t)}function Rt(t){let e={...ne,...t};if(o("proxy",`Starting proxy with options: ${F(e)}`,e?.verbose),e.viaDaemon){if(!e.from||!e.to){u.error("viaDaemon mode requires both `from` and `to`");return}K({proxies:[{id:e.id,from:e.from,to:e.to,path:e.path,cleanUrls:e.cleanUrls,changeOrigin:e.changeOrigin,pathRewrites:e.pathRewrites}],verbose:e.verbose}).catch((c)=>{u.error(`Failed to register with rpx daemon: ${c.message}`),y.exit(1)});return}let r=e.to||"",n=r.split(".").pop()?.toLowerCase()||"",l=y.platform==="darwin"&&r&&!r.includes("localhost")&&!r.includes("127.0.0.1"),a=["dev","app","page","new","day","foo"],s=["test","localhost","local","example","invalid"];if(l&&a.includes(n)&&e?.verbose)u.warn(`The .${n} TLD may not work reliably for local development`),u.info(` Google owns .${n} with HSTS preloading, which can bypass local DNS`),u.info(" Consider using a reserved TLD: .test, .localhost, or .local");if(l)import("./chunk-handw2xw.js").then(({setupDevelopmentDns:c})=>{c({domains:[r],verbose:e.verbose}).then((d)=>{if(d)Promise.resolve().then(()=>{if(e.verbose)if(s.includes(n))u.success(`DNS server started for .${n} domains`);else u.success(`DNS server started for .${n} domains (hosts file entry also added)`)});else o("dns",`Could not start DNS server - ${r} may not resolve in browser`,e.verbose)})}).catch((c)=>{o("dns",`Failed to start DNS server: ${c}`,e.verbose)});let f={from:e.from,to:e.to,cleanUrls:e.cleanUrls,https:ie(e),cleanup:e.cleanup,vitePluginUsage:e.vitePluginUsage,changeOrigin:e.changeOrigin,verbose:e.verbose,regenerateUntrustedCerts:e.regenerateUntrustedCerts};o("proxy",`Server options: ${F(f)}`,e.verbose),Ie(f).catch((c)=>{o("proxy",`Failed to start proxy: ${c}`,e.verbose),u.error(`Failed to start proxy: ${c.message}`),X({domains:[e.to],hosts:typeof e.cleanup==="boolean"?e.cleanup:e.cleanup?.hosts,certs:typeof e.cleanup==="boolean"?e.cleanup:e.cleanup?.certs,verbose:e.verbose})})}function Ot(t){return t?.verbose||!1}function je(t){if(t?.hostsManagement===!1)return!1;let e=t?.cleanup;if(e===!1)return!1;if(e&&typeof e==="object"&&e.hosts===!1)return!1;return!0}async function Y(t){let e={from:"localhost:5173",to:"rpx.localhost",https:!1,cleanup:{hosts:!0,certs:!1},vitePluginUsage:!1,verbose:!1,cleanUrls:!1,changeOrigin:!1,regenerateUntrustedCerts:!0};if(t)e={...e,...t};let r=Ot(e),n=e.https===!1;if(e.localCa)Me(e.localCa,e.onDemandTls);let l=je(e);if(o("config",`Starting with config: ${F(e,2)}`,r),o("config",`Is multi-proxy? ${"proxies"in e}`,r),o("config",`Hosts management enabled? ${l}`,r),e.viaDaemon){let x="proxies"in e&&Array.isArray(e.proxies)?e.proxies.map((w)=>({id:w.id,from:w.from,to:w.to,path:w.path,cleanUrls:w.cleanUrls??e.cleanUrls,changeOrigin:w.changeOrigin??e.changeOrigin,pathRewrites:w.pathRewrites})):[{id:e.id,from:e.from,to:e.to??"rpx.localhost",path:e.path,cleanUrls:e.cleanUrls,changeOrigin:e.changeOrigin,pathRewrites:e.pathRewrites}];await K({proxies:x,verbose:r});return}if("proxies"in e&&Array.isArray(e.proxies)){o("servers",`Found ${e.proxies.length} proxies in config`,r);for(let i of e.proxies)if(i.start){let x=`${i.from}-${i.to}`;try{o("watch",`Starting command for ${x} with command: ${i.start.command}`,r),u.info(`Starting command for ${x}...`),await te.startProcess(x,i.start,r);let w=M(i.from),C=new URL(w.startsWith("http")?w:`http://${w}`),D=C.hostname||"localhost",L=Number(C.port)||80;try{await z(D,L,r),o("watch",`Dev server is ready at ${D}:${L}`,r)}catch(fe){o("watch",`Connection check failed, but continuing with proxy setup: ${fe}`,r),u.warn("Dev server connection check failed. RPX will try to proceed anyway...")}}catch(w){throw o("watch",`Failed to start command for ${x}: ${w}`,r),Error(`Failed to start command for ${x}: ${w}`)}}else o("watch",`No start command for proxy ${i.from} -> ${i.to}`,r)}else if("start"in e&&e.start){o("watch","Found start command in single proxy config",r);let i=`${e.from}-${e.to}`;try{if(e.start)o("watch",`Starting command: ${e.start.command}`,r),await te.startProcess(i,e.start,r);let x=M(e.from),w=new URL(x.startsWith("http")?x:`http://${x}`),C=w.hostname||"localhost",D=Number(w.port)||80;try{await z(C,D,r),o("watch",`Dev server is ready at ${C}:${D}`,r)}catch(L){o("watch",`Connection check failed, but continuing with proxy setup: ${L}`,r),u.warn("Dev server connection check failed. RPX will try to proceed anyway...")}}catch(x){throw o("watch",`Failed to run start command: ${x}`,r),Error(`Failed to run start command: ${x}`)}}else o("watch","No start command found in config",r);let a="proxies"in e&&Array.isArray(e.proxies)?e.proxies[0]?.to:("to"in e)?e.to:"rpx.localhost";if(y.platform!=="win32"&&(e.https||l)){if(!Qe())try{o("sudo","Pre-acquiring sudo credentials for privileged operations",r),vt("sudo -v",{stdio:"inherit"})}catch{o("sudo","Could not pre-acquire sudo credentials",r)}}let s=[],f=null;if(e.productionCerts&&!n){if(s=await _e(e.productionCerts,r),s.length>0)o("ssl",`Using ${s.length} production SNI cert(s): ${s.map((i)=>i.serverName).join(", ")}`,r)}if(e.localCa&&!n){let i=await Ne(e.localCa,{verbose:r,onDemandTls:e.onDemandTls}),x=new Set(i.entries.map((w)=>w.serverName));s=[...i.entries,...s.filter((w)=>!x.has(w.serverName))],f=i.defaultTls,o("ssl",`Local CA: ${i.leafMinted?`minted leaf (${i.renewalReason})`:"reusing leaf"} for ${[...x].join(", ")}; valid until ${i.notAfter.toISOString()}`,r)}if(e.https){let i=s.length>0?null:await k(e);if(!i&&s.length===0){if(o("ssl",`No valid or trusted certificates found for ${a}, generating new ones`,e.verbose),await se(e),i=await k(e),!i)throw Error(`Failed to load SSL certificates after generation for ${a}`)}else o("ssl",`Using existing and trusted certificates for ${a}`,e.verbose);e._cachedSSLConfig=i}let c="proxies"in e&&Array.isArray(e.proxies)?e.proxies.map((i)=>({...i,https:e.https,cleanup:e.cleanup,cleanUrls:i.cleanUrls??("cleanUrls"in e?e.cleanUrls:!1),vitePluginUsage:e.vitePluginUsage,changeOrigin:i.changeOrigin??e.changeOrigin,verbose:r,_cachedSSLConfig:e._cachedSSLConfig})):[{from:"from"in e?e.from:"localhost:5173",to:"to"in e?e.to:"rpx.localhost",cleanUrls:"cleanUrls"in e?e.cleanUrls:!1,https:e.https,cleanup:e.cleanup,vitePluginUsage:e.vitePluginUsage,start:"start"in e?e.start:void 0,changeOrigin:e.changeOrigin,auth:"auth"in e?e.auth:void 0,verbose:r,_cachedSSLConfig:e._cachedSSLConfig}],d=c.map((i)=>i.to||"rpx.localhost"),h=n?null:s.length>0?s:e._cachedSSLConfig??null,m=d.filter((i)=>i&&!i.includes("localhost")&&!i.includes("127.0.0.1")),p=["dev","app","page","new","day","foo"],g=["test","localhost","local","example","invalid"],v=[...new Set(m.map((i)=>i.split(".").pop()?.toLowerCase()))],S=v.filter((i)=>!!i&&p.includes(i));if(S.length>0&&r)u.warn(`The following TLDs may not work reliably for local development: ${S.map((i)=>`.${i}`).join(", ")}`),u.info(" These TLDs have HSTS preloading which can bypass local DNS"),u.info(" Consider using reserved TLDs: .test, .localhost, or .local");if(l&&y.platform==="darwin"&&m.length>0){let{setupDevelopmentDns:i}=await import("./chunk-handw2xw.js");if(await i({domains:m,verbose:r})){if(r)if(v.every((C)=>!!C&&g.includes(C)))u.success(`DNS server started for ${v.map((C)=>`.${C}`).join(", ")} domains`);else u.success(`DNS server started for ${v.map((C)=>`.${C}`).join(", ")} domains (hosts file entries also added)`)}else o("dns","Could not start DNS server - custom domains may not resolve",r)}let P=async()=>{o("cleanup","Starting cleanup handler",e.verbose);try{let{tearDownDevelopmentDns:i}=await import("./chunk-handw2xw.js");await i({verbose:e.verbose})}catch(i){o("cleanup",`Error stopping DNS server: ${i}`,e.verbose)}try{await te.stopAll(e.verbose)}catch(i){o("cleanup",`Error stopping processes: ${i}`,e.verbose)}await X({domains:d,hosts:typeof e.cleanup==="boolean"?e.cleanup:e.cleanup?.hosts,certs:typeof e.cleanup==="boolean"?e.cleanup:e.cleanup?.certs,verbose:e.verbose||!1})};y.on("SIGINT",P),y.on("SIGTERM",P);let O=e.singlePortMode===!0,b=e.httpsPort??443,T=e.httpPort??80,H=e.originGuard?we(e.originGuard):null,ft=!!h&&(c.length>1||O||s.length>0),mt=!h&&c.length>0&&(O||c.length>1),ze=e.maxTlsContexts;if(ft&&h){o("proxies",`Creating shared HTTPS server for ${c.length} domains on port ${b}`,r);let i=await Ze(c,l,r),x=null,w=e.onDemandTls,C=w?.enabled?new ke({config:w,certsDir:w.certsDir??e.productionCerts?.certsDir??ot.join(de.homedir(),".stacks","rpx","on-demand-certs"),initial:s,verbose:r,onCertAdded:(_)=>{if(tt()==="restart")o("on-demand","certificate installed; restarting supervised gateway to reload TLS",r),setTimeout(()=>y.kill(y.pid,"SIGTERM"),10).unref();else fe(_)}}):null,D=null,L=!1;async function fe(_){if(D=_,L)return;L=!0;try{while(D){let q=D;if(D=null,o("proxies",`rebuilding :${b} with ${q.length} SNI cert(s)`,r),x)W.delete(x),x.stop(!1);let A=!1;for(let B=0;!A&&B<60;B++){let Xe=re({routeEntries:i,listenPort:b,sslConfig:q,defaultTls:f,maxTlsContexts:ze,originGuard:H,verbose:r});if(Xe){x=Xe,A=!0;break}await new Promise((ht)=>setTimeout(ht,Math.min(25*2**Math.min(B,4),500)))}if(!A)u.error(`rpx: CRITICAL — could not rebind :${b} after cert issuance; HTTPS unbound until the next cert event or a gateway restart`)}}finally{L=!1}}if(!await U(T,"0.0.0.0",r)){let _=new Set(i.map((A)=>A.host)),q=(A)=>_.has(A)||[..._].some((B)=>I(A,B));st(r,T,b,e.acmeChallengeWebroot,C,q)}if(await U(b,"0.0.0.0",r)){if(o("proxies",`Port ${b} is already in use, cannot start shared proxy`,r),r)u.warn(`Port ${b} is in use. Shared HTTPS proxy cannot start.`);return}let gt=C&&C.sniEntries().length>0?C.sniEntries():h;if(x=re({routeEntries:i,listenPort:b,sslConfig:gt,defaultTls:f,maxTlsContexts:ze,originGuard:H,verbose:r}),!x){u.error(`Shared HTTPS proxy failed to bind :${b}; not exiting`);return}}else if(mt){o("proxies",`Creating shared HTTP server for ${c.length} domains on port ${T}`,r);let i=await Ze(c,l,r);if(await U(T,"0.0.0.0",r)){if(o("proxies",`Port ${T} is already in use, cannot start shared proxy`,r),r)u.warn(`Port ${T} is in use. Shared HTTP proxy cannot start.`);return}if(!re({routeEntries:i,listenPort:T,sslConfig:null,originGuard:H,verbose:r})){u.error(`Shared HTTP proxy failed to bind :${T}; not exiting`);return}}else for(let i of c)try{let x=i.to||"rpx.localhost";o("proxy",`Starting proxy for ${x} with SSL config: ${!!h}`,i.verbose),await Ie({from:i.from||"localhost:5173",to:x,cleanUrls:i.cleanUrls||!1,https:i.https||!1,cleanup:i.cleanup||!1,vitePluginUsage:i.vitePluginUsage||!1,verbose:i.verbose||!1,_cachedSSLConfig:e._cachedSSLConfig,changeOrigin:i.changeOrigin||!1,loadBalancer:i.loadBalancer,auth:i.auth,path:i.path,pathRewrites:i.pathRewrites,httpPort:e.httpPort,httpsPort:e.httpsPort})}catch(x){o("proxies",`Failed to start proxy for ${i.to}: ${x}`,i.verbose),u.error(`Failed to start proxy for ${i.to}:`,x)}}async function Ze(t,e,r){let n=[],l=new Set;for(let a of t){let s=a.to||"rpx.localhost",f=a.cleanUrls||!1,c=a.path,d=Le(c),h=pe(a.auth);if(a.redirect){let m=$e(a.redirect);n.push({host:s,path:c,route:{redirect:m,basePath:d,auth:h}}),o("proxies",`Route: ${s}${c??""} → redirect ${m.status} ${m.to}${h?" (auth)":""}`,r)}else if(a.static)n.push({host:s,path:c,route:{static:Te(a.static,f),cleanUrls:f,basePath:d,auth:h}}),o("proxies",`Route: ${s}${c??""} → static ${typeof a.static==="string"?a.static:a.static.dir}${h?" (auth)":""}`,r);else{let m=M(a.from),p=new URL(m.startsWith("http")?m:`http://${m}`),g=ae(a.from??p.host,a.loadBalancer);le(g),oe.add(g),n.push({host:s,path:c,route:{sourceHost:p.host,upstreamPool:g,cleanUrls:f,changeOrigin:a.changeOrigin||!1,pathRewrites:a.pathRewrites,basePath:d,auth:h}}),o("proxies",`Route: ${s}${c??""} → ${p.host}${h?" (auth)":""}`,r)}if(l.has(s))continue;if(l.add(s),e&&!Ee(s)&&!s.includes("localhost")&&!s.includes("127.0.0.1"))try{if(!(await G([s],r))[0])await N([s],r)}catch{o("hosts",`Could not add hosts entry for ${s}`,r)}}return n}var j=null;function it(){if(y.env.RPX_BIND_HOSTNAME)return y.env.RPX_BIND_HOSTNAME;if(j)return j;try{j=Object.values(de.networkInterfaces()).flat().some((r)=>r&&r.family==="IPv6")?"::":"0.0.0.0"}catch{j="0.0.0.0"}return j}function re(t){let{routeEntries:e,listenPort:r,sslConfig:n,defaultTls:l,maxTlsContexts:a,originGuard:s,verbose:f}=t,c=Ae(e),d=Re((p,g)=>Fe(c,p,g),f),h=s?(p,g)=>s(p)??d(p,g):d,m=Oe(f);try{let p=Bun.serve({port:r,hostname:it(),reusePort:et(),...n?{tls:Array.isArray(n)?He({sni:n,defaultTls:l,maxTlsContexts:a,verbose:f}):Ue({key:n.key,cert:n.cert,ca:n.ca,requestCert:!1,rejectUnauthorized:!1})}:{},fetch(g,v){return h(g,v)},websocket:m,error(g){return o("server",`Shared proxy server error: ${g}`,f),new Response(`Server Error: ${g.message}`,{status:500})}});return W.add(p),o("proxies",`Shared ${n?"HTTPS":"HTTP"} proxy listening on port ${r} for ${c.size} domains`,f),p}catch(p){return o("proxies",`Failed to start shared proxy: ${p}`,f),console.error("Failed to start shared proxy:",p),null}}function Dt(t){if(t?.vitePluginUsage||!t?.verbose)return;if(console.log(""),console.log(` ${R.green(R.bold("rpx"))} ${R.green(`v${Je}`)}`),console.log(` ${R.green("➜")} ${R.dim(t?.from??"")} ${R.dim("➜")} ${R.cyan(t?.ssl?`https://${t?.to}`:`http://${t?.to}`)}`),t?.listenPort!==(t?.ssl?443:80))console.log(` ${R.green("➜")} Listening on port ${t?.listenPort}`);if(t?.cleanUrls)console.log(` ${R.green("➜")} Clean URLs enabled`)}import{readdir as Et,readFile as Lt}from"node:fs/promises";import*as at from"node:path";import*as lt from"node:process";var ct="/etc/rpx/certs",We="/etc/rpx/sites.d";function At(t,e){console.error(`[rpx gateway] SKIPPING malformed fragment ${t}; its host(s) will 404 until fixed: ${e.message}`)}async function pt(t,e=At){let r=[];try{r=(await Et(t)).filter((l)=>l.endsWith(".json")).sort()}catch{return[]}let n=[];for(let l of r)try{let a=JSON.parse(await Lt(at.join(t,l),"utf8"));if(!a||typeof a!=="object"||Array.isArray(a))throw TypeError("fragment is not a JSON object");n.push({file:l,fragment:a})}catch(a){e(l,a instanceof Error?a:Error(String(a)))}return n}function ut(t,e={}){let r=e.onWarning??((v)=>console.warn(`[rpx gateway] ${v}`)),n=[],l=new Set,a=new Map,s=new Set,f=new Set,c,d=e.certsDir??ct,h,m,p=!1;for(let{file:v,fragment:S}of t){for(let P of Array.isArray(S.proxies)?S.proxies:[]){let O=P.id||`${P.to}${P.path??""}`;if(l.has(O)){r(`duplicate route ${O} in ${v} ignored; first declared by ${a.get(O)}`);continue}l.add(O),a.set(O,v),n.push(P)}for(let P of S.onDemandTls?.allowedSuffixes??[])s.add(P);if(c??=S.onDemandTls?.email,S.onDemandTls&&S.onDemandTls.staging!==!0)p=!0;if(S.productionCerts?.certsDir)d=S.productionCerts.certsDir;if(h??=S.acmeChallengeWebroot,S.originGuard&&S.originGuard.header&&S.originGuard.value)if(m??={header:S.originGuard.header,value:S.originGuard.value},S.originGuard.header===m.header&&S.originGuard.value===m.value)for(let P of S.originGuard.hosts??[])f.add(P);else r(`origin-guard secret in ${v} differs from the one already in force; its hosts stay unguarded rather than rejecting all traffic`)}let g={proxies:n,productionCerts:{certsDir:d,certsDirServerNames:[...new Set(n.map((v)=>v.to).filter(Boolean))]},https:!0,hostsManagement:!1,cleanup:{hosts:!1,certs:!1}};if(s.size>0)g.onDemandTls={enabled:!0,allowedSuffixes:[...s],email:c,certsDir:d,staging:!p};if(h)g.acmeChallengeWebroot=h;if(m)g.originGuard={header:m.header,value:m.value,hosts:[...f]};return g}async function dt(t={}){let e=t.sitesDir??We,r=t.verbose??lt.env.RPX_VERBOSE!=="false",n=await pt(e,t.onFragmentError);o("gateway",`merged ${n.length} fragment(s) from ${e}: ${n.map((s)=>s.file).join(", ")||"<none>"}`,r);let a={...ut(n,{certsDir:t.certsDir}),verbose:r,singlePortMode:!0};if(t.https===!1)a.https=!1;if(t.httpPort!==void 0)a.httpPort=t.httpPort;if(t.httpsPort!==void 0)a.httpsPort=t.httpsPort;if(t.localCa)a.localCa=t.localCa;if(t.maxTlsContexts!==void 0)a.maxTlsContexts=t.maxTlsContexts;return a}async function Ft(t={}){let e=await dt(t);if(("proxies"in e&&Array.isArray(e.proxies)?e.proxies:[]).length===0&&!t.localCa)console.warn(`[rpx gateway] no routes found under ${t.sitesDir??We}; every request will answer 404 until a fragment is deployed`);await Y(e)}var ms=Y;export{xr as ACME_CHALLENGE_PREFIX,ct as DEFAULT_GATEWAY_CERTS_DIR,We as DEFAULT_GATEWAY_SITES_DIR,Er as DEFAULT_LOCAL_CA_RENEW_BEFORE_DAYS,Dr as DEFAULT_LOCAL_CA_VALIDITY_DAYS,Sr as DEFAULT_MAX_TLS_CONTEXTS,fo as DNS_PORT,io as DNS_STATE_VERSION,Ge as DefaultPortManager,ao as LEGACY_TLD_RESOLVER_LABELS,Or as LOCAL_CA_COMMON_NAME,Tr as LOCAL_CA_LEAF_CERT_FILENAME,Rr as LOCAL_CA_LEAF_KEY_FILENAME,Gt as MACOS_CA_TRUST_FLAGS,Bt as MACOS_SYSTEM_KEYCHAIN,ke as OnDemandCertManager,Qr as RPX_HOSTS_MARKER,mo as RPX_RESOLVER_MARKER,It as RPX_ROOT_CA_COMMON_NAME,Kt as SHARED_DEV_HOST_CERT_PATH,Vr as SiteSupervisor,Lo as acquireDaemonLock,N as addHosts,hs as authorizeSystemAccess,Ae as buildHostRoutes,He as buildListenerTls,ur as buildRedirectLocation,Zt as buildRegistryTlsProxyOptions,_e as buildSniTlsConfig,br as capTlsContexts,kt as certIncludesSanHostnames,k as checkExistingCertificates,G as checkHosts,X as cleanup,Ce as cleanupCertificates,nr as clearSslConfigCache,R as colors,ne as config,wo as contentLooksLikeRpxResolver,dr as contentTypeFor,we as createOriginGuard,Re as createProxyFetchHandler,Oe as createProxyWebSocketHandler,Gr as createSiteResolver,ae as createUpstreamPool,o as debugLog,ms as default,ne as defaultConfig,Ho as defaultDaemonSpawnCommand,Ve as deriveIdFromTarget,Nr as detectProjectPreset,uo as devDomainsFromHosts,Jt as devSslToSniEntries,oo as dropStaleRpxHostsLines,pr as enforceBasicAuth,ye as ensureDaemonRunning,Ne as ensureLocalCa,tr as ensureRootCA,Kr as escapeHtml,xs as execSudoSync,Ur as expandHome,ws as extractHostname,ro as filterRpxHostsEntries,Ir as findAvailablePort,no as findStaleRpxHosts,or as forceTrustCertificate,Yr as gcStaleEntries,se as generateCertificate,Oo as getDaemonPidPath,Ro as getDaemonRpxDir,jt as getMacosLoginKeychainPath,Wt as getMacosTrustKeychains,Ss as getPrimaryDomain,me as getRegistryDir,Qt as getRootCAPaths,er as getSharedDaemonCertPaths,Qe as getSudoPassword,to as hostsLineMapsHost,ie as httpsConfig,Hr as installLocalCaTrust,sr as isCertTrusted,Eo as isDaemonRunning,xo as isDnsServerRunning,$r as isLikelyHostname,bs as isMultiProxyConfig,vs as isMultiProxyOptions,Wr as isPidAlive,U as isPortInUse,gs as isProcessElevated,qt as isRootCaFingerprintInKeychains,Yt as isRootCaTrustedForSsl,$s as isSingleProxyConfig,Cs as isSingleProxyOptions,ge as isValidId,Ps as isValidRootCA,Ee as isWildcardPattern,Fr as leafRenewalReason,zt as listCertSha256HashesByCommonName,Br as listDiscoverableSites,rr as loadSSLConfig,Lr as localCaPaths,lr as markFailure,ar as markSuccess,yr as matchHost,Pr as matchHostList,Fe as matchHostRoute,Cr as matchesAllowedSuffix,I as matchesWildcard,ut as mergeGatewayFragments,lo as normalizeDevDomain,Le as normalizePathPrefix,Ht as normalizeSha256Fingerprint,eo as parseHostsLine,cr as parseHtpasswd,Ar as parseSanNames,Nt as parseSha256HashesFromSecurityListing,wr as pathPrefixMatches,jr as portManager,M as primaryUpstreamUrl,kr as projectNameFromHost,Xt as pruneStaleRootCas,De as readAcmeChallenge,Xr as readAll,_t as readCertCommonName,Ut as readCertSha256Fingerprint,Do as readDaemonPid,zr as readEntry,pt as readGatewayFragments,Mr as readSiteManifest,_o as reconcileDevelopmentDnsOnIdle,To as reconcileStaleDevelopmentDns,ys as redactSensitive,Ao as releaseDaemonLock,xe as removeEntry,Be as removeHosts,So as removeLegacyTldResolvers,$o as removeResolver,so as removeStaleRpxHosts,Zr as renderFailedPage,Jr as renderStartingPage,pe as resolveAuth,dt as resolveGatewayOptions,Me as resolveLocalCaConfig,Ts as resolvePathRewrite,$e as resolveRedirect,mr as resolveStaticFile,Te as resolveStaticRoute,co as resolverBasenameForDomain,po as resolverBasenamesForDomains,yo as resolverFilePath,Fo as runDaemon,K as runViaDaemon,Rs as safeDeleteFile,fr as safeRelativePath,F as safeStringify,ir as selectUpstream,gr as serveStaticFile,vr as serverNameFromCertFilename,bo as setupDevelopmentDns,Po as setupResolver,et as shouldReusePort,_r as siteIdForHost,go as startDnsServer,Ft as startGateway,le as startHealthChecks,Y as startProxies,Rt as startProxy,Ie as startServer,Uo as stopDaemon,ho as stopDnsServer,ce as stopHealthChecks,hr as stripBasePath,vo as syncDevelopmentDnsFromRegistry,Co as tearDownDevelopmentDns,Vt as trustRootCaForBrowsers,Mt as verifyHttpsChain,qr as watchRegistry,Ue as withLowMemoryTls,he as writeEntry};
|
|
2
|
+
`,s=(c)=>{let d=Q(c);return r.has(d)||n.some((h)=>I(d,h))},f=(c)=>{let d=St(c);if(!s(d))return;let h="/";try{h=new URL(c.url).pathname}catch{}if(l.some((m)=>h.startsWith(m)))return;if(wt(c.headers.get(e),t.value))return;return new Response(a,{status:403,headers:{"content-type":"text/plain"}})};return f.protects=s,f}var Ke={name:"@stacksjs/rpx",type:"module",version:"0.11.50",description:"A modern and smart reverse proxy.",author:"Chris Breuer <chris@stacksjs.org>",license:"MIT",homepage:"https://github.com/stacksjs/rpx",repository:{type:"git",url:"git+https://github.com/stacksjs/rpx.git"},bugs:{url:"https://github.com/stacksjs/rpx/issues"},keywords:["reverse proxy","ssl","development","environment","proxy","bun","stacks","typescript","javascript"],exports:{".":{types:"./dist/index.d.ts",bun:"./dist/index.js",import:"./dist/index.js"}},module:"./dist/index.js",types:"./dist/index.d.ts",bin:{rpx:"./dist/bin/cli.js","reverse-proxy":"./dist/bin/cli.js"},files:["README.md","dist"],scripts:{build:"bun build.ts && bun build --production ./bin/cli.ts --compile --outfile bin/rpx",compile:"bun build --production ./bin/cli.ts --compile --outfile bin/rpx","compile:all":"bun run compile:linux-x64 && bun run compile:linux-arm64 && bun run compile:windows-x64 && bun run compile:darwin-x64 && bun run compile:darwin-arm64","compile:linux-x64":"bun build --production ./bin/cli.ts --compile --target=bun-linux-x64 --outfile bin/rpx-linux-x64","compile:linux-arm64":"bun build --production ./bin/cli.ts --compile --target=bun-linux-arm64 --outfile bin/rpx-linux-arm64","compile:windows-x64":"bun build --production ./bin/cli.ts --compile --target=bun-windows-x64 --outfile bin/rpx-windows-x64.exe","compile:darwin-x64":"bun build --production ./bin/cli.ts --compile --target=bun-darwin-x64 --outfile bin/rpx-darwin-x64","compile:darwin-arm64":"bun build --production ./bin/cli.ts --compile --target=bun-darwin-arm64 --outfile bin/rpx-darwin-arm64",bench:"bun run bench/run.ts","bench:html":"bun run bench/run.ts --html","bench:latency":"bun run bench/run.ts --latency","bench:throughput":"bun run bench/run.ts --throughput",lint:"bunx --bun pickier .","lint:fix":"bunx --bun pickier . --fix",fresh:"bunx rimraf node_modules/ bun.lock && bun i",changelog:"changelogen --output CHANGELOG.md",prepublishOnly:"bun build.ts","release:binaries":"bun run compile:all && bun run zip",test:"bun test",typecheck:"bunx tsc --noEmit",zip:"bun run zip:all","zip:all":"bun run zip:linux-x64 && bun run zip:linux-arm64 && bun run zip:windows-x64 && bun run zip:darwin-x64 && bun run zip:darwin-arm64","zip:linux-x64":"zip -j bin/rpx-linux-x64.zip bin/rpx-linux-x64","zip:linux-arm64":"zip -j bin/rpx-linux-arm64.zip bin/rpx-linux-arm64","zip:windows-x64":"zip -j bin/rpx-windows-x64.zip bin/rpx-windows-x64.exe","zip:darwin-x64":"zip -j bin/rpx-darwin-x64.zip bin/rpx-darwin-x64","zip:darwin-arm64":"zip -j bin/rpx-darwin-arm64.zip bin/rpx-darwin-arm64"},dependencies:{"@stacksjs/clapp":"^0.2.10","@stacksjs/tlsx":"^0.13.19"},devDependencies:{bunfig:"^0.15.6",mitata:"^1.0.34",typescript:"^7.0.2"},"simple-git-hooks":{"pre-commit":"bunx lint-staged"},"lint-staged":{"*.{js,ts}":"bunx --bun pickier . --fix"}};var Je=Ke.version;var te=new Z,Ct=new Ge("0.0.0.0"),W=new Set,oe=new Set,Pe=!1,ee=null,Se=null;async function X(t){if(Pe)return o("cleanup","Cleanup already in progress, skipping",t?.verbose),Se||Promise.resolve();Pe=!0,o("cleanup","Starting cleanup process",t?.verbose),Se=new Promise((e)=>{ee=e});try{await te.stopAll(t?.verbose),u.info("Shutting down proxy servers...");let e=[],r=Array.from(W).map((n)=>new Promise((l)=>{let a=n;try{if(typeof a.stop==="function")a.stop(!0),o("cleanup","Bun server stopped",t?.verbose),l();else if(typeof a.close==="function")a.close(()=>{o("cleanup","Server closed successfully",t?.verbose),l()});else l()}catch(s){o("cleanup",`Error stopping server: ${s}`,t?.verbose),l()}}));e.push(...r),W.clear();for(let n of oe)ce(n);if(oe.clear(),t?.hosts&&t.domains?.length){o("cleanup","Cleaning up hosts file entries",t?.verbose),o("cleanup",`Original domains for cleanup: ${JSON.stringify(t.domains)}`,t?.verbose);let n=t.domains.filter((l)=>{if(l==="test.local")return!0;return l!=="localhost"&&!l.startsWith("localhost.")&&l!=="127.0.0.1"});if(o("cleanup",`Filtered domains for cleanup: ${JSON.stringify(n)}`,t?.verbose),n.length>0)u.info("Cleaning up hosts file entries..."),e.push(Be(n,t?.verbose).then(()=>{o("cleanup",`Removed hosts entries for ${n.join(", ")}`,t?.verbose)}).catch((l)=>{o("cleanup",`Failed to remove hosts entries: ${l}`,t?.verbose),u.warn(`Failed to clean up hosts file entries for ${n.join(", ")}:`,l)}))}if(t?.certs&&t.domains?.length){o("cleanup","Cleaning up SSL certificates",t?.verbose),u.info("Cleaning up SSL certificates...");let n=t.domains.map(async(l)=>{try{await Ce(l,t?.verbose),o("cleanup",`Removed certificates for ${l}`,t?.verbose)}catch(a){o("cleanup",`Failed to remove certificates for ${l}: ${a}`,t?.verbose),u.warn(`Failed to clean up certificates for ${l}:`,a)}});e.push(...n)}await Promise.allSettled(e),o("cleanup","All cleanup tasks completed successfully",t?.verbose),u.success("All cleanup tasks completed successfully")}catch(e){o("cleanup",`Error during cleanup: ${e}`,t?.verbose),u.error("Error during cleanup:",e)}finally{if(ee)ee();ee=null,Pe=!1;let e=t&&"vitePluginUsage"in t&&t.vitePluginUsage===!0;if(y.env.NODE_ENV!=="test"&&y.env.BUN_ENV!=="test"&&!e)y.exit(0)}return Se}var be=!1;function nt(t){if(be){o("signal",`Received second ${t} signal, forcing exit`,!0),y.exit(1);return}be=!0,o("signal",`Received ${t} signal, initiating cleanup`,!0),X().catch((e)=>{o("signal",`Cleanup failed after ${t}: ${e}`,!0),y.exit(1)}).finally(()=>{be=!1})}y.once("SIGINT",()=>nt("SIGINT"));y.once("SIGTERM",()=>nt("SIGTERM"));y.on("uncaughtException",(t)=>{u.error("Uncaught exception (continuing):",t)});y.on("unhandledRejection",(t)=>{u.error("Unhandled rejection (continuing):",t)});async function z(t,e,r,n=5){o("connection",`Testing connection to ${t}:${e} (retries left: ${n})`,r);let l=15000,a=Date.now();if(y.env.RPX_BYPASS_CONNECTION_TEST==="true"){o("connection",`Bypassing connection test for ${t}:${e} due to RPX_BYPASS_CONNECTION_TEST flag`,r);return}let s=()=>new Promise((f,c)=>{let d=rt.connect({host:t,port:e,timeout:3000});d.once("connect",()=>{o("connection",`Successfully connected to ${t}:${e}`,r),d.end(),f()}),d.once("timeout",()=>{o("connection",`Connection to ${t}:${e} timed out`,r),d.destroy(),c(Error("Connection timed out"))}),d.once("error",(h)=>{o("connection",`Failed to connect to ${t}:${e}: ${h}`,r),d.destroy(),c(h)})});try{await s()}catch(f){let c=f;if(Date.now()-a>l){o("connection",`Connection test timed out after ${l}ms, but continuing anyway`,r),u.warn(`Connection test to ${t}:${e} timed out, but RPX will try to proceed anyway.`);return}if(c.code==="ECONNREFUSED"&&n>0)return o("connection",`Connection refused, server might be starting up. Retrying in 2 seconds... (${n} retries left)`,r),await new Promise((h)=>setTimeout(h,2000)),z(t,e,r,n-1);if(n>0)try{o("connection",`Trying HTTP request to ${t}:${e}`,r),await new Promise((h,m)=>{let p=ue.request({hostname:t,port:e,path:"/",method:"HEAD",timeout:5000},(g)=>{o("connection",`Received HTTP response with status: ${g.statusCode}`,r),h()});p.on("error",(g)=>m(g)),p.on("timeout",()=>{p.destroy(),m(Error("HTTP request timed out"))}),p.end()}),o("connection",`HTTP request to ${t}:${e} succeeded`,r);return}catch(h){return o("connection",`HTTP request to ${t}:${e} failed: ${h}`,r),o("connection",`Retrying socket connection in 2 seconds... (${n} retries left)`,r),await new Promise((m)=>setTimeout(m,2000)),z(t,e,r,n-1)}let d=`Failed to connect to ${t}:${e} after ${5-n} attempts: ${c.message}`;o("connection",`${d}. To bypass this check set RPX_BYPASS_CONNECTION_TEST=true`,r),u.warn(d),u.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 Ie(t){o("server",`Starting server with options: ${F(t)}`,t.verbose);let e=M(t.from),r=new URL(e.startsWith("http")?e:`http://${e}`),n=new URL((t.to?.startsWith("http")?t.to:`http://${t.to}`)||"rpx.localhost"),l=Number.parseInt(r.port)||(r.protocol.includes("https:")?443:80),a=[n.hostname];if(je(t)&&!n.hostname.includes("localhost")&&!n.hostname.includes("127.0.0.1")){o("hosts",`Checking if hosts file entry exists for: ${n.hostname}`,t?.verbose);try{if(!(await G(a,t.verbose))[0]){u.info(`Adding ${n.hostname} to hosts file...`),u.info("This may require sudo/administrator privileges");try{await N(a,t.verbose)}catch(c){if(u.error("Failed to add hosts entry:",c.message),u.warn("You can manually add this entry to your hosts file:"),u.warn(`127.0.0.1 ${n.hostname}`),u.warn(`::1 ${n.hostname}`),y.platform==="win32")u.warn("On Windows:"),u.warn("1. Run notepad as administrator"),u.warn("2. Open C:\\Windows\\System32\\drivers\\etc\\hosts");else u.warn("On Unix systems:"),u.warn("sudo nano /etc/hosts")}}else o("hosts",`Host entry already exists for ${n.hostname}`,t.verbose)}catch(f){u.error("Failed to check hosts file:",f.message)}}try{await z(r.hostname,l,t.verbose)}catch(f){o("server",`Connection test failed: ${f}`,t.verbose),u.error(f.message),u.warn("Continuing with proxy setup despite connection test failure..."),u.info("If you need to bypass connection testing, set environment variable RPX_BYPASS_CONNECTION_TEST=true")}let s=t._cachedSSLConfig||null;if(t.https)try{if(t.https===!0)t.https=ie({...t,to:n.hostname});if(s=await k({...t,to:n.hostname,https:t.https}),!s){if(o("ssl",`Generating new certificates for ${n.hostname}`,t.verbose),await se({...t,from:r.toString(),to:n.hostname,https:t.https}),s=await k({...t,to:n.hostname,https:t.https}),!s)throw Error(`Failed to load SSL configuration after generating certificates for ${n.hostname}`)}}catch(f){throw o("server",`SSL setup failed: ${f}`,t.verbose),f}o("server",`Setting up reverse proxy with SSL config for ${n.hostname}`,t.verbose),await Tt({...t,from:e,originalFrom:t.from||e,to:n.hostname,fromPort:l,sourceUrl:{hostname:r.hostname,host:r.host},ssl:s})}async function $t(t,e,r,n,l,a,s,f,c,d,h,m){o("proxy",`Creating proxy server ${t} -> ${e} with cleanUrls: ${f}`,s);let p=ae(h??n.host,m);le(p);let g=[{host:e,route:{sourceHost:n.host,upstreamPool:p,cleanUrls:f||!1,changeOrigin:c||!1,basePath:"/",auth:d}}];if(!re({routeEntries:g,listenPort:r,sslConfig:l,originGuard:null,verbose:s??!1}))throw ce(p),Error(`Failed to start proxy server for ${e} on port ${r}`);oe.add(p),Dt({from:t,to:e,vitePluginUsage:a,listenPort:r,ssl:!!l,cleanUrls:f,verbose:s})}async function Tt(t){o("setup",`Setting up reverse proxy: ${F(t)}`,t.verbose);let{from:e,originalFrom:r,to:n,sourceUrl:l,ssl:a,verbose:s,cleanup:f,vitePluginUsage:c,changeOrigin:d,cleanUrls:h}=t,m=t.httpPort??80,p=t.httpsPort??443,g=it(),v=t.portManager||Ct,S=je(t);try{if(S&&n&&!n.includes("localhost")&&!n.includes("127.0.0.1")){if(!(await G([n],s))[0]){u.warn(`The hostname ${n} isn't in your hosts file. Adding it now...`);try{await N([n],s),u.success(`Added ${n} to your hosts file.`)}catch(H){u.error(`Failed to add ${n} to your hosts file: ${H}`),u.info(`You may need to manually add '127.0.0.1 ${n}' to your /etc/hosts file.`)}}}else if(S&&y.platform!=="darwin"&&n&&n.includes("localhost")&&!n.match(/^(localhost|127\.0\.0\.1)$/)){if(!(await G([n],s))[0]){o("hosts",`${n} not found in hosts file, adding...`,s);try{await N([n],s)}catch(H){o("hosts",`Failed to add ${n} to hosts file: ${H}`,s)}}}if(a&&!v.usedPorts.has(m)){if(!await U(m,g,s))o("setup","Starting HTTP redirect server",s),st(s,m,p),v.usedPorts.add(m);else if(o("setup","Port 80 is in use, skipping HTTP redirect",s),s)u.warn("Port 80 is in use, HTTP to HTTPS redirect will not be available")}let P=a?p:m,O=await U(P,g,s),b;if(O){if(o("setup",`Port ${P} is already in use`,s),s)u.warn(`Port ${P} is already in use. This may be another instance of rpx or another service.`);if(P===443){if(b=await v.getNextAvailablePort(3443,!0),o("setup",`Using port ${b} instead of ${P}`,s),s)u.info(`Using port ${b} instead. Access your site at https://${n}:${b}`)}else if(b=await v.getNextAvailablePort(P+1000,!0),o("setup",`Using port ${b} instead of ${P}`,s),s)u.info(`Using port ${b} instead. Access your site at http://${n}:${b}`)}else b=P,v.usedPorts.add(b),o("setup",`Using standard ${P===443?"HTTPS":"HTTP"} port ${P} for ${n}`,s);await $t(e,n,b,l,a,c,s,h,d,pe(t.auth),r,t.loadBalancer)}catch(P){o("setup",`Setup failed: ${P}`,s),u.error(`Failed to setup reverse proxy: ${P.message}`),X({domains:[n],hosts:typeof f==="boolean"?f:f?.hosts,certs:typeof f==="boolean"?f:f?.certs,verbose:s,vitePluginUsage:c})}}function st(t,e=80,r=443,n,l,a){o("redirect",`Starting HTTP redirect server on port ${e}`,t);let s=ue.createServer((f,c)=>{let d=f.url?f.url.split("?",1)[0]:"";if(d.startsWith("/.well-known/acme-challenge/")){if(l){let g=l.challengeStore.handlePath(d);if(g!==void 0){o("redirect",`Serving on-demand ACME challenge ${d}`,t),c.writeHead(200,{"content-type":"text/plain"}),c.end(g);return}}if(n){let g=De(n,d);if(g!=null){o("redirect",`Serving ACME challenge ${d}`,t),c.writeHead(200,{"content-type":"text/plain"}),c.end(g);return}}if(l){c.writeHead(404,{"content-type":"text/plain"}),c.end("challenge not found");return}}let h=f.headers.host||"",m=h.includes(":")?h.slice(0,h.indexOf(":")):h;if(l&&m&&!l.hasCert(m)&&(!a||a(m)))l.ensureCert(m).catch(()=>{});let p=r===443?m:`${m}:${r}`;o("redirect",`Redirecting request from ${h}${f.url} to https://${p}`,t),c.writeHead(301,{Location:`https://${p}${f.url}`}),c.end()}).listen(e);W.add(s),o("redirect","HTTP redirect server started",t)}function Rt(t){let e={...ne,...t};if(o("proxy",`Starting proxy with options: ${F(e)}`,e?.verbose),e.viaDaemon){if(!e.from||!e.to){u.error("viaDaemon mode requires both `from` and `to`");return}K({proxies:[{id:e.id,from:e.from,to:e.to,path:e.path,cleanUrls:e.cleanUrls,changeOrigin:e.changeOrigin,pathRewrites:e.pathRewrites}],verbose:e.verbose}).catch((c)=>{u.error(`Failed to register with rpx daemon: ${c.message}`),y.exit(1)});return}let r=e.to||"",n=r.split(".").pop()?.toLowerCase()||"",l=y.platform==="darwin"&&r&&!r.includes("localhost")&&!r.includes("127.0.0.1"),a=["dev","app","page","new","day","foo"],s=["test","localhost","local","example","invalid"];if(l&&a.includes(n)&&e?.verbose)u.warn(`The .${n} TLD may not work reliably for local development`),u.info(` Google owns .${n} with HSTS preloading, which can bypass local DNS`),u.info(" Consider using a reserved TLD: .test, .localhost, or .local");if(l)import("./chunk-handw2xw.js").then(({setupDevelopmentDns:c})=>{c({domains:[r],verbose:e.verbose}).then((d)=>{if(d)Promise.resolve().then(()=>{if(e.verbose)if(s.includes(n))u.success(`DNS server started for .${n} domains`);else u.success(`DNS server started for .${n} domains (hosts file entry also added)`)});else o("dns",`Could not start DNS server - ${r} may not resolve in browser`,e.verbose)})}).catch((c)=>{o("dns",`Failed to start DNS server: ${c}`,e.verbose)});let f={from:e.from,to:e.to,cleanUrls:e.cleanUrls,https:ie(e),cleanup:e.cleanup,vitePluginUsage:e.vitePluginUsage,changeOrigin:e.changeOrigin,verbose:e.verbose,regenerateUntrustedCerts:e.regenerateUntrustedCerts};o("proxy",`Server options: ${F(f)}`,e.verbose),Ie(f).catch((c)=>{o("proxy",`Failed to start proxy: ${c}`,e.verbose),u.error(`Failed to start proxy: ${c.message}`),X({domains:[e.to],hosts:typeof e.cleanup==="boolean"?e.cleanup:e.cleanup?.hosts,certs:typeof e.cleanup==="boolean"?e.cleanup:e.cleanup?.certs,verbose:e.verbose})})}function Ot(t){return t?.verbose||!1}function je(t){if(t?.hostsManagement===!1)return!1;let e=t?.cleanup;if(e===!1)return!1;if(e&&typeof e==="object"&&e.hosts===!1)return!1;return!0}async function Y(t){let e={from:"localhost:5173",to:"rpx.localhost",https:!1,cleanup:{hosts:!0,certs:!1},vitePluginUsage:!1,verbose:!1,cleanUrls:!1,changeOrigin:!1,regenerateUntrustedCerts:!0};if(t)e={...e,...t};let r=Ot(e),n=e.https===!1;if(e.localCa)Me(e.localCa,e.onDemandTls);let l=je(e);if(o("config",`Starting with config: ${F(e,2)}`,r),o("config",`Is multi-proxy? ${"proxies"in e}`,r),o("config",`Hosts management enabled? ${l}`,r),e.viaDaemon){let x="proxies"in e&&Array.isArray(e.proxies)?e.proxies.map((w)=>({id:w.id,from:w.from,to:w.to,path:w.path,cleanUrls:w.cleanUrls??e.cleanUrls,changeOrigin:w.changeOrigin??e.changeOrigin,pathRewrites:w.pathRewrites})):[{id:e.id,from:e.from,to:e.to??"rpx.localhost",path:e.path,cleanUrls:e.cleanUrls,changeOrigin:e.changeOrigin,pathRewrites:e.pathRewrites}];await K({proxies:x,verbose:r});return}if("proxies"in e&&Array.isArray(e.proxies)){o("servers",`Found ${e.proxies.length} proxies in config`,r);for(let i of e.proxies)if(i.start){let x=`${i.from}-${i.to}`;try{o("watch",`Starting command for ${x} with command: ${i.start.command}`,r),u.info(`Starting command for ${x}...`),await te.startProcess(x,i.start,r);let w=M(i.from),C=new URL(w.startsWith("http")?w:`http://${w}`),D=C.hostname||"localhost",L=Number(C.port)||80;try{await z(D,L,r),o("watch",`Dev server is ready at ${D}:${L}`,r)}catch(fe){o("watch",`Connection check failed, but continuing with proxy setup: ${fe}`,r),u.warn("Dev server connection check failed. RPX will try to proceed anyway...")}}catch(w){throw o("watch",`Failed to start command for ${x}: ${w}`,r),Error(`Failed to start command for ${x}: ${w}`)}}else o("watch",`No start command for proxy ${i.from} -> ${i.to}`,r)}else if("start"in e&&e.start){o("watch","Found start command in single proxy config",r);let i=`${e.from}-${e.to}`;try{if(e.start)o("watch",`Starting command: ${e.start.command}`,r),await te.startProcess(i,e.start,r);let x=M(e.from),w=new URL(x.startsWith("http")?x:`http://${x}`),C=w.hostname||"localhost",D=Number(w.port)||80;try{await z(C,D,r),o("watch",`Dev server is ready at ${C}:${D}`,r)}catch(L){o("watch",`Connection check failed, but continuing with proxy setup: ${L}`,r),u.warn("Dev server connection check failed. RPX will try to proceed anyway...")}}catch(x){throw o("watch",`Failed to run start command: ${x}`,r),Error(`Failed to run start command: ${x}`)}}else o("watch","No start command found in config",r);let a="proxies"in e&&Array.isArray(e.proxies)?e.proxies[0]?.to:("to"in e)?e.to:"rpx.localhost";if(y.platform!=="win32"&&(e.https||l)){if(!Qe())try{o("sudo","Pre-acquiring sudo credentials for privileged operations",r),vt("sudo -v",{stdio:"inherit"})}catch{o("sudo","Could not pre-acquire sudo credentials",r)}}let s=[],f=null;if(e.productionCerts&&!n){if(s=await _e(e.productionCerts,r),s.length>0)o("ssl",`Using ${s.length} production SNI cert(s): ${s.map((i)=>i.serverName).join(", ")}`,r)}if(e.localCa&&!n){let i=await Ne(e.localCa,{verbose:r,onDemandTls:e.onDemandTls}),x=new Set(i.entries.map((w)=>w.serverName));s=[...i.entries,...s.filter((w)=>!x.has(w.serverName))],f=i.defaultTls,o("ssl",`Local CA: ${i.leafMinted?`minted leaf (${i.renewalReason})`:"reusing leaf"} for ${[...x].join(", ")}; valid until ${i.notAfter.toISOString()}`,r)}if(e.https){let i=s.length>0?null:await k(e);if(!i&&s.length===0){if(o("ssl",`No valid or trusted certificates found for ${a}, generating new ones`,e.verbose),await se(e),i=await k(e),!i)throw Error(`Failed to load SSL certificates after generation for ${a}`)}else o("ssl",`Using existing and trusted certificates for ${a}`,e.verbose);e._cachedSSLConfig=i}let c="proxies"in e&&Array.isArray(e.proxies)?e.proxies.map((i)=>({...i,https:e.https,cleanup:e.cleanup,cleanUrls:i.cleanUrls??("cleanUrls"in e?e.cleanUrls:!1),vitePluginUsage:e.vitePluginUsage,changeOrigin:i.changeOrigin??e.changeOrigin,verbose:r,_cachedSSLConfig:e._cachedSSLConfig})):[{from:"from"in e?e.from:"localhost:5173",to:"to"in e?e.to:"rpx.localhost",cleanUrls:"cleanUrls"in e?e.cleanUrls:!1,https:e.https,cleanup:e.cleanup,vitePluginUsage:e.vitePluginUsage,start:"start"in e?e.start:void 0,changeOrigin:e.changeOrigin,auth:"auth"in e?e.auth:void 0,verbose:r,_cachedSSLConfig:e._cachedSSLConfig}],d=c.map((i)=>i.to||"rpx.localhost"),h=n?null:s.length>0?s:e._cachedSSLConfig??null,m=d.filter((i)=>i&&!i.includes("localhost")&&!i.includes("127.0.0.1")),p=["dev","app","page","new","day","foo"],g=["test","localhost","local","example","invalid"],v=[...new Set(m.map((i)=>i.split(".").pop()?.toLowerCase()))],S=v.filter((i)=>!!i&&p.includes(i));if(S.length>0&&r)u.warn(`The following TLDs may not work reliably for local development: ${S.map((i)=>`.${i}`).join(", ")}`),u.info(" These TLDs have HSTS preloading which can bypass local DNS"),u.info(" Consider using reserved TLDs: .test, .localhost, or .local");if(l&&y.platform==="darwin"&&m.length>0){let{setupDevelopmentDns:i}=await import("./chunk-handw2xw.js");if(await i({domains:m,verbose:r})){if(r)if(v.every((C)=>!!C&&g.includes(C)))u.success(`DNS server started for ${v.map((C)=>`.${C}`).join(", ")} domains`);else u.success(`DNS server started for ${v.map((C)=>`.${C}`).join(", ")} domains (hosts file entries also added)`)}else o("dns","Could not start DNS server - custom domains may not resolve",r)}let P=async()=>{o("cleanup","Starting cleanup handler",e.verbose);try{let{tearDownDevelopmentDns:i}=await import("./chunk-handw2xw.js");await i({verbose:e.verbose})}catch(i){o("cleanup",`Error stopping DNS server: ${i}`,e.verbose)}try{await te.stopAll(e.verbose)}catch(i){o("cleanup",`Error stopping processes: ${i}`,e.verbose)}await X({domains:d,hosts:typeof e.cleanup==="boolean"?e.cleanup:e.cleanup?.hosts,certs:typeof e.cleanup==="boolean"?e.cleanup:e.cleanup?.certs,verbose:e.verbose||!1})};y.on("SIGINT",P),y.on("SIGTERM",P);let O=e.singlePortMode===!0,b=e.httpsPort??443,T=e.httpPort??80,H=e.originGuard?we(e.originGuard):null,ft=!!h&&(c.length>1||O||s.length>0),mt=!h&&c.length>0&&(O||c.length>1),ze=e.maxTlsContexts;if(ft&&h){o("proxies",`Creating shared HTTPS server for ${c.length} domains on port ${b}`,r);let i=await Ze(c,l,r),x=null,w=e.onDemandTls,C=w?.enabled?new ke({config:w,certsDir:w.certsDir??e.productionCerts?.certsDir??ot.join(de.homedir(),".stacks","rpx","on-demand-certs"),initial:s,verbose:r,onCertAdded:(_)=>{if(tt()==="restart")o("on-demand","certificate installed; restarting supervised gateway to reload TLS",r),setTimeout(()=>y.kill(y.pid,"SIGTERM"),10).unref();else fe(_)}}):null,D=null,L=!1;async function fe(_){if(D=_,L)return;L=!0;try{while(D){let q=D;if(D=null,o("proxies",`rebuilding :${b} with ${q.length} SNI cert(s)`,r),x)W.delete(x),x.stop(!1);let A=!1;for(let B=0;!A&&B<60;B++){let Xe=re({routeEntries:i,listenPort:b,sslConfig:q,defaultTls:f,maxTlsContexts:ze,originGuard:H,verbose:r});if(Xe){x=Xe,A=!0;break}await new Promise((ht)=>setTimeout(ht,Math.min(25*2**Math.min(B,4),500)))}if(!A)u.error(`rpx: CRITICAL — could not rebind :${b} after cert issuance; HTTPS unbound until the next cert event or a gateway restart`)}}finally{L=!1}}if(!await U(T,"0.0.0.0",r)){let _=new Set(i.map((A)=>A.host)),q=(A)=>_.has(A)||[..._].some((B)=>I(A,B));st(r,T,b,e.acmeChallengeWebroot,C,q)}if(await U(b,"0.0.0.0",r)){if(o("proxies",`Port ${b} is already in use, cannot start shared proxy`,r),r)u.warn(`Port ${b} is in use. Shared HTTPS proxy cannot start.`);return}let gt=C&&C.sniEntries().length>0?C.sniEntries():h;if(x=re({routeEntries:i,listenPort:b,sslConfig:gt,defaultTls:f,maxTlsContexts:ze,originGuard:H,verbose:r}),!x){u.error(`Shared HTTPS proxy failed to bind :${b}; not exiting`);return}}else if(mt){o("proxies",`Creating shared HTTP server for ${c.length} domains on port ${T}`,r);let i=await Ze(c,l,r);if(await U(T,"0.0.0.0",r)){if(o("proxies",`Port ${T} is already in use, cannot start shared proxy`,r),r)u.warn(`Port ${T} is in use. Shared HTTP proxy cannot start.`);return}if(!re({routeEntries:i,listenPort:T,sslConfig:null,originGuard:H,verbose:r})){u.error(`Shared HTTP proxy failed to bind :${T}; not exiting`);return}}else for(let i of c)try{let x=i.to||"rpx.localhost";o("proxy",`Starting proxy for ${x} with SSL config: ${!!h}`,i.verbose),await Ie({from:i.from||"localhost:5173",to:x,cleanUrls:i.cleanUrls||!1,https:i.https||!1,cleanup:i.cleanup||!1,vitePluginUsage:i.vitePluginUsage||!1,verbose:i.verbose||!1,_cachedSSLConfig:e._cachedSSLConfig,changeOrigin:i.changeOrigin||!1,loadBalancer:i.loadBalancer,auth:i.auth,path:i.path,pathRewrites:i.pathRewrites,httpPort:e.httpPort,httpsPort:e.httpsPort})}catch(x){o("proxies",`Failed to start proxy for ${i.to}: ${x}`,i.verbose),u.error(`Failed to start proxy for ${i.to}:`,x)}}async function Ze(t,e,r){let n=[],l=new Set;for(let a of t){let s=a.to||"rpx.localhost",f=a.cleanUrls||!1,c=a.path,d=Le(c),h=pe(a.auth);if(a.redirect){let m=$e(a.redirect);n.push({host:s,path:c,route:{redirect:m,basePath:d,auth:h}}),o("proxies",`Route: ${s}${c??""} → redirect ${m.status} ${m.to}${h?" (auth)":""}`,r)}else if(a.static)n.push({host:s,path:c,route:{static:Te(a.static,f),cleanUrls:f,basePath:d,auth:h}}),o("proxies",`Route: ${s}${c??""} → static ${typeof a.static==="string"?a.static:a.static.dir}${h?" (auth)":""}`,r);else{let m=M(a.from),p=new URL(m.startsWith("http")?m:`http://${m}`),g=ae(a.from??p.host,a.loadBalancer);le(g),oe.add(g),n.push({host:s,path:c,route:{sourceHost:p.host,upstreamPool:g,cleanUrls:f,changeOrigin:a.changeOrigin||!1,pathRewrites:a.pathRewrites,basePath:d,auth:h}}),o("proxies",`Route: ${s}${c??""} → ${p.host}${h?" (auth)":""}`,r)}if(l.has(s))continue;if(l.add(s),e&&!Ee(s)&&!s.includes("localhost")&&!s.includes("127.0.0.1"))try{if(!(await G([s],r))[0])await N([s],r)}catch{o("hosts",`Could not add hosts entry for ${s}`,r)}}return n}var j=null;function it(){if(y.env.RPX_BIND_HOSTNAME)return y.env.RPX_BIND_HOSTNAME;if(j)return j;try{j=Object.values(de.networkInterfaces()).flat().some((r)=>r&&r.family==="IPv6")?"::":"0.0.0.0"}catch{j="0.0.0.0"}return j}function re(t){let{routeEntries:e,listenPort:r,sslConfig:n,defaultTls:l,maxTlsContexts:a,originGuard:s,verbose:f}=t,c=Ae(e),d=Re((p,g)=>Fe(c,p,g),f),h=s?(p,g)=>s(p)??d(p,g):d,m=Oe(f);try{let p=Bun.serve({port:r,hostname:it(),reusePort:et(),...n?{tls:Array.isArray(n)?He({sni:n,defaultTls:l,maxTlsContexts:a,verbose:f}):Ue({key:n.key,cert:n.cert,ca:n.ca,requestCert:!1,rejectUnauthorized:!1})}:{},fetch(g,v){return h(g,v)},websocket:m,error(g){return o("server",`Shared proxy server error: ${g}`,f),new Response(`Server Error: ${g.message}`,{status:500})}});return W.add(p),o("proxies",`Shared ${n?"HTTPS":"HTTP"} proxy listening on port ${r} for ${c.size} domains`,f),p}catch(p){return o("proxies",`Failed to start shared proxy: ${p}`,f),console.error("Failed to start shared proxy:",p),null}}function Dt(t){if(t?.vitePluginUsage||!t?.verbose)return;if(console.log(""),console.log(` ${R.green(R.bold("rpx"))} ${R.green(`v${Je}`)}`),console.log(` ${R.green("➜")} ${R.dim(t?.from??"")} ${R.dim("➜")} ${R.cyan(t?.ssl?`https://${t?.to}`:`http://${t?.to}`)}`),t?.listenPort!==(t?.ssl?443:80))console.log(` ${R.green("➜")} Listening on port ${t?.listenPort}`);if(t?.cleanUrls)console.log(` ${R.green("➜")} Clean URLs enabled`)}import{readdir as Et,readFile as Lt}from"node:fs/promises";import*as at from"node:path";import*as lt from"node:process";var ct="/etc/rpx/certs",We="/etc/rpx/sites.d";function At(t,e){console.error(`[rpx gateway] SKIPPING malformed fragment ${t}; its host(s) will 404 until fixed: ${e.message}`)}async function pt(t,e=At){let r=[];try{r=(await Et(t)).filter((l)=>l.endsWith(".json")).sort()}catch{return[]}let n=[];for(let l of r)try{let a=JSON.parse(await Lt(at.join(t,l),"utf8"));if(!a||typeof a!=="object"||Array.isArray(a))throw TypeError("fragment is not a JSON object");n.push({file:l,fragment:a})}catch(a){e(l,a instanceof Error?a:Error(String(a)))}return n}function ut(t,e={}){let r=e.onWarning??((v)=>console.warn(`[rpx gateway] ${v}`)),n=[],l=new Set,a=new Map,s=new Set,f=new Set,c,d=e.certsDir??ct,h,m,p=!1;for(let{file:v,fragment:S}of t){for(let P of Array.isArray(S.proxies)?S.proxies:[]){let O=P.id||`${P.to}${P.path??""}`;if(l.has(O)){r(`duplicate route ${O} in ${v} ignored; first declared by ${a.get(O)}`);continue}l.add(O),a.set(O,v),n.push(P)}for(let P of S.onDemandTls?.allowedSuffixes??[])s.add(P);if(c??=S.onDemandTls?.email,S.onDemandTls&&S.onDemandTls.staging!==!0)p=!0;if(S.productionCerts?.certsDir)d=S.productionCerts.certsDir;if(h??=S.acmeChallengeWebroot,S.originGuard&&S.originGuard.header&&S.originGuard.value)if(m??={header:S.originGuard.header,value:S.originGuard.value},S.originGuard.header===m.header&&S.originGuard.value===m.value)for(let P of S.originGuard.hosts??[])f.add(P);else r(`origin-guard secret in ${v} differs from the one already in force; its hosts stay unguarded rather than rejecting all traffic`)}let g={proxies:n,productionCerts:{certsDir:d,certsDirServerNames:[...new Set(n.map((v)=>v.to).filter(Boolean))]},https:!0,hostsManagement:!1,cleanup:{hosts:!1,certs:!1}};if(s.size>0)g.onDemandTls={enabled:!0,allowedSuffixes:[...s],email:c,certsDir:d,staging:!p};if(h)g.acmeChallengeWebroot=h;if(m)g.originGuard={header:m.header,value:m.value,hosts:[...f]};return g}async function dt(t={}){let e=t.sitesDir??We,r=t.verbose??lt.env.RPX_VERBOSE!=="false",n=await pt(e,t.onFragmentError);o("gateway",`merged ${n.length} fragment(s) from ${e}: ${n.map((s)=>s.file).join(", ")||"<none>"}`,r);let a={...ut(n,{certsDir:t.certsDir}),verbose:r,singlePortMode:!0};if(t.https===!1)a.https=!1;if(t.httpPort!==void 0)a.httpPort=t.httpPort;if(t.httpsPort!==void 0)a.httpsPort=t.httpsPort;if(t.localCa)a.localCa=t.localCa;if(t.maxTlsContexts!==void 0)a.maxTlsContexts=t.maxTlsContexts;return a}async function Ft(t={}){let e=await dt(t);if(("proxies"in e&&Array.isArray(e.proxies)?e.proxies:[]).length===0&&!t.localCa)console.warn(`[rpx gateway] no routes found under ${t.sitesDir??We}; every request will answer 404 until a fragment is deployed`);await Y(e)}var ms=Y;export{xr as ACME_CHALLENGE_PREFIX,ct as DEFAULT_GATEWAY_CERTS_DIR,We as DEFAULT_GATEWAY_SITES_DIR,Er as DEFAULT_LOCAL_CA_RENEW_BEFORE_DAYS,Dr as DEFAULT_LOCAL_CA_VALIDITY_DAYS,Sr as DEFAULT_MAX_TLS_CONTEXTS,fo as DNS_PORT,io as DNS_STATE_VERSION,Ge as DefaultPortManager,ao as LEGACY_TLD_RESOLVER_LABELS,Or as LOCAL_CA_COMMON_NAME,Tr as LOCAL_CA_LEAF_CERT_FILENAME,Rr as LOCAL_CA_LEAF_KEY_FILENAME,Gt as MACOS_CA_TRUST_FLAGS,Bt as MACOS_SYSTEM_KEYCHAIN,ke as OnDemandCertManager,Qr as RPX_HOSTS_MARKER,mo as RPX_RESOLVER_MARKER,It as RPX_ROOT_CA_COMMON_NAME,Kt as SHARED_DEV_HOST_CERT_PATH,Vr as SiteSupervisor,Lo as acquireDaemonLock,N as addHosts,hs as authorizeSystemAccess,Ae as buildHostRoutes,He as buildListenerTls,ur as buildRedirectLocation,Zt as buildRegistryTlsProxyOptions,_e as buildSniTlsConfig,br as capTlsContexts,kt as certIncludesSanHostnames,k as checkExistingCertificates,G as checkHosts,X as cleanup,Ce as cleanupCertificates,nr as clearSslConfigCache,R as colors,ne as config,wo as contentLooksLikeRpxResolver,dr as contentTypeFor,we as createOriginGuard,Re as createProxyFetchHandler,Oe as createProxyWebSocketHandler,Gr as createSiteResolver,ae as createUpstreamPool,o as debugLog,ms as default,ne as defaultConfig,Ho as defaultDaemonSpawnCommand,Ve as deriveIdFromTarget,Nr as detectProjectPreset,uo as devDomainsFromHosts,Jt as devSslToSniEntries,oo as dropStaleRpxHostsLines,pr as enforceBasicAuth,ye as ensureDaemonRunning,Ne as ensureLocalCa,tr as ensureRootCA,Kr as escapeHtml,xs as execSudoSync,Ur as expandHome,ws as extractHostname,ro as filterRpxHostsEntries,Ir as findAvailablePort,no as findStaleRpxHosts,or as forceTrustCertificate,Yr as gcStaleEntries,se as generateCertificate,Oo as getDaemonPidPath,Ro as getDaemonRpxDir,jt as getMacosLoginKeychainPath,Wt as getMacosTrustKeychains,Ss as getPrimaryDomain,me as getRegistryDir,Qt as getRootCAPaths,er as getSharedDaemonCertPaths,Qe as getSudoPassword,to as hostsLineMapsHost,ie as httpsConfig,Hr as installLocalCaTrust,sr as isCertTrusted,Eo as isDaemonRunning,xo as isDnsServerRunning,$r as isLikelyHostname,bs as isMultiProxyConfig,vs as isMultiProxyOptions,Wr as isPidAlive,U as isPortInUse,gs as isProcessElevated,qt as isRootCaFingerprintInKeychains,Yt as isRootCaTrustedForSsl,$s as isSingleProxyConfig,Cs as isSingleProxyOptions,ge as isValidId,Ps as isValidRootCA,Ee as isWildcardPattern,Fr as leafRenewalReason,zt as listCertSha256HashesByCommonName,Br as listDiscoverableSites,rr as loadSSLConfig,Lr as localCaPaths,lr as markFailure,ar as markSuccess,yr as matchHost,Pr as matchHostList,Fe as matchHostRoute,Cr as matchesAllowedSuffix,I as matchesWildcard,ut as mergeGatewayFragments,lo as normalizeDevDomain,Le as normalizePathPrefix,Ht as normalizeSha256Fingerprint,eo as parseHostsLine,cr as parseHtpasswd,Ar as parseSanNames,Nt as parseSha256HashesFromSecurityListing,wr as pathPrefixMatches,jr as portManager,M as primaryUpstreamUrl,kr as projectNameFromHost,Xt as pruneStaleRootCas,De as readAcmeChallenge,Xr as readAll,_t as readCertCommonName,Ut as readCertSha256Fingerprint,Do as readDaemonPid,zr as readEntry,pt as readGatewayFragments,Mr as readSiteManifest,_o as reconcileDevelopmentDnsOnIdle,To as reconcileStaleDevelopmentDns,ys as redactSensitive,Ao as releaseDaemonLock,xe as removeEntry,Be as removeHosts,So as removeLegacyTldResolvers,$o as removeResolver,so as removeStaleRpxHosts,Zr as renderFailedPage,Jr as renderStartingPage,pe as resolveAuth,dt as resolveGatewayOptions,Me as resolveLocalCaConfig,Ts as resolvePathRewrite,$e as resolveRedirect,mr as resolveStaticFile,Te as resolveStaticRoute,co as resolverBasenameForDomain,po as resolverBasenamesForDomains,yo as resolverFilePath,Fo as runDaemon,K as runViaDaemon,Rs as safeDeleteFile,fr as safeRelativePath,F as safeStringify,ir as selectUpstream,gr as serveStaticFile,vr as serverNameFromCertFilename,bo as setupDevelopmentDns,Po as setupResolver,et as shouldReusePort,_r as siteIdForHost,go as startDnsServer,Ft as startGateway,le as startHealthChecks,Y as startProxies,Rt as startProxy,Ie as startServer,Uo as stopDaemon,ho as stopDnsServer,ce as stopHealthChecks,hr as stripBasePath,vo as syncDevelopmentDnsFromRegistry,Co as tearDownDevelopmentDns,Vt as trustRootCaForBrowsers,Mt as verifyHttpsChain,qr as watchRegistry,Ue as withLowMemoryTls,he as writeEntry};
|